Compare commits

...

58 Commits

Author SHA1 Message Date
syuilo
bcbe83cb38 12.57.0 2020-11-15 13:50:02 +09:00
syuilo
37f983aee3 New Crowdin updates (#6838)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Kabyle)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (French)
2020-11-15 13:49:38 +09:00
syuilo
77de3f2b9d Pages埋め込みノートで詳細表示にするかどうか選べるように 2020-11-15 13:47:15 +09:00
syuilo
f655b54937 Improve Pages
Resolve #6654
2020-11-15 13:42:04 +09:00
syuilo
cac99ebdd4 wip: clip 2020-11-15 12:47:54 +09:00
takenoko
f0d0a1546a Use node v14.15.0 (#6837)
* Use node v14.15.0

* Update Dockerfile
2020-11-15 12:35:28 +09:00
syuilo
8e8459fa55 wip: clip 2020-11-15 12:34:47 +09:00
syuilo
d53c55ecb5 wip: clip 2020-11-15 12:04:54 +09:00
syuilo
ea33d61a90 Add description 2020-11-15 10:35:36 +09:00
syuilo
2fcc3388dd Update about-misskey.vue 2020-11-15 10:21:56 +09:00
rinsuki
ef7f033c32 CI: Node 15.x でも回すように (#6836) 2020-11-14 15:13:59 +09:00
syuilo
7aa54dc92e Update Dockerfile 2020-11-14 15:12:43 +09:00
syuilo
d10ad1b413 12.56.0 2020-11-14 14:34:04 +09:00
syuilo
34063a0b84 New Crowdin updates (#6820)
* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)
2020-11-14 14:32:53 +09:00
syuilo
8c9d975d69 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-11-14 14:32:04 +09:00
syuilo
03b072b894 Resolve #6704 2020-11-14 14:32:01 +09:00
futchitwo
d1bed49808 typoらしきものを修正 (#6825)
mame→name
2020-11-14 12:52:38 +09:00
syuilo
6c3417d9b5 Improve MkRadios 2020-11-14 12:50:24 +09:00
syuilo
ba65226460 UI整理 2020-11-14 12:16:28 +09:00
syuilo
9c9cd168ee Improve emoji picker 2020-11-14 11:47:30 +09:00
syuilo
abb3d2a8d9 Fix #6832 2020-11-14 09:59:33 +09:00
syuilo
637fe8a04b Update dependencies 🚀 2020-11-14 09:54:39 +09:00
syuilo
be321e95e5 Bump node version 2020-11-14 09:45:16 +09:00
MeiMei
ed46c1486c Fix toHtml (#6824) 2020-11-10 21:00:14 +09:00
syuilo
c9fcfc6862 Better MFM rendering 2020-11-09 22:32:01 +09:00
syuilo
8495e37566 Fix router 2020-11-09 22:31:50 +09:00
syuilo
247bd43ae2 12.55.0 2020-11-08 17:10:43 +09:00
syuilo
a6685b1559 New Crowdin updates (#6814)
* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Kabyle)

* New translations ja-JP.yml (Kannada)

* New translations ja-JP.yml (Uyghur)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (French)
2020-11-08 17:09:42 +09:00
syuilo
66c4e8064b Add bounce MFM animation 2020-11-08 17:08:51 +09:00
syuilo
9d1fa3f202 autoWatch機能を削除 2020-11-08 16:49:23 +09:00
syuilo
a6985d7dc7 Fix #6819 2020-11-08 16:37:51 +09:00
syuilo
027c021ac9 アスタリスク3つでのtadaアニメーションを復活 2020-11-08 16:35:22 +09:00
syuilo
604205ec09 MFMチートシートに数式追加 2020-11-08 16:14:49 +09:00
syuilo
77db016866 MFMチートシート 2020-11-08 15:24:46 +09:00
syuilo
c6a009dbae 最近使用した絵文字からリアクションピッカーに設定してある絵文字は除外するように 2020-11-08 13:58:16 +09:00
syuilo
4299e3f90c スマホでデスクトップモードにできないように 2020-11-08 13:39:36 +09:00
syuilo
19f4812c03 Remove outdated test 2020-11-08 12:42:13 +09:00
syuilo
d01c465a8d ユーザーピッカーに最近使用したユーザーを表示するように 2020-11-08 12:40:56 +09:00
syuilo
4f1409601e Respect order when userIds specified 2020-11-08 12:40:31 +09:00
syuilo
52cffe0864 絵文字ピッカーで最近使用した絵文字がバグっているのを修正
あとMkEmojiをリファクタリング
2020-11-08 12:08:07 +09:00
syuilo
0866d5c055 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2020-11-08 11:25:31 +09:00
syuilo
78c08f6503 Clean up 2020-11-08 11:25:28 +09:00
MeiMei
27d0ac3d75 In HTML to MFM, use angle bracket if needed (#6817) 2020-11-08 00:38:50 +09:00
syuilo
a8776002f3 Improve readability 2020-11-07 23:43:03 +09:00
syuilo
31aa008566 Improve MFM
MFMの構文を調整 + 新しいアニメーション追加
Resolve #6816
2020-11-07 23:41:21 +09:00
syuilo
9d405b4581 絵文字ピッカーでエンターしたときに検索結果の先頭のもので確定できるように 2020-11-07 21:33:36 +09:00
syuilo
80c490a18b 絵文字ピッカーでAND検索に対応 2020-11-07 21:28:28 +09:00
syuilo
30c9c3739f 12.54.0 2020-11-07 12:49:16 +09:00
syuilo
ee0e7a09e0 Update dependencies 🚀 2020-11-07 12:48:21 +09:00
syuilo
bfd9577f0d 絵文字ピッカーを開いたときに画面がスクロールするのを修正 2020-11-07 12:35:12 +09:00
syuilo
0cada4ca76 New Crowdin updates (#6801)
* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)
2020-11-07 12:32:27 +09:00
RaZZlom
a718ccc0b6 Add missing languages (#6800) 2020-11-07 12:32:08 +09:00
Xeltica
1fcfd8e645 ヘッダーに表示されるタブのインジケーターを実装 (#6803)
* Implemente indicators on headers

* 微調整
2020-11-07 12:31:23 +09:00
syuilo
c6dd932a0b Improve usability 2020-11-07 12:30:16 +09:00
syuilo
b79eed01e0 絵文字ピッカーの検索に絵文字をペーストしたとき確定されないのを修正 2020-11-07 10:56:38 +09:00
syuilo
3a7dbe9764 UI切り替えがサイドバーに設置されていない場合に機能しない問題を修正 2020-11-07 10:54:52 +09:00
syuilo
bef2534fa8 絵文字ピッカーを強化 + 絵文字ピッカーをリアクションピッカーとして使えるように
Resolve #5079
Resolve #3219
2020-11-07 10:43:27 +09:00
syuilo
888dcd2559 画像ダイアログでスクロールが発生しないように 2020-11-07 05:00:42 +09:00
91 changed files with 3576 additions and 1806 deletions

View File

@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
node-version: [12.x, 14.x]
node-version: [12.x, 14.x, 15.x]
services:
postgres:

View File

@@ -1 +1 @@
v14.4.0
v14.15.0

View File

@@ -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

View File

@@ -174,6 +174,7 @@ imageUrl: "عنوان URL للصورة"
remove: "حذف"
removed: "تم حذفه بنجاح"
removeAreYouSure: "متأكد من أنك تريد حذف {x}؟"
deleteAreYouSure: "متأكد من أنك تريد حذف {x}؟"
saved: "تم حفظه"
messaging: "الدردشة"
upload: "تحميل"
@@ -285,7 +286,6 @@ unregister: "إلغاء التسجيل"
passwordLessLogin: "لِج مِن دون كلمة سرية"
resetPassword: "أعد تعيين كلمتك السرية"
newPasswordIs: "كلمتك السرية الجديدة هي {password}"
autoNoteWatch: "راقب الملاحظات تلقائيا"
share: "شارِك"
notFound: "غير موجود"
help: "المساعدة"
@@ -380,6 +380,11 @@ smtpHost: "المضيف"
smtpUser: "اسم المستخدم"
smtpPass: "الكلمة السرية"
display: "المظهر"
_mfm:
mention: "أشر الى"
quote: "اقتبس"
emoji: "إيموجي مخصص"
search: "البحث"
_reversi:
total: "المجموع"
_channel:

View File

@@ -214,6 +214,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"
@@ -373,8 +375,6 @@ unregister: "Deaktivieren"
passwordLessLogin: "Passwortloses Anmelden einrichten"
resetPassword: "Passwort zurücksetzen"
newPasswordIs: "Das neue Passwort ist \"{password}\""
autoNoteWatch: "Notizen automatisch beobachten"
autoNoteWatchDescription: "Werde über Notizen, auf die du reagiert oder geantwortet hast, informiert"
reduceUiAnimation: "Animationen der Benutzeroberfläche reduzieren"
share: "Teilen"
notFound: "Nicht gefunden"
@@ -542,6 +542,7 @@ pluginInstallWarn: "Installiere nur vertrauenswürdige Plugins."
deck: "Deck"
undeck: "Deck verlassen"
useBlurEffectForModal: "Weichzeichnungseffekt für Modals verwenden"
useFullReactionPicker: "Vollständige Reaktionsauswahl nutzen"
generateAccessToken: "Zugriffstoken generieren"
permission: "Berechtigungen"
enableAll: "Alle aktivieren"
@@ -604,6 +605,54 @@ random: "Zufällig"
system: "System"
switchUi: "UI wechseln"
desktop: "Desktop"
_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"
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"
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"
@@ -1032,6 +1081,7 @@ _pages:
my: "Meine Seiten"
liked: "Seiten, die mir gefallen"
inspector: "Inspektor"
contents: "Inhalt"
content: "Inhalt"
variables: "Variablen"
title: "Titel"

View File

@@ -214,6 +214,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"
@@ -373,8 +375,6 @@ unregister: "Unregister"
passwordLessLogin: "Set up password-less login"
resetPassword: "Reset password"
newPasswordIs: "The new password is \"{password}\""
autoNoteWatch: "Watch note automatically"
autoNoteWatchDescription: "Get notified about the notes which you reactioned or replied."
reduceUiAnimation: "Reduce UI animation"
share: "Share"
notFound: "Not found"
@@ -542,6 +542,7 @@ pluginInstallWarn: "Please do not install untrustworthy plugins."
deck: "Deck"
undeck: "Leave Deck"
useBlurEffectForModal: "Use blur effect for modals"
useFullReactionPicker: "Use full-size reaction picker"
generateAccessToken: "Generate access token"
permission: "Permissions"
enableAll: "Enable all"
@@ -604,6 +605,54 @@ random: "Random"
system: "System"
switchUi: "Switch UI"
desktop: "Desktop"
_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"
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"
@@ -1032,6 +1081,7 @@ _pages:
my: "My pages"
liked: "Liked pages"
inspector: "Inspector"
contents: "Content"
content: "Page block"
variables: "Variables"
title: "Title"

View File

@@ -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"
@@ -373,8 +374,6 @@ unregister: "Cancelar registro"
passwordLessLogin: "Iniciar sesión sin contraseña"
resetPassword: "Resetear contraseña"
newPasswordIs: "La nueva contraseña es \"{password}\""
autoNoteWatch: "Ver nota automáticamente"
autoNoteWatchDescription: "Recibe notificaciones sobre las notas de otros usuarios que a los que respondiste y reaccionaste"
reduceUiAnimation: "Reducir la animación de la UI"
share: "Compartir"
notFound: "No se encuentra"
@@ -542,6 +541,7 @@ 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"
generateAccessToken: "Generar token de acceso"
permission: "Permisos"
enableAll: "Activar todo"
@@ -598,9 +598,66 @@ openInNewTab: "Abrir en una Nueva Pestaña"
openInSideView: "Abrir en una vista al costado"
defaultNavigationBehaviour: "Navegación por defecto"
editTheseSettingsMayBreakAccount: "Editar estas configuraciones puede dañar su cuenta."
instanceTicker: "Información de notas de la instancia"
waitingFor: "Esperando a {x}"
random: "Aleatorio"
system: "Sistema"
switchUi: "Cambiar interfaz de usuario"
desktop: "Escritorio"
_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"
chooseBoard: "Elegir tablero"
blackOrWhite: "Blancas/Negras"
blackIs: "{name} juega con fichas negras"
rules: "Reglas"
botSettings: "Opciones del bot"
thisGameIsStartedSoon: "El juego empezará en segundos"
waitingForOther: "Esperando el turno del adversario"
waitingForMe: "Esperando mi turno"
waitingBoth: "Prepárate"
ready: "Listo"
cancelReady: "No estoy listo"
opponentTurn: "Turno del adversario"
myTurn: "Mi turno"
turnOf: "Turno de {name}"
pastTurnOf: "Turno de {name}"
surrender: "Rendirse"
surrendered: "Por rendirse"
drawn: "Empate"
won: "{name} ha ganado"
black: "Negro"
white: "Blanco"
total: "Total"
turnCount: "Turno {count}"
myGames: "Mis juegos"
allGames: "Todos los juegos"
ended: "Finalizado"
playing: "Jugando"
isLlotheo: "El que tenga menos fichas gana (LLoTheO)"
loopedMap: "Mapa en bucle"
canPutEverywhere: "Puedes colocar donde quieras"
_instanceTicker:
none: "No mostrar"
remote: "Mostrar a usuarios remotos"
always: "Mostrar siempre"
_serverDisconnectedBehavior:
reload: "Recargar automáticamente"
dialog: "Mostrar diálogo de advertencia"
@@ -992,6 +1049,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"

View File

@@ -213,6 +213,7 @@ imageUrl: "URL de limage"
remove: "Supprimer"
removed: "Supprimé"
removeAreYouSure: "Supprimer «{x}» ?"
deleteAreYouSure: "Supprimer «{x}» ?"
saved: "Enregistré"
messaging: "Discuter"
upload: "Téléverser"
@@ -372,8 +373,6 @@ unregister: "Se désinscrire"
passwordLessLogin: "Connectez-vous sans mot de passe"
resetPassword: "Réinitialiser mot de passe"
newPasswordIs: "Votre nouveau mot de passe est \"{password}\""
autoNoteWatch: "Surveiller les notes automatiquement"
autoNoteWatchDescription: "Soyez informé des notes auxquelles vous avez réagi ou répondu."
reduceUiAnimation: "Réduire les animations dans linterface"
share: "Partager"
notFound: "Non trouvé"
@@ -582,6 +581,14 @@ setMultipleBySeparatingWithSpace: "Vous pouvez définir plus dun, 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"
_mfm:
mention: "Mentionner"
hashtag: "Hashtags"
link: "Lien"
center: "Centrée"
quote: "Citer"
emoji: "Émojis personnalisés"
search: "Rechercher"
_reversi:
total: "Total"
_serverDisconnectedBehavior:
@@ -933,6 +940,7 @@ _pages:
my: "Mes pages"
liked: "Pages favorites"
inspector: "Inspecteur"
contents: "Contenu"
content: "Bloc de page"
variables: "Variables"
title: "Titre"

View File

@@ -15,17 +15,24 @@ const merge = (...args) => args.reduce((a, c) => ({
const languages = [
'ar-SA',
//'cs-CZ',
//'da-DK',
'cs-CZ',
'da-DK',
'de-DE',
'en-US',
'es-ES',
'fr-FR',
'ja-JP',
'ja-KS',
'kab-KAB',
'kn-IN',
'ko-KR',
//'nl-NL',
//'pl-PL',
'nl-NL',
'no-NO',
'pl-PL',
'pt-PT',
'ru-RU',
'ug-CN',
'uk-UA',
'zh-CN',
'zh-TW',
];

View File

@@ -95,6 +95,7 @@ sensitive: "閲覧注意"
add: "追加"
reaction: "リアクション"
reactionSettingDescription: "リアクションピッカーに表示するリアクションを設定します。"
reactionSettingDescription2: "ドラッグして並び替えます。クリックして削除します。"
rememberNoteVisibility: "公開範囲を記憶する"
attachCancel: "添付取り消し"
markAsSensitive: "閲覧注意にする"
@@ -214,6 +215,8 @@ imageUrl: "画像URL"
remove: "削除"
removed: "削除しました"
removeAreYouSure: "「{x}」を削除しますか?"
deleteAreYouSure: "「{x}」を削除しますか?"
resetAreYouSure: "リセットしますか?"
saved: "保存しました"
messaging: "チャット"
upload: "アップロード"
@@ -373,8 +376,6 @@ unregister: "登録を解除"
passwordLessLogin: "パスワード無しログイン"
resetPassword: "パスワードをリセット"
newPasswordIs: "新しいパスワードは「{password}」です"
autoNoteWatch: "ノートの自動ウォッチ"
autoNoteWatchDescription: "あなたがリアクションしたり返信したりした他のユーザーのノートに関する通知を受け取るようにします。"
reduceUiAnimation: "UIのアニメーションを減らす"
share: "共有"
notFound: "見つかりません"
@@ -542,6 +543,7 @@ pluginInstallWarn: "信頼できないプラグインはインストールしな
deck: "デッキ"
undeck: "デッキ解除"
useBlurEffectForModal: "モーダルにぼかし効果を使用"
useFullReactionPicker: "フル機能リアクションピッカーを使用"
generateAccessToken: "アクセストークンの発行"
permission: "権限"
enableAll: "全て有効にする"
@@ -604,6 +606,58 @@ random: "ランダム"
system: "システム"
switchUi: "UI切り替え"
desktop: "デスクトップ"
clip: "クリップ"
createNew: "新規作成"
optional: "任意"
_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: "引用"
quoteDescription: "内容が引用であることを示すことができます。"
emoji: "カスタム絵文字"
emojiDescription: "コロンでカスタム絵文字名を囲むと、カスタム絵文字を表示させることができます。"
search: "検索"
searchDescription: "入力済み検索ボックスを表示させることができます。"
flip: "反転"
flipDescription: "内容を上下または左右に反転させます。"
jelly: "アニメーション(びよんびよん)"
jellyDescription: "びよんびよんするアニメーションを与えます。"
tada: "アニメーション(じゃーん)"
tadaDescription: "ジャーン!という感じのアニメーションを与えます。"
jump: "アニメーション(ジャンプ)"
jumpDescription: "飛び跳ねるようなアニメーションを与えます。"
bounce: "アニメーション(バウンド)"
bounceDescription: "ぽよんぽよん弾むようなアニメーションを与えます。"
shake: "アニメーション(ぶるぶる)"
shakeDescription: "ぶるぶるするアニメーションを与えます。"
twitch: "アニメーション(ブレ)"
twitchDescription: "激しくブレるアニメーションを与えます。"
spin: "アニメーション(回転)"
spinDescription: "回転するアニメーションを与えます。"
_reversi:
reversi: "リバーシ"
@@ -1050,6 +1104,7 @@ _pages:
created: "ページを作成しました"
updated: "ページを更新しました"
deleted: "ページを削除しました"
pageSetting: "ページ設定"
nameAlreadyExists: "指定されたページURLは既に存在しています"
invalidNameTitle: "不正なページURLです"
invalidNameText: "空白でないか確認してください"
@@ -1061,6 +1116,7 @@ _pages:
my: "自分のページ"
liked: "いいねしたページ"
inspector: "インスペクター"
contents: "コンテンツ"
content: "ページブロック"
variables: "変数"
title: "タイトル"
@@ -1121,6 +1177,12 @@ _pages:
width: "幅"
height: "高さ"
note: "ノート埋め込み"
_note:
id: "ートID"
idDescription: "ートURLをペーストして設定することもできます。"
detailed: "詳細な表示"
switch: "スイッチ"
_switch:
name: "変数名"

View File

@@ -214,6 +214,7 @@ imageUrl: "画像URL"
remove: "ほかす"
removed: "削除したで!"
removeAreYouSure: "「{x}」はなおしてしもてええか?"
deleteAreYouSure: "「{x}」はなおしてしもてええか?"
saved: "保存したで!"
messaging: "チャット"
upload: "アップロード"
@@ -373,8 +374,6 @@ unregister: "登録やめる"
passwordLessLogin: "パスワード無くてもログインできるようにする"
resetPassword: "パスワードをリセット"
newPasswordIs: "今度のパスワードは「{password}」や"
autoNoteWatch: "ノートを勝手に見張っとく"
autoNoteWatchDescription: "あんたがリアクションや返信した他のユーザーのノートの通知をあんたも受け取れるようになるんやで。通知欄の流れがめっちゃ早くなるで。"
reduceUiAnimation: "UIの動きやアニメーションを減らしてくれや。"
share: "わけわけ"
notFound: "見つからへんね"
@@ -417,6 +416,11 @@ checking: "確認しとるで"
smtpHost: "ホスト"
smtpUser: "ユーザー名"
smtpPass: "パスワード"
_mfm:
mention: "メンション"
quote: "引用"
emoji: "カスタム絵文字"
search: "探す"
_sidebar:
icon: "アイコン"
_theme:

View File

@@ -35,6 +35,9 @@ userList: "Tibdarin"
uiLanguage: "Tutlayt n wegrudem"
smtpUser: "Isem n umseqdac"
smtpPass: "Awal uffir"
_mfm:
mention: "Bder"
search: "Nadi"
_theme:
keys:
mention: "Bder"
@@ -54,6 +57,7 @@ _exportOrImport:
blockingList: "Seḥbes"
userLists: "Tibdarin"
_pages:
contents: "Agbur"
font: "Tasefsit"
fontSerif: "Serif"
fontSansSerif: "Sans Serif"

View File

@@ -56,6 +56,8 @@ instances: "ನಿದರ್ಶನ"
remove: "ಅಳಿಸು"
smtpUser: "ಬಳಕೆಹೆಸರು"
smtpPass: "ಗುಪ್ತಪದ"
_mfm:
search: "ಹುಡುಕು"
_sfx:
notification: "ಅಧಿಸೂಚನೆಗಳು"
_widgets:

View File

@@ -204,6 +204,7 @@ imageUrl: "이미지 URL"
remove: "삭제"
removed: "삭제하였습니다"
removeAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?"
deleteAreYouSure: "\"{x}\" 을(를) 삭제하시겠습니까?"
saved: "저장하였습니다"
messaging: "대화"
upload: "업로드"
@@ -362,8 +363,6 @@ unregister: "등록 해제"
passwordLessLogin: "비밀번호 없이 로그인"
resetPassword: "비밀번호 재설정"
newPasswordIs: "새로운 비밀번호는 \"{password}\" 입니다"
autoNoteWatch: "노트를 자동으로 지켜보기"
autoNoteWatchDescription: "리액션하거나 답글을 남긴 다른 유저의 노트에 대한 알림을 받습니다."
reduceUiAnimation: "UI의 애니메이션을 줄이기"
share: "공유"
notFound: "찾을 수 없습니다"
@@ -550,6 +549,14 @@ logs: "로그"
database: "데이터베이스"
channel: "채널"
random: "랜덤"
_mfm:
mention: "멘션"
hashtag: "해시태그"
link: "링크"
center: "가운데 정렬"
quote: "인용"
emoji: "커스텀 이모지"
search: "검색"
_reversi:
total: "합계"
_channel:
@@ -882,6 +889,7 @@ _pages:
my: "내 페이지"
liked: "좋아요한 페이지"
inspector: "인스펙터"
contents: "콘텐츠"
content: "페이지 블록"
variables: "변수"
title: "제목"

View File

@@ -10,6 +10,8 @@ cancel: "Anuluj"
enterUsername: "Wprowadź nazwę użytkownika"
smtpUser: "Nazwa użytkownika"
smtpPass: "Hasło"
_mfm:
search: "Szukaj"
_sfx:
notification: "Powiadomienia"
_widgets:

View File

@@ -6,12 +6,12 @@ search: "Поиск"
notifications: "Уведомления"
username: "Имя пользователя"
password: "Пароль"
fetchingAsApObject: "Приём с других сайтов"
ok: "Согласен"
gotIt: "Понятно!"
fetchingAsApObject: "Приём с других сайтов"
ok: "Окей"
gotIt: "Ясно!"
cancel: "Отмена"
enterUsername: "Введите имя пользователя"
renotedBy: "{user} передаёт…"
renotedBy: "{user} делится"
noNotes: "Нет ни одной заметки"
noNotifications: "Нет ни одного уведомления"
instance: "Инстанс"
@@ -30,16 +30,16 @@ uploading: "Загрузка..."
save: "Сохранить"
users: "Пользователи"
addUser: "Добавить пользователя"
favorite: "Избранное"
favorite: "В избранное"
favorites: "Избранное"
unfavorite: "Убрать из избранных"
unfavorite: "Убрать из избранного"
pin: "Закрепить в профиле"
unpin: "Открепить от профиля"
copyContent: "Скопировать содержимое"
copyLink: "Скопировать ссылку"
delete: "Удалить"
deleteAndEdit: "Удалить и отредактировать"
deleteAndEditConfirm: "Удалить этот пост и создать отредактированный? Все реакции, ссылки и ответы на существующий будут будут потеряны."
deleteAndEditConfirm: "Удалить эту заметку и создать отредактированную? Все реакции, ссылки и ответы на существующую будут будут потеряны."
addToList: "Добавить в список"
sendMessage: "Отправить сообщение"
copyUsername: "Скопировать имя пользователя"
@@ -51,19 +51,19 @@ receiveFollowRequest: "Получен запрос на подписку"
followRequestAccepted: "Запрос на подписку принят"
mention: "Упоминание"
mentions: "Упоминания"
directNotes: "Прямые сообщения"
importAndExport: "Импорт / Экспорт"
directNotes: "Личные сообщения"
importAndExport: "Импорт и экспорт"
import: "Импорт"
export: "Экспорт"
files: "Файлы"
download: "Скачать"
driveFileDeleteConfirm: "Удалить файл {name} ? Посты с ним также будут удалены"
unfollowConfirm: "Удалить из подписок {name}?"
driveFileDeleteConfirm: "Удалить файл «{name}»? Заметки с ним также будут удалены."
unfollowConfirm: "Удалить из подписок пользователя {name}?"
exportRequested: "Вы запросили экспорт. Это может занять некоторое время. Результат будет добавлен на «Диск»."
importRequested: "Вы запросили импорт. Это может занять некоторое время."
lists: "Списки"
noLists: "Нет ни одного списка"
note: "Пост"
note: "Заметка"
notes: "Заметки"
following: "Подписки"
followers: "Подписчики"
@@ -75,10 +75,10 @@ somethingHappened: "Что-то пошло не так"
retry: "Повторить попытку"
pageLoadError: "Не удалось загрузить страницу"
pageLoadErrorDescription: "Обычно это случается из-за сбоев в сети или кэша браузера. Попробуйте очистить кэш, или подождать пару минут, а потом попытаться загрузить страницу снова."
enterListName: "Введите имя списка"
enterListName: "Название списка"
privacy: "Конфиденциальность"
makeFollowManuallyApprove: "Принимать подписчиков вручную"
defaultNoteVisibility: "Видимость постов по умолчанию"
defaultNoteVisibility: "Видимость заметок по умолчанию"
follow: "Подписка"
followRequest: "Запрос на подписку"
followRequests: "Запросы на подписку"
@@ -94,8 +94,8 @@ clickToShow: "Нажмите для просмотра"
sensitive: "Содержимое не для всех"
add: "Добавить"
reaction: "Реакции"
reactionSettingDescription: "Выберите, что показывать в палитре реакций"
rememberNoteVisibility: "Запоминать видимость поста"
reactionSettingDescription: "Подберите, что будет у вас в палитре реакций"
rememberNoteVisibility: "Запоминать видимость заметок"
attachCancel: "Удалить вложение"
markAsSensitive: "Отметить как «не для всех»"
unmarkAsSensitive: "Снять отметку «не для всех»"
@@ -113,7 +113,7 @@ unsuspendConfirm: "Разморозить этот аккаунт?"
selectList: "Выберите список"
selectAntenna: "Выберите антенну"
selectWidget: "Выберите виджет"
editWidgets: "Редактировать виджет"
editWidgets: "Редактировать виджеты"
editWidgetsExit: "Готово"
customEmojis: "Эмодзи пользователя"
emoji: "Эмодзи"
@@ -128,13 +128,13 @@ flagAsCat: "Аккаунт кота"
autoAcceptFollowed: "Принимать подписчиков автоматически"
addAcount: "Добавить аккаунт"
loginFailed: "Неудачная попытка входа"
showOnRemote: "Перейти к оригиналу на его сайт"
showOnRemote: "Перейти к оригиналу на сайт"
general: "Общее"
wallpaper: "Обои"
setWallpaper: "Установить обои"
removeWallpaper: "Удалить обои"
searchWith: "Искать в {q}"
youHaveNoLists: "У вас нет списков"
searchWith: "Найденное «{q}»"
youHaveNoLists: "У вас нет ни одного списка"
followConfirm: "Подписаться на {name}?"
proxyAccount: "Учётная запись прокси"
proxyAccountDescription: "Учетная запись прокси предназначена служить подписчиком на пользователей с других сайтов. Например, если пользователь добавит кого-то с другого сайта а список, деятельность того не отобразится, пока никто с этого же сайта не подписан на него. Чтобы это стало возможным, на него подписывается прокси."
@@ -150,15 +150,15 @@ latestRequestReceivedAt: "Последний полученный запрос"
latestStatus: "Последний статус"
storageUsage: "Использовано"
charts: "Диаграммы"
perHour: "Каждый час"
perDay: "Каждый день"
perHour: "По часам"
perDay: "По дням"
stopActivityDelivery: "Остановить отправку обновлений активности"
blockThisInstance: "Блокировать этот инстанс"
operations: "Операции"
software: "Программы"
version: "Версия"
metadata: "Метаданные"
withNFiles: "файлов: {n}"
withNFiles: "Файлы, {n} шт."
monitor: "Монитор"
jobQueue: "Очередь заданий"
cpuAndMemory: "Процессор и память"
@@ -189,7 +189,7 @@ noCustomEmojis: "Эмодзи пользователя отсутствуют"
noJobs: "Нет заданий"
federating: "Федерируется"
blocked: "Заблокировано"
suspended: "Приостановленный"
suspended: "Заморожено"
all: "Всё"
subscribing: "Подписка"
publishing: "Публикация"
@@ -202,7 +202,7 @@ security: "Безопасность"
retypedNotMatch: "Не совпадают"
currentPassword: "Текущий пароль"
newPassword: "Новый пароль"
newPasswordRetype: "Новый пароль (повторно)"
newPasswordRetype: "Новый пароль (ещё раз)"
attachFile: "Прикрепить файлы"
more: "Ещё!"
featured: "Подборка"
@@ -214,6 +214,8 @@ imageUrl: "Ссылка на изображение"
remove: "Удалить"
removed: "Удалено"
removeAreYouSure: "Хотите удалить «{x}»?"
deleteAreYouSure: "Хотите удалить «{x}»?"
resetAreYouSure: "На самом деле сбросить?"
saved: "Сохранено"
messaging: "Сообщения"
upload: "Загрузить"
@@ -225,7 +227,7 @@ uploadFromUrlRequested: "Загрузка выбранного"
uploadFromUrlMayTakeTime: "Загрузка может занять некоторое время."
explore: "Обзор"
games: "Игры Misskey"
messageRead: "Прочитанных"
messageRead: "Прочитали"
noMoreHistory: "История закончилась"
startMessaging: "Отправить сообщение"
nUsersRead: "Прочитали {n}"
@@ -237,7 +239,7 @@ remoteUserCaution: "Это пользователь с другого сайта
activity: "Активность"
images: "Изображения"
birthday: "День рождения"
yearsOld: "{age} лет"
yearsOld: "Возраст: {age}"
registeredDate: "Дата регистрации"
location: "Местоположение"
theme: "Тема"
@@ -250,10 +252,10 @@ darkThemes: "Тёмные темы"
syncDeviceDarkMode: "Синхронизировать с темным режимом устройства"
drive: "Диск"
fileName: "Имя файла"
selectFile: "Выберите Файл"
selectFiles: "Выберите Файл"
selectFile: "Выберите файл"
selectFiles: "Выберите файлы"
selectFolder: "Выберите папку"
selectFolders: "Выберите папку"
selectFolders: "Выберите папки"
renameFile: "Переименовать файл"
folderName: "Имя папки"
createFolder: "Создать папку"
@@ -265,51 +267,51 @@ emptyFolder: "Папка пуста"
unableToDelete: "Удаление невозможно"
inputNewFileName: "Введите имя нового файла"
inputNewFolderName: "Пожалуйста, введите новое имя папки!"
circularReferenceFolder: "Конечная папка - это вложенная папка, которую вы хотите переместить."
circularReferenceFolder: "Вы пытаетесь переместить папку внутрь себя."
hasChildFilesOrFolders: "Эта папка не пуста и не может быть удалена."
copyUrl: "Копировать URL"
copyUrl: "Копировать ссылку"
rename: "Переименовать"
avatar: "Иконка"
banner: "Баннер"
avatar: "Аватар"
banner: "Шапка"
nsfw: "Содержимое не для всех"
whenServerDisconnected: "Когда соединение с сервером потеряно"
disconnectedFromServer: "Разорвано соединение с сервером"
reload: "Перезагрузить"
doNothing: "Ничего не делать"
reloadConfirm: одтвердить перезагрузку?"
reloadConfirm: "Перезагрузить ленту?"
watch: "Следить"
unwatch: "Отписаться"
accept: "Принять"
reject: "Отклонить"
normal: "Стабильно"
instanceName: "Имя экземпляра"
instanceName: "Название инстанса"
instanceDescription: "Описание инстанса"
maintainerName: "Имя администратора"
maintainerEmail: "email администратора"
tosUrl: "Пользовательское соглашение URL"
thisYear: "Текущий год"
thisMonth: "Текущий месяц"
today: "Сегодня"
dayX: "{day}дней"
monthX: "{month}месяц"
yearX: "{year}год"
pages: "Страница"
integration: "подключение"
maintainerEmail: "Электронная почта администратора"
tosUrl: "Ссылка на пользовательское соглашение"
thisYear: "Этот год"
thisMonth: "Этот месяц"
today: "Этот день"
dayX: "{day} день"
monthX: "{month} месяц"
yearX: "{year} год"
pages: "Страницы"
integration: "Интеграция"
connectSerice: "Соединение"
disconnectSerice: "Отключение"
enableLocalTimeline: "Включить локальную ленту"
enableGlobalTimeline: "Включить глобальную ленту"
disablingTimelinesInfo: "Администраторы и Модераторы всегда будут иметь доступ ко всем временным параметрам, даже если они не включены."
disablingTimelinesInfo: "У администраторов и модераторов есть доступ ко всем лентам, даже если они отключены."
registration: "Регистрация"
enableRegistration: "Разрешить регистрацию"
invite: "Пригласить"
proxyRemoteFiles: "Удаленные файлы прокси"
proxyRemoteFilesDescription: "Если эта функция включена, удаленные файлы, которые (1) не хранятся локально или (2) были удалены с превышением лимита хранения, будут проксированы локально (с эскизами). Это не влияет на хранение на сервере."
driveCapacityPerLocalAccount: "Емкость диска для локального пользователя"
driveCapacityPerRemoteAccount: "Емкость диска для удаленного пользователя"
proxyRemoteFiles: "Файлы с других сайтов пускать через прокси"
proxyRemoteFilesDescription: "Когда эта настройка включена, файлы с других серверов, которые не сохранены или удалены для освобождения места, будут проксироваться локально, а так же для них будут создаваться миниатюры. Эта настройка не затрагивает хранение на сервере."
driveCapacityPerLocalAccount: "Объём диска на одного локального пользователя"
driveCapacityPerRemoteAccount: "Объём диска на одного пользователя с другого сайта"
inMb: "В мегабайтах"
iconUrl: "URL-адрес иконки"
bannerUrl: "URL-адрес изображения баннера"
iconUrl: "Ссылка на аватар"
bannerUrl: "Ссылка на изображение в шапке"
basicInfo: "Общая информация"
pinnedUsers: "Прикреплённый пользователь"
pinnedUsersDescription: "Перечислите по одному имени пользователя в строке. Пользователи, перечисленные здесь, будут привязаны к закладке \"Изучение\"."
@@ -321,24 +323,24 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "Включить reCAPTCHA"
recaptchaSiteKey: "Ключ сайта"
recaptchaSecretKey: "Секретный ключ"
avoidMultiCaptchaConfirm: "Использование нескольких Captchas может вызвать помехи. Хотите отключить другую Captcha? Вы можете оставить несколько Captchas включенными, нажав \"Отмена\"."
avoidMultiCaptchaConfirm: "Несколько способов проверки могут мешать друг другу. Подтвердите, если хотите отключить другие способы. Или нажмите «Отмена», чтобы оставить их включёнными."
antennas: "Антенны"
manageAntennas: "Настройки антенн"
name: "Имя"
name: "Название"
antennaSource: "Источник антенны"
antennaKeywords: "Ключевые слова"
antennaExcludeKeywords: "Исключения"
antennaKeywordsDescription: "Разделяйте пробелами для условия \"И\". Разделяйте переводом строки для \"ИЛИ\"."
notifyAntenna: "Уведомлять о новых записях"
withFileAntenna: "Включать только заметки с вложениями"
antennaKeywordsDescription: "Пишите слова через пробел в одной строке, чтобы ловить их появление вместе; на отдельных строках располагайте слова, или группы слов, чтобы ловить любые из них."
notifyAntenna: "Уведомлять о новых заметках"
withFileAntenna: "Только заметки с вложениями"
serviceworker: "ServiceWorker"
enableServiceworker: "Включить ServiceWorker"
antennaUsersDescription: "Располагать каждое имя с новой строки"
antennaUsersDescription: "Пишите каждое название аккаута на отдельной строке"
caseSensitive: "С учётом регистра"
withReplies: "Включить ответы"
withReplies: "Включая ответы"
connectedTo: "Вы подключены к следующим аккаунтам"
notesAndReplies: "Посты и ответы"
withFiles: "с файлами"
notesAndReplies: "Заметки и ответы"
withFiles: "Заметки с файлами"
silence: "Заглушить"
silenceConfirm: " Заглушить этого пользователя? Уверены?"
unsilence: "Снять глушение"
@@ -364,7 +366,7 @@ administrator: "Администратор"
token: "Токен"
twoStepAuthentication: "Двухфакторная аутентификация"
moderator: "Модератор"
nUsersMentioned: "{n}указанные пользователи"
nUsersMentioned: "Упомянуло пользователей: {n}"
securityKey: "Ключ безопасности"
securityKeyName: "Имя ключа"
registerSecurityKey: "Зарегистрировать защитный ключ"
@@ -372,18 +374,16 @@ lastUsed: "Последнее использование"
unregister: "Отписаться"
passwordLessLogin: "Настроить вход без пароля"
resetPassword: "Сброс пароля:"
newPasswordIs: "Новый пароль - \"{пароль}\"."
autoNoteWatch: "Автоматически просматривать записи"
autoNoteWatchDescription: "Получать уведомления о заметках других пользователей, на которые вы отреагировали или на которые вы ответили."
reduceUiAnimation: "Уменьшить анимацию в пользовательском интерфейсе."
newPasswordIs: "Новый пароль — «{password}»."
reduceUiAnimation: "Уменьшить анимацию в пользовательском интерфейсе"
share: "Поделиться"
notFound: "Не найдено"
notFoundDescription: "Страница, соответствующая указанному URL, не найдена."
notFoundDescription: "Страница по указанной ссылке не найдена"
uploadFolder: "Место загрузки по умолчанию"
cacheClear: "Очистка кэша"
markAsReadAllNotifications: "Отметить все уведомления как прочитанные"
markAsReadAllUnreadNotes: "Отметьте все сообщения как прочитанные."
markAsReadAllTalkMessages: "Отметьте все сообщения как прочитанные"
markAsReadAllUnreadNotes: "Отметить все заметки как прочитанные"
markAsReadAllTalkMessages: "Отметить все реплики как прочитанные"
help: "Помощь"
inputMessageHere: "Введите сообщение здесь"
close: "Закрыть"
@@ -392,71 +392,71 @@ groups: "Группы"
createGroup: "Создать группу"
ownedGroups: "Собственные группы"
joinedGroups: "Участие в группах"
invites: "Пригласить"
invites: "Приглашения"
groupName: "Название группы"
members: "Участники"
transfer: "Перенос"
transfer: "Отдать"
messagingWithUser: "Сообщения пользователей"
messagingWithGroup: "Чат в группе"
title: "Заголовок."
title: "Заголовок"
text: "Текст"
enable: "Включить."
enable: "Включить"
next: "Дальше"
retype: "Введите повторно"
noteOf: "Посты {user}"
retype: "Введите ещё раз"
noteOf: "Что пишет {user}"
inviteToGroup: "Пригласить в группу"
maxNoteTextLength: "Максимальная длина текста"
quoteAttached: "Цитата"
quoteQuestion: "Хочешь добавить цитату?"
noMessagesYet: "Сообщений нет"
quoteQuestion: "Хотите добавить цитату?"
noMessagesYet: "Пока ни одного сообщения"
newMessageExists: "Новое сообщение"
onlyOneFileCanBeAttached: "К сообщению можно прикрепить только один файл."
signinRequired: "Пожалуйста, войдите."
invitations: "Пригласить"
onlyOneFileCanBeAttached: "К сообщению можно прикрепить только один файл"
signinRequired: "Пожалуйста, войдите"
invitations: "Приглашения"
invitationCode: "Код приглашения"
checking: "Проверка"
available: "Доступен"
available: "Доступно"
unavailable: "Не доступно"
usernameInvalidFormat: "Вы можете использовать a-z, A-Z, 0-9 и _"
usernameInvalidFormat: "Можно использовать только латинские буквы (A—Z, a—z), цифры (0—9) и знак подчёркивания (_)"
tooShort: "Слишком короткий"
tooLong: "Слишком длинный"
weakPassword: "Слабый пароль"
normalPassword: "Обычный пароль"
normalPassword: "Годный пароль"
strongPassword: "Надёжный пароль"
passwordMatched: "Подходящий пароль"
passwordNotMatched: "Пароль не совпадает"
signinWith: "Войти в систему с помощью {x}"
passwordMatched: "Совпали"
passwordNotMatched: "Не совпадают"
signinWith: "Использовать {x} для входа"
signinFailed: "Невозможно войти в систему. Введенное вами имя пользователя или пароль неверны."
tapSecurityKey: "Нажмите на свой электронный ключ"
or: "или"
uiLanguage: "Язык интерфейса"
groupInvited: "Приглашение в группу"
aboutX: "Описание {x}"
useOsNativeEmojis: "Используйте родной для ОС Emojis"
youHaveNoGroups: "Группы не найдены"
joinOrCreateGroup: "Получите приглашение присоединиться к группам или вы можете создать свою собственную группу."
noHistory: "Ничего не найдено"
useOsNativeEmojis: "Использовать эмодзи операционной системы"
youHaveNoGroups: "У вас нет ни одной группы"
joinOrCreateGroup: "Получайте приглашения в группы или создавайте свои собственные"
noHistory: "История пока пуста"
disableAnimatedMfm: "Отключение анимированной разметки MFM"
doing: "В пути"
doing: "В процессе"
category: "Категория"
tags: "Теги"
tags: "Метки"
docSource: "Источник документа"
createAccount: "Зарегистрироваться"
existingAcount: "У вас уже есть учетная запись?"
regenerate: "Восстановить"
createAccount: "Новый аккаунт"
existingAcount: "Уже существующий"
regenerate: "Создать повторно"
fontSize: "Размер шрифта"
noFollowRequests: "У вас нет никаких ожидающих ответа запросов"
noFollowRequests: "Нерассмотренные запросы на подписку отсутствуют"
openImageInNewTab: "Открыть изображение в новой вкладке"
dashboard: "Панель управления"
local: "Локальный"
remote: "Удаленный"
local: "С этого сайта"
remote: "С других сайтов"
total: "Всего"
weekOverWeekChanges: "Еженедельно"
dayOverDayChanges: "Ежедневно"
weekOverWeekChanges: "За неделю"
dayOverDayChanges: "За день"
appearance: "Внешний вид"
clientSettings: "Настройки клиента"
accountSettings: "Настройки учетной записи"
promotion: "Опубликовано"
promotion: "Продвинуто"
promote: "Продвинуть"
numberOfDays: "Количество дней"
hideThisNote: "Спрятать эту запись"
@@ -480,8 +480,8 @@ objectStorageUseProxyDesc: "Отключите, если не будете ис
objectStorageSetPublicRead: "Устанавливать public-read при загрузке на сервер"
serverLogs: "Журнал сервера"
deleteAll: "Удалить всё"
showFixedPostForm: "Показывать поле для ввода новой заметки наверху ленты."
newNoteRecived: "Есть новые посты"
showFixedPostForm: "Показывать поле для ввода новой заметки наверху ленты"
newNoteRecived: "Появилась новая заметка"
sounds: "Звуки"
listen: "Слушать"
none: "Ничего"
@@ -490,23 +490,23 @@ popout: "Развернуть"
volume: "Громкость"
details: "Подробнее"
chooseEmoji: "Выберите эмодзи"
unableToProcess: "Я не могу завершить операцию."
unableToProcess: "Не удаётся завершить операцию"
recentUsed: "Последние использованные"
install: "Установить"
uninstall: "Удалить"
installedApps: "Установленные приложения"
nothing: "Ничего не найдено"
nothing: "Ничего нет"
installedDate: "Дата установки"
lastUsedDate: "Дата использования"
state: "Состояние"
sort: "Сортировать"
ascendingOrder: "по возрастанию"
descendingOrder: "По убыванию"
scratchpad: "Редактор "
scratchpadDescription: "Scratchpad предоставляет экспериментальную среду для AiScript, позволяющую писать, запускать и проверять результаты кода, взаимодействующего с Misskey."
scratchpad: "Когтеточка"
scratchpadDescription: "«Когтеточка» — это место для опытов с AiScript. Здесь можно писать программы, взаимодействующие с Misskey, запускать и смотреть что из этого получается."
output: "Выходы"
script: "Скрипт"
disablePagesScript: "Отключение скриптов в Pages"
disablePagesScript: "Отключить скрипты на «Страницах»"
updateRemoteUser: "Обновить данные пользователя с его сервера"
deleteAllFiles: "Удалить все файлы"
deleteAllFilesConfirm: "Вы хотите удалить все файлы?"
@@ -515,9 +515,9 @@ removeAllFollowingDescription: "Отменить все подписки с до
userSuspended: "Этот пользователь был заморожен"
userSilenced: "Этот пользователь был заглушен"
sidebar: "Боковая панель"
divider: "Разделительная полоса"
divider: "Линия-разделитель"
addItem: "Добавить элемент"
rooms: "Комнаты"
rooms: "Комната"
relays: "Ретрансляторы"
addRelay: "Добавить ретранслятор"
inboxUrl: "URL ящика входящих сообщений"
@@ -529,19 +529,20 @@ enableInfiniteScroll: "Включить бесконечную прокрутк
visibility: "Видимость"
poll: "Опрос"
useCw: "Скрывать содержимое под предупреждением"
enablePlayer: "Включить плеер"
disablePlayer: "Выключить плеер"
expandTweet: "Разавернуть твит"
themeEditor: "Редактор темы"
enablePlayer: "Включить проигрыватель"
disablePlayer: "Выключить проигрыватель"
expandTweet: "Развернуть твит"
themeEditor: "Редактор темы оформления"
description: "Описание"
author: "Автор"
leaveConfirm: "Вы не сохранили изменения. Хотите выйти и потерять их?"
manage: "Управление"
plugins: "Плагины"
pluginInstallWarn: "Пожалуста, не устанавливайте плагины, которым не доверяете."
plugins: "Расширения"
pluginInstallWarn: "Пожалуста, не устанавливайте расширения, которым не доверяете"
deck: "Пульт"
undeck: "Покинуть пульт"
useBlurEffectForModal: "Размывка под формой поверх всего"
useFullReactionPicker: "Полнофункциональный выбор реакций"
generateAccessToken: "Создать токен доступа"
permission: "Разрешения"
enableAll: "Включить все"
@@ -552,56 +553,106 @@ notificationType: "Тип уведомления"
edit: "Изменить"
useStarForReactionFallback: "Ставить ★ в качестве реакции вместо неизвестного эмодзи"
emailConfig: "Настройки почтового сервера"
enableEmail: "Активировать функцию доставки электронной почты"
emailConfigInfo: "Он используется для подтверждения адреса электронной почты и сброса пароля."
email: "email"
enableEmail: "Включить обмен электронной почтой"
emailConfigInfo: "Используется для подтверждения адреса электронной почты и сброса пароля."
email: "Адрес электронной почты"
smtpConfig: "Конфигурация SMTP-сервера"
smtpHost: "Хост"
smtpPort: "smtp порт"
smtpPort: "Порт"
smtpUser: "Имя пользователя"
smtpPass: "Пароль"
emptyToDisableSmtpAuth: "Вы можете отключить SMTP аутентификацию, оставив ваше имя пользователя и пароль пустыми"
emptyToDisableSmtpAuth: "Не заполняйте имя пользователя и пароль, чтобы отключить аутентификацию в SMTP."
smtpSecure: "Использовать SSL/TLS для SMTP-соединений"
smtpSecureInfo: "Выключите его при использовании STARTTLS."
testEmail: "Проверка Email"
smtpSecureInfo: "Выключите при использовании STARTTLS."
testEmail: "Проверка доставки электронной почты"
wordMute: "Скрытие слов"
userSaysSomething: "{name} что-то сообщает"
makeActive: "Активировать"
display: "Показать"
display: "Отображение"
copy: "Копировать"
metrics: "Метрика"
metrics: "Метрики"
overview: "Обзор"
logs: "Лог-файлы"
logs: "Журналы"
delayed: "Задержка"
database: "База данных"
channel: "каналы"
channel: "Каналы"
create: "Создать"
notificationSetting: "Настройки уведомлений"
notificationSettingDesc: "Выберите тип уведомлений для отображения"
useGlobalSetting: "Использовать глобальные настройки"
useGlobalSettingDesc: "Использовать глобальные настройки"
useGlobalSettingDesc: "Если включено, будут использоваться настройки учётной записи. Если включить, этот виджет можно будет настроить индивидуально."
other: "Другие"
regenerateLoginToken: "Создать новый токен для входа"
regenerateLoginTokenDescription: "Создаёт новый токен, используемый внутри программы во время входа. Обычно в этом нет необходимости. При создании все устройства будут отключены."
setMultipleBySeparatingWithSpace: "Вы можете установить несколько, разделив их пробелами."
fileIdOrUrl: "ID файла или URL-адрес"
setMultipleBySeparatingWithSpace: "Можно написать несколько через пробел"
fileIdOrUrl: "Идентификатор файла или ссылка"
chatOpenBehavior: "Поведение окна чата при открытии"
sample: "Пример"
abuseReports: "Отчеты"
reportAbuse: "Отчеты"
abuseReports: "Жалобы"
reportAbuse: "Жалоба"
reportAbuseOf: "Пожаловаться на пользователя {name}"
fillAbuseReportDescription: "Пожалуйста, заполните данные отчета. Если речь идет о конкретном сообщении, пожалуйста, укажите его URL."
abuseReported: "Содержимое отправлено. Спасибо за сообщение"
fillAbuseReportDescription: "Опишите, пожалуйста, причину жалобы подробнее. Если речь о конкретной заметке, будьте добры приложить ссылку на неё."
abuseReported: "Жалоба отправлена. Большое спасибо за информацию."
send: "Отправить"
abuseMarkAsResolved: "Отметить отчет как решенный"
abuseMarkAsResolved: "Отметить жалобу как решённую"
openInNewTab: "Открыть в новой вкладке"
openInSideView: "Открывать в боковой колонке"
defaultNavigationBehaviour: "Поведение навигации по умолчанию"
editTheseSettingsMayBreakAccount: "От изменений в этих настройках ваша учётная запись может поломаться."
instanceTicker: "Строка с инстансом в заметке"
waitingFor: "Ждём {x}"
instanceTicker: "Строка с названием инстанса в заметках"
waitingFor: "Ждём, когда {x} ответит"
random: "Случайные"
system: "Система"
switchUi: "Выбор вида"
desktop: "Стол"
_mfm:
cheatSheet: "Подсказка по разметке MFM"
intro: "MFM — язык оформления текста, придуманный специально для Misskey, который здесь можно много где использовать. На этой странице собраны и кратко изложены способы его применения."
dummy: "Misskey расширяет границы Федиверса."
mention: "Упоминание"
mentionDescription: "При помощи знака «собака» перед именем можно упомянуть какого-нибудь пользователя."
hashtag: "Хэштег"
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: "Настройки игры"
@@ -610,34 +661,34 @@ _reversi:
blackIs: "{name} за чёрных"
rules: "Правила"
botSettings: "Настройки бота"
thisGameIsStartedSoon: "Игра скоро начнётся"
waitingForOther: "Ожидание оппонента..."
waitingForMe: "В ожидании, когда будете готовы"
waitingBoth: "Приготовьтесь"
thisGameIsStartedSoon: "Игра скоро начнётся."
waitingForOther: "Ожидание соперника..."
waitingForMe: "В ожидании, когда будете готовы."
waitingBoth: "Приготовьтесь."
ready: "Готово"
cancelReady: "Возврат к подготовке"
opponentTurn: "Ход соперника"
myTurn: "Ваш ход"
turnOf: "Ходит {name}"
pastTurnOf: "Ходит {name}"
turnOf: "Ходит {name}."
pastTurnOf: "Ходит {name}."
surrender: "Сдаться"
surrendered: "Сдавшись"
surrendered: "Противник сдался"
drawn: "Ничья"
won: "{name} — победитель"
won: "Победитель — {name}"
black: "Чёрные"
white: "Белые"
total: "Всего"
turnCount: "Ход {count}"
myGames: "Мои игры"
myGames: "Сыгранное вами"
allGames: "Все игры"
ended: "Завершено"
ended: "Завершена"
playing: "Идёт игра"
isLlotheo: "Выигрывает меньшее число камней (LLoTheO)"
loopedMap: "Замкнутая в кольцо доска"
canPutEverywhere: "Камни можно ставить везде"
_instanceTicker:
none: "Не показывать"
remote: "Только у пользователей с других сайтов"
remote: "Только для других сайтов"
always: "Показывать всегда"
_serverDisconnectedBehavior:
reload: "Автоматическая перезагрузка"
@@ -648,37 +699,37 @@ _channel:
edit: "Редактировать канал"
setBanner: "Установить баннер"
removeBanner: "Удалить баннер"
featured: "В тренде"
featured: "Из подборки"
owned: "Владелец"
following: "Читаю"
usersCount: "{n} Участники"
notesCount: "{n} Записи"
usersCount: "Участников: {n}"
notesCount: "Заметок: {n}"
_sidebar:
full: "Полный"
icon: "Иконка"
full: "Полностью"
icon: "Только значки"
hide: "Спрятать"
_wordMute:
muteWords: "Скрыть слово"
muteWordsDescription: "Разделяйте пробелами для условия \"И\". Разделяйте переводом строки для \"ИЛИ\"."
muteWordsDescription2: "Округляйте ключевые слова слэшами для использования регулярных выражений."
muteWordsDescription: "Пишите слова через пробел в одной строке, чтобы фильтровать их появление вместе; а если хотите фильтровать любое из них, пишите в отдельных строках."
muteWordsDescription2: "Здесь можно использовать регулярные выражения — просто заключите их между двумя дробными чертами (/)."
softDescription: "Соответствующие условиям заметки будут спрятаны из вашей ленты."
hardDescription: "Соответстующие условиям заметки вообще не будут попадать в вашу ленту. Даже если вы поменяете условия, отсеенные таким образом заметки уже не появятся."
soft: "Лёгкий "
hard: "Сложный"
mutedNotes: "Скрытые посты"
soft: "Мягкий"
hard: "Жёсткий"
mutedNotes: "Скрытые заметки"
_theme:
explore: "Обзор"
install: "Установить тему"
manage: "Менеджер тем"
code: "Код темы"
installed: "{name} установлено "
alreadyInstalled: "Тема уже установлена"
invalid: "Формат темы некорректный "
installed: "Тема «{name}» установлена."
alreadyInstalled: "Тема уже установлена."
invalid: "Формат темы некорректный."
make: "Создать тему"
base: "Основа"
addConstant: "Добавить константу"
constant: "Константа"
defaultValue: "Исходное содержимое"
defaultValue: "По умолчанию"
color: "Цвет"
refProp: "Ссылка на свойство"
refConst: "Ссылка на константу"
@@ -686,11 +737,11 @@ _theme:
func: "Функции"
funcKind: "Тип функции"
argument: "Аргумент"
basedProp: "Указанное свойство"
basedProp: "Исходное свойство"
alpha: "Непрозрачность"
darken: "Затемнение"
lighten: "Осветление"
inputConstantName: "Введите имя для константы"
inputConstantName: "Введите имя для константы."
importInfo: "Если вы введете код темы здесь, вы можете импортировать его в редактор тем."
deleteConstantConfirm: "Вы действительно хотите удалить константу {const}?"
keys:
@@ -751,7 +802,7 @@ _ago:
justNow: "Только что"
secondsAgo: "{n} с назад"
minutesAgo: "{n} мин назад"
hoursAgo: "{} часов назад"
hoursAgo: "{n} ч назад"
daysAgo: "{n} сут назад"
weeksAgo: "{n} нед. назад"
monthsAgo: "{n} мес. назад"
@@ -771,7 +822,7 @@ _tutorial:
step3_1: "Успешно заполнили профиль?"
step3_2: "Что ж, теперь самое время опубликуовать заметку. Если нажать вверху страницы на изображение карандаша, появится форма для текста."
step3_3: "Напишите в неё, что хотите, и нажмите на кнопку в правом верхнем углу."
step3_4: "Ничего не приходит в голову? Как насчёт: «я новенький, пока осваиваюсь в Misskey»?"
step3_4: "Ничего не приходит в голову? Как насчёт: «Я новенький, пока осваиваюсь в Misskey»?"
step4_1: "С написанием первой заметки покончено?"
step4_2: "Отлично, теперь она должна появиться в вашей ленте."
step5_1: "А теперь самое время немного оживить ленту, подписавшись на других."
@@ -848,7 +899,7 @@ _widgets:
notifications: "Уведомления"
timeline: "Лента"
calendar: "Календарь"
trends: "Популярное"
trends: "Актуальное"
clock: "Часы"
rss: "Просмотр RSS"
activity: "Активность"
@@ -859,7 +910,7 @@ _widgets:
_cw:
hide: "Спрятать"
show: "Показать еще"
chars: "символов: {count}"
chars: "знаков: {count}"
files: "файлов: {count}"
_poll:
noOnlyOneChoice: "Нужно хотя бы два варианта."
@@ -877,7 +928,7 @@ _poll:
totalVotes: "Голосов всего: {n}"
vote: "Проголосовать"
showResult: "Смотреть результаты"
voted: "Проголосовали"
voted: "Голос отдан"
closed: "Завершено"
remainingDays: "Осталось {d} сут {h} ч"
remainingHours: "Осталось {h} ч {m} мин"
@@ -946,10 +997,10 @@ _instanceCharts:
files: "Изменения числа файлов"
filesTotal: "Суммарное количество файлов"
_timelines:
home: "Персональное"
local: "Местное"
social: "Социальное"
global: "Глобальное"
home: "Персональная"
local: "Местная"
social: "Социальная"
global: "Всеобщая"
_rooms:
roomOf: "Комната {user}"
addFurniture: "Добавить обстановку"
@@ -959,7 +1010,7 @@ _rooms:
remove: "Выбросить"
clear: "Очистить"
clearConfirm: "Уверены что стоит убрать всю обстановку из вашей комнаты?"
leaveConfirm: "Измнения не сохранены, правда покинуть комнату?"
leaveConfirm: "Изменения не сохранены, правда хотите покинуть комнату?"
chooseImage: "Выберите изображение"
roomType: "Стиль комнаты"
carpetColor: "Цвет ковра"
@@ -1016,11 +1067,11 @@ _pages:
newPage: "Создать страницу"
editPage: "Править страницу"
readPage: "Читать страницу"
created: "Страница успешно создана"
updated: "Страница успешно обновлена"
deleted: "Страница успешно удалена"
nameAlreadyExists: "Указанный адрес страницы уже существует"
invalidNameTitle: "Указанный адрес страницы недопустим"
created: "Страница успешно создана."
updated: "Страница успешно обновлена."
deleted: "Страница успешно удалена."
nameAlreadyExists: "Указанный адрес страницы уже существует."
invalidNameTitle: "Указанный адрес страницы недопустим."
invalidNameText: "Проверьте, что не оставили поле пустым."
editThisPage: "Правка этой страницы"
viewSource: "Просмотр исходника"
@@ -1030,10 +1081,11 @@ _pages:
my: "Свои страницы"
liked: "Понравившиеся страницы"
inspector: "Инспектор"
contents: "Содержательные"
content: "Содержимое"
variables: "Переменные"
title: "Заголовок"
url: "URL страницы"
url: "Адрес страницы"
summary: "Краткое содержание"
alignCenter: "Выровнять элементы по центру"
hideTitleWhenPinned: "Скрыть заголовок страницы при привязке к профилю"
@@ -1095,7 +1147,7 @@ _pages:
inc: "Увеличивать на"
_button:
text: "Надпись"
colored: "Цветная"
colored: "Выделена цветом"
action: "Действие по нажатию"
_action:
dialog: "Показать всплывающий текст"
@@ -1295,21 +1347,21 @@ _pages:
argVariables: "Аргументы"
_relayStatus:
requesting: "В ожидании одобрения"
accepted: "Одобрено"
rejected: "Отказано"
accepted: "Одобрено."
rejected: "Отказано."
_notification:
fileUploaded: "Файл успешно загружен"
youGotMention: "{name} упоминает вас"
youGotReply: "{name} отвечает вам"
youGotQuote: "{name} цитирует вас"
youRenoted: "{name} передаёт вашу заметку"
youGotPoll: "{name} участник вашего опроса"
youGotMessagingMessageFromUser: "{name} пишет вам"
youGotMessagingMessageFromGroup: "Отправлено сообщение в группу «{name}»"
youWereFollowed: "У вас новый подписчик"
youReceivedFollowRequest: "У вас новый запрос на подписку"
yourFollowRequestAccepted: "Ваш запрос на подписку одобрен"
youWereInvitedToGroup: "Приглашение в группу"
fileUploaded: "Файл успешно загружен."
youGotMention: "{name} упоминает вас."
youGotReply: "{name} отвечает вам."
youGotQuote: "{name} цитирует вас."
youRenoted: "{name} передаёт вашу заметку."
youGotPoll: "{name} участвует в вашем опросе."
youGotMessagingMessageFromUser: "{name} пишет вам."
youGotMessagingMessageFromGroup: "Новое сообщение в группе «{name}»."
youWereFollowed: "У вас новый подписчик."
youReceivedFollowRequest: "У вас новый запрос на подписку."
yourFollowRequestAccepted: "Ваш запрос на подписку одобрен."
youWereInvitedToGroup: "Вы приглашены в группу."
_types:
all: "Все"
follow: "Подписки"
@@ -1321,7 +1373,7 @@ _notification:
pollVote: "Голосования"
receiveFollowRequest: "Получен запрос на подписку"
followRequestAccepted: "Запрос на подписку одобрен"
groupInvited: "Пришлашение в группы"
groupInvited: "Приглашение в группы"
app: "Уведомления из приложений"
_deck:
alwaysShowMainColumn: "Всегда показывать главную колонку"

View File

@@ -1,3 +1,5 @@
---
_lang_: "ياپونچە"
search: "ئىزدەش"
_mfm:
search: "ئىزدەش"

View File

@@ -5,6 +5,7 @@ search: "Пошук"
notifications: "Сповіщення"
username: "Ім'я користувача"
password: "Пароль"
fetchingAsApObject: "Отримуємо з федіверсу..."
ok: "OK"
gotIt: "Зрозуміло!"
cancel: "Скасувати"
@@ -43,6 +44,7 @@ copyUsername: "Скопіювати ім’я користувача"
searchUser: "Пошук користувачів"
reply: "Відповісти"
loadMore: "Показати більше"
youGotNewFollower: "У вас новий підписник"
receiveFollowRequest: "Отримано запит на підписку"
followRequestAccepted: "Запит на підписку прийнято"
mention: "Згадка"
@@ -53,6 +55,7 @@ import: "Імпорт"
export: "Експорт"
files: "Файли"
download: "Завантажити"
unfollowConfirm: "Ви впевнені, що хочете відписатися від {name}?"
lists: "Списки"
noLists: "Немає списків"
note: "Дописи"
@@ -71,7 +74,11 @@ privacy: "Приватність"
makeFollowManuallyApprove: "Підтверджувати підписників уручну"
defaultNoteVisibility: "Видимість допису за замовчуванням"
follow: "Підписка"
followRequest: "Запит на підписку"
followRequests: "Запити на підписку"
unfollow: "Відписатися"
followRequestPending: "Очікуючі запити на підписку"
enterEmoji: "Введіть емодзі"
renote: "Поширити"
unrenote: "Відміна поширення"
quote: "Цитата"
@@ -81,7 +88,10 @@ clickToShow: "Натисніть для перегляду"
sensitive: "NSFW"
add: "Додати"
reaction: "Реакції"
markAsSensitive: "Відмітити як NSFW"
rememberNoteVisibility: "Пам’ятати видимість дописів"
attachCancel: "Видалити вкладення"
markAsSensitive: "Позначити як NSFW"
unmarkAsSensitive: "Зняти позначку NSFW"
enterFileName: "Введіть ім'я файлу"
mute: "Ігнорувати"
unmute: "Показувати"
@@ -91,6 +101,8 @@ suspend: "Призупинити"
unsuspend: "Відновити"
blockConfirm: "Ви впевнені, що хочете заблокувати цей акаунт?"
unblockConfirm: "Ви впевнені, що хочете розблокувати цей акаунт?"
suspendConfirm: "Ви впевнені, що хочете призупинити цей акаунт?"
unsuspendConfirm: "Ви впевнені, що хочете відновити цей акаунт?"
selectList: "Виберіть список"
selectAntenna: "Виберіть антену"
selectWidget: "Виберіть віджет"
@@ -99,12 +111,314 @@ editWidgetsExit: "Готово"
customEmojis: "Кастомні емоджі"
emoji: "Емоджі"
emojiName: "Назва емоджі"
emojiUrl: "URL емодзі"
addEmoji: "Додати емодзі"
settingGuide: "Рекомендована конфігурація"
cacheRemoteFiles: "Кешувати дані з інших інстансів"
flagAsBot: "Акаунт бота"
flagAsCat: "Акаунт кота"
autoAcceptFollowed: "Автоматично приймати запити на підписку від користувачів, на яких ви підписані"
addAcount: "Додати акаунт"
loginFailed: "Не вдалося увійти"
showOnRemote: "Переглянути в оригіналі"
general: "Загальне"
wallpaper: "Шпалери"
setWallpaper: "Встановити шпалери"
removeWallpaper: "Прибрати шпалери"
searchWith: "Шукати з {q}"
youHaveNoLists: "У вас немає списків"
followConfirm: "Підписатися на {name}?"
proxyAccount: "Проксі-акаунт"
host: "Хост"
selectUser: "Виберіть користувача"
recipient: "Кому"
annotation: "Коментар"
federation: "Федіверс"
instances: "Інстанс"
registeredAt: "Приєднався(-лась)"
latestRequestSentAt: "Останній запит надіслано"
latestRequestReceivedAt: "Останній запит прийнято"
latestStatus: "Останній статус"
storageUsage: "Використання простіру"
charts: "Графіки"
perHour: "Щогодини"
perDay: "Щоденно"
stopActivityDelivery: "Припинити розсилання активності"
blockThisInstance: "Заблокувати цей інстанс"
operations: "Операції"
software: "Програмне забезпечення"
version: "Версія"
metadata: "Метадані"
withNFiles: "файли: {n}"
monitor: "Монітор"
jobQueue: "Черга завдань"
cpuAndMemory: "ЦП та пам'ять"
network: "Мережа"
disk: "Диск"
instanceInfo: "Про цей інстанс"
statistics: "Статистика"
clearQueue: "Очистити чергу"
clearQueueConfirmTitle: "Ви впевнені, що хочете очистити чергу?"
clearCachedFiles: "Очистити кеш"
clearCachedFilesConfirm: "Ви впевнені, що хочете видалити всі кешовані файли?"
blockedInstances: "Заблоковані інстанси"
muteAndBlock: "Ігнор і блокування"
mutedUsers: "Ігноровані користувачі"
blockedUsers: "Заблоковані користувачі"
noUsers: "Немає користувачів"
editProfile: "Редагувати профіль"
noteDeleteConfirm: "Ви дійсно хочете видалити цей допис?"
pinLimitExceeded: "Більше дописів не можна закріпити"
done: "Готово"
processing: "Обробка"
preview: "Передогляд"
default: "За умовчанням"
noCustomEmojis: "Немає кастомних емоджі"
noJobs: "Немає завдань"
federating: "Федерується"
blocked: "Заблоковано"
suspended: "Призупинено"
notResponding: "Не відповідає"
changePassword: "Змінити пароль"
security: "Безпека"
currentPassword: "Поточний пароль"
newPassword: "Новий пароль"
newPasswordRetype: "Новий пароль (повторно)"
attachFile: "Вкласти файл"
more: "Бiльше!"
featured: "Виділено"
noSuchUser: "Користувача не знайдено"
lookup: "Пошук"
announcements: "Оголошення"
imageUrl: "URL зображення"
remove: "Видалити"
removed: "Видалено"
saved: "Збережено"
messaging: "Чати"
upload: "Завантажити"
fromDrive: "З диска"
fromUrl: "З URL"
uploadFromUrl: "Завантажити з URL"
explore: "Огляд"
games: "Ігри Misskey"
noMoreHistory: "Подальшої історії немає"
start: "Розпочати"
home: "Домівка"
activity: "Активність"
images: "Зображення"
birthday: "День народження"
yearsOld: "{age} років"
registeredDate: "Приєднався(-лась)"
location: "Локація"
theme: "Тема"
themeForLightMode: "Світла тема"
themeForDarkMode: "Темна тема"
light: "Світла"
dark: "Темна"
lightThemes: "Світлі теми"
darkThemes: "Темні теми"
drive: "Диск"
fileName: "Ім'я файлу"
selectFile: "Вибрати файл"
selectFiles: "Вибрати файли"
selectFolder: "Вибрати теку"
selectFolders: "Вибрати теки"
renameFile: "Перейменувати файл"
folderName: "Ім'я теки"
createFolder: "Створити теку"
renameFolder: "Перейменувати теку"
deleteFolder: "Видалити теку"
addFile: "Додати файл"
emptyDrive: "Диск порожній"
emptyFolder: "Тека порожня"
unableToDelete: "Видалення неможливе"
inputNewFileName: "Введіть ім'я нового файлу"
inputNewFolderName: "Введіть ім'я нової теки"
hasChildFilesOrFolders: "Ця тека не порожня і не може бути видалена"
copyUrl: "Копіювати URL"
rename: "Перейменувати"
avatar: "Аватар"
banner: "Банер"
nsfw: "NSFW"
disconnectedFromServer: "Підключення до сервера було перервано"
reload: "Оновити"
doNothing: "Нічого не робити"
reloadConfirm: "Перезавантажити стрічку?"
watch: "Стежити"
unwatch: "Не стежити"
accept: "Прийняти"
reject: "Відхилити"
instanceName: "Назва інстансу"
instanceDescription: "Описання інстансу"
maintainerName: "Ім'я адміністратора"
maintainerEmail: "Email адміністратора"
tosUrl: "URL умов використання"
thisYear: "Рік"
thisMonth: "Місяць"
today: "День"
dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Сторінки"
integration: "Інтеграція"
connectSerice: "Під’єднатися"
disconnectSerice: "Відключитися"
enableLocalTimeline: "Увімкнути локальну стрічку"
enableGlobalTimeline: "Увімкнути глобальну стрічку"
registration: "Реєстрація"
enableRegistration: "Дозволити реєстрацію"
invite: "Запрошення"
proxyRemoteFiles: "Проксувати файли з інших інстансів"
iconUrl: "URL аватара"
bannerUrl: "URL банера"
basicInfo: "Основна інформація"
pinnedUsers: "Закріплені користувачі"
hcaptcha: "hCaptcha"
enableHcaptcha: "Увімкнути hCaptcha"
hcaptchaSiteKey: "Ключ сайту"
hcaptchaSecretKey: "Секретний ключ"
recaptcha: "reCAPTCHA"
enableRecaptcha: "Увімкнути reCAPTCHA"
recaptchaSiteKey: "Ключ сайту"
recaptchaSecretKey: "Секретний ключ"
antennas: "Антени"
manageAntennas: "Налаштування антен"
name: "Ім'я"
antennaSource: "Джерело антени"
antennaKeywords: "Ключові слова антени"
antennaExcludeKeywords: "Винятки"
serviceworker: "ServiceWorker"
enableServiceworker: "Ввімкнути ServiceWorker"
caseSensitive: "З урахуванням регістру"
notesAndReplies: "Дописи та відповіді"
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: "Всього"
appearance: "Вигляд"
clientSettings: "Налаштування клієнта"
accountSettings: "Налаштування акаунта"
promotion: "Просування"
promote: "Просунути"
numberOfDays: "Кількість днів"
hideThisNote: "Сховати цей допис"
objectStorageBaseUrl: "Base URL"
objectStorageBucket: "Bucket"
objectStoragePrefix: "Prefix"
objectStorageEndpoint: "Endpoint"
objectStorageRegion: "Region"
objectStorageUseSSL: "Використовувати SSL"
objectStorageUseProxy: "Використовувати Proxy"
deleteAll: "Видалити все"
newNoteRecived: "Є нові дописи"
sounds: "Звук"
listen: "Слухати"
showInPage: "Показати на сторінці"
volume: "Гучність"
install: "Інсталювати"
uninstall: "Видалити"
sort: "Сортування"
ascendingOrder: "За зростанням"
descendingOrder: "За спаданням"
script: "Скрипт"
deleteAllFiles: "Видалити всі файли"
deleteAllFilesConfirm: "Ви дійсно хочете видалити всі файли?"
sidebar: "Бокова панель"
rooms: "Кімнати"
relays: "Ретранслятори"
addRelay: "Додати ретранслятор"
smtpHost: "Хост"
smtpUser: "Ім'я користувача"
smtpPass: "Пароль"
regenerateLoginToken: "Оновити Login Token"
_mfm:
cheatSheet: " Довідка MFM"
mention: "Згадка"
quote: "Цитата"
emoji: "Кастомні емоджі"
search: "Пошук"
_reversi:
total: "Всього"
_sidebar:
icon: "Аватар"
_theme:
keys:
mention: "Згадка"
@@ -112,23 +426,40 @@ _theme:
_sfx:
note: "Дописи"
notification: "Сповіщення"
chat: "Чати"
_antennaSources:
homeTimeline: "Дописи тих, на кого ви підписані"
_widgets:
notifications: "Сповіщення"
timeline: "Стрічка"
activity: "Активність"
federation: "Федіверс"
_cw:
show: "Показати більше"
_visibility:
home: "Домівка"
followers: "Підписники"
localOnly: "Лише локально"
_postForm:
replyPlaceholder: "Відповідь на допис..."
_profile:
name: "Ім'я"
username: "Ім'я користувача"
_exportOrImport:
followingList: "Підписки"
muteList: "Ігнорувати"
blockingList: "Заблокувати"
userLists: "Списки"
_timelines:
home: "Домівка"
_rooms:
_roomType:
default: "За умовчанням"
_furnitures:
monitor: "Монітор"
_pages:
blocks:
image: "Зображення"
script:
categories:
list: "Списки"
@@ -149,6 +480,7 @@ _pages:
array: "Списки"
_notification:
youRenoted: "{name} поширив(ла) ваш допис"
youWereFollowed: "У вас новий підписник"
_types:
follow: "Підписки"
mention: "Згадка"
@@ -159,5 +491,6 @@ _deck:
_columns:
notifications: "Сповіщення"
tl: "Стрічка"
antenna: "Антени"
list: "Списки"
mentions: "Згадки"

View File

@@ -84,7 +84,7 @@ followRequest: "关注申请"
followRequests: "关注申请"
unfollow: "取消关注"
followRequestPending: "发送关注申请"
enterEmoji: "输入Emoji"
enterEmoji: "输入表情符号"
renote: "转发"
unrenote: "取消转发"
quote: "引用"
@@ -117,9 +117,9 @@ editWidgets: "编辑小工具"
editWidgetsExit: "完成编辑"
customEmojis: "自定义Emoji"
emoji: "表情符号"
emojiName: "Emoji 名称"
emojiUrl: "emoji 地址"
addEmoji: "添加Emoji"
emojiName: "表情符号名称"
emojiUrl: "表情符号地址"
addEmoji: "添加表情符号"
settingGuide: "推荐配置"
cacheRemoteFiles: "远程文件缓存"
cacheRemoteFilesDescription: "当禁用此设定时远程文件将直接从远程实例载入。禁用后会减小储存空间需求,但是会增加流量,因为缩略图不会被生成。"
@@ -185,7 +185,7 @@ done: "完成"
processing: "处理中"
preview: "预览"
default: "默认"
noCustomEmojis: "自定义Emoji"
noCustomEmojis: "没有自定义表情符号"
noJobs: "没有任务"
federating: "联合中"
blocked: "已拦截"
@@ -214,6 +214,8 @@ imageUrl: "图片URL"
remove: "删除"
removed: "已删除"
removeAreYouSure: "要删掉「{x}」吗?"
deleteAreYouSure: "要删掉「{x}」吗?"
resetAreYouSure: "恢复默认设置?"
saved: "已保存"
messaging: "聊天"
upload: "上传"
@@ -373,8 +375,6 @@ unregister: "删除账户"
passwordLessLogin: "无密码登录"
resetPassword: "重置密码"
newPasswordIs: "新的密码是「{password}」"
autoNoteWatch: "自动关注帖子"
autoNoteWatchDescription: "让您能够收到关于「回应」和回复其他用户的帖子的通知。"
reduceUiAnimation: "减少UI动画"
share: "分享"
notFound: "未找到"
@@ -432,7 +432,7 @@ or: "或者"
uiLanguage: "显示语言"
groupInvited: "群组招待"
aboutX: "关于 {x}"
useOsNativeEmojis: "使用OS原生Emoji"
useOsNativeEmojis: "使用OS原生表情符号"
youHaveNoGroups: "没有群组"
joinOrCreateGroup: "请加入一个现有的群组,或者创建新群组。"
noHistory: "没有历史记录"
@@ -542,6 +542,7 @@ pluginInstallWarn: "请不要安装不明来源的插件"
deck: "Deck"
undeck: "取消Deck"
useBlurEffectForModal: "模态框使用模糊效果"
useFullReactionPicker: "使用全功能的回应工具栏"
generateAccessToken: "生成访问令牌"
permission: "权限"
enableAll: "启用全部"
@@ -550,7 +551,7 @@ tokenRequested: "允许访问账户"
pluginTokenRequestedDescription: "此插件将能够拥有此处设置的权限"
notificationType: "通知类型"
edit: "编辑"
useStarForReactionFallback: "如果回应的颜文字未知,则使用★作为代替"
useStarForReactionFallback: "如果回应的是未知表情符号,则使用★作为代替"
emailConfig: "邮件服务器设置"
enableEmail: "启用发送邮件功能"
emailConfigInfo: "用于确认电子邮件和密码重置"
@@ -604,6 +605,54 @@ random: "随机"
system: "系统"
switchUi: "切换界面"
desktop: "桌面"
_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: "引用"
quoteDescription: "可以用来表示引用的内容。"
emoji: "自定义表情符号"
emojiDescription: "可以将自定义表情符号使用冒号括起来,就可以显示自定义表情符号了。"
search: "搜索"
searchDescription: "显示含有搜索内容示例的搜索框。"
flip: "翻转"
flipDescription: "将内容上下或左右翻转。"
jelly: "动画(果冻)"
jellyDescription: "显示果冻一样的动画效果。"
tada: "动画(锵锵)"
tadaDescription: "显示\"锵锵!\"的动画效果。"
jump: "动画(跳动)"
jumpDescription: "显示跳动的动画效果。"
bounce: "动画(弹性)"
bounceDescription: "显示弹性一样的动画效果。"
shake: "动画(摇晃)"
shakeDescription: "显示摇晃的动画效果。"
twitch: "动画(颤抖)"
twitchDescription: "显示强烈颤抖的动画效果。"
spin: "动画(回转)"
spinDescription: "显示回转的动画效果。"
_reversi:
reversi: "黑白棋"
gameSettings: "对局设置"
@@ -1032,6 +1081,7 @@ _pages:
my: "我的页面"
liked: "喜欢的页面"
inspector: "检查器"
contents: "内容"
content: "页面内容"
variables: "变量"
title: "标题"

View File

@@ -212,6 +212,7 @@ imageUrl: "圖片URL"
remove: "刪除"
removed: "成功移除"
removeAreYouSure: "確定要刪掉「{x}」嗎?"
deleteAreYouSure: "確定要刪掉「{x}」嗎?"
saved: "已保存"
messaging: "傳送訊息"
upload: "上傳"
@@ -370,8 +371,6 @@ unregister: "刪除賬戶"
passwordLessLogin: "設置無密碼登入"
resetPassword: "重置密碼"
newPasswordIs: "新密碼為「{password}」"
autoNoteWatch: "自動追隨貼文"
autoNoteWatchDescription: "收到反應或回覆過的貼文的通知"
reduceUiAnimation: "減少介面的動態視覺"
share: "分享"
notFound: "找不到"
@@ -559,6 +558,13 @@ send: "發送"
openInNewTab: "在新分頁中開啟"
random: "隨機"
system: "系統"
_mfm:
mention: "提及"
hashtag: "#tag"
link: "鏈接"
quote: "引用"
emoji: "自訂表情符號"
search: "搜尋"
_reversi:
reversi: "黑白棋"
gameSettings: "對弈設定"

View File

@@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class deleteAutoWatch1604821689616 implements MigrationInterface {
name = 'deleteAutoWatch1604821689616'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "autoWatch"`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user_profile" ADD "autoWatch" boolean NOT NULL DEFAULT false`);
}
}

View 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"`);
}
}

View 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`);
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.53.0",
"version": "12.57.0",
"codename": "indigo",
"repository": {
"type": "git",
@@ -99,51 +99,50 @@
"@types/tmp": "0.2.0",
"@types/uuid": "8.3.0",
"@types/web-push": "3.3.0",
"@types/webpack": "4.41.22",
"@types/webpack": "4.41.24",
"@types/webpack-stream": "3.2.11",
"@types/websocket": "1.0.1",
"@types/ws": "7.2.7",
"@typescript-eslint/parser": "4.4.0",
"@typescript-eslint/parser": "4.6.1",
"@vue/compiler-sfc": "3.0.2",
"abort-controller": "3.0.0",
"apexcharts": "3.22.0",
"apexcharts": "3.22.1",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
"aws-sdk": "2.770.0",
"aws-sdk": "2.787.0",
"bcryptjs": "2.4.3",
"blurhash": "1.1.3",
"bull": "3.18.0",
"bull": "3.18.1",
"cafy": "15.2.1",
"cbor": "5.1.0",
"chalk": "4.1.0",
"chart.js": "2.9.3",
"chart.js": "2.9.4",
"cli-highlight": "2.1.4",
"commander": "4.1.1",
"content-disposition": "0.5.3",
"core-js": "3.6.5",
"core-js": "3.7.0",
"crc-32": "1.2.0",
"css-loader": "5.0.0",
"css-loader": "5.0.1",
"cssnano": "4.1.10",
"dateformat": "3.0.3",
"deep-entries": "3.1.0",
"diskusage": "1.1.3",
"double-ended-queue": "2.1.0-0",
"escape-regexp": "0.0.1",
"eslint": "7.11.0",
"eslint": "7.12.1",
"eslint-plugin-vue": "7.1.0",
"eventemitter3": "4.0.7",
"feed": "4.2.1",
"fibers": "5.0.0",
"file-type": "16.0.0",
"file-type": "16.0.1",
"fluent-ffmpeg": "2.1.2",
"glob": "7.1.6",
"got": "11.7.0",
"got": "11.8.0",
"gulp": "4.0.2",
"gulp-rename": "2.0.0",
"gulp-replace": "1.0.0",
"gulp-sourcemaps": "2.6.5",
"gulp-terser": "1.4.0",
"gulp-tslint": "8.1.4",
"gulp-typescript": "6.0.0-alpha.1",
"hard-source-webpack-plugin": "0.13.1",
@@ -176,24 +175,24 @@
"lookup-dns-cache": "2.1.0",
"markdown-it": "11.0.1",
"markdown-it-anchor": "6.0.0",
"mocha": "8.1.3",
"mocha": "8.2.1",
"moji": "0.5.1",
"ms": "2.1.2",
"multer": "1.4.2",
"nested-property": "4.0.0",
"node-fetch": "2.6.1",
"nodemailer": "6.4.13",
"nodemailer": "6.4.15",
"object-assign-deep": "0.4.0",
"os-utils": "0.0.14",
"p-cancelable": "2.0.0",
"parse5": "6.0.1",
"parsimmon": "1.16.0",
"pg": "8.4.1",
"pg": "8.4.2",
"portscanner": "2.2.0",
"postcss": "8.1.3",
"postcss": "8.1.6",
"postcss-loader": "4.0.4",
"prismjs": "1.22.0",
"probe-image-size": "5.0.0",
"probe-image-size": "6.0.0",
"promise-limit": "2.7.0",
"promise-sequential": "1.1.1",
"pug": "2.0.4",
@@ -215,8 +214,8 @@
"rimraf": "3.0.2",
"rndstr": "1.0.0",
"s-age": "1.1.2",
"sass": "1.27.0",
"sass-loader": "10.0.4",
"sass": "1.29.0",
"sass-loader": "10.0.5",
"seedrandom": "3.0.5",
"sharp": "0.26.2",
"speakeasy": "2.0.0",
@@ -224,18 +223,18 @@
"style-loader": "2.0.0",
"summaly": "2.4.0",
"syslog-pro": "1.0.0",
"systeminformation": "4.27.10",
"systeminformation": "4.28.1",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"three": "0.117.1",
"tinycolor2": "1.4.2",
"tmp": "0.2.1",
"ts-loader": "8.0.6",
"ts-loader": "8.0.9",
"ts-node": "9.0.0",
"tslint": "6.1.3",
"tslint-sonarts": "1.9.0",
"typeorm": "0.2.28",
"typescript": "4.0.3",
"typeorm": "0.2.29",
"typescript": "4.0.5",
"ulid": "2.3.0",
"url-loader": "4.1.1",
"uuid": "8.3.1",
@@ -244,17 +243,17 @@
"vue-color": "2.7.1",
"vue-draggable-next": "1.0.8",
"vue-i18n": "9.0.0-beta.6",
"vue-json-pretty": "1.7.0",
"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.3.2",
"webpack-cli": "4.1.0",
"webpack": "5.4.0",
"webpack-cli": "4.2.0",
"websocket": "1.0.32",
"ws": "7.3.1",
"xev": "2.0.1"

View File

@@ -1,62 +1,90 @@
<template>
<MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')">
<div class="omfetrab _popup">
<header>
<button v-for="(category, i) in categories"
class="_button"
@click="go(category)"
:class="{ active: category.isActive }"
:key="i"
>
<Fa :icon="category.icon" fixed-width/>
</button>
</header>
<div class="omfetrab _popup" :class="{ compact }">
<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">
<template v-if="categories[0].isActive">
<header class="category"><Fa :icon="faHistory" fixed-width/> {{ $t('recentUsed') }}</header>
<div class="list">
<button v-for="emoji in ($store.state.device.recentEmojis || [])"
<section class="result">
<div v-if="searchResultCustom.length > 0">
<button v-for="emoji in searchResultCustom"
class="_button"
:title="emoji.name"
@click="chosen(emoji)"
@click="chosen(emoji, $event)"
:key="emoji"
tabindex="0"
>
<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>
<img v-else :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
</button>
</div>
<header class="category"><Fa :icon="faAsterisk" fixed-width/> {{ $t('customEmojis') }}</header>
</template>
<template v-if="categories.find(x => x.isActive).name">
<div class="list">
<button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)"
<div v-if="searchResultUnicode.length > 0">
<button v-for="emoji in searchResultUnicode"
class="_button"
:title="emoji.name"
@click="chosen(emoji)"
@click="chosen(emoji, $event)"
:key="emoji.name"
tabindex="0"
>
<MkEmoji :emoji="emoji.char"/>
</button>
</div>
</section>
<div class="index">
<section v-if="showPinned">
<div>
<button v-for="emoji in pinned"
class="_button"
@click="chosen(emoji, $event)"
tabindex="0"
>
<MkEmoji :emoji="emoji" :normal="true"/>
</button>
</div>
</section>
<section>
<header class="_acrylic"><Fa :icon="faClock" fixed-width/> {{ $t('recentUsed') }}</header>
<div>
<button v-for="emoji in $store.state.device.recentlyUsedEmojis"
class="_button"
@click="chosen(emoji, $event)"
:key="emoji"
>
<MkEmoji :emoji="emoji" :normal="true"/>
</button>
</div>
</section>
<div class="arrow"><Fa :icon="faChevronDown"/></div>
</div>
<section v-for="category in customEmojiCategories" :key="'custom:' + category" class="custom">
<header class="_acrylic" v-appear="() => visibleCategories[category] = true">{{ category || $t('other') }}</header>
<div v-if="visibleCategories[category]">
<button v-for="emoji in customEmojis.filter(e => e.category === category)"
class="_button"
:title="emoji.name"
@click="chosen(emoji, $event)"
:key="emoji.name"
>
<img :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
</button>
</div>
</section>
<section v-for="category in categories" :key="category.name" class="unicode">
<header class="_acrylic" v-appear="() => category.isActive = true"><Fa :icon="category.icon" fixed-width/> {{ category.name }}</header>
<div v-if="category.isActive">
<button v-for="emoji in emojilist.filter(e => e.category === category.name)"
class="_button"
:title="emoji.name"
@click="chosen(emoji, $event)"
:key="emoji.name"
>
<MkEmoji :emoji="emoji.char"/>
</button>
</div>
</template>
<template v-else>
<div v-for="(key, i) in Object.keys(customEmojis)" :key="i">
<header class="sub" v-if="key">{{ key }}</header>
<div class="list">
<button v-for="emoji in customEmojis[key]"
class="_button"
:title="emoji.name"
@click="chosen(emoji)"
:key="emoji.name"
>
<img :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
</button>
</div>
</div>
</template>
</section>
</div>
</div>
</MkModal>
@@ -66,10 +94,11 @@
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 } 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 { groupByX } from '../../prelude/array';
import MkModal from '@/components/ui/modal.vue';
import Particle from '@/components/particle.vue';
import * as os from '@/os';
export default defineComponent({
components: {
@@ -80,6 +109,13 @@ export default defineComponent({
src: {
required: false
},
showPinned: {
required: false,
default: true
},
compact: {
required: false
},
},
emits: ['done', 'closed'],
@@ -88,12 +124,15 @@ export default defineComponent({
return {
emojilist: markRaw(emojilist),
getStaticImageUrl,
customEmojis: {},
faGlobe, faHistory,
pinned: this.$store.state.settings.reactions,
customEmojiCategories: this.$store.getters['instance/emojiCategories'],
customEmojis: this.$store.state.instance.meta.emojis,
visibleCategories: {},
q: null,
searchResultCustom: [],
searchResultUnicode: [],
faGlobe, faClock, faChevronDown,
categories: [{
icon: faAsterisk,
isActive: true
}, {
name: 'face',
icon: faLaugh,
isActive: false
@@ -134,38 +173,212 @@ export default defineComponent({
};
},
created() {
let local = this.$store.state.instance.meta.emojis;
local = groupByX(local, (x: any) => x.category || '');
this.customEmojis = markRaw(local);
watch: {
q() {
if (this.q == null || this.q === '') {
this.searchResultCustom = [];
this.searchResultUnicode = [];
return;
}
const q = this.q.replace(/:/g, '');
const searchCustom = () => {
const max = 8;
const emojis = this.customEmojis;
const matches = new Set();
const exactMatch = emojis.find(e => e.name === q);
if (exactMatch) matches.add(exactMatch);
if (q.includes(' ')) { // AND検索
const keywords = q.split(' ');
// 名前にキーワードが含まれている
for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
// 名前またはエイリアスにキーワードが含まれている
for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
} else {
for (const emoji of emojis) {
if (emoji.name.startsWith(q)) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.aliases.some(alias => alias.startsWith(q))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.name.includes(q)) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.aliases.some(alias => alias.includes(q))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
}
return matches;
};
const searchUnicode = () => {
const max = 8;
const emojis = this.emojilist;
const matches = new Set();
const exactMatch = emojis.find(e => e.name === q);
if (exactMatch) matches.add(exactMatch);
if (q.includes(' ')) { // AND検索
const keywords = q.split(' ');
// 名前にキーワードが含まれている
for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
// 名前またはエイリアスにキーワードが含まれている
for (const emoji of emojis) {
if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.keywords.some(alias => alias.includes(keyword)))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
} else {
for (const emoji of emojis) {
if (emoji.name.startsWith(q)) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.keywords.some(keyword => keyword.startsWith(q))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.name.includes(q)) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
if (matches.size >= max) return matches;
for (const emoji of emojis) {
if (emoji.keywords.some(keyword => keyword.includes(q))) {
matches.add(emoji);
if (matches.size >= max) break;
}
}
}
return matches;
};
this.searchResultCustom = Array.from(searchCustom());
this.searchResultUnicode = Array.from(searchUnicode());
}
},
mounted() {
const isIos = navigator.userAgent.includes('WebKit') && !navigator.userAgent.includes('Chrome');
if (!isIos) {
this.$refs.search.focus({
preventScroll: true
});
}
},
methods: {
go(category: any) {
this.goCategory(category.name);
getKey(emoji: any) {
return typeof emoji === 'string' ? emoji : (emoji.char || `:${emoji.name}:`);
},
goCategory(name: string) {
let matched = false;
for (const c of this.categories) {
c.isActive = c.name === name;
if (c.isActive) {
matched = true;
}
chosen(emoji: any, ev) {
if (ev) {
const el = ev.currentTarget || ev.target;
const rect = el.getBoundingClientRect();
const x = rect.left + (el.clientWidth / 2);
const y = rect.top + (el.clientHeight / 2);
os.popup(Particle, { x, y }, {}, 'end');
}
if (!matched) {
this.categories[0].isActive = true;
}
},
chosen(emoji: any) {
const getKey = (emoji: any) => emoji.char || `:${emoji.name}:`;
let recents = this.$store.state.device.recentEmojis || [];
recents = recents.filter((e: any) => getKey(e) !== getKey(emoji));
recents.unshift(emoji)
this.$store.commit('device/set', { key: 'recentEmojis', value: recents.splice(0, 16) });
this.$emit('done', getKey(emoji));
const key = this.getKey(emoji);
this.$emit('done', key);
this.$refs.modal.close();
// 最近使った絵文字更新
if (!this.pinned.includes(key)) {
let recents = this.$store.state.device.recentlyUsedEmojis;
recents = recents.filter((e: any) => e !== key);
recents.unshift(key);
this.$store.commit('device/set', { key: 'recentlyUsedEmojis', value: recents.splice(0, 16) });
}
},
paste(event) {
const paste = (event.clipboardData || window.clipboardData).getData('text');
if (this.done(paste)) {
event.preventDefault();
}
},
done(query) {
if (query == null) query = this.q;
if (query == null) return;
const q = query.replace(/:/g, '');
const exactMatchCustom = this.customEmojis.find(e => e.name === q);
if (exactMatchCustom) {
this.chosen(exactMatchCustom);
return true;
}
const exactMatchUnicode = this.emojilist.find(e => e.char === q || e.name === q);
if (exactMatchUnicode) {
this.chosen(exactMatchUnicode);
return true;
}
if (this.searchResultCustom.length > 0) {
this.chosen(this.searchResultCustom[0]);
return true;
}
if (this.searchResultUnicode.length > 0) {
this.chosen(this.searchResultUnicode[0]);
return true;
}
},
}
});
@@ -173,87 +386,123 @@ export default defineComponent({
<style lang="scss" scoped>
.omfetrab {
width: 350px;
$eachSize: 40px;
$pad: 8px;
> header {
display: flex;
display: flex;
flex-direction: column;
width: ($eachSize * 7) + ($pad * 2);
contain: content;
--height: 300px;
> button {
flex: 1;
padding: 10px 0;
font-size: 16px;
transition: color 0.2s ease;
&.compact {
width: ($eachSize * 5) + ($pad * 2);
--height: 210px;
}
&:hover {
color: var(--fgHighlighted);
transition: color 0s;
}
> .search {
width: 100%;
padding: 12px;
box-sizing: border-box;
font-size: 1em;
outline: none;
border: none;
background: transparent;
color: var(--fg);
&.active {
color: var(--accent);
transition: color 0s;
}
&:not(.filled) {
order: 1;
z-index: 2;
box-shadow: 0px -1px 0 0px var(--divider);
}
}
> .emojis {
height: 300px;
height: var(--height);
overflow-y: auto;
overflow-x: hidden;
> header.category {
position: sticky;
top: 0;
left: 0;
z-index: 1;
padding: 8px;
background: var(--panel);
font-size: 12px;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
header.sub {
padding: 4px 8px;
font-size: 12px;
}
div.list {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
gap: 4px;
padding: 8px;
> button {
position: relative;
padding: 0;
> .index {
min-height: var(--height);
position: relative;
border-bottom: solid 1px var(--divider);
> .arrow {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 16px 0;
text-align: center;
opacity: 0.5;
pointer-events: none;
}
}
&:before {
content: '';
display: block;
width: 1px;
height: 0;
padding-bottom: 100%;
}
section {
> header {
position: sticky;
top: 0;
left: 0;
z-index: 1;
padding: 8px;
font-size: 12px;
}
> div {
padding: $pad;
> button {
position: relative;
padding: 0;
width: $eachSize;
height: $eachSize;
border-radius: 4px;
&:focus {
outline: solid 2px var(--focus);
z-index: 1;
}
&: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);
}
&:hover {
> * {
transform: scale(1.2);
transition: transform 0s;
font-size: 24px;
height: 1.25em;
vertical-align: -.25em;
pointer-events: none;
}
}
}
> * {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
font-size: 28px;
transition: transform 0.2s ease;
pointer-events: none;
&.result {
border-bottom: solid 1px var(--divider);
&:empty {
display: none;
}
}
&.unicode {
min-height: 384px;
}
&.custom {
min-height: 64px;
}
}
}
}

View File

@@ -2,7 +2,7 @@
<img v-if="customEmoji" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt"/>
<img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" :alt="alt" :title="alt"/>
<span v-else-if="char && useOsNativeEmojis">{{ char }}</span>
<span v-else>:{{ name }}:</span>
<span v-else>{{ emoji }}</span>
</template>
<script lang="ts">
@@ -12,13 +12,9 @@ import { twemojiSvgBase } from '../../misc/twemoji-base';
export default defineComponent({
props: {
name: {
type: String,
required: false
},
emoji: {
type: String,
required: false
required: true
},
normal: {
type: Boolean,
@@ -49,6 +45,10 @@ export default defineComponent({
},
computed: {
isCustom(): boolean {
return this.emoji.startsWith(':');
},
alt(): string {
return this.customEmoji ? `:${this.customEmoji.name}:` : this.char;
},
@@ -68,8 +68,8 @@ export default defineComponent({
watch: {
ce: {
handler() {
if (this.name) {
const customEmoji = this.ce.find(x => x.name == this.name);
if (this.isCustom) {
const customEmoji = this.ce.find(x => x.name === this.emoji.substr(1, this.emoji.length - 2));
if (customEmoji) {
this.customEmoji = customEmoji;
this.url = this.$store.state.device.disableShowingAnimatedImages
@@ -83,7 +83,7 @@ export default defineComponent({
},
created() {
if (!this.name) {
if (!this.isCustom) {
this.char = this.emoji;
}

View File

@@ -15,15 +15,15 @@
<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">
<span v-text="form[item].label || item"></span>
<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>
</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>
<MkInput 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>
</MkInput>
<MkTextarea v-else-if="form[item].type === 'string' && item.multiline" v-model:value="values[item]">
<span v-text="form[item].label || item"></span>
<MkTextarea 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>
</MkTextarea>
<MkSwitch v-else-if="form[item].type === 'boolean'" v-model:value="values[item]">

View File

@@ -41,10 +41,13 @@ export default defineComponent({
<style lang="scss" scoped>
.xubzgfga {
max-width: 1024px;
display: flex;
flex-direction: column;
height: 100%;
> header,
> footer {
align-self: center;
display: inline-block;
padding: 6px 9px;
font-size: 90%;
@@ -60,7 +63,10 @@ export default defineComponent({
> img {
display: block;
max-width: 100%;
flex: 1;
min-height: 0;
object-fit: contain;
width: 100%;
cursor: zoom-out;
image-orientation: from-image;
}

View File

@@ -77,10 +77,66 @@ export default defineComponent({
}, genEl(token.children));
}
case 'big': {
return h('strong', {
style: `display: inline-block; font-size: 150%;` + (this.$store.state.device.animatedMfm ? 'animation: anime-tada 1s linear infinite both;' : ''),
}, genEl(token.children));
case 'fn': {
// TODO: CSSを文字列で組み立てていくと token.node.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる
let style;
switch (token.node.props.name) {
case 'tada': {
style = `font-size: 150%;` + (this.$store.state.device.animatedMfm ? 'animation: tada 1s linear infinite both;' : '');
break;
}
case 'jelly': {
const speed = token.node.props.args.speed || '1s';
style = (this.$store.state.device.animatedMfm ? `animation: mfm-rubberBand ${speed} linear infinite both;` : '');
break;
}
case 'twitch': {
const speed = token.node.props.args.speed || '0.5s';
style = this.$store.state.device.animatedMfm ? `animation: mfm-twitch ${speed} ease infinite;` : '';
break;
}
case 'shake': {
const speed = token.node.props.args.speed || '0.5s';
style = this.$store.state.device.animatedMfm ? `animation: mfm-shake ${speed} ease infinite;` : '';
break;
}
case 'spin': {
const direction =
token.node.props.args.left ? 'reverse' :
token.node.props.args.alternate ? 'alternate' :
'normal';
const anime =
token.node.props.args.x ? 'mfm-spinX' :
token.node.props.args.y ? 'mfm-spinY' :
'mfm-spin';
const speed = token.node.props.args.speed || '1.5s';
style = this.$store.state.device.animatedMfm ? `animation: ${anime} ${speed} linear infinite; animation-direction: ${direction};` : '';
break;
}
case 'jump': {
style = this.$store.state.device.animatedMfm ? 'animation: mfm-jump 0.75s linear infinite;' : '';
break;
}
case 'bounce': {
style = this.$store.state.device.animatedMfm ? 'animation: mfm-bounce 0.75s linear infinite; transform-origin: center bottom;' : '';
break;
}
case 'flip': {
const transform =
(token.node.props.args.h && token.node.props.args.v) ? 'scale(-1, -1)' :
token.node.props.args.v ? 'scaleY(-1)' :
'scaleX(-1)';
style = `transform: ${transform};`;
break;
}
}
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': {
@@ -95,48 +151,6 @@ export default defineComponent({
}, genEl(token.children))];
}
case 'motion': {
return h('span', {
style: 'display: inline-block;' + (this.$store.state.device.animatedMfm ? 'animation: anime-rubberBand 1s linear infinite both;' : ''),
}, genEl(token.children));
}
case 'spin': {
const direction =
token.node.props.attr == 'left' ? 'reverse' :
token.node.props.attr == 'alternate' ? 'alternate' :
'normal';
const style = this.$store.state.device.animatedMfm
? `animation: anime-spin 1.5s linear infinite; animation-direction: ${direction};` : '';
return h('span', {
style: 'display: inline-block;' + style
}, genEl(token.children));
}
case 'jump': {
return h('span', {
style: this.$store.state.device.animatedMfm ? 'display: inline-block; animation: anime-jump 0.75s linear infinite;' : 'display: inline-block;'
}, genEl(token.children));
}
case 'flip': {
return h('span', {
style: 'display: inline-block; transform: scaleX(-1);'
}, genEl(token.children));
}
case 'twitch': {
return h('span', {
style: this.$store.state.device.animatedMfm ? 'display: inline-block; animation: anime-twitch 0.5s ease infinite;' : 'display: inline-block;'
}, genEl(token.children));
}
case 'shake': {
return h('span', {
style: this.$store.state.device.animatedMfm ? 'display: inline-block; animation: anime-shake 0.5s ease infinite;' : 'display: inline-block;'
}, genEl(token.children));
}
case 'url': {
return [h(MkUrl, {
key: Math.random(),
@@ -198,17 +212,10 @@ export default defineComponent({
}
}
case 'title': {
return [h('div', {
class: 'title'
}, genEl(token.children))];
}
case 'emoji': {
return [h(MkEmoji, {
key: Math.random(),
emoji: token.node.props.emoji,
name: token.node.props.name,
emoji: token.node.props.name ? `:${token.node.props.name}:` : token.node.props.emoji,
customEmojis: this.customEmojis,
normal: this.plain
})];

View File

@@ -13,6 +13,103 @@ export default defineComponent({
});
</script>
<style lang="scss">
@keyframes mfm-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes mfm-spinX {
0% { transform: perspective(128px) rotateX(0deg); }
100% { transform: perspective(128px) rotateX(360deg); }
}
@keyframes mfm-spinY {
0% { transform: perspective(128px) rotateY(0deg); }
100% { transform: perspective(128px) rotateY(360deg); }
}
@keyframes mfm-jump {
0% { transform: translateY(0); }
25% { transform: translateY(-16px); }
50% { transform: translateY(0); }
75% { transform: translateY(-8px); }
100% { transform: translateY(0); }
}
@keyframes mfm-bounce {
0% { transform: translateY(0) scale(1, 1); }
25% { transform: translateY(-16px) scale(1, 1); }
50% { transform: translateY(0) scale(1, 1); }
75% { transform: translateY(0) scale(1.5, 0.75); }
100% { transform: translateY(0) scale(1, 1); }
}
// const val = () => `translate(${Math.floor(Math.random() * 20) - 10}px, ${Math.floor(Math.random() * 20) - 10}px)`;
// let css = '';
// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; }
@keyframes mfm-twitch {
0% { transform: translate(7px, -2px) }
5% { transform: translate(-3px, 1px) }
10% { transform: translate(-7px, -1px) }
15% { transform: translate(0px, -1px) }
20% { transform: translate(-8px, 6px) }
25% { transform: translate(-4px, -3px) }
30% { transform: translate(-4px, -6px) }
35% { transform: translate(-8px, -8px) }
40% { transform: translate(4px, 6px) }
45% { transform: translate(-3px, 1px) }
50% { transform: translate(2px, -10px) }
55% { transform: translate(-7px, 0px) }
60% { transform: translate(-2px, 4px) }
65% { transform: translate(3px, -8px) }
70% { transform: translate(6px, 7px) }
75% { transform: translate(-7px, -2px) }
80% { transform: translate(-7px, -8px) }
85% { transform: translate(9px, 3px) }
90% { transform: translate(-3px, -2px) }
95% { transform: translate(-10px, 2px) }
100% { transform: translate(-2px, -6px) }
}
// const val = () => `translate(${Math.floor(Math.random() * 6) - 3}px, ${Math.floor(Math.random() * 6) - 3}px) rotate(${Math.floor(Math.random() * 24) - 12}deg)`;
// let css = '';
// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; }
@keyframes mfm-shake {
0% { transform: translate(-3px, -1px) rotate(-8deg) }
5% { transform: translate(0px, -1px) rotate(-10deg) }
10% { transform: translate(1px, -3px) rotate(0deg) }
15% { transform: translate(1px, 1px) rotate(11deg) }
20% { transform: translate(-2px, 1px) rotate(1deg) }
25% { transform: translate(-1px, -2px) rotate(-2deg) }
30% { transform: translate(-1px, 2px) rotate(-3deg) }
35% { transform: translate(2px, 1px) rotate(6deg) }
40% { transform: translate(-2px, -3px) rotate(-9deg) }
45% { transform: translate(0px, -1px) rotate(-12deg) }
50% { transform: translate(1px, 2px) rotate(10deg) }
55% { transform: translate(0px, -3px) rotate(8deg) }
60% { transform: translate(1px, -1px) rotate(8deg) }
65% { transform: translate(0px, -1px) rotate(-7deg) }
70% { transform: translate(-1px, -3px) rotate(6deg) }
75% { transform: translate(0px, -2px) rotate(4deg) }
80% { transform: translate(-2px, -1px) rotate(3deg) }
85% { transform: translate(1px, -3px) rotate(-10deg) }
90% { transform: translate(1px, 0px) rotate(3deg) }
95% { transform: translate(-2px, 0px) rotate(-3deg) }
100% { transform: translate(2px, 1px) rotate(2deg) }
}
@keyframes mfm-rubberBand {
from { transform: scale3d(1, 1, 1); }
30% { transform: scale3d(1.25, 0.75, 1); }
40% { transform: scale3d(0.75, 1.25, 1); }
50% { transform: scale3d(1.15, 0.85, 1); }
65% { transform: scale3d(0.95, 1.05, 1); }
75% { transform: scale3d(1.05, 0.95, 1); }
to { transform: scale3d(1, 1, 1); }
}
</style>
<style lang="scss" scoped>
.havbbuyv {
white-space: pre-wrap;
@@ -42,10 +139,5 @@ export default defineComponent({
word-break: break-all;
padding: 4px 6px;
}
::v-deep(.title) {
text-align: center;
border-bottom: solid 1px var(--divider);
}
}
</style>

View File

@@ -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,9 +498,9 @@ export default defineComponent({
react(viaKeyboard = false) {
pleaseLogin();
this.blur();
os.popup(import('@/components/reaction-picker.vue'), {
showFocus: viaKeyboard,
os.popup(import('@/components/emoji-picker.vue'), {
src: this.$refs.reactButton,
compact: !this.$store.state.device.useFullReactionPicker
}, {
done: reaction => {
if (reaction) {
@@ -610,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'),
@@ -762,6 +767,43 @@ 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')
}
});
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'),

View File

@@ -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 {

View File

@@ -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: {

View 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>

View File

@@ -1,9 +1,9 @@
<template>
<MkEmoji :emoji="reaction.startsWith(':') ? null : reaction" :name="reaction.startsWith(':') ? reaction.substr(1, reaction.length - 2) : null" :customEmojis="customEmojis" :is-reaction="true" :normal="true" :no-style="noStyle"/>
<MkEmoji :emoji="reaction" :custom-emojis="customEmojis" :is-reaction="true" :normal="true" :no-style="noStyle"/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';import * as os from '@/os';
import { defineComponent } from 'vue';
export default defineComponent({
props: {

View File

@@ -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>

View File

@@ -121,7 +121,11 @@ export default defineComponent({
return this.window();
}
this.$router.push(this.to);
if (this.$router.currentRoute.value.path === this.to) {
window.scroll({ top: 0, behavior: 'smooth' });
} else {
this.$router.push(this.to);
}
}
}
}

View File

@@ -23,11 +23,11 @@
<span>{{ item.text }}</span>
<i v-if="item.indicate"><Fa :icon="faCircle"/></i>
</a>
<button v-else-if="item.type === 'user'" @click="clicked(item.action)" :tabindex="i" class="_button item">
<button v-else-if="item.type === 'user'" @click="clicked(item.action, $event)" :tabindex="i" class="_button item">
<MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/>
<i v-if="item.indicate"><Fa :icon="faCircle"/></i>
</button>
<button v-else @click="clicked(item.action)" :tabindex="i" class="_button item" :class="{ danger: item.danger }">
<button v-else @click="clicked(item.action, $event)" :tabindex="i" class="_button item" :class="{ danger: item.danger }">
<Fa v-if="item.icon" :icon="item.icon" fixed-width/>
<MkAvatar v-if="item.avatar" :user="item.avatar" class="avatar"/>
<span>{{ item.text }}</span>
@@ -115,8 +115,8 @@ export default defineComponent({
}
},
methods: {
clicked(fn) {
fn();
clicked(fn, ev) {
fn(ev);
this.close();
},
close() {

View File

@@ -1,5 +1,5 @@
<template>
<div class="cxiknjgy" :class="{ autoMargin }">
<div class="cxiknjgy">
<slot :items="items"></slot>
<div class="empty" v-if="empty" key="_empty_">
<slot name="empty"></slot>
@@ -31,24 +31,12 @@ export default defineComponent({
pagination: {
required: true
},
autoMargin: {
required: false,
default: true
}
},
});
</script>
<style lang="scss" scoped>
.cxiknjgy {
&.autoMargin > *:not(:last-child) {
margin-bottom: 16px;
@media (max-width: 500px) {
margin-bottom: 8px;
}
}
> .more > .button {
margin-left: auto;
margin-right: auto;

View File

@@ -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;

View File

@@ -0,0 +1,58 @@
<script lang="ts">
import { defineComponent, h } from 'vue';
import MkRadio from '@/components/ui/radio.vue';
export default defineComponent({
components: {
MkRadio
},
props: {
defs: {
required: true
},
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" scoped>
.novjtcto {
margin: 32px 0;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
</style>

View File

@@ -14,10 +14,10 @@
<MkInput v-model:value="host" class="input" @update:value="search"><span>{{ $t('host') }}</span><template #prefix>@</template></MkInput>
</div>
</div>
<div class="tbhwbxda _section" :style="users.length > 0 ? 'padding: 0;' : ''">
<div class="tbhwbxda _section result" v-if="username != '' || host != ''" :class="{ hit: users.length > 0 }">
<div class="users" v-if="users.length > 0">
<div class="user" v-for="user in users" :key="user.id" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()">
<MkAvatar :user="user" class="avatar" :disable-link="true"/>
<MkAvatar :user="user" class="avatar"/>
<div class="body">
<MkUserName :user="user" class="name"/>
<MkAcct :user="user" class="acct"/>
@@ -28,6 +28,17 @@
<span>{{ $t('noUsers') }}</span>
</div>
</div>
<div class="tbhwbxda _section recent" v-if="username == '' && host == ''">
<div class="users">
<div class="user" v-for="user in recentUsers" :key="user.id" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()">
<MkAvatar :user="user" class="avatar"/>
<div class="body">
<MkUserName :user="user" class="name"/>
<MkAcct :user="user" class="acct"/>
</div>
</div>
</div>
</div>
</XModalWindow>
</template>
@@ -53,18 +64,23 @@ export default defineComponent({
return {
username: '',
host: '',
recentUsers: [],
users: [],
selected: null,
faTimes, faCheck
};
},
mounted() {
async mounted() {
this.focus();
this.$nextTick(() => {
this.focus();
});
this.recentUsers = await os.api('users/show', {
userIds: this.$store.state.device.recentlyUsedUsers
});
},
methods: {
@@ -90,6 +106,12 @@ export default defineComponent({
ok() {
this.$emit('ok', this.selected);
this.$refs.dialog.close();
// 最近使ったユーザー更新
let recents = this.$store.state.device.recentlyUsedUsers;
recents = recents.filter(x => x !== this.selected.id);
recents.unshift(this.selected.id);
this.$store.commit('device/set', { key: 'recentlyUsedUsers', value: recents.splice(0, 16) });
},
cancel() {
@@ -107,6 +129,14 @@ export default defineComponent({
overflow: auto;
height: 100%;
&.result.hit {
padding: 0;
}
&.recent {
padding: 0;
}
> .inputs {
> .input {
display: inline-block;

View File

@@ -275,10 +275,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);

View File

@@ -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>

View File

@@ -1,7 +1,7 @@
<template>
<div class="_section">
<MkPagination :pagination="pagination" #default="{items}" class="ruryvtyk _content" ref="list">
<section class="_card announcement" v-for="(announcement, i) in items" :key="announcement.id">
<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">
<Mfm :text="announcement.text"/>

154
src/client/pages/clip.vue Normal file
View 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>

View File

@@ -33,7 +33,7 @@
</div>
-->
<MkPagination :pagination="pagination" #default="{items}" ref="reports" :auto-margin="false" style="margin-top: var(--margin);">
<MkPagination :pagination="pagination" #default="{items}" ref="reports" style="margin-top: var(--margin);">
<div class="bcekxzvu _card _vMargin" v-for="report in items" :key="report.id">
<div class="_content target">
<MkAvatar class="avatar" :user="report.targetUser"/>

View File

@@ -34,7 +34,7 @@
<span>{{ $t('type') }}</span>
</MkInput>
</div>
<MkPagination :pagination="pagination" #default="{items}" class="urempief" ref="files" :auto-margin="false">
<MkPagination :pagination="pagination" #default="{items}" class="urempief" ref="files">
<button class="file _panel _button _vMargin" v-for="file in items" :key="file.id" @click="show(file, $event)">
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
<div class="body">

View File

@@ -52,7 +52,7 @@
</MkInput>
</div>
<MkPagination :pagination="pagination" #default="{items}" class="users" ref="users" :auto-margin="false">
<MkPagination :pagination="pagination" #default="{items}" class="users" ref="users">
<button class="user _panel _button _vMargin" v-for="user in items" :key="user.id" @click="show(user)">
<MkAvatar class="avatar" :user="user" :disable-link="true"/>
<div class="body">

View File

@@ -0,0 +1,269 @@
<template>
<div class="mwysmxbg">
<div class="_section">
<div class="_content">
<p>{{ $t('_mfm.intro') }}</p>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.mention') }}</div>
<div class="_content">
<p>{{ $t('_mfm.mentionDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_mention"/>
<MkTextarea v-model:value="preview_mention"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.hashtag') }}</div>
<div class="_content">
<p>{{ $t('_mfm.hashtagDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_hashtag"/>
<MkTextarea v-model:value="preview_hashtag"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.url') }}</div>
<div class="_content">
<p>{{ $t('_mfm.urlDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_url"/>
<MkTextarea v-model:value="preview_url"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.link') }}</div>
<div class="_content">
<p>{{ $t('_mfm.linkDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_link"/>
<MkTextarea v-model:value="preview_link"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.emoji') }}</div>
<div class="_content">
<p>{{ $t('_mfm.emojiDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_emoji"/>
<MkTextarea v-model:value="preview_emoji"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.bold') }}</div>
<div class="_content">
<p>{{ $t('_mfm.boldDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_bold"/>
<MkTextarea v-model:value="preview_bold"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.small') }}</div>
<div class="_content">
<p>{{ $t('_mfm.smallDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_small"/>
<MkTextarea v-model:value="preview_small"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.quote') }}</div>
<div class="_content">
<p>{{ $t('_mfm.quoteDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_quote"/>
<MkTextarea v-model:value="preview_quote"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.center') }}</div>
<div class="_content">
<p>{{ $t('_mfm.centerDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_center"/>
<MkTextarea v-model:value="preview_center"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.inlineCode') }}</div>
<div class="_content">
<p>{{ $t('_mfm.inlineCodeDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_inlineCode"/>
<MkTextarea v-model:value="preview_inlineCode"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.blockCode') }}</div>
<div class="_content">
<p>{{ $t('_mfm.blockCodeDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_blockCode"/>
<MkTextarea v-model:value="preview_blockCode"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.inlineMath') }}</div>
<div class="_content">
<p>{{ $t('_mfm.inlineMathDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_inlineMath"/>
<MkTextarea v-model:value="preview_inlineMath"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.search') }}</div>
<div class="_content">
<p>{{ $t('_mfm.searchDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_search"/>
<MkTextarea v-model:value="preview_search"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.flip') }}</div>
<div class="_content">
<p>{{ $t('_mfm.flipDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_flip"/>
<MkTextarea v-model:value="preview_flip"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.jelly') }}</div>
<div class="_content">
<p>{{ $t('_mfm.jellyDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_jelly"/>
<MkTextarea v-model:value="preview_jelly"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.tada') }}</div>
<div class="_content">
<p>{{ $t('_mfm.tadaDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_tada"/>
<MkTextarea v-model:value="preview_tada"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.jump') }}</div>
<div class="_content">
<p>{{ $t('_mfm.jumpDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_jump"/>
<MkTextarea v-model:value="preview_jump"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.bounce') }}</div>
<div class="_content">
<p>{{ $t('_mfm.bounceDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_bounce"/>
<MkTextarea v-model:value="preview_bounce"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.spin') }}</div>
<div class="_content">
<p>{{ $t('_mfm.spinDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_spin"/>
<MkTextarea v-model:value="preview_spin"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.shake') }}</div>
<div class="_content">
<p>{{ $t('_mfm.shakeDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_shake"/>
<MkTextarea v-model:value="preview_shake"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="_section">
<div class="_title">{{ $t('_mfm.twitch') }}</div>
<div class="_content">
<p>{{ $t('_mfm.twitchDescription') }}</p>
<div class="preview _panel">
<Mfm :text="preview_twitch"/>
<MkTextarea v-model:value="preview_twitch"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faQuestionCircle } from '@fortawesome/free-regular-svg-icons';
import MkTextarea from '@/components/ui/textarea.vue';
export default defineComponent({
components: {
MkTextarea
},
data() {
return {
INFO: {
title: this.$t('_mfm.cheatSheet'),
icon: faQuestionCircle,
},
preview_mention: '@example',
preview_hashtag: '#test',
preview_url: `https://example.com`,
preview_link: `[${this.$t('_mfm.dummy')}](https://example.com)`,
preview_emoji: `:${this.$store.state.instance.meta.emojis[0].name}:`,
preview_bold: `**${this.$t('_mfm.dummy')}**`,
preview_small: `<small>${this.$t('_mfm.dummy')}</small>`,
preview_center: `<center>${this.$t('_mfm.dummy')}</center>`,
preview_inlineCode: '`<: "Hello, world!"`',
preview_blockCode: '```\n~ (#i, 100) {\n\t<: ? ((i % 15) = 0) "FizzBuzz"\n\t\t.? ((i % 3) = 0) "Fizz"\n\t\t.? ((i % 5) = 0) "Buzz"\n\t\t. i\n}\n```',
preview_inlineMath: '\\(x= \\frac{-b\' \\pm \\sqrt{(b\')^2-ac}}{a}\\)',
preview_quote: `> ${this.$t('_mfm.dummy')}`,
preview_search: `${this.$t('_mfm.dummy')} 検索`,
preview_jelly: `[jelly 🍮]`,
preview_tada: `[tada 🍮]`,
preview_jump: `[jump 🍮]`,
preview_bounce: `[bounce 🍮]`,
preview_shake: `[shake 🍮]`,
preview_twitch: `[twitch 🍮]`,
preview_spin: `[spin 🍮] [spin.left 🍮] [spin.alternate 🍮]\n[spin.x 🍮] [spin.x,left 🍮] [spin.x,alternate 🍮]\n[spin.y 🍮] [spin.y,left 🍮] [spin.y,alternate 🍮]`,
preview_flip: `[flip ${this.$t('_mfm.dummy')}]\n[flip.v ${this.$t('_mfm.dummy')}]\n[flip.h,v ${this.$t('_mfm.dummy')}]`,
}
},
});
</script>
<style lang="scss" scoped>
.mwysmxbg {
.preview {
padding: 16px;
}
}
</style>

View File

@@ -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>

View File

@@ -0,0 +1,104 @@
<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')
}
});
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>

