Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db8b824b84 | ||
|
|
b71f62535d | ||
|
|
e9a1e281b9 | ||
|
|
0144408500 | ||
|
|
7660839e40 | ||
|
|
d1ca851ebe | ||
|
|
35a281d443 | ||
|
|
c2690fff47 | ||
|
|
c3a73a41d1 | ||
|
|
b72baa3295 | ||
|
|
73ce22c8a4 | ||
|
|
c4f7e6659f | ||
|
|
0739ae006d | ||
|
|
eaa92e784d | ||
|
|
48589e0da1 | ||
|
|
0044d83801 | ||
|
|
50e917d232 | ||
|
|
ccd14e0462 | ||
|
|
d0c0104546 | ||
|
|
cd34ade638 | ||
|
|
3f91e33a8c | ||
|
|
17cc996288 | ||
|
|
385776dc0f | ||
|
|
e52278c371 | ||
|
|
7ffc8c1eda | ||
|
|
1359615c82 | ||
|
|
7a7a56940c | ||
|
|
bcbe83cb38 | ||
|
|
37f983aee3 | ||
|
|
77de3f2b9d | ||
|
|
f655b54937 | ||
|
|
cac99ebdd4 | ||
|
|
f0d0a1546a | ||
|
|
8e8459fa55 | ||
|
|
d53c55ecb5 | ||
|
|
ea33d61a90 | ||
|
|
2fcc3388dd | ||
|
|
ef7f033c32 | ||
|
|
7aa54dc92e | ||
|
|
d10ad1b413 | ||
|
|
34063a0b84 | ||
|
|
8c9d975d69 | ||
|
|
03b072b894 | ||
|
|
d1bed49808 | ||
|
|
6c3417d9b5 | ||
|
|
ba65226460 | ||
|
|
9c9cd168ee | ||
|
|
abb3d2a8d9 | ||
|
|
637fe8a04b | ||
|
|
be321e95e5 | ||
|
|
ed46c1486c | ||
|
|
c9fcfc6862 | ||
|
|
8495e37566 |
@@ -15,7 +15,8 @@ jobs:
|
||||
executor: docker
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
- setup_remote_docker:
|
||||
version: 19.03.13
|
||||
- run:
|
||||
name: Build
|
||||
command: |
|
||||
|
||||
2
.github/workflows/nodejs.yml
vendored
2
.github/workflows/nodejs.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x]
|
||||
node-version: [12.x, 14.x, 15.x]
|
||||
|
||||
services:
|
||||
postgres:
|
||||
|
||||
@@ -1 +1 @@
|
||||
v14.4.0
|
||||
v14.15.0
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
FROM node:14.4.0-alpine AS base
|
||||
FROM node:14.15.0-alpine AS base
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN npm i -g npm@latest
|
||||
|
||||
WORKDIR /misskey
|
||||
|
||||
FROM base AS builder
|
||||
|
||||
@@ -174,6 +174,7 @@ imageUrl: "عنوان URL للصورة"
|
||||
remove: "حذف"
|
||||
removed: "تم حذفه بنجاح"
|
||||
removeAreYouSure: "متأكد من أنك تريد حذف {x}؟"
|
||||
deleteAreYouSure: "متأكد من أنك تريد حذف {x}؟"
|
||||
saved: "تم حفظه"
|
||||
messaging: "الدردشة"
|
||||
upload: "تحميل"
|
||||
@@ -379,6 +380,7 @@ smtpHost: "المضيف"
|
||||
smtpUser: "اسم المستخدم"
|
||||
smtpPass: "الكلمة السرية"
|
||||
display: "المظهر"
|
||||
public: "للعامة"
|
||||
_mfm:
|
||||
mention: "أشر الى"
|
||||
quote: "اقتبس"
|
||||
|
||||
@@ -95,6 +95,7 @@ sensitive: "NSFW"
|
||||
add: "Hinzufügen"
|
||||
reaction: "Reaktionen"
|
||||
reactionSettingDescription: "Gib deine Lieblingsreaktionen ein, um sie der Reaktionsauswahl hinzuzufügen."
|
||||
reactionSettingDescription2: "Ziehen zum Reorganisieren, Klicken zum Löschen."
|
||||
rememberNoteVisibility: "Notizsichtbarkeit merken"
|
||||
attachCancel: "Anhang entfernen"
|
||||
markAsSensitive: "Als NSFW markieren"
|
||||
@@ -124,6 +125,7 @@ settingGuide: "Empfohlene Einstellung"
|
||||
cacheRemoteFiles: "Dateien von anderen Instanzen im Cache speichern"
|
||||
cacheRemoteFilesDescription: "Wenn diese Einstellung deaktiviert ist, werden Dateien anderer Instanzen direkt von dort geladen. Hierdurch wird Speicherplatz gespart, aber mehr Bandbreite verbraucht, da keine Vorschaubilder generiert werden."
|
||||
flagAsBot: "Als Bot markieren"
|
||||
flagAsBotDescription: "Wenn dieser Account durch ein Programm gesteuert wird, setze diesen Haken. Falls aktiviert, agiert es als Flag für andere Entwickler um endlose Kettenreaktionen mit anderen Bots zu verhindern und lässt Misskey's interne Systeme diesen Account als Bot behandeln."
|
||||
flagAsCat: "Als Katze markieren"
|
||||
autoAcceptFollowed: "Follow-Anfragen automatisch akzeptieren"
|
||||
addAcount: "Benutzerkonto hinzufügen"
|
||||
@@ -214,6 +216,8 @@ imageUrl: "Bild-URL"
|
||||
remove: "Löschen"
|
||||
removed: "Erfolgreich gelöscht"
|
||||
removeAreYouSure: "Möchtest du \"{x}\" wirklich löschen?"
|
||||
deleteAreYouSure: "Möchtest du \"{x}\" wirklich löschen?"
|
||||
resetAreYouSure: "Wirklich zurücksetzen?"
|
||||
saved: "Gespeichert"
|
||||
messaging: "Chat"
|
||||
upload: "Hochladen"
|
||||
@@ -313,6 +317,8 @@ bannerUrl: "Banner-URL"
|
||||
basicInfo: "Basisdaten"
|
||||
pinnedUsers: "Angepinnte Benutzer"
|
||||
pinnedUsersDescription: "Gib einen Benutzernamen pro Zeile ein. Diese werden im \"Erkunden\" Tab angezeigt."
|
||||
pinnedPages: "Angepinnte Seiten"
|
||||
pinnedPagesDescription: "Gib hier die Pfäde zu den Seiten an, die du an die Spitze dieser Instanz anheften möchtest, getrennt durch neue Zeilen."
|
||||
hcaptcha: "hCaptcha"
|
||||
enableHcaptcha: "hCaptcha aktivieren"
|
||||
hcaptchaSiteKey: "Site key"
|
||||
@@ -541,6 +547,11 @@ deck: "Deck"
|
||||
undeck: "Deck verlassen"
|
||||
useBlurEffectForModal: "Weichzeichnungseffekt für Modals verwenden"
|
||||
useFullReactionPicker: "Vollständige Reaktionsauswahl nutzen"
|
||||
width: "Breite"
|
||||
height: "Höhe"
|
||||
large: "Groß"
|
||||
medium: "Mittel"
|
||||
small: "Klein"
|
||||
generateAccessToken: "Zugriffstoken generieren"
|
||||
permission: "Berechtigungen"
|
||||
enableAll: "Alle aktivieren"
|
||||
@@ -603,14 +614,59 @@ random: "Zufällig"
|
||||
system: "System"
|
||||
switchUi: "UI wechseln"
|
||||
desktop: "Desktop"
|
||||
clip: "Clip"
|
||||
createNew: "Neu erstellen"
|
||||
optional: "Optional"
|
||||
createNewClip: "Neuen Clip erstellen"
|
||||
public: "Öffentlich"
|
||||
_mfm:
|
||||
cheatSheet: "MFM Spickzettel"
|
||||
intro: "MFM ist eine an vielen Stellen verwendbare und Misskey-exklusive Markup-Sprache. Hier kannst du eine Liste von verfügbarer MFM-Syntax anschauen."
|
||||
dummy: "Misskey erweitert die Welt des Fediverse"
|
||||
mention: "Erwähnung"
|
||||
mentionDescription: "Mit At-Zeichen und Nutzername kann ein individueller Nutzer angegeben werden."
|
||||
hashtag: "Hashtag"
|
||||
hashtagDescription: "Mit einer Raute und Text kann ein Hashtag angegeben werden."
|
||||
url: "URL"
|
||||
urlDescription: "URLs können angezeigt werden."
|
||||
link: "Link"
|
||||
center: "Bestandteile zentrieren"
|
||||
quote: "Zitieren"
|
||||
linkDescription: "Ein spezifizierter Textabschnitt kann als URL angezeigt werden."
|
||||
bold: "Fett"
|
||||
boldDescription: "Zeichen zur Betonung dicker erscheinen lassen."
|
||||
small: "Klein"
|
||||
smallDescription: "Inhalt klein und dünn erscheinen lassen."
|
||||
center: "Zentrieren"
|
||||
centerDescription: "Inhalt zentriert anzeigen lassen."
|
||||
inlineCode: "Code (Eingebettet)"
|
||||
inlineCodeDescription: "Syntax-Hervorhebung für (Programm-)Code eingebettet anzeigen lassen."
|
||||
blockCode: "Code (Block)"
|
||||
blockCodeDescription: "Syntax-Hervorhebung für mehrzeiligen (Programm-)Code als Block anzeigen lassen."
|
||||
inlineMath: "Mathe (Eingebettet)"
|
||||
inlineMathDescription: "Mathematische Formeln (KaTeX) eingebettet anzeigen."
|
||||
blockMath: "Mathe (Block)"
|
||||
blockMathDescription: "Mehrzeilige mathematische Formeln (KaTeX) als Block einbetten."
|
||||
quote: "Zitationen"
|
||||
quoteDescription: "Inhalt als Zitat anzeigen lassen."
|
||||
emoji: "Benutzerdefinierte Emojis"
|
||||
search: "Suchen"
|
||||
emojiDescription: "Emoji-Namen mit Doppelpunkten umschließen, um benutzerdefinierte Emojis anzeigen zu lassen."
|
||||
search: "Suche"
|
||||
searchDescription: "Eine vorgefertige Suchanfragebox anzeigen lassen."
|
||||
flip: "Spiegelung"
|
||||
flipDescription: "Inhalt horizontal oder vertikal gespiegelt anzeigen lassen."
|
||||
jelly: "Animation (Dehnen)"
|
||||
jellyDescription: "Verleiht eine sich dehnende Animation."
|
||||
tada: "Animation (Tada)"
|
||||
tadaDescription: "Verleiht eine Animation mit \"Tada!\"-Gefühl"
|
||||
jump: "Animation (Sprung)"
|
||||
jumpDescription: "Verleiht eine springende Animation."
|
||||
bounce: "Animation (Federn)"
|
||||
bounceDescription: "Erzeugt eine federnde Animation."
|
||||
shake: "Animation (Zittern)"
|
||||
shakeDescription: "Verleiht eine zitternde Animation."
|
||||
twitch: "Animation (Zucken)"
|
||||
twitchDescription: "Verleiht eine sehr stark zuckende Animation."
|
||||
spin: "Animation (Rotieren)"
|
||||
spinDescription: "Verleiht eine rotierende Animation."
|
||||
_reversi:
|
||||
reversi: "Reversi"
|
||||
gameSettings: "Spieleinstellungen"
|
||||
@@ -1028,6 +1084,7 @@ _pages:
|
||||
created: "Seite erfolgreich erstellt"
|
||||
updated: "Seite erfolgreich aktualisiert"
|
||||
deleted: "Seite erfolgreich gelöscht"
|
||||
pageSetting: "Seiteneinstellungen"
|
||||
nameAlreadyExists: "Die angegebene Seiten-URL existiert bereits"
|
||||
invalidNameTitle: "Die angegebene Seiten-URL ist ungültig"
|
||||
invalidNameText: "Überprüfe, ob der Seitentitel nicht leer ist"
|
||||
@@ -1038,7 +1095,9 @@ _pages:
|
||||
unlike: "\"Gefällt mir\" entfernen"
|
||||
my: "Meine Seiten"
|
||||
liked: "Seiten, die mir gefallen"
|
||||
featured: "Beliebt"
|
||||
inspector: "Inspektor"
|
||||
contents: "Inhalt"
|
||||
content: "Inhalt"
|
||||
variables: "Variablen"
|
||||
title: "Titel"
|
||||
@@ -1062,7 +1121,7 @@ _pages:
|
||||
text: "Text"
|
||||
textarea: "Textfeld"
|
||||
section: "Abschnitt"
|
||||
image: "Bilder"
|
||||
image: "Bild"
|
||||
button: "Knopf"
|
||||
if: "Falls"
|
||||
_if:
|
||||
@@ -1077,7 +1136,7 @@ _pages:
|
||||
name: "Variablenname"
|
||||
text: "Titel"
|
||||
default: "Standardwert"
|
||||
textareaInput: "Eingabe des mehrzeiligen Textfelds"
|
||||
textareaInput: "Mehrzeiliges Texteingabefeld"
|
||||
_textareaInput:
|
||||
name: "Variablenname"
|
||||
text: "Titel"
|
||||
@@ -1092,6 +1151,11 @@ _pages:
|
||||
id: "Leinwand-ID"
|
||||
width: "Breite"
|
||||
height: "Höhe"
|
||||
note: "Eingebettete Notiz"
|
||||
_note:
|
||||
id: "Notiz ID"
|
||||
idDescription: "Du kannst alternativ auch die Notiz-URL angeben."
|
||||
detailed: "Detailierte Ansicht"
|
||||
switch: "Fallunterscheidung"
|
||||
_switch:
|
||||
name: "Variablenname"
|
||||
|
||||
@@ -95,6 +95,7 @@ sensitive: "NSFW"
|
||||
add: "Add"
|
||||
reaction: "Reaction"
|
||||
reactionSettingDescription: "Assign your favorite reactions which want to pin in reaction picker."
|
||||
reactionSettingDescription2: "Drag to reorganize, click to delete."
|
||||
rememberNoteVisibility: "Remember note visibility settings"
|
||||
attachCancel: "Remove attachment"
|
||||
markAsSensitive: "Mark as NSFW"
|
||||
@@ -124,6 +125,7 @@ settingGuide: "Suggested Configuration"
|
||||
cacheRemoteFiles: "Cache remote files"
|
||||
cacheRemoteFilesDescription: "When this setting is disabled, remote files are loaded directly from the remote instance. Disabling this will decrease storage usage, but will increase traffic, because thumbnails will not be generated."
|
||||
flagAsBot: "This account is a bot"
|
||||
flagAsBotDescription: "If this account is controlled by a program, set this option. If enabled, it will act as flag for other developers to prevent endless interaction chains with other bots and adjust Misskey's internal systems to treat this account as a bot."
|
||||
flagAsCat: "This account is a cat"
|
||||
autoAcceptFollowed: "Automatically approve follow requests from users you're following"
|
||||
addAcount: "Add Account"
|
||||
@@ -214,6 +216,8 @@ imageUrl: "Image URL"
|
||||
remove: "Delete"
|
||||
removed: "Successfully deleted"
|
||||
removeAreYouSure: "Are you sure that you want to delete \"{x}\"?"
|
||||
deleteAreYouSure: "Are you sure that you want to delete \"{x}\"?"
|
||||
resetAreYouSure: "Really reset?"
|
||||
saved: "Saved"
|
||||
messaging: "Messaging"
|
||||
upload: "Upload"
|
||||
@@ -313,6 +317,8 @@ bannerUrl: "Banner image URL"
|
||||
basicInfo: "Basic info"
|
||||
pinnedUsers: "Pinned user"
|
||||
pinnedUsersDescription: "List one username per line. Users listed here will be pinned under \"Explore\" tab."
|
||||
pinnedPages: "Pinned pages"
|
||||
pinnedPagesDescription: "Enter the paths of the pages you want to pin to the top page of this instance, separated by new lines."
|
||||
hcaptcha: "hCaptcha"
|
||||
enableHcaptcha: "Enable hCaptcha"
|
||||
hcaptchaSiteKey: "Site key"
|
||||
@@ -541,6 +547,11 @@ deck: "Deck"
|
||||
undeck: "Leave Deck"
|
||||
useBlurEffectForModal: "Use blur effect for modals"
|
||||
useFullReactionPicker: "Use full-size reaction picker"
|
||||
width: "Width"
|
||||
height: "Height"
|
||||
large: "Big"
|
||||
medium: "Medium"
|
||||
small: "Small"
|
||||
generateAccessToken: "Generate access token"
|
||||
permission: "Permissions"
|
||||
enableAll: "Enable all"
|
||||
@@ -603,14 +614,59 @@ random: "Random"
|
||||
system: "System"
|
||||
switchUi: "Switch UI"
|
||||
desktop: "Desktop"
|
||||
clip: "Clip"
|
||||
createNew: "Create new"
|
||||
optional: "Optional"
|
||||
createNewClip: "Create new clip"
|
||||
public: "Public"
|
||||
_mfm:
|
||||
cheatSheet: "MFM Cheatsheet"
|
||||
intro: "MFM is a Misskey-exclusive markup language that can be used in many places. Here you can view a list of all available MFM syntax."
|
||||
dummy: "Misskey expands the world of the Fediverse"
|
||||
mention: "Mention"
|
||||
mentionDescription: "Using an At-Symbol and a username, you can specify a specific user."
|
||||
hashtag: "Hashtag"
|
||||
hashtagDescription: "Using a number sign and text, you can specify a hashtag."
|
||||
url: "URL"
|
||||
urlDescription: "URLs can be displayed."
|
||||
link: "Link"
|
||||
center: "Center elements"
|
||||
linkDescription: "Specific parts of text can be displayed as URL."
|
||||
bold: "Bold"
|
||||
boldDescription: "Highlights letters by making them thicker."
|
||||
small: "Small"
|
||||
smallDescription: "Displays contents small and thinn."
|
||||
center: "Center"
|
||||
centerDescription: "Displays content centered."
|
||||
inlineCode: "Code (Inline)"
|
||||
inlineCodeDescription: "Displays inline syntax highlighting for (program-)code."
|
||||
blockCode: "Code (Block)"
|
||||
blockCodeDescription: "Displays syntax highlighting for multi-line (program-)code in a block."
|
||||
inlineMath: "Math (In-line)"
|
||||
inlineMathDescription: "Display math formulas (KaTeX) in-line"
|
||||
blockMath: "Math (Block)"
|
||||
blockMathDescription: "Display multi-line Math formulas (KaTeX) in a block"
|
||||
quote: "Quote"
|
||||
quoteDescription: "Displays content as quote."
|
||||
emoji: "Custom Emoji"
|
||||
emojiDescription: "By surrounding a custom emoji name with colons, custom emoji can be displayed."
|
||||
search: "Search"
|
||||
searchDescription: "Displays a search box with pre-entered text."
|
||||
flip: "Flip"
|
||||
flipDescription: "Flips content horizontally or vertically."
|
||||
jelly: "Animation (Jelly)"
|
||||
jellyDescription: "Infuses a jelly-like animation."
|
||||
tada: "Animation (Tada)"
|
||||
tadaDescription: "Infuses a \"Tada!\"-like animation."
|
||||
jump: "Animation (Jump)"
|
||||
jumpDescription: "Infuses a jumping animation."
|
||||
bounce: "Animation (Bounce)"
|
||||
bounceDescription: "Causes a bouncy animation."
|
||||
shake: "Animation (Shake)"
|
||||
shakeDescription: "Infuses a shaking animation."
|
||||
twitch: "Animation (Twitch)"
|
||||
twitchDescription: "Infuses a strongly twitching animation."
|
||||
spin: "Animation (Spin)"
|
||||
spinDescription: "Infuses a spinning animation."
|
||||
_reversi:
|
||||
reversi: "Reversi"
|
||||
gameSettings: "Game settings"
|
||||
@@ -1028,6 +1084,7 @@ _pages:
|
||||
created: "Successfully created a page!"
|
||||
updated: "Successfully updated the page!"
|
||||
deleted: "The page has been deleted"
|
||||
pageSetting: "Page settings"
|
||||
nameAlreadyExists: "The specified page URL already exists"
|
||||
invalidNameTitle: "The specified page URL is invalid"
|
||||
invalidNameText: "Check whether that is not a blank"
|
||||
@@ -1038,7 +1095,9 @@ _pages:
|
||||
unlike: "Undo like"
|
||||
my: "My pages"
|
||||
liked: "Liked pages"
|
||||
featured: "Featured"
|
||||
inspector: "Inspector"
|
||||
contents: "Content"
|
||||
content: "Page block"
|
||||
variables: "Variables"
|
||||
title: "Title"
|
||||
@@ -1092,6 +1151,11 @@ _pages:
|
||||
id: "Canvas ID"
|
||||
width: "Width"
|
||||
height: "Height"
|
||||
note: "Embedded note"
|
||||
_note:
|
||||
id: "Note ID"
|
||||
idDescription: "You can also paste the Note's URL to set it instead."
|
||||
detailed: "Detailed view"
|
||||
switch: "Switch"
|
||||
_switch:
|
||||
name: "Variable name"
|
||||
|
||||
@@ -214,6 +214,7 @@ imageUrl: "URL de la imágen"
|
||||
remove: "Borrar"
|
||||
removed: "Borrado"
|
||||
removeAreYouSure: "¿Desea borrar \"{x}\"?"
|
||||
deleteAreYouSure: "¿Desea borrar \"{x}\"?"
|
||||
saved: "Guardado"
|
||||
messaging: "Chat"
|
||||
upload: "Subir"
|
||||
@@ -540,6 +541,9 @@ pluginInstallWarn: "Por favor no instale plugins que no son de confianza"
|
||||
deck: "Deck"
|
||||
undeck: "Quitar deck"
|
||||
useBlurEffectForModal: "Usar efecto borroso en modales"
|
||||
useFullReactionPicker: "Reacción"
|
||||
width: "Ancho"
|
||||
height: "Altura"
|
||||
generateAccessToken: "Generar token de acceso"
|
||||
permission: "Permisos"
|
||||
enableAll: "Activar todo"
|
||||
@@ -602,14 +606,24 @@ random: "Aleatorio"
|
||||
system: "Sistema"
|
||||
switchUi: "Cambiar interfaz de usuario"
|
||||
desktop: "Escritorio"
|
||||
public: "Público"
|
||||
_mfm:
|
||||
cheatSheet: "Hoja de referencia de MFM"
|
||||
intro: "MFM es un lenguaje de marcado dedicado que se puede usar en varios lugares dentro de Misskey. Aquí puede ver una lista de sintaxis disponibles en MFM."
|
||||
mention: "Menciones"
|
||||
mentionDescription: "El signo @ seguido de un nombre de usuario se puede utilizar para notificar a un usuario en particular."
|
||||
hashtag: "Hashtag"
|
||||
url: "URL"
|
||||
link: "Vínculo"
|
||||
bold: "Negrita"
|
||||
center: "Centrar"
|
||||
blockCode: "Código (bloque)"
|
||||
blockCodeDescription: "Código de resaltado de sintaxis, como programas de varias líneas con bloques."
|
||||
quote: "Citar"
|
||||
emoji: "Emojis personalizados"
|
||||
search: "Buscar"
|
||||
flip: "Echar de un capirotazo"
|
||||
flipDescription: "Voltea el contenido hacia arriba / abajo o hacia la izquierda / derecha."
|
||||
_reversi:
|
||||
reversi: "Reversi"
|
||||
gameSettings: "Configuración del juego"
|
||||
@@ -1038,6 +1052,7 @@ _pages:
|
||||
my: "Mis páginas"
|
||||
liked: "Páginas que me gustan"
|
||||
inspector: "Inspector"
|
||||
contents: "Contenido"
|
||||
content: "Bloque de página"
|
||||
variables: "Variables"
|
||||
title: "Título"
|
||||
|
||||
@@ -213,6 +213,7 @@ imageUrl: "URL de l’image"
|
||||
remove: "Supprimer"
|
||||
removed: "Supprimé"
|
||||
removeAreYouSure: "Supprimer «{x}» ?"
|
||||
deleteAreYouSure: "Supprimer «{x}» ?"
|
||||
saved: "Enregistré"
|
||||
messaging: "Discuter"
|
||||
upload: "Téléverser"
|
||||
@@ -537,6 +538,8 @@ pluginInstallWarn: "N’installez que des extensions provenant de sources de con
|
||||
deck: "Deck"
|
||||
undeck: "Quitter le deck"
|
||||
useBlurEffectForModal: "Utiliser un effet de flou pour les modals"
|
||||
width: "Largeur"
|
||||
height: "Hauteur"
|
||||
generateAccessToken: "Générer un jeton d'accès"
|
||||
permission: "Autorisations "
|
||||
enableAll: "Tout activer"
|
||||
@@ -580,6 +583,7 @@ setMultipleBySeparatingWithSpace: "Vous pouvez définir plus d’un, séparés p
|
||||
fileIdOrUrl: "ID du fichier ou URL"
|
||||
chatOpenBehavior: "Comportement de la fenêtre de discussion lors de son ouverture"
|
||||
random: "Aléatoire"
|
||||
public: "Public"
|
||||
_mfm:
|
||||
mention: "Mentionner"
|
||||
hashtag: "Hashtags"
|
||||
@@ -939,6 +943,7 @@ _pages:
|
||||
my: "Mes pages"
|
||||
liked: "Pages favorites"
|
||||
inspector: "Inspecteur"
|
||||
contents: "Contenu"
|
||||
content: "Bloc de page"
|
||||
variables: "Variables"
|
||||
title: "Titre"
|
||||
|
||||
@@ -95,6 +95,7 @@ sensitive: "閲覧注意"
|
||||
add: "追加"
|
||||
reaction: "リアクション"
|
||||
reactionSettingDescription: "リアクションピッカーに表示するリアクションを設定します。"
|
||||
reactionSettingDescription2: "ドラッグして並び替えます。クリックして削除します。"
|
||||
rememberNoteVisibility: "公開範囲を記憶する"
|
||||
attachCancel: "添付取り消し"
|
||||
markAsSensitive: "閲覧注意にする"
|
||||
@@ -124,7 +125,9 @@ settingGuide: "おすすめ設定"
|
||||
cacheRemoteFiles: "リモートのファイルをキャッシュする"
|
||||
cacheRemoteFilesDescription: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。サーバーのストレージを節約できますが、サムネイルが生成されないので通信量が増加します。"
|
||||
flagAsBot: "Botとして設定"
|
||||
flagAsBotDescription: "このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Misskeyのシステム上での扱いがBotに合ったものになります。"
|
||||
flagAsCat: "Catとして設定"
|
||||
flagAsCatDescription: "このアカウントが猫であることを示す場合は、このフラグをオンにします。"
|
||||
autoAcceptFollowed: "フォロー中ユーザーからのフォロリクを自動承認"
|
||||
addAcount: "アカウント追加"
|
||||
loginFailed: "ログインに失敗しました"
|
||||
@@ -214,6 +217,8 @@ imageUrl: "画像URL"
|
||||
remove: "削除"
|
||||
removed: "削除しました"
|
||||
removeAreYouSure: "「{x}」を削除しますか?"
|
||||
deleteAreYouSure: "「{x}」を削除しますか?"
|
||||
resetAreYouSure: "リセットしますか?"
|
||||
saved: "保存しました"
|
||||
messaging: "チャット"
|
||||
upload: "アップロード"
|
||||
@@ -313,6 +318,8 @@ bannerUrl: "バナー画像のURL"
|
||||
basicInfo: "基本情報"
|
||||
pinnedUsers: "ピン留めユーザー"
|
||||
pinnedUsersDescription: "「みつける」ページなどにピン留めしたいユーザーを改行で区切って記述します。"
|
||||
pinnedPages: "ピン留めページ"
|
||||
pinnedPagesDescription: "インスタンスのトップページにピン留めしたいページのパスを改行で区切って記述します。"
|
||||
hcaptcha: "hCaptcha"
|
||||
enableHcaptcha: "hCaptchaを有効にする"
|
||||
hcaptchaSiteKey: "サイトキー"
|
||||
@@ -434,6 +441,7 @@ useOsNativeEmojis: "OSネイティブの絵文字を使用"
|
||||
youHaveNoGroups: "グループがありません"
|
||||
joinOrCreateGroup: "既存のグループに招待してもらうか、新しくグループを作成してください。"
|
||||
noHistory: "履歴はありません"
|
||||
signinHistory: "ログイン履歴"
|
||||
disableAnimatedMfm: "動きのあるMFMを無効にする"
|
||||
doing: "やっています"
|
||||
category: "カテゴリ"
|
||||
@@ -486,6 +494,7 @@ none: "なし"
|
||||
showInPage: "ページで表示"
|
||||
popout: "ポップアウト"
|
||||
volume: "音量"
|
||||
masterVolume: "マスター音量"
|
||||
details: "詳細"
|
||||
chooseEmoji: "絵文字を選択"
|
||||
unableToProcess: "操作を完了できません"
|
||||
@@ -541,6 +550,11 @@ deck: "デッキ"
|
||||
undeck: "デッキ解除"
|
||||
useBlurEffectForModal: "モーダルにぼかし効果を使用"
|
||||
useFullReactionPicker: "フル機能リアクションピッカーを使用"
|
||||
width: "幅"
|
||||
height: "高さ"
|
||||
large: "大"
|
||||
medium: "中"
|
||||
small: "小"
|
||||
generateAccessToken: "アクセストークンの発行"
|
||||
permission: "権限"
|
||||
enableAll: "全て有効にする"
|
||||
@@ -553,7 +567,8 @@ useStarForReactionFallback: "リアクション絵文字が不明な場合、代
|
||||
emailConfig: "メールサーバー設定"
|
||||
enableEmail: "メール配信機能を有効化する"
|
||||
emailConfigInfo: "メールアドレスの確認やパスワードリセットの際に使います"
|
||||
email: "メールアドレス"
|
||||
email: "メール"
|
||||
emailAddress: "メールアドレス"
|
||||
smtpConfig: "SMTP サーバーの設定"
|
||||
smtpHost: "ホスト"
|
||||
smtpPort: "ポート"
|
||||
@@ -585,6 +600,7 @@ regenerateLoginTokenDescription: "ログインに使用される内部トーク
|
||||
setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。"
|
||||
fileIdOrUrl: "ファイルIDまたはURL"
|
||||
chatOpenBehavior: "チャットを開くときの動作"
|
||||
behavior: "動作"
|
||||
sample: "サンプル"
|
||||
abuseReports: "通報"
|
||||
reportAbuse: "通報"
|
||||
@@ -603,6 +619,47 @@ random: "ランダム"
|
||||
system: "システム"
|
||||
switchUi: "UI切り替え"
|
||||
desktop: "デスクトップ"
|
||||
clip: "クリップ"
|
||||
createNew: "新規作成"
|
||||
optional: "任意"
|
||||
createNewClip: "新しいクリップを作成"
|
||||
public: "パブリック"
|
||||
i18nInfo: "Misskeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。"
|
||||
manageAccessTokens: "アクセストークンの管理"
|
||||
accountInfo: "アカウント情報"
|
||||
notesCount: "ノートの数"
|
||||
repliesCount: "返信した数"
|
||||
renotesCount: "Renoteした数"
|
||||
repliedCount: "返信された数"
|
||||
renotedCount: "Renoteされた数"
|
||||
followingCount: "フォロー数"
|
||||
followersCount: "フォロワー数"
|
||||
sentReactionsCount: "リアクションした数"
|
||||
receivedReactionsCount: "リアクションされた数"
|
||||
pollVotesCount: "アンケートに投票した数"
|
||||
pollVotedCount: "アンケートに投票された数"
|
||||
yes: "はい"
|
||||
no: "いいえ"
|
||||
driveFilesCount: "ドライブのファイル数"
|
||||
driveUsage: "ドライブ使用量"
|
||||
noCrawle: "クローラーによるインデックスを拒否"
|
||||
noCrawleDescription: "検索エンジンにあなたのユーザーページ、ノート、Pagesなどのコンテンツを登録(インデックス)しないよう要請します。"
|
||||
lockedAccountInfo: "フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。"
|
||||
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする"
|
||||
loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
|
||||
disableShowingAnimatedImages: "アニメーション画像を再生しない"
|
||||
verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。"
|
||||
notSet: "未設定"
|
||||
emailVerified: "メールアドレスが確認されました"
|
||||
noteFavoritesCount: "お気に入りノートの数"
|
||||
pageLikesCount: "Pageにいいねした数"
|
||||
pageLikedCount: "Pageにいいねされた数"
|
||||
reversiCount: "リバーシの対局数"
|
||||
|
||||
_nsfw:
|
||||
respect: "閲覧注意のメディアは隠す"
|
||||
ignore: "閲覧注意のメディアを隠さない"
|
||||
force: "常にメディアを隠す"
|
||||
|
||||
_mfm:
|
||||
cheatSheet: "MFMチートシート"
|
||||
@@ -729,6 +786,8 @@ _theme:
|
||||
manage: "テーマの管理"
|
||||
code: "テーマコード"
|
||||
installed: "{name}をインストールしました"
|
||||
installedThemes: "インストールされたテーマ"
|
||||
builtinThemes: "標準のテーマ"
|
||||
alreadyInstalled: "そのテーマは既にインストールされています"
|
||||
invalid: "テーマの形式が間違っています"
|
||||
make: "テーマを作る"
|
||||
@@ -804,6 +863,8 @@ _sfx:
|
||||
chatBg: "チャット(バックグラウンド)"
|
||||
antenna: "アンテナ受信"
|
||||
channel: "チャンネル通知"
|
||||
reversiPutBlack: "リバーシ: 黒が打ったとき"
|
||||
reversiPutWhite: "リバーシ: 白が打ったとき"
|
||||
|
||||
_ago:
|
||||
unknown: "謎"
|
||||
@@ -983,7 +1044,9 @@ _profile:
|
||||
username: "ユーザー名"
|
||||
description: "自己紹介"
|
||||
youCanIncludeHashtags: "ハッシュタグを含めることができます。"
|
||||
metadata: "補足情報"
|
||||
metadata: "追加情報"
|
||||
metadataEdit: "追加情報を編集"
|
||||
metadataDescription: "プロフィールに表として4つまでの追加情報を表示することができます。"
|
||||
metadataLabel: "ラベル"
|
||||
metadataContent: "内容"
|
||||
|
||||
@@ -1098,6 +1161,7 @@ _pages:
|
||||
created: "ページを作成しました"
|
||||
updated: "ページを更新しました"
|
||||
deleted: "ページを削除しました"
|
||||
pageSetting: "ページ設定"
|
||||
nameAlreadyExists: "指定されたページURLは既に存在しています"
|
||||
invalidNameTitle: "不正なページURLです"
|
||||
invalidNameText: "空白でないか確認してください"
|
||||
@@ -1108,7 +1172,9 @@ _pages:
|
||||
unlike: "いいね解除"
|
||||
my: "自分のページ"
|
||||
liked: "いいねしたページ"
|
||||
featured: "人気"
|
||||
inspector: "インスペクター"
|
||||
contents: "コンテンツ"
|
||||
content: "ページブロック"
|
||||
variables: "変数"
|
||||
title: "タイトル"
|
||||
@@ -1169,6 +1235,12 @@ _pages:
|
||||
width: "幅"
|
||||
height: "高さ"
|
||||
|
||||
note: "ノート埋め込み"
|
||||
_note:
|
||||
id: "ノートID"
|
||||
idDescription: "ノートURLをペーストして設定することもできます。"
|
||||
detailed: "詳細な表示"
|
||||
|
||||
switch: "スイッチ"
|
||||
_switch:
|
||||
name: "変数名"
|
||||
|
||||
@@ -214,6 +214,7 @@ imageUrl: "画像URL"
|
||||
remove: "ほかす"
|
||||
removed: "削除したで!"
|
||||
removeAreYouSure: "「{x}」はなおしてしもてええか?"
|
||||
deleteAreYouSure: "「{x}」はなおしてしもてええか?"
|
||||
saved: "保存したで!"
|
||||
messaging: "チャット"
|
||||
upload: "アップロード"
|
||||
|
||||
@@ -57,6 +57,7 @@ _exportOrImport:
|
||||
blockingList: "Seḥbes"
|
||||
userLists: "Tibdarin"
|
||||
_pages:
|
||||
contents: "Agbur"
|
||||
font: "Tasefsit"
|
||||
fontSerif: "Serif"
|
||||
fontSansSerif: "Sans Serif"
|
||||
|
||||
@@ -16,6 +16,9 @@ noNotes: "노트가 없습니다"
|
||||
noNotifications: "표시할 알림이 없습니다"
|
||||
instance: "인스턴스"
|
||||
settings: "설정"
|
||||
basicSettings: "기본 설정"
|
||||
otherSettings: "기타 설정"
|
||||
openInWindow: "창으로 열기"
|
||||
profile: "프로필"
|
||||
timeline: "타임라인"
|
||||
noAccountDescription: "자기소개가 없습니다"
|
||||
@@ -40,6 +43,7 @@ deleteAndEditConfirm: "이 노트를 삭제한 뒤 다시 편집하시겠습니
|
||||
addToList: "리스트에 추가"
|
||||
sendMessage: "메시지 보내기"
|
||||
copyUsername: "유저명 복사"
|
||||
searchUser: "사용자 검색"
|
||||
reply: "답글"
|
||||
loadMore: "더 보기"
|
||||
youGotNewFollower: "새로운 팔로워가 있습니다"
|
||||
@@ -66,7 +70,11 @@ followers: "팔로워"
|
||||
followsYou: "당신을 팔로우합니다"
|
||||
createList: "리스트 만들기"
|
||||
manageLists: "리스트 관리"
|
||||
error: "오류"
|
||||
somethingHappened: "오류가 발생했습니다"
|
||||
retry: "다시 시도"
|
||||
pageLoadError: "페이지를 불러오지 못했습니다."
|
||||
pageLoadErrorDescription: "네트워크 연결 또는 브라우저 캐시로 인해 발생했을 가능성이 높습니다. 캐시를 삭제하거나, 잠시 후 다시 시도해 주세요."
|
||||
enterListName: "리스트 이름을 입력"
|
||||
privacy: "프라이버시"
|
||||
makeFollowManuallyApprove: "팔로우를 수동으로 승인"
|
||||
@@ -87,6 +95,7 @@ sensitive: "열람주의"
|
||||
add: "추가"
|
||||
reaction: "리액션"
|
||||
reactionSettingDescription: "리액션 선택 상자에 표시할 리액션을 설정합니다."
|
||||
reactionSettingDescription2: "드래그하여 순서를 바꿉니다. 클릭하면 삭제됩니다."
|
||||
rememberNoteVisibility: "공개 범위를 기억하기"
|
||||
attachCancel: "첨부 취소"
|
||||
markAsSensitive: "열람주의로 설정"
|
||||
@@ -105,6 +114,8 @@ unsuspendConfirm: "이 계정의 정지를 해제하시겠습니까?"
|
||||
selectList: "리스트 선택"
|
||||
selectAntenna: "안테나 선택"
|
||||
selectWidget: "위젯 선택"
|
||||
editWidgets: "위젯 편집"
|
||||
editWidgetsExit: "편집 종료"
|
||||
customEmojis: "커스텀 이모지"
|
||||
emoji: "이모지"
|
||||
emojiName: "이모지 이름"
|
||||
@@ -114,6 +125,7 @@ settingGuide: "추천 설정"
|
||||
cacheRemoteFiles: "리모트 파일을 캐시"
|
||||
cacheRemoteFilesDescription: "이 설정을 해지하면 리모트 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라 서버의 저장 공간을 절약할 수 있지만, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다."
|
||||
flagAsBot: "나는 봇입니다"
|
||||
flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 봇이 이를 참고하여 봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다."
|
||||
flagAsCat: "나는 고양이다냥"
|
||||
autoAcceptFollowed: "팔로우 중인 유저로부터의 팔로우 요청을 자동 수락"
|
||||
addAcount: "계정 추가"
|
||||
@@ -204,6 +216,8 @@ imageUrl: "이미지 URL"
|
||||
remove: "삭제"
|
||||
removed: "삭제하였습니다"
|
||||
removeAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?"
|
||||
deleteAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?"
|
||||
resetAreYouSure: "초기화 하시겠습니까?"
|
||||
saved: "저장하였습니다"
|
||||
messaging: "대화"
|
||||
upload: "업로드"
|
||||
@@ -262,6 +276,7 @@ rename: "이름 변경"
|
||||
avatar: "아바타"
|
||||
banner: "배너"
|
||||
nsfw: "열람주의"
|
||||
whenServerDisconnected: "서버와의 접속이 끊겼을 때"
|
||||
disconnectedFromServer: "서버와의 연결이 끊어졌습니다"
|
||||
reload: "새로고침"
|
||||
doNothing: "무시하기"
|
||||
@@ -302,6 +317,8 @@ bannerUrl: "배너 이미지 URL"
|
||||
basicInfo: "기본 정보"
|
||||
pinnedUsers: "고정된 유저"
|
||||
pinnedUsersDescription: "\"발견하기\" 페이지 등에 고정하고 싶은 유저를 한 줄에 한 명씩 적습니다."
|
||||
pinnedPages: "고정한 페이지"
|
||||
pinnedPagesDescription: "인스턴스의 대문에 고정하고 싶은 페이지의 경로를 한 줄에 하나씩 적습니다."
|
||||
hcaptcha: "hCaptcha"
|
||||
enableHcaptcha: "hCaptcha 활성화"
|
||||
hcaptchaSiteKey: "사이트 키"
|
||||
@@ -440,6 +457,7 @@ remote: "리모트"
|
||||
total: "합계"
|
||||
weekOverWeekChanges: "지난주보다"
|
||||
dayOverDayChanges: "어제보다"
|
||||
clientSettings: "클라이언트 설정"
|
||||
accountSettings: "계정 설정"
|
||||
promotion: "프로모션"
|
||||
promote: "프로모션하기"
|
||||
@@ -462,6 +480,7 @@ objectStorageUseSSL: "SSL 사용"
|
||||
objectStorageUseSSLDesc: "API 호출시 HTTPS 를 사용하지 않는 경우 OFF 로 설정해 주세요"
|
||||
objectStorageUseProxy: "연결에 프록시를 사용"
|
||||
objectStorageUseProxyDesc: "오브젝트 스토리지 API 호출시 프록시를 사용하지 않는 경우 OFF 로 설정해 주세요"
|
||||
objectStorageSetPublicRead: "업로드할 때 'public-read'를 설정하기"
|
||||
serverLogs: "서버 로그"
|
||||
deleteAll: "모두 삭제"
|
||||
showFixedPostForm: "타임라인 상단에 글 작성란을 표시"
|
||||
@@ -469,6 +488,8 @@ newNoteRecived: "새 노트가 있습니다"
|
||||
sounds: "소리"
|
||||
listen: "듣기"
|
||||
none: "없음"
|
||||
showInPage: "페이지로 보기"
|
||||
popout: "새 창으로 열기"
|
||||
volume: "음량"
|
||||
details: "자세히"
|
||||
chooseEmoji: "이모지 선택"
|
||||
@@ -523,11 +544,18 @@ plugins: "플러그인"
|
||||
pluginInstallWarn: "신뢰할 수 없는 플러그인은 설치하지 마십시오."
|
||||
deck: "덱"
|
||||
undeck: "덱 해제"
|
||||
width: "폭"
|
||||
height: "높이"
|
||||
large: "크게"
|
||||
medium: "보통"
|
||||
small: "작게"
|
||||
generateAccessToken: "액세스 토큰 생성"
|
||||
permission: "권한"
|
||||
enableAll: "전체 선택"
|
||||
disableAll: "전체 해제"
|
||||
tokenRequested: "계정 접근 허용"
|
||||
pluginTokenRequestedDescription: "이 플러그인은 여기서 설정한 권한을 사용할 수 있게 됩니다."
|
||||
notificationType: "알림 유형"
|
||||
edit: "편집"
|
||||
useStarForReactionFallback: "알 수 없는 리액션 이모지 대신 ★ 사용"
|
||||
emailConfig: "메일 서버 설정"
|
||||
@@ -541,23 +569,93 @@ smtpPass: "비밀번호"
|
||||
emptyToDisableSmtpAuth: "SMTP 인증을 사용하지 않으려면 공란으로 비워둡니다."
|
||||
smtpSecure: "SMTP 연결에 Implicit SSL/TTS 사용"
|
||||
smtpSecureInfo: "STARTTLS 사용 시에는 해제합니다."
|
||||
testEmail: "이메일 전송 테스트"
|
||||
wordMute: "단어 뮤트"
|
||||
makeActive: "활성화"
|
||||
display: "표시"
|
||||
copy: "복사"
|
||||
overview: "요약"
|
||||
logs: "로그"
|
||||
delayed: "지연"
|
||||
database: "데이터베이스"
|
||||
channel: "채널"
|
||||
create: "생성"
|
||||
notificationSetting: "알림 설정"
|
||||
notificationSettingDesc: "표시할 알림의 종류를 선택해 주세요."
|
||||
useGlobalSettingDesc: "활성화하면 계정의 알림 설정이 적용되니다. 비활성화하면 개별적으로 설정할 수 있게 됩니다."
|
||||
other: "기타"
|
||||
regenerateLoginToken: "로그인 토큰을 재생성"
|
||||
regenerateLoginTokenDescription: "로그인할 때 사용되는 내부 토큰을 재생성합니다. 일반적으로 이 작업을 실행할 필요는 없습니다. 이 기능을 사용하면 이 계정으로 로그인한 모든 기기에서 로그아웃됩니다."
|
||||
setMultipleBySeparatingWithSpace: "공백으로 구분하여 여러 개 설정할 수 있습니다."
|
||||
fileIdOrUrl: "파일 ID 또는 URL"
|
||||
chatOpenBehavior: "대화를 열 때의 동작"
|
||||
sample: "예시"
|
||||
abuseReports: "신고"
|
||||
reportAbuse: "신고"
|
||||
reportAbuseOf: "{name}을 신고하기"
|
||||
fillAbuseReportDescription: "신고하려는 이유를 자세히 알려주세요. 특정 게시물을 신고할 때에는 게시물의 URL도 포함해 주세요."
|
||||
abuseReported: "신고를 보냈습니다. 신고해 주셔서 감사합니다."
|
||||
send: "전송"
|
||||
random: "랜덤"
|
||||
switchUi: "UI 전환"
|
||||
desktop: "데스크탑"
|
||||
clip: "클립"
|
||||
createNew: "새로 만들기"
|
||||
optional: "옵션"
|
||||
createNewClip: "새 클립 만들기"
|
||||
public: "공개"
|
||||
_mfm:
|
||||
cheatSheet: "MFM 도움말"
|
||||
intro: "MFM는 Misskey의 다양한 곳에서 사용할 수 있는 전용 마크업 언어입니다. 여기에서는 MFM에서 사용할 수 있는 구문을 확인할 수 있습니다."
|
||||
mention: "멘션"
|
||||
mentionDescription: "골뱅이표(@) 뒤에 사용자명을 넣어 특정 유저를 나타낼 수 있습니다."
|
||||
hashtag: "해시태그"
|
||||
hashtagDescription: "샵 또는 우물정자(#)를 앞에 붙여서 해시태그를 나타낼 수 있습니다."
|
||||
url: "URL"
|
||||
urlDescription: "URL을 나타낼 수 있습니다."
|
||||
link: "링크"
|
||||
boldDescription: "문자를 굵게 강조합니다."
|
||||
smallDescription: "내용을 작고 연하게 보이게 합니다."
|
||||
center: "가운데 정렬"
|
||||
centerDescription: "내용을 가운데 정렬로 보이게 합니다."
|
||||
inlineMathDescription: "수식(KaTeX)를 인라인으로 보이게 합니다."
|
||||
blockMathDescription: "여러 줄의 수식(KaTeX)를 블록으로 보이게 합니다."
|
||||
quote: "인용"
|
||||
emoji: "커스텀 이모지"
|
||||
emojiDescription: "커스텀 이모지의 이름을 쌍점(:)으로 감싸서 커스텀 이모지를 사용합니다."
|
||||
search: "검색"
|
||||
searchDescription: "주어진 키워드가 입력된 검색창을 보이게 합니다."
|
||||
_reversi:
|
||||
reversi: "리버시"
|
||||
gameSettings: "대국 설정"
|
||||
chooseBoard: "보드 선택"
|
||||
blackOrWhite: "선공/후공"
|
||||
blackIs: "{name}님이 흑(선공)"
|
||||
rules: "규칙"
|
||||
botSettings: "Bot 설정"
|
||||
thisGameIsStartedSoon: "잠시 후에 대국이 시작됩니다"
|
||||
waitingForOther: "상대의 준비가 완료될 때까지 기다리고 있습니다"
|
||||
waitingForMe: "당신의 준비 완료를 기다리고 있습니다"
|
||||
myTurn: "당신의 차례입니다"
|
||||
turnOf: "{name}님의 차례입니다"
|
||||
pastTurnOf: "{name}님의 차례"
|
||||
surrender: "기권"
|
||||
surrendered: "기권에 의해"
|
||||
drawn: "무승부"
|
||||
won: "{name}님의 승리"
|
||||
total: "합계"
|
||||
turnCount: "{count}턴 째"
|
||||
myGames: "내 대국"
|
||||
allGames: "모두의 대국"
|
||||
ended: "종료"
|
||||
playing: "지금 대국 중"
|
||||
isLlotheo: "돌이 적은 사람이 승리 (llotheo)"
|
||||
loopedMap: "루프 지도"
|
||||
canPutEverywhere: "어디에나 놓을 수 있음"
|
||||
_instanceTicker:
|
||||
none: "보이지 않음"
|
||||
remote: "리모트 유저에게만 보이기"
|
||||
always: "항상 보이기"
|
||||
_channel:
|
||||
create: "채널 생성"
|
||||
setBanner: "배너 설정"
|
||||
@@ -567,10 +665,11 @@ _channel:
|
||||
usersCount: "{n}명 참여 중"
|
||||
notesCount: "{n}노트"
|
||||
_sidebar:
|
||||
icon: "아바타"
|
||||
icon: "아이콘"
|
||||
hide: "숨기기"
|
||||
_wordMute:
|
||||
muteWords: "뮤트할 단어"
|
||||
muteWordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다。"
|
||||
mutedNotes: "뮤트된 노트"
|
||||
_theme:
|
||||
explore: "테마 찾아보기"
|
||||
@@ -888,6 +987,7 @@ _pages:
|
||||
my: "내 페이지"
|
||||
liked: "좋아요한 페이지"
|
||||
inspector: "인스펙터"
|
||||
contents: "콘텐츠"
|
||||
content: "페이지 블록"
|
||||
variables: "변수"
|
||||
title: "제목"
|
||||
@@ -941,6 +1041,8 @@ _pages:
|
||||
id: "캔버스 ID"
|
||||
width: "폭"
|
||||
height: "높이"
|
||||
_note:
|
||||
detailed: "세부 정보 보기"
|
||||
switch: "스위치"
|
||||
_switch:
|
||||
name: "변수명"
|
||||
|
||||
@@ -11,7 +11,7 @@ ok: "Окей"
|
||||
gotIt: "Ясно!"
|
||||
cancel: "Отмена"
|
||||
enterUsername: "Введите имя пользователя"
|
||||
renotedBy: "{user} передаёт"
|
||||
renotedBy: "{user} делится"
|
||||
noNotes: "Нет ни одной заметки"
|
||||
noNotifications: "Нет ни одного уведомления"
|
||||
instance: "Инстанс"
|
||||
@@ -95,6 +95,7 @@ sensitive: "Содержимое не для всех"
|
||||
add: "Добавить"
|
||||
reaction: "Реакции"
|
||||
reactionSettingDescription: "Подберите, что будет у вас в палитре реакций"
|
||||
reactionSettingDescription2: "Меняйте порядок перетаскиванием. Удаляйте нажатием."
|
||||
rememberNoteVisibility: "Запоминать видимость заметок"
|
||||
attachCancel: "Удалить вложение"
|
||||
markAsSensitive: "Отметить как «не для всех»"
|
||||
@@ -124,6 +125,7 @@ settingGuide: "Рекомендуемые настройки"
|
||||
cacheRemoteFiles: "Кешировать внешние файлы"
|
||||
cacheRemoteFilesDescription: "Когда эта настройка отключена, файлы с других сайтов будут загружаться прямо оттуда. Это сэкономит место на сервере, но увеличит трафик, так как не будут создаваться эскизы."
|
||||
flagAsBot: "Аккаунт бота"
|
||||
flagAsBotDescription: "Включите, если этот аккаунт управляется программой. Это позволит системе Misskey учитывать это, а также поможет разработчикам других ботов предотвратить бесконечные циклы взаимодействия."
|
||||
flagAsCat: "Аккаунт кота"
|
||||
autoAcceptFollowed: "Принимать подписчиков автоматически"
|
||||
addAcount: "Добавить аккаунт"
|
||||
@@ -205,7 +207,7 @@ newPassword: "Новый пароль"
|
||||
newPasswordRetype: "Новый пароль (ещё раз)"
|
||||
attachFile: "Прикрепить файлы"
|
||||
more: "Ещё!"
|
||||
featured: "Подборка"
|
||||
featured: "Горячее"
|
||||
usernameOrUserId: "Имя или идентификатор пользователя"
|
||||
noSuchUser: "Таких пользователей не найдено"
|
||||
lookup: "Запрос"
|
||||
@@ -214,6 +216,8 @@ imageUrl: "Ссылка на изображение"
|
||||
remove: "Удалить"
|
||||
removed: "Удалено"
|
||||
removeAreYouSure: "Хотите удалить «{x}»?"
|
||||
deleteAreYouSure: "Хотите удалить «{x}»?"
|
||||
resetAreYouSure: "На самом деле сбросить?"
|
||||
saved: "Сохранено"
|
||||
messaging: "Сообщения"
|
||||
upload: "Загрузить"
|
||||
@@ -266,7 +270,7 @@ unableToDelete: "Удаление невозможно"
|
||||
inputNewFileName: "Введите имя нового файла"
|
||||
inputNewFolderName: "Пожалуйста, введите новое имя папки!"
|
||||
circularReferenceFolder: "Вы пытаетесь переместить папку внутрь себя."
|
||||
hasChildFilesOrFolders: "В этой папке что-то есть, так что она не может быть удалена."
|
||||
hasChildFilesOrFolders: "Эта папка не пуста и не может быть удалена."
|
||||
copyUrl: "Копировать ссылку"
|
||||
rename: "Переименовать"
|
||||
avatar: "Аватар"
|
||||
@@ -313,6 +317,8 @@ bannerUrl: "Ссылка на изображение в шапке"
|
||||
basicInfo: "Общая информация"
|
||||
pinnedUsers: "Прикреплённый пользователь"
|
||||
pinnedUsersDescription: "Перечислите по одному имени пользователя в строке. Пользователи, перечисленные здесь, будут привязаны к закладке \"Изучение\"."
|
||||
pinnedPages: "Закрепленные страницы"
|
||||
pinnedPagesDescription: "Если хотите закрепить страницы на главной сайта, сюда можно добавить пути к ним, каждый в отдельной строке."
|
||||
hcaptcha: "hCaptcha"
|
||||
enableHcaptcha: "Включить hCaptcha"
|
||||
hcaptchaSiteKey: "Ключ сайта"
|
||||
@@ -541,6 +547,11 @@ deck: "Пульт"
|
||||
undeck: "Покинуть пульт"
|
||||
useBlurEffectForModal: "Размывка под формой поверх всего"
|
||||
useFullReactionPicker: "Полнофункциональный выбор реакций"
|
||||
width: "Ширина"
|
||||
height: "Высота"
|
||||
large: "Крупно"
|
||||
medium: "Средне"
|
||||
small: "Мелко"
|
||||
generateAccessToken: "Создать токен доступа"
|
||||
permission: "Разрешения"
|
||||
enableAll: "Включить все"
|
||||
@@ -603,14 +614,59 @@ random: "Случайные"
|
||||
system: "Система"
|
||||
switchUi: "Выбор вида"
|
||||
desktop: "Стол"
|
||||
clip: "Памятки"
|
||||
createNew: "Новый документ"
|
||||
optional: "Необязательно"
|
||||
createNewClip: "Новая памятка"
|
||||
public: "Общедоступно"
|
||||
_mfm:
|
||||
cheatSheet: "Подсказка по разметке MFM"
|
||||
intro: "MFM — язык оформления текста, придуманный специально для Misskey, который здесь можно много где использовать. На этой странице собраны и кратко изложены способы его применения."
|
||||
dummy: "Misskey расширяет границы Федиверса."
|
||||
mention: "Упоминание"
|
||||
mentionDescription: "При помощи знака «собака» перед именем можно упомянуть какого-нибудь пользователя."
|
||||
hashtag: "Хэштег"
|
||||
link: "Ссылка"
|
||||
hashtagDescription: "При помощи знака «решётка» перед словом задаётся хэштег."
|
||||
url: "Простая ссылка (URL)"
|
||||
urlDescription: "Ссылки могут отображаться непосредственно."
|
||||
link: "Ссылка с пояснением"
|
||||
linkDescription: "Можно ссылку оформить в виде произвольного текста."
|
||||
bold: "Жирный шрифт"
|
||||
boldDescription: "Выделяет текст, делая буквы жирнее."
|
||||
small: "Мелкий шрифт"
|
||||
smallDescription: "Делает текст маленьким и незаметным."
|
||||
center: "Выровнять элементы по центру"
|
||||
centerDescription: "Так можно выровнять что-то по центру."
|
||||
inlineCode: "Программа (в тексте)"
|
||||
inlineCodeDescription: "Подсвечивает фрагмент программы внутри сплошного текста."
|
||||
blockCode: "Программа (блок)"
|
||||
blockCodeDescription: "Оформляет текст программы в виде отдельного блокоа. Он может состоять из множества строк."
|
||||
inlineMath: "Математическое выражение (в тексте)"
|
||||
inlineMathDescription: "Позволяет вставлять математические выражения внутрь текста при помощи языка KaTeX."
|
||||
blockMath: "Математическое выражение (блок)"
|
||||
blockMathDescription: "Оформляет математическое выражение (KaTeX) на отдельной строке."
|
||||
quote: "Цитата"
|
||||
quoteDescription: "Так можно процитировать чей-то текст."
|
||||
emoji: "Эмодзи пользователя"
|
||||
emojiDescription: "Можно вставить эмодзи в текст, окружив название двоеточиями."
|
||||
search: "Поиск"
|
||||
searchDescription: "Можно добавить форму для поиска, сразу задав, что искать."
|
||||
flip: "Переворот"
|
||||
flipDescription: "Позволяет отразить текст зеркально по вертикали или горизонтали."
|
||||
jelly: "Анимация желе (шлёп-плёп)"
|
||||
jellyDescription: "Напоминает горку джема, дёргающуюся от шлепков."
|
||||
tada: "Анимация (та-дам!)"
|
||||
tadaDescription: "Получается нечто выпрыгивающее, как бы крича: «а вот и я!»"
|
||||
jump: "Анимация прыжков (прыг-скок)"
|
||||
jumpDescription: "Побуждает радостно подпрыгивать."
|
||||
bounce: "Анимация отскоков (бум-бум)"
|
||||
bounceDescription: "Это будет скакать как мяч."
|
||||
shake: "Анимация дрожи (б-р-р-р)"
|
||||
shakeDescription: "Такое дрожит, словно от холода. Или от страха."
|
||||
twitch: "Анимация тряски"
|
||||
twitchDescription: "Заставляет трястись как одержимого"
|
||||
spin: "Вращение"
|
||||
spinDescription: "Так можно крутить содержимое в разных направлениях."
|
||||
_reversi:
|
||||
reversi: "Реверси"
|
||||
gameSettings: "Настройки игры"
|
||||
@@ -657,9 +713,9 @@ _channel:
|
||||
edit: "Редактировать канал"
|
||||
setBanner: "Установить баннер"
|
||||
removeBanner: "Удалить баннер"
|
||||
featured: "Из подборки"
|
||||
owned: "Владелец"
|
||||
following: "Читаю"
|
||||
featured: "Актуальные"
|
||||
owned: "Собственные"
|
||||
following: "Подписки"
|
||||
usersCount: "Участников: {n}"
|
||||
notesCount: "Заметок: {n}"
|
||||
_sidebar:
|
||||
@@ -773,10 +829,10 @@ _time:
|
||||
_tutorial:
|
||||
title: "Как пользоваться Misskey"
|
||||
step1_1: "Добро пожаловать!"
|
||||
step1_2: "Эта страница называется «лента». Здесь будут появляться ваши «заметки» и тех, на кого вы «подписаны», и располагаться в порядке времени их появления."
|
||||
step1_2: "Эта страница называется «лента». Здесь будут появляться «заметки»: ваши личные и тех, на кого вы «подписаны». Они будут располагаться в порядке времени их появления."
|
||||
step1_3: "Правда, ваша лента пока пуста. Она начнёт заполняться, когда вы будете писать свои заметки и подписываться на других."
|
||||
step2_1: "Давайте, сначала заполним профиль, прежде чем начать писать заметки и подписываться на других."
|
||||
step2_2: "То, что вы расскажете в профиле, поможет многим лучше вас узнать, а значит, им будет легче присоединиться к вам — подписаться и читать заметки."
|
||||
step2_1: "Давайте, заполним профиль, прежде чем начать писать заметки и подписываться на других."
|
||||
step2_2: "То, что вы расскажете в профиле, поможет лучше вас узнать, а значит, многим будет легче присоединиться — вы скорее получите новых подписчиков и читателей."
|
||||
step3_1: "Успешно заполнили профиль?"
|
||||
step3_2: "Что ж, теперь самое время опубликуовать заметку. Если нажать вверху страницы на изображение карандаша, появится форма для текста."
|
||||
step3_3: "Напишите в неё, что хотите, и нажмите на кнопку в правом верхнем углу."
|
||||
@@ -784,11 +840,11 @@ _tutorial:
|
||||
step4_1: "С написанием первой заметки покончено?"
|
||||
step4_2: "Отлично, теперь она должна появиться в вашей ленте."
|
||||
step5_1: "А теперь самое время немного оживить ленту, подписавшись на других."
|
||||
step5_2: "На странице «{featured}» собраны популярные сегодня заметки, читая которые, вы можете найти кого-то вам интересного, а на «{explore}» можно посмотреть, кто популярен у остальных."
|
||||
step5_2: "На странице «{featured}» собраны популярные сегодня заметки, читая которые, вы можете найти кого-то вам интересного, а на странице «{explore}» можно посмотреть, кто популярен у остальных."
|
||||
step5_3: "Чтобы подписаться на кого-нибудь, щёлкните по его аватару и в открывшемся профиле нажмите кнопку «Подписаться»."
|
||||
step5_4: "Некоторые пользователи (около их имени «висит замок») вручную подтверждают чужие подписки. Так что иногда подписка начинает работать не сразу.\n"
|
||||
step6_1: "Если теперь в ленте видны и чужие заметки, значит у вас получилось."
|
||||
step6_2: "Можете ставить «реакции» чужим заметкам, чтобы непринуждённо выразить свои чувства к ним."
|
||||
step6_2: "Здесь можно непринуждённо выразить свои чувства к чьей-то заметке, отметив «реакцию» под ней."
|
||||
step6_3: "Отмечайте реакции, нажмая на символ «+» под заметкой и выбирая значок по душе."
|
||||
step7_1: "На этом вводный урок по использованию Misskey закончен. Спасибо, что прошли его до конца!"
|
||||
step7_2: "Хотите изучить Misskey глубже — добро пожаловать в раздел «{help}»."
|
||||
@@ -1028,6 +1084,7 @@ _pages:
|
||||
created: "Страница успешно создана."
|
||||
updated: "Страница успешно обновлена."
|
||||
deleted: "Страница успешно удалена."
|
||||
pageSetting: "Настройки страницы"
|
||||
nameAlreadyExists: "Указанный адрес страницы уже существует."
|
||||
invalidNameTitle: "Указанный адрес страницы недопустим."
|
||||
invalidNameText: "Проверьте, что не оставили поле пустым."
|
||||
@@ -1038,7 +1095,9 @@ _pages:
|
||||
unlike: "Отменить «нравится»"
|
||||
my: "Свои страницы"
|
||||
liked: "Понравившиеся страницы"
|
||||
featured: "Популярные"
|
||||
inspector: "Инспектор"
|
||||
contents: "Содержательные"
|
||||
content: "Содержимое"
|
||||
variables: "Переменные"
|
||||
title: "Заголовок"
|
||||
@@ -1092,6 +1151,11 @@ _pages:
|
||||
id: "Метка холста"
|
||||
width: "Ширина"
|
||||
height: "Высота"
|
||||
note: "Встроенная заметка"
|
||||
_note:
|
||||
id: "Идентификатор заметки"
|
||||
idDescription: "Можно также вставить ссылку на заметку."
|
||||
detailed: "Подробный вид"
|
||||
switch: "Выключатель"
|
||||
_switch:
|
||||
name: "Имя переменной"
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
---
|
||||
_lang_: "Українська"
|
||||
introMisskey: "Ласкаво просимо! Misskey - децентралізована служба мікроблогів з відкритим кодом.\nСтворюйте \"нотатки\", щоб поділитися тим, що відбувається, і розповісти всім про себе 📡\nЗа допомогою \"реакцій\" ви також можете швидко висловити свої почуття щодо нотаток інших 👍\nДавайте досліджувати новий світ 🚀"
|
||||
monthAndDay: "{month}/{day}"
|
||||
search: "Пошук"
|
||||
notifications: "Сповіщення"
|
||||
username: "Ім'я користувача"
|
||||
password: "Пароль"
|
||||
fetchingAsApObject: "Отримуємо з федіверсу..."
|
||||
ok: "OK"
|
||||
gotIt: "Зрозуміло!"
|
||||
cancel: "Скасувати"
|
||||
enterUsername: "Введіть ім'я користувача"
|
||||
renotedBy: "Поширено {user}"
|
||||
noNotes: "Немає дописів"
|
||||
noNotes: "Немає нотаток"
|
||||
noNotifications: "Немає сповіщень"
|
||||
instance: "Інстанс"
|
||||
settings: "Налаштування"
|
||||
@@ -37,13 +39,14 @@ copyContent: "Скопіювати контент"
|
||||
copyLink: "Скопіювати посилання"
|
||||
delete: "Видалити"
|
||||
deleteAndEdit: "Видалити й редагувати"
|
||||
deleteAndEditConfirm: "Ви впевнені, що хочете видалити цю нотатку та відредагувати її? Ви втратите всі реакції, поширення та відповіді на неї."
|
||||
addToList: "Додати до списку"
|
||||
sendMessage: "Надіслати повідомлення"
|
||||
copyUsername: "Скопіювати ім’я користувача"
|
||||
searchUser: "Пошук користувачів"
|
||||
reply: "Відповісти"
|
||||
loadMore: "Показати більше"
|
||||
youGotNewFollower: "У вас новий підписник"
|
||||
youGotNewFollower: "Новий підписник"
|
||||
receiveFollowRequest: "Отримано запит на підписку"
|
||||
followRequestAccepted: "Запит на підписку прийнято"
|
||||
mention: "Згадка"
|
||||
@@ -54,11 +57,14 @@ import: "Імпорт"
|
||||
export: "Експорт"
|
||||
files: "Файли"
|
||||
download: "Завантажити"
|
||||
driveFileDeleteConfirm: "Ви впевнені, що хочете видалити файл {name}? Нотатки із цим файлом також буде видалено."
|
||||
unfollowConfirm: "Ви впевнені, що хочете відписатися від {name}?"
|
||||
exportRequested: "Ви запросили експорт. Це може зайняти деякий час. Після завершення експорту отриманий файл буде додано на диск."
|
||||
importRequested: "Ви запросили імпорт. Це може зайняти деякий час."
|
||||
lists: "Списки"
|
||||
noLists: "Немає списків"
|
||||
note: "Дописи"
|
||||
notes: "Дописи"
|
||||
note: "Нотатки"
|
||||
notes: "Нотатки"
|
||||
following: "Підписки"
|
||||
followers: "Підписники"
|
||||
followsYou: "Підписаний(-а) на вас"
|
||||
@@ -68,10 +74,11 @@ error: "Помилка"
|
||||
somethingHappened: "Щось пішло не так"
|
||||
retry: "Спробувати знову"
|
||||
pageLoadError: "Помилка при завантаженні сторінки"
|
||||
pageLoadErrorDescription: "Зазвичай це пов’язано з помилками мережі або кешем браузера. Очистіть кеш або почекайте трохи й спробуйте ще раз."
|
||||
enterListName: "Введіть назву списку"
|
||||
privacy: "Приватність"
|
||||
makeFollowManuallyApprove: "Підтверджувати підписників уручну"
|
||||
defaultNoteVisibility: "Видимість допису за замовчуванням"
|
||||
defaultNoteVisibility: "Видимість за замовчуванням"
|
||||
follow: "Підписка"
|
||||
followRequest: "Запит на підписку"
|
||||
followRequests: "Запити на підписку"
|
||||
@@ -81,13 +88,15 @@ enterEmoji: "Введіть емодзі"
|
||||
renote: "Поширити"
|
||||
unrenote: "Відміна поширення"
|
||||
quote: "Цитата"
|
||||
pinnedNote: "Закріплений допис"
|
||||
pinnedNote: "Закріплена нотатка"
|
||||
you: "Ви"
|
||||
clickToShow: "Натисніть для перегляду"
|
||||
sensitive: "NSFW"
|
||||
add: "Додати"
|
||||
reaction: "Реакції"
|
||||
rememberNoteVisibility: "Пам’ятати видимість дописів"
|
||||
reactionSettingDescription: "Виберіть свої улюблені реакції, які хочете закріпити в селекторі реакцій."
|
||||
reactionSettingDescription2: "Перетягніть для реорганізації, натисніть для видалення."
|
||||
rememberNoteVisibility: "Пам’ятати параметри видимісті"
|
||||
attachCancel: "Видалити вкладення"
|
||||
markAsSensitive: "Позначити як NSFW"
|
||||
unmarkAsSensitive: "Зняти позначку NSFW"
|
||||
@@ -110,9 +119,11 @@ editWidgetsExit: "Готово"
|
||||
customEmojis: "Кастомні емоджі"
|
||||
emoji: "Емоджі"
|
||||
emojiName: "Назва емоджі"
|
||||
emojiUrl: "URL емодзі"
|
||||
addEmoji: "Додати емодзі"
|
||||
settingGuide: "Рекомендована конфігурація"
|
||||
cacheRemoteFiles: "Кешувати дані з інших інстансів"
|
||||
cacheRemoteFilesDescription: "Якщо кешування вимкнено, віддалені файли завантажуються безпосередньо з віддаленого інстансу. Це зменшує використання сховища, але збільшує трафік, оскільки не генеруются ескізи."
|
||||
flagAsBot: "Акаунт бота"
|
||||
flagAsCat: "Акаунт кота"
|
||||
autoAcceptFollowed: "Автоматично приймати запити на підписку від користувачів, на яких ви підписані"
|
||||
@@ -139,8 +150,9 @@ latestRequestReceivedAt: "Останній запит прийнято"
|
||||
latestStatus: "Останній статус"
|
||||
storageUsage: "Використання простіру"
|
||||
charts: "Графіки"
|
||||
perHour: "Щогодини"
|
||||
perHour: "Щогодинно"
|
||||
perDay: "Щоденно"
|
||||
stopActivityDelivery: "Припинити розсилання активності"
|
||||
blockThisInstance: "Заблокувати цей інстанс"
|
||||
operations: "Операції"
|
||||
software: "Програмне забезпечення"
|
||||
@@ -155,15 +167,20 @@ disk: "Диск"
|
||||
instanceInfo: "Про цей інстанс"
|
||||
statistics: "Статистика"
|
||||
clearQueue: "Очистити чергу"
|
||||
clearQueueConfirmTitle: "Ви впевнені, що хочете очистити чергу?"
|
||||
clearQueueConfirmText: "Будь-які невідправлені нотатки, що залишилися в черзі, не будуть передані. Зазвичай ця операція НЕ потрібна."
|
||||
clearCachedFiles: "Очистити кеш"
|
||||
clearCachedFilesConfirm: "Ви впевнені, що хочете видалити всі кешовані файли?"
|
||||
blockedInstances: "Заблоковані інстанси"
|
||||
blockedInstancesDescription: "Вкажіть інстанси, які потрібно заблокувати. Перелічені інстанси більше не зможуть спілкуватися з цим інстансом."
|
||||
muteAndBlock: "Ігнор і блокування"
|
||||
mutedUsers: "Ігноровані користувачі"
|
||||
blockedUsers: "Заблоковані користувачі"
|
||||
noUsers: "Немає користувачів"
|
||||
editProfile: "Редагувати профіль"
|
||||
noteDeleteConfirm: "Ви дійсно хочете видалити цей допис?"
|
||||
pinLimitExceeded: "Більше дописів не можна закріпити"
|
||||
noteDeleteConfirm: "Ви дійсно хочете видалити цю нотатку?"
|
||||
pinLimitExceeded: "Більше нотаток не можна закріпити"
|
||||
intro: "Встановлення Misskey завершено! Будь ласка, створіть акаунт адміністратора."
|
||||
done: "Готово"
|
||||
processing: "Обробка"
|
||||
preview: "Передогляд"
|
||||
@@ -172,30 +189,53 @@ noCustomEmojis: "Немає кастомних емоджі"
|
||||
noJobs: "Немає завдань"
|
||||
federating: "Федерується"
|
||||
blocked: "Заблоковано"
|
||||
suspended: "Призупинено"
|
||||
all: "Всі"
|
||||
subscribing: "Підписка"
|
||||
publishing: "Публікація"
|
||||
notResponding: "Не відповідає"
|
||||
instanceFollowing: "Підписка на інстанс"
|
||||
instanceFollowers: "Підписники інстансу"
|
||||
instanceUsers: "Користувачі цього інстансу"
|
||||
changePassword: "Змінити пароль"
|
||||
security: "Безпека"
|
||||
retypedNotMatch: "Введені дані не збігаються."
|
||||
currentPassword: "Поточний пароль"
|
||||
newPassword: "Новий пароль"
|
||||
newPasswordRetype: "Новий пароль (повторно)"
|
||||
attachFile: "Вкласти файл"
|
||||
more: "Бiльше!"
|
||||
featured: "Виділено"
|
||||
featured: "Популярні"
|
||||
usernameOrUserId: "Ім'я або ID користувача"
|
||||
noSuchUser: "Користувача не знайдено"
|
||||
lookup: "Пошук"
|
||||
announcements: "Оголошення"
|
||||
imageUrl: "URL зображення"
|
||||
remove: "Видалити"
|
||||
removed: "Видалено"
|
||||
removeAreYouSure: "Ви впевнені, що хочете видалити \"{x}\"?"
|
||||
deleteAreYouSure: "Ви впевнені, що хочете видалити \"{x}\"?"
|
||||
resetAreYouSure: "Дійсно ресет?"
|
||||
saved: "Збережено"
|
||||
messaging: "Чати"
|
||||
upload: "Завантажити"
|
||||
fromDrive: "З диска"
|
||||
fromUrl: "З URL"
|
||||
uploadFromUrl: "Завантажити з URL"
|
||||
uploadFromUrlDescription: "URL-адреса файлу для завантаження"
|
||||
uploadFromUrlRequested: "Обрано завантаження"
|
||||
uploadFromUrlMayTakeTime: "Завантаження може зайняти деякий час."
|
||||
explore: "Огляд"
|
||||
games: "Ігри Misskey"
|
||||
messageRead: "Прочитано"
|
||||
noMoreHistory: "Подальшої історії немає"
|
||||
startMessaging: "Розпочати діалог"
|
||||
nUsersRead: "Прочитали {n}"
|
||||
agreeTo: "Я погоджуюсь з {0}"
|
||||
tos: "Умови використання"
|
||||
start: "Розпочати"
|
||||
home: "Домівка"
|
||||
remoteUserCaution: "Інформація може бути неповною, оскільки це віддалений користувач."
|
||||
activity: "Активність"
|
||||
images: "Зображення"
|
||||
birthday: "День народження"
|
||||
@@ -209,6 +249,7 @@ light: "Світла"
|
||||
dark: "Темна"
|
||||
lightThemes: "Світлі теми"
|
||||
darkThemes: "Темні теми"
|
||||
syncDeviceDarkMode: "Синхронізувати темний режим із налаштуваннями вашого пристрою"
|
||||
drive: "Диск"
|
||||
fileName: "Ім'я файлу"
|
||||
selectFile: "Вибрати файл"
|
||||
@@ -221,17 +262,32 @@ createFolder: "Створити теку"
|
||||
renameFolder: "Перейменувати теку"
|
||||
deleteFolder: "Видалити теку"
|
||||
addFile: "Додати файл"
|
||||
emptyDrive: "Диск порожній"
|
||||
emptyFolder: "Тека порожня"
|
||||
unableToDelete: "Видалення неможливе"
|
||||
inputNewFileName: "Введіть ім'я нового файлу"
|
||||
inputNewFolderName: "Введіть ім'я нової теки"
|
||||
circularReferenceFolder: "Ви намагаєтесь перемістити папку в її підпапку."
|
||||
hasChildFilesOrFolders: "Ця тека не порожня і не може бути видалена"
|
||||
copyUrl: "Копіювати URL"
|
||||
rename: "Перейменувати"
|
||||
avatar: "Аватар"
|
||||
banner: "Банер"
|
||||
nsfw: "NSFW"
|
||||
whenServerDisconnected: "Коли зв’язок із сервером втрачено"
|
||||
disconnectedFromServer: "Зв’язок із сервером було перервано"
|
||||
reload: "Оновити"
|
||||
doNothing: "Нічого не робити"
|
||||
reloadConfirm: "Перезавантажити стрічку?"
|
||||
watch: "Стежити"
|
||||
unwatch: "Не стежити"
|
||||
accept: "Прийняти"
|
||||
reject: "Відхилити"
|
||||
instanceName: "Назва інстансу"
|
||||
instanceDescription: "Описання інстансу"
|
||||
maintainerName: "Ім'я адміністратора"
|
||||
maintainerEmail: "Email адміністратора"
|
||||
tosUrl: "URL умов використання"
|
||||
thisYear: "Рік"
|
||||
thisMonth: "Місяць"
|
||||
today: "День"
|
||||
@@ -239,31 +295,360 @@ dayX: "{day}"
|
||||
monthX: "{month}"
|
||||
yearX: "{year}"
|
||||
pages: "Сторінки"
|
||||
connectSerice: "Під’єднатися"
|
||||
integration: "Інтеграція"
|
||||
connectSerice: "Під’єднати"
|
||||
disconnectSerice: "Відключитися"
|
||||
enableLocalTimeline: "Увімкнути локальну стрічку"
|
||||
enableGlobalTimeline: "Увімкнути глобальну стрічку"
|
||||
disablingTimelinesInfo: "Адміністратори та модератори завжди мають доступ до всіх стрічок, навіть якщо вони вимкнуті."
|
||||
registration: "Реєстрація"
|
||||
enableRegistration: "Дозволити реєстрацію"
|
||||
invite: "Запросити"
|
||||
proxyRemoteFiles: "Проксувати файли з інших інстансів"
|
||||
driveCapacityPerLocalAccount: "Об'єм диска на одного локального користувача"
|
||||
driveCapacityPerRemoteAccount: "Об'єм диска на одного віддаленого користувача"
|
||||
inMb: "В мегабайтах"
|
||||
iconUrl: "URL аватара"
|
||||
bannerUrl: "URL банера"
|
||||
basicInfo: "Основна інформація"
|
||||
pinnedUsers: "Закріплені користувачі"
|
||||
pinnedPages: "Закріплені сторінки"
|
||||
pinnedPagesDescription: "Введіть шляхи сторінок, які ви бажаєте закріпити на головній сторінці цього інстанса, розділені новими рядками."
|
||||
hcaptcha: "hCaptcha"
|
||||
enableHcaptcha: "Увімкнути hCaptcha"
|
||||
hcaptchaSiteKey: "Ключ сайту"
|
||||
hcaptchaSecretKey: "Секретний ключ"
|
||||
recaptcha: "reCAPTCHA"
|
||||
enableRecaptcha: "Увімкнути reCAPTCHA"
|
||||
recaptchaSiteKey: "Ключ сайту"
|
||||
recaptchaSecretKey: "Секретний ключ"
|
||||
antennas: "Антени"
|
||||
manageAntennas: "Налаштування антен"
|
||||
name: "Ім'я"
|
||||
antennaSource: "Джерело антени"
|
||||
antennaKeywords: "Ключові слова антени"
|
||||
antennaExcludeKeywords: "Винятки"
|
||||
notifyAntenna: "Сповіщати про нові нотатки"
|
||||
withFileAntenna: "Тільки нотатки з вкладеними файлами"
|
||||
serviceworker: "ServiceWorker"
|
||||
enableServiceworker: "Ввімкнути ServiceWorker"
|
||||
caseSensitive: "З урахуванням регістру"
|
||||
withReplies: "Включаючи відповіді"
|
||||
connectedTo: "Наступні акаунти під'єднані"
|
||||
notesAndReplies: "Нотатки та відповіді"
|
||||
withFiles: "Файли"
|
||||
silence: "Тиша"
|
||||
unsilence: "Зняти тишу"
|
||||
popularUsers: "Популярні користувачі"
|
||||
recentlyUpdatedUsers: "Нещодавно активні користувачі"
|
||||
recentlyRegisteredUsers: "Нещодавно зареєстровані користувачі"
|
||||
recentlyDiscoveredUsers: "Нещодавно знайдені користувачі"
|
||||
exploreUsersCount: "{count} користувачів"
|
||||
exploreFediverse: "Огляд федіверсу"
|
||||
popularTags: "Популярні теги"
|
||||
userList: "Списки"
|
||||
about: "Інформація"
|
||||
aboutMisskey: "Про Misskey"
|
||||
administrator: "Адмін"
|
||||
token: "Токен"
|
||||
twoStepAuthentication: "Двохфакторна аутентифікація"
|
||||
moderator: "Модератор"
|
||||
securityKey: "Ключ захисту"
|
||||
securityKeyName: "Назва ключа"
|
||||
registerSecurityKey: "Зареєструвати ключ захисту"
|
||||
lastUsed: "Востаннє використано"
|
||||
unregister: "Скасувати реєстрацію"
|
||||
passwordLessLogin: "Налаштувати вхід без пароля"
|
||||
resetPassword: "Скинути пароль"
|
||||
newPasswordIs: "Новий пароль: {password}"
|
||||
share: "Поділитись"
|
||||
notFound: "Не знайдено"
|
||||
cacheClear: "Очистити кеш"
|
||||
help: "Допомога"
|
||||
inputMessageHere: "Введіть повідомлення тут"
|
||||
close: "Закрити"
|
||||
group: "Група"
|
||||
groups: "Групи"
|
||||
createGroup: "Створити групу"
|
||||
ownedGroups: "Власні групи"
|
||||
invites: "Запросити"
|
||||
groupName: "Назва групи"
|
||||
transfer: "Передача"
|
||||
messagingWithUser: "Чат з користувачами"
|
||||
messagingWithGroup: "Чат з групою"
|
||||
title: "Тема"
|
||||
text: "Текст"
|
||||
next: "Далі"
|
||||
retype: "Введіть ще раз"
|
||||
noteOf: "Нотатка {user}"
|
||||
inviteToGroup: "Запрошення до групи"
|
||||
maxNoteTextLength: "Максимальна довжина нотатки"
|
||||
quoteAttached: "Цитата"
|
||||
quoteQuestion: "Ви хочете додати цитату?"
|
||||
noMessagesYet: "Ще немає повідомлень"
|
||||
newMessageExists: "Є нові повідомлення"
|
||||
onlyOneFileCanBeAttached: "До повідомлення можна вкласти лише один файл"
|
||||
signinRequired: "Будь ласка, авторизуйтесь"
|
||||
invitations: "Запрошення"
|
||||
invitationCode: "Код запрошення"
|
||||
checking: "Перевірка…"
|
||||
available: "Доступно"
|
||||
unavailable: "Недоступно"
|
||||
usernameInvalidFormat: "літери, цифри та _ є прийнятними"
|
||||
tooShort: "Занадто короткий"
|
||||
tooLong: "Занадто довгий"
|
||||
weakPassword: "Слабкий пароль"
|
||||
strongPassword: "Міцний пароль"
|
||||
passwordMatched: "Все вірно"
|
||||
passwordNotMatched: "Паролі не співпадають"
|
||||
signinWith: "Увійти за допомогою {x}"
|
||||
uiLanguage: "Мова інтерфейсу"
|
||||
aboutX: "Про {x}"
|
||||
useOsNativeEmojis: "Використовувати емодзі ОС"
|
||||
youHaveNoGroups: "Немає груп"
|
||||
noHistory: "Історія порожня"
|
||||
disableAnimatedMfm: "Відключити анімації MFM"
|
||||
doing: "Виконується"
|
||||
category: "Категорія"
|
||||
tags: "Теги"
|
||||
docSource: "Джерело цього документа"
|
||||
createAccount: "Створити акаунт"
|
||||
existingAcount: "Існуючий акаунт"
|
||||
regenerate: "Оновити"
|
||||
fontSize: "Розмір шрифту"
|
||||
noFollowRequests: "Немає запитів на підписку"
|
||||
dashboard: "Панель приладів"
|
||||
local: "Локальні"
|
||||
remote: "Віддалені"
|
||||
total: "Всього"
|
||||
weekOverWeekChanges: "Тиждень"
|
||||
dayOverDayChanges: "Доба"
|
||||
appearance: "Вигляд"
|
||||
clientSettings: "Налаштування клієнта"
|
||||
accountSettings: "Налаштування акаунта"
|
||||
promotion: "Виділене"
|
||||
promote: "Виділити"
|
||||
numberOfDays: "Кількість днів"
|
||||
hideThisNote: "Сховати цю нотатку"
|
||||
showFeaturedNotesInTimeline: "Показувати популярні нотатки у стрічці"
|
||||
objectStorageBaseUrl: "Base URL"
|
||||
objectStorageBucket: "Bucket"
|
||||
objectStoragePrefix: "Prefix"
|
||||
objectStorageEndpoint: "Endpoint"
|
||||
objectStorageRegion: "Region"
|
||||
objectStorageUseSSL: "Використовувати SSL"
|
||||
objectStorageUseProxy: "Використовувати Proxy"
|
||||
deleteAll: "Видалити все"
|
||||
newNoteRecived: "Є нові нотатки"
|
||||
sounds: "Звуки"
|
||||
listen: "Слухати"
|
||||
none: "Відсутній"
|
||||
showInPage: "Показати на сторінці"
|
||||
popout: "Від'єднати"
|
||||
volume: "Гучність"
|
||||
details: "Детальніше"
|
||||
chooseEmoji: "Виберіть емодзі"
|
||||
recentUsed: "Нещодавні"
|
||||
install: "Встановити"
|
||||
uninstall: "Видалити"
|
||||
installedApps: "Встановлені аплікації"
|
||||
nothing: "Тут нічого немає"
|
||||
installedDate: "Дата встановлення"
|
||||
lastUsedDate: "Дата використання"
|
||||
state: "Стан"
|
||||
sort: "Сортування"
|
||||
ascendingOrder: "За зростанням"
|
||||
descendingOrder: "За спаданням"
|
||||
scratchpad: "Чернетка"
|
||||
output: "Вихід"
|
||||
script: "Скрипт"
|
||||
deleteAllFiles: "Видалити всі файли"
|
||||
deleteAllFilesConfirm: "Ви дійсно хочете видалити всі файли?"
|
||||
removeAllFollowing: "Скасувати всі підписки"
|
||||
sidebar: "Бокова панель"
|
||||
addItem: "Додати елемент"
|
||||
rooms: "Кімнати"
|
||||
relays: "Ретранслятори"
|
||||
addRelay: "Додати ретранслятор"
|
||||
addedRelays: "Додані ретранслятори"
|
||||
deletedNote: "Видалена нотатка"
|
||||
visibility: "Видимість"
|
||||
poll: "Опитування"
|
||||
expandTweet: "Розгорнути твіт"
|
||||
themeEditor: "Редактор тем"
|
||||
description: "Опис"
|
||||
author: "Автор"
|
||||
manage: "Управління"
|
||||
plugins: "Плагіни"
|
||||
large: "Крупний"
|
||||
medium: "Середній"
|
||||
small: "Маленький"
|
||||
generateAccessToken: "Згенерувати токен доступу"
|
||||
permission: "Права"
|
||||
enableAll: "Ввімкути все"
|
||||
disableAll: "Вимкнути все"
|
||||
tokenRequested: "Надати доступ до акаунту"
|
||||
notificationType: "Тип сповіщення"
|
||||
edit: "Редагувати"
|
||||
useStarForReactionFallback: "Використовувати ★ як запасний варіант, якщо емодзі реакції невідомий"
|
||||
emailConfig: "Налаштування email сервера"
|
||||
email: "E-mail адреса"
|
||||
smtpHost: "Хост"
|
||||
smtpPort: "Порт"
|
||||
smtpUser: "Ім'я користувача"
|
||||
smtpPass: "Пароль"
|
||||
testEmail: "Тестовий email"
|
||||
wordMute: "Ігнор слів"
|
||||
makeActive: "Активувати"
|
||||
copy: "Скопіювати"
|
||||
metrics: "Показники"
|
||||
overview: "Огляд"
|
||||
logs: "Журнал"
|
||||
delayed: "Затримка"
|
||||
database: "База даних"
|
||||
channel: "Канали"
|
||||
create: "Створити"
|
||||
notificationSetting: "Параметри сповіщень"
|
||||
notificationSettingDesc: "Виберіть типи сповіщень для відображення"
|
||||
useGlobalSetting: "Застосувати глобальнi параметри"
|
||||
other: "Інше"
|
||||
regenerateLoginToken: "Оновити Login Token"
|
||||
sample: "Приклад"
|
||||
reportAbuse: "Поскаржитись"
|
||||
reportAbuseOf: "Поскаржитись на {name}"
|
||||
send: "Відправити"
|
||||
abuseMarkAsResolved: "Позначити скаргу як вирішену"
|
||||
openInNewTab: "Відкрити в новій вкладці"
|
||||
waitingFor: "Чекаємо на {x}"
|
||||
random: "Випадковий"
|
||||
system: "Система"
|
||||
switchUi: "Інтерфейс"
|
||||
desktop: "Десктоп"
|
||||
clip: "Добірка"
|
||||
public: "Публічний"
|
||||
_mfm:
|
||||
cheatSheet: " Довідка MFM"
|
||||
intro: "MFM це ексклюзивна мова розмітки тексту в Misskey, яку можна використовувати в багатьох місцях. Тут ви можете переглянути приклади її синтаксису."
|
||||
mention: "Згадка"
|
||||
hashtag: "Хештеґ"
|
||||
url: "URL"
|
||||
link: "Посилання"
|
||||
linkDescription: "Окремі частини тексту можуть містити посилання"
|
||||
bold: "Жирний шрифт"
|
||||
boldDescription: "Виділяє літери, роблячи їх товще"
|
||||
small: "Дрібний шрифт"
|
||||
smallDescription: "Робить текст маленьким і тонким"
|
||||
center: "По центру"
|
||||
centerDescription: "Показує вміст у центрі"
|
||||
inlineCode: "Код (у рядку)"
|
||||
inlineCodeDescription: "Показує фрагмент тексту у рядку як програмний код"
|
||||
blockCode: "Код (блок)"
|
||||
blockCodeDescription: "Показує кілька рядків тексту як блок програмного кода"
|
||||
inlineMath: "Формула (у рядку)"
|
||||
blockMath: "Формули (блок)"
|
||||
quote: "Цитата"
|
||||
emoji: "Кастомні емоджі"
|
||||
search: "Пошук"
|
||||
searchDescription: "Відображає вікно пошуку з попередньо введеним текстом"
|
||||
flipDescription: "Віддзеркалює вміст по горизонталі або вертикалі"
|
||||
jelly: "Анімація (желе)"
|
||||
jellyDescription: "Створює желеподібну анімацію"
|
||||
tada: "Анімація (Тада!)"
|
||||
tadaDescription: "Створює анімацію з відчуттям \"Тада!\""
|
||||
jump: "Анімація (стрибки)"
|
||||
jumpDescription: "Показує стрибаючу анімацію"
|
||||
bounce: "Анімація (пружина)"
|
||||
shake: "Анімація (Shake)"
|
||||
twitch: "Анімація (Twitch)"
|
||||
spin: "Анімація (Spin)"
|
||||
_reversi:
|
||||
reversi: "Реверсі"
|
||||
gameSettings: "Налаштування гри"
|
||||
chooseBoard: "Вибір дошки"
|
||||
blackOrWhite: "Чорні / Білі"
|
||||
blackIs: "{name} грає чорними"
|
||||
rules: "Правила"
|
||||
thisGameIsStartedSoon: "Гра розпочнеться через кілька секунд"
|
||||
waitingForOther: "Чекаємо на хід суперника"
|
||||
waitingForMe: "Чекаємо на ваш хід"
|
||||
waitingBoth: "Приготуйтесь"
|
||||
ready: "Готовність"
|
||||
cancelReady: "Скасувати готовність"
|
||||
opponentTurn: "Хід суперника"
|
||||
myTurn: "Ваш хід"
|
||||
turnOf: "Хід {name}"
|
||||
pastTurnOf: "Хід {name}"
|
||||
surrender: "Здатися"
|
||||
drawn: "Нічия"
|
||||
won: "Перемога {name}"
|
||||
black: "Чорні"
|
||||
white: "Білі"
|
||||
total: "Всього"
|
||||
turnCount: "Хід {count}"
|
||||
myGames: "Мої ігри"
|
||||
allGames: "Усі ігри"
|
||||
ended: "Завершено"
|
||||
_instanceTicker:
|
||||
none: "Не відображати"
|
||||
remote: "Відображати для віддалених користувачів"
|
||||
always: "Відображати завжди"
|
||||
_serverDisconnectedBehavior:
|
||||
reload: "Автоматично перезавантажити"
|
||||
dialog: "Показати діалогове вікно"
|
||||
quiet: "Показати ненав’язливе попередження"
|
||||
_channel:
|
||||
create: "Створити канал"
|
||||
edit: "Редагувати канал"
|
||||
setBanner: "Встановити банер"
|
||||
removeBanner: "Видалити банер"
|
||||
featured: "Тренди"
|
||||
_sidebar:
|
||||
icon: "Аватар"
|
||||
_theme:
|
||||
keys:
|
||||
accent: "Акцент"
|
||||
bg: "Фон"
|
||||
fg: "Текст"
|
||||
focus: "Фокус"
|
||||
indicator: "Індикатор"
|
||||
panel: "Панель"
|
||||
shadow: "Тінь"
|
||||
header: "Заголовок"
|
||||
navBg: "Фон бокової панелі"
|
||||
navFg: "Текст бокової панелі"
|
||||
navHoverFg: "Текст бокової панелі (під курсором)"
|
||||
navActive: "Текст бокової панелі (активне)"
|
||||
navIndicator: "Індикатор бокової панелі"
|
||||
link: "Посилання"
|
||||
hashtag: "Хештеґ"
|
||||
mention: "Згадка"
|
||||
mentionMe: "Згадки (мене)"
|
||||
renote: "Поширити"
|
||||
_sfx:
|
||||
note: "Дописи"
|
||||
note: "Нотатки"
|
||||
notification: "Сповіщення"
|
||||
chat: "Чати"
|
||||
_ago:
|
||||
unknown: "Невідомо"
|
||||
future: "Майбутнє"
|
||||
justNow: "Щойно"
|
||||
secondsAgo: "{n}с тому"
|
||||
minutesAgo: "{n}х тому"
|
||||
hoursAgo: "{n}г тому"
|
||||
daysAgo: "{n}д тому"
|
||||
weeksAgo: "{n} тиж. тому"
|
||||
monthsAgo: "{n} міс. тому"
|
||||
yearsAgo: "{n} р. тому"
|
||||
_time:
|
||||
second: "с"
|
||||
minute: "х"
|
||||
hour: "г"
|
||||
day: "д"
|
||||
_tutorial:
|
||||
title: "Як користуватись Misskey"
|
||||
step1_1: "Ласкаво просимо!"
|
||||
_antennaSources:
|
||||
homeTimeline: "Дописи тих, на кого ви підписані"
|
||||
homeTimeline: "Нотатки тих, на кого ви підписані"
|
||||
_widgets:
|
||||
notifications: "Сповіщення"
|
||||
timeline: "Стрічка"
|
||||
@@ -271,24 +656,31 @@ _widgets:
|
||||
federation: "Федіверс"
|
||||
_cw:
|
||||
show: "Показати більше"
|
||||
_poll:
|
||||
deadlineTime: "г"
|
||||
_visibility:
|
||||
home: "Головна"
|
||||
home: "Домівка"
|
||||
followers: "Підписники"
|
||||
localOnly: "Лише локально"
|
||||
_postForm:
|
||||
replyPlaceholder: "Відповідь на допис..."
|
||||
replyPlaceholder: "Відповідь на цю нотатку..."
|
||||
_profile:
|
||||
name: "Ім'я"
|
||||
username: "Ім'я користувача"
|
||||
_exportOrImport:
|
||||
followingList: "Підписки"
|
||||
muteList: "Ігнорувати"
|
||||
blockingList: "Заблокувати"
|
||||
userLists: "Списки"
|
||||
_timelines:
|
||||
home: "Домівка"
|
||||
_rooms:
|
||||
_roomType:
|
||||
default: "За умовчанням"
|
||||
_furnitures:
|
||||
monitor: "Монітор"
|
||||
_pages:
|
||||
featured: "Популярні"
|
||||
blocks:
|
||||
image: "Зображення"
|
||||
script:
|
||||
@@ -307,11 +699,17 @@ _pages:
|
||||
arg1: "Списки"
|
||||
_listLen:
|
||||
arg1: "Списки"
|
||||
_fn:
|
||||
arg1: "Вихід"
|
||||
types:
|
||||
array: "Списки"
|
||||
_relayStatus:
|
||||
requesting: "Очікує затвердження"
|
||||
accepted: "Затверджено"
|
||||
rejected: "Відхилено"
|
||||
_notification:
|
||||
youRenoted: "{name} поширив(ла) ваш допис"
|
||||
youWereFollowed: "У вас новий підписник"
|
||||
youRenoted: "{name} поширив(ла) вашу нотатку"
|
||||
youWereFollowed: "Новий підписник"
|
||||
_types:
|
||||
follow: "Підписки"
|
||||
mention: "Згадка"
|
||||
@@ -322,5 +720,6 @@ _deck:
|
||||
_columns:
|
||||
notifications: "Сповіщення"
|
||||
tl: "Стрічка"
|
||||
antenna: "Антени"
|
||||
list: "Списки"
|
||||
mentions: "Згадки"
|
||||
|
||||
@@ -84,7 +84,7 @@ followRequest: "关注申请"
|
||||
followRequests: "关注申请"
|
||||
unfollow: "取消关注"
|
||||
followRequestPending: "发送关注申请"
|
||||
enterEmoji: "输入Emoji"
|
||||
enterEmoji: "输入表情符号"
|
||||
renote: "转发"
|
||||
unrenote: "取消转发"
|
||||
quote: "引用"
|
||||
@@ -95,6 +95,7 @@ sensitive: "阅读注意"
|
||||
add: "添加"
|
||||
reaction: "回应"
|
||||
reactionSettingDescription: "选择您想要置顶的回应。"
|
||||
reactionSettingDescription2: "通过拖动来重新排列。单击即可删除。"
|
||||
rememberNoteVisibility: "记录公开范围"
|
||||
attachCancel: "删除附件"
|
||||
markAsSensitive: "阅读注意"
|
||||
@@ -117,13 +118,14 @@ editWidgets: "编辑小工具"
|
||||
editWidgetsExit: "完成编辑"
|
||||
customEmojis: "自定义Emoji"
|
||||
emoji: "表情符号"
|
||||
emojiName: "Emoji 名称"
|
||||
emojiUrl: "emoji 地址"
|
||||
addEmoji: "添加Emoji"
|
||||
emojiName: "表情符号名称"
|
||||
emojiUrl: "表情符号地址"
|
||||
addEmoji: "添加表情符号"
|
||||
settingGuide: "推荐配置"
|
||||
cacheRemoteFiles: "远程文件缓存"
|
||||
cacheRemoteFilesDescription: "当禁用此设定时远程文件将直接从远程实例载入。禁用后会减小储存空间需求,但是会增加流量,因为缩略图不会被生成。"
|
||||
flagAsBot: "这个账户是Bot"
|
||||
flagAsBotDescription: "如果此帐户由程序控制,请启用此项。启用后,此标志可以帮助其他开发人员防止机器人之间产生无限互动的行为,并让Misskey的内部系统将此帐户识别为机器人。"
|
||||
flagAsCat: "这个账户是Cat"
|
||||
autoAcceptFollowed: "自动允许关注"
|
||||
addAcount: "添加账户"
|
||||
@@ -185,7 +187,7 @@ done: "完成"
|
||||
processing: "处理中"
|
||||
preview: "预览"
|
||||
default: "默认"
|
||||
noCustomEmojis: "无自定义Emoji"
|
||||
noCustomEmojis: "没有自定义表情符号"
|
||||
noJobs: "没有任务"
|
||||
federating: "联合中"
|
||||
blocked: "已拦截"
|
||||
@@ -214,6 +216,8 @@ imageUrl: "图片URL"
|
||||
remove: "删除"
|
||||
removed: "已删除"
|
||||
removeAreYouSure: "要删掉「{x}」吗?"
|
||||
deleteAreYouSure: "要删掉「{x}」吗?"
|
||||
resetAreYouSure: "恢复默认设置?"
|
||||
saved: "已保存"
|
||||
messaging: "聊天"
|
||||
upload: "上传"
|
||||
@@ -313,6 +317,8 @@ bannerUrl: "Banner URL"
|
||||
basicInfo: "基本信息"
|
||||
pinnedUsers: "置顶用户"
|
||||
pinnedUsersDescription: "在「发现」页面中使用换行标记想要置顶的用户。"
|
||||
pinnedPages: "固定页面"
|
||||
pinnedPagesDescription: "输入您要固定到实例首页的页面路径,以换行符分隔。"
|
||||
hcaptcha: "hCaptcha"
|
||||
enableHcaptcha: "启用 hCaptcha"
|
||||
hcaptchaSiteKey: "网站密钥"
|
||||
@@ -430,7 +436,7 @@ or: "或者"
|
||||
uiLanguage: "显示语言"
|
||||
groupInvited: "群组招待"
|
||||
aboutX: "关于 {x}"
|
||||
useOsNativeEmojis: "使用OS原生Emoji"
|
||||
useOsNativeEmojis: "使用OS原生表情符号"
|
||||
youHaveNoGroups: "没有群组"
|
||||
joinOrCreateGroup: "请加入一个现有的群组,或者创建新群组。"
|
||||
noHistory: "没有历史记录"
|
||||
@@ -541,6 +547,11 @@ deck: "Deck"
|
||||
undeck: "取消Deck"
|
||||
useBlurEffectForModal: "模态框使用模糊效果"
|
||||
useFullReactionPicker: "使用全功能的回应工具栏"
|
||||
width: "宽度"
|
||||
height: "高度"
|
||||
large: "大"
|
||||
medium: "中"
|
||||
small: "小"
|
||||
generateAccessToken: "生成访问令牌"
|
||||
permission: "权限"
|
||||
enableAll: "启用全部"
|
||||
@@ -549,7 +560,7 @@ tokenRequested: "允许访问账户"
|
||||
pluginTokenRequestedDescription: "此插件将能够拥有此处设置的权限"
|
||||
notificationType: "通知类型"
|
||||
edit: "编辑"
|
||||
useStarForReactionFallback: "如果回应的颜文字未知,则使用★作为代替"
|
||||
useStarForReactionFallback: "如果回应的是未知表情符号,则使用★作为代替"
|
||||
emailConfig: "邮件服务器设置"
|
||||
enableEmail: "启用发送邮件功能"
|
||||
emailConfigInfo: "用于确认电子邮件和密码重置"
|
||||
@@ -603,14 +614,59 @@ random: "随机"
|
||||
system: "系统"
|
||||
switchUi: "切换界面"
|
||||
desktop: "桌面"
|
||||
clip: "片段"
|
||||
createNew: "新建"
|
||||
optional: "可选"
|
||||
createNewClip: "新建片段"
|
||||
public: "公开"
|
||||
_mfm:
|
||||
cheatSheet: "MFM代码速查表"
|
||||
intro: "MFM是一种在Misskey中的各个位置使用的专用标记语言。在这里您可以看到MFM中可用的语法列表。"
|
||||
dummy: "通过Misskey扩展Fediverse的世界"
|
||||
mention: "提及"
|
||||
mentionDescription: "可以使用 @+用户名 来指示特定用户"
|
||||
hashtag: "话题标签"
|
||||
hashtagDescription: "可以使用井号+文字来表示话题标签。"
|
||||
url: "URL"
|
||||
urlDescription: "可以表示URL地址。"
|
||||
link: "链接"
|
||||
linkDescription: "可以将部分文字和URL关联起来。"
|
||||
bold: "粗体"
|
||||
boldDescription: "可以将文字显示为粗体来表示强调。"
|
||||
small: "缩小"
|
||||
smallDescription: "可以使内容文字变小、变淡。"
|
||||
center: "居中"
|
||||
centerDescription: "可以将内容居中显示。"
|
||||
inlineCode: "代码(内嵌)"
|
||||
inlineCodeDescription: "将文字中的程序代码语法高亮显示。"
|
||||
blockCode: "代码(块)"
|
||||
blockCodeDescription: "语法高亮显示整块程序代码。"
|
||||
inlineMath: "数学公式(内嵌)"
|
||||
inlineMathDescription: "显示内嵌的KaTex公式。"
|
||||
blockMath: "数学公式(块)"
|
||||
blockMathDescription: "显示整块的多行KaTex数学公式。"
|
||||
quote: "引用"
|
||||
emoji: "自定义Emoji"
|
||||
quoteDescription: "可以用来表示引用的内容。"
|
||||
emoji: "自定义表情符号"
|
||||
emojiDescription: "可以将自定义表情符号使用冒号括起来,就可以显示自定义表情符号了。"
|
||||
search: "搜索"
|
||||
searchDescription: "显示含有搜索内容示例的搜索框。"
|
||||
flip: "翻转"
|
||||
flipDescription: "将内容上下或左右翻转。"
|
||||
jelly: "动画(果冻)"
|
||||
jellyDescription: "显示果冻一样的动画效果。"
|
||||
tada: "动画(锵锵)"
|
||||
tadaDescription: "显示\"锵锵!\"的动画效果。"
|
||||
jump: "动画(跳动)"
|
||||
jumpDescription: "显示跳动的动画效果。"
|
||||
bounce: "动画(弹性)"
|
||||
bounceDescription: "显示弹性一样的动画效果。"
|
||||
shake: "动画(摇晃)"
|
||||
shakeDescription: "显示摇晃的动画效果。"
|
||||
twitch: "动画(颤抖)"
|
||||
twitchDescription: "显示强烈颤抖的动画效果。"
|
||||
spin: "动画(回转)"
|
||||
spinDescription: "显示回转的动画效果。"
|
||||
_reversi:
|
||||
reversi: "黑白棋"
|
||||
gameSettings: "对局设置"
|
||||
@@ -1028,6 +1084,7 @@ _pages:
|
||||
created: "页面已创建"
|
||||
updated: "页面已更新"
|
||||
deleted: "该页面已被删除"
|
||||
pageSetting: "页面设置"
|
||||
nameAlreadyExists: "该页面URL已存在"
|
||||
invalidNameTitle: "无效的页面URL"
|
||||
invalidNameText: "请确认该项不为空"
|
||||
@@ -1038,7 +1095,9 @@ _pages:
|
||||
unlike: "取消赞"
|
||||
my: "我的页面"
|
||||
liked: "喜欢的页面"
|
||||
featured: "热门"
|
||||
inspector: "检查器"
|
||||
contents: "内容"
|
||||
content: "页面内容"
|
||||
variables: "变量"
|
||||
title: "标题"
|
||||
@@ -1092,6 +1151,11 @@ _pages:
|
||||
id: "画布ID"
|
||||
width: "宽度"
|
||||
height: "高度"
|
||||
note: "嵌入的帖子"
|
||||
_note:
|
||||
id: "帖子ID"
|
||||
idDescription: "您也可以通过粘贴帖子的URL来进行设置。"
|
||||
detailed: "显示详细信息"
|
||||
switch: "开关"
|
||||
_switch:
|
||||
name: "变量名"
|
||||
|
||||
@@ -212,6 +212,7 @@ imageUrl: "圖片URL"
|
||||
remove: "刪除"
|
||||
removed: "成功移除"
|
||||
removeAreYouSure: "確定要刪掉「{x}」嗎?"
|
||||
deleteAreYouSure: "確定要刪掉「{x}」嗎?"
|
||||
saved: "已保存"
|
||||
messaging: "傳送訊息"
|
||||
upload: "上傳"
|
||||
@@ -518,6 +519,8 @@ plugins: "插件"
|
||||
pluginInstallWarn: "請不要安裝來源不明的插件。"
|
||||
deck: "多欄模式"
|
||||
undeck: "取消多欄模式"
|
||||
width: "寬度"
|
||||
height: "高度"
|
||||
permission: "權限"
|
||||
enableAll: "啟用全部"
|
||||
disableAll: "停用全部"
|
||||
@@ -557,6 +560,7 @@ send: "發送"
|
||||
openInNewTab: "在新分頁中開啟"
|
||||
random: "隨機"
|
||||
system: "系統"
|
||||
public: "公開"
|
||||
_mfm:
|
||||
mention: "提及"
|
||||
hashtag: "#tag"
|
||||
|
||||
15
migration/1605408848373-clip-description.ts
Normal file
15
migration/1605408848373-clip-description.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class clipDescription1605408848373 implements MigrationInterface {
|
||||
name = 'clipDescription1605408848373'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "clip" ADD "description" character varying(2048) DEFAULT null`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "clip" DROP COLUMN "description"`);
|
||||
}
|
||||
|
||||
}
|
||||
434
migration/1605408971051-comments.ts
Normal file
434
migration/1605408971051-comments.ts
Normal file
@@ -0,0 +1,434 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class comments1605408971051 implements MigrationInterface {
|
||||
name = 'comments1605408971051'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`COMMENT ON COLUMN "log"."createdAt" IS 'The created date of the Log.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."createdAt" IS 'The created date of the DriveFolder.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."name" IS 'The name of the DriveFolder.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."userId" IS 'The owner ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."parentId" IS 'The parent folder ID. If null, it means the DriveFolder is located in root.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."createdAt" IS 'The created date of the DriveFile.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."userId" IS 'The owner ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."userHost" IS 'The host of owner. It will be null if the user in local.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."md5" IS 'The MD5 hash of the DriveFile.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."name" IS 'The file name of the DriveFile.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."type" IS 'The content type (MIME) of the DriveFile.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."size" IS 'The file size (bytes) of the DriveFile.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."comment" IS 'The comment of the DriveFile.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."blurhash" IS 'The BlurHash string.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."properties" IS 'The any properties of the DriveFile. For example, it includes image width/height.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."url" IS 'The URL of the DriveFile.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."thumbnailUrl" IS 'The URL of the thumbnail of the DriveFile.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."webpublicUrl" IS 'The URL of the webpublic of the DriveFile.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."uri" IS 'The URI of the DriveFile. it will be null when the DriveFile is local.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."folderId" IS 'The parent folder ID. If null, it means the DriveFile is located in root.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."isSensitive" IS 'Whether the DriveFile is NSFW.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."isLink" IS 'Whether the DriveFile is direct link to remote server.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."createdAt" IS 'The created date of the User.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."updatedAt" IS 'The updated date of the User.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."username" IS 'The username of the User.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."usernameLower" IS 'The username (lowercased) of the User.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."name" IS 'The name of the User.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."followersCount" IS 'The count of followers.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."followingCount" IS 'The count of following.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."notesCount" IS 'The count of notes.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."avatarId" IS 'The ID of avatar DriveFile.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."bannerId" IS 'The ID of banner DriveFile.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isSuspended" IS 'Whether the User is suspended.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isSilenced" IS 'Whether the User is silenced.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isLocked" IS 'Whether the User is locked.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isBot" IS 'Whether the User is a bot.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isCat" IS 'Whether the User is a cat.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isAdmin" IS 'Whether the User is the admin.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isModerator" IS 'Whether the User is a moderator.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."host" IS 'The host of the User. It will be null if the origin of the user is local.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."inbox" IS 'The inbox URL of the User. It will be null if the origin of the user is local.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."sharedInbox" IS 'The sharedInbox URL of the User. It will be null if the origin of the user is local.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."featured" IS 'The featured URL of the User. It will be null if the origin of the user is local.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."uri" IS 'The URI of the User. It will be null if the origin of the user is local.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."token" IS 'The native access token of the User. It will be null if the origin of the user is local.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "app"."createdAt" IS 'The created date of the App.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "app"."userId" IS 'The owner ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "app"."secret" IS 'The secret key of the App.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "app"."name" IS 'The name of the App.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "app"."description" IS 'The description of the App.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "app"."permission" IS 'The permission of the App.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "app"."callbackUrl" IS 'The callbackUrl of the App.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "access_token"."createdAt" IS 'The created date of the AccessToken.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "access_token"."lastUsedAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "access_token"."session" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "access_token"."appId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "access_token"."name" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "access_token"."description" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "access_token"."iconUrl" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel"."createdAt" IS 'The created date of the Channel.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel"."userId" IS 'The owner ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel"."name" IS 'The name of the Channel.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel"."description" IS 'The description of the Channel.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel"."bannerId" IS 'The ID of banner Channel.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel"."notesCount" IS 'The count of notes.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel"."usersCount" IS 'The count of users.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."createdAt" IS 'The created date of the Note.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."replyId" IS 'The ID of reply target.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."renoteId" IS 'The ID of renote target.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."userId" IS 'The ID of author.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."uri" IS 'The URI of a note. it will be null when the note is local.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."url" IS 'The human readable url of a note. it will be null when the note is local.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."channelId" IS 'The ID of source channel.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."userHost" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."replyUserId" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."replyUserHost" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."renoteUserId" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."renoteUserHost" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "poll_vote"."createdAt" IS 'The created date of the PollVote.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_reaction"."createdAt" IS 'The created date of the NoteReaction.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_watching"."createdAt" IS 'The created date of the NoteWatching.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_watching"."userId" IS 'The watcher ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_watching"."noteId" IS 'The target Note ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_watching"."noteUserId" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_unread"."noteUserId" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_unread"."noteChannelId" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."createdAt" IS 'The created date of the FollowRequest.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeId" IS 'The followee user ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerId" IS 'The follower user ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."requestId" IS 'id of Follow Activity.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerHost" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerInbox" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerSharedInbox" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeHost" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeInbox" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeSharedInbox" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group"."createdAt" IS 'The created date of the UserGroup.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group"."userId" IS 'The ID of owner.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group_invitation"."createdAt" IS 'The created date of the UserGroupInvitation.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group_invitation"."userId" IS 'The user ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group_invitation"."userGroupId" IS 'The group ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "notification"."createdAt" IS 'The created date of the Notification.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "notification"."notifieeId" IS 'The ID of recipient user of the Notification.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "notification"."isRead" IS 'Whether the Notification is read.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "meta"."localDriveCapacityMb" IS 'Drive capacity of a local user (MB)'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "meta"."remoteDriveCapacityMb" IS 'Drive capacity of a remote user (MB)'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "meta"."maxNoteTextLength" IS 'Max allowed note text length in characters'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."createdAt" IS 'The created date of the Following.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followeeId" IS 'The followee user ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followerId" IS 'The follower user ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followerHost" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followerInbox" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followerSharedInbox" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followeeHost" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followeeInbox" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followeeSharedInbox" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."caughtAt" IS 'The caught date of the Instance.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."host" IS 'The host of the Instance.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."usersCount" IS 'The count of the users of the Instance.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."notesCount" IS 'The count of the notes of the Instance.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."softwareName" IS 'The software of the Instance.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."softwareVersion" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."openRegistrations" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."name" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."description" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."maintainerName" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."maintainerEmail" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."iconUrl" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."faviconUrl" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."themeColor" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "muting"."createdAt" IS 'The created date of the Muting.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "muting"."muteeId" IS 'The mutee user ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "muting"."muterId" IS 'The muter user ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "blocking"."createdAt" IS 'The created date of the Blocking.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "blocking"."blockeeId" IS 'The blockee user ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "blocking"."blockerId" IS 'The blocker user ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_list"."createdAt" IS 'The created date of the UserList.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_list"."userId" IS 'The owner ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_list"."name" IS 'The name of the UserList.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_list_joining"."createdAt" IS 'The created date of the UserListJoining.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_list_joining"."userId" IS 'The user ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_list_joining"."userListId" IS 'The list ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group_joining"."createdAt" IS 'The created date of the UserGroupJoining.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group_joining"."userId" IS 'The user ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group_joining"."userGroupId" IS 'The group ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_favorite"."createdAt" IS 'The created date of the NoteFavorite.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "abuse_user_report"."createdAt" IS 'The created date of the AbuseUserReport.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "abuse_user_report"."targetUserHost" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "abuse_user_report"."reporterHost" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."createdAt" IS 'The created date of the MessagingMessage.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."userId" IS 'The sender user ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."groupId" IS 'The recipient group ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "signin"."createdAt" IS 'The created date of the Signin.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "auth_session"."createdAt" IS 'The created date of the AuthSession.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."createdAt" IS 'The created date of the ReversiGame.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."startedAt" IS 'The started date of the ReversiGame.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."form1" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."form2" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "reversi_matching"."createdAt" IS 'The created date of the ReversiMatching.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_note_pining"."createdAt" IS 'The created date of the UserNotePinings.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "poll"."noteId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "poll"."noteVisibility" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "poll"."userId" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "poll"."userHost" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_keypair"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_publickey"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "page"."createdAt" IS 'The created date of the Page.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "page"."updatedAt" IS 'The updated date of the Page.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "page"."userId" IS 'The ID of author.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."location" IS 'The location of the User.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."birthday" IS 'The birthday (YYYY-MM-DD) of the User.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."description" IS 'The description (bio) of the User.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."url" IS 'Remote URL of the user.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."email" IS 'The email address of the User.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."password" IS 'The password hash of the User. It will be null if the origin of the user is local.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."clientData" IS 'The client-specific data of the User.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."room" IS 'The room data of the User.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."userHost" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."id" IS 'Variable-length id given to navigator.credentials.get()'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."publicKey" IS 'Variable-length public key used to verify attestations (hex-encoded).'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."lastUsed" IS 'The date of the last time the UserSecurityKey was successfully validated.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."name" IS 'User-defined name for this key'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."challenge" IS 'Hex-encoded sha256 hash of the challenge.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."createdAt" IS 'The date challenge was created for expiry purposes.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."registrationChallenge" IS 'Indicates that the challenge is only for registration purposes if true to prevent the challenge for being used as authentication.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "moderation_log"."createdAt" IS 'The created date of the ModerationLog.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "announcement"."createdAt" IS 'The created date of the Announcement.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "announcement"."updatedAt" IS 'The updated date of the Announcement.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "announcement_read"."createdAt" IS 'The created date of the AnnouncementRead.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "clip"."createdAt" IS 'The created date of the Clip.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "clip"."userId" IS 'The owner ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "clip"."name" IS 'The name of the Clip.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "clip"."description" IS 'The description of the Clip.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "clip_note"."noteId" IS 'The note ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "clip_note"."clipId" IS 'The clip ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "antenna"."createdAt" IS 'The created date of the Antenna.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "antenna"."userId" IS 'The owner ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "antenna"."name" IS 'The name of the Antenna.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "antenna_note"."noteId" IS 'The note ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "antenna_note"."antennaId" IS 'The antenna ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "promo_note"."noteId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "promo_note"."userId" IS '[Denormalized]'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "promo_read"."createdAt" IS 'The created date of the PromoRead.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "muted_note"."noteId" IS 'The note ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "muted_note"."userId" IS 'The user ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "muted_note"."reason" IS 'The reason of the MutedNote.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel_following"."createdAt" IS 'The created date of the ChannelFollowing.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel_following"."followeeId" IS 'The followee channel ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel_following"."followerId" IS 'The follower user ID.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel_note_pining"."createdAt" IS 'The created date of the ChannelNotePining.'`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel_note_pining"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel_following"."followerId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel_following"."followeeId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel_following"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "muted_note"."reason" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "muted_note"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "muted_note"."noteId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "promo_read"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "promo_note"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "promo_note"."noteId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "antenna_note"."antennaId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "antenna_note"."noteId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "antenna"."name" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "antenna"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "antenna"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "clip_note"."clipId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "clip_note"."noteId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "clip"."description" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "clip"."name" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "clip"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "clip"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "announcement_read"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "announcement"."updatedAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "announcement"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "moderation_log"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."registrationChallenge" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "attestation_challenge"."challenge" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."name" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."lastUsed" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."publicKey" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_security_key"."id" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."userHost" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."room" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."clientData" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."password" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."email" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."url" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."description" IS 'The description (bio) of the User.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."birthday" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."location" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "page"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "page"."updatedAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "page"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_publickey"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_keypair"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "poll"."userHost" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "poll"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "poll"."noteVisibility" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "poll"."noteId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_note_pining"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "reversi_matching"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."form2" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."form1" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."startedAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "auth_session"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "signin"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."groupId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "abuse_user_report"."reporterHost" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "abuse_user_report"."targetUserHost" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "abuse_user_report"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_favorite"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group_joining"."userGroupId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group_joining"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group_joining"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_list_joining"."userListId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_list_joining"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_list_joining"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_list"."name" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_list"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_list"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "blocking"."blockerId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "blocking"."blockeeId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "blocking"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "muting"."muterId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "muting"."muteeId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "muting"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."themeColor" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."faviconUrl" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."iconUrl" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."maintainerEmail" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."maintainerName" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."description" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."name" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."openRegistrations" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."softwareVersion" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."softwareName" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."notesCount" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."usersCount" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."host" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "instance"."caughtAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followeeSharedInbox" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followeeInbox" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followeeHost" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followerSharedInbox" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followerInbox" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followerHost" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followerId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."followeeId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "following"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "meta"."maxNoteTextLength" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "meta"."remoteDriveCapacityMb" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "meta"."localDriveCapacityMb" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "notification"."isRead" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "notification"."notifieeId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "notification"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group_invitation"."userGroupId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group_invitation"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group_invitation"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_group"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeSharedInbox" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeInbox" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeHost" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerSharedInbox" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerInbox" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerHost" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."requestId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followerId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."followeeId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "follow_request"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_unread"."noteChannelId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_unread"."noteUserId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_watching"."noteUserId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_watching"."noteId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_watching"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_watching"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note_reaction"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "poll_vote"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."renoteUserHost" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."renoteUserId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."replyUserHost" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."replyUserId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."userHost" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."channelId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."url" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."uri" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."renoteId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."replyId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "note"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel"."usersCount" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel"."notesCount" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel"."bannerId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel"."description" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel"."name" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "channel"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "access_token"."iconUrl" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "access_token"."description" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "access_token"."name" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "access_token"."appId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "access_token"."session" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "access_token"."lastUsedAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "access_token"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "app"."callbackUrl" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "app"."permission" IS 'The permission of the App.'`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "app"."description" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "app"."name" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "app"."secret" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "app"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "app"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."token" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."uri" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."featured" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."sharedInbox" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."inbox" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."host" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isModerator" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isAdmin" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isCat" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isBot" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isLocked" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isSilenced" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."isSuspended" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."bannerId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."avatarId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."notesCount" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."followingCount" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."followersCount" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."name" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."usernameLower" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."username" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."updatedAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."isLink" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."isSensitive" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."folderId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."uri" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."webpublicUrl" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."thumbnailUrl" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."url" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."properties" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."blurhash" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."comment" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."size" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."type" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."name" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."md5" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."userHost" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_file"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."parentId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."userId" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."name" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "drive_folder"."createdAt" IS NULL`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "log"."createdAt" IS NULL`);
|
||||
}
|
||||
|
||||
}
|
||||
14
migration/1605585339718-instance-pinned-pages.ts
Normal file
14
migration/1605585339718-instance-pinned-pages.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class instancePinnedPages1605585339718 implements MigrationInterface {
|
||||
name = 'instancePinnedPages1605585339718'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "pinnedPages" character varying(512) array NOT NULL DEFAULT '{"/featured", "/channels", "/explore", "/pages", "/about-misskey"}'::varchar[]`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "pinnedPages"`);
|
||||
}
|
||||
|
||||
}
|
||||
16
migration/1605965516823-instance-images.ts
Normal file
16
migration/1605965516823-instance-images.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class instanceImages1605965516823 implements MigrationInterface {
|
||||
name = 'instanceImages1605965516823'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "backgroundImageUrl" character varying(512)`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" ADD "logoImageUrl" character varying(512)`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "logoImageUrl"`);
|
||||
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "backgroundImageUrl"`);
|
||||
}
|
||||
|
||||
}
|
||||
16
migration/1606191203881-no-crawle.ts
Normal file
16
migration/1606191203881-no-crawle.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class noCrawle1606191203881 implements MigrationInterface {
|
||||
name = 'noCrawle1606191203881'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ADD "noCrawle" boolean NOT NULL DEFAULT false`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."noCrawle" IS 'Whether reject index by crawler.'`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user_profile"."noCrawle" IS 'Whether reject index by crawler.'`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "noCrawle"`);
|
||||
}
|
||||
|
||||
}
|
||||
10
package.json
10
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <syuilotan@yahoo.co.jp>",
|
||||
"version": "12.55.0",
|
||||
"version": "12.60.0",
|
||||
"codename": "indigo",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -242,17 +242,17 @@
|
||||
"vue": "3.0.2",
|
||||
"vue-color": "2.7.1",
|
||||
"vue-draggable-next": "1.0.8",
|
||||
"vue-i18n": "9.0.0-beta.6",
|
||||
"vue-i18n": "9.0.0-beta.7",
|
||||
"vue-json-pretty": "1.7.1",
|
||||
"vue-loader": "16.0.0-beta.8",
|
||||
"vue-prism-editor": "1.2.2",
|
||||
"vue-router": "4.0.0-beta.13",
|
||||
"vue-router": "4.0.0-rc.2",
|
||||
"vue-style-loader": "4.1.2",
|
||||
"vue-template-compiler": "2.6.12",
|
||||
"vuex": "4.0.0-beta.4",
|
||||
"vuex": "4.0.0-rc.1",
|
||||
"vuex-persistedstate": "3.1.0",
|
||||
"web-push": "3.4.4",
|
||||
"webpack": "5.4.0",
|
||||
"webpack": "5.6.0",
|
||||
"webpack-cli": "4.2.0",
|
||||
"websocket": "1.0.32",
|
||||
"ws": "7.3.1",
|
||||
|
||||
BIN
src/client/assets/sounds/syuilo/kick.mp3
Normal file
BIN
src/client/assets/sounds/syuilo/kick.mp3
Normal file
Binary file not shown.
BIN
src/client/assets/sounds/syuilo/snare.mp3
Normal file
BIN
src/client/assets/sounds/syuilo/snare.mp3
Normal file
Binary file not shown.
34
src/client/cold-storage.ts
Normal file
34
src/client/cold-storage.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// 常にメモリにロードしておく必要がないような設定情報を保管するストレージ
|
||||
|
||||
const PREFIX = 'miux:';
|
||||
|
||||
export const defaultDeviceSettings = {
|
||||
sound_masterVolume: 0.3,
|
||||
sound_note: { type: 'syuilo/down', volume: 1 },
|
||||
sound_noteMy: { type: 'syuilo/up', volume: 1 },
|
||||
sound_notification: { type: 'syuilo/pope2', volume: 1 },
|
||||
sound_chat: { type: 'syuilo/pope1', volume: 1 },
|
||||
sound_chatBg: { type: 'syuilo/waon', volume: 1 },
|
||||
sound_antenna: { type: 'syuilo/triple', volume: 1 },
|
||||
sound_channel: { type: 'syuilo/square-pico', volume: 1 },
|
||||
sound_reversiPutBlack: { type: 'syuilo/kick', volume: 0.3 },
|
||||
sound_reversiPutWhite: { type: 'syuilo/snare', volume: 0.3 },
|
||||
};
|
||||
|
||||
export const device = {
|
||||
get<T extends keyof typeof defaultDeviceSettings>(key: T): typeof defaultDeviceSettings[T] {
|
||||
// TODO: indexedDBにする
|
||||
// ただしその際はnullチェックではなくキー存在チェックにしないとダメ
|
||||
// (indexedDBはnullを保存できるため、ユーザーが意図してnullを格納した可能性がある)
|
||||
const value = localStorage.getItem(PREFIX + key);
|
||||
if (value == null) {
|
||||
return defaultDeviceSettings[key];
|
||||
} else {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
},
|
||||
|
||||
set(key: keyof typeof defaultDeviceSettings, value: any): any {
|
||||
localStorage.setItem(PREFIX + key, JSON.stringify(value));
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')">
|
||||
<div class="omfetrab _popup">
|
||||
<input ref="search" class="search" v-model.trim="q" :placeholder="$t('search')" @paste.stop="paste" @keyup.enter="done()" autofocus>
|
||||
<div class="omfetrab _popup" :class="['w' + width, 'h' + height, { big }]">
|
||||
<input ref="search" class="search" :class="{ filled: q != null && q != '' }" v-model.trim="q" :placeholder="$t('search')" @paste.stop="paste" @keyup.enter="done()">
|
||||
<div class="emojis">
|
||||
<section class="result">
|
||||
<div v-if="searchResultCustom.length > 0">
|
||||
@@ -30,7 +30,7 @@
|
||||
</section>
|
||||
|
||||
<div class="index">
|
||||
<section>
|
||||
<section v-if="showPinned">
|
||||
<div>
|
||||
<button v-for="emoji in pinned"
|
||||
class="_button"
|
||||
@@ -43,7 +43,7 @@
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header class="_acrylic"><Fa :icon="faHistory" fixed-width/> {{ $t('recentUsed') }}</header>
|
||||
<header class="_acrylic"><Fa :icon="faClock" fixed-width/> {{ $t('recentUsed') }}</header>
|
||||
<div>
|
||||
<button v-for="emoji in $store.state.device.recentlyUsedEmojis"
|
||||
class="_button"
|
||||
@@ -94,11 +94,12 @@
|
||||
import { defineComponent, markRaw } from 'vue';
|
||||
import { emojilist } from '../../misc/emojilist';
|
||||
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
|
||||
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faHistory, faUser, faChevronDown } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faClock, faUser, faChevronDown } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faHeart, faFlag, faLaugh } from '@fortawesome/free-regular-svg-icons';
|
||||
import MkModal from '@/components/ui/modal.vue';
|
||||
import Particle from '@/components/particle.vue';
|
||||
import * as os from '@/os';
|
||||
import { isDeviceTouch } from '../scripts/is-device-touch';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -109,7 +110,11 @@ export default defineComponent({
|
||||
src: {
|
||||
required: false
|
||||
},
|
||||
overridePinned: {
|
||||
showPinned: {
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
asReactionPicker: {
|
||||
required: false
|
||||
},
|
||||
},
|
||||
@@ -120,14 +125,17 @@ export default defineComponent({
|
||||
return {
|
||||
emojilist: markRaw(emojilist),
|
||||
getStaticImageUrl,
|
||||
pinned: this.overridePinned || this.$store.state.settings.reactions,
|
||||
pinned: this.$store.state.settings.reactions,
|
||||
width: this.asReactionPicker ? this.$store.state.device.reactionPickerWidth : 3,
|
||||
height: this.asReactionPicker ? this.$store.state.device.reactionPickerHeight : 2,
|
||||
big: this.asReactionPicker ? isDeviceTouch : false,
|
||||
customEmojiCategories: this.$store.getters['instance/emojiCategories'],
|
||||
customEmojis: this.$store.state.instance.meta.emojis,
|
||||
visibleCategories: {},
|
||||
q: null,
|
||||
searchResultCustom: [],
|
||||
searchResultUnicode: [],
|
||||
faGlobe, faHistory, faChevronDown,
|
||||
faGlobe, faClock, faChevronDown,
|
||||
categories: [{
|
||||
name: 'face',
|
||||
icon: faLaugh,
|
||||
@@ -311,9 +319,11 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$refs.search.focus({
|
||||
preventScroll: true
|
||||
});
|
||||
if (!os.isMobile) {
|
||||
this.$refs.search.focus({
|
||||
preventScroll: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -379,9 +389,41 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.omfetrab {
|
||||
width: 350px;
|
||||
$pad: 8px;
|
||||
--eachSize: 40px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
contain: content;
|
||||
|
||||
&.big {
|
||||
--eachSize: 44px;
|
||||
}
|
||||
|
||||
&.w1 {
|
||||
width: calc((var(--eachSize) * 5) + (#{$pad} * 2));
|
||||
}
|
||||
|
||||
&.w2 {
|
||||
width: calc((var(--eachSize) * 6) + (#{$pad} * 2));
|
||||
}
|
||||
|
||||
&.w3 {
|
||||
width: calc((var(--eachSize) * 7) + (#{$pad} * 2));
|
||||
}
|
||||
|
||||
&.h1 {
|
||||
--height: calc((var(--eachSize) * 4) + (#{$pad} * 2));
|
||||
}
|
||||
|
||||
&.h2 {
|
||||
--height: calc((var(--eachSize) * 6) + (#{$pad} * 2));
|
||||
}
|
||||
|
||||
&.h3 {
|
||||
--height: calc((var(--eachSize) * 8) + (#{$pad} * 2));
|
||||
}
|
||||
|
||||
> .search {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
@@ -391,17 +433,27 @@ export default defineComponent({
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
|
||||
&:not(.filled) {
|
||||
order: 1;
|
||||
z-index: 2;
|
||||
box-shadow: 0px -1px 0 0px var(--divider);
|
||||
}
|
||||
}
|
||||
|
||||
> .emojis {
|
||||
$height: 300px;
|
||||
|
||||
height: $height;
|
||||
height: var(--height);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> .index {
|
||||
min-height: $height;
|
||||
min-height: var(--height);
|
||||
position: relative;
|
||||
border-bottom: solid 1px var(--divider);
|
||||
|
||||
@@ -428,45 +480,33 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
> div {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
gap: 4px;
|
||||
padding: 8px;
|
||||
padding: $pad;
|
||||
|
||||
> button {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
width: var(--eachSize);
|
||||
height: var(--eachSize);
|
||||
border-radius: 4px;
|
||||
|
||||
&:focus {
|
||||
outline: solid 2px var(--focus);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 1px;
|
||||
height: 0;
|
||||
padding-bottom: 100%;
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
> * {
|
||||
transform: scale(1.2);
|
||||
transition: transform 0s;
|
||||
}
|
||||
&:active {
|
||||
background: var(--accent);
|
||||
box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
|
||||
}
|
||||
|
||||
> * {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
font-size: 28px;
|
||||
transition: transform 0.2s ease;
|
||||
font-size: 24px;
|
||||
height: 1.25em;
|
||||
vertical-align: -.25em;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@@ -474,6 +514,10 @@ export default defineComponent({
|
||||
|
||||
&.result {
|
||||
border-bottom: solid 1px var(--divider);
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.unicode {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<XModalWindow ref="dialog"
|
||||
:width="400"
|
||||
:width="450"
|
||||
:can-close="false"
|
||||
:with-ok-button="true"
|
||||
:ok-button-disabled="false"
|
||||
@@ -12,42 +12,61 @@
|
||||
<template #header>
|
||||
{{ title }}
|
||||
</template>
|
||||
<div class="xkpnjxcv _section">
|
||||
<label v-for="item in Object.keys(form).filter(item => !form[item].hidden)" :key="item">
|
||||
<MkInput v-if="form[item].type === 'number'" v-model:value="values[item]" type="number" :step="form[item].step || 1">
|
||||
<FormBase class="xkpnjxcv">
|
||||
<template v-for="item in Object.keys(form).filter(item => !form[item].hidden)">
|
||||
<FormInput v-if="form[item].type === 'number'" v-model:value="values[item]" type="number" :step="form[item].step || 1">
|
||||
<span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $t('optional') }})</span>
|
||||
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
|
||||
</FormInput>
|
||||
<FormInput v-else-if="form[item].type === 'string' && !form[item].multiline" v-model:value="values[item]" type="text">
|
||||
<span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $t('optional') }})</span>
|
||||
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
|
||||
</FormInput>
|
||||
<FormTextarea v-else-if="form[item].type === 'string' && form[item].multiline" v-model:value="values[item]">
|
||||
<span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $t('optional') }})</span>
|
||||
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
|
||||
</FormTextarea>
|
||||
<FormSwitch v-else-if="form[item].type === 'boolean'" v-model:value="values[item]">
|
||||
<span v-text="form[item].label || item"></span>
|
||||
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-else-if="form[item].type === 'string' && !item.multiline" v-model:value="values[item]" type="text">
|
||||
<span v-text="form[item].label || item"></span>
|
||||
</FormSwitch>
|
||||
<FormSelect v-else-if="form[item].type === 'enum'" v-model:value="values[item]">
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $t('optional') }})</span></template>
|
||||
<option v-for="item in form[item].enum" :value="item.value" :key="item.value">{{ item.label }}</option>
|
||||
</FormSelect>
|
||||
<FormRange v-else-if="form[item].type === 'range'" v-model:value="values[item]" :min="form[item].mim" :max="form[item].max" :step="form[item].step">
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $t('optional') }})</span></template>
|
||||
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
|
||||
</MkInput>
|
||||
<MkTextarea v-else-if="form[item].type === 'string' && item.multiline" v-model:value="values[item]">
|
||||
<span v-text="form[item].label || item"></span>
|
||||
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
|
||||
</MkTextarea>
|
||||
<MkSwitch v-else-if="form[item].type === 'boolean'" v-model:value="values[item]">
|
||||
<span v-text="form[item].label || item"></span>
|
||||
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
|
||||
</MkSwitch>
|
||||
</label>
|
||||
</div>
|
||||
</FormRange>
|
||||
<FormButton v-else-if="form[item].type === 'button'" @click="form[item].action($event, values)">
|
||||
<span v-text="form[item].content || item"></span>
|
||||
</FormButton>
|
||||
</template>
|
||||
</FormBase>
|
||||
</XModalWindow>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import XModalWindow from '@/components/ui/modal-window.vue';
|
||||
import MkInput from './ui/input.vue';
|
||||
import MkTextarea from './ui/textarea.vue';
|
||||
import MkSwitch from './ui/switch.vue';
|
||||
import FormBase from './form/base.vue';
|
||||
import FormInput from './form/input.vue';
|
||||
import FormTextarea from './form/textarea.vue';
|
||||
import FormSwitch from './form/switch.vue';
|
||||
import FormSelect from './form/select.vue';
|
||||
import FormRange from './form/range.vue';
|
||||
import FormButton from './form/button.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
XModalWindow,
|
||||
MkInput,
|
||||
MkTextarea,
|
||||
MkSwitch,
|
||||
FormBase,
|
||||
FormInput,
|
||||
FormTextarea,
|
||||
FormSwitch,
|
||||
FormSelect,
|
||||
FormRange,
|
||||
FormButton,
|
||||
},
|
||||
|
||||
props: {
|
||||
@@ -95,12 +114,6 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.xkpnjxcv {
|
||||
> label {
|
||||
display: block;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
56
src/client/components/form/base.vue
Normal file
56
src/client/components/form/base.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="rbusrurv" :class="{ wide: forceWide }" v-size="{ max: [400] }">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
forceWide: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rbusrurv {
|
||||
line-height: 1.4em;
|
||||
background: var(--bg);
|
||||
padding: 32px;
|
||||
|
||||
&:not(.wide).max-width_400px {
|
||||
padding: 32px 0;
|
||||
|
||||
> ::v-deep(*) {
|
||||
._formPanel {
|
||||
border: solid 0.5px var(--divider);
|
||||
border-radius: 0;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
._form_group {
|
||||
> * {
|
||||
&:not(:first-child) {
|
||||
&._formPanel, ._formPanel {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
&._formPanel, ._formPanel {
|
||||
border-bottom: solid 0.5px var(--divider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
81
src/client/components/form/button.vue
Normal file
81
src/client/components/form/button.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="yzpgjkxe _formItem">
|
||||
<div class="_formLabel"><slot name="label"></slot></div>
|
||||
<button class="main _button _formPanel _formClickable" :class="{ center, primary, danger }">
|
||||
<slot></slot>
|
||||
<div class="suffix">
|
||||
<slot name="suffix"></slot>
|
||||
<div class="icon">
|
||||
<slot name="suffixIcon"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div class="_formCaption"><slot name="desc"></slot></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import './form.scss';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
primary: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
danger: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
center: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.yzpgjkxe {
|
||||
> .main {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 14px 16px;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
|
||||
&.center {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
&.danger {
|
||||
color: #ff2a2a;
|
||||
}
|
||||
|
||||
> .suffix {
|
||||
display: inline-flex;
|
||||
margin-left: auto;
|
||||
opacity: 0.7;
|
||||
|
||||
> .icon {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
34
src/client/components/form/form.scss
Normal file
34
src/client/components/form/form.scss
Normal file
@@ -0,0 +1,34 @@
|
||||
._formPanel {
|
||||
background: var(--panel);
|
||||
border-radius: var(--radius);
|
||||
|
||||
&._formClickable {
|
||||
&:hover {
|
||||
background: var(--panelHighlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
._formLabel {
|
||||
font-size: 80%;
|
||||
padding: 0 16px 8px 16px;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
._formCaption {
|
||||
font-size: 80%;
|
||||
padding: 8px 16px 0 16px;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
._formItem {
|
||||
& + ._formItem {
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
42
src/client/components/form/group.vue
Normal file
42
src/client/components/form/group.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="vrtktovg _formItem" v-size="{ max: [500] }">
|
||||
<div class="_formLabel"><slot name="label"></slot></div>
|
||||
<div class="main _form_group">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="_formCaption"><slot name="caption"></slot></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vrtktovg {
|
||||
> .main {
|
||||
> ::v-deep(*) {
|
||||
margin: 0;
|
||||
|
||||
&:not(:first-child) {
|
||||
&._formPanel, ._formPanel {
|
||||
border-top: none;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
&._formPanel, ._formPanel {
|
||||
border-bottom: solid 0.5px var(--divider);
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
306
src/client/components/form/input.vue
Normal file
306
src/client/components/form/input.vue
Normal file
@@ -0,0 +1,306 @@
|
||||
<template>
|
||||
<div class="ztzhwixg _formItem" :class="{ inline, disabled }">
|
||||
<div class="_formLabel"><slot></slot></div>
|
||||
<div class="icon" ref="icon"><slot name="icon"></slot></div>
|
||||
<div class="input _formPanel">
|
||||
<div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div>
|
||||
<input v-if="debounce" ref="inputEl"
|
||||
v-debounce="500"
|
||||
:type="type"
|
||||
v-model.lazy="v"
|
||||
:disabled="disabled"
|
||||
:required="required"
|
||||
:readonly="readonly"
|
||||
:placeholder="placeholder"
|
||||
:pattern="pattern"
|
||||
:autocomplete="autocomplete"
|
||||
:spellcheck="spellcheck"
|
||||
:step="step"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
@keydown="onKeydown($event)"
|
||||
@input="onInput"
|
||||
:list="id"
|
||||
>
|
||||
<input v-else ref="inputEl"
|
||||
:type="type"
|
||||
v-model="v"
|
||||
:disabled="disabled"
|
||||
:required="required"
|
||||
:readonly="readonly"
|
||||
:placeholder="placeholder"
|
||||
:pattern="pattern"
|
||||
:autocomplete="autocomplete"
|
||||
:spellcheck="spellcheck"
|
||||
:step="step"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
@keydown="onKeydown($event)"
|
||||
@input="onInput"
|
||||
:list="id"
|
||||
>
|
||||
<datalist :id="id" v-if="datalist">
|
||||
<option v-for="data in datalist" :value="data"/>
|
||||
</datalist>
|
||||
<div class="suffix" ref="suffixEl"><slot name="suffix"></slot></div>
|
||||
</div>
|
||||
<button class="save _textButton" v-if="save && changed" @click="() => { changed = false; save(); }">{{ $t('save') }}</button>
|
||||
<div class="_formCaption"><slot name="desc"></slot></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
|
||||
import debounce from 'v-debounce';
|
||||
import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import './form.scss';
|
||||
|
||||
export default defineComponent({
|
||||
directives: {
|
||||
debounce
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
required: false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
},
|
||||
pattern: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
autocomplete: {
|
||||
required: false
|
||||
},
|
||||
spellcheck: {
|
||||
required: false
|
||||
},
|
||||
step: {
|
||||
required: false
|
||||
},
|
||||
debounce: {
|
||||
required: false
|
||||
},
|
||||
datalist: {
|
||||
type: Array,
|
||||
required: false,
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
save: {
|
||||
type: Function,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
emits: ['change', 'keydown', 'enter'],
|
||||
setup(props, context) {
|
||||
const { value, type, autofocus } = toRefs(props);
|
||||
const v = ref(value.value);
|
||||
const id = Math.random().toString(); // TODO: uuid?
|
||||
const focused = ref(false);
|
||||
const changed = ref(false);
|
||||
const invalid = ref(false);
|
||||
const filled = computed(() => v.value !== '' && v.value != null);
|
||||
const inputEl = ref(null);
|
||||
const prefixEl = ref(null);
|
||||
const suffixEl = ref(null);
|
||||
|
||||
const focus = () => inputEl.value.focus();
|
||||
const onInput = (ev) => {
|
||||
changed.value = true;
|
||||
context.emit('change', ev);
|
||||
};
|
||||
const onKeydown = (ev: KeyboardEvent) => {
|
||||
context.emit('keydown', ev);
|
||||
|
||||
if (ev.code === 'Enter') {
|
||||
context.emit('enter');
|
||||
}
|
||||
};
|
||||
|
||||
watch(value, newValue => {
|
||||
v.value = newValue;
|
||||
});
|
||||
|
||||
watch(v, newValue => {
|
||||
if (type?.value === 'number') {
|
||||
context.emit('update:value', parseFloat(newValue));
|
||||
} else {
|
||||
context.emit('update:value', newValue);
|
||||
}
|
||||
|
||||
invalid.value = inputEl.value.validity.badInput;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
if (autofocus.value) {
|
||||
focus();
|
||||
}
|
||||
|
||||
// このコンポーネントが作成された時、非表示状態である場合がある
|
||||
// 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する
|
||||
const clock = setInterval(() => {
|
||||
if (prefixEl.value) {
|
||||
if (prefixEl.value.offsetWidth) {
|
||||
inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px';
|
||||
}
|
||||
}
|
||||
if (suffixEl.value) {
|
||||
if (suffixEl.value.offsetWidth) {
|
||||
inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px';
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(clock);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
id,
|
||||
v,
|
||||
focused,
|
||||
invalid,
|
||||
changed,
|
||||
filled,
|
||||
inputEl,
|
||||
prefixEl,
|
||||
suffixEl,
|
||||
focus,
|
||||
onInput,
|
||||
onKeydown,
|
||||
faExclamationCircle,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ztzhwixg {
|
||||
position: relative;
|
||||
|
||||
> .icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
|
||||
&:not(:empty) + .input {
|
||||
margin-left: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
> .input {
|
||||
$height: 52px;
|
||||
position: relative;
|
||||
|
||||
> input {
|
||||
display: block;
|
||||
height: $height;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
font: inherit;
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
line-height: $height;
|
||||
color: var(--inputText);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
box-sizing: border-box;
|
||||
|
||||
&[type='file'] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
> .prefix,
|
||||
> .suffix {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
padding: 0 16px;
|
||||
font-size: 1em;
|
||||
line-height: $height;
|
||||
color: var(--inputLabel);
|
||||
pointer-events: none;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> * {
|
||||
display: inline-block;
|
||||
min-width: 16px;
|
||||
max-width: 150px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
> .prefix {
|
||||
left: 0;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
> .suffix {
|
||||
right: 0;
|
||||
padding-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> .save {
|
||||
margin: 6px 0 0 0;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
&.inline {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.7;
|
||||
|
||||
&, * {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
30
src/client/components/form/key-value-view.vue
Normal file
30
src/client/components/form/key-value-view.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="_formItem">
|
||||
<div class="_formPanel anocepby">
|
||||
<span class="key"><slot name="key"></slot></span>
|
||||
<span class="value"><slot name="value"></slot></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import './form.scss';
|
||||
|
||||
export default defineComponent({
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.anocepby {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 14px 16px;
|
||||
|
||||
> .value {
|
||||
margin-left: auto;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
90
src/client/components/form/link.vue
Normal file
90
src/client/components/form/link.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="qmfkfnzi _formItem">
|
||||
<a class="main _button _formPanel _formClickable" :href="to" target="_blank" v-if="external">
|
||||
<span class="icon"><slot name="icon"></slot></span>
|
||||
<span class="text"><slot></slot></span>
|
||||
<Fa :icon="faExternalLinkAlt" class="right"/>
|
||||
</a>
|
||||
<MkA class="main _button _formPanel _formClickable" :class="{ active }" :to="to" v-else>
|
||||
<span class="icon"><slot name="icon"></slot></span>
|
||||
<span class="text"><slot></slot></span>
|
||||
<Fa :icon="faChevronRight" class="right"/>
|
||||
</MkA>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faChevronRight, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import './form.scss';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
to: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
},
|
||||
external: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
faChevronRight, faExternalLinkAlt
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.qmfkfnzi {
|
||||
> .main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 14px 16px 14px 14px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
> .icon {
|
||||
width: 32px;
|
||||
margin-right: 2px;
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
opacity: 0.8;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
|
||||
& + .text {
|
||||
padding-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .text {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
> .right {
|
||||
margin-left: auto;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
42
src/client/components/form/pagination.vue
Normal file
42
src/client/components/form/pagination.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<FormGroup class="uljviswt _formItem">
|
||||
<template #label><slot name="label"></slot></template>
|
||||
<slot :items="items"></slot>
|
||||
<div class="empty" v-if="empty" key="_empty_">
|
||||
<slot name="empty"></slot>
|
||||
</div>
|
||||
<FormButton v-show="more" class="button" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary>
|
||||
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
|
||||
<template v-if="moreFetching"><MkLoading inline/></template>
|
||||
</FormButton>
|
||||
</FormGroup>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import FormButton from './button.vue';
|
||||
import FormGroup from './group.vue';
|
||||
import paging from '@/scripts/paging';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormButton,
|
||||
FormGroup,
|
||||
},
|
||||
|
||||
mixins: [
|
||||
paging({}),
|
||||
],
|
||||
|
||||
props: {
|
||||
pagination: {
|
||||
required: true
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.uljviswt {
|
||||
}
|
||||
</style>
|
||||
106
src/client/components/form/radios.vue
Normal file
106
src/client/components/form/radios.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, h } from 'vue';
|
||||
import MkRadio from '@/components/ui/radio.vue';
|
||||
import './form.scss';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkRadio
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
required: false
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: this.modelValue,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value() {
|
||||
this.$emit('update:modelValue', this.value);
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const label = this.$slots.desc();
|
||||
const options = this.$slots.default();
|
||||
|
||||
return h('div', {
|
||||
class: 'cnklmpwm _formItem'
|
||||
}, [
|
||||
h('div', {
|
||||
class: '_formLabel',
|
||||
}, label),
|
||||
...options.map(option => h('button', {
|
||||
class: '_button _formPanel _formClickable',
|
||||
key: option.props.value,
|
||||
onClick: () => this.value = option.props.value,
|
||||
}, [h('span', {
|
||||
class: ['check', { checked: this.value === option.props.value }],
|
||||
}), option.children]))
|
||||
]);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.cnklmpwm {
|
||||
> button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 14px 18px;
|
||||
text-align: left;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
border-top: none !important;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
&:not(:last-of-type) {
|
||||
border-bottom: solid 0.5px var(--divider);
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
> .check {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
background: none;
|
||||
border: 2px solid var(--inputBorder);
|
||||
border-radius: 100%;
|
||||
transition: inherit;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 3px;
|
||||
bottom: 3px;
|
||||
left: 3px;
|
||||
border-radius: 100%;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transition: .4s cubic-bezier(.25,.8,.25,1);
|
||||
}
|
||||
|
||||
&.checked {
|
||||
border-color: var(--accent);
|
||||
|
||||
&:after {
|
||||
background-color: var(--accent);
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
122
src/client/components/form/range.vue
Normal file
122
src/client/components/form/range.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<div class="ifitouly _formItem" :class="{ focused, disabled }">
|
||||
<div class="_formLabel"><slot name="label"></slot></div>
|
||||
<div class="_formPanel main">
|
||||
<input
|
||||
type="range"
|
||||
ref="input"
|
||||
v-model="v"
|
||||
:disabled="disabled"
|
||||
:min="min"
|
||||
:max="max"
|
||||
:step="step"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
@input="$emit('update:value', $event.target.value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="_formCaption"><slot name="caption"></slot></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
min: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 100
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 1
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
v: this.value,
|
||||
focused: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(v) {
|
||||
this.v = parseFloat(v);
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ifitouly {
|
||||
position: relative;
|
||||
|
||||
> .main {
|
||||
padding: 24px 16px;
|
||||
|
||||
> input {
|
||||
display: block;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
background: var(--X10);
|
||||
height: 4px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
147
src/client/components/form/select.vue
Normal file
147
src/client/components/form/select.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<div class="yrtfrpux _formItem" :class="{ disabled, inline }">
|
||||
<div class="_formLabel"><slot name="label"></slot></div>
|
||||
<div class="icon" ref="icon"><slot name="icon"></slot></div>
|
||||
<div class="input _formPanel _formClickable" @click="focus">
|
||||
<div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
|
||||
<select ref="input"
|
||||
v-model="v"
|
||||
:required="required"
|
||||
:disabled="disabled"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
>
|
||||
<slot></slot>
|
||||
</select>
|
||||
<div class="suffix">
|
||||
<Fa :icon="faChevronDown"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="_formCaption"><slot name="caption"></slot></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
|
||||
import './form.scss';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
required: false
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
faChevronDown,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
v: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(v) {
|
||||
this.$emit('update:value', v);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
focus() {
|
||||
this.$refs.input.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.yrtfrpux {
|
||||
position: relative;
|
||||
|
||||
> .icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
|
||||
&:not(:empty) + .input {
|
||||
margin-left: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
> .input {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
> select {
|
||||
display: block;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 0 16px;
|
||||
font: inherit;
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
height: 52px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
color: var(--fg);
|
||||
|
||||
option,
|
||||
optgroup {
|
||||
color: var(--fg);
|
||||
background: var(--bg);
|
||||
}
|
||||
}
|
||||
|
||||
> .prefix,
|
||||
> .suffix {
|
||||
display: block;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
font-size: 1em;
|
||||
line-height: 32px;
|
||||
color: var(--inputLabel);
|
||||
pointer-events: none;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> * {
|
||||
display: block;
|
||||
min-width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
> .prefix {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
> .suffix {
|
||||
padding: 0 16px 0 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
132
src/client/components/form/switch.vue
Normal file
132
src/client/components/form/switch.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div class="ijnpvmgr _formItem">
|
||||
<div class="main _formPanel _formClickable"
|
||||
:class="{ disabled, checked }"
|
||||
:aria-checked="checked"
|
||||
:aria-disabled="disabled"
|
||||
@click.prevent="toggle"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
ref="input"
|
||||
:disabled="disabled"
|
||||
@keydown.enter="toggle"
|
||||
>
|
||||
<span class="button">
|
||||
<span></span>
|
||||
</span>
|
||||
<span class="label">
|
||||
<span><slot></slot></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="_formCaption"><slot name="desc"></slot></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import './form.scss';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
checked(): boolean {
|
||||
return this.value;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
if (this.disabled) return;
|
||||
this.$emit('update:value', !this.checked);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ijnpvmgr {
|
||||
> .main {
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
|
||||
> * {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&.checked {
|
||||
> .button {
|
||||
background-color: var(--X10);
|
||||
border-color: var(--X10);
|
||||
|
||||
> * {
|
||||
background-color: var(--accent);
|
||||
transform: translateX(14px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> input {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
> .button {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
margin: 3px 0 0 0;
|
||||
width: 34px;
|
||||
height: 14px;
|
||||
background: var(--X6);
|
||||
outline: none;
|
||||
border-radius: 14px;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
|
||||
> * {
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
left: 0;
|
||||
border-radius: 100%;
|
||||
transition: background-color 0.3s, transform 0.3s;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 1px -1px rgba(#000, 0.2), 0 1px 1px 0 rgba(#000, 0.14), 0 1px 3px 0 rgba(#000, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
> .label {
|
||||
margin-left: 12px;
|
||||
display: block;
|
||||
transition: inherit;
|
||||
color: var(--fg);
|
||||
|
||||
> span {
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
transition: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
136
src/client/components/form/textarea.vue
Normal file
136
src/client/components/form/textarea.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div class="rivhosbp _formItem" :class="{ tall, pre }">
|
||||
<div class="_formLabel"><slot></slot></div>
|
||||
<div class="input _formPanel">
|
||||
<textarea ref="input" :class="{ code, _monospace: code }"
|
||||
:value="value"
|
||||
:required="required"
|
||||
:readonly="readonly"
|
||||
:pattern="pattern"
|
||||
:autocomplete="autocomplete"
|
||||
:spellcheck="!code"
|
||||
@input="onInput"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
></textarea>
|
||||
</div>
|
||||
<button class="save _textButton" v-if="save && changed" @click="() => { changed = false; save(); }">{{ $t('save') }}</button>
|
||||
<div class="_formCaption"><slot name="desc"></slot></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import './form.scss';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
required: false
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
},
|
||||
pattern: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
autocomplete: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
code: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
},
|
||||
tall: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
pre: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
save: {
|
||||
type: Function,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
changed: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
focus() {
|
||||
this.$refs.input.focus();
|
||||
},
|
||||
onInput(ev) {
|
||||
this.changed = true;
|
||||
this.$emit('update:value', ev.target.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rivhosbp {
|
||||
position: relative;
|
||||
|
||||
> .input {
|
||||
position: relative;
|
||||
|
||||
> textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 130px;
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
font: inherit;
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
color: var(--fg);
|
||||
|
||||
&.code {
|
||||
tab-size: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .save {
|
||||
margin: 6px 0 0 0;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
&.tall {
|
||||
> .input {
|
||||
> textarea {
|
||||
min-height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.pre {
|
||||
> .input {
|
||||
> textarea {
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
36
src/client/components/form/tuple.vue
Normal file
36
src/client/components/form/tuple.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="wthhikgt _formItem" v-size="{ max: [500] }">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wthhikgt {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
> ::v-deep(*) {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&.max-width_500px {
|
||||
display: block;
|
||||
|
||||
> ::v-deep(*) {
|
||||
margin: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
152
src/client/components/launch-pad.vue
Normal file
152
src/client/components/launch-pad.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<MkModal ref="modal" @click="$refs.modal.close()" @closed="$emit('closed')">
|
||||
<div class="szkkfdyq _popup">
|
||||
<div class="main">
|
||||
<template v-for="item in items">
|
||||
<button v-if="item.action" class="_button" @click="$event => { item.action($event); close(); }">
|
||||
<Fa :icon="item.icon" class="icon"/>
|
||||
<div class="text">{{ item.text }}</div>
|
||||
<i v-if="item.indicate"><Fa :icon="faCircle"/></i>
|
||||
</button>
|
||||
<MkA v-else :to="item.to" @click.passive="close()">
|
||||
<Fa :icon="item.icon" class="icon"/>
|
||||
<div class="text">{{ item.text }}</div>
|
||||
<i v-if="item.indicate"><Fa :icon="faCircle"/></i>
|
||||
</MkA>
|
||||
</template>
|
||||
</div>
|
||||
<div class="sub">
|
||||
<MkA to="/docs" @click.passive="close()">
|
||||
<Fa :icon="faQuestionCircle" class="icon"/>
|
||||
<div class="text">{{ $t('help') }}</div>
|
||||
</MkA>
|
||||
<MkA to="/about" @click.passive="close()">
|
||||
<Fa :icon="faInfoCircle" class="icon"/>
|
||||
<div class="text">{{ $t('aboutX', { x: instanceName }) }}</div>
|
||||
</MkA>
|
||||
<MkA to="/about-misskey" @click.passive="close()">
|
||||
<Fa :icon="faInfoCircle" class="icon"/>
|
||||
<div class="text">{{ $t('aboutMisskey') }}</div>
|
||||
</MkA>
|
||||
</div>
|
||||
</div>
|
||||
</MkModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faQuestionCircle, faInfoCircle, faCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkModal from '@/components/ui/modal.vue';
|
||||
import { sidebarDef } from '@/sidebar';
|
||||
import { instanceName } from '@/config';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkModal,
|
||||
},
|
||||
|
||||
emits: ['closed'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
menuDef: sidebarDef,
|
||||
items: [],
|
||||
instanceName,
|
||||
faQuestionCircle, faInfoCircle, faCircle,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
menu(): string[] {
|
||||
return this.$store.state.deviceUser.menu;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.items = Object.keys(this.menuDef).filter(k => !this.menu.includes(k)).map(k => this.menuDef[k]).filter(def => def.show == null ? true : def.show).map(def => ({
|
||||
type: def.to ? 'link' : 'button',
|
||||
text: this.$t(def.title),
|
||||
icon: def.icon,
|
||||
to: def.to,
|
||||
action: def.action,
|
||||
indicate: def.indicated,
|
||||
}));
|
||||
},
|
||||
|
||||
methods: {
|
||||
close() {
|
||||
this.$refs.modal.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.szkkfdyq {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
max-width: 800px;
|
||||
padding: 32px;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
text-align: center;
|
||||
border-radius: 16px;
|
||||
|
||||
@media (max-width: 500px) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
> .main, > .sub {
|
||||
> * {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
border-radius: var(--radius);
|
||||
|
||||
@media (max-width: 500px) {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
> .icon {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
> .text {
|
||||
margin-top: 8px;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
> i {
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
left: 32px;
|
||||
color: var(--indicator);
|
||||
font-size: 8px;
|
||||
animation: blink 1s infinite;
|
||||
|
||||
@media (max-width: 500px) {
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .sub {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: solid 1px var(--divider);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -68,7 +68,7 @@ export default defineComponent({
|
||||
created() {
|
||||
// Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする
|
||||
this.$watch('image', () => {
|
||||
this.hide = this.image.isSensitive && !this.$store.state.device.alwaysShowNsfw;
|
||||
this.hide = (this.$store.state.device.nsfw === 'force') ? true : this.image.isSensitive && (this.$store.state.device.nsfw !== 'ignore');
|
||||
if (this.image.blurhash) {
|
||||
this.color = extractAvgColorFromBlurhash(this.image.blurhash);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.hide = this.video.isSensitive && !this.$store.state.device.alwaysShowNsfw;
|
||||
this.hide = (this.$store.state.device.nsfw === 'force') ? true : this.video.isSensitive && (this.$store.state.device.nsfw !== 'ignore');
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -130,9 +130,13 @@ export default defineComponent({
|
||||
break;
|
||||
}
|
||||
}
|
||||
return h('span', {
|
||||
style: 'display: inline-block;' + style,
|
||||
}, genEl(token.children));
|
||||
if (style == null) {
|
||||
return h('span', {}, ['[', token.node.props.name, ...genEl(token.children), ']']);
|
||||
} else {
|
||||
return h('span', {
|
||||
style: 'display: inline-block;' + style,
|
||||
}, genEl(token.children));
|
||||
}
|
||||
}
|
||||
|
||||
case 'small': {
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineAsyncComponent, defineComponent, markRaw, ref } from 'vue';
|
||||
import { faSatelliteDish, faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faSatelliteDish, faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug, faExclamationCircle, faPaperclip } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faCopy, faTrashAlt, faEdit, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
|
||||
import { parse } from '../../mfm/parse';
|
||||
import { sum, unique } from '../../prelude/array';
|
||||
@@ -498,36 +498,20 @@ export default defineComponent({
|
||||
react(viaKeyboard = false) {
|
||||
pleaseLogin();
|
||||
this.blur();
|
||||
if (this.$store.state.device.useFullReactionPicker) {
|
||||
os.popup(import('@/components/emoji-picker.vue'), {
|
||||
src: this.$refs.reactButton,
|
||||
}, {
|
||||
done: reaction => {
|
||||
if (reaction) {
|
||||
os.api('notes/reactions/create', {
|
||||
noteId: this.appearNote.id,
|
||||
reaction: reaction
|
||||
});
|
||||
}
|
||||
this.focus();
|
||||
},
|
||||
}, 'closed');
|
||||
} else {
|
||||
os.popup(import('@/components/reaction-picker.vue'), {
|
||||
showFocus: viaKeyboard,
|
||||
src: this.$refs.reactButton,
|
||||
}, {
|
||||
done: reaction => {
|
||||
if (reaction) {
|
||||
os.api('notes/reactions/create', {
|
||||
noteId: this.appearNote.id,
|
||||
reaction: reaction
|
||||
});
|
||||
}
|
||||
this.focus();
|
||||
},
|
||||
}, 'closed');
|
||||
}
|
||||
os.popup(import('@/components/emoji-picker.vue'), {
|
||||
src: this.$refs.reactButton,
|
||||
asReactionPicker: true
|
||||
}, {
|
||||
done: reaction => {
|
||||
if (reaction) {
|
||||
os.api('notes/reactions/create', {
|
||||
noteId: this.appearNote.id,
|
||||
reaction: reaction
|
||||
});
|
||||
}
|
||||
this.focus();
|
||||
},
|
||||
}, 'closed');
|
||||
},
|
||||
|
||||
reactDirectly(reaction) {
|
||||
@@ -626,6 +610,11 @@ export default defineComponent({
|
||||
text: this.$t('favorite'),
|
||||
action: () => this.toggleFavorite(true)
|
||||
}),
|
||||
{
|
||||
icon: faPaperclip,
|
||||
text: this.$t('clip'),
|
||||
action: () => this.clip()
|
||||
},
|
||||
(this.appearNote.userId != this.$store.state.i.id) ? statePromise.then(state => state.isWatching ? {
|
||||
icon: faEyeSlash,
|
||||
text: this.$t('unwatch'),
|
||||
@@ -778,6 +767,44 @@ export default defineComponent({
|
||||
});
|
||||
},
|
||||
|
||||
async clip() {
|
||||
const clips = await os.api('clips/list');
|
||||
os.modalMenu([{
|
||||
icon: faPlus,
|
||||
text: this.$t('createNew'),
|
||||
action: async () => {
|
||||
const { canceled, result } = await os.form(this.$t('createNewClip'), {
|
||||
name: {
|
||||
type: 'string',
|
||||
label: this.$t('name')
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
multiline: true,
|
||||
label: this.$t('description')
|
||||
},
|
||||
isPublic: {
|
||||
type: 'boolean',
|
||||
label: this.$t('public'),
|
||||
default: false
|
||||
}
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
const clip = await os.apiWithDialog('clips/create', result);
|
||||
|
||||
os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id });
|
||||
}
|
||||
}, null, ...clips.map(clip => ({
|
||||
text: clip.name,
|
||||
action: () => {
|
||||
os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id });
|
||||
}
|
||||
}))], this.$refs.menuButton, {
|
||||
}).then(this.focus);
|
||||
},
|
||||
|
||||
async promote() {
|
||||
const { canceled, result: days } = await os.dialog({
|
||||
title: this.$t('numberOfDays'),
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<MkError v-if="error" @retry="init()"/>
|
||||
|
||||
<div v-show="more && reversed" style="margin-bottom: var(--margin);">
|
||||
<button class="_loadMore" v-appear="$store.state.device.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
|
||||
<button class="_loadMore" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
|
||||
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
|
||||
<template v-if="moreFetching"><MkLoading inline/></template>
|
||||
</button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1">
|
||||
<MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj _panel" tabindex="-1">
|
||||
<div class="thumbnail" v-if="page.eyeCatchingImage" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div>
|
||||
<article>
|
||||
<header>
|
||||
@@ -35,16 +35,11 @@ export default defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
.vhpxefrj {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
border: solid var(--lineWidth) var(--urlPreviewBorder);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--divider);
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
border-color: var(--urlPreviewBorderHover);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
> .thumbnail {
|
||||
|
||||
@@ -18,10 +18,11 @@ import XPost from './page.post.vue';
|
||||
import XCounter from './page.counter.vue';
|
||||
import XRadioButton from './page.radio-button.vue';
|
||||
import XCanvas from './page.canvas.vue';
|
||||
import XNote from './page.note.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter, XRadioButton, XCanvas
|
||||
XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter, XRadioButton, XCanvas, XNote
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
|
||||
39
src/client/components/page/page.note.vue
Normal file
39
src/client/components/page/page.note.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="voxdxuby">
|
||||
<XNote v-if="note" v-model:note="note" :key="note.id" :detail="value.detailed"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import XNote from '@/components/note.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
XNote
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
required: true
|
||||
},
|
||||
hpml: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
note: null,
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
this.note = await os.api('notes/show', { noteId: this.value.note });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.voxdxuby {
|
||||
margin: 1em 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,214 +0,0 @@
|
||||
<template>
|
||||
<MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')">
|
||||
<div class="rdfaahpb _popup" v-hotkey="keymap">
|
||||
<div class="buttons" ref="buttons" :class="{ showFocus }">
|
||||
<button class="_button" v-for="(reaction, i) in rs" :key="reaction" @click="react(reaction)" :tabindex="i + 1" :title="reaction" v-particle><XReactionIcon :reaction="reaction"/></button>
|
||||
</div>
|
||||
<input class="text" ref="text" v-model.trim="text" :placeholder="$t('enterEmoji')" @keyup.enter="reactText" @input="tryReactText">
|
||||
</div>
|
||||
</MkModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { emojiRegex } from '../../misc/emoji-regex';
|
||||
import XReactionIcon from '@/components/reaction-icon.vue';
|
||||
import MkModal from '@/components/ui/modal.vue';
|
||||
import { Autocomplete } from '@/scripts/autocomplete';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
XReactionIcon,
|
||||
MkModal,
|
||||
},
|
||||
|
||||
props: {
|
||||
reactions: {
|
||||
required: false
|
||||
},
|
||||
|
||||
showFocus: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
|
||||
src: {
|
||||
required: false
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['done', 'closed'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
rs: this.reactions || this.$store.state.settings.reactions,
|
||||
text: null,
|
||||
focus: null
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
keymap(): any {
|
||||
return {
|
||||
'esc': this.close,
|
||||
'enter|space|plus': this.choose,
|
||||
'up|k': this.focusUp,
|
||||
'left|h|shift+tab': this.focusLeft,
|
||||
'right|l|tab': this.focusRight,
|
||||
'down|j': this.focusDown,
|
||||
'1': () => this.react(this.rs[0]),
|
||||
'2': () => this.react(this.rs[1]),
|
||||
'3': () => this.react(this.rs[2]),
|
||||
'4': () => this.react(this.rs[3]),
|
||||
'5': () => this.react(this.rs[4]),
|
||||
'6': () => this.react(this.rs[5]),
|
||||
'7': () => this.react(this.rs[6]),
|
||||
'8': () => this.react(this.rs[7]),
|
||||
'9': () => this.react(this.rs[8]),
|
||||
'0': () => this.react(this.rs[9]),
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
focus(i) {
|
||||
this.$refs.buttons.children[i].focus({
|
||||
preventScroll: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.focus = 0;
|
||||
});
|
||||
|
||||
// TODO: detach when unmount
|
||||
new Autocomplete(this.$refs.text, this, { model: 'text' });
|
||||
},
|
||||
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit('done');
|
||||
this.$refs.modal.close();
|
||||
},
|
||||
|
||||
react(reaction) {
|
||||
this.$emit('done', reaction);
|
||||
this.$refs.modal.close();
|
||||
},
|
||||
|
||||
reactText() {
|
||||
if (!this.text) return;
|
||||
this.react(this.text);
|
||||
},
|
||||
|
||||
tryReactText() {
|
||||
if (!this.text) return;
|
||||
if (!this.text.match(emojiRegex)) return;
|
||||
this.reactText();
|
||||
},
|
||||
|
||||
focusUp() {
|
||||
this.focus = this.focus == 0 ? 9 : this.focus < 5 ? (this.focus + 4) : (this.focus - 5);
|
||||
},
|
||||
|
||||
focusDown() {
|
||||
this.focus = this.focus == 9 ? 0 : this.focus >= 5 ? (this.focus - 4) : (this.focus + 5);
|
||||
},
|
||||
|
||||
focusRight() {
|
||||
this.focus = this.focus == 9 ? 0 : (this.focus + 1);
|
||||
},
|
||||
|
||||
focusLeft() {
|
||||
this.focus = this.focus == 0 ? 9 : (this.focus - 1);
|
||||
},
|
||||
|
||||
choose() {
|
||||
this.$refs.buttons.children[this.focus].click();
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rdfaahpb {
|
||||
> .buttons {
|
||||
padding: 6px 6px 0 6px;
|
||||
width: 212px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
|
||||
@media (max-width: 1025px) {
|
||||
padding: 8px 8px 0 8px;
|
||||
width: 256px;
|
||||
}
|
||||
|
||||
&.showFocus {
|
||||
> button:focus {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
border: 2px solid var(--focus);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> button {
|
||||
padding: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 24px;
|
||||
border-radius: 2px;
|
||||
|
||||
@media (max-width: 1025px) {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
> * {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--accent);
|
||||
box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .text {
|
||||
width: 208px;
|
||||
padding: 8px;
|
||||
margin: 0 0 6px 0;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
|
||||
@media (max-width: 1025px) {
|
||||
width: 256px;
|
||||
margin: 4px 0 8px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -45,7 +45,7 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { faGripVertical, faChevronLeft, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faListUl, faPlus, faUserClock, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faInfoCircle, faQuestionCircle, faProjectDiagram, faStream, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faBell, faEnvelope, faLaugh, faComments } from '@fortawesome/free-regular-svg-icons';
|
||||
import { host, instanceName } from '@/config';
|
||||
import { host } from '@/config';
|
||||
import { search } from '@/scripts/search';
|
||||
import * as os from '@/os';
|
||||
import { sidebarDef } from '@/sidebar';
|
||||
@@ -223,30 +223,8 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
more(ev) {
|
||||
const items = Object.keys(this.menuDef).filter(k => !this.menu.includes(k)).map(k => this.menuDef[k]).filter(def => def.show == null ? true : def.show).map(def => ({
|
||||
type: def.to ? 'link' : 'button',
|
||||
text: this.$t(def.title),
|
||||
icon: def.icon,
|
||||
to: def.to,
|
||||
action: def.action,
|
||||
indicate: def.indicated,
|
||||
}));
|
||||
os.modalMenu([...items, null, {
|
||||
type: 'link',
|
||||
text: this.$t('help'),
|
||||
to: '/docs',
|
||||
icon: faQuestionCircle,
|
||||
}, {
|
||||
type: 'link',
|
||||
text: this.$t('aboutX', { x: instanceName }),
|
||||
to: '/about',
|
||||
icon: faInfoCircle,
|
||||
}, {
|
||||
type: 'link',
|
||||
text: this.$t('aboutMisskey'),
|
||||
to: '/about-misskey',
|
||||
icon: faInfoCircle,
|
||||
}], ev.currentTarget || ev.target);
|
||||
os.popup(import('./launch-pad.vue'), {}, {
|
||||
}, 'closed');
|
||||
},
|
||||
|
||||
addAcount() {
|
||||
|
||||
@@ -1,26 +1,32 @@
|
||||
<template>
|
||||
<div class="pxhvhrfw" v-size="{ max: [500] }">
|
||||
<button v-for="item in items" class="_button" @click="$emit('update:value', item.value)" :class="{ active: value === item.value }" :disabled="value === item.value" :key="item.value"><Fa v-if="item.icon" :icon="item.icon" class="icon"/>{{ item.label }}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent, h, resolveDirective, withDirectives } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
render() {
|
||||
const options = this.$slots.default();
|
||||
|
||||
return withDirectives(h('div', {
|
||||
class: 'pxhvhrfw',
|
||||
}, options.map(option => h('button', {
|
||||
class: ['_button', { active: this.value === option.props.value }],
|
||||
key: option.props.value,
|
||||
disabled: this.value === option.props.value,
|
||||
onClick: () => {
|
||||
this.$emit('update:value', option.props.value);
|
||||
}
|
||||
}, option.children))), [
|
||||
[resolveDirective('size'), { max: [500] }]
|
||||
]);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
.pxhvhrfw {
|
||||
display: flex;
|
||||
|
||||
|
||||
@@ -9,10 +9,13 @@
|
||||
<template #header>Req Viewer</template>
|
||||
|
||||
<div class="rlkneywz">
|
||||
<MkTab v-model:value="tab" :items="[{ label: 'Request', value: 'req', }, { label: 'Response', value: 'res', }]" style="border-bottom: solid 1px var(--divider);"/>
|
||||
<MkTab v-model:value="tab" style="border-bottom: solid 1px var(--divider);">
|
||||
<option value="req">Request</option>
|
||||
<option value="res">Response</option>
|
||||
</MkTab>
|
||||
|
||||
<code v-if="tab === 'req'">{{ reqStr }}</code>
|
||||
<code v-if="tab === 'res'">{{ resStr }}</code>
|
||||
<code v-if="tab === 'req'" class="_monospace">{{ reqStr }}</code>
|
||||
<code v-if="tab === 'res'" class="_monospace">{{ resStr }}</code>
|
||||
</div>
|
||||
</XWindow>
|
||||
</template>
|
||||
@@ -64,7 +67,6 @@ export default defineComponent({
|
||||
font-size: 0.9em;
|
||||
tab-size: 2;
|
||||
white-space: pre;
|
||||
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,8 +3,13 @@
|
||||
<template #header>
|
||||
<Fa :icon="faTerminal" style="margin-right: 0.5em;"/>Task Manager
|
||||
</template>
|
||||
<div class="qljqmnzj">
|
||||
<MkTab v-model:value="tab" :items="[{ label: 'Windows', value: 'windows', }, { label: 'Stream', value: 'stream', }, { label: 'Stream (Pool)', value: 'streamPool', }, { label: 'API', value: 'api', }]" style="border-bottom: solid 1px var(--divider);"/>
|
||||
<div class="qljqmnzj _monospace">
|
||||
<MkTab v-model:value="tab" style="border-bottom: solid 1px var(--divider);">
|
||||
<option value="windows">Windows</option>
|
||||
<option value="stream">Stream</option>
|
||||
<option value="streamPool">Stream (Pool)</option>
|
||||
<option value="api">API</option>
|
||||
</MkTab>
|
||||
|
||||
<div class="content">
|
||||
<div v-if="tab === 'windows'" class="windows" v-follow>
|
||||
@@ -145,7 +150,6 @@ export default defineComponent({
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
|
||||
|
||||
> .content {
|
||||
flex: 1;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import XNotes from './notes.vue';
|
||||
import * as os from '@/os';
|
||||
import * as sound from '@/scripts/sound';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -65,7 +66,7 @@ export default defineComponent({
|
||||
this.$emit('note');
|
||||
|
||||
if (this.sound) {
|
||||
os.sound(note.userId === this.$store.state.i.id ? 'noteMy' : 'note');
|
||||
sound.play(note.userId === this.$store.state.i.id ? 'noteMy' : 'note');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -51,7 +50,7 @@ export default defineComponent({
|
||||
.novjtctn {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin: 16px 32px 0 0;
|
||||
margin: 8px 20px 0 0;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
55
src/client/components/ui/radios.vue
Normal file
55
src/client/components/ui/radios.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, h } from 'vue';
|
||||
import MkRadio from '@/components/ui/radio.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkRadio
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
required: false
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: this.modelValue,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value() {
|
||||
this.$emit('update:modelValue', this.value);
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const label = this.$slots.desc();
|
||||
const options = this.$slots.default();
|
||||
|
||||
return h('div', {
|
||||
class: 'novjtcto'
|
||||
}, [
|
||||
h('div', label),
|
||||
...options.map(option => h(MkRadio, {
|
||||
key: option.props.value,
|
||||
value: option.props.value,
|
||||
modelValue: this.value,
|
||||
'onUpdate:modelValue': value => this.value = value,
|
||||
}, option.children))
|
||||
]);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.novjtcto {
|
||||
margin: 32px 0;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="timctyfi" :class="{ focused, disabled }">
|
||||
<div class="icon"><slot name="icon"></slot></div>
|
||||
<span class="title"><slot name="title"></slot></span>
|
||||
<span class="label"><slot name="label"></slot></span>
|
||||
<input
|
||||
type="range"
|
||||
ref="input"
|
||||
@@ -19,7 +19,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';import * as os from '@/os';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
||||
@@ -17,10 +17,8 @@
|
||||
<span></span>
|
||||
</span>
|
||||
<span class="label">
|
||||
<span :aria-hidden="!checked"><slot></slot></span>
|
||||
<p :aria-hidden="!checked">
|
||||
<slot name="desc"></slot>
|
||||
</p>
|
||||
<span><slot></slot></span>
|
||||
<p><slot name="desc"></slot></p>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="adhpbeos" :class="{ focused, filled, tall, pre }">
|
||||
<div class="input">
|
||||
<span class="label" ref="label"><slot></slot></span>
|
||||
<textarea ref="input" :class="{ code }"
|
||||
<textarea ref="input" :class="{ code, _monospace: code }"
|
||||
:value="value"
|
||||
:required="required"
|
||||
:readonly="readonly"
|
||||
@@ -166,7 +166,6 @@ export default defineComponent({
|
||||
|
||||
&.code {
|
||||
tab-size: 2;
|
||||
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ import { router } from './router';
|
||||
import { applyTheme } from '@/scripts/theme';
|
||||
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode';
|
||||
import { i18n, lang } from './i18n';
|
||||
import { stream, sound, isMobile, dialog } from '@/os';
|
||||
import { stream, isMobile, dialog } from '@/os';
|
||||
import * as sound from './scripts/sound';
|
||||
|
||||
console.info(`Misskey v${version}`);
|
||||
|
||||
@@ -50,7 +51,7 @@ if (_DEV_) {
|
||||
document.addEventListener('touchend', () => {}, { passive: true });
|
||||
|
||||
if (localStorage.getItem('theme') == null) {
|
||||
applyTheme(require('@/themes/l-white.json5'));
|
||||
applyTheme(require('@/themes/l-light.json5'));
|
||||
}
|
||||
|
||||
//#region SEE: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
|
||||
@@ -307,7 +308,7 @@ if (store.getters.isSignedIn) {
|
||||
hasUnreadMessagingMessage: true
|
||||
});
|
||||
|
||||
sound('chatBg');
|
||||
sound.play('chatBg');
|
||||
});
|
||||
|
||||
main.on('readAllAntennas', () => {
|
||||
@@ -321,7 +322,7 @@ if (store.getters.isSignedIn) {
|
||||
hasUnreadAntenna: true
|
||||
});
|
||||
|
||||
sound('antenna');
|
||||
sound.play('antenna');
|
||||
});
|
||||
|
||||
main.on('readAllAnnouncements', () => {
|
||||
@@ -341,7 +342,7 @@ if (store.getters.isSignedIn) {
|
||||
hasUnreadChannel: true
|
||||
});
|
||||
|
||||
sound('channel');
|
||||
sound.play('channel');
|
||||
});
|
||||
|
||||
main.on('readAllAnnouncements', () => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { apiUrl, debug } from '@/config';
|
||||
import MkPostFormDialog from '@/components/post-form-dialog.vue';
|
||||
import MkWaitingDialog from '@/components/waiting-dialog.vue';
|
||||
import { resolve } from '@/router';
|
||||
import { device } from './cold-storage';
|
||||
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
export const isMobile = /mobile|iphone|ipad|android/.test(ua);
|
||||
@@ -275,10 +276,11 @@ export async function selectDriveFolder(multiple: boolean) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function pickEmoji(src?: HTMLElement) {
|
||||
export async function pickEmoji(src?: HTMLElement, opts) {
|
||||
return new Promise((resolve, reject) => {
|
||||
popup(import('@/components/emoji-picker.vue'), {
|
||||
src
|
||||
src,
|
||||
...opts
|
||||
}, {
|
||||
done: emoji => {
|
||||
resolve(emoji);
|
||||
@@ -343,15 +345,6 @@ export function post(props: Record<string, any>) {
|
||||
});
|
||||
}
|
||||
|
||||
export function sound(type: string) {
|
||||
if (store.state.device.sfxVolume === 0) return;
|
||||
const sound = store.state.device['sfx' + type.substr(0, 1).toUpperCase() + type.substr(1)];
|
||||
if (sound == null) return;
|
||||
const audio = new Audio(`/assets/sounds/${sound}.mp3`);
|
||||
audio.volume = store.state.device.sfxVolume;
|
||||
audio.play();
|
||||
}
|
||||
|
||||
export const deckGlobalEvents = new EventEmitter();
|
||||
|
||||
export const uploads = ref([]);
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<section class="_section">
|
||||
<div class="_content">
|
||||
<div class="_card">
|
||||
<div class="_title"><Mfm text="<motion>❤</motion>"/> {{ $t('patrons') }}</div>
|
||||
<div class="_title"><Mfm text="[jelly ❤]"/> {{ $t('patrons') }}</div>
|
||||
<div class="_content">
|
||||
<ul style="margin: 0;">
|
||||
<li>Gargron</li>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="_section">
|
||||
<MkPagination :pagination="pagination" #default="{items}" class="ruryvtyk _content" ref="list">
|
||||
<MkPagination :pagination="pagination" #default="{items}" class="ruryvtyk _content">
|
||||
<section class="_card announcement _vMargin" v-for="(announcement, i) in items" :key="announcement.id">
|
||||
<div class="_title"><span v-if="$store.getters.isSignedIn && !announcement.isRead">🆕 </span>{{ announcement.title }}</div>
|
||||
<div class="_content">
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<XPostForm :channel="channel" class="post-form _content _panel _vMargin" fixed/>
|
||||
<XPostForm :channel="channel" class="post-form _content _panel _vMargin" fixed v-if="this.$store.getters.isSignedIn"/>
|
||||
|
||||
<XTimeline class="_content _vMargin" src="channel" :channel="channelId" @before="before" @after="after"/>
|
||||
</div>
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="_section" style="padding: 0;">
|
||||
<MkTab class="_content" v-model:value="tab" :items="[{ label: $t('_channel.featured'), value: 'featured', icon: faFireAlt }, { label: $t('_channel.following'), value: 'following', icon: faHeart }, { label: $t('_channel.owned'), value: 'owned', icon: faEdit }]"/>
|
||||
<div class="_section" style="padding: 0;" v-if="this.$store.getters.isSignedIn">
|
||||
<MkTab class="_content" v-model:value="tab">
|
||||
<option value="featured"><Fa :icon="faFireAlt"/> {{ $t('_channel.featured') }}</option>
|
||||
<option value="following"><Fa :icon="faHeart"/> {{ $t('_channel.following') }}</option>
|
||||
<option value="owned"><Fa :icon="faEdit"/> {{ $t('_channel.owned') }}</option>
|
||||
</MkTab>
|
||||
</div>
|
||||
|
||||
<div class="_section">
|
||||
<div class="_content grwlizim featured" v-if="tab === 'featured'">
|
||||
<MkPagination :pagination="featuredPagination" #default="{items}">
|
||||
<MkChannelPreview v-for="channel in items" class="uveselbe" :channel="channel" :key="channel.id"/>
|
||||
<MkChannelPreview v-for="channel in items" class="_vMargin" :channel="channel" :key="channel.id"/>
|
||||
</MkPagination>
|
||||
</div>
|
||||
|
||||
<div class="_content grwlizim following" v-if="tab === 'following'">
|
||||
<MkPagination :pagination="followingPagination" #default="{items}">
|
||||
<MkChannelPreview v-for="channel in items" class="uveselbe" :channel="channel" :key="channel.id"/>
|
||||
<MkChannelPreview v-for="channel in items" class="_vMargin" :channel="channel" :key="channel.id"/>
|
||||
</MkPagination>
|
||||
</div>
|
||||
|
||||
<div class="_content grwlizim owned" v-if="tab === 'owned'">
|
||||
<MkButton class="new" @click="create()"><Fa :icon="faPlus"/></MkButton>
|
||||
<MkPagination :pagination="ownedPagination" #default="{items}">
|
||||
<MkChannelPreview v-for="channel in items" class="uveselbe" :channel="channel" :key="channel.id"/>
|
||||
<MkChannelPreview v-for="channel in items" class="_vMargin" :channel="channel" :key="channel.id"/>
|
||||
</MkPagination>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,7 +48,11 @@ export default defineComponent({
|
||||
return {
|
||||
INFO: {
|
||||
title: this.$t('channel'),
|
||||
icon: faSatelliteDish
|
||||
icon: faSatelliteDish,
|
||||
action: {
|
||||
icon: faPlus,
|
||||
handler: this.create
|
||||
}
|
||||
},
|
||||
tab: 'featured',
|
||||
featuredPagination: {
|
||||
@@ -69,23 +77,3 @@ export default defineComponent({
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.grwlizim {
|
||||
padding: 16px 0;
|
||||
|
||||
&.my .uveselbe:first-child {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.uveselbe:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.uveselbe:not(:last-child) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
154
src/client/pages/clip.vue
Normal file
154
src/client/pages/clip.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<div v-if="clip" class="_section">
|
||||
<div class="okzinsic _content _panel _vMargin">
|
||||
<div class="description" v-if="clip.description">
|
||||
<Mfm :text="clip.description" :is-note="false" :i="$store.state.i"/>
|
||||
</div>
|
||||
<div class="user">
|
||||
<MkAvatar :user="clip.user" class="avatar"/> <MkUserName :user="clip.user" :nowrap="false"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<XNotes class="_content _vMargin" :pagination="pagination" :detail="true"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { faEllipsisH, faPaperclip, faPencilAlt, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkContainer from '@/components/ui/container.vue';
|
||||
import XPostForm from '@/components/post-form.vue';
|
||||
import XNotes from '@/components/notes.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkContainer,
|
||||
XPostForm,
|
||||
XNotes,
|
||||
},
|
||||
|
||||
props: {
|
||||
clipId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
INFO: computed(() => this.clip ? {
|
||||
title: this.clip.name,
|
||||
icon: faPaperclip,
|
||||
action: {
|
||||
icon: faEllipsisH,
|
||||
handler: this.menu
|
||||
}
|
||||
} : null),
|
||||
clip: null,
|
||||
pagination: {
|
||||
endpoint: 'clips/notes',
|
||||
limit: 10,
|
||||
params: () => ({
|
||||
clipId: this.clipId,
|
||||
})
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
isOwned(): boolean {
|
||||
return this.$store.getters.isSignedIn && this.clip && (this.$store.state.i.id === this.clip.userId);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
clipId: {
|
||||
async handler() {
|
||||
this.clip = await os.api('clips/show', {
|
||||
clipId: this.clipId,
|
||||
});
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
menu(ev) {
|
||||
os.modalMenu([this.isOwned ? {
|
||||
icon: faPencilAlt,
|
||||
text: this.$t('edit'),
|
||||
action: async () => {
|
||||
const { canceled, result } = await os.form(this.clip.name, {
|
||||
name: {
|
||||
type: 'string',
|
||||
label: this.$t('name'),
|
||||
default: this.clip.name
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
multiline: true,
|
||||
label: this.$t('description'),
|
||||
default: this.clip.description
|
||||
},
|
||||
isPublic: {
|
||||
type: 'boolean',
|
||||
label: this.$t('public'),
|
||||
default: this.clip.isPublic
|
||||
}
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
os.apiWithDialog('clips/update', {
|
||||
clipId: this.clip.id,
|
||||
...result
|
||||
});
|
||||
}
|
||||
} : undefined, this.isOwned ? {
|
||||
icon: faTrashAlt,
|
||||
text: this.$t('delete'),
|
||||
danger: true,
|
||||
action: async () => {
|
||||
const { canceled } = await os.dialog({
|
||||
type: 'warning',
|
||||
text: this.$t('deleteAreYouSure', { x: this.clip.name }),
|
||||
showCancelButton: true
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
await os.apiWithDialog('clips/delete', {
|
||||
clipId: this.clip.id,
|
||||
});
|
||||
}
|
||||
} : undefined], ev.currentTarget || ev.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.okzinsic {
|
||||
position: relative;
|
||||
|
||||
> .description {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
> .user {
|
||||
$height: 32px;
|
||||
padding: 16px;
|
||||
border-top: solid 1px var(--divider);
|
||||
line-height: $height;
|
||||
|
||||
> .avatar {
|
||||
width: $height;
|
||||
height: $height;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div class="mk-instance-emojis">
|
||||
<div class="_section" style="padding: 0;">
|
||||
<MkTab v-model:value="tab" :items="[{ label: $t('local'), value: 'local' }, { label: $t('remote'), value: 'remote' }]"/>
|
||||
<MkTab v-model:value="tab">
|
||||
<option value="local">{{ $t('local') }}</option>
|
||||
<option value="remote">{{ $t('remote') }}</option>
|
||||
</MkTab>
|
||||
</div>
|
||||
|
||||
<div class="_section">
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<template>
|
||||
<div v-if="meta">
|
||||
<section class="_section info">
|
||||
<div v-if="meta" class="_section">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faInfoCircle"/> {{ $t('basicInfo') }}</div>
|
||||
<div class="_content">
|
||||
<MkInput v-model:value="name">{{ $t('instanceName') }}</MkInput>
|
||||
<MkTextarea v-model:value="description">{{ $t('instanceDescription') }}</MkTextarea>
|
||||
<MkInput v-model:value="iconUrl"><template #icon><Fa :icon="faLink"/></template>{{ $t('iconUrl') }}</MkInput>
|
||||
<MkInput v-model:value="bannerUrl"><template #icon><Fa :icon="faLink"/></template>{{ $t('bannerUrl') }}</MkInput>
|
||||
<MkInput v-model:value="backgroundImageUrl"><template #icon><Fa :icon="faLink"/></template>{{ $t('backgroundImageUrl') }}</MkInput>
|
||||
<MkInput v-model:value="logoImageUrl"><template #icon><Fa :icon="faLink"/></template>{{ $t('logoImageUrl') }}</MkInput>
|
||||
<MkInput v-model:value="tosUrl"><template #icon><Fa :icon="faLink"/></template>{{ $t('tosUrl') }}</MkInput>
|
||||
<MkInput v-model:value="maintainerName">{{ $t('maintainerName') }}</MkInput>
|
||||
<MkInput v-model:value="maintainerEmail" type="email"><template #icon><Fa :icon="faEnvelope"/></template>{{ $t('maintainerEmail') }}</MkInput>
|
||||
@@ -16,7 +18,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_section info">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_content">
|
||||
<MkInput v-model:value="maxNoteTextLength" type="number" :save="() => save()"><template #icon><Fa :icon="faPencilAlt"/></template>{{ $t('maxNoteTextLength') }}</MkInput>
|
||||
</div>
|
||||
@@ -30,7 +32,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_section info">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faUser"/> {{ $t('registration') }}</div>
|
||||
<div class="_content">
|
||||
<MkSwitch v-model:value="enableRegistration" @update:value="save()">{{ $t('enableRegistration') }}</MkSwitch>
|
||||
@@ -38,7 +40,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_section">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faShieldAlt"/> {{ $t('hcaptcha') }}</div>
|
||||
<div class="_content">
|
||||
<MkSwitch v-model:value="enableHcaptcha">{{ $t('enableHcaptcha') }}</MkSwitch>
|
||||
@@ -56,7 +58,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_section">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faShieldAlt"/> {{ $t('recaptcha') }}</div>
|
||||
<div class="_content">
|
||||
<MkSwitch v-model:value="enableRecaptcha" ref="enableRecaptcha">{{ $t('enableRecaptcha') }}</MkSwitch>
|
||||
@@ -74,7 +76,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_section">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faEnvelope" /> {{ $t('emailConfig') }}</div>
|
||||
<div class="_content">
|
||||
<MkSwitch v-model:value="enableEmail" @update:value="save()">{{ $t('enableEmail') }}<template #desc>{{ $t('emailConfigInfo') }}</template></MkSwitch>
|
||||
@@ -97,7 +99,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_section">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faBolt"/> {{ $t('serviceworker') }}</div>
|
||||
<div class="_content">
|
||||
<MkSwitch v-model:value="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworkerInfo') }}</template></MkSwitch>
|
||||
@@ -113,7 +115,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_section">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faThumbtack"/> {{ $t('pinnedUsers') }}</div>
|
||||
<div class="_content">
|
||||
<MkTextarea v-model:value="pinnedUsers">
|
||||
@@ -125,7 +127,19 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_section">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faThumbtack"/> {{ $t('pinnedPages') }}</div>
|
||||
<div class="_content">
|
||||
<MkTextarea v-model:value="pinnedPages">
|
||||
<template #desc>{{ $t('pinnedPagesDescription') }}</template>
|
||||
</MkTextarea>
|
||||
</div>
|
||||
<div class="_footer">
|
||||
<MkButton primary @click="save(true)"><Fa :icon="faSave"/> {{ $t('save') }}</MkButton>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faCloud"/> {{ $t('files') }}</div>
|
||||
<div class="_content">
|
||||
<MkSwitch v-model:value="cacheRemoteFiles">{{ $t('cacheRemoteFiles') }}<template #desc>{{ $t('cacheRemoteFilesDescription') }}</template></MkSwitch>
|
||||
@@ -138,7 +152,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_section">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faCloud"/> {{ $t('objectStorage') }}</div>
|
||||
<div class="_content">
|
||||
<MkSwitch v-model:value="useObjectStorage">{{ $t('useObjectStorage') }}</MkSwitch>
|
||||
@@ -166,7 +180,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_section">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faGhost"/> {{ $t('proxyAccount') }}</div>
|
||||
<div class="_content">
|
||||
<MkInput :value="proxyAccount ? proxyAccount.username : null" disabled><template #prefix>@</template>{{ $t('proxyAccount') }}<template #desc>{{ $t('proxyAccountDescription') }}</template></MkInput>
|
||||
@@ -174,7 +188,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_section">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faBan"/> {{ $t('blockedInstances') }}</div>
|
||||
<div class="_content">
|
||||
<MkTextarea v-model:value="blockedHosts">
|
||||
@@ -186,7 +200,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_section">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faShareAlt"/> {{ $t('integration') }}</div>
|
||||
<div class="_content">
|
||||
<header><Fa :icon="faTwitter"/> Twitter</header>
|
||||
@@ -220,7 +234,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="_section">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faArchway" /> Summaly Proxy</div>
|
||||
<div class="_content">
|
||||
<MkInput v-model:value="summalyProxy">URL</MkInput>
|
||||
@@ -260,6 +274,7 @@ export default defineComponent({
|
||||
title: this.$t('instance'),
|
||||
icon: faCog,
|
||||
},
|
||||
meta: null,
|
||||
url,
|
||||
proxyAccount: null,
|
||||
proxyAccountId: null,
|
||||
@@ -269,6 +284,7 @@ export default defineComponent({
|
||||
remoteDriveCapacityMb: 0,
|
||||
blockedHosts: '',
|
||||
pinnedUsers: '',
|
||||
pinnedPages: '',
|
||||
maintainerName: null,
|
||||
maintainerEmail: null,
|
||||
name: null,
|
||||
@@ -278,6 +294,8 @@ export default defineComponent({
|
||||
email: null,
|
||||
bannerUrl: null,
|
||||
iconUrl: null,
|
||||
logoImageUrl: null,
|
||||
backgroundImageUrl: null,
|
||||
maxNoteTextLength: 0,
|
||||
enableRegistration: false,
|
||||
enableLocalTimeline: false,
|
||||
@@ -323,18 +341,16 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
meta() {
|
||||
return this.$store.state.instance.meta;
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
this.meta = await os.api('meta', { detail: true });
|
||||
|
||||
created() {
|
||||
this.name = this.meta.name;
|
||||
this.description = this.meta.description;
|
||||
this.tosUrl = this.meta.tosUrl;
|
||||
this.bannerUrl = this.meta.bannerUrl;
|
||||
this.iconUrl = this.meta.iconUrl;
|
||||
this.logoImageUrl = this.meta.logoImageUrl;
|
||||
this.backgroundImageUrl = this.meta.backgroundImageUrl;
|
||||
this.enableEmail = this.meta.enableEmail;
|
||||
this.email = this.meta.email;
|
||||
this.maintainerName = this.meta.maintainerName;
|
||||
@@ -356,6 +372,7 @@ export default defineComponent({
|
||||
this.remoteDriveCapacityMb = this.meta.driveCapacityPerRemoteUserMb;
|
||||
this.blockedHosts = this.meta.blockedHosts.join('\n');
|
||||
this.pinnedUsers = this.meta.pinnedUsers.join('\n');
|
||||
this.pinnedPages = this.meta.pinnedPages.join('\n');
|
||||
this.enableServiceWorker = this.meta.enableServiceWorker;
|
||||
this.swPublicKey = this.meta.swPublickey;
|
||||
this.swPrivateKey = this.meta.swPrivateKey;
|
||||
@@ -487,6 +504,8 @@ export default defineComponent({
|
||||
tosUrl: this.tosUrl,
|
||||
bannerUrl: this.bannerUrl,
|
||||
iconUrl: this.iconUrl,
|
||||
logoImageUrl: this.logoImageUrl,
|
||||
backgroundImageUrl: this.backgroundImageUrl,
|
||||
maintainerName: this.maintainerName,
|
||||
maintainerEmail: this.maintainerEmail,
|
||||
maxNoteTextLength: this.maxNoteTextLength,
|
||||
@@ -506,6 +525,7 @@ export default defineComponent({
|
||||
remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
|
||||
blockedHosts: this.blockedHosts.split('\n') || [],
|
||||
pinnedUsers: this.pinnedUsers ? this.pinnedUsers.split('\n') : [],
|
||||
pinnedPages: this.pinnedPages ? this.pinnedPages.split('\n') : [],
|
||||
enableServiceWorker: this.enableServiceWorker,
|
||||
swPublicKey: this.swPublicKey,
|
||||
swPrivateKey: this.swPrivateKey,
|
||||
|
||||
@@ -38,6 +38,7 @@ import parseAcct from '../../../misc/acct/parse';
|
||||
import { isBottom, onScrollBottom, scroll } from '@/scripts/scroll';
|
||||
import * as os from '@/os';
|
||||
import { popout } from '@/scripts/popout';
|
||||
import * as sound from '@/scripts/sound';
|
||||
|
||||
const Component = defineComponent({
|
||||
components: {
|
||||
@@ -218,7 +219,7 @@ const Component = defineComponent({
|
||||
},
|
||||
|
||||
onMessage(message) {
|
||||
os.sound('chat');
|
||||
sound.play('chat');
|
||||
|
||||
const _isBottom = isBottom(this.$el, 64);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<XAntenna v-if="draft" :antenna="draft" @created="onAntennaCreated" style="margin-bottom: var(--margin);"/>
|
||||
|
||||
<MkPagination :pagination="pagination" #default="{items}" class="antennas" ref="list">
|
||||
<XAntenna v-for="(antenna, i) in items" :key="antenna.id" :antenna="antenna" @created="onAntennaDeleted"/>
|
||||
<XAntenna v-for="(antenna, i) in items" :key="antenna.id" :antenna="antenna" @deleted="onAntennaDeleted"/>
|
||||
</MkPagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
105
src/client/pages/my-clips/index.vue
Normal file
105
src/client/pages/my-clips/index.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="_section qtcaoidl">
|
||||
<MkButton @click="create" primary class="add"><Fa :icon="faPlus"/> {{ $t('add') }}</MkButton>
|
||||
|
||||
<div class="_content">
|
||||
<MkPagination :pagination="pagination" #default="{items}" ref="list" class="list">
|
||||
<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _vMargin">
|
||||
<b>{{ item.name }}</b>
|
||||
<div v-if="item.description" class="description">{{ item.description }}</div>
|
||||
</MkA>
|
||||
</MkPagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faPlus, faPaperclip } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkPagination from '@/components/ui/pagination.vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkPagination,
|
||||
MkButton,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
INFO: {
|
||||
title: this.$t('clip'),
|
||||
icon: faPaperclip,
|
||||
action: {
|
||||
icon: faPlus,
|
||||
handler: this.create
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
endpoint: 'clips/list',
|
||||
limit: 10,
|
||||
},
|
||||
draft: null,
|
||||
faPlus
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
async create() {
|
||||
const { canceled, result } = await os.form(this.$t('createNewClip'), {
|
||||
name: {
|
||||
type: 'string',
|
||||
label: this.$t('name')
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
multiline: true,
|
||||
label: this.$t('description')
|
||||
},
|
||||
isPublic: {
|
||||
type: 'boolean',
|
||||
label: this.$t('public'),
|
||||
default: false
|
||||
}
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
os.apiWithDialog('clips/create', result);
|
||||
},
|
||||
|
||||
onClipCreated() {
|
||||
this.$refs.list.reload();
|
||||
this.draft = null;
|
||||
},
|
||||
|
||||
onClipDeleted() {
|
||||
this.$refs.list.reload();
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.qtcaoidl {
|
||||
> .add {
|
||||
margin: 0 auto 16px auto;
|
||||
}
|
||||
|
||||
> ._content {
|
||||
> .list {
|
||||
> .item {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
|
||||
> .description {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: solid 1px var(--divider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="_section" style="padding: 0;">
|
||||
<MkTab v-model:value="tab" :items="[{ label: $t('ownedGroups'), value: 'owned' }, { label: $t('joinedGroups'), value: 'joined' }, { label: $t('invites'), icon: faEnvelopeOpenText, value: 'invites' }]"/>
|
||||
<MkTab v-model:value="tab">
|
||||
<option value="owned">{{ $t('ownedGroups') }}</option>
|
||||
<option value="joined">{{ $t('joinedGroups') }}</option>
|
||||
<option value="invites"><Fa :icon="faEnvelopeOpenText"/> {{ $t('invites') }}</option>
|
||||
</MkTab>
|
||||
</div>
|
||||
|
||||
<div class="_section">
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
<template>
|
||||
<div class="fcuexfpr">
|
||||
<div v-if="note" class="note">
|
||||
<div class="_section">
|
||||
<XNotes v-if="showNext" class="_content" :pagination="next"/>
|
||||
<MkButton v-else-if="hasNext" class="load _content" @click="showNext = true"><Fa :icon="faChevronUp"/></MkButton>
|
||||
<div class="_section" v-if="showNext">
|
||||
<XNotes class="_content" :pagination="next"/>
|
||||
</div>
|
||||
|
||||
<div class="_section">
|
||||
<div class="_content">
|
||||
<div class="_section main">
|
||||
<MkButton v-if="!showNext && hasNext" class="load next _content" @click="showNext = true"><Fa :icon="faChevronUp"/></MkButton>
|
||||
<div class="_content _vMargin">
|
||||
<MkRemoteCaution v-if="note.user.host != null" :href="note.url || note.uri" class="_vMargin"/>
|
||||
<XNote v-model:note="note" :key="note.id" :detail="true" class="_vMargin"/>
|
||||
</div>
|
||||
<div class="_content clips _vMargin" v-if="clips && clips.length > 0">
|
||||
<div class="title">{{ $t('clip') }}</div>
|
||||
<MkA v-for="item in clips" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _vMargin">
|
||||
<b>{{ item.name }}</b>
|
||||
<div v-if="item.description" class="description">{{ item.description }}</div>
|
||||
<div class="user">
|
||||
<MkAvatar :user="item.user" class="avatar"/> <MkUserName :user="item.user" :nowrap="false"/>
|
||||
</div>
|
||||
</MkA>
|
||||
</div>
|
||||
<MkButton v-if="!showPrev && hasPrev" class="load prev _content" @click="showPrev = true"><Fa :icon="faChevronDown"/></MkButton>
|
||||
</div>
|
||||
|
||||
<div class="_section">
|
||||
<XNotes v-if="showPrev" class="_content" :pagination="prev"/>
|
||||
<MkButton v-else-if="hasPrev" class="load _content" @click="showPrev = true"><Fa :icon="faChevronDown"/></MkButton>
|
||||
<div class="_section" v-if="showPrev">
|
||||
<XNotes class="_content" :pagination="prev"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,7 +38,6 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { faChevronUp, faChevronDown } from '@fortawesome/free-solid-svg-icons';
|
||||
import Progress from '@/scripts/loading';
|
||||
import XNote from '@/components/note.vue';
|
||||
import XNotes from '@/components/notes.vue';
|
||||
import MkRemoteCaution from '@/components/remote-caution.vue';
|
||||
@@ -55,6 +64,7 @@ export default defineComponent({
|
||||
avatar: this.note.user,
|
||||
} : null),
|
||||
note: null,
|
||||
clips: null,
|
||||
hasPrev: false,
|
||||
hasNext: false,
|
||||
showPrev: false,
|
||||
@@ -88,11 +98,13 @@ export default defineComponent({
|
||||
},
|
||||
methods: {
|
||||
fetch() {
|
||||
Progress.start();
|
||||
os.api('notes/show', {
|
||||
noteId: this.noteId
|
||||
}).then(note => {
|
||||
Promise.all([
|
||||
os.api('notes/clips', {
|
||||
noteId: note.id,
|
||||
}),
|
||||
os.api('users/notes', {
|
||||
userId: note.userId,
|
||||
untilId: note.id,
|
||||
@@ -103,15 +115,14 @@ export default defineComponent({
|
||||
sinceId: note.id,
|
||||
limit: 1,
|
||||
}),
|
||||
]).then(([prev, next]) => {
|
||||
]).then(([clips, prev, next]) => {
|
||||
this.clips = clips;
|
||||
this.hasPrev = prev.length !== 0;
|
||||
this.hasNext = next.length !== 0;
|
||||
this.note = note;
|
||||
});
|
||||
}).catch(e => {
|
||||
this.error = e;
|
||||
}).finally(() => {
|
||||
Progress.done();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -121,10 +132,46 @@ export default defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
.fcuexfpr {
|
||||
> .note {
|
||||
> ._section {
|
||||
> .main {
|
||||
> .load {
|
||||
min-width: 0;
|
||||
border-radius: 999px;
|
||||
|
||||
&.next {
|
||||
margin-bottom: var(--margin);
|
||||
}
|
||||
|
||||
&.prev {
|
||||
margin-top: var(--margin);
|
||||
}
|
||||
}
|
||||
|
||||
> .clips {
|
||||
> .title {
|
||||
font-weight: bold;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
> .item {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
|
||||
> .description {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
> .user {
|
||||
$height: 32px;
|
||||
padding-top: 16px;
|
||||
border-top: solid 1px var(--divider);
|
||||
line-height: $height;
|
||||
|
||||
> .avatar {
|
||||
width: $height;
|
||||
height: $height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
65
src/client/pages/page-editor/els/page-editor.el.note.vue
Normal file
65
src/client/pages/page-editor/els/page-editor.el.note.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<XContainer @remove="() => $emit('remove')" :draggable="true">
|
||||
<template #header><Fa :icon="faStickyNote"/> {{ $t('_pages.blocks.note') }}</template>
|
||||
|
||||
<section style="padding: 0 16px 0 16px;">
|
||||
<MkInput v-model:value="id">
|
||||
<span>{{ $t('_pages.blocks._note.id') }}</span>
|
||||
<template #desc>{{ $t('_pages.blocks._note.idDescription') }}</template>
|
||||
</MkInput>
|
||||
<MkSwitch v-model:value="value.detailed"><span>{{ $t('_pages.blocks._note.detailed') }}</span></MkSwitch>
|
||||
|
||||
<XNote v-if="note" v-model:note="note" :key="note.id + ':' + (value.detailed ? 'detailed' : 'normal')" :detail="value.detailed" style="margin-bottom: 16px;"/>
|
||||
</section>
|
||||
</XContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faStickyNote } from '@fortawesome/free-solid-svg-icons';
|
||||
import XContainer from '../page-editor.container.vue';
|
||||
import MkInput from '@/components/ui/input.vue';
|
||||
import MkSwitch from '@/components/ui/switch.vue';
|
||||
import XNote from '@/components/note.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
XContainer, MkInput, MkSwitch, XNote
|
||||
},
|
||||
|
||||
props: {
|
||||
value: {
|
||||
required: true
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
id: this.value.note,
|
||||
note: null,
|
||||
faStickyNote
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
id: {
|
||||
async handler() {
|
||||
if (this.id && (this.id.startsWith('http://') || this.id.startsWith('https://'))) {
|
||||
this.value.note = this.id.endsWith('/') ? this.id.substr(0, this.id.length - 1).split('/').pop() : this.id.split('/').pop();
|
||||
} else {
|
||||
this.value.note = this.id;
|
||||
}
|
||||
|
||||
this.note = await os.api('notes/show', { noteId: this.value.note });
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.value.note == null) this.value.note = null;
|
||||
if (this.value.detailed == null) this.value.detailed = false;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -20,12 +20,13 @@ import XPost from './els/page-editor.el.post.vue';
|
||||
import XCounter from './els/page-editor.el.counter.vue';
|
||||
import XRadioButton from './els/page-editor.el.radio-button.vue';
|
||||
import XCanvas from './els/page-editor.el.canvas.vue';
|
||||
import XNote from './els/page-editor.el.note.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
XDraggable: defineAsyncComponent(() => import('vue-draggable-next').then(x => x.VueDraggableNext)),
|
||||
XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter, XRadioButton, XCanvas
|
||||
XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter, XRadioButton, XCanvas, XNote
|
||||
},
|
||||
|
||||
props: {
|
||||
|
||||
@@ -1,57 +1,54 @@
|
||||
<template>
|
||||
<div class="_section">
|
||||
<div class="_content">
|
||||
<div class="gwbmwxkm _panel _vMargin">
|
||||
<header>
|
||||
<div class="title"><Fa :icon="faStickyNote"/> {{ readonly ? $t('_pages.readPage') : pageId ? $t('_pages.editPage') : $t('_pages.newPage') }}</div>
|
||||
<div class="buttons">
|
||||
<button class="_button" @click="del()" v-if="!readonly"><Fa :icon="faTrashAlt"/></button>
|
||||
<button class="_button" @click="() => showOptions = !showOptions"><Fa :icon="faCog"/></button>
|
||||
<button class="_button" @click="save()" v-if="!readonly"><Fa :icon="faSave"/></button>
|
||||
</div>
|
||||
</header>
|
||||
<MkA class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $t('_pages.viewPage') }}</MkA>
|
||||
|
||||
<section>
|
||||
<MkA class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $t('_pages.viewPage') }}</MkA>
|
||||
<MkButton @click="save" primary class="save" style="margin: 16px auto 16px auto;"><Fa :icon="faSave"/> {{ $t('save') }}</MkButton>
|
||||
|
||||
<MkContainer :body-togglable="true" :expanded="true" class="_vMargin">
|
||||
<template #header><Fa :icon="faCog"/> {{ $t('_pages.pageSetting') }}</template>
|
||||
<div class="_section">
|
||||
<MkInput v-model:value="title">
|
||||
<span>{{ $t('_pages.title') }}</span>
|
||||
</MkInput>
|
||||
|
||||
<template v-if="showOptions">
|
||||
<MkInput v-model:value="summary">
|
||||
<span>{{ $t('_pages.summary') }}</span>
|
||||
</MkInput>
|
||||
<MkInput v-model:value="summary">
|
||||
<span>{{ $t('_pages.summary') }}</span>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model:value="name">
|
||||
<template #prefix>{{ url }}/@{{ author.username }}/pages/</template>
|
||||
<span>{{ $t('_pages.url') }}</span>
|
||||
</MkInput>
|
||||
<MkInput v-model:value="name">
|
||||
<template #prefix>{{ url }}/@{{ author.username }}/pages/</template>
|
||||
<span>{{ $t('_pages.url') }}</span>
|
||||
</MkInput>
|
||||
|
||||
<MkSwitch v-model:value="alignCenter">{{ $t('_pages.alignCenter') }}</MkSwitch>
|
||||
<MkSwitch v-model:value="alignCenter">{{ $t('_pages.alignCenter') }}</MkSwitch>
|
||||
|
||||
<MkSelect v-model:value="font">
|
||||
<template #label>{{ $t('_pages.font') }}</template>
|
||||
<option value="serif">{{ $t('_pages.fontSerif') }}</option>
|
||||
<option value="sans-serif">{{ $t('_pages.fontSansSerif') }}</option>
|
||||
</MkSelect>
|
||||
<MkSelect v-model:value="font">
|
||||
<template #label>{{ $t('_pages.font') }}</template>
|
||||
<option value="serif">{{ $t('_pages.fontSerif') }}</option>
|
||||
<option value="sans-serif">{{ $t('_pages.fontSansSerif') }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkSwitch v-model:value="hideTitleWhenPinned">{{ $t('_pages.hideTitleWhenPinned') }}</MkSwitch>
|
||||
<MkSwitch v-model:value="hideTitleWhenPinned">{{ $t('_pages.hideTitleWhenPinned') }}</MkSwitch>
|
||||
|
||||
<div class="eyeCatch">
|
||||
<MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage()"><Fa :icon="faPlus"/> {{ $t('_pages.eyeCatchingImageSet') }}</MkButton>
|
||||
<div v-else-if="eyeCatchingImage">
|
||||
<img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name"/>
|
||||
<MkButton @click="removeEyeCatchingImage()" v-if="!readonly"><Fa :icon="faTrashAlt"/> {{ $t('_pages.eyeCatchingImageRemove') }}</MkButton>
|
||||
</div>
|
||||
<div class="eyeCatch">
|
||||
<MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage"><Fa :icon="faPlus"/> {{ $t('_pages.eyeCatchingImageSet') }}</MkButton>
|
||||
<div v-else-if="eyeCatchingImage">
|
||||
<img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name" style="max-width: 100%;"/>
|
||||
<MkButton @click="removeEyeCatchingImage()" v-if="!readonly"><Fa :icon="faTrashAlt"/> {{ $t('_pages.eyeCatchingImageRemove') }}</MkButton>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</MkContainer>
|
||||
|
||||
<MkContainer :body-togglable="true" :expanded="true" class="_vMargin">
|
||||
<template #header><Fa :icon="faStickyNote"/> {{ $t('_pages.contents') }}</template>
|
||||
<div class="_section">
|
||||
<XBlocks class="content" v-model:value="content" :hpml="hpml"/>
|
||||
|
||||
<MkButton @click="add()" v-if="!readonly"><Fa :icon="faPlus"/></MkButton>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</MkContainer>
|
||||
|
||||
<MkContainer :body-togglable="true" class="_vMargin">
|
||||
<template #header><Fa :icon="faMagic"/> {{ $t('_pages.variables') }}</template>
|
||||
@@ -85,14 +82,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, defineAsyncComponent } from 'vue';
|
||||
import { defineComponent, defineAsyncComponent, computed } from 'vue';
|
||||
import 'prismjs';
|
||||
import { highlight, languages } from 'prismjs/components/prism-core';
|
||||
import 'prismjs/components/prism-clike';
|
||||
import 'prismjs/components/prism-javascript';
|
||||
import 'prismjs/themes/prism-okaidia.css';
|
||||
import 'vue-prism-editor/dist/prismeditor.min.css';
|
||||
import { faICursor, faPlus, faMagic, faCog, faCode, faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faICursor, faPlus, faMagic, faCog, faCode, faExternalLinkSquareAlt, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faSave, faStickyNote, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import XVariable from './page-editor.script-block.vue';
|
||||
@@ -108,6 +105,7 @@ import { HpmlTypeChecker } from '@/scripts/hpml/type-checker';
|
||||
import { url } from '@/config';
|
||||
import { collectPageVars } from '@/scripts/collect-page-vars';
|
||||
import * as os from '@/os';
|
||||
import { selectFile } from '@/scripts/select-file';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -132,6 +130,13 @@ export default defineComponent({
|
||||
|
||||
data() {
|
||||
return {
|
||||
INFO: computed(() => this.initPageId ? {
|
||||
title: this.$t('_pages.editPage'),
|
||||
icon: faPencilAlt,
|
||||
} : {
|
||||
title: this.$t('_pages.newPage'),
|
||||
icon: faPencilAlt,
|
||||
}),
|
||||
author: this.$store.state.i,
|
||||
readonly: false,
|
||||
page: null,
|
||||
@@ -149,7 +154,6 @@ export default defineComponent({
|
||||
variables: [],
|
||||
hpml: null,
|
||||
script: '',
|
||||
showOptions: false,
|
||||
url,
|
||||
faPlus, faICursor, faSave, faStickyNote, faMagic, faCog, faTrashAlt, faExternalLinkSquareAlt, faCode
|
||||
};
|
||||
@@ -273,7 +277,7 @@ export default defineComponent({
|
||||
type: 'success',
|
||||
text: this.$t('_pages.created')
|
||||
});
|
||||
this.$router.push(`/my/pages/edit/${this.pageId}`);
|
||||
this.$router.push(`/pages/edit/${this.pageId}`);
|
||||
}).catch(onError);
|
||||
}
|
||||
},
|
||||
@@ -292,7 +296,7 @@ export default defineComponent({
|
||||
type: 'success',
|
||||
text: this.$t('_pages.deleted')
|
||||
});
|
||||
this.$router.push(`/my/pages`);
|
||||
this.$router.push(`/pages`);
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -353,6 +357,7 @@ export default defineComponent({
|
||||
{ value: 'text', text: this.$t('_pages.blocks.text') },
|
||||
{ value: 'image', text: this.$t('_pages.blocks.image') },
|
||||
{ value: 'textarea', text: this.$t('_pages.blocks.textarea') },
|
||||
{ value: 'note', text: this.$t('_pages.blocks.note') },
|
||||
{ value: 'canvas', text: this.$t('_pages.blocks.canvas') },
|
||||
]
|
||||
}, {
|
||||
@@ -413,8 +418,8 @@ export default defineComponent({
|
||||
return list;
|
||||
},
|
||||
|
||||
setEyeCatchingImage() {
|
||||
os.selectDriveFile(false).then(file => {
|
||||
setEyeCatchingImage(e) {
|
||||
selectFile(e.currentTarget || e.target, null, false).then(file => {
|
||||
this.eyeCatchingImageId = file.id;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<div class="_content">
|
||||
<MkA :to="`./${page.name}/view-source`" class="link">{{ $t('_pages.viewSource') }}</MkA>
|
||||
<template v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId">
|
||||
<MkA :to="`/my/pages/edit/${page.id}`" class="link">{{ $t('_pages.editThisPage') }}</MkA>
|
||||
<MkA :to="`/pages/edit/${page.id}`" class="link">{{ $t('_pages.editThisPage') }}</MkA>
|
||||
<button v-if="$store.state.i.pinnedPageId === page.id" @click="pin(false)" class="link _textButton">{{ $t('unpin') }}</button>
|
||||
<button v-else @click="pin(true)" class="link _textButton">{{ $t('pin') }}</button>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<MkTab v-model:value="tab" :items="[{ label: $t('_pages.my'), value: 'my', icon: faEdit }, { label: $t('_pages.liked'), value: 'liked', icon: faHeart }]"/>
|
||||
<MkTab v-model:value="tab" v-if="this.$store.getters.isSignedIn">
|
||||
<option value="featured"><Fa :icon="faFireAlt"/> {{ $t('_pages.featured') }}</option>
|
||||
<option value="my"><Fa :icon="faEdit"/> {{ $t('_pages.my') }}</option>
|
||||
<option value="liked"><Fa :icon="faHeart"/> {{ $t('_pages.liked') }}</option>
|
||||
</MkTab>
|
||||
|
||||
<div class="_section">
|
||||
<div class="rknalgpo _content" v-if="tab === 'featured'">
|
||||
<MkPagination :pagination="featuredPagesPagination" #default="{items}">
|
||||
<MkPagePreview v-for="page in items" class="ckltabjg" :page="page" :key="page.id"/>
|
||||
</MkPagination>
|
||||
</div>
|
||||
|
||||
<div class="rknalgpo _content my" v-if="tab === 'my'">
|
||||
<MkButton class="new" @click="create()"><Fa :icon="faPlus"/></MkButton>
|
||||
<MkPagination :pagination="myPagesPagination" #default="{items}">
|
||||
@@ -21,7 +31,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faPlus, faEdit } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faPlus, faEdit, faFireAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faStickyNote, faHeart } from '@fortawesome/free-regular-svg-icons';
|
||||
import MkPagePreview from '@/components/page-preview.vue';
|
||||
import MkPagination from '@/components/ui/pagination.vue';
|
||||
@@ -42,7 +52,11 @@ export default defineComponent({
|
||||
handler: this.create
|
||||
}
|
||||
},
|
||||
tab: 'my',
|
||||
tab: 'featured',
|
||||
featuredPagesPagination: {
|
||||
endpoint: 'pages/featured',
|
||||
noPaging: true,
|
||||
},
|
||||
myPagesPagination: {
|
||||
endpoint: 'i/pages',
|
||||
limit: 5,
|
||||
@@ -51,12 +65,12 @@ export default defineComponent({
|
||||
endpoint: 'i/page-likes',
|
||||
limit: 5,
|
||||
},
|
||||
faStickyNote, faPlus, faEdit, faHeart
|
||||
faStickyNote, faPlus, faEdit, faHeart, faFireAlt
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
create() {
|
||||
this.$router.push(`/my/pages/new`);
|
||||
this.$router.push(`/pages/new`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -94,6 +94,7 @@ import { url } from '@/config';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import { userPage } from '@/filters/user';
|
||||
import * as os from '@/os';
|
||||
import * as sound from '@/scripts/sound';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -245,11 +246,7 @@ export default defineComponent({
|
||||
this.o.put(this.myColor, pos);
|
||||
|
||||
// サウンドを再生する
|
||||
if (this.$store.state.device.enableSounds) {
|
||||
const sound = new Audio(`${url}/assets/reversi-put-me.mp3`);
|
||||
sound.volume = this.$store.state.device.soundVolume;
|
||||
sound.play();
|
||||
}
|
||||
sound.play(this.myColor ? 'reversiPutBlack' : 'reversiPutWhite');
|
||||
|
||||
this.connection.send('set', {
|
||||
pos: pos
|
||||
@@ -268,10 +265,8 @@ export default defineComponent({
|
||||
this.$forceUpdate();
|
||||
|
||||
// サウンドを再生する
|
||||
if (this.$store.state.device.enableSounds && x.color != this.myColor) {
|
||||
const sound = new Audio(`${url}/assets/reversi-put-you.mp3`);
|
||||
sound.volume = this.$store.state.device.soundVolume;
|
||||
sound.play();
|
||||
if (x.color !== this.myColor) {
|
||||
sound.play(x.color ? 'reversiPutBlack' : 'reversiPutWhite');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -75,14 +75,25 @@ import MkButton from '@/components/ui/button.vue';
|
||||
import MkInfo from '@/components/ui/info.vue';
|
||||
import MkInput from '@/components/ui/input.vue';
|
||||
import MkSwitch from '@/components/ui/switch.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormBase,
|
||||
MkButton, MkInfo, MkInput, MkSwitch
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
INFO: {
|
||||
title: this.$t('twoStepAuthentication'),
|
||||
icon: faLock
|
||||
},
|
||||
data: null,
|
||||
supportsCredentials: !!navigator.credentials,
|
||||
usePasswordLessLogin: this.$store.state.i.usePasswordLessLogin,
|
||||
@@ -92,6 +103,7 @@ export default defineComponent({
|
||||
faLock
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
register() {
|
||||
os.dialog({
|
||||
@@ -225,6 +237,7 @@ export default defineComponent({
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
updatePasswordLessLogin() {
|
||||
os.api('i/2fa/password-less', {
|
||||
value: !!this.usePasswordLessLogin
|
||||
185
src/client/pages/settings/account-info.vue
Normal file
185
src/client/pages/settings/account-info.vue
Normal file
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<FormBase>
|
||||
<FormKeyValueView>
|
||||
<template #key>ID</template>
|
||||
<template #value><span class="_monospace">{{ $store.state.i.id }}</span></template>
|
||||
</FormKeyValueView>
|
||||
|
||||
<FormGroup>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('registeredDate') }}</template>
|
||||
<template #value><MkTime :time="$store.state.i.createdAt" mode="detail"/></template>
|
||||
</FormKeyValueView>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="stats">
|
||||
<template #label>{{ $t('statistics') }}</template>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('notesCount') }}</template>
|
||||
<template #value>{{ number(stats.notesCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('repliesCount') }}</template>
|
||||
<template #value>{{ number(stats.repliesCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('renotesCount') }}</template>
|
||||
<template #value>{{ number(stats.renotesCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('repliedCount') }}</template>
|
||||
<template #value>{{ number(stats.repliedCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('renotedCount') }}</template>
|
||||
<template #value>{{ number(stats.renotedCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('pollVotesCount') }}</template>
|
||||
<template #value>{{ number(stats.pollVotesCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('pollVotedCount') }}</template>
|
||||
<template #value>{{ number(stats.pollVotedCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('sentReactionsCount') }}</template>
|
||||
<template #value>{{ number(stats.sentReactionsCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('receivedReactionsCount') }}</template>
|
||||
<template #value>{{ number(stats.receivedReactionsCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('noteFavoritesCount') }}</template>
|
||||
<template #value>{{ number(stats.noteFavoritesCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('followingCount') }}</template>
|
||||
<template #value>{{ number(stats.followingCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('followingCount') }} ({{ $t('local') }})</template>
|
||||
<template #value>{{ number(stats.localFollowingCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('followingCount') }} ({{ $t('remote') }})</template>
|
||||
<template #value>{{ number(stats.remoteFollowingCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('followersCount') }}</template>
|
||||
<template #value>{{ number(stats.followersCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('followersCount') }} ({{ $t('local') }})</template>
|
||||
<template #value>{{ number(stats.localFollowersCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('followersCount') }} ({{ $t('remote') }})</template>
|
||||
<template #value>{{ number(stats.remoteFollowersCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('pageLikesCount') }}</template>
|
||||
<template #value>{{ number(stats.pageLikesCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('pageLikedCount') }}</template>
|
||||
<template #value>{{ number(stats.pageLikedCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('driveFilesCount') }}</template>
|
||||
<template #value>{{ number(stats.driveFilesCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('driveUsage') }}</template>
|
||||
<template #value>{{ bytes(stats.driveUsage) }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>{{ $t('reversiCount') }}</template>
|
||||
<template #value>{{ number(stats.reversiCount) }}</template>
|
||||
</FormKeyValueView>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<template #label>{{ $t('other') }}</template>
|
||||
<FormKeyValueView>
|
||||
<template #key>emailVerified</template>
|
||||
<template #value>{{ $store.state.i.emailVerified ? $t('yes') : $t('no') }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>twoFactorEnabled</template>
|
||||
<template #value>{{ $store.state.i.twoFactorEnabled ? $t('yes') : $t('no') }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>securityKeys</template>
|
||||
<template #value>{{ $store.state.i.securityKeys ? $t('yes') : $t('no') }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>usePasswordLessLogin</template>
|
||||
<template #value>{{ $store.state.i.usePasswordLessLogin ? $t('yes') : $t('no') }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>isModerator</template>
|
||||
<template #value>{{ $store.state.i.isModerator ? $t('yes') : $t('no') }}</template>
|
||||
</FormKeyValueView>
|
||||
<FormKeyValueView>
|
||||
<template #key>isAdmin</template>
|
||||
<template #value>{{ $store.state.i.isAdmin ? $t('yes') : $t('no') }}</template>
|
||||
</FormKeyValueView>
|
||||
</FormGroup>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, defineComponent } from 'vue';
|
||||
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormSelect from '@/components/form/select.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import FormKeyValueView from '@/components/form/key-value-view.vue';
|
||||
import * as os from '@/os';
|
||||
import number from '@/filters/number';
|
||||
import bytes from '@/filters/bytes';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormBase,
|
||||
FormSelect,
|
||||
FormSwitch,
|
||||
FormButton,
|
||||
FormLink,
|
||||
FormGroup,
|
||||
FormKeyValueView,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
INFO: {
|
||||
title: this.$t('accountInfo'),
|
||||
icon: faInfoCircle
|
||||
},
|
||||
stats: null
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$emit('info', this.INFO);
|
||||
|
||||
os.api('users/stats', {
|
||||
userId: this.$store.state.i.id
|
||||
}).then(stats => {
|
||||
this.stats = stats;
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
number,
|
||||
bytes,
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,26 +1,27 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="_section">
|
||||
<div class="_content">
|
||||
<MkButton @click="generateToken">{{ $t('generateAccessToken') }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="_section">
|
||||
<MkA to="/api-console" :behavior="isDesktop ? 'window' : null">API console</MkA>
|
||||
</div>
|
||||
</div>
|
||||
<FormBase>
|
||||
<FormButton @click="generateToken" primary>{{ $t('generateAccessToken') }}</FormButton>
|
||||
<FormLink to="/settings/apps">{{ $t('manageAccessTokens') }}</FormLink>
|
||||
<FormLink to="/api-console" :behavior="isDesktop ? 'window' : null">API console</FormLink>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faKey } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkInput from '@/components/ui/input.vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormSelect from '@/components/form/select.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton, MkInput
|
||||
FormBase,
|
||||
FormButton,
|
||||
FormLink,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<MkPagination :pagination="pagination" class="bfomjevm" ref="list">
|
||||
<FormBase>
|
||||
<FormPagination :pagination="pagination" ref="list">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
|
||||
@@ -8,8 +8,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{items}">
|
||||
<div class="token _panel" v-for="token in items" :key="token.id">
|
||||
<img class="icon" :src="token.iconUrl" alt=""/>
|
||||
<div class="_formPanel bfomjevm" v-for="token in items" :key="token.id">
|
||||
<img class="icon" :src="token.iconUrl" alt="" v-if="token.iconUrl"/>
|
||||
<div class="body">
|
||||
<div class="name">{{ token.name }}</div>
|
||||
<div class="description">{{ token.description }}</div>
|
||||
@@ -33,21 +33,29 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</div>
|
||||
</FormPagination>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faTrashAlt, faPlug } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkPagination from '@/components/ui/pagination.vue';
|
||||
import FormPagination from '@/components/form/pagination.vue';
|
||||
import FormSelect from '@/components/form/select.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkPagination
|
||||
FormBase,
|
||||
FormPagination,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
INFO: {
|
||||
@@ -65,6 +73,10 @@ export default defineComponent({
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$emit('info', this.INFO);
|
||||
},
|
||||
|
||||
methods: {
|
||||
revoke(token) {
|
||||
os.api('i/revoke-token', { tokenId: token.id }).then(() => {
|
||||
@@ -77,26 +89,24 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bfomjevm {
|
||||
> .token {
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
|
||||
> .icon {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
margin: 0 12px 0 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
> .icon {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
margin: 0 12px 0 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
> .body {
|
||||
width: calc(100% - 62px);
|
||||
position: relative;
|
||||
> .body {
|
||||
width: calc(100% - 62px);
|
||||
position: relative;
|
||||
|
||||
> .name {
|
||||
font-weight: bold;
|
||||
}
|
||||
> .name {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
90
src/client/pages/settings/deck.vue
Normal file
90
src/client/pages/settings/deck.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<FormBase>
|
||||
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faColumns"/> </div>
|
||||
<div class="_content">
|
||||
<div>{{ $t('defaultNavigationBehaviour') }}</div>
|
||||
<MkSwitch v-model:value="deckNavWindow">{{ $t('openInWindow') }}</MkSwitch>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<MkSwitch v-model:value="deckAlwaysShowMainColumn">
|
||||
{{ $t('_deck.alwaysShowMainColumn') }}
|
||||
</MkSwitch>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<div>{{ $t('_deck.columnAlign') }}</div>
|
||||
<MkRadio v-model="deckColumnAlign" value="left">{{ $t('left') }}</MkRadio>
|
||||
<MkRadio v-model="deckColumnAlign" value="center">{{ $t('center') }}</MkRadio>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faImage, faCog, faColumns } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkSwitch from '@/components/ui/switch.vue';
|
||||
import MkSelect from '@/components/ui/select.vue';
|
||||
import MkRadio from '@/components/ui/radio.vue';
|
||||
import MkRadios from '@/components/ui/radios.vue';
|
||||
import MkRange from '@/components/ui/range.vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormSelect from '@/components/form/select.vue';
|
||||
import FormRadios from '@/components/form/radios.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import { clientDb, set } from '@/db';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
MkSwitch,
|
||||
MkSelect,
|
||||
MkRadio,
|
||||
MkRadios,
|
||||
MkRange,
|
||||
FormSwitch,
|
||||
FormSelect,
|
||||
FormRadios,
|
||||
FormBase,
|
||||
FormGroup,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
INFO: {
|
||||
title: this.$t('deck'),
|
||||
icon: faColumns
|
||||
},
|
||||
faImage, faCog,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
deckNavWindow: {
|
||||
get() { return this.$store.state.device.deckNavWindow; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'deckNavWindow', value }); }
|
||||
},
|
||||
|
||||
deckAlwaysShowMainColumn: {
|
||||
get() { return this.$store.state.device.deckAlwaysShowMainColumn; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'deckAlwaysShowMainColumn', value }); }
|
||||
},
|
||||
|
||||
deckColumnAlign: {
|
||||
get() { return this.$store.state.device.deckColumnAlign; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'deckColumnAlign', value }); }
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$emit('info', this.INFO);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
71
src/client/pages/settings/email-address.vue
Normal file
71
src/client/pages/settings/email-address.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<FormBase>
|
||||
<FormGroup>
|
||||
<FormInput v-model:value="emailAddress" type="email">
|
||||
{{ $t('emailAddress') }}
|
||||
<template #desc v-if="$store.state.i.email && !$store.state.i.emailVerified">{{ $t('verificationEmailSent') }}</template>
|
||||
<template #desc v-else-if="emailAddress === $store.state.i.email && $store.state.i.emailVerified">{{ $t('emailVerified') }}</template>
|
||||
</FormInput>
|
||||
</FormGroup>
|
||||
<FormButton @click="save" primary>{{ $t('save') }}</FormButton>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faCog } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faBell, faEnvelope } from '@fortawesome/free-regular-svg-icons';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormBase,
|
||||
FormInput,
|
||||
FormButton,
|
||||
FormGroup,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
INFO: {
|
||||
title: this.$t('emailAddress'),
|
||||
icon: faEnvelope
|
||||
},
|
||||
emailAddress: null,
|
||||
code: null,
|
||||
faCog
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.emailAddress = this.$store.state.i.email;
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$emit('info', this.INFO);
|
||||
},
|
||||
|
||||
methods: {
|
||||
save() {
|
||||
os.dialog({
|
||||
title: this.$t('password'),
|
||||
input: {
|
||||
type: 'password'
|
||||
}
|
||||
}).then(({ canceled, result: password }) => {
|
||||
if (canceled) return;
|
||||
os.api('i/update-email', {
|
||||
password: password,
|
||||
email: this.emailAddress,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
52
src/client/pages/settings/email.vue
Normal file
52
src/client/pages/settings/email.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<FormBase>
|
||||
<FormGroup>
|
||||
<template #label>{{ $t('emailAddress') }}</template>
|
||||
<FormLink to="/settings/email/address">
|
||||
<template v-if="$store.state.i.email && !$store.state.i.emailVerified" #icon><Fa :icon="faExclamationTriangle" style="color: var(--warn);"/></template>
|
||||
<template v-else-if="$store.state.i.email && $store.state.i.emailVerified" #icon><Fa :icon="faCheck" style="color: var(--success);"/></template>
|
||||
{{ $store.state.i.email || $t('notSet') }}
|
||||
</FormLink>
|
||||
</FormGroup>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faCog, faExclamationTriangle, faCheck } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faBell, faEnvelope } from '@fortawesome/free-regular-svg-icons';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormBase,
|
||||
FormLink,
|
||||
FormButton,
|
||||
FormGroup,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
INFO: {
|
||||
title: this.$t('email'),
|
||||
icon: faEnvelope
|
||||
},
|
||||
faCog, faExclamationTriangle, faCheck
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$emit('info', this.INFO);
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,105 +1,110 @@
|
||||
<template>
|
||||
<div class="_section">
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faCog"/> {{ $t('general') }}</div>
|
||||
<div class="_content">
|
||||
<div>{{ $t('defaultNavigationBehaviour') }}</div>
|
||||
<MkSwitch v-model:value="defaultSideView">{{ $t('openInSideView') }}</MkSwitch>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<div>{{ $t('whenServerDisconnected') }}</div>
|
||||
<MkRadio v-model="serverDisconnectedBehavior" value="reload">{{ $t('_serverDisconnectedBehavior.reload') }}</MkRadio>
|
||||
<MkRadio v-model="serverDisconnectedBehavior" value="dialog">{{ $t('_serverDisconnectedBehavior.dialog') }}</MkRadio>
|
||||
<MkRadio v-model="serverDisconnectedBehavior" value="quiet">{{ $t('_serverDisconnectedBehavior.quiet') }}</MkRadio>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<MkSwitch v-model:value="imageNewTab">{{ $t('openImageInNewTab') }}</MkSwitch>
|
||||
<MkSwitch v-model:value="showFixedPostForm">{{ $t('showFixedPostForm') }}</MkSwitch>
|
||||
<MkSwitch v-model:value="enableInfiniteScroll">{{ $t('enableInfiniteScroll') }}</MkSwitch>
|
||||
<MkSwitch v-model:value="disablePagesScript">{{ $t('disablePagesScript') }}</MkSwitch>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<div>{{ $t('chatOpenBehavior') }}</div>
|
||||
<MkRadio v-model="chatOpenBehavior" value="page">{{ $t('showInPage') }}</MkRadio>
|
||||
<MkRadio v-model="chatOpenBehavior" value="window">{{ $t('openInWindow') }}</MkRadio>
|
||||
<MkRadio v-model="chatOpenBehavior" value="popout">{{ $t('popout') }}</MkRadio>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<MkSelect v-model:value="lang">
|
||||
<template #label>{{ $t('uiLanguage') }}</template>
|
||||
<FormBase>
|
||||
<FormSwitch v-model:value="showFixedPostForm">{{ $t('showFixedPostForm') }}</FormSwitch>
|
||||
|
||||
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
|
||||
</MkSelect>
|
||||
</div>
|
||||
</section>
|
||||
<FormSelect v-model:value="lang">
|
||||
<template #label>{{ $t('uiLanguage') }}</template>
|
||||
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
|
||||
<template #caption>
|
||||
<i18n-t keypath="i18nInfo" tag="span">
|
||||
<template #link>
|
||||
<MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</template>
|
||||
</FormSelect>
|
||||
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faCog"/> {{ $t('appearance') }}</div>
|
||||
<div class="_content">
|
||||
<MkSwitch v-model:value="disableAnimatedMfm">{{ $t('disableAnimatedMfm') }}</MkSwitch>
|
||||
<MkSwitch v-model:value="reduceAnimation">{{ $t('reduceUiAnimation') }}</MkSwitch>
|
||||
<MkSwitch v-model:value="useBlurEffectForModal">{{ $t('useBlurEffectForModal') }}</MkSwitch>
|
||||
<MkSwitch v-model:value="useOsNativeEmojis">
|
||||
{{ $t('useOsNativeEmojis') }}
|
||||
<template #desc><Mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<div>{{ $t('fontSize') }}</div>
|
||||
<MkRadio v-model="fontSize" value="small"><span style="font-size: 14px;">Aa</span></MkRadio>
|
||||
<MkRadio v-model="fontSize" :value="null"><span style="font-size: 16px;">Aa</span></MkRadio>
|
||||
<MkRadio v-model="fontSize" value="large"><span style="font-size: 18px;">Aa</span></MkRadio>
|
||||
<MkRadio v-model="fontSize" value="veryLarge"><span style="font-size: 20px;">Aa</span></MkRadio>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<div>{{ $t('instanceTicker') }}</div>
|
||||
<MkRadio v-model="instanceTicker" value="none">{{ $t('_instanceTicker.none') }}</MkRadio>
|
||||
<MkRadio v-model="instanceTicker" value="remote">{{ $t('_instanceTicker.remote') }}</MkRadio>
|
||||
<MkRadio v-model="instanceTicker" value="always">{{ $t('_instanceTicker.always') }}</MkRadio>
|
||||
</div>
|
||||
</section>
|
||||
<FormGroup>
|
||||
<template #label>{{ $t('behavior') }}</template>
|
||||
<FormSwitch v-model:value="imageNewTab">{{ $t('openImageInNewTab') }}</FormSwitch>
|
||||
<FormSwitch v-model:value="enableInfiniteScroll">{{ $t('enableInfiniteScroll') }}</FormSwitch>
|
||||
<FormSwitch v-model:value="disablePagesScript">{{ $t('disablePagesScript') }}</FormSwitch>
|
||||
</FormGroup>
|
||||
|
||||
<section class="_card _vMargin">
|
||||
<div class="_title"><Fa :icon="faColumns"/> {{ $t('deck') }}</div>
|
||||
<div class="_content">
|
||||
<div>{{ $t('defaultNavigationBehaviour') }}</div>
|
||||
<MkSwitch v-model:value="deckNavWindow">{{ $t('openInWindow') }}</MkSwitch>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<MkSwitch v-model:value="deckAlwaysShowMainColumn">
|
||||
{{ $t('_deck.alwaysShowMainColumn') }}
|
||||
</MkSwitch>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<div>{{ $t('_deck.columnAlign') }}</div>
|
||||
<MkRadio v-model="deckColumnAlign" value="left">{{ $t('left') }}</MkRadio>
|
||||
<MkRadio v-model="deckColumnAlign" value="center">{{ $t('center') }}</MkRadio>
|
||||
</div>
|
||||
</section>
|
||||
<FormSelect v-model:value="serverDisconnectedBehavior">
|
||||
<template #label>{{ $t('whenServerDisconnected') }}</template>
|
||||
<option value="reload">{{ $t('_serverDisconnectedBehavior.reload') }}</option>
|
||||
<option value="dialog">{{ $t('_serverDisconnectedBehavior.dialog') }}</option>
|
||||
<option value="quiet">{{ $t('_serverDisconnectedBehavior.quiet') }}</option>
|
||||
</FormSelect>
|
||||
|
||||
<MkButton @click="cacheClear()" primary style="margin: var(--margin) auto;">{{ $t('cacheClear') }}</MkButton>
|
||||
</div>
|
||||
<FormGroup>
|
||||
<template #label>{{ $t('appearance') }}</template>
|
||||
<FormSwitch v-model:value="disableAnimatedMfm">{{ $t('disableAnimatedMfm') }}</FormSwitch>
|
||||
<FormSwitch v-model:value="reduceAnimation">{{ $t('reduceUiAnimation') }}</FormSwitch>
|
||||
<FormSwitch v-model:value="useBlurEffectForModal">{{ $t('useBlurEffectForModal') }}</FormSwitch>
|
||||
<FormSwitch v-model:value="useOsNativeEmojis">{{ $t('useOsNativeEmojis') }}
|
||||
<div><Mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div>
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model:value="loadRawImages">{{ $t('loadRawImages') }}</FormSwitch>
|
||||
<FormSwitch v-model:value="disableShowingAnimatedImages">{{ $t('disableShowingAnimatedImages') }}</FormSwitch>
|
||||
</FormGroup>
|
||||
|
||||
<FormRadios v-model="fontSize">
|
||||
<template #desc>{{ $t('fontSize') }}</template>
|
||||
<option value="small"><span style="font-size: 14px;">Aa</span></option>
|
||||
<option :value="null"><span style="font-size: 16px;">Aa</span></option>
|
||||
<option value="large"><span style="font-size: 18px;">Aa</span></option>
|
||||
<option value="veryLarge"><span style="font-size: 20px;">Aa</span></option>
|
||||
</FormRadios>
|
||||
|
||||
<FormSelect v-model:value="instanceTicker">
|
||||
<template #label>{{ $t('instanceTicker') }}</template>
|
||||
<option value="none">{{ $t('_instanceTicker.none') }}</option>
|
||||
<option value="remote">{{ $t('_instanceTicker.remote') }}</option>
|
||||
<option value="always">{{ $t('_instanceTicker.always') }}</option>
|
||||
</FormSelect>
|
||||
|
||||
<FormSelect v-model:value="nsfw">
|
||||
<template #label>{{ $t('nsfw') }}</template>
|
||||
<option value="respect">{{ $t('_nsfw.respect') }}</option>
|
||||
<option value="ignore">{{ $t('_nsfw.ignore') }}</option>
|
||||
<option value="force">{{ $t('_nsfw.force') }}</option>
|
||||
</FormSelect>
|
||||
|
||||
<FormGroup>
|
||||
<template #label>{{ $t('defaultNavigationBehaviour') }}</template>
|
||||
<FormSwitch v-model:value="defaultSideView">{{ $t('openInSideView') }}</FormSwitch>
|
||||
</FormGroup>
|
||||
|
||||
<FormSelect v-model:value="chatOpenBehavior">
|
||||
<template #label>{{ $t('chatOpenBehavior') }}</template>
|
||||
<option value="page">{{ $t('showInPage') }}</option>
|
||||
<option value="window">{{ $t('openInWindow') }}</option>
|
||||
<option value="popout">{{ $t('popout') }}</option>
|
||||
</FormSelect>
|
||||
|
||||
<FormLink to="/settings/deck">{{ $t('deck') }}</FormLink>
|
||||
|
||||
<FormButton @click="cacheClear()" danger>{{ $t('cacheClear') }}</FormButton>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faImage, faCog, faColumns, faCogs } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkSwitch from '@/components/ui/switch.vue';
|
||||
import MkSelect from '@/components/ui/select.vue';
|
||||
import MkRadio from '@/components/ui/radio.vue';
|
||||
import MkRange from '@/components/ui/range.vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormSelect from '@/components/form/select.vue';
|
||||
import FormRadios from '@/components/form/radios.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import MkLink from '@/components/link.vue';
|
||||
import { langs } from '@/config';
|
||||
import { clientDb, set } from '@/db';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
MkSwitch,
|
||||
MkSelect,
|
||||
MkRadio,
|
||||
MkRange,
|
||||
MkLink,
|
||||
FormSwitch,
|
||||
FormSelect,
|
||||
FormRadios,
|
||||
FormBase,
|
||||
FormGroup,
|
||||
FormLink,
|
||||
FormButton,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
@@ -163,11 +168,6 @@ export default defineComponent({
|
||||
set(value) { this.$store.commit('device/set', { key: 'defaultSideView', value }); }
|
||||
},
|
||||
|
||||
deckNavWindow: {
|
||||
get() { return this.$store.state.device.deckNavWindow; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'deckNavWindow', value }); }
|
||||
},
|
||||
|
||||
chatOpenBehavior: {
|
||||
get() { return this.$store.state.device.chatOpenBehavior; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'chatOpenBehavior', value }); }
|
||||
@@ -178,20 +178,25 @@ export default defineComponent({
|
||||
set(value) { this.$store.commit('device/set', { key: 'instanceTicker', value }); }
|
||||
},
|
||||
|
||||
loadRawImages: {
|
||||
get() { return this.$store.state.device.loadRawImages; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'loadRawImages', value }); }
|
||||
},
|
||||
|
||||
disableShowingAnimatedImages: {
|
||||
get() { return this.$store.state.device.disableShowingAnimatedImages; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'disableShowingAnimatedImages', value }); }
|
||||
},
|
||||
|
||||
nsfw: {
|
||||
get() { return this.$store.state.device.nsfw; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'nsfw', value }); }
|
||||
},
|
||||
|
||||
enableInfiniteScroll: {
|
||||
get() { return this.$store.state.device.enableInfiniteScroll; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'enableInfiniteScroll', value }); }
|
||||
},
|
||||
|
||||
deckAlwaysShowMainColumn: {
|
||||
get() { return this.$store.state.device.deckAlwaysShowMainColumn; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'deckAlwaysShowMainColumn', value }); }
|
||||
},
|
||||
|
||||
deckColumnAlign: {
|
||||
get() { return this.$store.state.device.deckColumnAlign; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'deckColumnAlign', value }); }
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
||||
@@ -1,51 +1,62 @@
|
||||
<template>
|
||||
<div class="vvcocwet" :class="{ wide: !narrow }" ref="el">
|
||||
<div class="nav" v-if="!narrow || page == null">
|
||||
<div class="menu">
|
||||
<div class="label">{{ $t('basicSettings') }}</div>
|
||||
<MkA class="item" :class="{ active: page === 'profile' }" replace to="/settings/profile"><Fa :icon="faUser" fixed-width class="icon"/>{{ $t('profile') }}</MkA>
|
||||
<MkA class="item" :class="{ active: page === 'privacy' }" replace to="/settings/privacy"><Fa :icon="faLockOpen" fixed-width class="icon"/>{{ $t('privacy') }}</MkA>
|
||||
<MkA class="item" :class="{ active: page === 'reaction' }" replace to="/settings/reaction"><Fa :icon="faLaugh" fixed-width class="icon"/>{{ $t('reaction') }}</MkA>
|
||||
<MkA class="item" :class="{ active: page === 'notifications' }" replace to="/settings/notifications"><Fa :icon="faBell" fixed-width class="icon"/>{{ $t('notifications') }}</MkA>
|
||||
<MkA class="item" :class="{ active: page === 'integration' }" replace to="/settings/integration"><Fa :icon="faShareAlt" fixed-width class="icon"/>{{ $t('integration') }}</MkA>
|
||||
<MkA class="item" :class="{ active: page === 'security' }" replace to="/settings/security"><Fa :icon="faLock" fixed-width class="icon"/>{{ $t('security') }}</MkA>
|
||||
</div>
|
||||
<div class="menu">
|
||||
<div class="label">{{ $t('clientSettings') }}</div>
|
||||
<MkA class="item" :class="{ active: page === 'general' }" replace to="/settings/general"><Fa :icon="faCogs" fixed-width class="icon"/>{{ $t('general') }}</MkA>
|
||||
<MkA class="item" :class="{ active: page === 'theme' }" replace to="/settings/theme"><Fa :icon="faPalette" fixed-width class="icon"/>{{ $t('theme') }}</MkA>
|
||||
<MkA class="item" :class="{ active: page === 'sidebar' }" replace to="/settings/sidebar"><Fa :icon="faListUl" fixed-width class="icon"/>{{ $t('sidebar') }}</MkA>
|
||||
<MkA class="item" :class="{ active: page === 'sounds' }" replace to="/settings/sounds"><Fa :icon="faMusic" fixed-width class="icon"/>{{ $t('sounds') }}</MkA>
|
||||
<MkA class="item" :class="{ active: page === 'plugins' }" replace to="/settings/plugins"><Fa :icon="faPlug" fixed-width class="icon"/>{{ $t('plugins') }}</MkA>
|
||||
</div>
|
||||
<div class="menu">
|
||||
<div class="label">{{ $t('otherSettings') }}</div>
|
||||
<MkA class="item" :class="{ active: page === 'import-export' }" replace to="/settings/import-export"><Fa :icon="faBoxes" fixed-width class="icon"/>{{ $t('importAndExport') }}</MkA>
|
||||
<MkA class="item" :class="{ active: page === 'mute-block' }" replace to="/settings/mute-block"><Fa :icon="faBan" fixed-width class="icon"/>{{ $t('muteAndBlock') }}</MkA>
|
||||
<MkA class="item" :class="{ active: page === 'word-mute' }" replace to="/settings/word-mute"><Fa :icon="faCommentSlash" fixed-width class="icon"/>{{ $t('wordMute') }}</MkA>
|
||||
<MkA class="item" :class="{ active: page === 'api' }" replace to="/settings/api"><Fa :icon="faKey" fixed-width class="icon"/>API</MkA>
|
||||
<MkA class="item" :class="{ active: page === 'other' }" replace to="/settings/other"><Fa :icon="faEllipsisH" fixed-width class="icon"/>{{ $t('other') }}</MkA>
|
||||
</div>
|
||||
<div class="menu">
|
||||
<button class="_button item" @click="logout">{{ $t('logout') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<FormBase class="nav" v-if="!narrow || page == null" :force-wide="!narrow">
|
||||
<FormGroup>
|
||||
<template #label>{{ $t('basicSettings') }}</template>
|
||||
<FormLink :active="page === 'profile'" replace to="/settings/profile"><template #icon><Fa :icon="faUser"/></template>{{ $t('profile') }}</FormLink>
|
||||
<FormLink :active="page === 'privacy'" replace to="/settings/privacy"><template #icon><Fa :icon="faLockOpen"/></template>{{ $t('privacy') }}</FormLink>
|
||||
<FormLink :active="page === 'reaction'" replace to="/settings/reaction"><template #icon><Fa :icon="faLaugh"/></template>{{ $t('reaction') }}</FormLink>
|
||||
<FormLink :active="page === 'notifications'" replace to="/settings/notifications"><template #icon><Fa :icon="faBell"/></template>{{ $t('notifications') }}</FormLink>
|
||||
<FormLink :active="page === 'email'" replace to="/settings/email"><template #icon><Fa :icon="faEnvelope"/></template>{{ $t('email') }}</FormLink>
|
||||
<FormLink :active="page === 'integration'" replace to="/settings/integration"><template #icon><Fa :icon="faShareAlt"/></template>{{ $t('integration') }}</FormLink>
|
||||
<FormLink :active="page === 'security'" replace to="/settings/security"><template #icon><Fa :icon="faLock"/></template>{{ $t('security') }}</FormLink>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<template #label>{{ $t('clientSettings') }}</template>
|
||||
<FormLink :active="page === 'general'" replace to="/settings/general"><template #icon><Fa :icon="faCogs"/></template>{{ $t('general') }}</FormLink>
|
||||
<FormLink :active="page === 'theme'" replace to="/settings/theme"><template #icon><Fa :icon="faPalette"/></template>{{ $t('theme') }}</FormLink>
|
||||
<FormLink :active="page === 'sidebar'" replace to="/settings/sidebar"><template #icon><Fa :icon="faListUl"/></template>{{ $t('sidebar') }}</FormLink>
|
||||
<FormLink :active="page === 'sounds'" replace to="/settings/sounds"><template #icon><Fa :icon="faMusic"/></template>{{ $t('sounds') }}</FormLink>
|
||||
<FormLink :active="page === 'plugins'" replace to="/settings/plugins"><template #icon><Fa :icon="faPlug"/></template>{{ $t('plugins') }}</FormLink>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<template #label>{{ $t('otherSettings') }}</template>
|
||||
<FormLink :active="page === 'import-export'" replace to="/settings/import-export"><template #icon><Fa :icon="faBoxes"/></template>{{ $t('importAndExport') }}</FormLink>
|
||||
<FormLink :active="page === 'mute-block'" replace to="/settings/mute-block"><template #icon><Fa :icon="faBan"/></template>{{ $t('muteAndBlock') }}</FormLink>
|
||||
<FormLink :active="page === 'word-mute'" replace to="/settings/word-mute"><template #icon><Fa :icon="faCommentSlash"/></template>{{ $t('wordMute') }}</FormLink>
|
||||
<FormLink :active="page === 'api'" replace to="/settings/api"><template #icon><Fa :icon="faKey"/></template>API</FormLink>
|
||||
<FormLink :active="page === 'other'" replace to="/settings/other"><template #icon><Fa :icon="faEllipsisH"/></template>{{ $t('other') }}</FormLink>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormButton @click="logout" danger>{{ $t('logout') }}</FormButton>
|
||||
</FormGroup>
|
||||
</FormBase>
|
||||
<div class="main">
|
||||
<transition :name="($store.state.device.animation && !narrow) ? 'view-slide' : ''" appear mode="out-in">
|
||||
<component :is="component" @info="onInfo"/>
|
||||
</transition>
|
||||
<component :is="component" @info="onInfo"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineAsyncComponent, defineComponent, onMounted, ref } from 'vue';
|
||||
import { computed, defineAsyncComponent, defineComponent, nextTick, onMounted, ref, watch } from 'vue';
|
||||
import { faCog, faPalette, faPlug, faUser, faListUl, faLock, faCommentSlash, faMusic, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey, faBoxes } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faLaugh, faBell } from '@fortawesome/free-regular-svg-icons';
|
||||
import { faLaugh, faBell, faEnvelope } from '@fortawesome/free-regular-svg-icons';
|
||||
import { store } from '@/store';
|
||||
import { i18n } from '@/i18n';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import { scroll } from '../../scripts/scroll';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormBase,
|
||||
FormLink,
|
||||
FormGroup,
|
||||
FormButton,
|
||||
},
|
||||
|
||||
props: {
|
||||
page: {
|
||||
type: String,
|
||||
@@ -74,21 +85,35 @@ export default defineComponent({
|
||||
case 'word-mute': return defineAsyncComponent(() => import('./word-mute.vue'));
|
||||
case 'integration': return defineAsyncComponent(() => import('./integration.vue'));
|
||||
case 'security': return defineAsyncComponent(() => import('./security.vue'));
|
||||
case '2fa': return defineAsyncComponent(() => import('./2fa.vue'));
|
||||
case 'api': return defineAsyncComponent(() => import('./api.vue'));
|
||||
case 'apps': return defineAsyncComponent(() => import('./apps.vue'));
|
||||
case 'other': return defineAsyncComponent(() => import('./other.vue'));
|
||||
case 'general': return defineAsyncComponent(() => import('./general.vue'));
|
||||
case 'email': return defineAsyncComponent(() => import('./email.vue'));
|
||||
case 'email/address': return defineAsyncComponent(() => import('./email-address.vue'));
|
||||
case 'theme': return defineAsyncComponent(() => import('./theme.vue'));
|
||||
case 'theme/install': return defineAsyncComponent(() => import('./theme.install.vue'));
|
||||
case 'theme/manage': return defineAsyncComponent(() => import('./theme.manage.vue'));
|
||||
case 'sidebar': return defineAsyncComponent(() => import('./sidebar.vue'));
|
||||
case 'sounds': return defineAsyncComponent(() => import('./sounds.vue'));
|
||||
case 'deck': return defineAsyncComponent(() => import('./deck.vue'));
|
||||
case 'plugins': return defineAsyncComponent(() => import('./plugins.vue'));
|
||||
case 'import-export': return defineAsyncComponent(() => import('./import-export.vue'));
|
||||
case 'account-info': return defineAsyncComponent(() => import('./account-info.vue'));
|
||||
case 'regedit': return defineAsyncComponent(() => import('./regedit.vue'));
|
||||
default: return null;
|
||||
}
|
||||
});
|
||||
|
||||
watch(component, () => {
|
||||
nextTick(() => {
|
||||
scroll(el.value, 0);
|
||||
});
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
narrow.value = el.value.offsetWidth < 650;
|
||||
narrow.value = el.value.offsetWidth < 1025;
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -102,76 +127,36 @@ export default defineComponent({
|
||||
store.dispatch('logout');
|
||||
location.href = '/';
|
||||
},
|
||||
faPalette, faPlug, faUser, faListUl, faLock, faLaugh, faCommentSlash, faMusic, faBell, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey, faBoxes,
|
||||
faPalette, faPlug, faUser, faListUl, faLock, faLaugh, faCommentSlash, faMusic, faBell, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey, faBoxes, faEnvelope,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.view-slide-enter-active, .view-slide-leave-active {
|
||||
transition: opacity 0.3s, transform 0.3s !important;
|
||||
}
|
||||
.view-slide-enter-from, .view-slide-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(32px);
|
||||
}
|
||||
|
||||
.vvcocwet {
|
||||
> .nav {
|
||||
> .menu {
|
||||
margin: 16px 0;
|
||||
|
||||
> .label {
|
||||
padding: 8px 32px;
|
||||
font-size: 80%;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
> .item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0 32px;
|
||||
line-height: 48px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
//background: var(--panel);
|
||||
//border-bottom: solid 1px var(--divider);
|
||||
transition: padding 0.2s ease, color 0.1s ease;
|
||||
|
||||
&:first-of-type {
|
||||
//border-top: solid 1px var(--divider);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--accent);
|
||||
padding-left: 42px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
padding-left: 42px;
|
||||
}
|
||||
|
||||
> .icon {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.wide {
|
||||
display: flex;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
|
||||
> .nav {
|
||||
width: 30%;
|
||||
max-width: 300px;
|
||||
width: 32%;
|
||||
box-sizing: border-box;
|
||||
border-right: solid 0.5px var(--divider);
|
||||
}
|
||||
|
||||
> .main {
|
||||
flex: 1;
|
||||
--baseContentWidth: 100%;
|
||||
|
||||
::v-deep(._section) {
|
||||
padding: 0 0 32px 0;
|
||||
|
||||
& + ._section {
|
||||
padding-top: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<section class="rrfwjxfl _section">
|
||||
<MkTab v-model:value="tab" :items="[{ label: $t('mutedUsers'), value: 'mute' }, { label: $t('blockedUsers'), value: 'block' }]" style="margin-bottom: var(--margin);"/>
|
||||
<MkTab v-model:value="tab" style="margin-bottom: var(--margin);">
|
||||
<option value="mute">{{ $t('mutedUsers') }}</option>
|
||||
<option value="block">{{ $t('blockedUsers') }}</option>
|
||||
</MkTab>
|
||||
<div class="_content" v-if="tab === 'mute'">
|
||||
<MkPagination :pagination="mutingPagination" class="muting">
|
||||
<template #empty><MkInfo>{{ $t('noUsers') }}</MkInfo></template>
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="_section">
|
||||
<MkButton full primary @click="configure"><Fa :icon="faCog"/> {{ $t('notificationSetting') }}</MkButton>
|
||||
</div>
|
||||
<div class="_section">
|
||||
<MkButton full @click="readAllNotifications">{{ $t('markAsReadAllNotifications') }}</MkButton>
|
||||
<MkButton full @click="readAllUnreadNotes">{{ $t('markAsReadAllUnreadNotes') }}</MkButton>
|
||||
<MkButton full @click="readAllMessagingMessages">{{ $t('markAsReadAllTalkMessages') }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
<FormBase>
|
||||
<FormLink @click="configure">{{ $t('notificationSetting') }}</FormLink>
|
||||
<FormGroup>
|
||||
<FormButton @click="readAllNotifications">{{ $t('markAsReadAllNotifications') }}</FormButton>
|
||||
<FormButton @click="readAllUnreadNotes">{{ $t('markAsReadAllUnreadNotes') }}</FormButton>
|
||||
<FormButton @click="readAllMessagingMessages">{{ $t('markAsReadAllTalkMessages') }}</FormButton>
|
||||
</FormGroup>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faCog } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faBell } from '@fortawesome/free-regular-svg-icons';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkSwitch from '@/components/ui/switch.vue';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import { notificationTypes } from '../../../types';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
MkSwitch,
|
||||
FormBase,
|
||||
FormLink,
|
||||
FormButton,
|
||||
FormGroup,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
|
||||
@@ -1,40 +1,43 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="_section">
|
||||
<div class="_card">
|
||||
<div class="_content">
|
||||
<MkSwitch v-model:value="$store.state.i.injectFeaturedNote" @update:value="onChangeInjectFeaturedNote">
|
||||
{{ $t('showFeaturedNotesInTimeline') }}
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="_section">
|
||||
<MkSwitch v-model:value="debug" @update:value="changeDebug">
|
||||
<FormBase>
|
||||
<FormSwitch :value="$store.state.i.injectFeaturedNote" @update:value="onChangeInjectFeaturedNote">
|
||||
{{ $t('showFeaturedNotesInTimeline') }}
|
||||
</FormSwitch>
|
||||
|
||||
<FormLink to="/settings/account-info">{{ $t('accountInfo') }}</FormLink>
|
||||
|
||||
<FormGroup>
|
||||
<FormSwitch v-model:value="debug" @update:value="changeDebug">
|
||||
DEBUG MODE
|
||||
</MkSwitch>
|
||||
<div v-if="debug">
|
||||
<MkA to="/settings/regedit">RegEdit</MkA>
|
||||
<MkButton @click="taskmanager">Task Manager</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FormSwitch>
|
||||
<template v-if="debug">
|
||||
<FormLink to="/settings/regedit">RegEdit</FormLink>
|
||||
<FormButton @click="taskmanager">Task Manager</FormButton>
|
||||
</template>
|
||||
</FormGroup>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, defineComponent } from 'vue';
|
||||
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkSelect from '@/components/ui/select.vue';
|
||||
import MkSwitch from '@/components/ui/switch.vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormSelect from '@/components/form/select.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import * as os from '@/os';
|
||||
import { debug } from '@/config';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkSelect,
|
||||
MkSwitch,
|
||||
MkButton,
|
||||
FormBase,
|
||||
FormSelect,
|
||||
FormSwitch,
|
||||
FormButton,
|
||||
FormLink,
|
||||
FormGroup,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
|
||||
@@ -1,36 +1,43 @@
|
||||
<template>
|
||||
<div class="_section">
|
||||
<div class="_card">
|
||||
<div class="_content">
|
||||
<MkSwitch v-model:value="isLocked" @update:value="save()">{{ $t('makeFollowManuallyApprove') }}</MkSwitch>
|
||||
<MkSwitch v-model:value="autoAcceptFollowed" v-if="isLocked" @update:value="save()">{{ $t('autoAcceptFollowed') }}</MkSwitch>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<MkSwitch v-model:value="rememberNoteVisibility" @update:value="save()">{{ $t('rememberNoteVisibility') }}</MkSwitch>
|
||||
<MkSelect v-model:value="defaultNoteVisibility" style="margin-bottom: 8px;" v-if="!rememberNoteVisibility">
|
||||
<template #label>{{ $t('defaultNoteVisibility') }}</template>
|
||||
<option value="public">{{ $t('_visibility.public') }}</option>
|
||||
<option value="home">{{ $t('_visibility.home') }}</option>
|
||||
<option value="followers">{{ $t('_visibility.followers') }}</option>
|
||||
<option value="specified">{{ $t('_visibility.specified') }}</option>
|
||||
</MkSelect>
|
||||
<MkSwitch v-model:value="defaultNoteLocalOnly" v-if="!rememberNoteVisibility">{{ $t('_visibility.localOnly') }}</MkSwitch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FormBase>
|
||||
<FormGroup>
|
||||
<FormSwitch v-model:value="isLocked" @update:value="save()">{{ $t('makeFollowManuallyApprove') }}</FormSwitch>
|
||||
<FormSwitch v-model:value="autoAcceptFollowed" :disabled="!isLocked" @update:value="save()">{{ $t('autoAcceptFollowed') }}</FormSwitch>
|
||||
<template #caption>{{ $t('lockedAccountInfo') }}</template>
|
||||
</FormGroup>
|
||||
<FormSwitch v-model:value="noCrawle" @update:value="save()">
|
||||
{{ $t('noCrawle') }}
|
||||
<template #desc>{{ $t('noCrawleDescription') }}</template>
|
||||
</FormSwitch>
|
||||
<FormSwitch v-model:value="rememberNoteVisibility" @update:value="save()">{{ $t('rememberNoteVisibility') }}</FormSwitch>
|
||||
<FormGroup v-if="!rememberNoteVisibility">
|
||||
<template #label>{{ $t('defaultNoteVisibility') }}</template>
|
||||
<FormSelect v-model:value="defaultNoteVisibility">
|
||||
<option value="public">{{ $t('_visibility.public') }}</option>
|
||||
<option value="home">{{ $t('_visibility.home') }}</option>
|
||||
<option value="followers">{{ $t('_visibility.followers') }}</option>
|
||||
<option value="specified">{{ $t('_visibility.specified') }}</option>
|
||||
</FormSelect>
|
||||
<FormSwitch v-model:value="defaultNoteLocalOnly">{{ $t('_visibility.localOnly') }}</FormSwitch>
|
||||
</FormGroup>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faLockOpen } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkSelect from '@/components/ui/select.vue';
|
||||
import MkSwitch from '@/components/ui/switch.vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormSelect from '@/components/form/select.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkSelect,
|
||||
MkSwitch,
|
||||
FormBase,
|
||||
FormSelect,
|
||||
FormGroup,
|
||||
FormSwitch,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
@@ -43,6 +50,7 @@ export default defineComponent({
|
||||
},
|
||||
isLocked: false,
|
||||
autoAcceptFollowed: false,
|
||||
noCrawle: false,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -66,6 +74,7 @@ export default defineComponent({
|
||||
created() {
|
||||
this.isLocked = this.$store.state.i.isLocked;
|
||||
this.autoAcceptFollowed = this.$store.state.i.autoAcceptFollowed;
|
||||
this.noCrawle = this.$store.state.i.noCrawle;
|
||||
},
|
||||
|
||||
mounted() {
|
||||
@@ -77,6 +86,7 @@ export default defineComponent({
|
||||
os.api('i/update', {
|
||||
isLocked: !!this.isLocked,
|
||||
autoAcceptFollowed: !!this.autoAcceptFollowed,
|
||||
noCrawle: !!this.noCrawle,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +1,67 @@
|
||||
<template>
|
||||
<div class="_section">
|
||||
<div class="llvierxe _card">
|
||||
<div class="_title"><Fa :icon="faUser"/> {{ $t('profile') }}<small style="display: block; font-weight: normal; opacity: 0.6;">@{{ $store.state.i.username }}@{{ host }}</small></div>
|
||||
<div class="_content">
|
||||
<div class="header" :style="{ backgroundImage: $store.state.i.bannerUrl ? `url(${ $store.state.i.bannerUrl })` : null }" @click="changeBanner">
|
||||
<MkAvatar class="avatar" :user="$store.state.i" :disable-preview="true" :disable-link="true" @click.stop="changeAvatar"/>
|
||||
</div>
|
||||
|
||||
<MkInput v-model:value="name" :max="30">
|
||||
<span>{{ $t('_profile.name') }}</span>
|
||||
</MkInput>
|
||||
|
||||
<MkTextarea v-model:value="description" :max="500">
|
||||
<span>{{ $t('_profile.description') }}</span>
|
||||
<template #desc>{{ $t('_profile.youCanIncludeHashtags') }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<MkInput v-model:value="location">
|
||||
<span>{{ $t('location') }}</span>
|
||||
<template #prefix><Fa :icon="faMapMarkerAlt"/></template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model:value="birthday" type="date">
|
||||
<template #title>{{ $t('birthday') }}</template>
|
||||
<template #prefix><Fa :icon="faBirthdayCake"/></template>
|
||||
</MkInput>
|
||||
|
||||
<details class="fields">
|
||||
<summary>{{ $t('_profile.metadata') }}</summary>
|
||||
<div class="row">
|
||||
<MkInput v-model:value="fieldName0">{{ $t('_profile.metadataLabel') }}</MkInput>
|
||||
<MkInput v-model:value="fieldValue0">{{ $t('_profile.metadataContent') }}</MkInput>
|
||||
</div>
|
||||
<div class="row">
|
||||
<MkInput v-model:value="fieldName1">{{ $t('_profile.metadataLabel') }}</MkInput>
|
||||
<MkInput v-model:value="fieldValue1">{{ $t('_profile.metadataContent') }}</MkInput>
|
||||
</div>
|
||||
<div class="row">
|
||||
<MkInput v-model:value="fieldName2">{{ $t('_profile.metadataLabel') }}</MkInput>
|
||||
<MkInput v-model:value="fieldValue2">{{ $t('_profile.metadataContent') }}</MkInput>
|
||||
</div>
|
||||
<div class="row">
|
||||
<MkInput v-model:value="fieldName3">{{ $t('_profile.metadataLabel') }}</MkInput>
|
||||
<MkInput v-model:value="fieldValue3">{{ $t('_profile.metadataContent') }}</MkInput>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<MkSwitch v-model:value="isBot">{{ $t('flagAsBot') }}</MkSwitch>
|
||||
<MkSwitch v-model:value="isCat">{{ $t('flagAsCat') }}</MkSwitch>
|
||||
</div>
|
||||
<div class="_footer">
|
||||
<MkButton @click="save(true)" primary><Fa :icon="faSave"/> {{ $t('save') }}</MkButton>
|
||||
</div>
|
||||
<FormBase class="llvierxe">
|
||||
<div class="header _formItem" :style="{ backgroundImage: $store.state.i.bannerUrl ? `url(${ $store.state.i.bannerUrl })` : null }" @click="changeBanner">
|
||||
<MkAvatar class="avatar" :user="$store.state.i" :disable-preview="true" :disable-link="true" @click.stop="changeAvatar"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormInput v-model:value="name" :max="30">
|
||||
<span>{{ $t('_profile.name') }}</span>
|
||||
</FormInput>
|
||||
|
||||
<FormTextarea v-model:value="description" :max="500">
|
||||
<span>{{ $t('_profile.description') }}</span>
|
||||
<template #desc>{{ $t('_profile.youCanIncludeHashtags') }}</template>
|
||||
</FormTextarea>
|
||||
|
||||
<FormInput v-model:value="location">
|
||||
<span>{{ $t('location') }}</span>
|
||||
<template #prefix><Fa :icon="faMapMarkerAlt"/></template>
|
||||
</FormInput>
|
||||
|
||||
<FormInput v-model:value="birthday" type="date">
|
||||
<span>{{ $t('birthday') }}</span>
|
||||
<template #prefix><Fa :icon="faBirthdayCake"/></template>
|
||||
</FormInput>
|
||||
|
||||
<FormGroup>
|
||||
<FormButton @click="editMetadata" primary>{{ $t('_profile.metadataEdit') }}</FormButton>
|
||||
<template #caption>{{ $t('_profile.metadataDescription') }}</template>
|
||||
</FormGroup>
|
||||
|
||||
<FormSwitch v-model:value="isCat">{{ $t('flagAsCat') }}<template #desc>{{ $t('flagAsCatDescription') }}</template></FormSwitch>
|
||||
|
||||
<FormSwitch v-model:value="isBot">{{ $t('flagAsBot') }}<template #desc>{{ $t('flagAsBotDescription') }}</template></FormSwitch>
|
||||
|
||||
<FormSwitch v-model:value="alwaysMarkNsfw">{{ $t('alwaysMarkSensitive') }}</FormSwitch>
|
||||
|
||||
<FormButton @click="save(true)" primary><Fa :icon="faSave"/> {{ $t('save') }}</FormButton>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faUnlockAlt, faCogs, faUser, faMapMarkerAlt, faBirthdayCake } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faSave } from '@fortawesome/free-regular-svg-icons';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkInput from '@/components/ui/input.vue';
|
||||
import MkTextarea from '@/components/ui/textarea.vue';
|
||||
import MkSwitch from '@/components/ui/switch.vue';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormTextarea from '@/components/form/textarea.vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormTuple from '@/components/form/tuple.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import { host } from '@/config';
|
||||
import { selectFile } from '@/scripts/select-file';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
MkInput,
|
||||
MkTextarea,
|
||||
MkSwitch,
|
||||
FormButton,
|
||||
FormInput,
|
||||
FormTextarea,
|
||||
FormSwitch,
|
||||
FormTuple,
|
||||
FormBase,
|
||||
FormGroup,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
@@ -101,6 +89,7 @@ export default defineComponent({
|
||||
bannerId: null,
|
||||
isBot: false,
|
||||
isCat: false,
|
||||
alwaysMarkNsfw: false,
|
||||
saving: false,
|
||||
faSave, faUnlockAlt, faCogs, faUser, faMapMarkerAlt, faBirthdayCake
|
||||
}
|
||||
@@ -115,6 +104,7 @@ export default defineComponent({
|
||||
this.bannerId = this.$store.state.i.bannerId;
|
||||
this.isBot = this.$store.state.i.isBot;
|
||||
this.isCat = this.$store.state.i.isCat;
|
||||
this.alwaysMarkNsfw = this.$store.state.i.alwaysMarkNsfw;
|
||||
|
||||
this.fieldName0 = this.$store.state.i.fields[0] ? this.$store.state.i.fields[0].name : null;
|
||||
this.fieldValue0 = this.$store.state.i.fields[0] ? this.$store.state.i.fields[0].value : null;
|
||||
@@ -147,7 +137,60 @@ export default defineComponent({
|
||||
});
|
||||
},
|
||||
|
||||
save(notify) {
|
||||
async editMetadata() {
|
||||
const { canceled, result } = await os.form(this.$t('_profile.metadata'), {
|
||||
fieldName0: {
|
||||
type: 'string',
|
||||
label: this.$t('_profile.metadataLabel') + ' 1',
|
||||
default: this.fieldName0,
|
||||
},
|
||||
fieldValue0: {
|
||||
type: 'string',
|
||||
label: this.$t('_profile.metadataContent') + ' 1',
|
||||
default: this.fieldValue0,
|
||||
},
|
||||
fieldName1: {
|
||||
type: 'string',
|
||||
label: this.$t('_profile.metadataLabel') + ' 2',
|
||||
default: this.fieldName1,
|
||||
},
|
||||
fieldValue1: {
|
||||
type: 'string',
|
||||
label: this.$t('_profile.metadataContent') + ' 2',
|
||||
default: this.fieldValue1,
|
||||
},
|
||||
fieldName2: {
|
||||
type: 'string',
|
||||
label: this.$t('_profile.metadataLabel') + ' 3',
|
||||
default: this.fieldName2,
|
||||
},
|
||||
fieldValue2: {
|
||||
type: 'string',
|
||||
label: this.$t('_profile.metadataContent') + ' 3',
|
||||
default: this.fieldValue2,
|
||||
},
|
||||
fieldName3: {
|
||||
type: 'string',
|
||||
label: this.$t('_profile.metadataLabel') + ' 4',
|
||||
default: this.fieldName3,
|
||||
},
|
||||
fieldValue3: {
|
||||
type: 'string',
|
||||
label: this.$t('_profile.metadataContent') + ' 4',
|
||||
default: this.fieldValue3,
|
||||
},
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
this.fieldName0 = result.fieldName0;
|
||||
this.fieldValue0 = result.fieldValue0;
|
||||
this.fieldName1 = result.fieldName1;
|
||||
this.fieldValue1 = result.fieldValue1;
|
||||
this.fieldName2 = result.fieldName2;
|
||||
this.fieldValue2 = result.fieldValue2;
|
||||
this.fieldName3 = result.fieldName3;
|
||||
this.fieldValue3 = result.fieldValue3;
|
||||
|
||||
const fields = [
|
||||
{ name: this.fieldName0, value: this.fieldValue0 },
|
||||
{ name: this.fieldName1, value: this.fieldValue1 },
|
||||
@@ -155,6 +198,19 @@ export default defineComponent({
|
||||
{ name: this.fieldName3, value: this.fieldValue3 },
|
||||
];
|
||||
|
||||
os.api('i/update', {
|
||||
fields,
|
||||
}).then(i => {
|
||||
os.success();
|
||||
}).catch(err => {
|
||||
os.dialog({
|
||||
type: 'error',
|
||||
text: err.id
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
save(notify) {
|
||||
this.saving = true;
|
||||
|
||||
os.api('i/update', {
|
||||
@@ -162,9 +218,9 @@ export default defineComponent({
|
||||
description: this.description || null,
|
||||
location: this.location || null,
|
||||
birthday: this.birthday || null,
|
||||
fields,
|
||||
isBot: !!this.isBot,
|
||||
isCat: !!this.isCat,
|
||||
alwaysMarkNsfw: !!this.alwaysMarkNsfw,
|
||||
}).then(i => {
|
||||
this.saving = false;
|
||||
this.$store.state.i.avatarId = i.avatarId;
|
||||
@@ -189,41 +245,29 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.llvierxe {
|
||||
> ._content {
|
||||
> .header {
|
||||
position: relative;
|
||||
height: 150px;
|
||||
overflow: hidden;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: 5px;
|
||||
border: solid 1px var(--divider);
|
||||
box-sizing: border-box;
|
||||
> .header {
|
||||
position: relative;
|
||||
height: 150px;
|
||||
overflow: hidden;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: 5px;
|
||||
border: solid 1px var(--divider);
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
||||
> .avatar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: block;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
margin: auto;
|
||||
cursor: pointer;
|
||||
|
||||
> .avatar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: block;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
margin: auto;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 0 6px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
> .fields {
|
||||
> .row {
|
||||
> * {
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
box-shadow: 0 0 0 6px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,56 @@
|
||||
<template>
|
||||
<div class="_section">
|
||||
<div class="_card">
|
||||
<div class="_title"><Fa :icon="faLaugh"/> {{ $t('reaction') }}</div>
|
||||
<div class="_content">
|
||||
<MkInput v-model:value="reactions" style="font-family: 'Segoe UI Emoji', 'Noto Color Emoji', Roboto, HelveticaNeue, Arial, sans-serif">
|
||||
{{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }} <button class="_textButton" @click="chooseEmoji">{{ $t('chooseEmoji') }}</button></template>
|
||||
</MkInput>
|
||||
<MkButton inline @click="setDefault"><Fa :icon="faUndo"/> {{ $t('default') }}</MkButton>
|
||||
<MkSwitch v-model:value="useFullReactionPicker">{{ $t('useFullReactionPicker') }}</MkSwitch>
|
||||
</div>
|
||||
<div class="_footer">
|
||||
<MkButton @click="save()" primary inline :disabled="!changed"><Fa :icon="faSave"/> {{ $t('save') }}</MkButton>
|
||||
<MkButton inline @click="preview"><Fa :icon="faEye"/> {{ $t('preview') }}</MkButton>
|
||||
<FormBase>
|
||||
<div class="_formItem">
|
||||
<div class="_formLabel">{{ $t('reactionSettingDescription') }}</div>
|
||||
<div class="_formPanel">
|
||||
<XDraggable class="zoaiodol" :list="reactions" animation="150" delay="100" delay-on-touch-only="true">
|
||||
<button class="_button item" v-for="reaction in reactions" :key="reaction" @click="remove(reaction, $event)">
|
||||
<MkEmoji :emoji="reaction" :normal="true"/>
|
||||
</button>
|
||||
<template #footer>
|
||||
<button>a</button>
|
||||
</template>
|
||||
</XDraggable>
|
||||
</div>
|
||||
<div class="_formCaption">{{ $t('reactionSettingDescription2') }} <button class="_textButton" @click="chooseEmoji">{{ $t('chooseEmoji') }}</button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormRadios v-model="reactionPickerWidth">
|
||||
<template #desc>{{ $t('width') }}</template>
|
||||
<option :value="1">{{ $t('small') }}</option>
|
||||
<option :value="2">{{ $t('medium') }}</option>
|
||||
<option :value="3">{{ $t('large') }}</option>
|
||||
</FormRadios>
|
||||
<FormRadios v-model="reactionPickerHeight">
|
||||
<template #desc>{{ $t('height') }}</template>
|
||||
<option :value="1">{{ $t('small') }}</option>
|
||||
<option :value="2">{{ $t('medium') }}</option>
|
||||
<option :value="3">{{ $t('large') }}</option>
|
||||
</FormRadios>
|
||||
<FormButton @click="preview"><Fa :icon="faEye"/> {{ $t('preview') }}</FormButton>
|
||||
<FormButton danger @click="setDefault"><Fa :icon="faUndo"/> {{ $t('default') }}</FormButton>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faLaugh, faSave, faEye } from '@fortawesome/free-regular-svg-icons';
|
||||
import { faUndo } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkInput from '@/components/ui/input.vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkSwitch from '@/components/ui/switch.vue';
|
||||
import { emojiRegexWithCustom } from '../../../misc/emoji-regex';
|
||||
import { VueDraggableNext } from 'vue-draggable-next';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormRadios from '@/components/form/radios.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import { defaultSettings } from '@/store';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkInput,
|
||||
MkButton,
|
||||
MkSwitch,
|
||||
FormInput,
|
||||
FormButton,
|
||||
FormBase,
|
||||
FormRadios,
|
||||
XDraggable: VueDraggableNext,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
@@ -41,29 +59,36 @@ export default defineComponent({
|
||||
return {
|
||||
INFO: {
|
||||
title: this.$t('reaction'),
|
||||
icon: faLaugh
|
||||
icon: faLaugh,
|
||||
action: {
|
||||
icon: faEye,
|
||||
handler: this.preview
|
||||
}
|
||||
},
|
||||
reactions: this.$store.state.settings.reactions.join(''),
|
||||
changed: false,
|
||||
reactions: JSON.parse(JSON.stringify(this.$store.state.settings.reactions)),
|
||||
faLaugh, faSave, faEye, faUndo
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
splited(): any {
|
||||
return this.reactions.match(emojiRegexWithCustom);
|
||||
},
|
||||
|
||||
useFullReactionPicker: {
|
||||
get() { return this.$store.state.device.useFullReactionPicker; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'useFullReactionPicker', value: value }); }
|
||||
},
|
||||
reactionPickerWidth: {
|
||||
get() { return this.$store.state.device.reactionPickerWidth; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'reactionPickerWidth', value: value }); }
|
||||
},
|
||||
reactionPickerHeight: {
|
||||
get() { return this.$store.state.device.reactionPickerHeight; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'reactionPickerHeight', value: value }); }
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
reactions: {
|
||||
handler() {
|
||||
this.changed = true;
|
||||
this.save();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
@@ -75,34 +100,57 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
save() {
|
||||
this.$store.dispatch('settings/set', { key: 'reactions', value: this.splited });
|
||||
this.changed = false;
|
||||
this.$store.dispatch('settings/set', { key: 'reactions', value: this.reactions });
|
||||
},
|
||||
|
||||
remove(reaction, ev) {
|
||||
os.modalMenu([{
|
||||
text: this.$t('remove'),
|
||||
action: () => {
|
||||
this.reactions = this.reactions.filter(x => x !== reaction)
|
||||
}
|
||||
}], ev.currentTarget || ev.target);
|
||||
},
|
||||
|
||||
preview(ev) {
|
||||
if (this.$store.state.device.useFullReactionPicker) {
|
||||
os.popup(import('@/components/emoji-picker.vue'), {
|
||||
overridePinned: this.splited,
|
||||
src: ev.currentTarget || ev.target,
|
||||
}, {}, 'closed');
|
||||
} else {
|
||||
os.popup(import('@/components/reaction-picker.vue'), {
|
||||
reactions: this.splited,
|
||||
showFocus: false,
|
||||
src: ev.currentTarget || ev.target,
|
||||
}, {}, 'closed');
|
||||
}
|
||||
os.popup(import('@/components/emoji-picker.vue'), {
|
||||
asReactionPicker: true,
|
||||
src: ev.currentTarget || ev.target,
|
||||
}, {}, 'closed');
|
||||
},
|
||||
|
||||
setDefault() {
|
||||
this.reactions = defaultSettings.reactions.join('');
|
||||
async setDefault() {
|
||||
const { canceled } = await os.dialog({
|
||||
type: 'warning',
|
||||
text: this.$t('resetAreYouSure'),
|
||||
showCancelButton: true
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
this.reactions = JSON.parse(JSON.stringify(defaultSettings.reactions));
|
||||
},
|
||||
|
||||
chooseEmoji(ev) {
|
||||
os.pickEmoji(ev.currentTarget || ev.target).then(emoji => {
|
||||
this.reactions += emoji;
|
||||
os.pickEmoji(ev.currentTarget || ev.target, {
|
||||
showPinned: false
|
||||
}).then(emoji => {
|
||||
if (!this.reactions.includes(emoji)) {
|
||||
this.reactions.push(emoji);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.zoaiodol {
|
||||
padding: 16px;
|
||||
|
||||
> .item {
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,29 +1,45 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="_section">
|
||||
<X2fa/>
|
||||
</div>
|
||||
<div class="_section">
|
||||
<MkButton primary @click="change()" full>{{ $t('changePassword') }}</MkButton>
|
||||
</div>
|
||||
<div class="_section">
|
||||
<MkButton class="_vMargin" primary @click="regenerateToken" full><Fa :icon="faSyncAlt"/> {{ $t('regenerateLoginToken') }}</MkButton>
|
||||
<div class="_caption _vMargin" style="padding: 0 6px;">{{ $t('regenerateLoginTokenDescription') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<FormBase>
|
||||
<X2fa/>
|
||||
<FormLink to="/settings/2fa"><template #icon><Fa :icon="faMobileAlt"/></template>{{ $t('twoStepAuthentication') }}</FormLink>
|
||||
<FormButton primary @click="change()">{{ $t('changePassword') }}</FormButton>
|
||||
<FormPagination :pagination="pagination">
|
||||
<template #label>{{ $t('signinHistory') }}</template>
|
||||
<template #default="{items}">
|
||||
<div class="_formPanel timnmucd" v-for="item in items" :key="item.id">
|
||||
<header>
|
||||
<Fa class="icon succ" :icon="faCheck" v-if="item.success"/>
|
||||
<Fa class="icon fail" :icon="faTimesCircle" v-else/>
|
||||
<code class="ip _monospace">{{ item.ip }}</code>
|
||||
<MkTime :time="item.createdAt" class="time"/>
|
||||
</header>
|
||||
</div>
|
||||
</template>
|
||||
</FormPagination>
|
||||
<FormGroup>
|
||||
<FormButton danger @click="regenerateToken"><Fa :icon="faSyncAlt"/> {{ $t('regenerateLoginToken') }}</FormButton>
|
||||
<template #caption>{{ $t('regenerateLoginTokenDescription') }}</template>
|
||||
</FormGroup>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faLock, faSyncAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import X2fa from './security.2fa.vue';
|
||||
import { faCheck, faTimesCircle, faLock, faSyncAlt, faMobileAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import FormPagination from '@/components/form/pagination.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
X2fa,
|
||||
FormBase,
|
||||
FormLink,
|
||||
FormButton,
|
||||
FormPagination,
|
||||
FormGroup,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
@@ -34,7 +50,11 @@ export default defineComponent({
|
||||
title: this.$t('security'),
|
||||
icon: faLock
|
||||
},
|
||||
faLock, faSyncAlt
|
||||
pagination: {
|
||||
endpoint: 'i/signin-history',
|
||||
limit: 5,
|
||||
},
|
||||
faLock, faSyncAlt, faCheck, faTimesCircle, faMobileAlt,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -98,3 +118,32 @@ export default defineComponent({
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.timnmucd {
|
||||
padding: 16px;
|
||||
|
||||
> header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> .icon {
|
||||
width: 1em;
|
||||
margin-right: 0.75em;
|
||||
|
||||
&.succ {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
&.fail {
|
||||
color: var(--error);
|
||||
}
|
||||
}
|
||||
|
||||
> .time {
|
||||
margin-left: auto;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
<template>
|
||||
<div class="_section">
|
||||
<div class="_card">
|
||||
<div class="_content">
|
||||
<MkTextarea v-model:value="items" tall>
|
||||
<span>{{ $t('sidebar') }}</span>
|
||||
<template #desc><button class="_textButton" @click="addItem">{{ $t('addItem') }}</button></template>
|
||||
</MkTextarea>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<div>{{ $t('display') }}</div>
|
||||
<MkRadio v-model="sidebarDisplay" value="full">{{ $t('_sidebar.full') }}</MkRadio>
|
||||
<MkRadio v-model="sidebarDisplay" value="icon">{{ $t('_sidebar.icon') }}</MkRadio>
|
||||
<!-- <MkRadio v-model="sidebarDisplay" value="hide" disabled>{{ $t('_sidebar.hide') }}</MkRadio>--> <!-- TODO: サイドバーを完全に隠せるようにすると、別途ハンバーガーボタンのようなものをUIに表示する必要があり面倒 -->
|
||||
</div>
|
||||
<div class="_footer">
|
||||
<MkButton inline @click="save()" primary><Fa :icon="faSave"/> {{ $t('save') }}</MkButton>
|
||||
<MkButton inline @click="reset()"><Fa :icon="faRedo"/> {{ $t('default') }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FormBase>
|
||||
<FormTextarea v-model:value="items" tall>
|
||||
<span>{{ $t('sidebar') }}</span>
|
||||
<template #desc><button class="_textButton" @click="addItem">{{ $t('addItem') }}</button></template>
|
||||
</FormTextarea>
|
||||
|
||||
<FormRadios v-model="sidebarDisplay">
|
||||
<template #desc>{{ $t('display') }}</template>
|
||||
<option value="full">{{ $t('_sidebar.full') }}</option>
|
||||
<option value="icon">{{ $t('_sidebar.icon') }}</option>
|
||||
<!-- <MkRadio v-model="sidebarDisplay" value="hide" disabled>{{ $t('_sidebar.hide') }}</MkRadio>--> <!-- TODO: サイドバーを完全に隠せるようにすると、別途ハンバーガーボタンのようなものをUIに表示する必要があり面倒 -->
|
||||
</FormRadios>
|
||||
|
||||
<FormButton @click="save()" primary><Fa :icon="faSave"/> {{ $t('save') }}</FormButton>
|
||||
<FormButton @click="reset()" danger><Fa :icon="faRedo"/> {{ $t('default') }}</FormButton>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faListUl, faSave, faRedo } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkTextarea from '@/components/ui/textarea.vue';
|
||||
import MkRadio from '@/components/ui/radio.vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormTextarea from '@/components/form/textarea.vue';
|
||||
import FormRadios from '@/components/form/radios.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import { defaultDeviceUserSettings } from '@/store';
|
||||
import * as os from '@/os';
|
||||
import { sidebarDef } from '@/sidebar';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
MkTextarea,
|
||||
MkRadio,
|
||||
FormBase,
|
||||
FormButton,
|
||||
FormTextarea,
|
||||
FormRadios,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
@@ -102,7 +102,3 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,62 +1,35 @@
|
||||
<template>
|
||||
<div class="_section">
|
||||
<div class="_card">
|
||||
<div class="_title"><Fa :icon="faMusic"/> {{ $t('sounds') }}</div>
|
||||
<div class="_content">
|
||||
<MkRange v-model:value="sfxVolume" :min="0" :max="1" :step="0.1">
|
||||
<Fa slot="icon" :icon="volumeIcon"/>
|
||||
<span slot="title">{{ $t('volume') }}</span>
|
||||
</MkRange>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<MkSelect v-model:value="sfxNote">
|
||||
<template #label>{{ $t('_sfx.note') }}</template>
|
||||
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
|
||||
<template #text><button class="_textButton" @click="listen(sfxNote)" v-if="sfxNote"><Fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
|
||||
</MkSelect>
|
||||
<MkSelect v-model:value="sfxNoteMy">
|
||||
<template #label>{{ $t('_sfx.noteMy') }}</template>
|
||||
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
|
||||
<template #text><button class="_textButton" @click="listen(sfxNoteMy)" v-if="sfxNoteMy"><Fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
|
||||
</MkSelect>
|
||||
<MkSelect v-model:value="sfxNotification">
|
||||
<template #label>{{ $t('_sfx.notification') }}</template>
|
||||
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
|
||||
<template #text><button class="_textButton" @click="listen(sfxNotification)" v-if="sfxNotification"><Fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
|
||||
</MkSelect>
|
||||
<MkSelect v-model:value="sfxChat">
|
||||
<template #label>{{ $t('_sfx.chat') }}</template>
|
||||
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
|
||||
<template #text><button class="_textButton" @click="listen(sfxChat)" v-if="sfxChat"><Fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
|
||||
</MkSelect>
|
||||
<MkSelect v-model:value="sfxChatBg">
|
||||
<template #label>{{ $t('_sfx.chatBg') }}</template>
|
||||
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
|
||||
<template #text><button class="_textButton" @click="listen(sfxChatBg)" v-if="sfxChatBg"><Fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
|
||||
</MkSelect>
|
||||
<MkSelect v-model:value="sfxAntenna">
|
||||
<template #label>{{ $t('_sfx.antenna') }}</template>
|
||||
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
|
||||
<template #text><button class="_textButton" @click="listen(sfxAntenna)" v-if="sfxAntenna"><Fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
|
||||
</MkSelect>
|
||||
<MkSelect v-model:value="sfxChannel">
|
||||
<template #label>{{ $t('_sfx.channel') }}</template>
|
||||
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
|
||||
<template #text><button class="_textButton" @click="listen(sfxChannel)" v-if="sfxChannel"><Fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
|
||||
</MkSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FormBase>
|
||||
<FormRange v-model:value="masterVolume" :min="0" :max="1" :step="0.05">
|
||||
<template #label><Fa :icon="volumeIcon" :key="volumeIcon"/> {{ $t('masterVolume') }}</template>
|
||||
</FormRange>
|
||||
|
||||
<FormGroup>
|
||||
<template #label>{{ $t('sounds') }}</template>
|
||||
<FormButton v-for="type in Object.keys(sounds)" :key="type" :center="false" @click="edit(type)">
|
||||
{{ $t('_sfx.' + type) }}
|
||||
<template #suffix>{{ sounds[type].type || $t('none') }}</template>
|
||||
<template #suffixIcon><Fa :icon="faChevronDown"/></template>
|
||||
</FormButton>
|
||||
</FormGroup>
|
||||
|
||||
<FormButton @click="reset()" danger><Fa :icon="faRedo"/> {{ $t('default') }}</FormButton>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { faMusic, faPlay, faVolumeUp, faVolumeMute } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkSelect from '@/components/ui/select.vue';
|
||||
import MkRange from '@/components/ui/range.vue';
|
||||
import { faMusic, faPlay, faVolumeUp, faVolumeMute, faChevronDown, faRedo } from '@fortawesome/free-solid-svg-icons';
|
||||
import FormRange from '@/components/form/range.vue';
|
||||
import FormSelect from '@/components/form/select.vue';
|
||||
import FormBase from '@/components/form/base.vue';
|
||||
import FormButton from '@/components/form/button.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
import * as os from '@/os';
|
||||
import { device, defaultDeviceSettings } from '@/cold-storage';
|
||||
import { playFile } from '@/scripts/sound';
|
||||
|
||||
const sounds = [
|
||||
const soundsTypes = [
|
||||
null,
|
||||
'syuilo/up',
|
||||
'syuilo/down',
|
||||
@@ -73,6 +46,8 @@ const sounds = [
|
||||
'syuilo/square-pico',
|
||||
'syuilo/reverved',
|
||||
'syuilo/ryukyu',
|
||||
'syuilo/kick',
|
||||
'syuilo/snare',
|
||||
'aisha/1',
|
||||
'aisha/2',
|
||||
'aisha/3',
|
||||
@@ -82,71 +57,98 @@ const sounds = [
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkSelect,
|
||||
MkRange,
|
||||
FormSelect,
|
||||
FormButton,
|
||||
FormBase,
|
||||
FormRange,
|
||||
FormGroup,
|
||||
},
|
||||
|
||||
emits: ['info'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
sounds,
|
||||
faMusic, faPlay, faVolumeUp, faVolumeMute,
|
||||
INFO: {
|
||||
title: this.$t('sounds'),
|
||||
icon: faMusic
|
||||
},
|
||||
sounds: {},
|
||||
faMusic, faPlay, faVolumeUp, faVolumeMute, faChevronDown, faRedo,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
sfxVolume: {
|
||||
get() { return this.$store.state.device.sfxVolume; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxVolume', value: parseFloat(value, 10) }); }
|
||||
masterVolume: { // TODO: (外部)関数にcomputedを使うのはアレなので直す
|
||||
get() { return device.get('sound_masterVolume'); },
|
||||
set(value) { device.set('sound_masterVolume', value); }
|
||||
},
|
||||
|
||||
sfxNote: {
|
||||
get() { return this.$store.state.device.sfxNote; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxNote', value }); }
|
||||
},
|
||||
|
||||
sfxNoteMy: {
|
||||
get() { return this.$store.state.device.sfxNoteMy; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxNoteMy', value }); }
|
||||
},
|
||||
|
||||
sfxNotification: {
|
||||
get() { return this.$store.state.device.sfxNotification; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxNotification', value }); }
|
||||
},
|
||||
|
||||
sfxChat: {
|
||||
get() { return this.$store.state.device.sfxChat; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxChat', value }); }
|
||||
},
|
||||
|
||||
sfxChatBg: {
|
||||
get() { return this.$store.state.device.sfxChatBg; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxChatBg', value }); }
|
||||
},
|
||||
|
||||
sfxAntenna: {
|
||||
get() { return this.$store.state.device.sfxAntenna; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxAntenna', value }); }
|
||||
},
|
||||
|
||||
sfxChannel: {
|
||||
get() { return this.$store.state.device.sfxChannel; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'sfxChannel', value }); }
|
||||
},
|
||||
|
||||
volumeIcon: {
|
||||
get() {
|
||||
return this.sfxVolume === 0 ? faVolumeMute : faVolumeUp;
|
||||
}
|
||||
volumeIcon() {
|
||||
return this.masterVolume === 0 ? faVolumeMute : faVolumeUp;
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.sounds.note = device.get('sound_note');
|
||||
this.sounds.noteMy = device.get('sound_noteMy');
|
||||
this.sounds.notification = device.get('sound_notification');
|
||||
this.sounds.chat = device.get('sound_chat');
|
||||
this.sounds.chatBg = device.get('sound_chatBg');
|
||||
this.sounds.antenna = device.get('sound_antenna');
|
||||
this.sounds.channel = device.get('sound_channel');
|
||||
this.sounds.reversiPutBlack = device.get('sound_reversiPutBlack');
|
||||
this.sounds.reversiPutWhite = device.get('sound_reversiPutWhite');
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$emit('info', this.INFO);
|
||||
},
|
||||
|
||||
methods: {
|
||||
listen(sound) {
|
||||
const audio = new Audio(`/assets/sounds/${sound}.mp3`);
|
||||
audio.volume = this.$store.state.device.sfxVolume;
|
||||
audio.play();
|
||||
async edit(type) {
|
||||
const { canceled, result } = await os.form(this.$t('_sfx.' + type), {
|
||||
type: {
|
||||
type: 'enum',
|
||||
enum: soundsTypes.map(x => ({
|
||||
value: x,
|
||||
label: x == null ? this.$t('none') : x,
|
||||
})),
|
||||
label: this.$t('sound'),
|
||||
default: this.sounds[type].type,
|
||||
},
|
||||
volume: {
|
||||
type: 'range',
|
||||
mim: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
label: this.$t('volume'),
|
||||
default: this.sounds[type].volume
|
||||
},
|
||||
listen: {
|
||||
type: 'button',
|
||||
content: this.$t('listen'),
|
||||
action: (_, values) => {
|
||||
playFile(values.type, values.volume);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
const v = {
|
||||
type: result.type,
|
||||
volume: result.volume,
|
||||
};
|
||||
|
||||
device.set('sound_' + type, v);
|
||||
this.sounds[type] = v;
|
||||
},
|
||||
|
||||
reset() {
|
||||
for (const sound of Object.keys(this.sounds)) {
|
||||
const v = defaultDeviceSettings['sound_' + sound];
|
||||
device.set('sound_' + sound, v);
|
||||
this.sounds[sound] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user