View 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>

View File

@@ -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: {

View File

@@ -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
};
@@ -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;
});
},

View File

@@ -11,7 +11,7 @@
<Mfm :key="'past-turn-of:' + turnUser().name" :text="$t('_reversi.pastTurnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/>
</p>
<p class="turn1" v-if="iAmPlayer && !game.isEnded && !isMyTurn()">{{ $t('_reversi.opponentTurn') }}<MkEllipsis/></p>
<p class="turn2" v-if="iAmPlayer && !game.isEnded && isMyTurn()" style="animation: anime-tada 1s linear infinite both;">{{ $t('_reversi.myTurn') }}</p>
<p class="turn2" v-if="iAmPlayer && !game.isEnded && isMyTurn()" style="animation: tada 1s linear infinite both;">{{ $t('_reversi.myTurn') }}</p>
<p class="result" v-if="game.isEnded && logPos == logs.length">
<template v-if="game.winner">
<Mfm :key="'won'" :text="$t('_reversi.won', { name: game.winner.name })" :plain="true" :custom-emojis="game.winner.emojis"/>

View File

@@ -1,38 +1,40 @@
<template>
<div class="_section">
<div class="">
<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">
<MkRadios v-model="serverDisconnectedBehavior">
<template #desc>{{ $t('whenServerDisconnected') }}</template>
<option value="reload">{{ $t('_serverDisconnectedBehavior.reload') }}</option>
<option value="dialog">{{ $t('_serverDisconnectedBehavior.dialog') }}</option>
<option value="quiet">{{ $t('_serverDisconnectedBehavior.quiet') }}</option>
</MkRadios>
<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>
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
</MkSelect>
</div>
</section>
<section class="_card _vMargin">
<div class="_title"><Fa :icon="faCog"/> {{ $t('defaultNavigationBehaviour') }}</div>
<div class="_content">
<MkSwitch v-model:value="defaultSideView">{{ $t('openInSideView') }}</MkSwitch>
</div>
<div class="_content">
<MkRadios v-model="chatOpenBehavior">
<template #desc>{{ $t('chatOpenBehavior') }}</template>
<option value="page">{{ $t('showInPage') }}</option>
<option value="window">{{ $t('openInWindow') }}</option>
<option value="popout">{{ $t('popout') }}</option>
</MkRadios>
</div>
</section>
<section class="_card _vMargin">
<div class="_title"><Fa :icon="faCog"/> {{ $t('appearance') }}</div>
<div class="_content">
@@ -43,19 +45,19 @@
{{ $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>
<MkRadios 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>
</MkRadios>
<MkRadios v-model="instanceTicker">
<template #desc>{{ $t('instanceTicker') }}</template>
<option value="none">{{ $t('_instanceTicker.none') }}</option>
<option value="remote">{{ $t('_instanceTicker.remote') }}</option>
<option value="always">{{ $t('_instanceTicker.always') }}</option>
</MkRadios>
</div>
</section>
@@ -88,6 +90,7 @@ 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 { langs } from '@/config';
import { clientDb, set } from '@/db';
@@ -99,6 +102,7 @@ export default defineComponent({
MkSwitch,
MkSelect,
MkRadio,
MkRadios,
MkRange,
},

View File

@@ -31,9 +31,7 @@
</div>
</div>
<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>
@@ -109,14 +107,6 @@ export default defineComponent({
</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 {
@@ -133,7 +123,7 @@ export default defineComponent({
width: 100%;
box-sizing: border-box;
padding: 0 32px;
line-height: 48px;
line-height: 40px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -168,10 +158,22 @@ export default defineComponent({
> .nav {
width: 30%;
max-width: 300px;
font-size: 0.95em;
border-right: solid 1px var(--divider);
}
> .main {
flex: 1;
padding: 32px;
--baseContentWidth: 100%;
::v-deep(._section) {
padding: 0 0 32px 0;
& + ._section {
padding-top: 32px;
}
}
}
}
}

View File

@@ -3,15 +3,6 @@
<div class="_section">
<MkButton full primary @click="configure"><Fa :icon="faCog"/> {{ $t('notificationSetting') }}</MkButton>
</div>
<div class="_section">
<div class="_card">
<div class="_content">
<MkSwitch v-model:value="$store.state.i.autoWatch" @update:value="onChangeAutoWatch">
{{ $t('autoNoteWatch') }}<template #desc>{{ $t('autoNoteWatchDescription') }}</template>
</MkSwitch>
</div>
</div>
</div>
<div class="_section">
<MkButton full @click="readAllNotifications">{{ $t('markAsReadAllNotifications') }}</MkButton>
<MkButton full @click="readAllUnreadNotes">{{ $t('markAsReadAllUnreadNotes') }}</MkButton>
@@ -52,12 +43,6 @@ export default defineComponent({
},
methods: {
onChangeAutoWatch(v) {
os.api('i/update', {
autoWatch: v
});
},
readAllUnreadNotes() {
os.api('i/read-all-unread-notes');
},

View File

@@ -3,14 +3,21 @@
<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>
<div class="_caption" style="padding: 0 8px 8px 8px;">{{ $t('reactionSettingDescription') }}</div>
<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 class="_caption" style="padding: 8px;">{{ $t('reactionSettingDescription2') }} <button class="_textButton" @click="chooseEmoji">{{ $t('chooseEmoji') }}</button></div>
<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>
<MkButton inline @click="setDefault"><Fa :icon="faUndo"/> {{ $t('default') }}</MkButton>
</div>
</div>
</div>
@@ -20,8 +27,10 @@
import { defineComponent } from 'vue';
import { faLaugh, faSave, faEye } from '@fortawesome/free-regular-svg-icons';
import { faUndo } from '@fortawesome/free-solid-svg-icons';
import { VueDraggableNext } from 'vue-draggable-next';
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 { defaultSettings } from '@/store';
import * as os from '@/os';
@@ -30,6 +39,8 @@ export default defineComponent({
components: {
MkInput,
MkButton,
MkSwitch,
XDraggable: VueDraggableNext,
},
emits: ['info'],
@@ -40,22 +51,22 @@ export default defineComponent({
title: this.$t('reaction'),
icon: faLaugh
},
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 }); }
},
},
watch: {
reactions: {
handler() {
this.changed = true;
this.save();
},
deep: true
}
@@ -67,27 +78,59 @@ 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) {
os.popup(import('@/components/reaction-picker.vue'), {
reactions: this.splited,
showFocus: false,
os.popup(import('@/components/emoji-picker.vue'), {
compact: !this.$store.state.device.useFullReactionPicker,
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 {
border: solid 1px var(--divider);
border-radius: var(--radius);
padding: 16px;
> .item {
display: inline-block;
padding: 8px;
cursor: move;
}
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div class="_section">
<div class="">
<div class="rfqxtzch _card _vMargin">
<div class="_content">
<div class="darkMode" :class="{ disabled: syncDeviceDarkMode }">
@@ -22,8 +22,12 @@
</label>
</div>
</div>
</div>
<div class="_content">
<MkSwitch v-model:value="syncDeviceDarkMode">{{ $t('syncDeviceDarkMode') }}</MkSwitch>
</div>
</div>
<div class="_card _vMargin">
<div class="_content">
<MkSelect v-model:value="lightTheme">
<template #label>{{ $t('themeForLightMode') }}</template>

View File

@@ -162,7 +162,7 @@ export default defineComponent({
dialogCancelByBgClick: true,
dialogInput: false,
dialogResult: null,
formTitle: null,
formTitle: 'Test form',
formForm: JSON.stringify({
foo: {
type: 'boolean',
@@ -179,6 +179,12 @@ export default defineComponent({
default: 'Misskey makes you happy.',
label: 'This is a string property'
},
qux: {
type: 'string',
multiline: true,
default: 'Misskey makes\nyou happy.',
label: 'Multiline string'
},
}, null, '\t'),
formResult: null,
mfm: '',

View File

@@ -36,7 +36,8 @@ export const router = createRouter({
{ path: '/channels', component: page('channels') },
{ path: '/channels/new', component: page('channel-editor') },
{ path: '/channels/:channelId/edit', component: page('channel-editor'), props: true },
{ path: '/channels/:channelId', component: page('channel'), props: true },
{ path: '/channels/:channelId', component: page('channel'), props: route => ({ channelId: route.params.channelId }) },
{ path: '/clips/:clipId', component: page('clip'), props: route => ({ clipId: route.params.clipId }) },
{ path: '/my/notifications', component: page('notifications') },
{ path: '/my/favorites', component: page('favorites') },
{ path: '/my/messages', component: page('messages') },
@@ -55,6 +56,7 @@ export const router = createRouter({
{ path: '/my/groups', component: page('my-groups/index') },
{ path: '/my/groups/:group', component: page('my-groups/group') },
{ path: '/my/antennas', component: page('my-antennas/index') },
{ path: '/my/clips', component: page('my-clips/index') },
{ path: '/my/apps', component: page('apps') },
{ path: '/scratchpad', component: page('scratchpad') },
{ path: '/instance', component: page('instance/index') },
@@ -72,7 +74,9 @@ export const router = createRouter({
{ path: '/tags/:tag', component: page('tag'), props: route => ({ tag: route.params.tag }) },
{ path: '/games/reversi', component: page('reversi/index') },
{ path: '/games/reversi/:gameId', component: page('reversi/game'), props: route => ({ gameId: route.params.gameId }) },
{ path: '/mfm-cheat-sheet', component: page('mfm-cheat-sheet') },
{ path: '/api-console', component: page('api-console') },
{ path: '/test', component: page('test') },
{ path: '/auth/:token', component: page('auth') },
{ path: '/miauth/:session', component: page('miauth') },
{ path: '/authorize-follow', component: page('follow') },

View File

@@ -1,5 +1,5 @@
import { faBell, faComments, faEnvelope } from '@fortawesome/free-regular-svg-icons';
import { faAt, faBroadcastTower, faCloud, faColumns, faDoorClosed, faFileAlt, faFireAlt, faGamepad, faHashtag, faListUl, faSatellite, faSatelliteDish, faSearch, faStar, faTerminal, faUserClock, faUsers } from '@fortawesome/free-solid-svg-icons';
import { faAt, faBroadcastTower, faCloud, faColumns, faDoorClosed, faFileAlt, faFireAlt, faGamepad, faHashtag, faListUl, faPaperclip, faSatellite, faSatelliteDish, faSearch, faStar, faTerminal, faUserClock, faUsers } from '@fortawesome/free-solid-svg-icons';
import { computed } from 'vue';
import { store } from '@/store';
import { search } from '@/scripts/search';
@@ -99,6 +99,12 @@ export const sidebarDef = {
show: computed(() => store.getters.isSignedIn),
to: '/my/pages',
},
clips: {
title: 'clip',
icon: faPaperclip,
show: computed(() => store.getters.isSignedIn),
to: '/my/clips',
},
channels: {
title: 'channel',
icon: faSatelliteDish,

View File

@@ -59,7 +59,8 @@ export const defaultDeviceSettings = {
useOsNativeEmojis: false,
serverDisconnectedBehavior: 'quiet',
accounts: [],
recentEmojis: [],
recentlyUsedEmojis: [],
recentlyUsedUsers: [],
themes: [],
darkTheme: '8050783a-7f63-445a-b270-36d0f6ba1677',
lightTheme: '4eea646f-7afa-4645-83e9-83af0333cd37',
@@ -76,6 +77,7 @@ export const defaultDeviceSettings = {
disablePagesScript: false,
enableInfiniteScroll: true,
useBlurEffectForModal: true,
useFullReactionPicker: false,
sidebarDisplay: 'full', // full, icon, hide
instanceTicker: 'remote', // none, remote, always
roomGraphicsQuality: 'medium',
@@ -182,6 +184,16 @@ export const store = createStore({
meta: null
},
getters: {
emojiCategories: state => {
const categories = new Set();
for (const emoji of state.meta.emojis) {
categories.add(emoji.category);
}
return Array.from(categories);
},
},
mutations: {
set(state, meta) {
state.meta = meta;
@@ -241,7 +253,7 @@ export const store = createStore({
init(state, x) {
for (const [key, value] of Object.entries(defaultDeviceUserSettings)) {
if (x[key]) {
if (Object.prototype.hasOwnProperty.call(x, key)) {
state[key] = x[key];
} else {
state[key] = value;
@@ -459,7 +471,7 @@ export const store = createStore({
init(state, x) {
for (const [key, value] of Object.entries(defaultSettings)) {
if (x[key]) {
if (Object.prototype.hasOwnProperty.call(x, key)) {
state[key] = x[key];
} else {
state[key] = value;

View File

@@ -244,7 +244,7 @@ hr {
> ._title {
margin: 0;
padding: 22px 32px;
font-size: 1.1em;
font-size: 1em;
border-bottom: solid 1px var(--panelHeaderDivider);
font-weight: bold;
background: var(--panelHeaderBg);
@@ -346,7 +346,6 @@ hr {
> ._title {
margin-bottom: 24px;
font-size: 1.25em;
font-weight: bold;
}
@@ -486,74 +485,7 @@ hr {
90% { opacity: 0; transform: scale(0.5); }
}
@keyframes anime-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes anime-jump {
0% { transform: translateY(0); }
25% { transform: translateY(-16px); }
50% { transform: translateY(0); }
75% { transform: translateY(-8px); }
100% { transform: translateY(0); }
}
// const val = () => `translate(${Math.floor(Math.random() * 20) - 10}px, ${Math.floor(Math.random() * 20) - 10}px)`;
// let css = '';
// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; }
@keyframes anime-twitch {
0% { transform: translate(7px, -2px) }
5% { transform: translate(-3px, 1px) }
10% { transform: translate(-7px, -1px) }
15% { transform: translate(0px, -1px) }
20% { transform: translate(-8px, 6px) }
25% { transform: translate(-4px, -3px) }
30% { transform: translate(-4px, -6px) }
35% { transform: translate(-8px, -8px) }
40% { transform: translate(4px, 6px) }
45% { transform: translate(-3px, 1px) }
50% { transform: translate(2px, -10px) }
55% { transform: translate(-7px, 0px) }
60% { transform: translate(-2px, 4px) }
65% { transform: translate(3px, -8px) }
70% { transform: translate(6px, 7px) }
75% { transform: translate(-7px, -2px) }
80% { transform: translate(-7px, -8px) }
85% { transform: translate(9px, 3px) }
90% { transform: translate(-3px, -2px) }
95% { transform: translate(-10px, 2px) }
100% { transform: translate(-2px, -6px) }
}
// const val = () => `translate(${Math.floor(Math.random() * 6) - 3}px, ${Math.floor(Math.random() * 6) - 3}px) rotate(${Math.floor(Math.random() * 24) - 12}deg)`;
// let css = '';
// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; }
@keyframes anime-shake {
0% { transform: translate(-3px, -1px) rotate(-8deg) }
5% { transform: translate(0px, -1px) rotate(-10deg) }
10% { transform: translate(1px, -3px) rotate(0deg) }
15% { transform: translate(1px, 1px) rotate(11deg) }
20% { transform: translate(-2px, 1px) rotate(1deg) }
25% { transform: translate(-1px, -2px) rotate(-2deg) }
30% { transform: translate(-1px, 2px) rotate(-3deg) }
35% { transform: translate(2px, 1px) rotate(6deg) }
40% { transform: translate(-2px, -3px) rotate(-9deg) }
45% { transform: translate(0px, -1px) rotate(-12deg) }
50% { transform: translate(1px, 2px) rotate(10deg) }
55% { transform: translate(0px, -3px) rotate(8deg) }
60% { transform: translate(1px, -1px) rotate(8deg) }
65% { transform: translate(0px, -1px) rotate(-7deg) }
70% { transform: translate(-1px, -3px) rotate(6deg) }
75% { transform: translate(0px, -2px) rotate(4deg) }
80% { transform: translate(-2px, -1px) rotate(3deg) }
85% { transform: translate(1px, -3px) rotate(-10deg) }
90% { transform: translate(1px, 0px) rotate(3deg) }
95% { transform: translate(-2px, 0px) rotate(-3deg) }
100% { transform: translate(2px, 1px) rotate(2deg) }
}
@keyframes anime-tada {
@keyframes tada {
from {
transform: scale3d(1, 1, 1);
}
@@ -580,33 +512,3 @@ hr {
transform: scale3d(1, 1, 1);
}
}
@keyframes anime-rubberBand {
from {
transform: scale3d(1, 1, 1);
}
30% {
transform: scale3d(1.25, 0.75, 1);
}
40% {
transform: scale3d(0.75, 1.25, 1);
}
50% {
transform: scale3d(1.15, 0.85, 1);
}
65% {
transform: scale3d(0.95, 1.05, 1);
}
75% {
transform: scale3d(1.05, 0.95, 1);
}
to {
transform: scale3d(1, 1, 1);
}
}

View File

@@ -1,14 +1,15 @@
<template>
<div class="fdidabkb">
<transition :name="$store.state.device.animation ? 'header' : ''" mode="out-in" appear>
<button class="_button back" v-if="withBack && canBack" @click="back()"><Fa :icon="faChevronLeft"/></button>
<button class="_button back" v-if="withBack && canBack" @click.stop="back()"><Fa :icon="faChevronLeft"/></button>
</transition>
<template v-if="info">
<div class="titleContainer">
<template v-if="info.tabs">
<div class="title" v-for="tab in info.tabs" :key="tab.id" :class="{ _button: tab.onClick, selected: tab.selected }" @click="tab.onClick" v-tooltip="tab.tooltip">
<div class="title" v-for="tab in info.tabs" :key="tab.id" :class="{ _button: tab.onClick, selected: tab.selected }" @click.stop="tab.onClick" v-tooltip="tab.tooltip">
<Fa v-if="tab.icon" :icon="tab.icon" :key="tab.icon" class="icon"/>
<span v-if="tab.title" class="text">{{ tab.title }}</span>
<Fa class="indicator" v-if="tab.indicate" :icon="faCircle"/>
</div>
</template>
<template v-else>
@@ -20,14 +21,14 @@
</div>
</template>
</div>
<button class="_button action" v-if="info.action" @click="info.action.handler"><Fa :icon="info.action.icon" :key="info.action.icon"/></button>
<button class="_button action" v-if="info.action" @click.stop="info.action.handler"><Fa :icon="info.action.icon" :key="info.action.icon"/></button>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faChevronLeft } from '@fortawesome/free-solid-svg-icons';
import { faChevronLeft, faCircle } from '@fortawesome/free-solid-svg-icons';
export default defineComponent({
props: {
@@ -45,7 +46,7 @@ export default defineComponent({
return {
canBack: false,
height: 0,
faChevronLeft
faChevronLeft, faCircle
};
},
@@ -131,6 +132,17 @@ export default defineComponent({
overflow: hidden;
text-overflow: ellipsis;
padding: 0 16px;
position: relative;
> .indicator {
position: absolute;
top: initial;
right: 8px;
top: 8px;
color: var(--indicator);
font-size: 12px;
animation: blink 1s infinite;
}
> .icon + .text {
margin-left: 8px;

View File

@@ -3,7 +3,7 @@
<XSidebar ref="nav" class="sidebar"/>
<div class="contents" ref="contents" :class="{ wallpaper }">
<header class="header" ref="header" @contextmenu.prevent.stop="onContextmenu">
<header class="header" ref="header" @contextmenu.prevent.stop="onContextmenu" @click="onHeaderClick">
<XHeader :info="pageInfo"/>
</header>
<main ref="main">
@@ -211,6 +211,10 @@ export default defineComponent({
if (window._scroll) window._scroll();
},
onHeaderClick() {
window.scroll({ top: 0, behavior: 'smooth' });
},
onContextmenu(e) {
const path = this.$route.path;
os.contextMenu([{
@@ -292,6 +296,7 @@ export default defineComponent({
backdrop-filter: blur(32px);
background-color: var(--header);
border-bottom: solid 1px var(--divider);
user-select: none;
}
> main {

View File

@@ -48,6 +48,12 @@ export default defineComponent({
},
},
created() {
if (window.innerWidth < 1024) {
localStorage.setItem('ui', 'default');
location.reload();
}
},
methods: {
help() {

View File

@@ -6,7 +6,7 @@ Misskey Webクライアントのプラグイン機能を使うと、クライア
プラグインは、AiScriptのメタデータ埋め込み機能を使って、デフォルトとしてプラグインのメタデータを定義する必要があります。
メタデータは次のプロパティを含むオブジェクトです。
### mame
### name
プラグイン名
### author

View File

@@ -1,5 +1,5 @@
import { parseFragment, DefaultTreeDocumentFragment } from 'parse5';
import { urlRegex } from './prelude';
import { urlRegexFull } from './prelude';
export function fromHtml(html: string, hashtagNames?: string[]): string {
const dom = parseFragment(html) as DefaultTreeDocumentFragment;
@@ -54,7 +54,11 @@ export function fromHtml(html: string, hashtagNames?: string[]): string {
}
// その他
} else {
text += (!href || (txt === href.value && txt.match(urlRegex))) ? txt : `[${txt}](${href.value})`;
text += !href ? txt
: txt === href.value
? txt.match(urlRegexFull) ? txt
: `<${txt}>`
: `[${txt}](${href.value})`;
}
break;

View File

@@ -23,7 +23,6 @@ export const mfmLanguage = P.createLanguage({
root: r => P.alt(r.block, r.inline).atLeast(1),
plain: r => P.alt(r.emoji, r.text).atLeast(1),
block: r => P.alt(
r.title,
r.quote,
r.search,
r.blockCode,
@@ -37,14 +36,6 @@ export const mfmLanguage = P.createLanguage({
return P.makeFailure(i, 'not newline');
}
}),
title: r => r.startOfLine.then(P((input, i) => {
const text = input.substr(i);
const match = text.match(/^([【\[]([^【\[】\]\n]+?)[】\]])(\n|$)/);
if (!match) return P.makeFailure(i, 'not a title');
const q = match[2].trim();
const contents = r.inline.atLeast(1).tryParse(q);
return P.makeSuccess(i + match[0].length, createTree('title', contents, {}));
})),
quote: r => r.startOfLine.then(P((input, i) => {
const text = input.substr(i);
if (!text.match(/^>[\s\S]+?/)) return P.makeFailure(i, 'not a quote');
@@ -72,12 +63,6 @@ export const mfmLanguage = P.createLanguage({
r.small,
r.italic,
r.strike,
r.motion,
r.spin,
r.jump,
r.flip,
r.twitch,
r.shake,
r.inlineCode,
r.mathInline,
r.mention,
@@ -85,9 +70,14 @@ export const mfmLanguage = P.createLanguage({
r.url,
r.link,
r.emoji,
r.fn,
r.text
),
big: r => P.regexp(/^\*\*\*([\s\S]+?)\*\*\*/, 1).map(x => createTree('big', r.inline.atLeast(1).tryParse(x), {})),
// TODO: そのうち消す
big: r => P.regexp(/^\*\*\*([\s\S]+?)\*\*\*/, 1).map(x => createTree('fn', r.inline.atLeast(1).tryParse(x), {
name: 'tada',
args: {}
})),
bold: r => {
const asterisk = P.regexp(/\*\*([\s\S]+?)\*\*/, 1);
const underscore = P.regexp(/__([a-zA-Z0-9\s]+?)__/, 1);
@@ -107,25 +97,6 @@ export const mfmLanguage = P.createLanguage({
return P.alt(xml, underscore).map(x => createTree('italic', r.inline.atLeast(1).tryParse(x), {}));
},
strike: r => P.regexp(/~~([^\n~]+?)~~/, 1).map(x => createTree('strike', r.inline.atLeast(1).tryParse(x), {})),
motion: r => {
const paren = P.regexp(/\(\(\(([\s\S]+?)\)\)\)/, 1);
const xml = P.regexp(/<motion>(.+?)<\/motion>/, 1);
return P.alt(paren, xml).map(x => createTree('motion', r.inline.atLeast(1).tryParse(x), {}));
},
spin: r => {
return P((input, i) => {
const text = input.substr(i);
const match = text.match(/^<spin(\s[a-z]+?)?>(.+?)<\/spin>/i);
if (!match) return P.makeFailure(i, 'not a spin');
return P.makeSuccess(i + match[0].length, {
content: match[2], attr: match[1] ? match[1].trim() : null
});
}).map(x => createTree('spin', r.inline.atLeast(1).tryParse(x.content), { attr: x.attr }));
},
jump: r => P.regexp(/<jump>(.+?)<\/jump>/, 1).map(x => createTree('jump', r.inline.atLeast(1).tryParse(x), {})),
flip: r => P.regexp(/<flip>(.+?)<\/flip>/, 1).map(x => createTree('flip', r.inline.atLeast(1).tryParse(x), {})),
twitch: r => P.regexp(/<twitch>(.+?)<\/twitch>/, 1).map(x => createTree('twitch', r.inline.atLeast(1).tryParse(x), {})),
shake: r => P.regexp(/<shake>(.+?)<\/shake>/, 1).map(x => createTree('shake', r.inline.atLeast(1).tryParse(x), {})),
center: r => r.startOfLine.then(P.regexp(/<center>([\s\S]+?)<\/center>/, 1).map(x => createTree('center', r.inline.atLeast(1).tryParse(x), {}))),
inlineCode: () => P.regexp(/`([^´\n]+?)`/, 1).map(x => createLeaf('inlineCode', { code: x })),
mathBlock: r => r.startOfLine.then(P.regexp(/\\\[([\s\S]+?)\\\]/, 1).map(x => createLeaf('mathBlock', { formula: x.trim() }))),
@@ -192,5 +163,29 @@ export const mfmLanguage = P.createLanguage({
const code = P.regexp(emojiRegex).map(x => createLeaf('emoji', { emoji: x }));
return P.alt(name, code);
},
fn: r => {
return P.seqObj(
P.string('['), ['fn', P.regexp(/[^\s\n\[\]]+/)] as any, P.string(' '), P.optWhitespace, ['text', P.regexp(/[^\n\[\]]+/)] as any, P.string(']'),
).map((x: any) => {
let name = x.fn;
const args = {};
const separator = x.fn.indexOf('.');
if (separator > -1) {
name = x.fn.substr(0, separator);
for (const arg of x.fn.substr(separator + 1).split(',')) {
const kv = arg.split('=');
if (kv.length === 1) {
args[kv[0]] = true;
} else {
args[kv[0]] = kv[1];
}
}
}
return createTree('fn', r.inline.atLeast(1).tryParse(x.text), {
name,
args
});
});
},
text: () => P.any.map(x => createLeaf('text', { text: x }))
});

View File

@@ -36,4 +36,5 @@ export function createTree(type: string, children: MfmForest, props: any): MfmTr
return T.createTree({ type, props }, children);
}
export const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
export const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
export const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;

View File

@@ -25,12 +25,6 @@ export function toHtml(tokens: MfmForest | null, mentionedRemoteUsers: IMentione
return el;
},
big(token) {
const el = doc.createElement('strong');
appendChildren(token.children, el);
return el;
},
small(token) {
const el = doc.createElement('small');
appendChildren(token.children, el);
@@ -49,46 +43,16 @@ export function toHtml(tokens: MfmForest | null, mentionedRemoteUsers: IMentione
return el;
},
motion(token) {
fn(token) {
const el = doc.createElement('i');
appendChildren(token.children, el);
return el;
},
spin(token) {
const el = doc.createElement('i');
appendChildren(token.children, el);
return el;
},
jump(token) {
const el = doc.createElement('i');
appendChildren(token.children, el);
return el;
},
twitch(token) {
const el = doc.createElement('i');
appendChildren(token.children, el);
return el;
},
shake(token) {
const el = doc.createElement('i');
appendChildren(token.children, el);
return el;
},
flip(token) {
const el = doc.createElement('span');
appendChildren(token.children, el);
return el;
},
blockCode(token) {
const pre = doc.createElement('pre');
const inner = doc.createElement('code');
inner.innerHTML = token.node.props.code;
inner.textContent = token.node.props.code;
pre.appendChild(inner);
return pre;
},
@@ -157,12 +121,6 @@ export function toHtml(tokens: MfmForest | null, mentionedRemoteUsers: IMentione
return el;
},
title(token) {
const el = doc.createElement('h1');
appendChildren(token.children, el);
return el;
},
text(token) {
const el = doc.createElement('span');
const nodes = (token.node.props.text as string).split(/\r\n|\r|\n/).map(x => doc.createTextNode(x) as Node);

View File

@@ -18,10 +18,6 @@ export function toString(tokens: MfmForest | null, opts?: RestoreOptions): strin
return `**${appendChildren(token.children, opts)}**`;
},
big(token, opts) {
return `***${appendChildren(token.children, opts)}***`;
},
small(token, opts) {
return `<small>${appendChildren(token.children, opts)}</small>`;
},
@@ -34,30 +30,11 @@ export function toString(tokens: MfmForest | null, opts?: RestoreOptions): strin
return `<i>${appendChildren(token.children, opts)}</i>`;
},
motion(token, opts) {
return `<motion>${appendChildren(token.children, opts)}</motion>`;
},
spin(token, opts) {
const attr = token.node.props?.attr;
const post = attr ? ` ${attr}` : '';
return `<spin${post}>${appendChildren(token.children, opts)}</spin>`;
},
jump(token, opts) {
return `<jump>${appendChildren(token.children, opts)}</jump>`;
},
twitch(token, opts) {
return `<twitch>${appendChildren(token.children, opts)}</twitch>`;
},
shake(token, opts) {
return `<shake>${appendChildren(token.children, opts)}</shake>`;
},
flip(token, opts) {
return `<flip>${appendChildren(token.children, opts)}</flip>`;
fn(token, opts) {
const name = token.node.props?.name;
const args = token.node.props?.args || {};
const argsStr = Object.entries(args).map(([k, v]) => v === true ? k : `${k}=${v}`).join(',');
return `[${name}${argsStr !== '' ? '.' + argsStr : ''} ${appendChildren(token.children, opts)}]`;
},
blockCode(token) {
@@ -104,10 +81,6 @@ export function toString(tokens: MfmForest | null, opts?: RestoreOptions): strin
return `${appendChildren(token.children, {doNyaize: false}).replace(/^/gm,'>').trim()}\n`;
},
title(token, opts) {
return `[${appendChildren(token.children, opts)}]\n`;
},
text(token, opts) {
return (opts && opts.doNyaize) ? nyaize(token.node.props.text) : token.node.props.text;
},

View File

@@ -35,4 +35,10 @@ export class Clip {
default: false
})
public isPublic: boolean;
@Column('varchar', {
length: 2048, nullable: true, default: null,
comment: 'The description of the Clip.'
})
public description: string | null;
}

View File

@@ -106,11 +106,6 @@ export class UserProfile {
})
public room: Record<string, any>;
@Column('boolean', {
default: false,
})
public autoWatch: boolean;
@Column('boolean', {
default: false,
})

View File

@@ -2,6 +2,8 @@ import { EntityRepository, Repository } from 'typeorm';
import { Clip } from '../entities/clip';
import { ensure } from '../../prelude/ensure';
import { SchemaType } from '../../misc/schema';
import { Users } from '..';
import { awaitAll } from '../../prelude/await-all';
export type PackedClip = SchemaType<typeof packedClipSchema>;
@@ -12,11 +14,15 @@ export class ClipRepository extends Repository<Clip> {
): Promise<PackedClip> {
const clip = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return {
return await awaitAll({
id: clip.id,
createdAt: clip.createdAt.toISOString(),
userId: clip.userId,
user: Users.pack(clip.user || clip.userId),
name: clip.name,
};
description: clip.description,
isPublic: clip.isPublic,
});
}
}
@@ -37,10 +43,30 @@ export const packedClipSchema = {
format: 'date-time',
description: 'The date that the Clip was created.'
},
userId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
user: {
type: 'object' as const,
ref: 'User',
optional: false as const, nullable: false as const,
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
description: 'The name of the Clip.'
},
description: {
type: 'string' as const,
optional: false as const, nullable: true as const,
description: 'The description of the Clip.'
},
isPublic: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
description: 'Whether this Clip is public.',
},
},
};

View File

@@ -235,7 +235,6 @@ export class UserRepository extends Repository<User> {
...(opts.detail && meId === user.id ? {
avatarId: user.avatarId,
bannerId: user.bannerId,
autoWatch: profile!.autoWatch,
injectFeaturedNote: profile!.injectFeaturedNote,
alwaysMarkNsfw: profile!.alwaysMarkNsfw,
carefulBot: profile!.carefulBot,

View File

@@ -0,0 +1,76 @@
import $ from 'cafy';
import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { ClipNotes, Clips } from '../../../../models';
import { ApiError } from '../../error';
import { genId } from '../../../../misc/gen-id';
import { getNote } from '../../common/getters';
export const meta = {
tags: ['account', 'notes', 'clips'],
requireCredential: true as const,
kind: 'write:account',
params: {
clipId: {
validator: $.type(ID),
},
noteId: {
validator: $.type(ID),
},
},
errors: {
noSuchClip: {
message: 'No such clip.',
code: 'NO_SUCH_CLIP',
id: 'd6e76cc0-a1b5-4c7c-a287-73fa9c716dcf'
},
noSuchNote: {
message: 'No such note.',
code: 'NO_SUCH_NOTE',
id: 'fc8c0b49-c7a3-4664-a0a6-b418d386bb8b'
},
alreadyClipped: {
message: 'The note has already been clipped.',
code: 'ALREADY_CLIPPED',
id: '734806c4-542c-463a-9311-15c512803965'
},
}
};
export default define(meta, async (ps, user) => {
const clip = await Clips.findOne({
id: ps.clipId,
userId: user.id
});
if (clip == null) {
throw new ApiError(meta.errors.noSuchClip);
}
const note = await getNote(ps.noteId).catch(e => {
if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw e;
});
const exist = await ClipNotes.findOne({
noteId: note.id,
clipId: clip.id
});
if (exist != null) {
throw new ApiError(meta.errors.alreadyClipped);
}
await ClipNotes.save({
id: genId(),
noteId: note.id,
clipId: clip.id
});
});

View File

@@ -13,6 +13,14 @@ export const meta = {
params: {
name: {
validator: $.str.range(1, 100)
},
isPublic: {
validator: $.optional.bool
},
description: {
validator: $.optional.nullable.str.range(1, 2048)
}
},
};
@@ -23,6 +31,8 @@ export default define(meta, async (ps, user) => {
createdAt: new Date(),
userId: user.id,
name: ps.name,
isPublic: ps.isPublic,
description: ps.description,
});
return await Clips.pack(clip);

View File

@@ -1,10 +1,11 @@
import $ from 'cafy';
import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { Clips, Notes } from '../../../../models';
import { ClipNotes, Clips, Notes } from '../../../../models';
import { makePaginationQuery } from '../../common/make-pagination-query';
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query';
import { ApiError } from '../../error';
export const meta = {
tags: ['account', 'notes', 'clips'],
@@ -14,6 +15,10 @@ export const meta = {
kind: 'read:account',
params: {
clipId: {
validator: $.type(ID),
},
limit: {
validator: $.optional.num.range(1, 100),
default: 10
@@ -30,7 +35,7 @@ export const meta = {
errors: {
noSuchClip: {
message: 'No such list.',
message: 'No such clip.',
code: 'NO_SUCH_CLIP',
id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00'
}

View File

@@ -18,6 +18,14 @@ export const meta = {
name: {
validator: $.str.range(1, 100),
},
isPublic: {
validator: $.optional.bool
},
description: {
validator: $.optional.nullable.str.range(1, 2048)
}
},
@@ -42,7 +50,9 @@ export default define(meta, async (ps, user) => {
}
await Clips.update(clip.id, {
name: ps.name
name: ps.name,
description: ps.description,
isPublic: ps.isPublic,
});
return await Clips.pack(clip.id);

View File

@@ -120,13 +120,6 @@ export const meta = {
}
},
autoWatch: {
validator: $.optional.bool,
desc: {
'ja-JP': '投稿の自動ウォッチをするか否か'
}
},
injectFeaturedNote: {
validator: $.optional.bool,
},
@@ -212,7 +205,6 @@ export default define(meta, async (ps, user, token) => {
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
if (typeof ps.autoWatch === 'boolean') profileUpdates.autoWatch = ps.autoWatch;
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
if (typeof ps.alwaysMarkNsfw === 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;

View File

@@ -1,6 +1,5 @@
import $ from 'cafy';
import { ID } from '../../../../../misc/cafy-id';
import watch from '../../../../../services/note/watch';
import { publishNoteStream } from '../../../../../services/stream';
import { createNotification } from '../../../../../services/create-notification';
import define from '../../../define';
@@ -10,7 +9,7 @@ import { deliver } from '../../../../../queue';
import { renderActivity } from '../../../../../remote/activitypub/renderer';
import renderVote from '../../../../../remote/activitypub/renderer/vote';
import { deliverQuestionUpdate } from '../../../../../services/note/polls/update';
import { PollVotes, NoteWatchings, Users, Polls, UserProfiles } from '../../../../../models';
import { PollVotes, NoteWatchings, Users, Polls } from '../../../../../models';
import { Not } from 'typeorm';
import { IRemoteUser } from '../../../../../models/entities/user';
import { genId } from '../../../../../misc/gen-id';
@@ -152,13 +151,6 @@ export default define(meta, async (ps, user) => {
}
});
const profile = await UserProfiles.findOne(user.id).then(ensure);
// この投稿をWatchする
if (profile.autoWatch !== false) {
watch(user.id, note);
}
// リモート投票の場合リプライ送信
if (note.userHost != null) {
const pollOwner = await Users.findOne(note.userId).then(ensure) as IRemoteUser;

View File

@@ -80,7 +80,13 @@ export default define(meta, async (ps, me) => {
isSuspended: false
});
return await Promise.all(users.map(u => Users.pack(u, me, {
// リクエストされた通りに並べ替え
const _users = [];
for (const id of ps.userIds) {
_users.push(users.find(x => x.id === id));
}
return await Promise.all(_users.map(u => Users.pack(u, me, {
detail: true
})));
} else {

View File

@@ -5,7 +5,6 @@ import renderNote from '../../remote/activitypub/renderer/note';
import renderCreate from '../../remote/activitypub/renderer/create';
import renderAnnounce from '../../remote/activitypub/renderer/announce';
import { renderActivity } from '../../remote/activitypub/renderer';
import watch from './watch';
import { parse } from '../../mfm/parse';
import { resolveUser } from '../../remote/resolve-user';
import config from '../../config';
@@ -340,18 +339,11 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
await createMentionedEvents(mentionedUsers, note, nm);
const profile = await UserProfiles.findOne(user.id).then(ensure);
// If has in reply to note
if (data.reply) {
// Fetch watchers
nmRelatedPromises.push(notifyToWatchersOfReplyee(data.reply, user, nm));
// この投稿をWatchする
if (Users.isLocalUser(user) && profile.autoWatch) {
watch(user.id, data.reply);
}
// 通知
if (data.reply.userHost === null) {
nm.push(data.reply.userId, 'reply');
@@ -371,11 +363,6 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
// Fetch watchers
nmRelatedPromises.push(notifyToWatchersOfRenotee(data.renote, user, nm, type));
// この投稿をWatchする
if (Users.isLocalUser(user) && profile.autoWatch) {
watch(user.id, data.renote);
}
// Publish event
if ((user.id !== data.renote.userId) && data.renote.userHost === null) {
publishMainStream(data.renote.userId, 'renote', noteObj);

View File

@@ -1,8 +1,7 @@
import watch from '../../../services/note/watch';
import { publishNoteStream } from '../../stream';
import { User } from '../../../models/entities/user';
import { Note } from '../../../models/entities/note';
import { PollVotes, Users, NoteWatchings, Polls, UserProfiles } from '../../../models';
import { PollVotes, NoteWatchings, Polls } from '../../../models';
import { Not } from 'typeorm';
import { genId } from '../../../misc/gen-id';
import { createNotification } from '../../create-notification';
@@ -68,11 +67,4 @@ export default async function(user: User, note: Note, choice: number) {
});
}
});
const profile = await UserProfiles.findOne(user.id);
// ローカルユーザーが投票した場合この投稿をWatchする
if (Users.isLocalUser(user) && profile!.autoWatch) {
watch(user.id, note);
}
}

View File

@@ -1,13 +1,11 @@
import { publishNoteStream } from '../../stream';
import watch from '../watch';
import { renderLike } from '../../../remote/activitypub/renderer/like';
import DeliverManager from '../../../remote/activitypub/deliver-manager';
import { renderActivity } from '../../../remote/activitypub/renderer';
import { IdentifiableError } from '../../../misc/identifiable-error';
import { toDbReaction, decodeReaction } from '../../../misc/reaction-lib';
import { User, IRemoteUser } from '../../../models/entities/user';
import { Note } from '../../../models/entities/note';
import { NoteReactions, Users, NoteWatchings, Notes, UserProfiles, Emojis } from '../../../models';
import { NoteReactions, Users, NoteWatchings, Notes, Emojis } from '../../../models';
import { Not } from 'typeorm';
import { perUserReactionsChart } from '../../chart';
import { genId } from '../../../misc/gen-id';
@@ -101,13 +99,6 @@ export default async (user: User, note: Note, reaction?: string) => {
}
});
const profile = await UserProfiles.findOne(user.id);
// ユーザーがローカルユーザーかつ自動ウォッチ設定がオンならばこの投稿をWatchする
if (Users.isLocalUser(user) && profile!.autoWatch) {
watch(user.id, note);
}
//#region 配信
if (Users.isLocalUser(user) && !note.localOnly) {
const content = renderActivity(await renderLike(inserted, note));

View File

@@ -12,6 +12,7 @@ import * as assert from 'assert';
import { parse, parsePlain } from '../src/mfm/parse';
import { toHtml } from '../src/mfm/to-html';
import { fromHtml } from '../src/mfm/from-html';
import { toString } from '../src/mfm/to-string';
import { createTree as tree, createLeaf as leaf, MfmTree } from '../src/mfm/prelude';
import { removeOrphanedBrackets } from '../src/mfm/language';
@@ -227,16 +228,6 @@ describe('MFM', () => {
});
});
it('big', () => {
const tokens = parse('***Strawberry*** Pasta');
assert.deepStrictEqual(tokens, [
tree('big', [
text('Strawberry')
], {}),
text(' Pasta'),
]);
});
it('small', () => {
const tokens = parse('<small>smaller</small>');
assert.deepStrictEqual(tokens, [
@@ -246,133 +237,6 @@ describe('MFM', () => {
]);
});
it('flip', () => {
const tokens = parse('<flip>foo</flip>');
assert.deepStrictEqual(tokens, [
tree('flip', [
text('foo')
], {}),
]);
});
describe('spin', () => {
it('text', () => {
const tokens = parse('<spin>foo</spin>');
assert.deepStrictEqual(tokens, [
tree('spin', [
text('foo')
], {
attr: null
}),
]);
});
it('emoji', () => {
const tokens = parse('<spin>:foo:</spin>');
assert.deepStrictEqual(tokens, [
tree('spin', [
leaf('emoji', { name: 'foo' })
], {
attr: null
}),
]);
});
it('with attr', () => {
const tokens = parse('<spin left>:foo:</spin>');
assert.deepStrictEqual(tokens, [
tree('spin', [
leaf('emoji', { name: 'foo' })
], {
attr: 'left'
}),
]);
});
/*
it('multi', () => {
const tokens = parse('<spin>:foo:</spin><spin>:foo:</spin>');
assert.deepStrictEqual(tokens, [
tree('spin', [
leaf('emoji', { name: 'foo' })
], {
attr: null
}),
tree('spin', [
leaf('emoji', { name: 'foo' })
], {
attr: null
}),
]);
});
it('nested', () => {
const tokens = parse('<spin><spin>:foo:</spin></spin>');
assert.deepStrictEqual(tokens, [
tree('spin', [
tree('spin', [
leaf('emoji', { name: 'foo' })
], {
attr: null
}),
], {
attr: null
}),
]);
});
*/
});
it('jump', () => {
const tokens = parse('<jump>:foo:</jump>');
assert.deepStrictEqual(tokens, [
tree('jump', [
leaf('emoji', { name: 'foo' })
], {}),
]);
});
describe('motion', () => {
it('by triple brackets', () => {
const tokens = parse('(((foo)))');
assert.deepStrictEqual(tokens, [
tree('motion', [
text('foo')
], {}),
]);
});
it('by triple brackets (with other texts)', () => {
const tokens = parse('bar(((foo)))bar');
assert.deepStrictEqual(tokens, [
text('bar'),
tree('motion', [
text('foo')
], {}),
text('bar'),
]);
});
it('by <motion> tag', () => {
const tokens = parse('<motion>foo</motion>');
assert.deepStrictEqual(tokens, [
tree('motion', [
text('foo')
], {}),
]);
});
it('by <motion> tag (with other texts)', () => {
const tokens = parse('bar<motion>foo</motion>bar');
assert.deepStrictEqual(tokens, [
text('bar'),
tree('motion', [
text('foo')
], {}),
text('bar'),
]);
});
});
describe('mention', () => {
it('local', () => {
const tokens = parse('@himawari foo');
@@ -1081,49 +945,6 @@ describe('MFM', () => {
]);
});
describe('title', () => {
it('simple', () => {
const tokens = parse('【foo】');
assert.deepStrictEqual(tokens, [
tree('title', [
text('foo')
], {})
]);
});
it('require line break', () => {
const tokens = parse('a【foo】');
assert.deepStrictEqual(tokens, [
text('a【foo】')
]);
});
it('with before and after texts', () => {
const tokens = parse('before\n【foo】\nafter');
assert.deepStrictEqual(tokens, [
text('before\n'),
tree('title', [
text('foo')
], {}),
text('after')
]);
});
it('ignore multiple title blocks', () => {
const tokens = parse('【foo】bar【baz】');
assert.deepStrictEqual(tokens, [
text('【foo】bar【baz】')
]);
});
it('disallow linebreak in title', () => {
const tokens = parse('【foo\nbar】');
assert.deepStrictEqual(tokens, [
text('【foo\nbar】')
]);
});
});
describe('center', () => {
it('simple', () => {
const tokens = parse('<center>foo</center>');
@@ -1310,30 +1131,6 @@ describe('MFM', () => {
it('小さい字', () => {
assert.deepStrictEqual(toString(parse('<small>小さい字</small>')), '<small>小さい字</small>');
});
it('モーション', () => {
assert.deepStrictEqual(toString(parse('<motion>モーション</motion>')), '<motion>モーション</motion>');
});
it('モーション2', () => {
assert.deepStrictEqual(toString(parse('(((モーション)))')), '<motion>モーション</motion>');
});
it('ビッグ+', () => {
assert.deepStrictEqual(toString(parse('*** ビッグ+ ***')), '*** ビッグ+ ***');
});
it('回転', () => {
assert.deepStrictEqual(toString(parse('<spin>回転</spin>')), '<spin>回転</spin>');
});
it('右回転', () => {
assert.deepStrictEqual(toString(parse('<spin right>右回転</spin>')), '<spin right>右回転</spin>');
});
it('左回転', () => {
assert.deepStrictEqual(toString(parse('<spin left>左回転</spin>')), '<spin left>左回転</spin>');
});
it('往復回転', () => {
assert.deepStrictEqual(toString(parse('<spin alternate>往復回転</spin>')), '<spin alternate>往復回転</spin>');
});
it('ジャンプ', () => {
assert.deepStrictEqual(toString(parse('<jump>ジャンプ</jump>')), '<jump>ジャンプ</jump>');
});
it('コードブロック', () => {
assert.deepStrictEqual(toString(parse('```\nコードブロック\n```')), '```\nコードブロック\n```');
});
@@ -1352,12 +1149,6 @@ describe('MFM', () => {
it('詳細なしリンク', () => {
assert.deepStrictEqual(toString(parse('?[詳細なしリンク](http://example.com)')), '?[詳細なしリンク](http://example.com)');
});
it('【タイトル】', () => {
assert.deepStrictEqual(toString(parse('【タイトル】')), '[タイトル]');
});
it('[タイトル]', () => {
assert.deepStrictEqual(toString(parse('[タイトル]')), '[タイトル]');
});
it('インライン数式', () => {
assert.deepStrictEqual(toString(parse('\\(インライン数式\\)')), '\\(インライン数式\\)');
});
@@ -1366,3 +1157,37 @@ describe('MFM', () => {
});
});
});
describe('fromHtml', () => {
it('br', () => {
assert.deepStrictEqual(fromHtml('<p>abc<br><br/>d</p>'), 'abc\n\nd');
});
it('link with different text', () => {
assert.deepStrictEqual(fromHtml('<p>a <a href="https://example.com/b">c</a> d</p>'), 'a [c](https://example.com/b) d');
});
it('link with same text', () => {
assert.deepStrictEqual(fromHtml('<p>a <a href="https://example.com/b">https://example.com/b</a> d</p>'), 'a https://example.com/b d');
});
it('link with same text, but not encoded', () => {
assert.deepStrictEqual(fromHtml('<p>a <a href="https://example.com/ä">https://example.com/ä</a> d</p>'), 'a <https://example.com/ä> d');
});
it('link with no url', () => {
assert.deepStrictEqual(fromHtml('<p>a <a href="b">c</a> d</p>'), 'a [c](b) d');
});
it('link without href', () => {
assert.deepStrictEqual(fromHtml('<p>a <a>c</a> d</p>'), 'a c d');
});
it('mention', () => {
assert.deepStrictEqual(fromHtml('<p>a <a href="https://example.com/@user" class="u-url mention">@user</a> d</p>'), 'a @user@example.com d');
});
it('hashtag', () => {
assert.deepStrictEqual(fromHtml('<p>a <a href="https://example.com/tags/a">#a</a> d</p>', ['#a']), 'a #a d');
});
});

798
yarn.lock

File diff suppressed because it is too large Load Diff