Compare commits
	
		
			80 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | c52e30e8e0 | ||
|   | 0cb04ded36 | ||
|   | 6cfad65ac7 | ||
|   | ed20805b10 | ||
|   | 576303cd72 | ||
|   | 0062e084f8 | ||
|   | b90d76dcfe | ||
|   | c95619b2bf | ||
|   | 82150bd5b8 | ||
|   | 9e03335ff8 | ||
|   | b3c5c3f0ea | ||
|   | 65858dab3e | ||
|   | c968633d15 | ||
|   | 39a8942daf | ||
|   | 7705a7928e | ||
|   | d2c14b844e | ||
|   | 3e45e6c165 | ||
|   | fe8334931f | ||
|   | f565c5f730 | ||
|   | c03e2febb0 | ||
|   | c2f4fb7ba7 | ||
|   | 5f869e5d87 | ||
|   | 65f1afc4e0 | ||
|   | aec2762bf1 | ||
|   | a41144a00f | ||
|   | aa28e8a7a6 | ||
|   | a1a51ce518 | ||
|   | 90999e0ef9 | ||
|   | 2ceeb17056 | ||
|   | 18afdd6040 | ||
|   | b9972ec6bd | ||
|   | ebb53e87f3 | ||
|   | 9f9d7325fd | ||
|   | 742a005523 | ||
|   | 3a28c06534 | ||
|   | 46d5711071 | ||
|   | 48113f3afd | ||
|   | be29972ddf | ||
|   | 49b3a83f76 | ||
|   | 342794c728 | ||
|   | 3739638c81 | ||
|   | 401351d9c8 | ||
|   | 6073a03967 | ||
|   | 3e3d294188 | ||
|   | f6f96ae5bf | ||
|   | 62ccb53c24 | ||
|   | e410e22980 | ||
|   | fc5ceea335 | ||
|   | 38af8d4737 | ||
|   | 33b0cab596 | ||
|   | 2a47e4a1e1 | ||
|   | 46f53868c5 | ||
|   | eac7f11aa7 | ||
|   | e219188f46 | ||
|   | 3df8c701a7 | ||
|   | 1186813c75 | ||
|   | 645b6fdc8a | ||
|   | ae0596a729 | ||
|   | bb5fd3c1f2 | ||
|   | 532fa9c5f9 | ||
|   | 80a4aa6fa6 | ||
|   | 4f218f544f | ||
|   | f261f8d7d1 | ||
|   | e044d11782 | ||
|   | d2da459dd8 | ||
|   | b1e6a33d6b | ||
|   | 0d276d0d61 | ||
|   | 998936651a | ||
|   | 1bec25e8e6 | ||
|   | f220e4183f | ||
|   | e965b57dc2 | ||
|   | 5e6e1e237a | ||
|   | 41fe364b49 | ||
|   | 2953ba17c3 | ||
|   | f3b3e06329 | ||
|   | 98249942d5 | ||
|   | 0fc8445425 | ||
|   | 943a1940e2 | ||
|   | 15d166e30e | ||
|   | 83619fda98 | 
							
								
								
									
										43
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										43
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							| @@ -1,13 +1,32 @@ | |||||||
| ## Summary | <!-- ℹ お読みください | ||||||
|  | PRありがとうございます! PRを作成する前に、以下をご確認ください: | ||||||
|  | 可能であればタイトルに、以下で示すようなPRの種類が分かるキーワードをプリフィクスしてください。 | ||||||
|  | fix / refactor / feat / enhance / perf / chore | ||||||
|  | また、PRの粒度が適切であることを確認してください。ひとつのPRに複数の種類の変更や関心を含めることは避けてください。 | ||||||
|  | このPRによって解決されるIssueがある場合は、そのIssue IDを本文内に記入してください。 | ||||||
|  | CHANGELOG.mdに変更点を追記してください。リファクタリングなど、利用者に影響を与えない変更についてはこの限りではありません。 | ||||||
|  | 機能追加やバグ修正をした場合は、可能であればテストケースを追加してください。 | ||||||
|  | ご協力ありがとうございます🤗 | ||||||
|  | --> | ||||||
|  | <!-- ℹ README | ||||||
|  | Thank you for your PR! Before creating a PR, please check the following: | ||||||
|  | If possible, prefix the title with a keyword that identifies the type of this PR, as shown below. | ||||||
|  | fix / refactor / feat / enhance / perf / chore | ||||||
|  | Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR. | ||||||
|  | If there is an issue to be resolved by this PR, please include the Issue ID in the text. | ||||||
|  | Please add the summary of the changes to CHANGELOG.md. However, this is not necessary for changes that do not affect the users, such as refactoring. | ||||||
|  | If you have added a feature or fixed a bug, please add a test case if possible. | ||||||
|  | Thanks for your cooperation 🤗 | ||||||
|  | --> | ||||||
|  |  | ||||||
| <!-- | # What | ||||||
|   - | <!-- このPRで何をしたのか? どう変わるのか? --> | ||||||
|   - * Please describe your changes here * | <!-- What did you do with this PR? How will it change things? --> | ||||||
|   - |  | ||||||
|   - If you are going to resolve some issue, please add this context. | # Why | ||||||
|   - Resolve #ISSUE_NUMBER | <!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? --> | ||||||
|   - | <!-- Why do you do it? What are your intentions? What is the problem? --> | ||||||
|   - If you are going to fix some bug issue, please add this context. |  | ||||||
|   - Fix #ISSUE_NUMBER | # Additional info (optional) | ||||||
|   - | <!-- テスト観点など --> | ||||||
|   --> | <!-- Test perspective, etc --> | ||||||
|   | |||||||
| @@ -242,6 +242,9 @@ npx ts-node ./node_modules/typeorm/cli.js migration:generate -n 変更の名前 | |||||||
|  |  | ||||||
| 作成されたスクリプトは不必要な変更を含むため除去してください。 | 作成されたスクリプトは不必要な変更を含むため除去してください。 | ||||||
|  |  | ||||||
|  | ### コネクションには`markRaw`せよ | ||||||
|  | **Vueのコンポーネントのdataオプションとして**misskey.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。 | ||||||
|  |  | ||||||
| ## その他 | ## その他 | ||||||
| ### HTMLのクラス名で follow という単語は使わない | ### HTMLのクラス名で follow という単語は使わない | ||||||
| 広告ブロッカーで誤ってブロックされる | 広告ブロッカーで誤ってブロックされる | ||||||
|   | |||||||
| @@ -2,6 +2,6 @@ files: | |||||||
|   - source: /locales/ja-JP.yml |   - source: /locales/ja-JP.yml | ||||||
|     translation: /locales/%locale%.yml |     translation: /locales/%locale%.yml | ||||||
|     update_option: update_as_unapproved |     update_option: update_as_unapproved | ||||||
|   - source: /src/docs/ja-JP/*.md |   - source: /src/docs/ja-JP/**/*.md | ||||||
|     translation: /src/docs/%locale%/%original_file_name% |     translation: /src/docs/%locale%/**/%original_file_name% | ||||||
|     update_option: update_as_unapproved |     update_option: update_as_unapproved | ||||||
|   | |||||||
| @@ -427,9 +427,13 @@ inUse: "مستخدم" | |||||||
| info: "عن" | info: "عن" | ||||||
| user: "المستخدمون" | user: "المستخدمون" | ||||||
| administration: "إدارة " | administration: "إدارة " | ||||||
|  | postToGallery: "انشر في المعرض" | ||||||
|  | gallery: "المعرض" | ||||||
| expiration: "ينتهي استطلاع الرأي في" | expiration: "ينتهي استطلاع الرأي في" | ||||||
| middle: "متوسط" | middle: "متوسط" | ||||||
| global: "الشامل" | global: "الشامل" | ||||||
|  | _docs: | ||||||
|  |   admin: "إدارة " | ||||||
| _email: | _email: | ||||||
|   _follow: |   _follow: | ||||||
|     title: "يتابعك" |     title: "يتابعك" | ||||||
|   | |||||||
| @@ -111,6 +111,7 @@ editWidgets: "Upravit widget" | |||||||
| editWidgetsExit: "Hotovo" | editWidgetsExit: "Hotovo" | ||||||
| customEmojis: "Vlastní emoji" | customEmojis: "Vlastní emoji" | ||||||
| emoji: "Emoji" | emoji: "Emoji" | ||||||
|  | emojis: "Emoji" | ||||||
| emojiName: "Jméno emoji" | emojiName: "Jméno emoji" | ||||||
| emojiUrl: "URL obrázku" | emojiUrl: "URL obrázku" | ||||||
| addEmoji: "Přidat emoji" | addEmoji: "Přidat emoji" | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,19 +1,19 @@ | |||||||
| --- | --- | ||||||
| _lang_: "Esperanto" | _lang_: "Esperanto" | ||||||
| headlineMisskey: "Reto ligiĝas per notoj" | headlineMisskey: "Reto ligiĝanta per notoj" | ||||||
| introMisskey: "Bonvenon! Miskejo estas malferma kodaの分散型マイクロブログサービスです。\nBonvolu Krei「noto」、いま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「 reaktigoj 」機能で、皆のnotojに素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀" | introMisskey: "Bonvenon! Misskey (Ĉi-sekve Miskejo) estas malfermitkoda malcentriza mikrobloga servo.\nKreu \"noto\"n por ke kunhavu tion kio nun okazas aŭ ke eksendu tion kio pri vi📡\nFunkcion \"reago\" vi povas uzi kaj aldoni vian reagon pri ciu noto de ĉiu homo👍\nVolu esplori nova mondo🚀" | ||||||
| monthAndDay: "{day}-a/{month}" | monthAndDay: "{day}-a/{month}" | ||||||
| search: "Serĉi" | search: "Serĉi" | ||||||
| notifications: "Sciigoj" | notifications: "Sciigoj" | ||||||
| username: "Uzantonomo" | username: "Uzantnomo" | ||||||
| password: "Pasvorto" | password: "Pasvorto" | ||||||
| forgotPassword: "Ĉu vi forgesis pasvorton?" | forgotPassword: "Ĉu vi forgesis pasvorton?" | ||||||
| fetchingAsApObject: "Informpetado de fediverso..." | fetchingAsApObject: "Informpetado de Fediverso..." | ||||||
| ok: "Okej" | ok: "Akcepteble" | ||||||
| gotIt: "Mi konprenas!" | gotIt: "Mi konprenas!" | ||||||
| cancel: "Nuligi" | cancel: "Nuligi" | ||||||
| enterUsername: "Entajpu uzantonomon" | enterUsername: "Entajpu uzantnomon" | ||||||
| renotedBy: "Renotigojn faras {user}" | renotedBy: "Renoton faras {user}" | ||||||
| noNotes: "Neniu noto!" | noNotes: "Neniu noto!" | ||||||
| noNotifications: "Vi ne havas sciigojn." | noNotifications: "Vi ne havas sciigojn." | ||||||
| instance: "Ekzemplo" | instance: "Ekzemplo" | ||||||
| @@ -22,7 +22,8 @@ basicSettings: "Ĝeneralaj agordoj" | |||||||
| otherSettings: "Aliaj agordoj" | otherSettings: "Aliaj agordoj" | ||||||
| openInWindow: "Malfermi en nova fenestro" | openInWindow: "Malfermi en nova fenestro" | ||||||
| profile: "Profilo" | profile: "Profilo" | ||||||
| timeline: "Tempolinio" | timeline: "Templinio" | ||||||
|  | noAccountDescription: "Tiu uzanto ne enhavas biografion je la profilo." | ||||||
| login: "Ensaluti" | login: "Ensaluti" | ||||||
| loggingIn: "Ensalutado..." | loggingIn: "Ensalutado..." | ||||||
| logout: "Elsaluti" | logout: "Elsaluti" | ||||||
| @@ -42,33 +43,34 @@ unpin: "Depingli" | |||||||
| copyContent: "Kopii enhavon" | copyContent: "Kopii enhavon" | ||||||
| copyLink: "Kopii ligilon" | copyLink: "Kopii ligilon" | ||||||
| delete: "Forviŝi" | delete: "Forviŝi" | ||||||
| deleteAndEdit: "Forviŝi kaj redakti" | deleteAndEdit: "Foriginte redakti" | ||||||
| deleteAndEditConfirm: "Ĉu vi certas, ke vi volas forviŝi la noton? La reaktigoj, renotigoj, kaj respondoj ankaŭ forigiĝos." | deleteAndEditConfirm: "Ĉu vi certas, ke vi volas forigi la noton kaj redakti ĝin? Ĉiuj reagoj, renotoj, kaj respondoj ankaŭ foriĝos." | ||||||
| addToList: "Aldoni al listo" | addToList: "Aldoni al la listo" | ||||||
| sendMessage: "Sendi mesaĝon" | sendMessage: "Sendi mesaĝon" | ||||||
| copyUsername: "Kopii uzantonomon" | copyUsername: "Kopii uzantnomon" | ||||||
| searchUser: "Serĉi uzanton" | searchUser: "Serĉi uzanton" | ||||||
| reply: "Respondi" | reply: "Respondi" | ||||||
| loadMore: "Vidu plu" | loadMore: "Vidu pli" | ||||||
| showMore: "Vidi plu" | showMore: "Vidi pli" | ||||||
| youGotNewFollower: "Vi estas eksekvita." | youGotNewFollower: "Vin eksekvis" | ||||||
|  | receiveFollowRequest: "Eksekvopeton riceviĝis." | ||||||
|  | followRequestAccepted: "La eksekvopeto akceptiĝis." | ||||||
| mention: "Mencioj" | mention: "Mencioj" | ||||||
| mentions: "Mencioj" | mentions: "Al vi" | ||||||
| directNotes: "Senperaj notoj" |  | ||||||
| importAndExport: "Importaĵo / Eksportaĵo" | importAndExport: "Importaĵo / Eksportaĵo" | ||||||
| import: "Importi" | import: "Importi" | ||||||
| export: "Eksporti" | export: "Eksporti" | ||||||
| files: "Dosieroj" | files: "Dosieroj" | ||||||
| download: "Elŝuti" | download: "Elŝuti" | ||||||
| driveFileDeleteConfirm: "Ĉu vi certas ke vi volas forviŝi la dosieron \"{name}\"? La notoj kun la aldonaĵo ankaŭ forviŝiĝos." | driveFileDeleteConfirm: "Ĉu vi certas ke vi volas forviŝi la dosierujon {name}? Noto aldonita ĝin ankaŭ foriĝos." | ||||||
| unfollowConfirm: "Ĉu vi certas, ke vi volas ne plu sekvi {name}?" | unfollowConfirm: "Ĉu vi certas, ke vi volas ne plu sekvi {name}'(o)n?" | ||||||
| lists: "Listoj" | lists: "Listoj" | ||||||
| noLists: "Neniu listo" | noLists: "Neniu listo" | ||||||
| note: "Elsendi noto" | note: "Elsendi noto" | ||||||
| notes: "Notoj" | notes: "Notoj" | ||||||
| following: "Sekvi" | following: "Sekvatoj" | ||||||
| followers: "Sekvantoj" | followers: "Sekvantoj" | ||||||
| followsYou: "Sekvas vin" | followsYou: "Vin sekvas " | ||||||
| createList: "Kreii liston" | createList: "Kreii liston" | ||||||
| error: "Eraro" | error: "Eraro" | ||||||
| somethingHappened: "Problemo okazis." | somethingHappened: "Problemo okazis." | ||||||
| @@ -76,13 +78,15 @@ retry: "Reprovi" | |||||||
| enterListName: "Entajpu nomon de la listo" | enterListName: "Entajpu nomon de la listo" | ||||||
| privacy: "Privateco" | privacy: "Privateco" | ||||||
| follow: "Sekvi" | follow: "Sekvi" | ||||||
| followRequest: "Peti eksekvi" | followRequest: "Peti akcepti de vi eksekvi" | ||||||
| followRequests: "Eksekvopetoj" | followRequests: "Eksekvopetoj" | ||||||
| unfollow: "Ne plu sekvi" | unfollow: "Ne plu sekvi" | ||||||
| renote: "Renotici" | enterEmoji: "Entajpu emoĵion" | ||||||
| unrenote: "Forigi renotici" | renote: "Fari renoton" | ||||||
| cantRenote: "Tiu noto estas renototebla." | unrenote: "Malfari renoton" | ||||||
| cantReRenote: "Renotigo ne estas renotigebla." | renoted: "Renoton fariĝis." | ||||||
|  | cantRenote: "Tiu noto ne estas renototebla." | ||||||
|  | cantReRenote: "Oni ne povas fari renoton kiu enhavas renoto." | ||||||
| quote: "Citi" | quote: "Citi" | ||||||
| pinnedNote: "Pinglita noto" | pinnedNote: "Pinglita noto" | ||||||
| pinned: "Alpingli sur la profilo" | pinned: "Alpingli sur la profilo" | ||||||
| @@ -103,22 +107,38 @@ unblockConfirm: "Ĉu vi certas ke vi volas malbloki la uzanton?" | |||||||
| suspendConfirm: "Ĉu vi certas ke vi volas frostigi la uzanton?" | suspendConfirm: "Ĉu vi certas ke vi volas frostigi la uzanton?" | ||||||
| unsuspendConfirm: "Ĉu vi certas ke vi volas fandi la uzanton?" | unsuspendConfirm: "Ĉu vi certas ke vi volas fandi la uzanton?" | ||||||
| selectList: "Elekti liston" | selectList: "Elekti liston" | ||||||
| emojiUrl: "Retadreso de la emoĵio" | selectAntenna: "Elekti antenon" | ||||||
|  | selectWidget: "Elekti enestraĵon" | ||||||
|  | editWidgets: "Redakti fenestraĵon" | ||||||
|  | editWidgetsExit: "Fini la redaktadon" | ||||||
|  | customEmojis: "Personecigitaj emoĵioj" | ||||||
|  | emoji: "Emoĵio" | ||||||
|  | emojis: "Emoĵio" | ||||||
|  | emojiName: "Nomo de emoĵio" | ||||||
|  | emojiUrl: "URL de la bildo de emoĵio" | ||||||
|  | addEmoji: "Aldoni emoĵion" | ||||||
|  | cacheRemoteFiles: "Havi staplon por foraj dosieroj" | ||||||
| flagAsBot: "Tiu uzanto estas roboto" | flagAsBot: "Tiu uzanto estas roboto" | ||||||
| flagAsCat: "Tiu uzanto estas kato" | flagAsCat: "Tiu uzanto estas kato" | ||||||
| addAccount: "Aldoni konton" | addAccount: "Aldoni konton" | ||||||
| showOnRemote: "Vidi sur la transa ekzemplo" | showOnRemote: "Vidi sur la fora ekzemplo" | ||||||
| general: "Ĝenerala" | general: "Ĝenerala" | ||||||
| searchWith: "Serĉi: {q}" | searchWith: "Serĉi: {q}" | ||||||
| youHaveNoLists: "Vi ne havas listojn." | youHaveNoLists: "Vi ne havas listojn." | ||||||
| followConfirm: "Ĉu vi certas, ke vi volas sekvi {name}'n?" | followConfirm: "Ĉu vi certas ke vi volas sekvi {name}'(o)n?" | ||||||
| selectUser: "Elekti uzanton" | selectUser: "Elekti uzanton" | ||||||
| annotation: "Komentarioj" | annotation: "Komentarioj" | ||||||
| federation: "Fediverso" | federation: "Fediverso" | ||||||
| instances: "Ekzemplo" | instances: "Ekzemplo" | ||||||
|  | perHour: "Po horo" | ||||||
|  | perDay: "Po tago" | ||||||
| blockThisInstance: "Bloki tiu ekzemplo" | blockThisInstance: "Bloki tiu ekzemplo" | ||||||
|  | withNFiles: "{n} dosiero(j)" | ||||||
| disk: "Diskilo" | disk: "Diskilo" | ||||||
| blockedInstances: "Blokitaj ekzemploj" | instanceInfo: "Informo pri la ekzemplo" | ||||||
|  | clearCachedFiles: "Forviŝi datumon en staplo" | ||||||
|  | clearCachedFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn transajn dosierojn en la staplo?" | ||||||
|  | blockedInstances: "Blokataj ekzemploj" | ||||||
| muteAndBlock: "Silentitaj / Blokitaj" | muteAndBlock: "Silentitaj / Blokitaj" | ||||||
| mutedUsers: "Silentigitaj uzantoj" | mutedUsers: "Silentigitaj uzantoj" | ||||||
| blockedUsers: "Blokitaj uzantoj" | blockedUsers: "Blokitaj uzantoj" | ||||||
| @@ -131,8 +151,8 @@ federating: "Konfederado" | |||||||
| blocked: "Blokita" | blocked: "Blokita" | ||||||
| subscribing: "Abonita" | subscribing: "Abonita" | ||||||
| notResponding: "Alvokato ne disponeblas" | notResponding: "Alvokato ne disponeblas" | ||||||
| instanceFollowing: "Sekvi ekzemplon" | instanceFollowing: "Sekvatoj sur la ekzemplo" | ||||||
| instanceFollowers: "Sekvantoj de la ekzemplo" | instanceFollowers: "Sekvantoj el la ekzemplo" | ||||||
| instanceUsers: "Uzantoj de la ekzemplo" | instanceUsers: "Uzantoj de la ekzemplo" | ||||||
| changePassword: "Ŝanĝi pasvorton" | changePassword: "Ŝanĝi pasvorton" | ||||||
| currentPassword: "Aktuala pasvorto" | currentPassword: "Aktuala pasvorto" | ||||||
| @@ -140,164 +160,309 @@ newPassword: "Nova pasvorto" | |||||||
| newPasswordRetype: "Reentajpu la novan pasvorton" | newPasswordRetype: "Reentajpu la novan pasvorton" | ||||||
| attachFile: "Aldoni dosieron" | attachFile: "Aldoni dosieron" | ||||||
| more: "Plu!" | more: "Plu!" | ||||||
| usernameOrUserId: "Uzantonomo aŭ ID de uzanto" | usernameOrUserId: "Uzantnomo aŭ identigilo de uzanto" | ||||||
| noSuchUser: "Neniuj uzantoj trovitaj." | noSuchUser: "Neniuj uzantoj trovitaj." | ||||||
| remove: "Forviŝi" | imageUrl: "URL de bildo" | ||||||
|  | remove: "Forigi" | ||||||
| removed: "Forviŝis" | removed: "Forviŝis" | ||||||
| removeAreYouSure: "Ĉu vi certas ke vi volas forigi \"{x}\"?" | removeAreYouSure: "Ĉu vi certas ke vi volas forigi \"{x}\"'(o)n?" | ||||||
| deleteAreYouSure: "Ĉu vi certas ke vi volas forigi \"{x}\"?" | deleteAreYouSure: "Ĉu vi certas ke vi volas forviŝi \"{x}\"'(o)n?" | ||||||
| messaging: "Babilejoj" | messaging: "Retbabili" | ||||||
| upload: "Alŝuti" | upload: "Alŝuti" | ||||||
| fromDrive: "De la diskilo" | fromDrive: "De la diskingo en Miskejo" | ||||||
| fromUrl: "De retadreso" | fromUrl: "De URL" | ||||||
| uploadFromUrl: "Aldoni de retadreso" | uploadFromUrl: "Alŝuti de URL" | ||||||
| uploadFromUrlDescription: "Retadreso de la dosiero kiun vi volu alŝuti" | uploadFromUrlDescription: "URL de la dosiero kiun vi volu alŝuti" | ||||||
| games: "Ludoj sur Miskejo" | games: "Ludoj sur Miskejo" | ||||||
| messageRead: "Legita" | messageRead: "Legita" | ||||||
| startMessaging: "Komenci babiladon" | startMessaging: "Komenci babiladon" | ||||||
| tos: "Kondiĉoj de Uzado" | tos: "Kondiĉoj de Uzado" | ||||||
| start: "Komenciĝi" | start: "Komenciĝi" | ||||||
| home: "Ĉefpaĝo" | home: "Hejmo" | ||||||
| drive: "Diskilo" | remoteUserCaution: "Ĉi tiu Infomoj estas ne tute ekzakta pro distanca uzanto." | ||||||
|  | images: "Bildoj" | ||||||
|  | birthday: "Naskiĝtago" | ||||||
|  | registeredDate: "Registriĝdato" | ||||||
|  | drive: "Diskingo" | ||||||
| fileName: "Dosiernomo" | fileName: "Dosiernomo" | ||||||
| selectFile: "Elekti dosieron" | selectFile: "Elekti dosieron" | ||||||
| selectFiles: "Elekti dosieron" | selectFiles: "Elekti dosieron" | ||||||
| renameFile: "Renomigi dosieron" | renameFile: "Alinomi la dosieron" | ||||||
|  | folderName: "Nomo de la dosierujo" | ||||||
|  | renameFolder: "Alinomi la dosierujon" | ||||||
| deleteFolder: "Forviŝi dosierujon" | deleteFolder: "Forviŝi dosierujon" | ||||||
| addFile: "Aldoni dosieron" | addFile: "Aldoni dosieron" | ||||||
| emptyDrive: "La diskilo enhavas neniun." | emptyDrive: "La diskingo enhavas neniun." | ||||||
| unableToDelete: "Ne forigebla" | unableToDelete: "Ne forigebla" | ||||||
| inputNewFileName: "Entajpu nova dosiernomon" | inputNewFileName: "Entajpu nova dosiernomon" | ||||||
| hasChildFilesOrFolders: "La dosierujo estas neforviŝebla pro tio, ke ĝi enhavas dosieron." | inputNewFolderName: "Entajpu nova nomon de la dosierujo" | ||||||
| copyUrl: "Kopii retadreson" | hasChildFilesOrFolders: "La dosierujo enhavas dosieron kaj ne estas forigebla." | ||||||
|  | copyUrl: "Kopii URL" | ||||||
|  | rename: "Alinomi" | ||||||
|  | avatar: "Ikono" | ||||||
| nsfw: "Enhavo ne estas deca por laborejo (NSFW)" | nsfw: "Enhavo ne estas deca por laborejo (NSFW)" | ||||||
| instanceName: "Nomo de la ekzemplo" | instanceName: "Nomo de la ekzemplo" | ||||||
|  | maintainerName: "Nomo de la administranto" | ||||||
|  | maintainerEmail: "Retpoŝto de la administranto" | ||||||
|  | tosUrl: "URL de kondiĉoj de uzado" | ||||||
|  | thisYear: "Ĉi-jare" | ||||||
|  | thisMonth: "Ĉi-monate" | ||||||
|  | today: "Hodiaŭ" | ||||||
|  | dayX: "{day}-a" | ||||||
|  | monthX: "{month}" | ||||||
|  | yearX: "La jaro {year}" | ||||||
| connectService: "Konekti" | connectService: "Konekti" | ||||||
| disconnectService: "Farkonektiĝi" | disconnectService: "Farkonektiĝi" | ||||||
| driveCapacityPerLocalAccount: "Volumo po unu loka-uzanto" | driveCapacityPerLocalAccount: "Volumo de miskej-diskingo po unu loka uzanto" | ||||||
| driveCapacityPerRemoteAccount: "Volumo po unu transa uzanto" | driveCapacityPerRemoteAccount: "Volumo de miskej-diskingo po unu transa uzanto" | ||||||
|  | iconUrl: "URL de la ikono (retpaĝsimbolo, ktp.)" | ||||||
| pinnedUsers: "Alpinglita uzanto" | pinnedUsers: "Alpinglita uzanto" | ||||||
| pinnedNotes: "Pinglita noto" | pinnedNotes: "Pinglita noto" | ||||||
|  | name: "Nomo" | ||||||
| withFileAntenna: "Nur kun aldonaĵo" | withFileAntenna: "Nur kun aldonaĵo" | ||||||
| notesAndReplies: "Kun respondoj" | notesAndReplies: "Kun respondoj" | ||||||
| withFiles: "Kun aldonaĵo" | withFiles: "Kun aldonaĵo" | ||||||
| silenceConfirm: "Ĉu vi certas ke vi volas silentigi la uzanton?" | silenceConfirm: "Ĉu vi certas ke vi volas silentigi la uzanton?" | ||||||
| unsilenceConfirm: "Ĉu vi certas ke vi volas malsilentigi la uzanton?" | unsilenceConfirm: "Ĉu vi certas, ke vi ne plu volas ke la uzanto silentas?" | ||||||
|  | popularTags: "Popularaj kradvortoj" | ||||||
| userList: "Listoj" | userList: "Listoj" | ||||||
| aboutMisskey: "Pri Miskejo" | aboutMisskey: "Pri Miskejo" | ||||||
|  | securityKeyName: "Nomo de la ŝlosilo" | ||||||
| passwordLessLogin: "Ensaluti sen pasvorto" | passwordLessLogin: "Ensaluti sen pasvorto" | ||||||
| resetPassword: "Restarigi pasvorton" | resetPassword: "Restarigi pasvorton" | ||||||
| newPasswordIs: "La nova pasvorto estas {password}." | newPasswordIs: "La nova pasvorto estas {password}." | ||||||
|  | cacheClear: "Forviŝi datumon en stalo" | ||||||
|  | help: "Manlibro de uzado" | ||||||
| inputMessageHere: "Entajpu masaĝo tie ĉi" | inputMessageHere: "Entajpu masaĝo tie ĉi" | ||||||
|  | groupName: "Grupa nomo" | ||||||
|  | messagingWithUser: "Mesaĝado kun uzanto" | ||||||
|  | messagingWithGroup: "Mesaĝi kun grupo" | ||||||
| noteOf: "Noto de {user}" | noteOf: "Noto de {user}" | ||||||
|  | noMessagesYet: "Neniu mesaĝo" | ||||||
| newMessageExists: "Vi ricevis novan mesaĝon." | newMessageExists: "Vi ricevis novan mesaĝon." | ||||||
| onlyOneFileCanBeAttached: "Vi povas aldoni nur unu dosieron po unu mesaĝo." | onlyOneFileCanBeAttached: "Vi povas aldoni nur unu dosieron po unu mesaĝo." | ||||||
| uiLanguage: "Lingvo de la interfaco" | uiLanguage: "Lingvo de la interfaco" | ||||||
|  | tags: "Etikedoj" | ||||||
|  | createAccount: "Krei konton" | ||||||
|  | existingAccount: "Ekzista konto" | ||||||
| noFollowRequests: "Vi ne havas eksekvopetojn." | noFollowRequests: "Vi ne havas eksekvopetojn." | ||||||
|  | openImageInNewTab: "Fermi la bildo sur nova tablo" | ||||||
| local: "Loka" | local: "Loka" | ||||||
| remote: "Transa" | remote: "Transa" | ||||||
|  | accountSettings: "Agordoj de Konto" | ||||||
|  | numberOfDays: "Nombro de tagoj" | ||||||
| hideThisNote: "Kaŝi tiun noton" | hideThisNote: "Kaŝi tiun noton" | ||||||
| deleteAllFiles: "Forvisi ĉiujn dosierojn" | objectStorageBaseUrl: "Baza URL" | ||||||
|  | deleteAll: "Forviŝi ĉiujn" | ||||||
|  | showInPage: "Vidi en paĝo" | ||||||
|  | deleteAllFiles: "Forviŝi ĉiujn dosierojn" | ||||||
| deleteAllFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn viajn dosierojn?" | deleteAllFilesConfirm: "Ĉu vi certas, ke vi volas forviŝi ĉiujn viajn dosierojn?" | ||||||
|  | deletedNote: "Forviŝita noto" | ||||||
| invisibleNote: "Malpublika noto" | invisibleNote: "Malpublika noto" | ||||||
|  | poll: "Balotujo" | ||||||
| emailServer: "Retpoŝta servilo" | emailServer: "Retpoŝta servilo" | ||||||
| email: "Retpoŝto" | email: "Retpoŝto" | ||||||
| emailAddress: "Retpoŝtadreso" | emailAddress: "Retpoŝta adreso" | ||||||
| smtpUser: "Uzantonomo" | smtpUser: "Uzantnomo" | ||||||
| smtpPass: "Pasvorto" | smtpPass: "Pasvorto" | ||||||
| userSaysSomething: "{name} parolis ion" | userSaysSomething: "{name} parolis ion" | ||||||
|  | display: "Vidi" | ||||||
| database: "Datumbazo" | database: "Datumbazo" | ||||||
| channel: "Kanalo" | channel: "Kanalo" | ||||||
| fileIdOrUrl: "Dosirero ID aŭ retadreso" | fileIdOrUrl: "Dosiera identigilo aŭ URL" | ||||||
|  | abuseReports: "Signali" | ||||||
|  | reportAbuse: "Signali" | ||||||
|  | reportAbuseOf: "Signali {name}'(o)n" | ||||||
| send: "Sendi" | send: "Sendi" | ||||||
| i18nInfo: "Tradukojn de Misskey en diversaj lingvoj faras volontuloj. Vi povus kunlabori en tradukado sur {link}, se vi volus." | i18nInfo: "Misskey estas tradukata en diversaj lingvoj far volontuloj. Oni povas kontribui por la tradukado sur {link}." | ||||||
| driveFilesCount: "Numero de dosieroj en la diskilo" | followingCount: "Numero de sekvatoj" | ||||||
| onlineUsersCount: "{n} uzanto(j) estas surkonektita" | followersCount: "Numero de sekvantoj" | ||||||
|  | yes: "Jes" | ||||||
|  | no: "Ne" | ||||||
|  | driveFilesCount: "Numero de dosieroj en la diskingo" | ||||||
|  | noteFavoritesCount: "Numero de la preferataj notoj" | ||||||
|  | makeExplorable: "Igi videbla konto sur la paĝo \"Esplorado\"" | ||||||
|  | showTitlebar: "Montri titolobredon" | ||||||
|  | clearCache: "Forviŝi datumon en staplo" | ||||||
|  | onlineUsersCount: "{n} uzanto(j) estas surlinea" | ||||||
| nUsers: "{n} uzanto(j)" | nUsers: "{n} uzanto(j)" | ||||||
|  | saveAs: "Konservi kiel…" | ||||||
|  | createdAt: "Kreita je" | ||||||
|  | updatedAt: "Laste ĝisdatigita" | ||||||
|  | deleteConfirm: "Ĉu certas forviŝi?" | ||||||
|  | closeAccount: "Forigi konton" | ||||||
| emailNotification: "Sciigoj per retpoŝto" | emailNotification: "Sciigoj per retpoŝto" | ||||||
| publish: "Publikigi" | publish: "Publikigi" | ||||||
| inChannelSearch: "Serĉi en kanalo" | inChannelSearch: "Serĉi en kanalo" | ||||||
| typingUsers: "{users} estas entajpanta(j)..." | typingUsers: "{users} estas entajpanta(j)..." | ||||||
| online: "Surkonektita" | online: "Surkonektita" | ||||||
| offline: "Forkonektita" | offline: "Forkonektita" | ||||||
| instanceBlocking: "Ekzempla blokado" | instanceBlocking: "Blokado de ekzemplo" | ||||||
|  | selectAccount: "Elekti konton" | ||||||
| user: "Uzanto" | user: "Uzanto" | ||||||
|  | accounts: "Kontoj" | ||||||
|  | global: "Konfederacia" | ||||||
|  | sent: "Sendi" | ||||||
|  | hashtags: "Kradvorto" | ||||||
| _gallery: | _gallery: | ||||||
|   liked: "Ŝatitaj notoj" |   liked: "Ŝatitaj notoj" | ||||||
| _email: | _email: | ||||||
|   _follow: |   _follow: | ||||||
|     title: "Vi estas eksekvita." |     title: "Vin eksekvis" | ||||||
|   _receiveFollowRequest: |   _receiveFollowRequest: | ||||||
|     title: "Vi ricevis eksekvopeton." |     title: "Vi ricevis eksekvopeton." | ||||||
| _aboutMisskey: | _aboutMisskey: | ||||||
|   about: "Misskey estas malferma koda programo evoluigata far syuilo ekde la 2014." |   about: "Misskey estas malfermitkoda programo evoluigata de syuilo ekde la 2014." | ||||||
|  |   contributors: "Precipaj kontribuantoj" | ||||||
|  |   allContributors: "Ĉiuj kontribuintoj" | ||||||
|   source: "Fontkodo" |   source: "Fontkodo" | ||||||
|   translation: "Traduki Misskey'on" |   translation: "Traduki Miskejon" | ||||||
|  |   patrons: "Mecenatoj" | ||||||
| _mfm: | _mfm: | ||||||
|   mention: "Mencioj" |   mention: "Mencioj" | ||||||
|   url: "Retadreso" |   hashtag: "Kradvorto" | ||||||
|  |   url: "URL" | ||||||
|   blockCode: "Kodo (Ujo)" |   blockCode: "Kodo (Ujo)" | ||||||
|   blockMath: "Formulo (Ujo)" |   blockMath: "Formulo (Ujo)" | ||||||
|   quote: "Citi" |   quote: "Citi" | ||||||
|  |   emoji: "Personecigitaj emoĵioj" | ||||||
|   search: "Serĉi" |   search: "Serĉi" | ||||||
| _instanceTicker: | _instanceTicker: | ||||||
|   none: "Ne montri" |   none: "Ne montri" | ||||||
|   remote: "Montri al transaj uzantoj" |   remote: "Montri al transaj uzantoj" | ||||||
|  |   always: "Ĉiam montri" | ||||||
| _channel: | _channel: | ||||||
|   create: "Krei kanalon" |   create: "Krei kanalon" | ||||||
|   edit: "Redakti kanalon" |   edit: "Redakti kanalon" | ||||||
|   following: "Sekvaton" |   following: "Sekvata" | ||||||
|  | _menuDisplay: | ||||||
|  |   hide: "Kaŝi" | ||||||
|  | _wordMute: | ||||||
|  |   muteWords: "Silentanta vorto" | ||||||
|  |   mutedNotes: "Silentigataj notoj" | ||||||
| _theme: | _theme: | ||||||
|   keys: |   keys: | ||||||
|  |     hashtag: "Kradvorto" | ||||||
|     mention: "Mencioj" |     mention: "Mencioj" | ||||||
|     renote: "Renotici" |     renote: "Fari renoton" | ||||||
| _sfx: | _sfx: | ||||||
|   note: "Nova noto" |   note: "Nova noto" | ||||||
|  |   noteMy: "Mia noto" | ||||||
|   notification: "Sciigoj" |   notification: "Sciigoj" | ||||||
|   chat: "Babilejoj" |   chat: "Retbabilejo" | ||||||
|   channel: "Kanala sciigoj" |   chatBg: "Retbabilejo (BG)" | ||||||
|  |   antenna: "Ricevo de anteno" | ||||||
|  |   channel: "Sciigoj de kanalo" | ||||||
|  | _ago: | ||||||
|  |   secondsAgo: "Antaŭ {n} sekundoj" | ||||||
|  |   minutesAgo: "Antaŭ {n} minutoj" | ||||||
|  |   hoursAgo: "Antaŭ {n} horoj" | ||||||
|  |   daysAgo: "Antaŭ {n} tagoj" | ||||||
|  |   weeksAgo: "Antaŭ {n} semajnoj" | ||||||
|  |   monthsAgo: "Antaŭ {n} monatoj" | ||||||
|  |   yearsAgo: "Antaŭ {n} jaroj" | ||||||
|  | _time: | ||||||
|  |   second: "sek" | ||||||
|  |   minute: "min" | ||||||
|  |   hour: "hor" | ||||||
|  |   day: "Tago" | ||||||
| _tutorial: | _tutorial: | ||||||
|   title: "Uzado de Miskejo" |   title: "Uzado de Misskey" | ||||||
|  |   step1_1: "Bonvenon." | ||||||
|  |   step7_2: "Se vi volus scii pli pri Miskejon, volu rigardi la fako {help}." | ||||||
| _permissions: | _permissions: | ||||||
|   "read:blocks": "Vidi la listo de la uzantoj kiun vi blokis." |   "read:blocks": "Vidi la listo de la uzantoj kiun vi blokis." | ||||||
|   "read:drive": "Vidi dosierojn en la diskilo" |   "write:blocks": "Redakti la liston de la uzantoj kiun vi blokis." | ||||||
|   "read:channels": "Legi kanalon" |   "read:drive": "Ĉia operacio por legi la informon de dosiero en via diskingo de Miskejo" | ||||||
|  |   "write:drive": "Ĉia operacio por skribi, forviŝi, aŭ alimaniere ŝanĝi la informon de dosiero en via diskingo de Miskejo" | ||||||
|  |   "read:favorites": "Vidi la listo de la preferoj" | ||||||
|  |   "read:following": "Vidi tion kion vi sekvas" | ||||||
|  |   "write:following": "Sekvi kaj/aŭ malsekvi alian uzanton" | ||||||
|  |   "read:messaging": "Vidi via retbabilado" | ||||||
|  |   "write:notes": "Krei / Forviŝi noton" | ||||||
|  |   "read:notifications": "Vidi sciigojn" | ||||||
|  |   "read:reactions": "Vidi reagojn" | ||||||
|  |   "read:pages": "Vidi via paĝojn" | ||||||
|  |   "read:page-likes": "Vidi ŝatojn de paĝo" | ||||||
|  |   "read:channels": "Vidi kanalojn" | ||||||
|  | _antennaSources: | ||||||
|  |   homeTimeline: "Notoj far uzantoj sekvataj de vi" | ||||||
|  | _weekday: | ||||||
|  |   sunday: "dimanĉo" | ||||||
|  |   monday: "lundo" | ||||||
|  |   tuesday: "mardo" | ||||||
|  |   wednesday: "merkredo" | ||||||
|  |   thursday: "ĵaŭdo" | ||||||
|  |   friday: "vendredo" | ||||||
|  |   saturday: "sabato" | ||||||
| _widgets: | _widgets: | ||||||
|   notifications: "Sciigoj" |   notifications: "Sciigoj" | ||||||
|   timeline: "Tempolinio" |   timeline: "Templinio" | ||||||
|  |   clock: "Horloĝo" | ||||||
|   federation: "Fediverso" |   federation: "Fediverso" | ||||||
|   onlineUsers: "Surkonektita uzanto" |   onlineUsers: "Surkonektita uzanto" | ||||||
| _cw: | _cw: | ||||||
|   show: "Vidu plu" |   show: "Vidu pli" | ||||||
|   files: "{count} dosiero(j)" |   files: "{count} dosiero(j)" | ||||||
|  | _poll: | ||||||
|  |   choiceN: "Balotilo {n}" | ||||||
|  |   noMore: "Oni ne plu povas aldoni." | ||||||
|  |   infinite: "Neniam" | ||||||
|  |   deadlineTime: "hor" | ||||||
|  |   votesCount: "{n} balotiloj" | ||||||
|  |   vote: "Baloti" | ||||||
|  |   closed: "Oni jam balotis ĝin" | ||||||
| _visibility: | _visibility: | ||||||
|   publicDescription: "Via noto aperiĝos sur konfederacia tempolinio" |   publicDescription: "Via noto aperiĝos sur la konfederacia templinio" | ||||||
|   home: "Ĉefpaĝo" |   home: "Hejmo" | ||||||
|   homeDescription: "Elsendi nur sur hejma tempolinio" |   homeDescription: "Elsendi nur sur la hejmtemplinio" | ||||||
|   followers: "Sekvantoj" |   followers: "Sekvantoj" | ||||||
|   followersDescription: "Elsendi nur al sekvantoj de mi" |   followersDescription: "Elsendi nur al sekvantoj al mi" | ||||||
|   localOnly: "Nur loka" |   localOnly: "Nur loka" | ||||||
|   localOnlyDescription: "Nelegabla al transaj uzantoj" |   localOnlyDescription: "Ne montri al transaj uzantoj" | ||||||
| _postForm: | _postForm: | ||||||
|   channelPlaceholder: "Elsendi sur la kanalo" |   replyPlaceholder: "Respondado al tiu noto..." | ||||||
|  |   quotePlaceholder: "Citado tiun noton..." | ||||||
|  |   channelPlaceholder: "Sendi sur la kanalo" | ||||||
| _profile: | _profile: | ||||||
|   username: "Uzantonomo" |   name: "Nomo" | ||||||
|  |   username: "Uzantnomo" | ||||||
|  |   changeAvatar: "Ŝanĝi profilbildon" | ||||||
| _exportOrImport: | _exportOrImport: | ||||||
|   followingList: "Sekvi" |   followingList: "Sekvataj" | ||||||
|   muteList: "Silentigi" |   muteList: "Silentigado" | ||||||
|   blockingList: "Blokado" |   blockingList: "Blokado" | ||||||
|   userLists: "Listoj" |   userLists: "Listoj" | ||||||
| _timelines: | _timelines: | ||||||
|   home: "Hejmo" |   home: "Hejmo" | ||||||
|   local: "Loka" |   local: "Loka" | ||||||
|   social: "Hejmo kaj loka" |   social: "Sociala" | ||||||
|  |   global: "Konfederacia" | ||||||
| _rooms: | _rooms: | ||||||
|  |   translate: "Movi" | ||||||
|  |   chooseImage: "Elekti bildon" | ||||||
|   _furnitures: |   _furnitures: | ||||||
|     server: "Servilo" |     server: "Servilo" | ||||||
|  |     moon: "La luno" | ||||||
| _pages: | _pages: | ||||||
|  |   deleted: "La paĝo estas forigita." | ||||||
|  |   viewPage: "Vidi via paĝojn" | ||||||
|  |   my: "Miaj paĝoj" | ||||||
|   content: "Blokado de paĝo" |   content: "Blokado de paĝo" | ||||||
|   url: "Retadreso de la paĝo" |   url: "URL de paĝo" | ||||||
|   chooseBlock: "Aldoni blokado" |   chooseBlock: "Aldoni ujon" | ||||||
|  |   blocks: | ||||||
|  |     image: "Bildoj" | ||||||
|  |     _post: | ||||||
|  |       canvasId: "Kanvasa identigilo" | ||||||
|  |     _canvas: | ||||||
|  |       id: "Kanvasa identigilo" | ||||||
|  |     _note: | ||||||
|  |       id: "Identigilo de noto" | ||||||
|  |     _button: | ||||||
|  |       _action: | ||||||
|  |         _pushEvent: | ||||||
|  |           event: "Nomo de la evento" | ||||||
|   script: |   script: | ||||||
|     categories: |     categories: | ||||||
|       list: "Listoj" |       list: "Listoj" | ||||||
| @@ -317,21 +482,26 @@ _pages: | |||||||
|         arg1: "Listoj" |         arg1: "Listoj" | ||||||
|     types: |     types: | ||||||
|       array: "Listoj" |       array: "Listoj" | ||||||
|  |       stringArray: "List de teksto" | ||||||
| _notification: | _notification: | ||||||
|   fileUploaded: "La dosiero sukcese alŝutiĝis." |   fileUploaded: "La dosiero sukcese alŝutiĝis." | ||||||
|   youWereFollowed: "Vi estas eksekvita." |   youGotPoll: "{name} balotis" | ||||||
|  |   youGotMessagingMessageFromUser: "{name} sentis mesaĝon al vi." | ||||||
|  |   youWereFollowed: "Vin eksekvis" | ||||||
|   youReceivedFollowRequest: "Vi ricevis eksekvopeton." |   youReceivedFollowRequest: "Vi ricevis eksekvopeton." | ||||||
|   yourFollowRequestAccepted: "Via eksekvopeto estas akceptita." |   yourFollowRequestAccepted: "Via eksekvopeto estas akceptita." | ||||||
|   _types: |   _types: | ||||||
|     follow: "Sekvi" |     follow: "Sekvatoj" | ||||||
|     mention: "Mencioj" |     mention: "Mencioj" | ||||||
|     renote: "Renotici" |     renote: "Fari renoton" | ||||||
|     quote: "Citi" |     quote: "Citi" | ||||||
|     reaction: "Reagoj" |     reaction: "Reagoj" | ||||||
|     receiveFollowRequest: "Eksekvopeto ricevita" |     receiveFollowRequest: "Eksekvopeto ricevita" | ||||||
|  |     followRequestAccepted: "Eksekvopeto akceptiĝis." | ||||||
| _deck: | _deck: | ||||||
|  |   profile: "Agordaro" | ||||||
|   _columns: |   _columns: | ||||||
|     notifications: "Sciigoj" |     notifications: "Sciigoj" | ||||||
|     tl: "Tempolinio" |     tl: "Templinio" | ||||||
|     list: "Listoj" |     list: "Listoj" | ||||||
|     mentions: "Mencioj" |     mentions: "Al vi" | ||||||
|   | |||||||
| @@ -127,6 +127,7 @@ editWidgets: "Editar widgets" | |||||||
| editWidgetsExit: "Terminar edición" | editWidgetsExit: "Terminar edición" | ||||||
| customEmojis: "Emojis personalizados" | customEmojis: "Emojis personalizados" | ||||||
| emoji: "Emoji" | emoji: "Emoji" | ||||||
|  | emojis: "Emoji" | ||||||
| emojiName: "Nombre del emoji" | emojiName: "Nombre del emoji" | ||||||
| emojiUrl: "URL de la imágen del emoji" | emojiUrl: "URL de la imágen del emoji" | ||||||
| addEmoji: "Agregar emoji" | addEmoji: "Agregar emoji" | ||||||
| @@ -665,6 +666,10 @@ administration: "Administrar" | |||||||
| expiration: "Termina el" | expiration: "Termina el" | ||||||
| middle: "Mediano" | middle: "Mediano" | ||||||
| global: "Global" | global: "Global" | ||||||
|  | sent: "Enviar" | ||||||
|  | hashtags: "Hashtag" | ||||||
|  | _docs: | ||||||
|  |   admin: "Administrar" | ||||||
| _ad: | _ad: | ||||||
|   back: "Deseleccionar" |   back: "Deseleccionar" | ||||||
| _gallery: | _gallery: | ||||||
|   | |||||||
| @@ -128,6 +128,7 @@ editWidgets: "Modifier les widgets" | |||||||
| editWidgetsExit: "Valider les modifications" | editWidgetsExit: "Valider les modifications" | ||||||
| customEmojis: "Émojis personnalisés" | customEmojis: "Émojis personnalisés" | ||||||
| emoji: "Émoji" | emoji: "Émoji" | ||||||
|  | emojis: "Émoji" | ||||||
| emojiName: "Nom de l’émoji" | emojiName: "Nom de l’émoji" | ||||||
| emojiUrl: "URL de l’émoji" | emojiUrl: "URL de l’émoji" | ||||||
| addEmoji: "Ajouter un émoji" | addEmoji: "Ajouter un émoji" | ||||||
| @@ -528,6 +529,7 @@ removeAllFollowing: "Retenir tous les abonnements" | |||||||
| removeAllFollowingDescription: "Se désabonner de tous les comptes de {host}. Veuillez lancer cette action uniquement si l’instance n’existe plus." | removeAllFollowingDescription: "Se désabonner de tous les comptes de {host}. Veuillez lancer cette action uniquement si l’instance n’existe plus." | ||||||
| userSuspended: "Cet·te utilisateur·rice a été suspendu·e." | userSuspended: "Cet·te utilisateur·rice a été suspendu·e." | ||||||
| userSilenced: "Cette utilisateur·trice a été mis·e en sourdine." | userSilenced: "Cette utilisateur·trice a été mis·e en sourdine." | ||||||
|  | menu: "Menu" | ||||||
| divider: "Séparateur" | divider: "Séparateur" | ||||||
| addItem: "Ajouter un élément" | addItem: "Ajouter un élément" | ||||||
| rooms: "Chambre" | rooms: "Chambre" | ||||||
| @@ -760,7 +762,19 @@ middle: "Moyen" | |||||||
| low: "Basse" | low: "Basse" | ||||||
| emailNotConfiguredWarning: "Vous n'avez pas configuré d'adresse e-mail." | emailNotConfiguredWarning: "Vous n'avez pas configuré d'adresse e-mail." | ||||||
| ratio: "Ratio" | ratio: "Ratio" | ||||||
|  | customCss: "CSS personnalisé" | ||||||
|  | customCssWarn: "Utilisez cette fonctionnalité uniquement si vous savez exactement ce que vous faites. Une configuration inadaptée peut empêcher le client de s'exécuter normalement." | ||||||
| global: "Global" | global: "Global" | ||||||
|  | squareAvatars: "Avatars carrés" | ||||||
|  | sent: "Envoyer" | ||||||
|  | hashtags: "Hashtags" | ||||||
|  | troubleshooting: "Résolution de problèmes" | ||||||
|  | _docs: | ||||||
|  |   continueReading: "Lire plus" | ||||||
|  |   features: "Fonctionnalités" | ||||||
|  |   generalTopics: "Sujets généraux" | ||||||
|  |   advancedTopics: "Sujets avancés" | ||||||
|  |   admin: "Gestion" | ||||||
| _ad: | _ad: | ||||||
|   back: "Retour" |   back: "Retour" | ||||||
|   reduceFrequencyOfThisAd: "Voir cette publicité moins souvent" |   reduceFrequencyOfThisAd: "Voir cette publicité moins souvent" | ||||||
| @@ -859,6 +873,8 @@ _mfm: | |||||||
|   blurDescription: "Le contenu peut être flouté ; il sera visible en le survolant avec le curseur." |   blurDescription: "Le contenu peut être flouté ; il sera visible en le survolant avec le curseur." | ||||||
|   font: "Police de caractères" |   font: "Police de caractères" | ||||||
|   fontDescription: "Il est possible de choisir la police." |   fontDescription: "Il est possible de choisir la police." | ||||||
|  |   rainbow: "Arc-en-ciel" | ||||||
|  |   rainbowDescription: "Permet d'afficher le contenu en couleurs arc-en-ciel." | ||||||
| _reversi: | _reversi: | ||||||
|   reversi: "Reversi" |   reversi: "Reversi" | ||||||
|   gameSettings: "Réglages de la partie" |   gameSettings: "Réglages de la partie" | ||||||
| @@ -911,6 +927,9 @@ _channel: | |||||||
|   usersCount: "{n} Participant·e·s" |   usersCount: "{n} Participant·e·s" | ||||||
|   notesCount: "{n} Notes" |   notesCount: "{n} Notes" | ||||||
| _menuDisplay: | _menuDisplay: | ||||||
|  |   sideFull: "Latéral" | ||||||
|  |   sideIcon: "Latéral (icônes)" | ||||||
|  |   top: "Haut de page" | ||||||
|   hide: "Masquer" |   hide: "Masquer" | ||||||
| _wordMute: | _wordMute: | ||||||
|   muteWords: "Mots à filtrer" |   muteWords: "Mots à filtrer" | ||||||
| @@ -1590,11 +1609,11 @@ _notification: | |||||||
|   youWereInvitedToGroup: "Invité·e au groupe" |   youWereInvitedToGroup: "Invité·e au groupe" | ||||||
|   _types: |   _types: | ||||||
|     all: "Toutes" |     all: "Toutes" | ||||||
|     follow: "Abonnements" |     follow: "Nouvel·le abonné·e" | ||||||
|     mention: "Mentions" |     mention: "Mentions" | ||||||
|     reply: "Réponses" |     reply: "Réponses" | ||||||
|     renote: "Partager" |     renote: "Renotes" | ||||||
|     quote: "Citer" |     quote: "Citations" | ||||||
|     reaction: "Réactions" |     reaction: "Réactions" | ||||||
|     pollVote: "Votes dans des sondages" |     pollVote: "Votes dans des sondages" | ||||||
|     receiveFollowRequest: "Demande d'abonnement reçue" |     receiveFollowRequest: "Demande d'abonnement reçue" | ||||||
|   | |||||||
| @@ -128,6 +128,7 @@ editWidgets: "Sunting gawit" | |||||||
| editWidgetsExit: "Selesai" | editWidgetsExit: "Selesai" | ||||||
| customEmojis: "Emoji kustom" | customEmojis: "Emoji kustom" | ||||||
| emoji: "Emoji" | emoji: "Emoji" | ||||||
|  | emojis: "Emoji" | ||||||
| emojiName: "Nama emoji" | emojiName: "Nama emoji" | ||||||
| emojiUrl: "URL Emoji" | emojiUrl: "URL Emoji" | ||||||
| addEmoji: "Tambahkan emoji" | addEmoji: "Tambahkan emoji" | ||||||
| @@ -528,6 +529,7 @@ removeAllFollowing: "Tahan semua mengikuti" | |||||||
| removeAllFollowingDescription: "Batal mengikuti semua akun dari {host}. Mohon jalankan ini ketika instansi sudah tidak ada lagi." | removeAllFollowingDescription: "Batal mengikuti semua akun dari {host}. Mohon jalankan ini ketika instansi sudah tidak ada lagi." | ||||||
| userSuspended: "Pengguna ini telah dibekukan." | userSuspended: "Pengguna ini telah dibekukan." | ||||||
| userSilenced: "Pengguna ini telah dibungkam." | userSilenced: "Pengguna ini telah dibungkam." | ||||||
|  | menu: "Menu" | ||||||
| divider: "Pembagi" | divider: "Pembagi" | ||||||
| addItem: "Tambahkan item" | addItem: "Tambahkan item" | ||||||
| rooms: "Ruang" | rooms: "Ruang" | ||||||
| @@ -760,7 +762,14 @@ middle: "Sedang" | |||||||
| low: "Rendah" | low: "Rendah" | ||||||
| emailNotConfiguredWarning: "Alamat surel tidak disetel." | emailNotConfiguredWarning: "Alamat surel tidak disetel." | ||||||
| ratio: "Rasio" | ratio: "Rasio" | ||||||
|  | customCss: "Custom CSS" | ||||||
|  | customCssWarn: "Pengaturan ini seharusnya digunakan jika kamu tahu cara kerjanya. Memasukkan nilai yang tidak tepat dapat menyebabkan klien tidak berfungsi semestinya." | ||||||
| global: "Global" | global: "Global" | ||||||
|  | squareAvatars: "Tampilkan avatar sebagai persegi" | ||||||
|  | sent: "Kirim" | ||||||
|  | hashtags: "Tagar" | ||||||
|  | _docs: | ||||||
|  |   admin: "Manajemen" | ||||||
| _ad: | _ad: | ||||||
|   back: "Kembali" |   back: "Kembali" | ||||||
|   reduceFrequencyOfThisAd: "Tampilkan iklan ini lebih sedikit" |   reduceFrequencyOfThisAd: "Tampilkan iklan ini lebih sedikit" | ||||||
| @@ -911,6 +920,9 @@ _channel: | |||||||
|   usersCount: "{n} Partisipan" |   usersCount: "{n} Partisipan" | ||||||
|   notesCount: "terdapat {n} catatan" |   notesCount: "terdapat {n} catatan" | ||||||
| _menuDisplay: | _menuDisplay: | ||||||
|  |   sideFull: "Horisontal" | ||||||
|  |   sideIcon: "Horisontal (Ikon)" | ||||||
|  |   top: "Atas" | ||||||
|   hide: "Sembunyikan" |   hide: "Sembunyikan" | ||||||
| _wordMute: | _wordMute: | ||||||
|   muteWords: "Kata yang dibisukan" |   muteWords: "Kata yang dibisukan" | ||||||
|   | |||||||
| @@ -127,6 +127,7 @@ editWidgets: "Modifica i widget" | |||||||
| editWidgetsExit: "Modifica fine" | editWidgetsExit: "Modifica fine" | ||||||
| customEmojis: "Emoji personalizzati" | customEmojis: "Emoji personalizzati" | ||||||
| emoji: "Emoji" | emoji: "Emoji" | ||||||
|  | emojis: "Emoji" | ||||||
| emojiName: "Nome dell'emoji" | emojiName: "Nome dell'emoji" | ||||||
| emojiUrl: "URL dell'emoji" | emojiUrl: "URL dell'emoji" | ||||||
| addEmoji: "Aggiungi un emoji" | addEmoji: "Aggiungi un emoji" | ||||||
| @@ -741,6 +742,13 @@ low: "Bassa" | |||||||
| emailNotConfiguredWarning: "Non hai impostato nessun indirizzo e-mail." | emailNotConfiguredWarning: "Non hai impostato nessun indirizzo e-mail." | ||||||
| ratio: "Rapporto" | ratio: "Rapporto" | ||||||
| global: "Federata" | global: "Federata" | ||||||
|  | sent: "Inviare" | ||||||
|  | hashtags: "Hashtag" | ||||||
|  | troubleshooting: "Risoluzione problemi" | ||||||
|  | _docs: | ||||||
|  |   continueReading: "Leggi di più" | ||||||
|  |   features: "Funzionalità" | ||||||
|  |   admin: "Gestione" | ||||||
| _ad: | _ad: | ||||||
|   back: "Indietro" |   back: "Indietro" | ||||||
|   reduceFrequencyOfThisAd: "Visualizza questa pubblicità meno spesso" |   reduceFrequencyOfThisAd: "Visualizza questa pubblicità meno spesso" | ||||||
| @@ -799,6 +807,7 @@ _mfm: | |||||||
|   blur: "Sfocatura" |   blur: "Sfocatura" | ||||||
|   font: "Tipo di carattere" |   font: "Tipo di carattere" | ||||||
|   fontDescription: "Puoi scegliere il tipo di carattere per il contenuto." |   fontDescription: "Puoi scegliere il tipo di carattere per il contenuto." | ||||||
|  |   rainbow: "Arcobaleno" | ||||||
| _reversi: | _reversi: | ||||||
|   reversi: "Reversi" |   reversi: "Reversi" | ||||||
|   gameSettings: "Impostazioni di gioco" |   gameSettings: "Impostazioni di gioco" | ||||||
| @@ -1386,12 +1395,12 @@ _notification: | |||||||
|   youWereInvitedToGroup: "Invitat@ al gruppo" |   youWereInvitedToGroup: "Invitat@ al gruppo" | ||||||
|   _types: |   _types: | ||||||
|     all: "Tutto" |     all: "Tutto" | ||||||
|     follow: "Follows" |     follow: "Nuovə follower" | ||||||
|     mention: "Menzioni" |     mention: "Menzioni" | ||||||
|     reply: "Rispondi" |     reply: "Risposte" | ||||||
|     renote: "Rinota" |     renote: "Rinota" | ||||||
|     quote: "Cita" |     quote: "Cita" | ||||||
|     reaction: "Reazione" |     reaction: "Reazioni" | ||||||
|     pollVote: "Voti ricevuti" |     pollVote: "Voti ricevuti" | ||||||
|     receiveFollowRequest: "Richiesta di follow ricevuta" |     receiveFollowRequest: "Richiesta di follow ricevuta" | ||||||
|     followRequestAccepted: "Richiesta di follow accettata" |     followRequestAccepted: "Richiesta di follow accettata" | ||||||
|   | |||||||
| @@ -128,6 +128,7 @@ editWidgets: "ウィジェットを編集" | |||||||
| editWidgetsExit: "編集を終了" | editWidgetsExit: "編集を終了" | ||||||
| customEmojis: "カスタム絵文字" | customEmojis: "カスタム絵文字" | ||||||
| emoji: "絵文字" | emoji: "絵文字" | ||||||
|  | emojis: "絵文字" | ||||||
| emojiName: "絵文字名" | emojiName: "絵文字名" | ||||||
| emojiUrl: "絵文字画像URL" | emojiUrl: "絵文字画像URL" | ||||||
| addEmoji: "絵文字を追加" | addEmoji: "絵文字を追加" | ||||||
| @@ -765,6 +766,18 @@ customCss: "カスタムCSS" | |||||||
| customCssWarn: "この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。" | customCssWarn: "この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。" | ||||||
| global: "グローバル" | global: "グローバル" | ||||||
| squareAvatars: "アイコンを四角形で表示" | squareAvatars: "アイコンを四角形で表示" | ||||||
|  | sent: "送信" | ||||||
|  | received: "受信" | ||||||
|  | searchResult: "検索結果" | ||||||
|  | hashtags: "ハッシュタグ" | ||||||
|  | troubleshooting: "トラブルシューティング" | ||||||
|  |  | ||||||
|  | _docs:  | ||||||
|  |   continueReading: "続きを読む" | ||||||
|  |   features: "機能" | ||||||
|  |   generalTopics: "一般的なトピック" | ||||||
|  |   advancedTopics: "高度なトピック" | ||||||
|  |   admin: "管理" | ||||||
|  |  | ||||||
| _ad: | _ad: | ||||||
|   back: "戻る" |   back: "戻る" | ||||||
| @@ -872,6 +885,8 @@ _mfm: | |||||||
|   blurDescription: "内容をぼかすことができます。ポインターを上に乗せるとはっきり見えるようになります。" |   blurDescription: "内容をぼかすことができます。ポインターを上に乗せるとはっきり見えるようになります。" | ||||||
|   font: "フォント" |   font: "フォント" | ||||||
|   fontDescription: "内容のフォントを指定することができます。" |   fontDescription: "内容のフォントを指定することができます。" | ||||||
|  |   rainbow: "レインボー" | ||||||
|  |   rainbowDescription: "内容をレインボーにします。" | ||||||
|  |  | ||||||
| _reversi: | _reversi: | ||||||
|   reversi: "リバーシ" |   reversi: "リバーシ" | ||||||
|   | |||||||
| @@ -127,6 +127,7 @@ editWidgets: "ウィジェットをいじる" | |||||||
| editWidgetsExit: "編集終ったで" | editWidgetsExit: "編集終ったで" | ||||||
| customEmojis: "カスタム絵文字" | customEmojis: "カスタム絵文字" | ||||||
| emoji: "絵文字" | emoji: "絵文字" | ||||||
|  | emojis: "絵文字" | ||||||
| emojiName: "絵文字名" | emojiName: "絵文字名" | ||||||
| emojiUrl: "絵文字画像URL" | emojiUrl: "絵文字画像URL" | ||||||
| addEmoji: "絵文字を追加" | addEmoji: "絵文字を追加" | ||||||
| @@ -647,6 +648,10 @@ high: "高い" | |||||||
| middle: "中" | middle: "中" | ||||||
| low: "低い" | low: "低い" | ||||||
| global: "グローバル" | global: "グローバル" | ||||||
|  | sent: "送信" | ||||||
|  | hashtags: "ハッシュタグ" | ||||||
|  | _docs: | ||||||
|  |   admin: "管理" | ||||||
| _ad: | _ad: | ||||||
|   back: "戻る" |   back: "戻る" | ||||||
| _gallery: | _gallery: | ||||||
|   | |||||||
| @@ -7,7 +7,9 @@ username: "Isem n umseqdac" | |||||||
| password: "Awal uffir" | password: "Awal uffir" | ||||||
| ok: "IH" | ok: "IH" | ||||||
| settings: "Iɣewwaṛen" | settings: "Iɣewwaṛen" | ||||||
|  | otherSettings: "Iɣewwaren nniḍen" | ||||||
| profile: "Amaɣnu" | profile: "Amaɣnu" | ||||||
|  | signup: "Jerred" | ||||||
| save: "Sekles" | save: "Sekles" | ||||||
| delete: "Kkes" | delete: "Kkes" | ||||||
| addToList: "Rnu ɣer tebdart" | addToList: "Rnu ɣer tebdart" | ||||||
| @@ -27,15 +29,31 @@ followers: "Imeḍfaṛen" | |||||||
| followsYou: "Yeṭṭafaṛ-ik·em-id" | followsYou: "Yeṭṭafaṛ-ik·em-id" | ||||||
| createList: "Snulfu-d tabdart" | createList: "Snulfu-d tabdart" | ||||||
| enterListName: "Isem n tebdart" | enterListName: "Isem n tebdart" | ||||||
|  | privacy: "Tabaḍnit" | ||||||
| follow: "Ḍfeṛ" | follow: "Ḍfeṛ" | ||||||
| you: "Kečči·mmi" | you: "Kečči·mmi" | ||||||
| selectList: "Fren tabdart" | selectList: "Fren tabdart" | ||||||
| youHaveNoLists: "Ulac ɣur-k·m ula d yiwet n tabdart" | youHaveNoLists: "Ulac ɣur-k·m ula d yiwet n tabdart" | ||||||
|  | security: "Taɣellist" | ||||||
| remove: "Kkes" | remove: "Kkes" | ||||||
| userList: "Tibdarin" | userList: "Tibdarin" | ||||||
|  | securityKey: "Tasarutt n tɣellist" | ||||||
|  | securityKeyName: "Isem n tsarutt" | ||||||
|  | signinRequired: "Ttxil jerred" | ||||||
|  | signinWith: "Tuqqna s {x}" | ||||||
|  | tapSecurityKey: "Sekcem tasarutt-ik·im n tɣellist" | ||||||
| uiLanguage: "Tutlayt n wegrudem" | uiLanguage: "Tutlayt n wegrudem" | ||||||
|  | accountSettings: "Iɣewwaṛen n umiḍan" | ||||||
|  | plugins: "Izegrar" | ||||||
|  | email: "Imayl" | ||||||
|  | emailAddress: "Tansa imayl" | ||||||
| smtpUser: "Isem n umseqdac" | smtpUser: "Isem n umseqdac" | ||||||
| smtpPass: "Awal uffir" | smtpPass: "Awal uffir" | ||||||
|  | other: "Wiyyaḍ" | ||||||
|  | accountInfo: "Talɣut n umiḍan" | ||||||
|  | emailNotification: "Ilɣa imayl" | ||||||
|  | selectAccount: "Fren amiḍan" | ||||||
|  | accounts: "Imiḍan" | ||||||
| _email: | _email: | ||||||
|   _follow: |   _follow: | ||||||
|     title: "Yeṭṭafaṛ-ik·em-id" |     title: "Yeṭṭafaṛ-ik·em-id" | ||||||
| @@ -48,6 +66,8 @@ _theme: | |||||||
|     mention: "Bder" |     mention: "Bder" | ||||||
| _sfx: | _sfx: | ||||||
|   notification: "Ilɣuyen" |   notification: "Ilɣuyen" | ||||||
|  | _permissions: | ||||||
|  |   "write:account": "Ẓreg talɣut n umiḍan-ik·im" | ||||||
| _widgets: | _widgets: | ||||||
|   notifications: "Ilɣuyen" |   notifications: "Ilɣuyen" | ||||||
| _cw: | _cw: | ||||||
|   | |||||||
| @@ -128,6 +128,7 @@ editWidgets: "위젯 편집" | |||||||
| editWidgetsExit: "편집 종료" | editWidgetsExit: "편집 종료" | ||||||
| customEmojis: "커스텀 이모지" | customEmojis: "커스텀 이모지" | ||||||
| emoji: "이모지" | emoji: "이모지" | ||||||
|  | emojis: "이모지" | ||||||
| emojiName: "이모지 이름" | emojiName: "이모지 이름" | ||||||
| emojiUrl: "이모지 URL" | emojiUrl: "이모지 URL" | ||||||
| addEmoji: "이모지 추가" | addEmoji: "이모지 추가" | ||||||
| @@ -528,6 +529,7 @@ removeAllFollowing: "모든 팔로잉 해제" | |||||||
| removeAllFollowingDescription: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게 된 경우 등에 실행해 주세요." | removeAllFollowingDescription: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게 된 경우 등에 실행해 주세요." | ||||||
| userSuspended: "이 계정은 정지된 상태입니다." | userSuspended: "이 계정은 정지된 상태입니다." | ||||||
| userSilenced: "이 계정은 사일런스된 상태입니다." | userSilenced: "이 계정은 사일런스된 상태입니다." | ||||||
|  | menu: "메뉴" | ||||||
| divider: "구분선" | divider: "구분선" | ||||||
| addItem: "항목 추가" | addItem: "항목 추가" | ||||||
| rooms: "방" | rooms: "방" | ||||||
| @@ -760,7 +762,21 @@ middle: "보통" | |||||||
| low: "낮음" | low: "낮음" | ||||||
| emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다." | emailNotConfiguredWarning: "메일 주소가 설정되어 있지 않습니다." | ||||||
| ratio: "비율" | ratio: "비율" | ||||||
|  | customCss: "CSS 사용자화" | ||||||
|  | customCssWarn: "이 설정은 기능을 알고 있는 경우에만 사용해야 합니다. 잘못된 값을 입력하면 클라이언트가 정상적으로 작동하지 않을 수 있습니다." | ||||||
| global: "글로벌" | global: "글로벌" | ||||||
|  | squareAvatars: "프로필 아이콘을 사각형으로 표시" | ||||||
|  | sent: "전송" | ||||||
|  | received: "수신" | ||||||
|  | searchResult: "검색 결과" | ||||||
|  | hashtags: "해시태그" | ||||||
|  | troubleshooting: "트러블 슈팅" | ||||||
|  | _docs: | ||||||
|  |   continueReading: "계속 읽기" | ||||||
|  |   features: "기능" | ||||||
|  |   generalTopics: "일반 주제" | ||||||
|  |   advancedTopics: "심화 주제" | ||||||
|  |   admin: "관리" | ||||||
| _ad: | _ad: | ||||||
|   back: "뒤로" |   back: "뒤로" | ||||||
|   reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기" |   reduceFrequencyOfThisAd: "이 광고의 표시 빈도 낮추기" | ||||||
| @@ -859,6 +875,8 @@ _mfm: | |||||||
|   blurDescription: "내용이 흐리게 보입니다. 마우스를 위에 올려두면 내용이 보입니다." |   blurDescription: "내용이 흐리게 보입니다. 마우스를 위에 올려두면 내용이 보입니다." | ||||||
|   font: "폰트" |   font: "폰트" | ||||||
|   fontDescription: "내용의 글꼴을 지정할 수 있습니다." |   fontDescription: "내용의 글꼴을 지정할 수 있습니다." | ||||||
|  |   rainbow: "무지개" | ||||||
|  |   rainbowDescription: "내용을 무지개로 표시합니다." | ||||||
| _reversi: | _reversi: | ||||||
|   reversi: "리버시" |   reversi: "리버시" | ||||||
|   gameSettings: "대국 설정" |   gameSettings: "대국 설정" | ||||||
| @@ -911,6 +929,9 @@ _channel: | |||||||
|   usersCount: "{n}명 참여 중" |   usersCount: "{n}명 참여 중" | ||||||
|   notesCount: "{n}노트" |   notesCount: "{n}노트" | ||||||
| _menuDisplay: | _menuDisplay: | ||||||
|  |   sideFull: "가로" | ||||||
|  |   sideIcon: "가로(아이콘)" | ||||||
|  |   top: "상단" | ||||||
|   hide: "숨기기" |   hide: "숨기기" | ||||||
| _wordMute: | _wordMute: | ||||||
|   muteWords: "뮤트할 단어" |   muteWords: "뮤트할 단어" | ||||||
|   | |||||||
| @@ -128,6 +128,7 @@ editWidgets: "Edytuj widżet" | |||||||
| editWidgetsExit: "Gotowe" | editWidgetsExit: "Gotowe" | ||||||
| customEmojis: "Niestandardowe emoji" | customEmojis: "Niestandardowe emoji" | ||||||
| emoji: "Emoji" | emoji: "Emoji" | ||||||
|  | emojis: "Emoji" | ||||||
| emojiName: "Nazwa emoji" | emojiName: "Nazwa emoji" | ||||||
| emojiUrl: "Adres URL emoji" | emojiUrl: "Adres URL emoji" | ||||||
| addEmoji: "Dodaj emoji" | addEmoji: "Dodaj emoji" | ||||||
| @@ -735,6 +736,10 @@ low: "Niski" | |||||||
| emailNotConfiguredWarning: "Nie podano adresu e-mail" | emailNotConfiguredWarning: "Nie podano adresu e-mail" | ||||||
| ratio: "Stosunek" | ratio: "Stosunek" | ||||||
| global: "Globalna" | global: "Globalna" | ||||||
|  | sent: "Wyślij" | ||||||
|  | hashtags: "Hashtag" | ||||||
|  | _docs: | ||||||
|  |   admin: "Zarządzanie" | ||||||
| _ad: | _ad: | ||||||
|   back: "Wróć" |   back: "Wróć" | ||||||
|   reduceFrequencyOfThisAd: "Pokazuj tę reklamę rzadziej" |   reduceFrequencyOfThisAd: "Pokazuj tę reklamę rzadziej" | ||||||
|   | |||||||
| @@ -128,6 +128,7 @@ editWidgets: "Редактировать виджеты" | |||||||
| editWidgetsExit: "Готово" | editWidgetsExit: "Готово" | ||||||
| customEmojis: "Эмодзи пользователя" | customEmojis: "Эмодзи пользователя" | ||||||
| emoji: "Эмодзи" | emoji: "Эмодзи" | ||||||
|  | emojis: "Эмодзи" | ||||||
| emojiName: "Название эмодзи" | emojiName: "Название эмодзи" | ||||||
| emojiUrl: "URL эмодзи" | emojiUrl: "URL эмодзи" | ||||||
| addEmoji: "Добавить эмодзи" | addEmoji: "Добавить эмодзи" | ||||||
| @@ -761,6 +762,14 @@ low: "Низкий" | |||||||
| emailNotConfiguredWarning: "Не указан адрес электронной почты" | emailNotConfiguredWarning: "Не указан адрес электронной почты" | ||||||
| ratio: "Соотношение" | ratio: "Соотношение" | ||||||
| global: "Всеобщая" | global: "Всеобщая" | ||||||
|  | sent: "Отправить" | ||||||
|  | hashtags: "Хэштег" | ||||||
|  | _docs: | ||||||
|  |   continueReading: "Читать подробнее" | ||||||
|  |   features: "Возможности" | ||||||
|  |   generalTopics: "Основные темы" | ||||||
|  |   advancedTopics: "Дополнительные темы" | ||||||
|  |   admin: "Управление" | ||||||
| _ad: | _ad: | ||||||
|   back: "Выход" |   back: "Выход" | ||||||
|   reduceFrequencyOfThisAd: "Реже показывать эту рекламу" |   reduceFrequencyOfThisAd: "Реже показывать эту рекламу" | ||||||
| @@ -859,6 +868,8 @@ _mfm: | |||||||
|   blurDescription: "Размывает текст до нечитаемости, будто его поместили за матовое стекло. Наведение указателя мыши на размытый текст возвращает чёткость." |   blurDescription: "Размывает текст до нечитаемости, будто его поместили за матовое стекло. Наведение указателя мыши на размытый текст возвращает чёткость." | ||||||
|   font: "Шрифт" |   font: "Шрифт" | ||||||
|   fontDescription: "Так можно писать произвольным шрифтом." |   fontDescription: "Так можно писать произвольным шрифтом." | ||||||
|  |   rainbow: "Радуга" | ||||||
|  |   rainbowDescription: "Заставлять содержимое отображаться в цветах радуги." | ||||||
| _reversi: | _reversi: | ||||||
|   reversi: "Реверси" |   reversi: "Реверси" | ||||||
|   gameSettings: "Настройки игры" |   gameSettings: "Настройки игры" | ||||||
| @@ -911,6 +922,9 @@ _channel: | |||||||
|   usersCount: "Участников: {n}" |   usersCount: "Участников: {n}" | ||||||
|   notesCount: "Заметок: {n}" |   notesCount: "Заметок: {n}" | ||||||
| _menuDisplay: | _menuDisplay: | ||||||
|  |   sideFull: "Сторона" | ||||||
|  |   sideIcon: "Сторона (иконки)" | ||||||
|  |   top: "Вверх" | ||||||
|   hide: "Спрятать" |   hide: "Спрятать" | ||||||
| _wordMute: | _wordMute: | ||||||
|   muteWords: "Скрыть слово" |   muteWords: "Скрыть слово" | ||||||
|   | |||||||
| @@ -127,6 +127,7 @@ editWidgets: "Редагувати віджети" | |||||||
| editWidgetsExit: "Готово" | editWidgetsExit: "Готово" | ||||||
| customEmojis: "Кастомні емоджі" | customEmojis: "Кастомні емоджі" | ||||||
| emoji: "Емоджі" | emoji: "Емоджі" | ||||||
|  | emojis: "Емоджі" | ||||||
| emojiName: "Назва емоджі" | emojiName: "Назва емоджі" | ||||||
| emojiUrl: "URL емодзі" | emojiUrl: "URL емодзі" | ||||||
| addEmoji: "Додати емодзі" | addEmoji: "Додати емодзі" | ||||||
| @@ -689,6 +690,10 @@ administration: "Управління" | |||||||
| expiration: "Опитування закінчується" | expiration: "Опитування закінчується" | ||||||
| middle: "Середній" | middle: "Середній" | ||||||
| global: "Глобальна" | global: "Глобальна" | ||||||
|  | sent: "Відправити" | ||||||
|  | hashtags: "Хештеґ" | ||||||
|  | _docs: | ||||||
|  |   admin: "Управління" | ||||||
| _ad: | _ad: | ||||||
|   back: "Назад" |   back: "Назад" | ||||||
| _gallery: | _gallery: | ||||||
|   | |||||||
| @@ -128,6 +128,7 @@ editWidgets: "编辑小工具" | |||||||
| editWidgetsExit: "完成编辑" | editWidgetsExit: "完成编辑" | ||||||
| customEmojis: "自定义表情符号" | customEmojis: "自定义表情符号" | ||||||
| emoji: "表情符号" | emoji: "表情符号" | ||||||
|  | emojis: "表情符号" | ||||||
| emojiName: "表情符号名称" | emojiName: "表情符号名称" | ||||||
| emojiUrl: "表情符号地址" | emojiUrl: "表情符号地址" | ||||||
| addEmoji: "添加表情符号" | addEmoji: "添加表情符号" | ||||||
| @@ -765,6 +766,17 @@ customCss: "自定义 CSS" | |||||||
| customCssWarn: "这些设置必须有相关的基础知识,不当的配置可能导致客户端无法正常使用!" | customCssWarn: "这些设置必须有相关的基础知识,不当的配置可能导致客户端无法正常使用!" | ||||||
| global: "全局" | global: "全局" | ||||||
| squareAvatars: "显示方形头像图标" | squareAvatars: "显示方形头像图标" | ||||||
|  | sent: "发送" | ||||||
|  | received: "收取" | ||||||
|  | searchResult: "搜索结果" | ||||||
|  | hashtags: "话题标签" | ||||||
|  | troubleshooting: "故障排除" | ||||||
|  | _docs: | ||||||
|  |   continueReading: "继续阅读" | ||||||
|  |   features: "特性" | ||||||
|  |   generalTopics: "通常提示" | ||||||
|  |   advancedTopics: "进阶提示" | ||||||
|  |   admin: "管理" | ||||||
| _ad: | _ad: | ||||||
|   back: "返回" |   back: "返回" | ||||||
|   reduceFrequencyOfThisAd: "减少此广告的频率" |   reduceFrequencyOfThisAd: "减少此广告的频率" | ||||||
| @@ -863,6 +875,8 @@ _mfm: | |||||||
|   blurDescription: "产生模糊效果。将鼠标指针放在上面即可将内容显示出来。" |   blurDescription: "产生模糊效果。将鼠标指针放在上面即可将内容显示出来。" | ||||||
|   font: "字体" |   font: "字体" | ||||||
|   fontDescription: "可以设置内容所使用的字体。" |   fontDescription: "可以设置内容所使用的字体。" | ||||||
|  |   rainbow: "彩虹" | ||||||
|  |   rainbowDescription: "用彩虹色来显示内容。" | ||||||
| _reversi: | _reversi: | ||||||
|   reversi: "黑白棋" |   reversi: "黑白棋" | ||||||
|   gameSettings: "对局设置" |   gameSettings: "对局设置" | ||||||
|   | |||||||
| @@ -128,6 +128,7 @@ editWidgets: "編輯小工具" | |||||||
| editWidgetsExit: "完成" | editWidgetsExit: "完成" | ||||||
| customEmojis: "自訂表情符號" | customEmojis: "自訂表情符號" | ||||||
| emoji: "表情符號" | emoji: "表情符號" | ||||||
|  | emojis: "表情符號" | ||||||
| emojiName: "表情符號名稱" | emojiName: "表情符號名稱" | ||||||
| emojiUrl: "表情符號URL" | emojiUrl: "表情符號URL" | ||||||
| addEmoji: "加入表情符號" | addEmoji: "加入表情符號" | ||||||
| @@ -751,6 +752,10 @@ low: "低" | |||||||
| emailNotConfiguredWarning: "沒有設定電子郵件地址" | emailNotConfiguredWarning: "沒有設定電子郵件地址" | ||||||
| ratio: "%" | ratio: "%" | ||||||
| global: "公開" | global: "公開" | ||||||
|  | sent: "發送" | ||||||
|  | hashtags: "#tag" | ||||||
|  | _docs: | ||||||
|  |   admin: "管理" | ||||||
| _ad: | _ad: | ||||||
|   back: "返回" |   back: "返回" | ||||||
|   reduceFrequencyOfThisAd: "降低此廣告的頻率 " |   reduceFrequencyOfThisAd: "降低此廣告的頻率 " | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
| 	"name": "misskey", | 	"name": "misskey", | ||||||
| 	"author": "syuilo <syuilotan@yahoo.co.jp>", | 	"author": "syuilo <syuilotan@yahoo.co.jp>", | ||||||
| 	"version": "12.84.1", | 	"version": "12.85.0", | ||||||
| 	"codename": "indigo", | 	"codename": "indigo", | ||||||
| 	"repository": { | 	"repository": { | ||||||
| 		"type": "git", | 		"type": "git", | ||||||
| @@ -101,7 +101,7 @@ | |||||||
| 		"@types/websocket": "1.0.3", | 		"@types/websocket": "1.0.3", | ||||||
| 		"@types/ws": "7.4.6", | 		"@types/ws": "7.4.6", | ||||||
| 		"@typescript-eslint/parser": "4.28.3", | 		"@typescript-eslint/parser": "4.28.3", | ||||||
| 		"@vue/compiler-sfc": "3.2.0-beta.4", | 		"@vue/compiler-sfc": "3.1.5", | ||||||
| 		"abort-controller": "3.0.0", | 		"abort-controller": "3.0.0", | ||||||
| 		"apexcharts": "3.27.2", | 		"apexcharts": "3.27.2", | ||||||
| 		"autobind-decorator": "2.4.0", | 		"autobind-decorator": "2.4.0", | ||||||
| @@ -238,7 +238,7 @@ | |||||||
| 		"uuid": "8.3.2", | 		"uuid": "8.3.2", | ||||||
| 		"v-debounce": "0.1.2", | 		"v-debounce": "0.1.2", | ||||||
| 		"vanilla-tilt": "1.7.0", | 		"vanilla-tilt": "1.7.0", | ||||||
| 		"vue": "3.2.0-beta.4", | 		"vue": "3.1.5", | ||||||
| 		"vue-color": "2.8.1", | 		"vue-color": "2.8.1", | ||||||
| 		"vue-json-pretty": "1.8.1", | 		"vue-json-pretty": "1.8.1", | ||||||
| 		"vue-loader": "16.3.1", | 		"vue-loader": "16.3.1", | ||||||
|   | |||||||
| @@ -10,9 +10,9 @@ | |||||||
| 	</template> | 	</template> | ||||||
| 	<div class="dpvffvvy _monolithic_"> | 	<div class="dpvffvvy _monolithic_"> | ||||||
| 		<div class="_section"> | 		<div class="_section"> | ||||||
| 			<MkTextarea v-model:value="comment"> | 			<MkTextarea v-model="comment"> | ||||||
| 				<span>{{ $ts.details }}</span> | 				<template #label>{{ $ts.details }}</template> | ||||||
| 				<template #desc>{{ $ts.fillAbuseReportDescription }}</template> | 				<template #caption>{{ $ts.fillAbuseReportDescription }}</template> | ||||||
| 			</MkTextarea> | 			</MkTextarea> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div class="_section"> | 		<div class="_section"> | ||||||
|   | |||||||
| @@ -14,8 +14,8 @@ | |||||||
| 		</div> | 		</div> | ||||||
| 		<header v-if="title"><Mfm :text="title"/></header> | 		<header v-if="title"><Mfm :text="title"/></header> | ||||||
| 		<div class="body" v-if="text"><Mfm :text="text"/></div> | 		<div class="body" v-if="text"><Mfm :text="text"/></div> | ||||||
| 		<MkInput v-if="input" v-model:value="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></MkInput> | 		<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></MkInput> | ||||||
| 		<MkSelect v-if="select" v-model:value="selectedValue" autofocus> | 		<MkSelect v-if="select" v-model="selectedValue" autofocus> | ||||||
| 			<template v-if="select.items"> | 			<template v-if="select.items"> | ||||||
| 				<option v-for="item in select.items" :value="item.value">{{ item.text }}</option> | 				<option v-for="item in select.items" :value="item.value">{{ item.text }}</option> | ||||||
| 			</template> | 			</template> | ||||||
|   | |||||||
| @@ -114,7 +114,7 @@ export default defineComponent({ | |||||||
| 			if (this.selectMode) { | 			if (this.selectMode) { | ||||||
| 				this.$emit('chosen', this.file); | 				this.$emit('chosen', this.file); | ||||||
| 			} else { | 			} else { | ||||||
| 				os.modalMenu(this.getMenu(), ev.currentTarget || ev.target); | 				os.popupMenu(this.getMenu(), ev.currentTarget || ev.target); | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ | |||||||
| 			<span class="separator" v-if="folder != null"><i class="fas fa-angle-right"></i></span> | 			<span class="separator" v-if="folder != null"><i class="fas fa-angle-right"></i></span> | ||||||
| 			<span class="folder current" v-if="folder != null">{{ folder.name }}</span> | 			<span class="folder current" v-if="folder != null">{{ folder.name }}</span> | ||||||
| 		</div> | 		</div> | ||||||
|  | 		<button @click="showMenu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button> | ||||||
| 	</nav> | 	</nav> | ||||||
| 	<div class="main" :class="{ uploading: uploadings.length > 0, fetching }" | 	<div class="main" :class="{ uploading: uploadings.length > 0, fetching }" | ||||||
| 		ref="main" | 		ref="main" | ||||||
| @@ -46,7 +47,7 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent } from 'vue'; | import { defineComponent, markRaw } from 'vue'; | ||||||
| import XNavFolder from './drive.nav-folder.vue'; | import XNavFolder from './drive.nav-folder.vue'; | ||||||
| import XFolder from './drive.folder.vue'; | import XFolder from './drive.folder.vue'; | ||||||
| import XFile from './drive.file.vue'; | import XFile from './drive.file.vue'; | ||||||
| @@ -139,7 +140,7 @@ export default defineComponent({ | |||||||
| 			}); | 			}); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		this.connection = os.stream.useChannel('drive'); | 		this.connection = markRaw(os.stream.useChannel('drive')); | ||||||
|  |  | ||||||
| 		this.connection.on('fileCreated', this.onStreamDriveFileCreated); | 		this.connection.on('fileCreated', this.onStreamDriveFileCreated); | ||||||
| 		this.connection.on('fileUpdated', this.onStreamDriveFileUpdated); | 		this.connection.on('fileUpdated', this.onStreamDriveFileUpdated); | ||||||
| @@ -627,8 +628,12 @@ export default defineComponent({ | |||||||
| 			}]; | 			}]; | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		onContextmenu(e) { | 		showMenu(ev) { | ||||||
| 			os.contextMenu(this.getMenu(), e); | 			os.popupMenu(this.getMenu(), ev.currentTarget || ev.target); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		onContextmenu(ev) { | ||||||
|  | 			os.contextMenu(this.getMenu(), ev); | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| @@ -641,7 +646,7 @@ export default defineComponent({ | |||||||
| 	height: 100%; | 	height: 100%; | ||||||
|  |  | ||||||
| 	> nav { | 	> nav { | ||||||
| 		display: block; | 		display: flex; | ||||||
| 		z-index: 2; | 		z-index: 2; | ||||||
| 		width: 100%; | 		width: 100%; | ||||||
| 		padding: 0 8px; | 		padding: 0 8px; | ||||||
| @@ -696,6 +701,10 @@ export default defineComponent({ | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		> .menu { | ||||||
|  | 			margin-left: auto; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	> .main { | 	> .main { | ||||||
|   | |||||||
| @@ -1,17 +1,17 @@ | |||||||
| <template> | <template> | ||||||
| <MkModal ref="modal" :manual-showing="manualShowing" :src="src" :front="true" @click="$refs.modal.close()" @opening="opening" @close="$emit('close')" @closed="$emit('closed')"> | <MkPopup ref="popup" :manual-showing="manualShowing" :src="src" :front="true" @click="$refs.popup.close()" @opening="opening" @close="$emit('close')" @closed="$emit('closed')"> | ||||||
| 	<MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen" ref="picker"/> | 	<MkEmojiPicker class="_shadow" :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen" ref="picker"/> | ||||||
| </MkModal> | </MkPopup> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent, markRaw } from 'vue'; | import { defineComponent, markRaw } from 'vue'; | ||||||
| import MkModal from '@client/components/ui/modal.vue'; | import MkPopup from '@client/components/ui/popup.vue'; | ||||||
| import MkEmojiPicker from '@client/components/emoji-picker.vue'; | import MkEmojiPicker from '@client/components/emoji-picker.vue'; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| 	components: { | 	components: { | ||||||
| 		MkModal, | 		MkPopup, | ||||||
| 		MkEmojiPicker, | 		MkEmojiPicker, | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| @@ -33,7 +33,7 @@ export default defineComponent({ | |||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	emits: ['done', 'closed'], | 	emits: ['done', 'close', 'closed'], | ||||||
|  |  | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| @@ -44,7 +44,7 @@ export default defineComponent({ | |||||||
| 	methods: { | 	methods: { | ||||||
| 		chosen(emoji: any) { | 		chosen(emoji: any) { | ||||||
| 			this.$emit('done', emoji); | 			this.$emit('done', emoji); | ||||||
| 			this.$refs.modal.close(); | 			this.$refs.popup.close(); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		opening() { | 		opening() { | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent } from 'vue'; | import { defineComponent, markRaw } from 'vue'; | ||||||
| import * as os from '@client/os'; | import * as os from '@client/os'; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| @@ -71,7 +71,7 @@ export default defineComponent({ | |||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	mounted() { | 	mounted() { | ||||||
| 		this.connection = os.stream.useChannel('main'); | 		this.connection = markRaw(os.stream.useChannel('main')); | ||||||
|  |  | ||||||
| 		this.connection.on('follow', this.onFollowChange); | 		this.connection.on('follow', this.onFollowChange); | ||||||
| 		this.connection.on('unfollow', this.onFollowChange); | 		this.connection.on('unfollow', this.onFollowChange); | ||||||
|   | |||||||
| @@ -9,14 +9,14 @@ | |||||||
|  |  | ||||||
| 	<form class="_monolithic_" @submit.prevent="onSubmit" v-if="$instance.enableEmail"> | 	<form class="_monolithic_" @submit.prevent="onSubmit" v-if="$instance.enableEmail"> | ||||||
| 		<div class="_section"> | 		<div class="_section"> | ||||||
| 			<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required> | 			<MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required> | ||||||
| 				<span>{{ $ts.username }}</span> | 				<template #label>{{ $ts.username }}</template> | ||||||
| 				<template #prefix>@</template> | 				<template #prefix>@</template> | ||||||
| 			</MkInput> | 			</MkInput> | ||||||
|  |  | ||||||
| 			<MkInput v-model:value="email" type="email" spellcheck="false" required> | 			<MkInput v-model="email" type="email" spellcheck="false" required> | ||||||
| 				<span>{{ $ts.emailAddress }}</span> | 				<template #label>{{ $ts.emailAddress }}</template> | ||||||
| 				<template #desc>{{ $ts._forgotPassword.enterEmail }}</template> | 				<template #caption>{{ $ts._forgotPassword.enterEmail }}</template> | ||||||
| 			</MkInput> | 			</MkInput> | ||||||
|  |  | ||||||
| 			<MkButton type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ $ts.send }}</MkButton> | 			<MkButton type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ $ts.send }}</MkButton> | ||||||
|   | |||||||
| @@ -117,6 +117,11 @@ export default defineComponent({ | |||||||
| 	75% { transform: scale3d(1.05, 0.95, 1); } | 	75% { transform: scale3d(1.05, 0.95, 1); } | ||||||
| 	to { transform: scale3d(1, 1, 1); } | 	to { transform: scale3d(1, 1, 1); } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @keyframes mfm-rainbow { | ||||||
|  | 	0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); } | ||||||
|  | 	100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); } | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| <template> | <template> | ||||||
| <div class="zbcjwnqg" style="margin-top: -8px;"> | <div class="zbcjwnqg" style="margin-top: -8px;"> | ||||||
| 	<div class="selects" style="display: flex;"> | 	<div class="selects" style="display: flex;"> | ||||||
| 		<MkSelect v-model:value="chartSrc" style="margin: 0; flex: 1;"> | 		<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;"> | ||||||
| 			<optgroup :label="$ts.federation"> | 			<optgroup :label="$ts.federation"> | ||||||
| 				<option value="federation-instances">{{ $ts._charts.federationInstancesIncDec }}</option> | 				<option value="federation-instances">{{ $ts._charts.federationInstancesIncDec }}</option> | ||||||
| 				<option value="federation-instances-total">{{ $ts._charts.federationInstancesTotal }}</option> | 				<option value="federation-instances-total">{{ $ts._charts.federationInstancesTotal }}</option> | ||||||
| @@ -24,7 +24,7 @@ | |||||||
| 				<option value="drive-total">{{ $ts._charts.storageUsageTotal }}</option> | 				<option value="drive-total">{{ $ts._charts.storageUsageTotal }}</option> | ||||||
| 			</optgroup> | 			</optgroup> | ||||||
| 		</MkSelect> | 		</MkSelect> | ||||||
| 		<MkSelect v-model:value="chartSpan" style="margin: 0;"> | 		<MkSelect v-model="chartSpan" style="margin: 0;"> | ||||||
| 			<option value="hour">{{ $ts.perHour }}</option> | 			<option value="hour">{{ $ts.perHour }}</option> | ||||||
| 			<option value="day">{{ $ts.perDay }}</option> | 			<option value="day">{{ $ts.perDay }}</option> | ||||||
| 		</MkSelect> | 		</MkSelect> | ||||||
|   | |||||||
| @@ -165,6 +165,10 @@ export default defineComponent({ | |||||||
| 								class: '_mfm_blur_', | 								class: '_mfm_blur_', | ||||||
| 							}, genEl(token.children)); | 							}, genEl(token.children)); | ||||||
| 						} | 						} | ||||||
|  | 						case 'rainbow': { | ||||||
|  | 							style = this.$store.state.animatedMfm ? 'animation: mfm-rainbow 1s linear infinite;' : ''; | ||||||
|  | 							break; | ||||||
|  | 						} | ||||||
| 					} | 					} | ||||||
| 					if (style == null) { | 					if (style == null) { | ||||||
| 						return h('span', {}, ['[', token.props.name, ...genEl(token.children), ']']); | 						return h('span', {}, ['[', token.props.name, ...genEl(token.children), ']']); | ||||||
|   | |||||||
| @@ -2,12 +2,9 @@ | |||||||
| <MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')"> | <MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')"> | ||||||
| 	<div class="hrmcaedk _popup _narrow_" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }"> | 	<div class="hrmcaedk _popup _narrow_" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }"> | ||||||
| 		<div class="header" @contextmenu="onContextmenu"> | 		<div class="header" @contextmenu="onContextmenu"> | ||||||
| 			<button class="_button" @click="back()" v-if="history.length > 0"><i class="fas fa-chevron-left"></i></button> |  | ||||||
| 			<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button> |  | ||||||
| 			<span class="title"> | 			<span class="title"> | ||||||
| 				<XHeader :info="pageInfo" :with-back="false"/> | 				<XHeader :info="pageInfo" :back-button="history.length > 0" @back="back()" :close-button="true" @close="$refs.modal.close()"/> | ||||||
| 			</span> | 			</span> | ||||||
| 			<button class="_button" @click="$refs.modal.close()"><i class="fas fa-times"></i></button> |  | ||||||
| 		</div> | 		</div> | ||||||
| 		<div class="body _flat_"> | 		<div class="body _flat_"> | ||||||
| 			<keep-alive> | 			<keep-alive> | ||||||
| @@ -177,35 +174,19 @@ export default defineComponent({ | |||||||
| 		flex-shrink: 0; | 		flex-shrink: 0; | ||||||
| 		box-shadow: 0px 1px var(--divider); | 		box-shadow: 0px 1px var(--divider); | ||||||
|  |  | ||||||
| 		> button { |  | ||||||
| 			height: $height; |  | ||||||
| 			width: $height; |  | ||||||
|  |  | ||||||
| 			@media (max-width: 500px) { |  | ||||||
| 				height: $height-narrow; |  | ||||||
| 				width: $height-narrow; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> .title { | 		> .title { | ||||||
| 			flex: 1; | 			flex: 1; | ||||||
| 			line-height: $height; | 			height: $height; | ||||||
| 			padding-left: 32px; |  | ||||||
| 			font-weight: bold; | 			font-weight: bold; | ||||||
| 			white-space: nowrap; | 			white-space: nowrap; | ||||||
| 			overflow: hidden; | 			overflow: hidden; | ||||||
| 			text-overflow: ellipsis; | 			text-overflow: ellipsis; | ||||||
| 			pointer-events: none; |  | ||||||
|  |  | ||||||
| 			@media (max-width: 500px) { | 			@media (max-width: 500px) { | ||||||
| 				line-height: $height-narrow; | 				height: $height-narrow; | ||||||
| 				padding-left: 16px; | 				padding-left: 16px; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		> button + .title { |  | ||||||
| 			padding-left: 0; |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	> .body { | 	> .body { | ||||||
|   | |||||||
| @@ -454,7 +454,7 @@ export default defineComponent({ | |||||||
| 		renote(viaKeyboard = false) { | 		renote(viaKeyboard = false) { | ||||||
| 			pleaseLogin(); | 			pleaseLogin(); | ||||||
| 			this.blur(); | 			this.blur(); | ||||||
| 			os.modalMenu([{ | 			os.popupMenu([{ | ||||||
| 				text: this.$ts.renote, | 				text: this.$ts.renote, | ||||||
| 				icon: 'fas fa-retweet', | 				icon: 'fas fa-retweet', | ||||||
| 				action: () => { | 				action: () => { | ||||||
| @@ -743,14 +743,14 @@ export default defineComponent({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		menu(viaKeyboard = false) { | 		menu(viaKeyboard = false) { | ||||||
| 			os.modalMenu(this.getMenu(), this.$refs.menuButton, { | 			os.popupMenu(this.getMenu(), this.$refs.menuButton, { | ||||||
| 				viaKeyboard | 				viaKeyboard | ||||||
| 			}).then(this.focus); | 			}).then(this.focus); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		showRenoteMenu(viaKeyboard = false) { | 		showRenoteMenu(viaKeyboard = false) { | ||||||
| 			if (!this.isMyRenote) return; | 			if (!this.isMyRenote) return; | ||||||
| 			os.modalMenu([{ | 			os.popupMenu([{ | ||||||
| 				text: this.$ts.unrenote, | 				text: this.$ts.unrenote, | ||||||
| 				icon: 'fas fa-trash-alt', | 				icon: 'fas fa-trash-alt', | ||||||
| 				danger: true, | 				danger: true, | ||||||
| @@ -794,7 +794,7 @@ export default defineComponent({ | |||||||
|  |  | ||||||
| 		async clip() { | 		async clip() { | ||||||
| 			const clips = await os.api('clips/list'); | 			const clips = await os.api('clips/list'); | ||||||
| 			os.modalMenu([{ | 			os.popupMenu([{ | ||||||
| 				icon: 'fas fa-plus', | 				icon: 'fas fa-plus', | ||||||
| 				text: this.$ts.createNew, | 				text: this.$ts.createNew, | ||||||
| 				action: async () => { | 				action: async () => { | ||||||
|   | |||||||
| @@ -24,8 +24,8 @@ | |||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent } from 'vue'; | import { defineComponent } from 'vue'; | ||||||
| import notePage from '../filters/note'; | import notePage from '@client/filters/note'; | ||||||
| import { userPage } from '../filters/user'; | import { userPage } from '@client/filters/user'; | ||||||
| import * as os from '@client/os'; | import * as os from '@client/os'; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|   | |||||||
| @@ -429,7 +429,7 @@ export default defineComponent({ | |||||||
| 		renote(viaKeyboard = false) { | 		renote(viaKeyboard = false) { | ||||||
| 			pleaseLogin(); | 			pleaseLogin(); | ||||||
| 			this.blur(); | 			this.blur(); | ||||||
| 			os.modalMenu([{ | 			os.popupMenu([{ | ||||||
| 				text: this.$ts.renote, | 				text: this.$ts.renote, | ||||||
| 				icon: 'fas fa-retweet', | 				icon: 'fas fa-retweet', | ||||||
| 				action: () => { | 				action: () => { | ||||||
| @@ -718,14 +718,14 @@ export default defineComponent({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		menu(viaKeyboard = false) { | 		menu(viaKeyboard = false) { | ||||||
| 			os.modalMenu(this.getMenu(), this.$refs.menuButton, { | 			os.popupMenu(this.getMenu(), this.$refs.menuButton, { | ||||||
| 				viaKeyboard | 				viaKeyboard | ||||||
| 			}).then(this.focus); | 			}).then(this.focus); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		showRenoteMenu(viaKeyboard = false) { | 		showRenoteMenu(viaKeyboard = false) { | ||||||
| 			if (!this.isMyRenote) return; | 			if (!this.isMyRenote) return; | ||||||
| 			os.modalMenu([{ | 			os.popupMenu([{ | ||||||
| 				text: this.$ts.unrenote, | 				text: this.$ts.unrenote, | ||||||
| 				icon: 'fas fa-trash-alt', | 				icon: 'fas fa-trash-alt', | ||||||
| 				danger: true, | 				danger: true, | ||||||
| @@ -769,7 +769,7 @@ export default defineComponent({ | |||||||
|  |  | ||||||
| 		async clip() { | 		async clip() { | ||||||
| 			const clips = await os.api('clips/list'); | 			const clips = await os.api('clips/list'); | ||||||
| 			os.modalMenu([{ | 			os.popupMenu([{ | ||||||
| 				icon: 'fas fa-plus', | 				icon: 'fas fa-plus', | ||||||
| 				text: this.$ts.createNew, | 				text: this.$ts.createNew, | ||||||
| 				action: async () => { | 				action: async () => { | ||||||
|   | |||||||
| @@ -11,16 +11,16 @@ | |||||||
| 	<template #header>{{ $ts.notificationSetting }}</template> | 	<template #header>{{ $ts.notificationSetting }}</template> | ||||||
| 	<div class="_monolithic_"> | 	<div class="_monolithic_"> | ||||||
| 		<div v-if="showGlobalToggle" class="_section"> | 		<div v-if="showGlobalToggle" class="_section"> | ||||||
| 			<MkSwitch v-model:value="useGlobalSetting"> | 			<MkSwitch v-model="useGlobalSetting"> | ||||||
| 				{{ $ts.useGlobalSetting }} | 				{{ $ts.useGlobalSetting }} | ||||||
| 				<template #desc>{{ $ts.useGlobalSettingDesc }}</template> | 				<template #caption>{{ $ts.useGlobalSettingDesc }}</template> | ||||||
| 			</MkSwitch> | 			</MkSwitch> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div v-if="!useGlobalSetting" class="_section"> | 		<div v-if="!useGlobalSetting" class="_section"> | ||||||
| 			<MkInfo>{{ $ts.notificationSettingDesc }}</MkInfo> | 			<MkInfo>{{ $ts.notificationSettingDesc }}</MkInfo> | ||||||
| 			<MkButton inline @click="disableAll">{{ $ts.disableAll }}</MkButton> | 			<MkButton inline @click="disableAll">{{ $ts.disableAll }}</MkButton> | ||||||
| 			<MkButton inline @click="enableAll">{{ $ts.enableAll }}</MkButton> | 			<MkButton inline @click="enableAll">{{ $ts.enableAll }}</MkButton> | ||||||
| 			<MkSwitch v-for="type in notificationTypes" :key="type" v-model:value="typesMap[type]">{{ $t(`_notification._types.${type}`) }}</MkSwitch> | 			<MkSwitch v-for="type in notificationTypes" :key="type" v-model="typesMap[type]">{{ $t(`_notification._types.${type}`) }}</MkSwitch> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| </XModalWindow> | </XModalWindow> | ||||||
|   | |||||||
| @@ -58,12 +58,12 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent } from 'vue'; | import { defineComponent, markRaw } from 'vue'; | ||||||
| import { getNoteSummary } from '@/misc/get-note-summary'; | import { getNoteSummary } from '@/misc/get-note-summary'; | ||||||
| import XReactionIcon from './reaction-icon.vue'; | import XReactionIcon from './reaction-icon.vue'; | ||||||
| import MkFollowButton from './follow-button.vue'; | import MkFollowButton from './follow-button.vue'; | ||||||
| import notePage from '../filters/note'; | import notePage from '@client/filters/note'; | ||||||
| import { userPage } from '../filters/user'; | import { userPage } from '@client/filters/user'; | ||||||
| import { i18n } from '@client/i18n'; | import { i18n } from '@client/i18n'; | ||||||
| import * as os from '@client/os'; | import * as os from '@client/os'; | ||||||
|  |  | ||||||
| @@ -109,7 +109,7 @@ export default defineComponent({ | |||||||
|  |  | ||||||
| 			this.readObserver.observe(this.$el); | 			this.readObserver.observe(this.$el); | ||||||
|  |  | ||||||
| 			this.connection = os.stream.useChannel('main'); | 			this.connection = markRaw(os.stream.useChannel('main')); | ||||||
| 			this.connection.on('readAllNotifications', () => this.readObserver.unobserve(this.$el)); | 			this.connection.on('readAllNotifications', () => this.readObserver.unobserve(this.$el)); | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent, PropType } from 'vue'; | import { defineComponent, PropType, markRaw } from 'vue'; | ||||||
| import paging from '@client/scripts/paging'; | import paging from '@client/scripts/paging'; | ||||||
| import XNotification from './notification.vue'; | import XNotification from './notification.vue'; | ||||||
| import XList from './date-separated-list.vue'; | import XList from './date-separated-list.vue'; | ||||||
| @@ -89,7 +89,7 @@ export default defineComponent({ | |||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	mounted() { | 	mounted() { | ||||||
| 		this.connection = os.stream.useChannel('main'); | 		this.connection = markRaw(os.stream.useChannel('main')); | ||||||
| 		this.connection.on('notification', this.onNotification); | 		this.connection.on('notification', this.onNotification); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ | |||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent } from 'vue'; | import { defineComponent } from 'vue'; | ||||||
| import { userName } from '../filters/user'; | import { userName } from '@client/filters/user'; | ||||||
| import * as os from '@client/os'; | import * as os from '@client/os'; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|   | |||||||
| @@ -3,16 +3,12 @@ | |||||||
| 	:initial-width="500" | 	:initial-width="500" | ||||||
| 	:initial-height="500" | 	:initial-height="500" | ||||||
| 	:can-resize="true" | 	:can-resize="true" | ||||||
| 	:close-right="true" | 	:close-button="false" | ||||||
| 	:contextmenu="contextmenu" | 	:contextmenu="contextmenu" | ||||||
| 	@closed="$emit('closed')" | 	@closed="$emit('closed')" | ||||||
| > | > | ||||||
| 	<template #header> | 	<template #header> | ||||||
| 		<XHeader :info="pageInfo" :with-back="false"/> | 		<XHeader :info="pageInfo" :back-button="history.length > 0" @back="back()" :close-button="true" @close="close()"/> | ||||||
| 	</template> |  | ||||||
| 	<template #buttons> |  | ||||||
| 		<button class="_button" @click="back()" v-if="history.length > 0"><i class="fas fa-chevron-left"></i></button> |  | ||||||
| 		<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button> |  | ||||||
| 	</template> | 	</template> | ||||||
| 	<div class="yrolvcoq _flat_"> | 	<div class="yrolvcoq _flat_"> | ||||||
| 		<component :is="component" v-bind="props" :ref="changePage"/> | 		<component :is="component" v-bind="props" :ref="changePage"/> | ||||||
| @@ -139,6 +135,10 @@ export default defineComponent({ | |||||||
| 			this.navigate(this.history.pop(), false); | 			this.navigate(this.history.pop(), false); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | 		close() { | ||||||
|  | 			this.$refs.window.close(); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
| 		expand() { | 		expand() { | ||||||
| 			this.$router.push(this.path); | 			this.$router.push(this.path); | ||||||
| 			this.$refs.window.close(); | 			this.$refs.window.close(); | ||||||
| @@ -155,6 +155,5 @@ export default defineComponent({ | |||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .yrolvcoq { | .yrolvcoq { | ||||||
| 	min-height: 100%; | 	min-height: 100%; | ||||||
| 	background: var(--bg); |  | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| <template> | <template> | ||||||
| <div> | <div> | ||||||
| 	<MkInput class="kudkigyw" :value="value" @update:value="updateValue($event)" type="number">{{ hpml.interpolate(block.text) }}</MkInput> | 	<MkInput class="kudkigyw" :model-value="value" @update:modelValue="updateValue($event)" type="number"> | ||||||
|  | 		<template #label>{{ hpml.interpolate(block.text) }}</template> | ||||||
|  | 	</MkInput> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
| <div class="ngbfujlo"> | <div class="ngbfujlo"> | ||||||
| 	<MkTextarea :value="text" readonly style="margin: 0;"></MkTextarea> | 	<MkTextarea :model-value="text" readonly style="margin: 0;"></MkTextarea> | ||||||
| 	<MkButton class="button" primary @click="post()" :disabled="posting || posted"> | 	<MkButton class="button" primary @click="post()" :disabled="posting || posted"> | ||||||
| 		<i v-if="posted" class="fas fa-check"></i> | 		<i v-if="posted" class="fas fa-check"></i> | ||||||
| 		<i v-else class="fas fa-paper-plane"></i> | 		<i v-else class="fas fa-paper-plane"></i> | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
| <div class="hkcxmtwj"> | <div class="hkcxmtwj"> | ||||||
| 	<MkSwitch :value="value" @update:value="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkSwitch> | 	<MkSwitch :model-value="value" @update:modelValue="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkSwitch> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| <template> | <template> | ||||||
| <div> | <div> | ||||||
| 	<MkInput class="kudkigyw" :value="value" @update:value="updateValue($event)" type="text">{{ hpml.interpolate(block.text) }}</MkInput> | 	<MkInput class="kudkigyw" :model-value="value" @update:modelValue="updateValue($event)" type="text"> | ||||||
|  | 		<template #label>{{ hpml.interpolate(block.text) }}</template> | ||||||
|  | 	</MkInput> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| <template> | <template> | ||||||
| <div> | <div> | ||||||
| 	<MkTextarea :value="value" @update:value="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkTextarea> | 	<MkTextarea :model-value="value" @update:modelValue="updateValue($event)"> | ||||||
|  | 		<template #label>{{ hpml.interpolate(block.text) }}</template> | ||||||
|  | 	</MkTextarea> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| <template> | <template> | ||||||
| <MkTextarea :value="text" readonly></MkTextarea> | <MkTextarea :model-value="text" readonly></MkTextarea> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   | |||||||
| @@ -5,8 +5,8 @@ | |||||||
| 	</p> | 	</p> | ||||||
| 	<ul ref="choices"> | 	<ul ref="choices"> | ||||||
| 		<li v-for="(choice, i) in choices" :key="i"> | 		<li v-for="(choice, i) in choices" :key="i"> | ||||||
| 			<MkInput class="input" :value="choice" @update:value="onInput(i, $event)"> | 			<MkInput class="input" :model-value="choice" @update:modelValue="onInput(i, $event)"> | ||||||
| 				<span>{{ $t('_poll.choiceN', { n: i + 1 }) }}</span> | 				<template #label>{{ $t('_poll.choiceN', { n: i + 1 }) }}</template> | ||||||
| 			</MkInput> | 			</MkInput> | ||||||
| 			<button @click="remove(i)" class="_button"> | 			<button @click="remove(i)" class="_button"> | ||||||
| 				<i class="fas fa-times"></i> | 				<i class="fas fa-times"></i> | ||||||
| @@ -16,27 +16,27 @@ | |||||||
| 	<MkButton class="add" v-if="choices.length < 10" @click="add">{{ $ts.add }}</MkButton> | 	<MkButton class="add" v-if="choices.length < 10" @click="add">{{ $ts.add }}</MkButton> | ||||||
| 	<MkButton class="add" v-else disabled>{{ $ts._poll.noMore }}</MkButton> | 	<MkButton class="add" v-else disabled>{{ $ts._poll.noMore }}</MkButton> | ||||||
| 	<section> | 	<section> | ||||||
| 		<MkSwitch v-model:value="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch> | 		<MkSwitch v-model="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch> | ||||||
| 		<div> | 		<div> | ||||||
| 			<MkSelect v-model:value="expiration"> | 			<MkSelect v-model="expiration"> | ||||||
| 				<template #label>{{ $ts._poll.expiration }}</template> | 				<template #label>{{ $ts._poll.expiration }}</template> | ||||||
| 				<option value="infinite">{{ $ts._poll.infinite }}</option> | 				<option value="infinite">{{ $ts._poll.infinite }}</option> | ||||||
| 				<option value="at">{{ $ts._poll.at }}</option> | 				<option value="at">{{ $ts._poll.at }}</option> | ||||||
| 				<option value="after">{{ $ts._poll.after }}</option> | 				<option value="after">{{ $ts._poll.after }}</option> | ||||||
| 			</MkSelect> | 			</MkSelect> | ||||||
| 			<section v-if="expiration === 'at'"> | 			<section v-if="expiration === 'at'"> | ||||||
| 				<MkInput v-model:value="atDate" type="date" class="input"> | 				<MkInput v-model="atDate" type="date" class="input"> | ||||||
| 					<span>{{ $ts._poll.deadlineDate }}</span> | 					<template #label>{{ $ts._poll.deadlineDate }}</template> | ||||||
| 				</MkInput> | 				</MkInput> | ||||||
| 				<MkInput v-model:value="atTime" type="time" class="input"> | 				<MkInput v-model="atTime" type="time" class="input"> | ||||||
| 					<span>{{ $ts._poll.deadlineTime }}</span> | 					<template #label>{{ $ts._poll.deadlineTime }}</template> | ||||||
| 				</MkInput> | 				</MkInput> | ||||||
| 			</section> | 			</section> | ||||||
| 			<section v-if="expiration === 'after'"> | 			<section v-if="expiration === 'after'"> | ||||||
| 				<MkInput v-model:value="after" type="number" class="input"> | 				<MkInput v-model="after" type="number" class="input"> | ||||||
| 					<span>{{ $ts._poll.duration }}</span> | 					<template #label>{{ $ts._poll.duration }}</template> | ||||||
| 				</MkInput> | 				</MkInput> | ||||||
| 				<MkSelect v-model:value="unit"> | 				<MkSelect v-model="unit"> | ||||||
| 					<option value="second">{{ $ts._time.second }}</option> | 					<option value="second">{{ $ts._time.second }}</option> | ||||||
| 					<option value="minute">{{ $ts._time.minute }}</option> | 					<option value="minute">{{ $ts._time.minute }}</option> | ||||||
| 					<option value="hour">{{ $ts._time.hour }}</option> | 					<option value="hour">{{ $ts._time.hour }}</option> | ||||||
|   | |||||||
| @@ -112,7 +112,7 @@ export default defineComponent({ | |||||||
|  |  | ||||||
| 		showFileMenu(file, ev: MouseEvent) { | 		showFileMenu(file, ev: MouseEvent) { | ||||||
| 			if (this.menu) return; | 			if (this.menu) return; | ||||||
| 			this.menu = os.modalMenu([{ | 			this.menu = os.popupMenu([{ | ||||||
| 				text: this.$ts.renameFile, | 				text: this.$ts.renameFile, | ||||||
| 				icon: 'fas fa-i-cursor', | 				icon: 'fas fa-i-cursor', | ||||||
| 				action: () => { this.rename(file) } | 				action: () => { this.rename(file) } | ||||||
|   | |||||||
| @@ -37,6 +37,7 @@ | |||||||
| 		<MkInfo warn v-if="hasNotSpecifiedMentions" class="hasNotSpecifiedMentions">{{ $ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ $ts.add }}</button></MkInfo> | 		<MkInfo warn v-if="hasNotSpecifiedMentions" class="hasNotSpecifiedMentions">{{ $ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ $ts.add }}</button></MkInfo> | ||||||
| 		<input v-show="useCw" ref="cw" class="cw" v-model="cw" :placeholder="$ts.annotation" @keydown="onKeydown"> | 		<input v-show="useCw" ref="cw" class="cw" v-model="cw" :placeholder="$ts.annotation" @keydown="onKeydown"> | ||||||
| 		<textarea v-model="text" class="text" :class="{ withCw: useCw }" ref="text" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd" /> | 		<textarea v-model="text" class="text" :class="{ withCw: useCw }" ref="text" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd" /> | ||||||
|  | 		<input v-show="withHashtags" ref="hashtags" class="hashtags" v-model="hashtags" :placeholder="$ts.hashtags" list="hashtags"> | ||||||
| 		<XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> | 		<XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> | ||||||
| 		<XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/> | 		<XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/> | ||||||
| 		<footer> | 		<footer> | ||||||
| @@ -44,9 +45,13 @@ | |||||||
| 			<button class="_button" @click="togglePoll" :class="{ active: poll }" v-tooltip="$ts.poll"><i class="fas fa-poll-h"></i></button> | 			<button class="_button" @click="togglePoll" :class="{ active: poll }" v-tooltip="$ts.poll"><i class="fas fa-poll-h"></i></button> | ||||||
| 			<button class="_button" @click="useCw = !useCw" :class="{ active: useCw }" v-tooltip="$ts.useCw"><i class="fas fa-eye-slash"></i></button> | 			<button class="_button" @click="useCw = !useCw" :class="{ active: useCw }" v-tooltip="$ts.useCw"><i class="fas fa-eye-slash"></i></button> | ||||||
| 			<button class="_button" @click="insertMention" v-tooltip="$ts.mention"><i class="fas fa-at"></i></button> | 			<button class="_button" @click="insertMention" v-tooltip="$ts.mention"><i class="fas fa-at"></i></button> | ||||||
|  | 			<button class="_button" @click="withHashtags = !withHashtags" v-tooltip="$ts.hashtags"><i class="fas fa-hashtag"></i></button> | ||||||
| 			<button class="_button" @click="insertEmoji" v-tooltip="$ts.emoji"><i class="fas fa-laugh-squint"></i></button> | 			<button class="_button" @click="insertEmoji" v-tooltip="$ts.emoji"><i class="fas fa-laugh-squint"></i></button> | ||||||
| 			<button class="_button" @click="showActions" v-tooltip="$ts.plugin" v-if="postFormActions.length > 0"><i class="fas fa-plug"></i></button> | 			<button class="_button" @click="showActions" v-tooltip="$ts.plugin" v-if="postFormActions.length > 0"><i class="fas fa-plug"></i></button> | ||||||
| 		</footer> | 		</footer> | ||||||
|  | 		<datalist id="hashtags"> | ||||||
|  | 			<option v-for="hashtag in recentHashtags" :value="hashtag" :key="hashtag"/> | ||||||
|  | 		</datalist> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
| @@ -67,10 +72,11 @@ import { Autocomplete } from '@client/scripts/autocomplete'; | |||||||
| import { noteVisibilities } from '../../types'; | import { noteVisibilities } from '../../types'; | ||||||
| import * as os from '@client/os'; | import * as os from '@client/os'; | ||||||
| import { selectFile } from '@client/scripts/select-file'; | import { selectFile } from '@client/scripts/select-file'; | ||||||
| import { notePostInterruptors, postFormActions } from '@client/store'; | import { defaultStore, notePostInterruptors, postFormActions } from '@client/store'; | ||||||
| import { isMobile } from '@client/scripts/is-mobile'; | import { isMobile } from '@client/scripts/is-mobile'; | ||||||
| import { throttle } from 'throttle-debounce'; | import { throttle } from 'throttle-debounce'; | ||||||
| import MkInfo from '@client/components/ui/info.vue'; | import MkInfo from '@client/components/ui/info.vue'; | ||||||
|  | import { defaultStore } from '@client/store'; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| 	components: { | 	components: { | ||||||
| @@ -212,7 +218,10 @@ export default defineComponent({ | |||||||
|  |  | ||||||
| 		max(): number { | 		max(): number { | ||||||
| 			return this.$instance ? this.$instance.maxNoteTextLength : 1000; | 			return this.$instance ? this.$instance.maxNoteTextLength : 1000; | ||||||
| 		} | 		}, | ||||||
|  |  | ||||||
|  | 		withHashtags: defaultStore.makeGetterSetter('postFormWithHashtags'), | ||||||
|  | 		hashtags: defaultStore.makeGetterSetter('postFormHashtags'), | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	watch: { | 	watch: { | ||||||
| @@ -303,6 +312,7 @@ export default defineComponent({ | |||||||
| 		// TODO: detach when unmount | 		// TODO: detach when unmount | ||||||
| 		new Autocomplete(this.$refs.text, this, { model: 'text' }); | 		new Autocomplete(this.$refs.text, this, { model: 'text' }); | ||||||
| 		new Autocomplete(this.$refs.cw, this, { model: 'cw' }); | 		new Autocomplete(this.$refs.cw, this, { model: 'cw' }); | ||||||
|  | 		new Autocomplete(this.$refs.hashtags, this, { model: 'hashtags' }); | ||||||
|  |  | ||||||
| 		this.$nextTick(() => { | 		this.$nextTick(() => { | ||||||
| 			// 書きかけの投稿を復元 | 			// 書きかけの投稿を復元 | ||||||
| @@ -605,6 +615,11 @@ export default defineComponent({ | |||||||
| 				viaMobile: isMobile | 				viaMobile: isMobile | ||||||
| 			}; | 			}; | ||||||
|  |  | ||||||
|  | 			if (this.withHashtags) { | ||||||
|  | 				const hashtags = this.hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); | ||||||
|  | 				data.text = data.text ? `${data.text} ${hashtags}` : hashtags; | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			// plugin | 			// plugin | ||||||
| 			if (notePostInterruptors.length > 0) { | 			if (notePostInterruptors.length > 0) { | ||||||
| 				for (const interruptor of notePostInterruptors) { | 				for (const interruptor of notePostInterruptors) { | ||||||
| @@ -618,8 +633,8 @@ export default defineComponent({ | |||||||
| 				this.$nextTick(() => { | 				this.$nextTick(() => { | ||||||
| 					this.deleteDraft(); | 					this.deleteDraft(); | ||||||
| 					this.$emit('posted'); | 					this.$emit('posted'); | ||||||
| 					if (this.text && this.text != '') { | 					if (data.text && data.text != '') { | ||||||
| 						const hashtags = mfm.parse(this.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); | 						const hashtags = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); | ||||||
| 						const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; | 						const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; | ||||||
| 						localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); | 						localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); | ||||||
| 					} | 					} | ||||||
| @@ -649,7 +664,7 @@ export default defineComponent({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		showActions(ev) { | 		showActions(ev) { | ||||||
| 			os.modalMenu(postFormActions.map(action => ({ | 			os.popupMenu(postFormActions.map(action => ({ | ||||||
| 				text: action.title, | 				text: action.title, | ||||||
| 				action: () => { | 				action: () => { | ||||||
| 					action.handler({ | 					action.handler({ | ||||||
| @@ -785,6 +800,7 @@ export default defineComponent({ | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		> .cw, | 		> .cw, | ||||||
|  | 		> .hashtags, | ||||||
| 		> .text { | 		> .text { | ||||||
| 			display: block; | 			display: block; | ||||||
| 			box-sizing: border-box; | 			box-sizing: border-box; | ||||||
| @@ -813,6 +829,13 @@ export default defineComponent({ | |||||||
| 			border-bottom: solid 0.5px var(--divider); | 			border-bottom: solid 0.5px var(--divider); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		> .hashtags { | ||||||
|  | 			z-index: 1; | ||||||
|  | 			padding-top: 8px; | ||||||
|  | 			padding-bottom: 8px; | ||||||
|  | 			border-top: solid 0.5px var(--divider); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		> .text { | 		> .text { | ||||||
| 			max-width: 100%; | 			max-width: 100%; | ||||||
| 			min-width: 100%; | 			min-width: 100%; | ||||||
| @@ -872,6 +895,7 @@ export default defineComponent({ | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			> .cw, | 			> .cw, | ||||||
|  | 			> .hashtags, | ||||||
| 			> .text { | 			> .text { | ||||||
| 				padding: 0 16px; | 				padding: 0 16px; | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| <template> | <template> | ||||||
| <div class="_card"> | <div class="_card"> | ||||||
| 	<div class="_content"> | 	<div class="_content"> | ||||||
| 		<MkInput v-model:value="text"> | 		<MkInput v-model="text"> | ||||||
| 			<span>Text</span> | 			<template #label>Text</template> | ||||||
| 		</MkInput> | 		</MkInput> | ||||||
| 		<MkSwitch v-model:value="flag"> | 		<MkSwitch v-model="flag"> | ||||||
| 			<span>Switch is now {{ flag ? 'on' : 'off' }}</span> | 			<span>Switch is now {{ flag ? 'on' : 'off' }}</span> | ||||||
| 		</MkSwitch> | 		</MkSwitch> | ||||||
| 		<div style="margin: 32px 0;"> | 		<div style="margin: 32px 0;"> | ||||||
| @@ -93,7 +93,7 @@ export default defineComponent({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		async openMenu(ev) { | 		async openMenu(ev) { | ||||||
| 			os.modalMenu([{ | 			os.popupMenu([{ | ||||||
| 				type: 'label', | 				type: 'label', | ||||||
| 				text: 'Fruits' | 				text: 'Fruits' | ||||||
| 			}, { | 			}, { | ||||||
|   | |||||||
| @@ -3,15 +3,13 @@ | |||||||
| 	<div class="auth _section"> | 	<div class="auth _section"> | ||||||
| 		<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div> | 		<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div> | ||||||
| 		<div class="normal-signin" v-if="!totpLogin"> | 		<div class="normal-signin" v-if="!totpLogin"> | ||||||
| 			<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:value="onUsernameChange"> | 			<MkInput v-model="username" :placeholder="$ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:modelValue="onUsernameChange"> | ||||||
| 				<span>{{ $ts.username }}</span> |  | ||||||
| 				<template #prefix>@</template> | 				<template #prefix>@</template> | ||||||
| 				<template #suffix>@{{ host }}</template> | 				<template #suffix>@{{ host }}</template> | ||||||
| 			</MkInput> | 			</MkInput> | ||||||
| 			<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required> | 			<MkInput v-model="password" :placeholder="$ts.password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required> | ||||||
| 				<span>{{ $ts.password }}</span> |  | ||||||
| 				<template #prefix><i class="fas fa-lock"></i></template> | 				<template #prefix><i class="fas fa-lock"></i></template> | ||||||
| 				<template #desc><button class="_textButton" @click="resetPassword">{{ $ts.forgotPassword }}</button></template> | 				<template #caption><button class="_textButton" @click="resetPassword" type="button">{{ $ts.forgotPassword }}</button></template> | ||||||
| 			</MkInput> | 			</MkInput> | ||||||
| 			<MkButton type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton> | 			<MkButton type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton> | ||||||
| 		</div> | 		</div> | ||||||
| @@ -27,12 +25,12 @@ | |||||||
| 			</div> | 			</div> | ||||||
| 			<div class="twofa-group totp-group"> | 			<div class="twofa-group totp-group"> | ||||||
| 				<p style="margin-bottom:0;">{{ $ts.twoStepAuthentication }}</p> | 				<p style="margin-bottom:0;">{{ $ts.twoStepAuthentication }}</p> | ||||||
| 				<MkInput v-model:value="password" type="password" :with-password-toggle="true" v-if="user && user.usePasswordLessLogin" required> | 				<MkInput v-model="password" type="password" :with-password-toggle="true" v-if="user && user.usePasswordLessLogin" required> | ||||||
| 					<span>{{ $ts.password }}</span> | 					<template #label>{{ $ts.password }}</template> | ||||||
| 					<template #prefix><i class="fas fa-lock"></i></template> | 					<template #prefix><i class="fas fa-lock"></i></template> | ||||||
| 				</MkInput> | 				</MkInput> | ||||||
| 				<MkInput v-model:value="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required> | 				<MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required> | ||||||
| 					<span>{{ $ts.token }}</span> | 					<template #label>{{ $ts.token }}</template> | ||||||
| 					<template #prefix><i class="fas fa-gavel"></i></template> | 					<template #prefix><i class="fas fa-gavel"></i></template> | ||||||
| 				</MkInput> | 				</MkInput> | ||||||
| 				<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton> | 				<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton> | ||||||
|   | |||||||
| @@ -1,39 +1,39 @@ | |||||||
| <template> | <template> | ||||||
| <form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()"> | <form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()"> | ||||||
| 	<template v-if="meta"> | 	<template v-if="meta"> | ||||||
| 		<MkInput v-if="meta.disableRegistration" v-model:value="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required> | 		<MkInput v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required> | ||||||
| 			<span>{{ $ts.invitationCode }}</span> | 			<template #label>{{ $ts.invitationCode }}</template> | ||||||
| 			<template #prefix><i class="fas fa-key"></i></template> | 			<template #prefix><i class="fas fa-key"></i></template> | ||||||
| 		</MkInput> | 		</MkInput> | ||||||
| 		<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @update:value="onChangeUsername"> | 		<MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @update:modelValue="onChangeUsername"> | ||||||
| 			<span>{{ $ts.username }}</span> | 			<template #label>{{ $ts.username }}</template> | ||||||
| 			<template #prefix>@</template> | 			<template #prefix>@</template> | ||||||
| 			<template #suffix>@{{ host }}</template> | 			<template #suffix>@{{ host }}</template> | ||||||
| 			<template #desc> | 			<template #caption> | ||||||
| 				<span v-if="usernameState == 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span> | 				<span v-if="usernameState == 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span> | ||||||
| 				<span v-if="usernameState == 'ok'" style="color:#3CB7B5"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span> | 				<span v-if="usernameState == 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span> | ||||||
| 				<span v-if="usernameState == 'unavailable'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span> | 				<span v-if="usernameState == 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span> | ||||||
| 				<span v-if="usernameState == 'error'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span> | 				<span v-if="usernameState == 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span> | ||||||
| 				<span v-if="usernameState == 'invalid-format'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.usernameInvalidFormat }}</span> | 				<span v-if="usernameState == 'invalid-format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.usernameInvalidFormat }}</span> | ||||||
| 				<span v-if="usernameState == 'min-range'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooShort }}</span> | 				<span v-if="usernameState == 'min-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooShort }}</span> | ||||||
| 				<span v-if="usernameState == 'max-range'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span> | 				<span v-if="usernameState == 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span> | ||||||
| 			</template> | 			</template> | ||||||
| 		</MkInput> | 		</MkInput> | ||||||
| 		<MkInput v-model:value="password" type="password" :autocomplete="Math.random()" required @update:value="onChangePassword"> | 		<MkInput v-model="password" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePassword"> | ||||||
| 			<span>{{ $ts.password }}</span> | 			<template #label>{{ $ts.password }}</template> | ||||||
| 			<template #prefix><i class="fas fa-lock"></i></template> | 			<template #prefix><i class="fas fa-lock"></i></template> | ||||||
| 			<template #desc> | 			<template #caption> | ||||||
| 				<p v-if="passwordStrength == 'low'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.weakPassword }}</p> | 				<span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.weakPassword }}</span> | ||||||
| 				<p v-if="passwordStrength == 'medium'" style="color:#3CB7B5"><i class="fas fa-check fa-fw"></i> {{ $ts.normalPassword }}</p> | 				<span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="fas fa-check fa-fw"></i> {{ $ts.normalPassword }}</span> | ||||||
| 				<p v-if="passwordStrength == 'high'" style="color:#3CB7B5"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</p> | 				<span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</span> | ||||||
| 			</template> | 			</template> | ||||||
| 		</MkInput> | 		</MkInput> | ||||||
| 		<MkInput v-model:value="retypedPassword" type="password" :autocomplete="Math.random()" required @update:value="onChangePasswordRetype"> | 		<MkInput v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePasswordRetype"> | ||||||
| 			<span>{{ $ts.password }} ({{ $ts.retype }})</span> | 			<template #label>{{ $ts.password }} ({{ $ts.retype }})</template> | ||||||
| 			<template #prefix><i class="fas fa-lock"></i></template> | 			<template #prefix><i class="fas fa-lock"></i></template> | ||||||
| 			<template #desc> | 			<template #caption> | ||||||
| 				<p v-if="passwordRetypeState == 'match'" style="color:#3CB7B5"><i class="fas fa-check fa-fw"></i> {{ $ts.passwordMatched }}</p> | 				<span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.passwordMatched }}</span> | ||||||
| 				<p v-if="passwordRetypeState == 'not-match'" style="color:#FF1161"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</p> | 				<span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</span> | ||||||
| 			</template> | 			</template> | ||||||
| 		</MkInput> | 		</MkInput> | ||||||
| 		<label v-if="meta.tosUrl" class="tou"> | 		<label v-if="meta.tosUrl" class="tou"> | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent } from 'vue'; | import { defineComponent, markRaw } from 'vue'; | ||||||
| import XNotes from './notes.vue'; | import XNotes from './notes.vue'; | ||||||
| import * as os from '@client/os'; | import * as os from '@client/os'; | ||||||
| import * as sound from '@client/scripts/sound'; | import * as sound from '@client/scripts/sound'; | ||||||
| @@ -92,33 +92,33 @@ export default defineComponent({ | |||||||
| 			this.query = { | 			this.query = { | ||||||
| 				antennaId: this.antenna | 				antennaId: this.antenna | ||||||
| 			}; | 			}; | ||||||
| 			this.connection = os.stream.useChannel('antenna', { | 			this.connection = markRaw(os.stream.useChannel('antenna', { | ||||||
| 				antennaId: this.antenna | 				antennaId: this.antenna | ||||||
| 			}); | 			})); | ||||||
| 			this.connection.on('note', prepend); | 			this.connection.on('note', prepend); | ||||||
| 		} else if (this.src == 'home') { | 		} else if (this.src == 'home') { | ||||||
| 			endpoint = 'notes/timeline'; | 			endpoint = 'notes/timeline'; | ||||||
| 			this.connection = os.stream.useChannel('homeTimeline'); | 			this.connection = markRaw(os.stream.useChannel('homeTimeline')); | ||||||
| 			this.connection.on('note', prepend); | 			this.connection.on('note', prepend); | ||||||
|  |  | ||||||
| 			this.connection2 = os.stream.useChannel('main'); | 			this.connection2 = markRaw(os.stream.useChannel('main')); | ||||||
| 			this.connection2.on('follow', onChangeFollowing); | 			this.connection2.on('follow', onChangeFollowing); | ||||||
| 			this.connection2.on('unfollow', onChangeFollowing); | 			this.connection2.on('unfollow', onChangeFollowing); | ||||||
| 		} else if (this.src == 'local') { | 		} else if (this.src == 'local') { | ||||||
| 			endpoint = 'notes/local-timeline'; | 			endpoint = 'notes/local-timeline'; | ||||||
| 			this.connection = os.stream.useChannel('localTimeline'); | 			this.connection = markRaw(os.stream.useChannel('localTimeline')); | ||||||
| 			this.connection.on('note', prepend); | 			this.connection.on('note', prepend); | ||||||
| 		} else if (this.src == 'social') { | 		} else if (this.src == 'social') { | ||||||
| 			endpoint = 'notes/hybrid-timeline'; | 			endpoint = 'notes/hybrid-timeline'; | ||||||
| 			this.connection = os.stream.useChannel('hybridTimeline'); | 			this.connection = markRaw(os.stream.useChannel('hybridTimeline')); | ||||||
| 			this.connection.on('note', prepend); | 			this.connection.on('note', prepend); | ||||||
| 		} else if (this.src == 'global') { | 		} else if (this.src == 'global') { | ||||||
| 			endpoint = 'notes/global-timeline'; | 			endpoint = 'notes/global-timeline'; | ||||||
| 			this.connection = os.stream.useChannel('globalTimeline'); | 			this.connection = markRaw(os.stream.useChannel('globalTimeline')); | ||||||
| 			this.connection.on('note', prepend); | 			this.connection.on('note', prepend); | ||||||
| 		} else if (this.src == 'mentions') { | 		} else if (this.src == 'mentions') { | ||||||
| 			endpoint = 'notes/mentions'; | 			endpoint = 'notes/mentions'; | ||||||
| 			this.connection = os.stream.useChannel('main'); | 			this.connection = markRaw(os.stream.useChannel('main')); | ||||||
| 			this.connection.on('mention', prepend); | 			this.connection.on('mention', prepend); | ||||||
| 		} else if (this.src == 'directs') { | 		} else if (this.src == 'directs') { | ||||||
| 			endpoint = 'notes/mentions'; | 			endpoint = 'notes/mentions'; | ||||||
| @@ -130,16 +130,16 @@ export default defineComponent({ | |||||||
| 					prepend(note); | 					prepend(note); | ||||||
| 				} | 				} | ||||||
| 			}; | 			}; | ||||||
| 			this.connection = os.stream.useChannel('main'); | 			this.connection = markRaw(os.stream.useChannel('main')); | ||||||
| 			this.connection.on('mention', onNote); | 			this.connection.on('mention', onNote); | ||||||
| 		} else if (this.src == 'list') { | 		} else if (this.src == 'list') { | ||||||
| 			endpoint = 'notes/user-list-timeline'; | 			endpoint = 'notes/user-list-timeline'; | ||||||
| 			this.query = { | 			this.query = { | ||||||
| 				listId: this.list | 				listId: this.list | ||||||
| 			}; | 			}; | ||||||
| 			this.connection = os.stream.useChannel('userList', { | 			this.connection = markRaw(os.stream.useChannel('userList', { | ||||||
| 				listId: this.list | 				listId: this.list | ||||||
| 			}); | 			})); | ||||||
| 			this.connection.on('note', prepend); | 			this.connection.on('note', prepend); | ||||||
| 			this.connection.on('userAdded', onUserAdded); | 			this.connection.on('userAdded', onUserAdded); | ||||||
| 			this.connection.on('userRemoved', onUserRemoved); | 			this.connection.on('userRemoved', onUserRemoved); | ||||||
| @@ -148,9 +148,9 @@ export default defineComponent({ | |||||||
| 			this.query = { | 			this.query = { | ||||||
| 				channelId: this.channel | 				channelId: this.channel | ||||||
| 			}; | 			}; | ||||||
| 			this.connection = os.stream.useChannel('channel', { | 			this.connection = markRaw(os.stream.useChannel('channel', { | ||||||
| 				channelId: this.channel | 				channelId: this.channel | ||||||
| 			}); | 			})); | ||||||
| 			this.connection.on('note', prepend); | 			this.connection.on('note', prepend); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,13 +14,15 @@ | |||||||
| 		<MkInfo warn>{{ information }}</MkInfo> | 		<MkInfo warn>{{ information }}</MkInfo> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div class="_section"> | 	<div class="_section"> | ||||||
| 		<MkInput v-model:value="name">{{ $ts.name }}</MkInput> | 		<MkInput v-model="name"> | ||||||
|  | 			<template #label>{{ $ts.name }}</template> | ||||||
|  | 		</MkInput> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div class="_section"> | 	<div class="_section"> | ||||||
| 		<div style="margin-bottom: 16px;"><b>{{ $ts.permission }}</b></div> | 		<div style="margin-bottom: 16px;"><b>{{ $ts.permission }}</b></div> | ||||||
| 		<MkButton inline @click="disableAll">{{ $ts.disableAll }}</MkButton> | 		<MkButton inline @click="disableAll">{{ $ts.disableAll }}</MkButton> | ||||||
| 		<MkButton inline @click="enableAll">{{ $ts.enableAll }}</MkButton> | 		<MkButton inline @click="enableAll">{{ $ts.enableAll }}</MkButton> | ||||||
| 		<MkSwitch v-for="kind in (initialPermissions || kinds)" :key="kind" v-model:value="permissions[kind]">{{ $t(`_permissions.${kind}`) }}</MkSwitch> | 		<MkSwitch v-for="kind in (initialPermissions || kinds)" :key="kind" v-model="permissions[kind]">{{ $t(`_permissions.${kind}`) }}</MkSwitch> | ||||||
| 	</div> | 	</div> | ||||||
| </XModalWindow> | </XModalWindow> | ||||||
| </template> | </template> | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
| <component class="bghgjjyj _button" | <component class="bghgjjyj _button" | ||||||
| 	:is="link ? 'a' : 'button'" | 	:is="link ? 'MkA' : 'button'" | ||||||
| 	:class="{ inline, primary, danger, full }" | 	:class="{ inline, primary, danger, full }" | ||||||
| 	:type="type" | 	:type="type" | ||||||
| 	@click="$emit('click', $event)" | 	@click="$emit('click', $event)" | ||||||
| @@ -115,6 +115,7 @@ export default defineComponent({ | |||||||
| 	z-index: 1; // 他コンポーネントのbox-shadowに隠されないようにするため | 	z-index: 1; // 他コンポーネントのbox-shadowに隠されないようにするため | ||||||
| 	display: block; | 	display: block; | ||||||
| 	min-width: 100px; | 	min-width: 100px; | ||||||
|  | 	width: max-content; | ||||||
| 	padding: 8px 14px; | 	padding: 8px 14px; | ||||||
| 	text-align: center; | 	text-align: center; | ||||||
| 	font-weight: normal; | 	font-weight: normal; | ||||||
| @@ -125,6 +126,7 @@ export default defineComponent({ | |||||||
| 	background: var(--buttonBg); | 	background: var(--buttonBg); | ||||||
| 	border-radius: 999px; | 	border-radius: 999px; | ||||||
| 	overflow: hidden; | 	overflow: hidden; | ||||||
|  | 	box-sizing: border-box; | ||||||
|  |  | ||||||
| 	&:not(:disabled):hover { | 	&:not(:disabled):hover { | ||||||
| 		background: var(--buttonHoverBg); | 		background: var(--buttonHoverBg); | ||||||
| @@ -140,7 +142,7 @@ export default defineComponent({ | |||||||
|  |  | ||||||
| 	&.primary { | 	&.primary { | ||||||
| 		font-weight: bold; | 		font-weight: bold; | ||||||
| 		color: #fff !important; | 		color: var(--fgOnAccent) !important; | ||||||
| 		background: var(--accent); | 		background: var(--accent); | ||||||
|  |  | ||||||
| 		&:not(:disabled):hover { | 		&:not(:disabled):hover { | ||||||
|   | |||||||
| @@ -99,9 +99,12 @@ export default defineComponent({ | |||||||
| 		z-index: 10; | 		z-index: 10; | ||||||
| 		position: sticky; | 		position: sticky; | ||||||
| 		top: var(--stickyTop, 0px); | 		top: var(--stickyTop, 0px); | ||||||
|  | 		background: var(--panel); | ||||||
|  | 		/* TODO panelの半透明バージョンをプログラマティックに作りたい | ||||||
| 		background: var(--X17); | 		background: var(--X17); | ||||||
| 		-webkit-backdrop-filter: blur(8px); | 		-webkit-backdrop-filter: blur(8px); | ||||||
| 		backdrop-filter: blur(20px); | 		backdrop-filter: blur(20px); | ||||||
|  | 		*/ | ||||||
|  |  | ||||||
| 		> .title { | 		> .title { | ||||||
| 			margin: 0; | 			margin: 0; | ||||||
|   | |||||||
| @@ -1,32 +1,9 @@ | |||||||
| <template> | <template> | ||||||
| <div class="juejbjww" :class="{ focused, filled, inline, disabled }"> | <div class="matxzzsk"> | ||||||
| 	<div class="icon" ref="icon"><slot name="icon"></slot></div> | 	<div class="label" @click="focus"><slot name="label"></slot></div> | ||||||
| 	<div class="input"> | 	<div class="input" :class="{ inline, disabled, focused }"> | ||||||
| 		<span class="label" ref="labelEl"><slot></slot></span> |  | ||||||
| 		<span class="title" ref="title"> |  | ||||||
| 			<slot name="title"></slot> |  | ||||||
| 			<span class="warning" v-if="invalid"><i class="fas fa-exclamation-circle"></i>{{ $refs.input.validationMessage }}</span> |  | ||||||
| 		</span> |  | ||||||
| 		<div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div> | 		<div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div> | ||||||
| 		<input v-if="debounce" ref="inputEl" | 		<input ref="inputEl" | ||||||
| 			v-debounce="500" |  | ||||||
| 			:type="type" |  | ||||||
| 			v-model.lazy="v" |  | ||||||
| 			:disabled="disabled" |  | ||||||
| 			:required="required" |  | ||||||
| 			:readonly="readonly" |  | ||||||
| 			:placeholder="placeholder" |  | ||||||
| 			:pattern="pattern" |  | ||||||
| 			:autocomplete="autocomplete" |  | ||||||
| 			:spellcheck="spellcheck" |  | ||||||
| 			:step="step" |  | ||||||
| 			@focus="focused = true" |  | ||||||
| 			@blur="focused = false" |  | ||||||
| 			@keydown="onKeydown($event)" |  | ||||||
| 			@input="onInput" |  | ||||||
| 			:list="id" |  | ||||||
| 		> |  | ||||||
| 		<input v-else ref="inputEl" |  | ||||||
| 			:type="type" | 			:type="type" | ||||||
| 			v-model="v" | 			v-model="v" | ||||||
| 			:disabled="disabled" | 			:disabled="disabled" | ||||||
| @@ -48,23 +25,25 @@ | |||||||
| 		</datalist> | 		</datalist> | ||||||
| 		<div class="suffix" ref="suffixEl"><slot name="suffix"></slot></div> | 		<div class="suffix" ref="suffixEl"><slot name="suffix"></slot></div> | ||||||
| 	</div> | 	</div> | ||||||
| 	<button class="save _textButton" v-if="save && changed" @click="() => { changed = false; save(); }">{{ $ts.save }}</button> | 	<div class="caption"><slot name="caption"></slot></div> | ||||||
| 	<div class="desc _caption"><slot name="desc"></slot></div> |  | ||||||
|  | 	<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; | import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; | ||||||
| import debounce from 'v-debounce'; | import MkButton from './button.vue'; | ||||||
| import * as os from '@client/os'; | import { debounce } from 'throttle-debounce'; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| 	directives: { | 	components: { | ||||||
| 		debounce | 		MkButton, | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	props: { | 	props: { | ||||||
| 		value: { | 		modelValue: { | ||||||
| 			required: false | 			required: true | ||||||
| 		}, | 		}, | ||||||
| 		type: { | 		type: { | ||||||
| 			type: String, | 			type: String, | ||||||
| @@ -104,9 +83,6 @@ export default defineComponent({ | |||||||
| 		step: { | 		step: { | ||||||
| 			required: false | 			required: false | ||||||
| 		}, | 		}, | ||||||
| 		debounce: { |  | ||||||
| 			required: false |  | ||||||
| 		}, |  | ||||||
| 		datalist: { | 		datalist: { | ||||||
| 			type: Array, | 			type: Array, | ||||||
| 			required: false, | 			required: false, | ||||||
| @@ -116,15 +92,23 @@ export default defineComponent({ | |||||||
| 			required: false, | 			required: false, | ||||||
| 			default: false | 			default: false | ||||||
| 		}, | 		}, | ||||||
| 		save: { | 		debounce: { | ||||||
| 			type: Function, | 			type: Boolean, | ||||||
| 			required: false, | 			required: false, | ||||||
|  | 			default: false | ||||||
|  | 		}, | ||||||
|  | 		manualSave: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false, | ||||||
|  | 			default: false | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	emits: ['change', 'keydown', 'enter'], |  | ||||||
|  | 	emits: ['change', 'keydown', 'enter', 'update:modelValue'], | ||||||
|  |  | ||||||
| 	setup(props, context) { | 	setup(props, context) { | ||||||
| 		const { value, type, autofocus } = toRefs(props); | 		const { modelValue, type, autofocus } = toRefs(props); | ||||||
| 		const v = ref(value.value); | 		const v = ref(modelValue.value); | ||||||
| 		const id = Math.random().toString(); // TODO: uuid? | 		const id = Math.random().toString(); // TODO: uuid? | ||||||
| 		const focused = ref(false); | 		const focused = ref(false); | ||||||
| 		const changed = ref(false); | 		const changed = ref(false); | ||||||
| @@ -133,7 +117,6 @@ export default defineComponent({ | |||||||
| 		const inputEl = ref(null); | 		const inputEl = ref(null); | ||||||
| 		const prefixEl = ref(null); | 		const prefixEl = ref(null); | ||||||
| 		const suffixEl = ref(null); | 		const suffixEl = ref(null); | ||||||
| 		const labelEl = ref(null); |  | ||||||
|  |  | ||||||
| 		const focus = () => inputEl.value.focus(); | 		const focus = () => inputEl.value.focus(); | ||||||
| 		const onInput = (ev) => { | 		const onInput = (ev) => { | ||||||
| @@ -148,15 +131,28 @@ export default defineComponent({ | |||||||
| 			} | 			} | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		watch(value, newValue => { | 		const updated = () => { | ||||||
|  | 			changed.value = false; | ||||||
|  | 			if (type?.value === 'number') { | ||||||
|  | 				context.emit('update:modelValue', parseFloat(v.value)); | ||||||
|  | 			} else { | ||||||
|  | 				context.emit('update:modelValue', v.value); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		const debouncedUpdated = debounce(1000, updated); | ||||||
|  |  | ||||||
|  | 		watch(modelValue, newValue => { | ||||||
| 			v.value = newValue; | 			v.value = newValue; | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		watch(v, newValue => { | 		watch(v, newValue => { | ||||||
| 			if (type?.value === 'number') { | 			if (!props.manualSave) { | ||||||
| 				context.emit('update:value', parseFloat(newValue)); | 				if (props.debounce) { | ||||||
|  | 					debouncedUpdated(); | ||||||
| 				} else { | 				} else { | ||||||
| 				context.emit('update:value', newValue); | 					updated(); | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			invalid.value = inputEl.value.validity.badInput; | 			invalid.value = inputEl.value.validity.badInput; | ||||||
| @@ -172,7 +168,6 @@ export default defineComponent({ | |||||||
| 				// 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する | 				// 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する | ||||||
| 				const clock = setInterval(() => { | 				const clock = setInterval(() => { | ||||||
| 					if (prefixEl.value) { | 					if (prefixEl.value) { | ||||||
| 						labelEl.value.style.left = (prefixEl.value.offsetLeft + prefixEl.value.offsetWidth) + 'px'; |  | ||||||
| 						if (prefixEl.value.offsetWidth) { | 						if (prefixEl.value.offsetWidth) { | ||||||
| 							inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; | 							inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; | ||||||
| 						} | 						} | ||||||
| @@ -200,148 +195,78 @@ export default defineComponent({ | |||||||
| 			inputEl, | 			inputEl, | ||||||
| 			prefixEl, | 			prefixEl, | ||||||
| 			suffixEl, | 			suffixEl, | ||||||
| 			labelEl, |  | ||||||
| 			focus, | 			focus, | ||||||
| 			onInput, | 			onInput, | ||||||
| 			onKeydown, | 			onKeydown, | ||||||
|  | 			updated, | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .juejbjww { | .matxzzsk { | ||||||
| 	position: relative; | 	margin: 1.5em 0; | ||||||
| 	margin: 32px 0; |  | ||||||
|  |  | ||||||
| 	&:not(.inline):first-child { | 	> .label { | ||||||
| 		margin-top: 8px; | 		font-size: 0.85em; | ||||||
|  | 		padding: 0 0 8px 12px; | ||||||
|  | 		user-select: none; | ||||||
|  |  | ||||||
|  | 		&:empty { | ||||||
|  | 			display: none; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	&:not(.inline):last-child { | 	> .caption { | ||||||
| 		margin-bottom: 8px; | 		font-size: 0.8em; | ||||||
| 	} | 		padding: 8px 0 0 12px; | ||||||
|  | 		color: var(--fgTransparentWeak); | ||||||
|  |  | ||||||
| 	> .icon { | 		&:empty { | ||||||
| 		position: absolute; | 			display: none; | ||||||
| 		top: 0; |  | ||||||
| 		left: 0; |  | ||||||
| 		width: 24px; |  | ||||||
| 		text-align: center; |  | ||||||
| 		line-height: 32px; |  | ||||||
|  |  | ||||||
| 		&:not(:empty) + .input { |  | ||||||
| 			margin-left: 28px; |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	> .input { | 	> .input { | ||||||
|  | 		$height: 42px; | ||||||
| 		position: relative; | 		position: relative; | ||||||
|  |  | ||||||
| 		&:before { |  | ||||||
| 			content: ''; |  | ||||||
| 			display: block; |  | ||||||
| 			position: absolute; |  | ||||||
| 			bottom: 0; |  | ||||||
| 			left: 0; |  | ||||||
| 			right: 0; |  | ||||||
| 			height: 1px; |  | ||||||
| 			background: var(--inputBorder); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&:after { |  | ||||||
| 			content: ''; |  | ||||||
| 			display: block; |  | ||||||
| 			position: absolute; |  | ||||||
| 			bottom: 0; |  | ||||||
| 			left: 0; |  | ||||||
| 			right: 0; |  | ||||||
| 			height: 2px; |  | ||||||
| 			background: var(--accent); |  | ||||||
| 			opacity: 0; |  | ||||||
| 			transform: scaleX(0.12); |  | ||||||
| 			transition: border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); |  | ||||||
| 			will-change: border opacity transform; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> .label { |  | ||||||
| 			position: absolute; |  | ||||||
| 			z-index: 1; |  | ||||||
| 			top: 0; |  | ||||||
| 			left: 0; |  | ||||||
| 			pointer-events: none; |  | ||||||
| 			transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); |  | ||||||
| 			transition-duration: 0.3s; |  | ||||||
| 			font-size: 1em; |  | ||||||
| 			line-height: 32px; |  | ||||||
| 			color: var(--inputLabel); |  | ||||||
| 			pointer-events: none; |  | ||||||
| 			//will-change transform |  | ||||||
| 			transform-origin: top left; |  | ||||||
| 			transform: scale(1); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> .title { |  | ||||||
| 			position: absolute; |  | ||||||
| 			z-index: 1; |  | ||||||
| 			top: -17px; |  | ||||||
| 			left: 0 !important; |  | ||||||
| 			pointer-events: none; |  | ||||||
| 			font-size: 1em; |  | ||||||
| 			line-height: 32px; |  | ||||||
| 			color: var(--inputLabel); |  | ||||||
| 			pointer-events: none; |  | ||||||
| 			//will-change transform |  | ||||||
| 			transform-origin: top left; |  | ||||||
| 			transform: scale(.75); |  | ||||||
| 			white-space: nowrap; |  | ||||||
| 			width: 133%; |  | ||||||
| 			overflow: hidden; |  | ||||||
| 			text-overflow: ellipsis; |  | ||||||
|  |  | ||||||
| 			> .warning { |  | ||||||
| 				margin-left: 0.5em; |  | ||||||
| 				color: var(--infoWarnFg); |  | ||||||
|  |  | ||||||
| 				> svg { |  | ||||||
| 					margin-right: 0.1em; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> input { | 		> input { | ||||||
| 			$height: 32px; | 			appearance: none; | ||||||
|  | 			-webkit-appearance: none; | ||||||
| 			display: block; | 			display: block; | ||||||
| 			height: $height; | 			height: $height; | ||||||
| 			width: 100%; | 			width: 100%; | ||||||
| 			margin: 0; | 			margin: 0; | ||||||
| 			padding: 0; | 			padding: 0 12px; | ||||||
| 			font: inherit; | 			font: inherit; | ||||||
| 			font-weight: normal; | 			font-weight: normal; | ||||||
| 			font-size: 1em; | 			font-size: 1em; | ||||||
| 			line-height: $height; | 			color: var(--fg); | ||||||
| 			color: var(--inputText); | 			background: var(--panel); | ||||||
| 			background: transparent; | 			border: solid 1px var(--inputBorder); | ||||||
| 			border: none; | 			border-radius: 6px; | ||||||
| 			border-radius: 0; |  | ||||||
| 			outline: none; | 			outline: none; | ||||||
| 			box-shadow: none; | 			box-shadow: none; | ||||||
| 			box-sizing: border-box; | 			box-sizing: border-box; | ||||||
|  | 			transition: border-color 0.1s ease-out; | ||||||
|  |  | ||||||
| 			&[type='file'] { | 			&:hover { | ||||||
| 				display: none; | 				border-color: var(--inputBorderHover); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		> .prefix, | 		> .prefix, | ||||||
| 		> .suffix { | 		> .suffix { | ||||||
| 			display: block; | 			display: flex; | ||||||
|  | 			align-items: center; | ||||||
| 			position: absolute; | 			position: absolute; | ||||||
| 			z-index: 1; | 			z-index: 1; | ||||||
| 			top: 0; | 			top: 0; | ||||||
|  | 			padding: 0 12px; | ||||||
| 			font-size: 1em; | 			font-size: 1em; | ||||||
| 			line-height: 32px; | 			height: $height; | ||||||
| 			color: var(--inputLabel); |  | ||||||
| 			pointer-events: none; | 			pointer-events: none; | ||||||
|  |  | ||||||
| 			&:empty { | 			&:empty { | ||||||
| @@ -360,54 +285,12 @@ export default defineComponent({ | |||||||
|  |  | ||||||
| 		> .prefix { | 		> .prefix { | ||||||
| 			left: 0; | 			left: 0; | ||||||
| 			padding-right: 4px; | 			padding-right: 6px; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		> .suffix { | 		> .suffix { | ||||||
| 			right: 0; | 			right: 0; | ||||||
| 			padding-left: 4px; | 			padding-left: 6px; | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .save { |  | ||||||
| 		margin: 6px 0 0 0; |  | ||||||
| 		font-size: 0.8em; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .desc { |  | ||||||
| 		margin: 6px 0 0 0; |  | ||||||
|  |  | ||||||
| 		&:empty { |  | ||||||
| 			display: none; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		* { |  | ||||||
| 			margin: 0; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	&.focused { |  | ||||||
| 		> .input { |  | ||||||
| 			&:after { |  | ||||||
| 				opacity: 1; |  | ||||||
| 				transform: scaleX(1); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			> .label { |  | ||||||
| 				color: var(--accent); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	&.focused, |  | ||||||
| 	&.filled { |  | ||||||
| 		> .input { |  | ||||||
| 			> .label { |  | ||||||
| 				top: -17px; |  | ||||||
| 				left: 0 !important; |  | ||||||
| 				transform: scale(0.75); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		&.inline { | 		&.inline { | ||||||
| @@ -415,6 +298,13 @@ export default defineComponent({ | |||||||
| 			margin: 0; | 			margin: 0; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		&.focused { | ||||||
|  | 			> input { | ||||||
|  | 				border-color: var(--accent); | ||||||
|  | 				//box-shadow: 0 0 0 4px var(--focus); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		&.disabled { | 		&.disabled { | ||||||
| 			opacity: 0.7; | 			opacity: 0.7; | ||||||
|  |  | ||||||
| @@ -422,5 +312,6 @@ export default defineComponent({ | |||||||
| 				cursor: not-allowed !important; | 				cursor: not-allowed !important; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -171,13 +171,13 @@ export default defineComponent({ | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		&:hover { | 		&:hover { | ||||||
| 			color: #fff; | 			color: var(--fgOnAccent); | ||||||
| 			background: var(--accent); | 			background: var(--accent); | ||||||
| 			text-decoration: none; | 			text-decoration: none; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		&:active { | 		&:active { | ||||||
| 			color: #fff; | 			color: var(--fgOnAccent); | ||||||
| 			background: var(--accentDarken); | 			background: var(--accentDarken); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,19 +1,20 @@ | |||||||
| <template> | <template> | ||||||
| <MkModal ref="modal" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')"> | <MkPopup ref="popup" :src="src" @closed="$emit('closed')"> | ||||||
| 	<MkMenu :items="items" :align="align" @close="$refs.modal.close()" class="_popup"/> | 	<MkMenu :items="items" :align="align" @close="$refs.popup.close()" class="_popup _shadow"/> | ||||||
| </MkModal> | </MkPopup> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent } from 'vue'; | import { defineComponent } from 'vue'; | ||||||
| import MkModal from './modal.vue'; | import MkPopup from './popup.vue'; | ||||||
| import MkMenu from './menu.vue'; | import MkMenu from './menu.vue'; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| 	components: { | 	components: { | ||||||
| 		MkModal, | 		MkPopup, | ||||||
| 		MkMenu, | 		MkMenu, | ||||||
| 	}, | 	}, | ||||||
|  | 
 | ||||||
| 	props: { | 	props: { | ||||||
| 		items: { | 		items: { | ||||||
| 			type: Array, | 			type: Array, | ||||||
| @@ -31,17 +32,7 @@ export default defineComponent({ | |||||||
| 			required: false | 			required: false | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	emits: ['closed'], | 
 | ||||||
| 	computed: { | 	emits: ['close', 'closed'], | ||||||
| 		keymap(): any { |  | ||||||
| 			return { |  | ||||||
| 				'esc': () => this.$refs.modal.close(), |  | ||||||
| 			}; |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
| 
 |  | ||||||
| <style lang="scss" scoped> |  | ||||||
| 
 |  | ||||||
| </style> |  | ||||||
							
								
								
									
										213
									
								
								src/client/components/ui/popup.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								src/client/components/ui/popup.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,213 @@ | |||||||
|  | <template> | ||||||
|  | <transition :name="$store.state.animation ? 'popup-menu' : ''" :duration="$store.state.animation ? 300 : 0" appear @after-leave="onClosed" @enter="$emit('opening')" @after-enter="childRendered"> | ||||||
|  | 	<div v-show="manualShowing != null ? manualShowing : showing" class="ccczpooj" :class="{ front, fixed, top: position === 'top' }" ref="content" :style="{ pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }"> | ||||||
|  | 		<slot></slot> | ||||||
|  | 	</div> | ||||||
|  | </transition> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import { defineComponent, PropType } from 'vue'; | ||||||
|  |  | ||||||
|  | function getFixedContainer(el: Element | null): Element | null { | ||||||
|  | 	if (el == null || el.tagName === 'BODY') return null; | ||||||
|  | 	const position = window.getComputedStyle(el).getPropertyValue('position'); | ||||||
|  | 	if (position === 'fixed') { | ||||||
|  | 		return el; | ||||||
|  | 	} else { | ||||||
|  | 		return getFixedContainer(el.parentElement); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default defineComponent({ | ||||||
|  | 	props: { | ||||||
|  | 		manualShowing: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false, | ||||||
|  | 			default: null, | ||||||
|  | 		}, | ||||||
|  | 		srcCenter: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false | ||||||
|  | 		}, | ||||||
|  | 		src: { | ||||||
|  | 			type: Object as PropType<HTMLElement>, | ||||||
|  | 			required: false, | ||||||
|  | 		}, | ||||||
|  | 		position: { | ||||||
|  | 			required: false | ||||||
|  | 		}, | ||||||
|  | 		front: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false, | ||||||
|  | 			default: false, | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	emits: ['opening', 'click', 'esc', 'close', 'closed'], | ||||||
|  |  | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			showing: true, | ||||||
|  | 			fixed: false, | ||||||
|  | 			transformOrigin: 'center', | ||||||
|  | 			contentClicking: false, | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	mounted() { | ||||||
|  | 		this.$watch('src', () => { | ||||||
|  | 			if (this.src) { | ||||||
|  | 				this.src.style.pointerEvents = 'none'; | ||||||
|  | 			} | ||||||
|  | 			this.fixed = getFixedContainer(this.src) != null; | ||||||
|  | 			this.$nextTick(() => { | ||||||
|  | 				this.align(); | ||||||
|  | 			}); | ||||||
|  | 		}, { immediate: true }); | ||||||
|  |  | ||||||
|  | 		this.$nextTick(() => { | ||||||
|  | 			const popover = this.$refs.content as any; | ||||||
|  | 			new ResizeObserver((entries, observer) => { | ||||||
|  | 				this.align(); | ||||||
|  | 			}).observe(popover); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		document.addEventListener('mousedown', this.onDocumentClick, { passive: true }); | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	beforeUnmount() { | ||||||
|  | 		document.removeEventListener('mousedown', this.onDocumentClick); | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	methods: { | ||||||
|  | 		align() { | ||||||
|  | 			if (this.src == null) return; | ||||||
|  |  | ||||||
|  | 			const popover = this.$refs.content as any; | ||||||
|  |  | ||||||
|  | 			if (popover == null) return; | ||||||
|  |  | ||||||
|  | 			const rect = this.src.getBoundingClientRect(); | ||||||
|  | 			 | ||||||
|  | 			const width = popover.offsetWidth; | ||||||
|  | 			const height = popover.offsetHeight; | ||||||
|  |  | ||||||
|  | 			let left; | ||||||
|  | 			let top; | ||||||
|  |  | ||||||
|  | 			if (this.srcCenter) { | ||||||
|  | 				const x = rect.left + (this.fixed ? 0 : window.pageXOffset) + (this.src.offsetWidth / 2); | ||||||
|  | 				const y = rect.top + (this.fixed ? 0 : window.pageYOffset) + (this.src.offsetHeight / 2); | ||||||
|  | 				left = (x - (width / 2)); | ||||||
|  | 				top = (y - (height / 2)); | ||||||
|  | 			} else { | ||||||
|  | 				const x = rect.left + (this.fixed ? 0 : window.pageXOffset) + (this.src.offsetWidth / 2); | ||||||
|  | 				const y = rect.top + (this.fixed ? 0 : window.pageYOffset) + this.src.offsetHeight; | ||||||
|  | 				left = (x - (width / 2)); | ||||||
|  | 				top = y; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (this.fixed) { | ||||||
|  | 				if (left + width > window.innerWidth) { | ||||||
|  | 					left = window.innerWidth - width; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if (top + height > window.innerHeight) { | ||||||
|  | 					top = window.innerHeight - height; | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				if (left + width - window.pageXOffset > window.innerWidth) { | ||||||
|  | 					left = window.innerWidth - width + window.pageXOffset - 1; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if (top + height - window.pageYOffset > window.innerHeight) { | ||||||
|  | 					top = window.innerHeight - height + window.pageYOffset - 1; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (top < 0) { | ||||||
|  | 				top = 0; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (left < 0) { | ||||||
|  | 				left = 0; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (top > rect.top + (this.fixed ? 0 : window.pageYOffset)) { | ||||||
|  | 				this.transformOrigin = 'center top'; | ||||||
|  | 			} else { | ||||||
|  | 				this.transformOrigin = 'center'; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			popover.style.left = left + 'px'; | ||||||
|  | 			popover.style.top = top + 'px'; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		childRendered() { | ||||||
|  | 			// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する | ||||||
|  | 			const content = this.$refs.content.children[0]; | ||||||
|  | 			content.addEventListener('mousedown', e => { | ||||||
|  | 				this.contentClicking = true; | ||||||
|  | 				window.addEventListener('mouseup', e => { | ||||||
|  | 					// click イベントより先に mouseup イベントが発生するかもしれないのでちょっと待つ | ||||||
|  | 					setTimeout(() => { | ||||||
|  | 						this.contentClicking = false; | ||||||
|  | 					}, 100); | ||||||
|  | 				}, { passive: true, once: true }); | ||||||
|  | 			}, { passive: true }); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		close() { | ||||||
|  | 			if (this.src) this.src.style.pointerEvents = 'auto'; | ||||||
|  | 			this.showing = false; | ||||||
|  | 			this.$emit('close'); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		onClosed() { | ||||||
|  | 			this.$emit('closed'); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		onDocumentClick(ev) { | ||||||
|  | 			const flyoutElement = this.$refs.content; | ||||||
|  | 			let targetElement = ev.target; | ||||||
|  | 			do { | ||||||
|  | 				if (targetElement === flyoutElement) { | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 				targetElement = targetElement.parentNode; | ||||||
|  | 			} while (targetElement); | ||||||
|  | 			this.close(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .popup-menu-enter-active { | ||||||
|  | 	transform-origin: var(--transformOrigin); | ||||||
|  | 	transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1), transform 0.2s cubic-bezier(0, 0, 0.2, 1) !important; | ||||||
|  | } | ||||||
|  | .popup-menu-leave-active { | ||||||
|  | 	transform-origin: var(--transformOrigin); | ||||||
|  | 	transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1), transform 0.2s cubic-bezier(0.4, 0, 1, 1) !important; | ||||||
|  | } | ||||||
|  | .popup-menu-enter-from, .popup-menu-leave-to { | ||||||
|  | 	pointer-events: none; | ||||||
|  | 	opacity: 0; | ||||||
|  | 	transform: scale(0.9); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ccczpooj { | ||||||
|  | 	position: absolute; | ||||||
|  | 	z-index: 10000; | ||||||
|  |  | ||||||
|  | 	&.fixed { | ||||||
|  | 		position: fixed; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	&.front { | ||||||
|  | 		z-index: 20000; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @@ -1,185 +1,218 @@ | |||||||
| <template> | <template> | ||||||
| <div class="eiipwacr" :class="{ focused, disabled, filled, inline }"> | <div class="vblkjoeq"> | ||||||
| 	<div class="icon" ref="icon"><slot name="icon"></slot></div> | 	<div class="label" @click="focus"><slot name="label"></slot></div> | ||||||
| 	<div class="input" @click="focus"> | 	<div class="input" :class="{ inline, disabled, focused }"> | ||||||
| 		<span class="label" ref="label"><slot name="label"></slot></span> | 		<div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div> | ||||||
| 		<div class="prefix" ref="prefix"><slot name="prefix"></slot></div> | 		<select ref="inputEl" | ||||||
| 		<select ref="input" |  | ||||||
| 			v-model="v" | 			v-model="v" | ||||||
| 			:required="required" |  | ||||||
| 			:disabled="disabled" | 			:disabled="disabled" | ||||||
|  | 			:required="required" | ||||||
|  | 			:readonly="readonly" | ||||||
|  | 			:placeholder="placeholder" | ||||||
| 			@focus="focused = true" | 			@focus="focused = true" | ||||||
| 			@blur="focused = false" | 			@blur="focused = false" | ||||||
|  | 			@input="onInput" | ||||||
| 		> | 		> | ||||||
| 			<slot></slot> | 			<slot></slot> | ||||||
| 		</select> | 		</select> | ||||||
| 		<div class="suffix"> | 		<div class="suffix" ref="suffixEl"><i class="fas fa-chevron-down"></i></div> | ||||||
| 			<slot name="suffix"> |  | ||||||
| 				<i class="fas fa-chevron-down"></i> |  | ||||||
| 			</slot> |  | ||||||
| 	</div> | 	</div> | ||||||
| 	</div> | 	<div class="caption"><slot name="caption"></slot></div> | ||||||
| 	<div class="text"><slot name="text"></slot></div> |  | ||||||
|  | 	<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent } from 'vue'; | import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; | ||||||
|  | import MkButton from './button.vue'; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|  | 	components: { | ||||||
|  | 		MkButton, | ||||||
|  | 	}, | ||||||
|  |  | ||||||
| 	props: { | 	props: { | ||||||
| 		value: { | 		modelValue: { | ||||||
| 			required: false | 			required: true | ||||||
| 		}, | 		}, | ||||||
| 		required: { | 		required: { | ||||||
| 			type: Boolean, | 			type: Boolean, | ||||||
| 			required: false | 			required: false | ||||||
| 		}, | 		}, | ||||||
|  | 		readonly: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false | ||||||
|  | 		}, | ||||||
| 		disabled: { | 		disabled: { | ||||||
| 			type: Boolean, | 			type: Boolean, | ||||||
| 			required: false | 			required: false | ||||||
| 		}, | 		}, | ||||||
|  | 		placeholder: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: false | ||||||
|  | 		}, | ||||||
|  | 		autofocus: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false, | ||||||
|  | 			default: false | ||||||
|  | 		}, | ||||||
| 		inline: { | 		inline: { | ||||||
| 			type: Boolean, | 			type: Boolean, | ||||||
| 			required: false, | 			required: false, | ||||||
| 			default: false | 			default: false | ||||||
| 		}, | 		}, | ||||||
|  | 		manualSave: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false, | ||||||
|  | 			default: false | ||||||
| 		}, | 		}, | ||||||
| 	data() { | 	}, | ||||||
|  |  | ||||||
|  | 	emits: ['change', 'update:modelValue'], | ||||||
|  |  | ||||||
|  | 	setup(props, context) { | ||||||
|  | 		const { modelValue, autofocus } = toRefs(props); | ||||||
|  | 		const v = ref(modelValue.value); | ||||||
|  | 		const focused = ref(false); | ||||||
|  | 		const changed = ref(false); | ||||||
|  | 		const invalid = ref(false); | ||||||
|  | 		const filled = computed(() => v.value !== '' && v.value != null); | ||||||
|  | 		const inputEl = ref(null); | ||||||
|  | 		const prefixEl = ref(null); | ||||||
|  | 		const suffixEl = ref(null); | ||||||
|  |  | ||||||
|  | 		const focus = () => inputEl.value.focus(); | ||||||
|  | 		const onInput = (ev) => { | ||||||
|  | 			changed.value = true; | ||||||
|  | 			context.emit('change', ev); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		const updated = () => { | ||||||
|  | 			changed.value = false; | ||||||
|  | 			context.emit('update:modelValue', v.value); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		watch(modelValue, newValue => { | ||||||
|  | 			v.value = newValue; | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		watch(v, newValue => { | ||||||
|  | 			if (!props.manualSave) { | ||||||
|  | 				updated(); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			invalid.value = inputEl.value.validity.badInput; | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		onMounted(() => { | ||||||
|  | 			nextTick(() => { | ||||||
|  | 				if (autofocus.value) { | ||||||
|  | 					focus(); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// このコンポーネントが作成された時、非表示状態である場合がある | ||||||
|  | 				// 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する | ||||||
|  | 				const clock = setInterval(() => { | ||||||
|  | 					if (prefixEl.value) { | ||||||
|  | 						if (prefixEl.value.offsetWidth) { | ||||||
|  | 							inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 					if (suffixEl.value) { | ||||||
|  | 						if (suffixEl.value.offsetWidth) { | ||||||
|  | 							inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px'; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				}, 100); | ||||||
|  |  | ||||||
|  | 				onUnmounted(() => { | ||||||
|  | 					clearInterval(clock); | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		return { | 		return { | ||||||
| 			focused: false, | 			v, | ||||||
|  | 			focused, | ||||||
|  | 			invalid, | ||||||
|  | 			changed, | ||||||
|  | 			filled, | ||||||
|  | 			inputEl, | ||||||
|  | 			prefixEl, | ||||||
|  | 			suffixEl, | ||||||
|  | 			focus, | ||||||
|  | 			onInput, | ||||||
|  | 			updated, | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 	computed: { |  | ||||||
| 		v: { |  | ||||||
| 			get() { |  | ||||||
| 				return this.value; |  | ||||||
| 			}, |  | ||||||
| 			set(v) { |  | ||||||
| 				this.$emit('update:value', v); |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 		filled(): boolean { |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 	mounted() { |  | ||||||
| 		if (this.$refs.prefix) { |  | ||||||
| 			this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px'; |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 	methods: { |  | ||||||
| 		focus() { |  | ||||||
| 			this.$refs.input.focus(); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .eiipwacr { | .vblkjoeq { | ||||||
| 	position: relative; | 	margin: 1.5em 0; | ||||||
| 	margin: 32px 0; |  | ||||||
|  |  | ||||||
| 	&:not(.inline):first-child { | 	> .label { | ||||||
| 		margin-top: 8px; | 		font-size: 0.85em; | ||||||
|  | 		padding: 0 0 8px 12px; | ||||||
|  | 		user-select: none; | ||||||
|  |  | ||||||
|  | 		&:empty { | ||||||
|  | 			display: none; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	&:not(.inline):last-child { | 	> .caption { | ||||||
| 		margin-bottom: 8px; | 		font-size: 0.8em; | ||||||
| 	} | 		padding: 8px 0 0 12px; | ||||||
|  | 		color: var(--fgTransparentWeak); | ||||||
|  |  | ||||||
| 	> .icon { | 		&:empty { | ||||||
| 		position: absolute; | 			display: none; | ||||||
| 		top: 0; |  | ||||||
| 		left: 0; |  | ||||||
| 		width: 24px; |  | ||||||
| 		text-align: center; |  | ||||||
| 		line-height: 32px; |  | ||||||
|  |  | ||||||
| 		&:not(:empty) + .input { |  | ||||||
| 			margin-left: 28px; |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	> .input { | 	> .input { | ||||||
| 		display: flex; | 		$height: 42px; | ||||||
| 		position: relative; | 		position: relative; | ||||||
|  |  | ||||||
| 		&:before { |  | ||||||
| 			content: ''; |  | ||||||
| 			display: block; |  | ||||||
| 			position: absolute; |  | ||||||
| 			bottom: 0; |  | ||||||
| 			left: 0; |  | ||||||
| 			right: 0; |  | ||||||
| 			height: 1px; |  | ||||||
| 			background: var(--inputBorder); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&:after { |  | ||||||
| 			content: ''; |  | ||||||
| 			display: block; |  | ||||||
| 			position: absolute; |  | ||||||
| 			bottom: 0; |  | ||||||
| 			left: 0; |  | ||||||
| 			right: 0; |  | ||||||
| 			height: 2px; |  | ||||||
| 			background: var(--accent); |  | ||||||
| 			opacity: 0; |  | ||||||
| 			transform: scaleX(0.12); |  | ||||||
| 			transition: border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); |  | ||||||
| 			will-change: border opacity transform; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> .label { |  | ||||||
| 			position: absolute; |  | ||||||
| 			top: 0; |  | ||||||
| 			left: 0; |  | ||||||
| 			pointer-events: none; |  | ||||||
| 			transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); |  | ||||||
| 			transition-duration: 0.3s; |  | ||||||
| 			font-size: 1em; |  | ||||||
| 			line-height: 32px; |  | ||||||
| 			pointer-events: none; |  | ||||||
| 			//will-change transform |  | ||||||
| 			transform-origin: top left; |  | ||||||
| 			transform: scale(1); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> select { | 		> select { | ||||||
|  | 			appearance: none; | ||||||
|  | 			-webkit-appearance: none; | ||||||
| 			display: block; | 			display: block; | ||||||
| 			flex: 1; | 			height: $height; | ||||||
| 			width: 100%; | 			width: 100%; | ||||||
| 			padding: 0; | 			margin: 0; | ||||||
|  | 			padding: 0 12px; | ||||||
| 			font: inherit; | 			font: inherit; | ||||||
| 			font-weight: normal; | 			font-weight: normal; | ||||||
| 			font-size: 1em; | 			font-size: 1em; | ||||||
| 			height: 32px; | 			color: var(--fg); | ||||||
| 			background: none; | 			background: var(--panel); | ||||||
| 			border: none; | 			border: solid 1px var(--inputBorder); | ||||||
| 			border-radius: 0; | 			border-radius: 6px; | ||||||
| 			outline: none; | 			outline: none; | ||||||
| 			box-shadow: none; | 			box-shadow: none; | ||||||
| 			appearance: none; | 			box-sizing: border-box; | ||||||
| 			-webkit-appearance: none; | 			cursor: pointer; | ||||||
| 			color: var(--fg); | 			transition: border-color 0.1s ease-out; | ||||||
|  |  | ||||||
| 			option, | 			&:hover { | ||||||
| 			optgroup { | 				border-color: var(--inputBorderHover); | ||||||
| 				color: var(--fg); |  | ||||||
| 				background: var(--bg); |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		> .prefix, | 		> .prefix, | ||||||
| 		> .suffix { | 		> .suffix { | ||||||
| 			display: block; | 			display: flex; | ||||||
| 			align-self: center; | 			align-items: center; | ||||||
| 			justify-self: center; | 			position: absolute; | ||||||
|  | 			z-index: 1; | ||||||
|  | 			top: 0; | ||||||
|  | 			padding: 0 12px; | ||||||
| 			font-size: 1em; | 			font-size: 1em; | ||||||
| 			line-height: 32px; | 			height: $height; | ||||||
| 			color: var(--inputLabel); |  | ||||||
| 			pointer-events: none; | 			pointer-events: none; | ||||||
|  |  | ||||||
| 			&:empty { | 			&:empty { | ||||||
| @@ -187,53 +220,41 @@ export default defineComponent({ | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			> * { | 			> * { | ||||||
| 				display: block; | 				display: inline-block; | ||||||
| 				min-width: 16px; | 				min-width: 16px; | ||||||
|  | 				max-width: 150px; | ||||||
|  | 				overflow: hidden; | ||||||
|  | 				white-space: nowrap; | ||||||
|  | 				text-overflow: ellipsis; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		> .prefix { | 		> .prefix { | ||||||
| 			padding-right: 4px; | 			left: 0; | ||||||
|  | 			padding-right: 6px; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		> .suffix { | 		> .suffix { | ||||||
| 			padding-left: 4px; | 			right: 0; | ||||||
| 		} | 			padding-left: 6px; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	> .text { | 		&.inline { | ||||||
| 		margin: 6px 0; | 			display: inline-block; | ||||||
| 		font-size: 0.8em; |  | ||||||
|  |  | ||||||
| 		&:empty { |  | ||||||
| 			display: none; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		* { |  | ||||||
| 			margin: 0; | 			margin: 0; | ||||||
| 		} | 		} | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 		&.focused { | 		&.focused { | ||||||
| 		> .input { | 			> select { | ||||||
| 			&:after { | 				border-color: var(--accent); | ||||||
| 				opacity: 1; |  | ||||||
| 				transform: scaleX(1); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			> .label { |  | ||||||
| 				color: var(--accent); |  | ||||||
| 			} |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	&.focused, | 		&.disabled { | ||||||
| 	&.filled { | 			opacity: 0.7; | ||||||
| 		> .input { |  | ||||||
| 			> .label { | 			&, * { | ||||||
| 				top: -17px; | 				cursor: not-allowed !important; | ||||||
| 				left: 0 !important; |  | ||||||
| 				transform: scale(0.75); |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ | |||||||
| 	</span> | 	</span> | ||||||
| 	<span class="label"> | 	<span class="label"> | ||||||
| 		<span><slot></slot></span> | 		<span><slot></slot></span> | ||||||
| 		<p><slot name="desc"></slot></p> | 		<p><slot name="caption"></slot></p> | ||||||
| 	</span> | 	</span> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
| @@ -28,7 +28,7 @@ import { defineComponent } from 'vue'; | |||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| 	props: { | 	props: { | ||||||
| 		value: { | 		modelValue: { | ||||||
| 			type: Boolean, | 			type: Boolean, | ||||||
| 			default: false | 			default: false | ||||||
| 		}, | 		}, | ||||||
| @@ -39,13 +39,13 @@ export default defineComponent({ | |||||||
| 	}, | 	}, | ||||||
| 	computed: { | 	computed: { | ||||||
| 		checked(): boolean { | 		checked(): boolean { | ||||||
| 			return this.value; | 			return this.modelValue; | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 	methods: { | 	methods: { | ||||||
| 		toggle() { | 		toggle() { | ||||||
| 			if (this.disabled) return; | 			if (this.disabled) return; | ||||||
| 			this.$emit('update:value', !this.checked); | 			this.$emit('update:modelValue', !this.checked); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| @@ -136,7 +136,7 @@ export default defineComponent({ | |||||||
|  |  | ||||||
| 		> p { | 		> p { | ||||||
| 			margin: 0; | 			margin: 0; | ||||||
| 			opacity: 0.7; | 			color: var(--fgTransparentWeak); | ||||||
| 			font-size: 90%; | 			font-size: 90%; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,30 +1,45 @@ | |||||||
| <template> | <template> | ||||||
| <div class="adhpbeos" :class="{ focused, filled, tall, pre }"> | <div class="adhpbeos"> | ||||||
| 	<div class="input"> | 	<div class="label" @click="focus"><slot name="label"></slot></div> | ||||||
| 		<span class="label" ref="label"><slot></slot></span> | 	<div class="input" :class="{ disabled, focused, tall, pre }"> | ||||||
| 		<textarea ref="input" :class="{ code, _monospace: code }" | 		<textarea ref="inputEl" | ||||||
| 			:value="value" | 			:class="{ code, _monospace: code }" | ||||||
|  | 			v-model="v" | ||||||
|  | 			:disabled="disabled" | ||||||
| 			:required="required" | 			:required="required" | ||||||
| 			:readonly="readonly" | 			:readonly="readonly" | ||||||
|  | 			:placeholder="placeholder" | ||||||
| 			:pattern="pattern" | 			:pattern="pattern" | ||||||
| 			:autocomplete="autocomplete" | 			:autocomplete="autocomplete" | ||||||
| 			:spellcheck="!code" | 			:spellcheck="spellcheck" | ||||||
| 			@input="onInput" |  | ||||||
| 			@focus="focused = true" | 			@focus="focused = true" | ||||||
| 			@blur="focused = false" | 			@blur="focused = false" | ||||||
|  | 			@keydown="onKeydown($event)" | ||||||
|  | 			@input="onInput" | ||||||
| 		></textarea> | 		></textarea> | ||||||
| 	</div> | 	</div> | ||||||
| 	<button class="save _textButton" v-if="save && changed" @click="() => { changed = false; save(); }">{{ $ts.save }}</button> | 	<div class="caption"><slot name="caption"></slot></div> | ||||||
| 	<div class="desc _caption"><slot name="desc"></slot></div> |  | ||||||
|  | 	<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent } from 'vue'; | import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; | ||||||
|  | import MkButton from './button.vue'; | ||||||
|  | import { debounce } from 'throttle-debounce'; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|  | 	components: { | ||||||
|  | 		MkButton, | ||||||
|  | 	}, | ||||||
|  |  | ||||||
| 	props: { | 	props: { | ||||||
| 		value: { | 		modelValue: { | ||||||
|  | 			required: true | ||||||
|  | 		}, | ||||||
|  | 		type: { | ||||||
|  | 			type: String, | ||||||
| 			required: false | 			required: false | ||||||
| 		}, | 		}, | ||||||
| 		required: { | 		required: { | ||||||
| @@ -35,14 +50,29 @@ export default defineComponent({ | |||||||
| 			type: Boolean, | 			type: Boolean, | ||||||
| 			required: false | 			required: false | ||||||
| 		}, | 		}, | ||||||
|  | 		disabled: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false | ||||||
|  | 		}, | ||||||
| 		pattern: { | 		pattern: { | ||||||
| 			type: String, | 			type: String, | ||||||
| 			required: false | 			required: false | ||||||
| 		}, | 		}, | ||||||
| 		autocomplete: { | 		placeholder: { | ||||||
| 			type: String, | 			type: String, | ||||||
| 			required: false | 			required: false | ||||||
| 		}, | 		}, | ||||||
|  | 		autofocus: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false, | ||||||
|  | 			default: false | ||||||
|  | 		}, | ||||||
|  | 		autocomplete: { | ||||||
|  | 			required: false | ||||||
|  | 		}, | ||||||
|  | 		spellcheck: { | ||||||
|  | 			required: false | ||||||
|  | 		}, | ||||||
| 		code: { | 		code: { | ||||||
| 			type: Boolean, | 			type: Boolean, | ||||||
| 			required: false | 			required: false | ||||||
| @@ -57,169 +87,164 @@ export default defineComponent({ | |||||||
| 			required: false, | 			required: false, | ||||||
| 			default: false | 			default: false | ||||||
| 		}, | 		}, | ||||||
| 		save: { | 		debounce: { | ||||||
| 			type: Function, | 			type: Boolean, | ||||||
| 			required: false, | 			required: false, | ||||||
|  | 			default: false | ||||||
|  | 		}, | ||||||
|  | 		manualSave: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false, | ||||||
|  | 			default: false | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	data() { |  | ||||||
|  | 	emits: ['change', 'keydown', 'enter', 'update:modelValue'], | ||||||
|  |  | ||||||
|  | 	setup(props, context) { | ||||||
|  | 		const { modelValue, autofocus } = toRefs(props); | ||||||
|  | 		const v = ref(modelValue.value); | ||||||
|  | 		const focused = ref(false); | ||||||
|  | 		const changed = ref(false); | ||||||
|  | 		const invalid = ref(false); | ||||||
|  | 		const filled = computed(() => v.value !== '' && v.value != null); | ||||||
|  | 		const inputEl = ref(null); | ||||||
|  |  | ||||||
|  | 		const focus = () => inputEl.value.focus(); | ||||||
|  | 		const onInput = (ev) => { | ||||||
|  | 			changed.value = true; | ||||||
|  | 			context.emit('change', ev); | ||||||
|  | 		}; | ||||||
|  | 		const onKeydown = (ev: KeyboardEvent) => { | ||||||
|  | 			context.emit('keydown', ev); | ||||||
|  |  | ||||||
|  | 			if (ev.code === 'Enter') { | ||||||
|  | 				context.emit('enter'); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		const updated = () => { | ||||||
|  | 			changed.value = false; | ||||||
|  | 			context.emit('update:modelValue', v.value); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		const debouncedUpdated = debounce(1000, updated); | ||||||
|  |  | ||||||
|  | 		watch(modelValue, newValue => { | ||||||
|  | 			v.value = newValue; | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		watch(v, newValue => { | ||||||
|  | 			if (!props.manualSave) { | ||||||
|  | 				if (props.debounce) { | ||||||
|  | 					debouncedUpdated(); | ||||||
|  | 				} else { | ||||||
|  | 					updated(); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			invalid.value = inputEl.value.validity.badInput; | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		onMounted(() => { | ||||||
|  | 			nextTick(() => { | ||||||
|  | 				if (autofocus.value) { | ||||||
|  | 					focus(); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		return { | 		return { | ||||||
| 			focused: false, | 			v, | ||||||
| 			changed: false, | 			focused, | ||||||
| 		} | 			invalid, | ||||||
|  | 			changed, | ||||||
|  | 			filled, | ||||||
|  | 			inputEl, | ||||||
|  | 			focus, | ||||||
|  | 			onInput, | ||||||
|  | 			onKeydown, | ||||||
|  | 			updated, | ||||||
|  | 		}; | ||||||
| 	}, | 	}, | ||||||
| 	computed: { |  | ||||||
| 		filled(): boolean { |  | ||||||
| 			return this.value != '' && this.value != null; |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 	methods: { |  | ||||||
| 		focus() { |  | ||||||
| 			this.$refs.input.focus(); |  | ||||||
| 		}, |  | ||||||
| 		onInput(ev) { |  | ||||||
| 			this.changed = true; |  | ||||||
| 			this.$emit('update:value', ev.target.value); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .adhpbeos { | .adhpbeos { | ||||||
| 	margin: 42px 0 32px 0; | 	margin: 1.5em 0; | ||||||
| 	position: relative; |  | ||||||
|  |  | ||||||
| 	&:first-child { | 	> .label { | ||||||
| 		margin-top: 16px; | 		font-size: 0.85em; | ||||||
|  | 		padding: 0 0 8px 12px; | ||||||
|  | 		user-select: none; | ||||||
|  |  | ||||||
|  | 		&:empty { | ||||||
|  | 			display: none; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	&:last-child { | 	> .caption { | ||||||
| 		margin-bottom: 0; | 		font-size: 0.8em; | ||||||
|  | 		padding: 8px 0 0 12px; | ||||||
|  | 		color: var(--fgTransparentWeak); | ||||||
|  |  | ||||||
|  | 		&:empty { | ||||||
|  | 			display: none; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	> .input { | 	> .input { | ||||||
| 		position: relative; | 		position: relative; | ||||||
|  |  | ||||||
| 		&:before { |  | ||||||
| 			content: ''; |  | ||||||
| 			display: block; |  | ||||||
| 			position: absolute; |  | ||||||
| 			top: 0; |  | ||||||
| 			bottom: 0; |  | ||||||
| 			left: 0; |  | ||||||
| 			right: 0; |  | ||||||
| 			background: none; |  | ||||||
| 			border: solid 1px var(--inputBorder); |  | ||||||
| 			border-radius: 3px; |  | ||||||
| 			pointer-events: none; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		&:after { |  | ||||||
| 			content: ''; |  | ||||||
| 			display: block; |  | ||||||
| 			position: absolute; |  | ||||||
| 			top: 0; |  | ||||||
| 			bottom: 0; |  | ||||||
| 			left: 0; |  | ||||||
| 			right: 0; |  | ||||||
| 			background: none; |  | ||||||
| 			border: solid 2px var(--accent); |  | ||||||
| 			border-radius: 3px; |  | ||||||
| 			opacity: 0; |  | ||||||
| 			transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1); |  | ||||||
| 			pointer-events: none; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> .label { |  | ||||||
| 			position: absolute; |  | ||||||
| 			top: 6px; |  | ||||||
| 			left: 12px; |  | ||||||
| 			pointer-events: none; |  | ||||||
| 			transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); |  | ||||||
| 			transition-duration: 0.3s; |  | ||||||
| 			font-size: 1em; |  | ||||||
| 			line-height: 32px; |  | ||||||
| 			pointer-events: none; |  | ||||||
| 			//will-change transform |  | ||||||
| 			transform-origin: top left; |  | ||||||
| 			transform: scale(1); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> textarea { | 		> textarea { | ||||||
|  | 			appearance: none; | ||||||
|  | 			-webkit-appearance: none; | ||||||
| 			display: block; | 			display: block; | ||||||
| 			width: 100%; | 			width: 100%; | ||||||
| 			min-width: 100%; | 			min-width: 100%; | ||||||
| 			max-width: 100%; | 			max-width: 100%; | ||||||
| 			min-height: 130px; | 			min-height: 130px; | ||||||
|  | 			margin: 0; | ||||||
| 			padding: 12px; | 			padding: 12px; | ||||||
| 			box-sizing: border-box; |  | ||||||
| 			font: inherit; | 			font: inherit; | ||||||
| 			font-weight: normal; | 			font-weight: normal; | ||||||
| 			font-size: 1em; | 			font-size: 1em; | ||||||
| 			background: transparent; | 			color: var(--fg); | ||||||
| 			border: none; | 			background: var(--panel); | ||||||
| 			border-radius: 0; | 			border: solid 1px var(--inputBorder); | ||||||
|  | 			border-radius: 6px; | ||||||
| 			outline: none; | 			outline: none; | ||||||
| 			box-shadow: none; | 			box-shadow: none; | ||||||
| 			color: var(--fg); | 			box-sizing: border-box; | ||||||
|  | 			transition: border-color 0.1s ease-out; | ||||||
|  |  | ||||||
| 			&.code { | 			&:hover { | ||||||
| 				tab-size: 2; | 				border-color: var(--inputBorderHover); | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .save { |  | ||||||
| 		margin: 6px 0 0 0; |  | ||||||
| 		font-size: 0.8em; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	> .desc { |  | ||||||
| 		margin: 6px 0 0 0; |  | ||||||
|  |  | ||||||
| 		&:empty { |  | ||||||
| 			display: none; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		* { |  | ||||||
| 			margin: 0; |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		&.focused { | 		&.focused { | ||||||
| 		> .input { | 			> textarea { | ||||||
| 			&:after { | 				border-color: var(--accent); | ||||||
| 				opacity: 1; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			> .label { |  | ||||||
| 				color: var(--accent); |  | ||||||
| 			} |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	&.focused, | 		&.disabled { | ||||||
| 	&.filled { | 			opacity: 0.7; | ||||||
| 		> .input { |  | ||||||
| 			> .label { | 			&, * { | ||||||
| 				top: -24px; | 				cursor: not-allowed !important; | ||||||
| 				left: 0 !important; |  | ||||||
| 				transform: scale(0.75); |  | ||||||
| 			} |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		&.tall { | 		&.tall { | ||||||
| 		> .input { |  | ||||||
| 			> textarea { | 			> textarea { | ||||||
| 				min-height: 200px; | 				min-height: 200px; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 		&.pre { | 		&.pre { | ||||||
| 		> .input { |  | ||||||
| 			> textarea { | 			> textarea { | ||||||
| 				white-space: pre; | 				white-space: pre; | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -3,15 +3,11 @@ | |||||||
| 	<div class="ebkgocck" :class="{ front }" v-if="showing"> | 	<div class="ebkgocck" :class="{ front }" v-if="showing"> | ||||||
| 		<div class="body _popup _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown"> | 		<div class="body _popup _shadow _narrow_" @mousedown="onBodyMousedown" @keydown="onKeydown"> | ||||||
| 			<div class="header" :class="{ mini }" @contextmenu.prevent.stop="onContextmenu"> | 			<div class="header" :class="{ mini }" @contextmenu.prevent.stop="onContextmenu"> | ||||||
| 				<slot v-if="closeRight" name="buttons"><button class="_button" style="pointer-events: none;"></button></slot> | 				<button v-if="closeButton" class="_button" @click="close()"><i class="fas fa-times"></i></button> | ||||||
| 				<button v-else class="_button" @click="close()"><i class="fas fa-times"></i></button> |  | ||||||
|  |  | ||||||
| 				<span class="title" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown"> | 				<span class="title" @mousedown.prevent="onHeaderMousedown" @touchstart.prevent="onHeaderMousedown"> | ||||||
| 					<slot name="header"></slot> | 					<slot name="header"></slot> | ||||||
| 				</span> | 				</span> | ||||||
|  |  | ||||||
| 				<button v-if="closeRight" class="_button" @click="close()"><i class="fas fa-times"></i></button> |  | ||||||
| 				<slot v-else name="buttons"><button class="_button" style="pointer-events: none;"></button></slot> |  | ||||||
| 			</div> | 			</div> | ||||||
| 			<div class="body" v-if="padding"> | 			<div class="body" v-if="padding"> | ||||||
| 				<div class="_section"> | 				<div class="_section"> | ||||||
| @@ -86,10 +82,10 @@ export default defineComponent({ | |||||||
| 			required: false, | 			required: false, | ||||||
| 			default: false, | 			default: false, | ||||||
| 		}, | 		}, | ||||||
| 		closeRight: { | 		closeButton: { | ||||||
| 			type: Boolean, | 			type: Boolean, | ||||||
| 			required: false, | 			required: false, | ||||||
| 			default: false, | 			default: true, | ||||||
| 		}, | 		}, | ||||||
| 		mini: { | 		mini: { | ||||||
| 			type: Boolean, | 			type: Boolean, | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ | |||||||
| import { defineComponent } from 'vue'; | import { defineComponent } from 'vue'; | ||||||
| import { parseAcct } from '@/misc/acct'; | import { parseAcct } from '@/misc/acct'; | ||||||
| import MkFollowButton from './follow-button.vue'; | import MkFollowButton from './follow-button.vue'; | ||||||
| import { userPage } from '../filters/user'; | import { userPage } from '@client/filters/user'; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| 	components: { | 	components: { | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ | |||||||
| import { defineComponent } from 'vue'; | import { defineComponent } from 'vue'; | ||||||
| import paging from '@client/scripts/paging'; | import paging from '@client/scripts/paging'; | ||||||
| import MkUserInfo from './user-info.vue'; | import MkUserInfo from './user-info.vue'; | ||||||
| import { userPage } from '../filters/user'; | import { userPage } from '@client/filters/user'; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| 	components: { | 	components: { | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ | |||||||
| import { defineComponent } from 'vue'; | import { defineComponent } from 'vue'; | ||||||
| import { parseAcct } from '@/misc/acct'; | import { parseAcct } from '@/misc/acct'; | ||||||
| import MkFollowButton from './follow-button.vue'; | import MkFollowButton from './follow-button.vue'; | ||||||
| import { userPage } from '../filters/user'; | import { userPage } from '@client/filters/user'; | ||||||
| import * as os from '@client/os'; | import * as os from '@client/os'; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|   | |||||||
| @@ -10,9 +10,15 @@ | |||||||
| 	<template #header>{{ $ts.selectUser }}</template> | 	<template #header>{{ $ts.selectUser }}</template> | ||||||
| 	<div class="tbhwbxda _monolithic_"> | 	<div class="tbhwbxda _monolithic_"> | ||||||
| 		<div class="_section"> | 		<div class="_section"> | ||||||
| 			<div class="inputs"> | 			<div class="_inputSplit _inputNoTopMargin _inputNoBottomMargin"> | ||||||
| 				<MkInput v-model:value="username" class="input" @update:value="search" ref="username"><span>{{ $ts.username }}</span><template #prefix>@</template></MkInput> | 				<MkInput v-model="username" class="input" @update:modelValue="search" ref="username"> | ||||||
| 				<MkInput v-model:value="host" class="input" @update:value="search"><span>{{ $ts.host }}</span><template #prefix>@</template></MkInput> | 					<template #label>{{ $ts.username }}</template> | ||||||
|  | 					<template #prefix>@</template> | ||||||
|  | 				</MkInput> | ||||||
|  | 				<MkInput v-model="host" class="input" @update:modelValue="search"> | ||||||
|  | 					<template #label>{{ $ts.host }}</template> | ||||||
|  | 					<template #prefix>@</template> | ||||||
|  | 				</MkInput> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div class="_section result" v-if="username != '' || host != ''" :class="{ hit: users.length > 0 }"> | 		<div class="_section result" v-if="username != '' || host != ''" :class="{ hit: users.length > 0 }"> | ||||||
| @@ -138,14 +144,6 @@ export default defineComponent({ | |||||||
| 			padding: 0; | 			padding: 0; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		> .inputs { |  | ||||||
| 			> .input { |  | ||||||
| 				display: inline-block; |  | ||||||
| 				width: 50%; |  | ||||||
| 				margin: 0; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		> .users { | 		> .users { | ||||||
| 			flex: 1; | 			flex: 1; | ||||||
| 			overflow: auto; | 			overflow: auto; | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent } from 'vue'; | import { defineComponent } from 'vue'; | ||||||
| import paging from '@client/scripts/paging'; | import paging from '@client/scripts/paging'; | ||||||
| import { userPage } from '../filters/user'; | import { userPage } from '@client/filters/user'; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
| 	mixins: [ | 	mixins: [ | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| <div class="vjoppmmu"> | <div class="vjoppmmu"> | ||||||
| 	<template v-if="edit"> | 	<template v-if="edit"> | ||||||
| 		<header> | 		<header> | ||||||
| 			<MkSelect v-model:value="widgetAdderSelected" style="margin-bottom: var(--margin)"> | 			<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)"> | ||||||
| 				<template #label>{{ $ts.selectWidget }}</template> | 				<template #label>{{ $ts.selectWidget }}</template> | ||||||
| 				<option v-for="widget in widgetDefs" :value="widget" :key="widget">{{ $t(`_widgets.${widget}`) }}</option> | 				<option v-for="widget in widgetDefs" :value="widget" :key="widget">{{ $t(`_widgets.${widget}`) }}</option> | ||||||
| 			</MkSelect> | 			</MkSelect> | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ import '@client/style.scss'; | |||||||
|  |  | ||||||
| import * as Sentry from '@sentry/browser'; | import * as Sentry from '@sentry/browser'; | ||||||
| import { Integrations } from '@sentry/tracing'; | import { Integrations } from '@sentry/tracing'; | ||||||
| import { computed, createApp, watch } from 'vue'; | import { computed, createApp, watch, markRaw } from 'vue'; | ||||||
|  |  | ||||||
| import widgets from '@client/widgets'; | import widgets from '@client/widgets'; | ||||||
| import directives from '@client/directives'; | import directives from '@client/directives'; | ||||||
| @@ -282,7 +282,7 @@ if ($i) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	const main = stream.useChannel('main', null, 'System'); | 	const main = markRaw(stream.useChannel('main', null, 'System')); | ||||||
|  |  | ||||||
| 	// 自分の情報が更新されたとき | 	// 自分の情報が更新されたとき | ||||||
| 	main.on('meUpdated', i => { | 	main.on('meUpdated', i => { | ||||||
|   | |||||||
| @@ -113,6 +113,16 @@ export const menuDef = { | |||||||
| 		icon: 'fas fa-satellite-dish', | 		icon: 'fas fa-satellite-dish', | ||||||
| 		to: '/channels', | 		to: '/channels', | ||||||
| 	}, | 	}, | ||||||
|  | 	federation: { | ||||||
|  | 		title: 'federation', | ||||||
|  | 		icon: 'fas fa-globe', | ||||||
|  | 		to: '/federation', | ||||||
|  | 	}, | ||||||
|  | 	emojis: { | ||||||
|  | 		title: 'emojis', | ||||||
|  | 		icon: 'fas fa-laugh', | ||||||
|  | 		to: '/emojis', | ||||||
|  | 	}, | ||||||
| 	games: { | 	games: { | ||||||
| 		title: 'games', | 		title: 'games', | ||||||
| 		icon: 'fas fa-gamepad', | 		icon: 'fas fa-gamepad', | ||||||
| @@ -133,7 +143,7 @@ export const menuDef = { | |||||||
| 		title: 'switchUi', | 		title: 'switchUi', | ||||||
| 		icon: 'fas fa-columns', | 		icon: 'fas fa-columns', | ||||||
| 		action: (ev) => { | 		action: (ev) => { | ||||||
| 			os.modalMenu([{ | 			os.popupMenu([{ | ||||||
| 				text: i18n.locale.default, | 				text: i18n.locale.default, | ||||||
| 				action: () => { | 				action: () => { | ||||||
| 					localStorage.setItem('ui', 'default'); | 					localStorage.setItem('ui', 'default'); | ||||||
|   | |||||||
| @@ -368,10 +368,10 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea: | |||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function modalMenu(items: any[], src?: HTMLElement, options?: { align?: string; viaKeyboard?: boolean }) { | export function popupMenu(items: any[], src?: HTMLElement, options?: { align?: string; viaKeyboard?: boolean }) { | ||||||
| 	return new Promise((resolve, reject) => { | 	return new Promise((resolve, reject) => { | ||||||
| 		let dispose; | 		let dispose; | ||||||
| 		popup(import('@client/components/ui/modal-menu.vue'), { | 		popup(import('@client/components/ui/popup-menu.vue'), { | ||||||
| 			items, | 			items, | ||||||
| 			src, | 			src, | ||||||
| 			align: options?.align, | 			align: options?.align, | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| <template> | <template> | ||||||
| <transition :name="$store.state.animation ? 'zoom' : ''" appear> | <transition :name="$store.state.animation ? 'zoom' : ''" appear> | ||||||
| 	<div class="_section"> | 	<div class="mjndxjch"> | ||||||
| 		<div class="mjndxjch _content"> |  | ||||||
| 		<img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> | 		<img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> | ||||||
| 			<p><i class="fas fa-exclamation-triangle"></i> {{ $ts.pageLoadError }}</p> | 		<p><b><i class="fas fa-exclamation-triangle"></i> {{ $ts.pageLoadError }}</b></p> | ||||||
| 		<p>{{ $ts.pageLoadErrorDescription }}</p> | 		<p>{{ $ts.pageLoadErrorDescription }}</p> | ||||||
| 		</div> | 		<p><MkA to="/docs/general/troubleshooting" class="_link">{{ $ts.troubleshooting }}</MkA></p> | ||||||
|  | 		<p v-if="error" class="error">ERROR: {{ error }}</p> | ||||||
| 	</div> | 	</div> | ||||||
| </transition> | </transition> | ||||||
| </template> | </template> | ||||||
| @@ -19,6 +19,11 @@ export default defineComponent({ | |||||||
| 	components: { | 	components: { | ||||||
| 		MkButton, | 		MkButton, | ||||||
| 	}, | 	}, | ||||||
|  | 	props: { | ||||||
|  | 		error: { | ||||||
|  | 			required: false, | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			[symbols.PAGE_INFO]: { | 			[symbols.PAGE_INFO]: { | ||||||
| @@ -32,10 +37,11 @@ export default defineComponent({ | |||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .mjndxjch { | .mjndxjch { | ||||||
|  | 	padding: 32px; | ||||||
| 	text-align: center; | 	text-align: center; | ||||||
|  |  | ||||||
| 	> p { | 	> p { | ||||||
| 		margin: 0 0 8px 0; | 		margin: 0 0 12px 0; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	> .button { | 	> .button { | ||||||
| @@ -45,8 +51,12 @@ export default defineComponent({ | |||||||
| 	> img { | 	> img { | ||||||
| 		vertical-align: bottom; | 		vertical-align: bottom; | ||||||
| 		height: 128px; | 		height: 128px; | ||||||
| 		margin-bottom: 16px; | 		margin-bottom: 24px; | ||||||
| 		border-radius: 16px; | 		border-radius: 16px; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	> .error { | ||||||
|  | 		opacity: 0.7; | ||||||
|  | 	} | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -272,7 +272,7 @@ export default defineComponent({ | |||||||
| 	 | 	 | ||||||
| 		showTypeMenu(e: MouseEvent) { | 		showTypeMenu(e: MouseEvent) { | ||||||
| 			return new Promise<ThemeValue>((resolve) => { | 			return new Promise<ThemeValue>((resolve) => { | ||||||
| 				os.modalMenu([{ | 				os.popupMenu([{ | ||||||
| 					text: this.$ts._theme.defaultValue, | 					text: this.$ts._theme.defaultValue, | ||||||
| 					action: () => resolve(null), | 					action: () => resolve(null), | ||||||
| 				}, { | 				}, { | ||||||
|   | |||||||
| @@ -1,13 +1,13 @@ | |||||||
| <template> | <template> | ||||||
| <div class="_root"> | <div class="_root"> | ||||||
| 	<div class="_block" style="padding: 24px;"> | 	<div class="_block" style="padding: 24px;"> | ||||||
| 		<MkInput v-model:value="endpoint" :datalist="endpoints" @update:value="onEndpointChange()"> | 		<MkInput v-model="endpoint" :datalist="endpoints" @update:modelValue="onEndpointChange()"> | ||||||
| 			<span>Endpoint</span> | 			<template #label>Endpoint</template> | ||||||
| 		</MkInput> | 		</MkInput> | ||||||
| 		<MkTextarea v-model:value="body" code> | 		<MkTextarea v-model="body" code> | ||||||
| 			<span>Params (JSON or JSON5)</span> | 			<template #label>Params (JSON or JSON5)</template> | ||||||
| 		</MkTextarea> | 		</MkTextarea> | ||||||
| 		<MkSwitch v-model:value="withCredential"> | 		<MkSwitch v-model="withCredential"> | ||||||
| 			With credential | 			With credential | ||||||
| 		</MkSwitch> | 		</MkSwitch> | ||||||
| 		<MkButton primary full @click="send" :disabled="sending"> | 		<MkButton primary full @click="send" :disabled="sending"> | ||||||
| @@ -16,8 +16,8 @@ | |||||||
| 		</MkButton> | 		</MkButton> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div v-if="res" class="_block" style="padding: 24px;"> | 	<div v-if="res" class="_block" style="padding: 24px;"> | ||||||
| 		<MkTextarea v-model:value="res" code readonly tall> | 		<MkTextarea v-model="res" code readonly tall> | ||||||
| 			<span>Response</span> | 			<template #label>Response</template> | ||||||
| 		</MkTextarea> | 		</MkTextarea> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -2,9 +2,13 @@ | |||||||
| <div> | <div> | ||||||
| 	<div class="_section"> | 	<div class="_section"> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkInput v-model:value="name">{{ $ts.name }}</MkInput> | 			<MkInput v-model="name"> | ||||||
|  | 				<template #label>{{ $ts.name }}</template> | ||||||
|  | 			</MkInput> | ||||||
|  |  | ||||||
| 			<MkTextarea v-model:value="description">{{ $ts.description }}</MkTextarea> | 			<MkTextarea v-model="description"> | ||||||
|  | 				<template #label>{{ $ts.description }}</template> | ||||||
|  | 			</MkTextarea> | ||||||
|  |  | ||||||
| 			<div class="banner"> | 			<div class="banner"> | ||||||
| 				<MkButton v-if="bannerId == null" @click="setBannerImage"><i class="fas fa-plus"></i> {{ $ts._channel.setBanner }}</MkButton> | 				<MkButton v-if="bannerId == null" @click="setBannerImage"><i class="fas fa-plus"></i> {{ $ts._channel.setBanner }}</MkButton> | ||||||
|   | |||||||
| @@ -79,7 +79,7 @@ export default defineComponent({ | |||||||
|  |  | ||||||
| 	methods: { | 	methods: { | ||||||
| 		menu(ev) { | 		menu(ev) { | ||||||
| 			os.modalMenu([this.isOwned ? { | 			os.popupMenu([this.isOwned ? { | ||||||
| 				icon: 'fas fa-pencil-alt', | 				icon: 'fas fa-pencil-alt', | ||||||
| 				text: this.$ts.edit, | 				text: this.$ts.edit, | ||||||
| 				action: async () => { | 				action: async () => { | ||||||
|   | |||||||
| @@ -1,10 +1,12 @@ | |||||||
| <template> | <template> | ||||||
| <div class="qyqbqfal" v-size="{ max: [500] }"> | <div class="qyqbqfal" v-size="{ max: [500] }"> | ||||||
|  | 	<div class="main"> | ||||||
| 		<div class="title">{{ title }}</div> | 		<div class="title">{{ title }}</div> | ||||||
| 		<div class="body" v-html="body"></div> | 		<div class="body" v-html="body"></div> | ||||||
| 		<div class="footer"> | 		<div class="footer"> | ||||||
| 			<MkLink :url="`https://github.com/misskey-dev/misskey/blob/master/src/docs/${lang}/${doc}.md`" class="at">{{ $ts.docSource }}</MkLink> | 			<MkLink :url="`https://github.com/misskey-dev/misskey/blob/master/src/docs/${lang}/${doc}.md`" class="at">{{ $ts.docSource }}</MkLink> | ||||||
| 		</div> | 		</div> | ||||||
|  | 	</div> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| @@ -62,6 +64,10 @@ export default defineComponent({ | |||||||
| 		fetchDoc() { | 		fetchDoc() { | ||||||
| 			fetch(`${url}/doc-assets/${lang}/${this.doc}.md`).then(res => res.text()).then(md => { | 			fetch(`${url}/doc-assets/${lang}/${this.doc}.md`).then(res => res.text()).then(md => { | ||||||
| 				this.parse(md); | 				this.parse(md); | ||||||
|  | 			}).catch(() => { | ||||||
|  | 				fetch(`${url}/doc-assets/ja-JP/${this.doc}.md`).then(res => res.text()).then(md => { | ||||||
|  | 					this.parse(md); | ||||||
|  | 				}); | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| @@ -105,13 +111,17 @@ export default defineComponent({ | |||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .qyqbqfal { | .qyqbqfal { | ||||||
| 	padding: 32px; | 	padding: 32px; | ||||||
| 	max-width: 800px; | 	background: var(--panel); | ||||||
| 	margin: 0 auto; | 	line-height: 1.5; | ||||||
|  |  | ||||||
| 	&.max-width_500px { | 	&.max-width_500px { | ||||||
| 		padding: 16px; | 		padding: 16px; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	> .main { | ||||||
|  | 		max-width: 800px; | ||||||
|  | 		margin: 0 auto; | ||||||
|  |  | ||||||
| 		> .title { | 		> .title { | ||||||
| 			font-size: 1.5em; | 			font-size: 1.5em; | ||||||
| 			font-weight: bold; | 			font-weight: bold; | ||||||
| @@ -153,6 +163,10 @@ export default defineComponent({ | |||||||
| 				border-bottom: solid 0.5px var(--divider); | 				border-bottom: solid 0.5px var(--divider); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			::v-deep(h3) { | ||||||
|  | 				margin: 1.25em 0 0.5em 0; | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			::v-deep(table) { | 			::v-deep(table) { | ||||||
| 				width: 100%; | 				width: 100%; | ||||||
| 				max-width: 100%; | 				max-width: 100%; | ||||||
| @@ -195,6 +209,24 @@ export default defineComponent({ | |||||||
| 					padding: 0; | 					padding: 0; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			::v-deep(.info) { | ||||||
|  | 				font-size: 90%; | ||||||
|  | 				background: var(--infoBg); | ||||||
|  | 				color: var(--infoFg); | ||||||
|  | 				padding: 1em; | ||||||
|  | 				margin: 0.75em 0; | ||||||
|  | 				border-radius: 6px; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			::v-deep(.warn) { | ||||||
|  | 				font-size: 90%; | ||||||
|  | 				background: var(--infoWarnBg); | ||||||
|  | 				color: var(--infoWarnFg); | ||||||
|  | 				padding: 1em; | ||||||
|  | 				margin: 0.75em 0; | ||||||
|  | 				border-radius: 6px; | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		> .footer { | 		> .footer { | ||||||
| @@ -202,5 +234,6 @@ export default defineComponent({ | |||||||
| 			margin: 1.5em 0 0 0; | 			margin: 1.5em 0 0 0; | ||||||
| 			border-top: solid 2px var(--divider); | 			border-top: solid 2px var(--divider); | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -1,14 +1,50 @@ | |||||||
| <template> | <template> | ||||||
| <div> | <div class="vtaihdtm"> | ||||||
| 	<main class="_section"> | 	<div class="search"> | ||||||
| 		<div class="_content"> | 		<MkInput v-model="query" :debounce="true" type="search" class="_inputNoTopMargin _inputNoBottomMargin" :placeholder="$ts.search"> | ||||||
| 			<ul> | 			<template #prefix><i class="fas fa-search"></i></template> | ||||||
| 				<li v-for="doc in docs" :key="doc.path"> | 		</MkInput> | ||||||
| 					<MkA :to="`/docs/${doc.path}`">{{ doc.title }}</MkA> |  | ||||||
| 				</li> |  | ||||||
| 			</ul> |  | ||||||
| 	</div> | 	</div> | ||||||
| 	</main> | 	<MkFolder> | ||||||
|  | 		<template #header>{{ $ts._docs.generalTopics }}</template> | ||||||
|  | 		<div class="docs"> | ||||||
|  | 			<MkA v-for="doc in docs.filter(doc => doc.path.startsWith('general/'))" :key="doc.path" :to="`/docs/${doc.path}`" class="doc"> | ||||||
|  | 				<div class="title">{{ doc.title }}</div> | ||||||
|  | 				<div class="summary">{{ doc.summary }}</div> | ||||||
|  | 				<div class="read">{{ $ts._docs.continueReading }}</div> | ||||||
|  | 			</MkA> | ||||||
|  | 		</div> | ||||||
|  | 	</MkFolder> | ||||||
|  | 	<MkFolder> | ||||||
|  | 		<template #header>{{ $ts._docs.features }}</template> | ||||||
|  | 		<div class="docs"> | ||||||
|  | 			<MkA v-for="doc in docs.filter(doc => doc.path.startsWith('features/'))" :key="doc.path" :to="`/docs/${doc.path}`" class="doc"> | ||||||
|  | 				<div class="title">{{ doc.title }}</div> | ||||||
|  | 				<div class="summary">{{ doc.summary }}</div> | ||||||
|  | 				<div class="read">{{ $ts._docs.continueReading }}</div> | ||||||
|  | 			</MkA> | ||||||
|  | 		</div> | ||||||
|  | 	</MkFolder> | ||||||
|  | 	<MkFolder> | ||||||
|  | 		<template #header>{{ $ts._docs.advancedTopics }}</template> | ||||||
|  | 		<div class="docs"> | ||||||
|  | 			<MkA v-for="doc in docs.filter(doc => doc.path.startsWith('advanced/'))" :key="doc.path" :to="`/docs/${doc.path}`" class="doc"> | ||||||
|  | 				<div class="title">{{ doc.title }}</div> | ||||||
|  | 				<div class="summary">{{ doc.summary }}</div> | ||||||
|  | 				<div class="read">{{ $ts._docs.continueReading }}</div> | ||||||
|  | 			</MkA> | ||||||
|  | 		</div> | ||||||
|  | 	</MkFolder> | ||||||
|  | 	<MkFolder> | ||||||
|  | 		<template #header>{{ $ts._docs.admin }}</template> | ||||||
|  | 		<div class="docs"> | ||||||
|  | 			<MkA v-for="doc in docs.filter(doc => doc.path.startsWith('admin/'))" :key="doc.path" :to="`/docs/${doc.path}`" class="doc"> | ||||||
|  | 				<div class="title">{{ doc.title }}</div> | ||||||
|  | 				<div class="summary">{{ doc.summary }}</div> | ||||||
|  | 				<div class="read">{{ $ts._docs.continueReading }}</div> | ||||||
|  | 			</MkA> | ||||||
|  | 		</div> | ||||||
|  | 	</MkFolder> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| @@ -16,8 +52,15 @@ | |||||||
| import { defineComponent } from 'vue'; | import { defineComponent } from 'vue'; | ||||||
| import { url, lang } from '@client/config'; | import { url, lang } from '@client/config'; | ||||||
| import * as symbols from '@client/symbols'; | import * as symbols from '@client/symbols'; | ||||||
|  | import MkFolder from '@client/components/ui/folder.vue'; | ||||||
|  | import MkInput from '@client/components/ui/input.vue'; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|  | 	components: { | ||||||
|  | 		MkFolder, | ||||||
|  | 		MkInput, | ||||||
|  | 	}, | ||||||
|  |  | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			[symbols.PAGE_INFO]: { | 			[symbols.PAGE_INFO]: { | ||||||
| @@ -25,13 +68,72 @@ export default defineComponent({ | |||||||
| 				icon: 'fas fa-question-circle' | 				icon: 'fas fa-question-circle' | ||||||
| 			}, | 			}, | ||||||
| 			docs: [], | 			docs: [], | ||||||
|  | 			query: null, | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	watch: { | ||||||
|  | 		query() { | ||||||
|  | 			fetch(`${url}/docs.json?lang=${lang}&q=${this.query}`).then(res => res.json()).then(docs => { | ||||||
|  | 				this.docs = docs; | ||||||
|  | 			}); | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	created() { | 	created() { | ||||||
|  | 		fetch(`${url}/docs.json?lang=ja-JP`).then(res => res.json()).then(jaDocs => { | ||||||
| 			fetch(`${url}/docs.json?lang=${lang}`).then(res => res.json()).then(docs => { | 			fetch(`${url}/docs.json?lang=${lang}`).then(res => res.json()).then(docs => { | ||||||
| 			this.docs = docs; | 				this.docs = jaDocs.map(doc => { | ||||||
|  | 					const exist = docs.find(d => d.path === doc.path); | ||||||
|  | 					return exist || doc; | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
| 		}); | 		}); | ||||||
| 	}, | 	}, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .vtaihdtm { | ||||||
|  | 	background: var(--panel); | ||||||
|  |  | ||||||
|  | 	> .search { | ||||||
|  | 		padding: 16px; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	.docs { | ||||||
|  | 		display: grid; | ||||||
|  | 		grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); | ||||||
|  | 		grid-gap: 12px; | ||||||
|  | 		margin: 0 16px 16px 16px; | ||||||
|  |  | ||||||
|  | 		> .doc { | ||||||
|  | 			display: inline-block; | ||||||
|  | 			padding: 16px; | ||||||
|  | 			border: solid 1px var(--divider); | ||||||
|  | 			border-radius: 6px; | ||||||
|  |  | ||||||
|  | 			&:hover { | ||||||
|  | 				border: solid 1px var(--accent); | ||||||
|  | 				text-decoration: none; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			> .title { | ||||||
|  | 				font-weight: bold; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			> .summary { | ||||||
|  | 				white-space: nowrap; | ||||||
|  | 				overflow: hidden; | ||||||
|  | 				text-overflow: ellipsis; | ||||||
|  | 				font-size: 0.9em; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			> .read { | ||||||
|  | 				color: var(--link); | ||||||
|  | 				font-size: 0.9em; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | </style> | ||||||
|   | |||||||
| @@ -20,7 +20,6 @@ export default defineComponent({ | |||||||
| 			[symbols.PAGE_INFO]: { | 			[symbols.PAGE_INFO]: { | ||||||
| 				title: computed(() => this.folder ? this.folder.name : this.$ts.drive), | 				title: computed(() => this.folder ? this.folder.name : this.$ts.drive), | ||||||
| 				icon: 'fas fa-cloud', | 				icon: 'fas fa-cloud', | ||||||
| 				menu: () => this.$refs.drive.getMenu() |  | ||||||
| 			}, | 			}, | ||||||
| 			folder: null, | 			folder: null, | ||||||
| 		}; | 		}; | ||||||
|   | |||||||
							
								
								
									
										151
									
								
								src/client/pages/emojis.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								src/client/pages/emojis.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | |||||||
|  | <template> | ||||||
|  | <div class="driuhtrh"> | ||||||
|  | 	<div class="query"> | ||||||
|  | 		<MkInput v-model="q" class="_inputNoTopMargin _inputNoBottomMargin" :placeholder="$ts.search"> | ||||||
|  | 			<template #prefix><i class="fas fa-search"></i></template> | ||||||
|  | 		</MkInput> | ||||||
|  | 	</div> | ||||||
|  |  | ||||||
|  | 	<div class="emojis"> | ||||||
|  | 		<MkFolder v-if="searchEmojis"> | ||||||
|  | 			<template #header>{{ $ts.searchResult }}</template> | ||||||
|  | 			<div class="zuvgdzyt"> | ||||||
|  | 				<button v-for="emoji in searchEmojis" :key="emoji.name" class="emoji _button" @click="menu(emoji, $event)"> | ||||||
|  | 					<img :src="emoji.url" class="img" :alt="emoji.name"/> | ||||||
|  | 					<div class="body"> | ||||||
|  | 						<div class="name _monospace">{{ emoji.name }}</div> | ||||||
|  | 						<div class="info">{{ emoji.aliases.join(' ') }}</div> | ||||||
|  | 					</div> | ||||||
|  | 				</button> | ||||||
|  | 			</div> | ||||||
|  | 		</MkFolder> | ||||||
|  | 		<MkFolder v-for="category in customEmojiCategories" :key="category"> | ||||||
|  | 			<template #header>{{ category || $ts.other }}</template> | ||||||
|  | 			<div class="zuvgdzyt"> | ||||||
|  | 				<button v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji _button" @click="menu(emoji, $event)"> | ||||||
|  | 					<img :src="emoji.url" class="img" :alt="emoji.name"/> | ||||||
|  | 					<div class="body"> | ||||||
|  | 						<div class="name _monospace">{{ emoji.name }}</div> | ||||||
|  | 						<div class="info">{{ emoji.aliases.join(' ') }}</div> | ||||||
|  | 					</div> | ||||||
|  | 				</button> | ||||||
|  | 			</div> | ||||||
|  | 		</MkFolder> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import { defineComponent } from 'vue'; | ||||||
|  | import MkButton from '@client/components/ui/button.vue'; | ||||||
|  | import MkInput from '@client/components/ui/input.vue'; | ||||||
|  | import MkSelect from '@client/components/ui/select.vue'; | ||||||
|  | import MkFolder from '@client/components/ui/folder.vue'; | ||||||
|  | import * as os from '@client/os'; | ||||||
|  | import * as symbols from '@client/symbols'; | ||||||
|  | import { emojiCategories } from '@client/instance'; | ||||||
|  | import copyToClipboard from '@client/scripts/copy-to-clipboard'; | ||||||
|  |  | ||||||
|  | export default defineComponent({ | ||||||
|  | 	components: { | ||||||
|  | 		MkButton, | ||||||
|  | 		MkInput, | ||||||
|  | 		MkSelect, | ||||||
|  | 		MkFolder, | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			[symbols.PAGE_INFO]: { | ||||||
|  | 				title: this.$ts.customEmojis, | ||||||
|  | 				icon: 'fas fa-laugh' | ||||||
|  | 			}, | ||||||
|  | 			q: '', | ||||||
|  | 			customEmojiCategories: emojiCategories, | ||||||
|  | 			customEmojis: this.$instance.emojis, | ||||||
|  | 			searchEmojis: null, | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	watch: { | ||||||
|  | 		q() { | ||||||
|  | 			if (this.q === '' || this.q == null) { | ||||||
|  | 				this.searchEmojis = null; | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			this.searchEmojis = this.customEmojis.filter(e => e.name.includes(this.q) || e.aliases.includes(this.q)); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	methods: { | ||||||
|  | 		menu(emoji, ev) { | ||||||
|  | 			os.popupMenu([{ | ||||||
|  | 				type: 'label', | ||||||
|  | 				text: ':' + emoji.name + ':', | ||||||
|  | 			}, { | ||||||
|  | 				text: this.$ts.copy, | ||||||
|  | 				icon: 'fas fa-copy', | ||||||
|  | 				action: () => { | ||||||
|  | 					copyToClipboard(`:${emoji.name}:`); | ||||||
|  | 					os.success(); | ||||||
|  | 				} | ||||||
|  | 			}], ev.currentTarget || ev.target); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .driuhtrh { | ||||||
|  | 	> .query { | ||||||
|  | 		background: var(--bg); | ||||||
|  | 		padding: 16px; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	> .emojis { | ||||||
|  | 		.zuvgdzyt { | ||||||
|  | 			display: grid; | ||||||
|  | 			grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); | ||||||
|  | 			grid-gap: 12px; | ||||||
|  | 			margin: 0 var(--margin) var(--margin) var(--margin); | ||||||
|  |  | ||||||
|  | 			> .emoji { | ||||||
|  | 				display: flex; | ||||||
|  | 				align-items: center; | ||||||
|  | 				padding: 12px; | ||||||
|  | 				text-align: left; | ||||||
|  | 				border: solid 1px var(--divider); | ||||||
|  | 				border-radius: 8px; | ||||||
|  |  | ||||||
|  | 				&:hover { | ||||||
|  | 					border-color: var(--accent); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				> .img { | ||||||
|  | 					width: 42px; | ||||||
|  | 					height: 42px; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				> .body { | ||||||
|  | 					padding: 0 0 0 8px; | ||||||
|  | 					white-space: nowrap; | ||||||
|  | 					overflow: hidden; | ||||||
|  |  | ||||||
|  | 					> .name { | ||||||
|  | 						text-overflow: ellipsis; | ||||||
|  | 						overflow: hidden; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					> .info { | ||||||
|  | 						opacity: 0.5; | ||||||
|  | 						font-size: 0.9em; | ||||||
|  | 						text-overflow: ellipsis; | ||||||
|  | 						overflow: hidden; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @@ -2,7 +2,10 @@ | |||||||
| <div class="lznhrdub _root"> | <div class="lznhrdub _root"> | ||||||
| 	<div> | 	<div> | ||||||
| 		<div class="_isolated"> | 		<div class="_isolated"> | ||||||
| 			<MkInput v-model:value="query" :debounce="true" type="search"><template #icon><i class="fas fa-search"></i></template><span>{{ $ts.searchUser }}</span></MkInput> | 			<MkInput v-model="query" :debounce="true" type="search"> | ||||||
|  | 				<template #prefix><i class="fas fa-search"></i></template> | ||||||
|  | 				<template #label>{{ $ts.searchUser }}</template> | ||||||
|  | 			</MkInput> | ||||||
| 		</div> | 		</div> | ||||||
|  |  | ||||||
| 		<XUserList v-if="query" class="_gap" :pagination="searchPagination" ref="search"/> | 		<XUserList v-if="query" class="_gap" :pagination="searchPagination" ref="search"/> | ||||||
|   | |||||||
| @@ -1,9 +1,12 @@ | |||||||
| <template> | <template> | ||||||
| <div class="enuoauvw"> | <div class="taeiyria"> | ||||||
| 	<div class="query"> | 	<div class="query"> | ||||||
| 		<MkInput v-model:value="host" :debounce="true"><span>{{ $ts.host }}</span></MkInput> | 		<MkInput v-model="host" :debounce="true" class="_inputNoTopMargin"> | ||||||
| 		<div class="inputs" style="display: flex;"> | 			<template #prefix><i class="fas fa-search"></i></template> | ||||||
| 			<MkSelect v-model:value="state" style="margin: 0; flex: 1;"> | 			<template #label>{{ $ts.host }}</template> | ||||||
|  | 		</MkInput> | ||||||
|  | 		<div class="_inputSplit _inputNoBottomMargin"> | ||||||
|  | 			<MkSelect v-model="state"> | ||||||
| 				<template #label>{{ $ts.state }}</template> | 				<template #label>{{ $ts.state }}</template> | ||||||
| 				<option value="all">{{ $ts.all }}</option> | 				<option value="all">{{ $ts.all }}</option> | ||||||
| 				<option value="federating">{{ $ts.federating }}</option> | 				<option value="federating">{{ $ts.federating }}</option> | ||||||
| @@ -13,7 +16,7 @@ | |||||||
| 				<option value="blocked">{{ $ts.blocked }}</option> | 				<option value="blocked">{{ $ts.blocked }}</option> | ||||||
| 				<option value="notResponding">{{ $ts.notResponding }}</option> | 				<option value="notResponding">{{ $ts.notResponding }}</option> | ||||||
| 			</MkSelect> | 			</MkSelect> | ||||||
| 			<MkSelect v-model:value="sort" style="margin: 0; flex: 1;"> | 			<MkSelect v-model="sort"> | ||||||
| 				<template #label>{{ $ts.sort }}</template> | 				<template #label>{{ $ts.sort }}</template> | ||||||
| 				<option value="+pubSub">{{ $ts.pubSub }} ({{ $ts.descendingOrder }})</option> | 				<option value="+pubSub">{{ $ts.pubSub }} ({{ $ts.descendingOrder }})</option> | ||||||
| 				<option value="-pubSub">{{ $ts.pubSub }} ({{ $ts.ascendingOrder }})</option> | 				<option value="-pubSub">{{ $ts.pubSub }} ({{ $ts.ascendingOrder }})</option> | ||||||
| @@ -38,16 +41,53 @@ | |||||||
| 	</div> | 	</div> | ||||||
| 
 | 
 | ||||||
| 	<MkPagination :pagination="pagination" #default="{items}" ref="instances" :key="host + state"> | 	<MkPagination :pagination="pagination" #default="{items}" ref="instances" :key="host + state"> | ||||||
| 		<div class="ppgwaixt _block" v-for="instance in items" :key="instance.id" @click="info(instance)"> | 		<div class="dqokceoi"> | ||||||
| 			<div class="host"><i class="fas fa-circle indicator" :class="getStatus(instance)"></i><b>{{ instance.host }}</b></div> | 			<MkA class="instance" v-for="instance in items" :key="instance.id" :to="`/instance-info/${instance.host}`"> | ||||||
| 			<div class="status"> | 				<div class="host"><img :src="instance.faviconUrl">{{ instance.host }}</div> | ||||||
|  | 				<div class="table"> | ||||||
|  | 					<div class="cell"> | ||||||
|  | 						<div class="key">{{ $ts.registeredAt }}</div> | ||||||
|  | 						<div class="value"><MkTime :time="instance.caughtAt"/></div> | ||||||
|  | 					</div> | ||||||
|  | 					<div class="cell"> | ||||||
|  | 						<div class="key">{{ $ts.software }}</div> | ||||||
|  | 						<div class="value">{{ instance.softwareName || `(${$ts.unknown})` }}</div> | ||||||
|  | 					</div> | ||||||
|  | 					<div class="cell"> | ||||||
|  | 						<div class="key">{{ $ts.version }}</div> | ||||||
|  | 						<div class="value">{{ instance.softwareVersion || `(${$ts.unknown})` }}</div> | ||||||
|  | 					</div> | ||||||
|  | 					<div class="cell"> | ||||||
|  | 						<div class="key">{{ $ts.users }}</div> | ||||||
|  | 						<div class="value">{{ instance.usersCount }}</div> | ||||||
|  | 					</div> | ||||||
|  | 					<div class="cell"> | ||||||
|  | 						<div class="key">{{ $ts.notes }}</div> | ||||||
|  | 						<div class="value">{{ instance.notesCount }}</div> | ||||||
|  | 					</div> | ||||||
|  | 					<div class="cell"> | ||||||
|  | 						<div class="key">{{ $ts.sent }}</div> | ||||||
|  | 						<div class="value"><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></div> | ||||||
|  | 					</div> | ||||||
|  | 					<div class="cell"> | ||||||
|  | 						<div class="key">{{ $ts.received }}</div> | ||||||
|  | 						<div class="value"><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></div> | ||||||
|  | 					</div> | ||||||
|  | 				</div> | ||||||
|  | 				<div class="footer"> | ||||||
|  | 					<span class="status" :class="getStatus(instance)">{{ getStatus(instance) }}</span> | ||||||
|  | 					<span class="pubSub"> | ||||||
| 						<span class="sub" v-if="instance.followersCount > 0"><i class="fas fa-caret-down icon"></i>Sub</span> | 						<span class="sub" v-if="instance.followersCount > 0"><i class="fas fa-caret-down icon"></i>Sub</span> | ||||||
| 						<span class="sub" v-else><i class="fas fa-caret-down icon"></i>-</span> | 						<span class="sub" v-else><i class="fas fa-caret-down icon"></i>-</span> | ||||||
| 						<span class="pub" v-if="instance.followingCount > 0"><i class="fas fa-caret-up icon"></i>Pub</span> | 						<span class="pub" v-if="instance.followingCount > 0"><i class="fas fa-caret-up icon"></i>Pub</span> | ||||||
| 						<span class="pub" v-else><i class="fas fa-caret-up icon"></i>-</span> | 						<span class="pub" v-else><i class="fas fa-caret-up icon"></i>-</span> | ||||||
| 				<span class="lastCommunicatedAt"><i class="fas fa-exchange-alt icon"></i><MkTime :time="instance.lastCommunicatedAt"/></span> | 					</span> | ||||||
| 				<span class="latestStatus"><i class="fas fa-traffic-light icon"></i>{{ instance.latestStatus || '-' }}</span> | 					<span class="right"> | ||||||
|  | 						<span class="latestStatus">{{ instance.latestStatus || '-' }}</span> | ||||||
|  | 						<span class="lastCommunicatedAt"><MkTime :time="instance.lastCommunicatedAt"/></span> | ||||||
|  | 					</span> | ||||||
| 				</div> | 				</div> | ||||||
|  | 			</MkA> | ||||||
| 		</div> | 		</div> | ||||||
| 	</MkPagination> | 	</MkPagination> | ||||||
| </div> | </div> | ||||||
| @@ -59,7 +99,6 @@ import MkButton from '@client/components/ui/button.vue'; | |||||||
| import MkInput from '@client/components/ui/input.vue'; | import MkInput from '@client/components/ui/input.vue'; | ||||||
| import MkSelect from '@client/components/ui/select.vue'; | import MkSelect from '@client/components/ui/select.vue'; | ||||||
| import MkPagination from '@client/components/ui/pagination.vue'; | import MkPagination from '@client/components/ui/pagination.vue'; | ||||||
| import MkInstanceInfo from './instance.vue'; |  | ||||||
| import * as os from '@client/os'; | import * as os from '@client/os'; | ||||||
| import * as symbols from '@client/symbols'; | import * as symbols from '@client/symbols'; | ||||||
| 
 | 
 | ||||||
| @@ -117,69 +156,107 @@ export default defineComponent({ | |||||||
| 
 | 
 | ||||||
| 	methods: { | 	methods: { | ||||||
| 		getStatus(instance) { | 		getStatus(instance) { | ||||||
| 			if (instance.isSuspended) return 'off'; | 			if (instance.isSuspended) return 'suspended'; | ||||||
| 			if (instance.isNotResponding) return 'red'; | 			if (instance.isNotResponding) return 'error'; | ||||||
| 			return 'green'; | 			return 'alive'; | ||||||
| 		}, | 		}, | ||||||
| 
 |  | ||||||
| 		info(instance) { |  | ||||||
| 			os.popup(MkInstanceInfo, { |  | ||||||
| 				instance: instance |  | ||||||
| 			}, {}, 'closed'); |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .enuoauvw { | .taeiyria { | ||||||
| 	> .query { | 	> .query { | ||||||
| 		margin: var(--margin); | 		background: var(--bg); | ||||||
|  | 		padding: 16px; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .ppgwaixt { | .dqokceoi { | ||||||
| 	cursor: pointer; | 	display: grid; | ||||||
|  | 	grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); | ||||||
|  | 	grid-gap: 12px; | ||||||
| 	padding: 16px; | 	padding: 16px; | ||||||
| 
 | 
 | ||||||
|  | 	> .instance { | ||||||
|  | 		padding: 16px; | ||||||
|  | 		border: solid 1px var(--divider); | ||||||
|  | 		border-radius: 6px; | ||||||
|  | 
 | ||||||
| 		&:hover { | 		&:hover { | ||||||
| 		color: var(--accent); | 			border: solid 1px var(--accent); | ||||||
|  | 			text-decoration: none; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		> .host { | 		> .host { | ||||||
| 		> .indicator { | 			font-weight: bold; | ||||||
|  | 			white-space: nowrap; | ||||||
|  | 			overflow: hidden; | ||||||
|  | 			text-overflow: ellipsis; | ||||||
|  | 
 | ||||||
|  | 			> img { | ||||||
|  | 				width: 18px; | ||||||
|  | 				height: 18px; | ||||||
|  | 				margin-right: 6px; | ||||||
|  | 				vertical-align: middle; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		> .table { | ||||||
|  | 			display: grid; | ||||||
|  | 			grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); | ||||||
|  | 			grid-gap: 6px; | ||||||
|  | 			margin: 6px 0; | ||||||
| 			font-size: 70%; | 			font-size: 70%; | ||||||
| 			vertical-align: baseline; |  | ||||||
| 			margin-right: 4px; |  | ||||||
| 
 | 
 | ||||||
| 			&.green { | 			> .cell { | ||||||
| 				color: #49c5ba; | 				> .key, > .value { | ||||||
|  | 					white-space: nowrap; | ||||||
|  | 					overflow: hidden; | ||||||
|  | 					text-overflow: ellipsis; | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 			&.yellow { | 				> .key { | ||||||
| 				color: #c5a549; | 					opacity: 0.7; | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 			&.red { | 				> .value { | ||||||
| 				color: #c54949; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			&.off { |  | ||||||
| 				color: rgba(0, 0, 0, 0.5); |  | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	> .status { | 		> .footer { | ||||||
| 			display: flex; | 			display: flex; | ||||||
| 			align-items: center; | 			align-items: center; | ||||||
| 		font-size: 90%; |  | ||||||
| 
 | 
 | ||||||
| 		> span { | 			> .status { | ||||||
| 			flex: 1; | 				&.suspended { | ||||||
|  | 					opacity: 0.5; | ||||||
|  | 				} | ||||||
| 
 | 
 | ||||||
| 			> .icon { | 				&.error { | ||||||
| 				margin-right: 6px; | 					color: var(--error); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				&.alive { | ||||||
|  | 					color: var(--success); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			> .pubSub { | ||||||
|  | 				margin-left: 8px; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			> .right { | ||||||
|  | 				margin-left: auto; | ||||||
|  | 				font-size: 0.9em; | ||||||
|  | 
 | ||||||
|  | 				> .latestStatus { | ||||||
|  | 					border: solid 1px var(--divider); | ||||||
|  | 					border-radius: 4px; | ||||||
|  | 					margin: 0 8px; | ||||||
|  | 					padding: 0 4px; | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -32,7 +32,7 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent } from 'vue'; | import { defineComponent } from 'vue'; | ||||||
| import MkPagination from '@client/components/ui/pagination.vue'; | import MkPagination from '@client/components/ui/pagination.vue'; | ||||||
| import { userPage, acct } from '../filters/user'; | import { userPage, acct } from '@client/filters/user'; | ||||||
| import * as os from '@client/os'; | import * as os from '@client/os'; | ||||||
| import * as symbols from '@client/symbols'; | import * as symbols from '@client/symbols'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -62,7 +62,7 @@ | |||||||
| 			<div class="_formLabel">{{ $ts.statistics }}</div> | 			<div class="_formLabel">{{ $ts.statistics }}</div> | ||||||
| 			<div class="_formPanel cmhjzshl"> | 			<div class="_formPanel cmhjzshl"> | ||||||
| 				<div class="selects"> | 				<div class="selects"> | ||||||
| 					<MkSelect v-model:value="chartSrc" style="margin: 0; flex: 1;"> | 					<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;"> | ||||||
| 						<option value="requests">{{ $ts._instanceCharts.requests }}</option> | 						<option value="requests">{{ $ts._instanceCharts.requests }}</option> | ||||||
| 						<option value="users">{{ $ts._instanceCharts.users }}</option> | 						<option value="users">{{ $ts._instanceCharts.users }}</option> | ||||||
| 						<option value="users-total">{{ $ts._instanceCharts.usersTotal }}</option> | 						<option value="users-total">{{ $ts._instanceCharts.usersTotal }}</option> | ||||||
| @@ -75,7 +75,7 @@ | |||||||
| 						<option value="drive-files">{{ $ts._instanceCharts.files }}</option> | 						<option value="drive-files">{{ $ts._instanceCharts.files }}</option> | ||||||
| 						<option value="drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option> | 						<option value="drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option> | ||||||
| 					</MkSelect> | 					</MkSelect> | ||||||
| 					<MkSelect v-model:value="chartSpan" style="margin: 0;"> | 					<MkSelect v-model="chartSpan" style="margin: 0;"> | ||||||
| 						<option value="hour">{{ $ts.perHour }}</option> | 						<option value="hour">{{ $ts.perHour }}</option> | ||||||
| 						<option value="day">{{ $ts.perDay }}</option> | 						<option value="day">{{ $ts.perDay }}</option> | ||||||
| 					</MkSelect> | 					</MkSelect> | ||||||
|   | |||||||
| @@ -3,19 +3,19 @@ | |||||||
| 	<div class="_section reports"> | 	<div class="_section reports"> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<div class="inputs" style="display: flex;"> | 			<div class="inputs" style="display: flex;"> | ||||||
| 				<MkSelect v-model:value="state" style="margin: 0; flex: 1;"> | 				<MkSelect v-model="state" style="margin: 0; flex: 1;"> | ||||||
| 					<template #label>{{ $ts.state }}</template> | 					<template #label>{{ $ts.state }}</template> | ||||||
| 					<option value="all">{{ $ts.all }}</option> | 					<option value="all">{{ $ts.all }}</option> | ||||||
| 					<option value="unresolved">{{ $ts.unresolved }}</option> | 					<option value="unresolved">{{ $ts.unresolved }}</option> | ||||||
| 					<option value="resolved">{{ $ts.resolved }}</option> | 					<option value="resolved">{{ $ts.resolved }}</option> | ||||||
| 				</MkSelect> | 				</MkSelect> | ||||||
| 				<MkSelect v-model:value="targetUserOrigin" style="margin: 0; flex: 1;"> | 				<MkSelect v-model="targetUserOrigin" style="margin: 0; flex: 1;"> | ||||||
| 					<template #label>{{ $ts.targetUserOrigin }}</template> | 					<template #label>{{ $ts.targetUserOrigin }}</template> | ||||||
| 					<option value="combined">{{ $ts.all }}</option> | 					<option value="combined">{{ $ts.all }}</option> | ||||||
| 					<option value="local">{{ $ts.local }}</option> | 					<option value="local">{{ $ts.local }}</option> | ||||||
| 					<option value="remote">{{ $ts.remote }}</option> | 					<option value="remote">{{ $ts.remote }}</option> | ||||||
| 				</MkSelect> | 				</MkSelect> | ||||||
| 				<MkSelect v-model:value="reporterOrigin" style="margin: 0; flex: 1;"> | 				<MkSelect v-model="reporterOrigin" style="margin: 0; flex: 1;"> | ||||||
| 					<template #label>{{ $ts.reporterOrigin }}</template> | 					<template #label>{{ $ts.reporterOrigin }}</template> | ||||||
| 					<option value="combined">{{ $ts.all }}</option> | 					<option value="combined">{{ $ts.all }}</option> | ||||||
| 					<option value="local">{{ $ts.local }}</option> | 					<option value="local">{{ $ts.local }}</option> | ||||||
| @@ -68,7 +68,7 @@ import MkButton from '@client/components/ui/button.vue'; | |||||||
| import MkInput from '@client/components/ui/input.vue'; | import MkInput from '@client/components/ui/input.vue'; | ||||||
| import MkSelect from '@client/components/ui/select.vue'; | import MkSelect from '@client/components/ui/select.vue'; | ||||||
| import MkPagination from '@client/components/ui/pagination.vue'; | import MkPagination from '@client/components/ui/pagination.vue'; | ||||||
| import { acct } from '../../filters/user'; | import { acct } from '@client/filters/user'; | ||||||
| import * as os from '@client/os'; | import * as os from '@client/os'; | ||||||
| import * as symbols from '@client/symbols'; | import * as symbols from '@client/symbols'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,11 +4,11 @@ | |||||||
| 	<section class="_card _gap ads" v-for="ad in ads"> | 	<section class="_card _gap ads" v-for="ad in ads"> | ||||||
| 		<div class="_content ad"> | 		<div class="_content ad"> | ||||||
| 			<MkAd v-if="ad.url" :specify="ad"/> | 			<MkAd v-if="ad.url" :specify="ad"/> | ||||||
| 			<MkInput v-model:value="ad.url" type="url"> | 			<MkInput v-model="ad.url" type="url"> | ||||||
| 				<span>URL</span> | 				<template #label>URL</template> | ||||||
| 			</MkInput> | 			</MkInput> | ||||||
| 			<MkInput v-model:value="ad.imageUrl"> | 			<MkInput v-model="ad.imageUrl"> | ||||||
| 				<span>{{ $ts.imageUrl }}</span> | 				<template #label>{{ $ts.imageUrl }}</template> | ||||||
| 			</MkInput> | 			</MkInput> | ||||||
| 			<div style="margin: 32px 0;"> | 			<div style="margin: 32px 0;"> | ||||||
| 				<MkRadio v-model="ad.place" value="square">square</MkRadio> | 				<MkRadio v-model="ad.place" value="square">square</MkRadio> | ||||||
| @@ -23,14 +23,14 @@ | |||||||
| 				<MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio> | 				<MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio> | ||||||
| 			</div> | 			</div> | ||||||
| 			--> | 			--> | ||||||
| 			<MkInput v-model:value="ad.ratio" type="number"> | 			<MkInput v-model="ad.ratio" type="number"> | ||||||
| 				<span>{{ $ts.ratio }}</span> | 				<template #label>{{ $ts.ratio }}</template> | ||||||
| 			</MkInput> | 			</MkInput> | ||||||
| 			<MkInput v-model:value="ad.expiresAt" type="date"> | 			<MkInput v-model="ad.expiresAt" type="date"> | ||||||
| 				<span>{{ $ts.expiration }}</span> | 				<template #label>{{ $ts.expiration }}</template> | ||||||
| 			</MkInput> | 			</MkInput> | ||||||
| 			<MkTextarea v-model:value="ad.memo"> | 			<MkTextarea v-model="ad.memo"> | ||||||
| 				<span>{{ $ts.memo }}</span> | 				<template #label>{{ $ts.memo }}</template> | ||||||
| 			</MkTextarea> | 			</MkTextarea> | ||||||
| 			<div class="buttons"> | 			<div class="buttons"> | ||||||
| 				<MkButton class="button" inline @click="save(ad)" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> | 				<MkButton class="button" inline @click="save(ad)" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> | ||||||
|   | |||||||
| @@ -3,14 +3,14 @@ | |||||||
| 	<MkButton @click="add()" primary style="margin: 0 auto 16px auto;"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> | 	<MkButton @click="add()" primary style="margin: 0 auto 16px auto;"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> | ||||||
| 	<section class="_card _gap announcements" v-for="announcement in announcements"> | 	<section class="_card _gap announcements" v-for="announcement in announcements"> | ||||||
| 		<div class="_content announcement"> | 		<div class="_content announcement"> | ||||||
| 			<MkInput v-model:value="announcement.title"> | 			<MkInput v-model="announcement.title"> | ||||||
| 				<span>{{ $ts.title }}</span> | 				<template #label>{{ $ts.title }}</template> | ||||||
| 			</MkInput> | 			</MkInput> | ||||||
| 			<MkTextarea v-model:value="announcement.text"> | 			<MkTextarea v-model="announcement.text"> | ||||||
| 				<span>{{ $ts.text }}</span> | 				<template #label>{{ $ts.text }}</template> | ||||||
| 			</MkTextarea> | 			</MkTextarea> | ||||||
| 			<MkInput v-model:value="announcement.imageUrl"> | 			<MkInput v-model="announcement.imageUrl"> | ||||||
| 				<span>{{ $ts.imageUrl }}</span> | 				<template #label>{{ $ts.imageUrl }}</template> | ||||||
| 			</MkInput> | 			</MkInput> | ||||||
| 			<p v-if="announcement.reads">{{ $t('nUsersRead', { n: announcement.reads }) }}</p> | 			<p v-if="announcement.reads">{{ $t('nUsersRead', { n: announcement.reads }) }}</p> | ||||||
| 			<div class="buttons"> | 			<div class="buttons"> | ||||||
|   | |||||||
| @@ -11,11 +11,15 @@ | |||||||
| 	<div class="_monolithic_"> | 	<div class="_monolithic_"> | ||||||
| 		<div class="yigymqpb _section"> | 		<div class="yigymqpb _section"> | ||||||
| 			<img :src="emoji.url" class="img"/> | 			<img :src="emoji.url" class="img"/> | ||||||
| 			<MkInput v-model:value="name"><span>{{ $ts.name }}</span></MkInput> | 			<MkInput v-model="name"> | ||||||
| 			<MkInput v-model:value="category" :datalist="categories"><span>{{ $ts.category }}</span></MkInput> | 				<template #label>{{ $ts.name }}</template> | ||||||
| 			<MkInput v-model:value="aliases"> | 			</MkInput> | ||||||
| 				<span>{{ $ts.tags }}</span> | 			<MkInput v-model="category" :datalist="categories"> | ||||||
| 				<template #desc>{{ $ts.setMultipleBySeparatingWithSpace }}</template> | 				<template #label>{{ $ts.category }}</template> | ||||||
|  | 			</MkInput> | ||||||
|  | 			<MkInput v-model="aliases"> | ||||||
|  | 				<template #label>{{ $ts.tags }}</template> | ||||||
|  | 				<template #caption>{{ $ts.setMultipleBySeparatingWithSpace }}</template> | ||||||
| 			</MkInput> | 			</MkInput> | ||||||
| 			<MkButton danger @click="del()"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</MkButton> | 			<MkButton danger @click="del()"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</MkButton> | ||||||
| 		</div> | 		</div> | ||||||
|   | |||||||
| @@ -7,7 +7,10 @@ | |||||||
|  |  | ||||||
| 	<div class="local" v-if="tab === 'local'"> | 	<div class="local" v-if="tab === 'local'"> | ||||||
| 		<MkButton primary @click="add" style="margin: var(--margin) auto;"><i class="fas fa-plus"></i> {{ $ts.addEmoji }}</MkButton> | 		<MkButton primary @click="add" style="margin: var(--margin) auto;"><i class="fas fa-plus"></i> {{ $ts.addEmoji }}</MkButton> | ||||||
| 		<MkInput v-model:value="query" :debounce="true" type="search" style="margin: var(--margin);"><template #icon><i class="fas fa-search"></i></template><span>{{ $ts.search }}</span></MkInput> | 		<MkInput v-model="query" :debounce="true" type="search" style="margin: var(--margin);"> | ||||||
|  | 			<template #prefix><i class="fas fa-search"></i></template> | ||||||
|  | 			<template #label>{{ $ts.search }}</template> | ||||||
|  | 		</MkInput> | ||||||
| 		<MkPagination :pagination="pagination" ref="emojis"> | 		<MkPagination :pagination="pagination" ref="emojis"> | ||||||
| 			<template #empty><span>{{ $ts.noCustomEmojis }}</span></template> | 			<template #empty><span>{{ $ts.noCustomEmojis }}</span></template> | ||||||
| 			<template #default="{items}"> | 			<template #default="{items}"> | ||||||
| @@ -25,8 +28,13 @@ | |||||||
| 	</div> | 	</div> | ||||||
|  |  | ||||||
| 	<div class="remote" v-else-if="tab === 'remote'"> | 	<div class="remote" v-else-if="tab === 'remote'"> | ||||||
| 		<MkInput v-model:value="queryRemote" :debounce="true" type="search" style="margin: var(--margin);"><template #icon><i class="fas fa-search"></i></template><span>{{ $ts.search }}</span></MkInput> | 		<MkInput v-model="queryRemote" :debounce="true" type="search" style="margin: var(--margin);"> | ||||||
| 		<MkInput v-model:value="host" :debounce="true" style="margin: var(--margin);"><span>{{ $ts.host }}</span></MkInput> | 			<template #prefix><i class="fas fa-search"></i></template> | ||||||
|  | 			<template #label>{{ $ts.search }}</template> | ||||||
|  | 		</MkInput> | ||||||
|  | 		<MkInput v-model="host" :debounce="true" style="margin: var(--margin);"> | ||||||
|  | 			<template #label>{{ $ts.host }}</template> | ||||||
|  | 		</MkInput> | ||||||
| 		<MkPagination :pagination="remotePagination" ref="remoteEmojis"> | 		<MkPagination :pagination="remotePagination" ref="remoteEmojis"> | ||||||
| 			<template #empty><span>{{ $ts.noCustomEmojis }}</span></template> | 			<template #empty><span>{{ $ts.noCustomEmojis }}</span></template> | ||||||
| 			<template #default="{items}"> | 			<template #default="{items}"> | ||||||
| @@ -138,7 +146,7 @@ export default defineComponent({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		remoteMenu(emoji, ev) { | 		remoteMenu(emoji, ev) { | ||||||
| 			os.modalMenu([{ | 			os.popupMenu([{ | ||||||
| 				type: 'label', | 				type: 'label', | ||||||
| 				text: ':' + emoji.name + ':', | 				text: ':' + emoji.name + ':', | ||||||
| 			}, { | 			}, { | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ | |||||||
| 		</div> | 		</div> | ||||||
| 		<div class="_section"> | 		<div class="_section"> | ||||||
| 			<div class="_content"> | 			<div class="_content"> | ||||||
| 				<MkSwitch @update:value="toggleIsSensitive" v-model:value="isSensitive">NSFW</MkSwitch> | 				<MkSwitch @update:modelValue="toggleIsSensitive" v-model="isSensitive">NSFW</MkSwitch> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div class="_section"> | 		<div class="_section"> | ||||||
|   | |||||||
| @@ -9,8 +9,8 @@ | |||||||
| 	<div class="_section lookup"> | 	<div class="_section lookup"> | ||||||
| 		<div class="_title"><i class="fas fa-search"></i> {{ $ts.lookup }}</div> | 		<div class="_title"><i class="fas fa-search"></i> {{ $ts.lookup }}</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<MkInput class="target" v-model:value="q" type="text" @enter="find()"> | 			<MkInput class="target" v-model="q" type="text" @enter="find()"> | ||||||
| 				<span>{{ $ts.fileIdOrUrl }}</span> | 				<template #label>{{ $ts.fileIdOrUrl }}</template> | ||||||
| 			</MkInput> | 			</MkInput> | ||||||
| 			<MkButton @click="find()" primary><i class="fas fa-search"></i> {{ $ts.lookup }}</MkButton> | 			<MkButton @click="find()" primary><i class="fas fa-search"></i> {{ $ts.lookup }}</MkButton> | ||||||
| 		</div> | 		</div> | ||||||
| @@ -19,19 +19,19 @@ | |||||||
| 	<div class="_section"> | 	<div class="_section"> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<div class="inputs" style="display: flex;"> | 			<div class="inputs" style="display: flex;"> | ||||||
| 				<MkSelect v-model:value="origin" style="margin: 0; flex: 1;"> | 				<MkSelect v-model="origin" style="margin: 0; flex: 1;"> | ||||||
| 					<template #label>{{ $ts.instance }}</template> | 					<template #label>{{ $ts.instance }}</template> | ||||||
| 					<option value="combined">{{ $ts.all }}</option> | 					<option value="combined">{{ $ts.all }}</option> | ||||||
| 					<option value="local">{{ $ts.local }}</option> | 					<option value="local">{{ $ts.local }}</option> | ||||||
| 					<option value="remote">{{ $ts.remote }}</option> | 					<option value="remote">{{ $ts.remote }}</option> | ||||||
| 				</MkSelect> | 				</MkSelect> | ||||||
| 				<MkInput v-model:value="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params().origin === 'local'"> | 				<MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params().origin === 'local'"> | ||||||
| 					<span>{{ $ts.host }}</span> | 					<template #label>{{ $ts.host }}</template> | ||||||
| 				</MkInput> | 				</MkInput> | ||||||
| 			</div> | 			</div> | ||||||
| 			<div class="inputs" style="display: flex; padding-top: 1.2em;"> | 			<div class="inputs" style="display: flex; padding-top: 1.2em;"> | ||||||
| 				<MkInput v-model:value="type" :debounce="true" type="search" style="margin: 0; flex: 1;"> | 				<MkInput v-model="type" :debounce="true" type="search" style="margin: 0; flex: 1;"> | ||||||
| 					<span>{{ $ts.type }}</span> | 					<template #label>{{ $ts.type }}</template> | ||||||
| 				</MkInput> | 				</MkInput> | ||||||
| 			</div> | 			</div> | ||||||
| 			<MkPagination :pagination="pagination" #default="{items}" class="urempief" ref="files"> | 			<MkPagination :pagination="pagination" #default="{items}" class="urempief" ref="files"> | ||||||
|   | |||||||
| @@ -100,7 +100,7 @@ export default defineComponent({ | |||||||
| 				case 'overview': return defineAsyncComponent(() => import('./overview.vue')); | 				case 'overview': return defineAsyncComponent(() => import('./overview.vue')); | ||||||
| 				case 'users': return defineAsyncComponent(() => import('./users.vue')); | 				case 'users': return defineAsyncComponent(() => import('./users.vue')); | ||||||
| 				case 'emojis': return defineAsyncComponent(() => import('./emojis.vue')); | 				case 'emojis': return defineAsyncComponent(() => import('./emojis.vue')); | ||||||
| 				case 'federation': return defineAsyncComponent(() => import('./federation.vue')); | 				case 'federation': return defineAsyncComponent(() => import('../federation.vue')); | ||||||
| 				case 'queue': return defineAsyncComponent(() => import('./queue.vue')); | 				case 'queue': return defineAsyncComponent(() => import('./queue.vue')); | ||||||
| 				case 'files': return defineAsyncComponent(() => import('./files.vue')); | 				case 'files': return defineAsyncComponent(() => import('./files.vue')); | ||||||
| 				case 'announcements': return defineAsyncComponent(() => import('./announcements.vue')); | 				case 'announcements': return defineAsyncComponent(() => import('./announcements.vue')); | ||||||
| @@ -167,7 +167,7 @@ export default defineComponent({ | |||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		const lookup = (ev) => { | 		const lookup = (ev) => { | ||||||
| 			os.modalMenu([{ | 			os.popupMenu([{ | ||||||
| 				text: i18n.locale.user, | 				text: i18n.locale.user, | ||||||
| 				icon: 'fas fa-user', | 				icon: 'fas fa-user', | ||||||
| 				action: () => { | 				action: () => { | ||||||
|   | |||||||
| @@ -77,7 +77,7 @@ | |||||||
| 			<div class="header"> | 			<div class="header"> | ||||||
| 				<span class="label">{{ $ts.charts }}</span> | 				<span class="label">{{ $ts.charts }}</span> | ||||||
| 				<div class="selects"> | 				<div class="selects"> | ||||||
| 					<MkSelect v-model:value="chartSrc" style="margin: 0; flex: 1;"> | 					<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;"> | ||||||
| 						<option value="requests">{{ $ts._instanceCharts.requests }}</option> | 						<option value="requests">{{ $ts._instanceCharts.requests }}</option> | ||||||
| 						<option value="users">{{ $ts._instanceCharts.users }}</option> | 						<option value="users">{{ $ts._instanceCharts.users }}</option> | ||||||
| 						<option value="users-total">{{ $ts._instanceCharts.usersTotal }}</option> | 						<option value="users-total">{{ $ts._instanceCharts.usersTotal }}</option> | ||||||
| @@ -90,7 +90,7 @@ | |||||||
| 						<option value="drive-files">{{ $ts._instanceCharts.files }}</option> | 						<option value="drive-files">{{ $ts._instanceCharts.files }}</option> | ||||||
| 						<option value="drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option> | 						<option value="drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option> | ||||||
| 					</MkSelect> | 					</MkSelect> | ||||||
| 					<MkSelect v-model:value="chartSpan" style="margin: 0;"> | 					<MkSelect v-model="chartSpan" style="margin: 0;"> | ||||||
| 						<option value="hour">{{ $ts.perHour }}</option> | 						<option value="hour">{{ $ts.perHour }}</option> | ||||||
| 						<option value="day">{{ $ts.perDay }}</option> | 						<option value="day">{{ $ts.perDay }}</option> | ||||||
| 					</MkSelect> | 					</MkSelect> | ||||||
| @@ -102,8 +102,8 @@ | |||||||
| 		</div> | 		</div> | ||||||
| 		<div class="operations section"> | 		<div class="operations section"> | ||||||
| 			<span class="label">{{ $ts.operations }}</span> | 			<span class="label">{{ $ts.operations }}</span> | ||||||
| 			<MkSwitch v-model:value="isSuspended" class="switch">{{ $ts.stopActivityDelivery }}</MkSwitch> | 			<MkSwitch v-model="isSuspended" class="switch">{{ $ts.stopActivityDelivery }}</MkSwitch> | ||||||
| 			<MkSwitch :value="isBlocked" class="switch" @update:value="changeBlock">{{ $ts.blockThisInstance }}</MkSwitch> | 			<MkSwitch :model-value="isBlocked" class="switch" @update:modelValue="changeBlock">{{ $ts.blockThisInstance }}</MkSwitch> | ||||||
| 			<details> | 			<details> | ||||||
| 				<summary>{{ $ts.deleteAllFiles }}</summary> | 				<summary>{{ $ts.deleteAllFiles }}</summary> | ||||||
| 				<MkButton @click="deleteAllFiles()" style="margin: 0.5em 0 0.5em 0;"><i class="fas fa-trash-alt"></i> {{ $ts.deleteAllFiles }}</MkButton> | 				<MkButton @click="deleteAllFiles()" style="margin: 0.5em 0 0.5em 0;"><i class="fas fa-trash-alt"></i> {{ $ts.deleteAllFiles }}</MkButton> | ||||||
| @@ -131,8 +131,8 @@ import MkSelect from '@client/components/ui/select.vue'; | |||||||
| import MkButton from '@client/components/ui/button.vue'; | import MkButton from '@client/components/ui/button.vue'; | ||||||
| import MkSwitch from '@client/components/ui/switch.vue'; | import MkSwitch from '@client/components/ui/switch.vue'; | ||||||
| import MkInfo from '@client/components/ui/info.vue'; | import MkInfo from '@client/components/ui/info.vue'; | ||||||
| import bytes from '../../filters/bytes'; | import bytes from '@client/filters/bytes'; | ||||||
| import number from '../../filters/number'; | import number from '@client/filters/number'; | ||||||
| import * as os from '@client/os'; | import * as os from '@client/os'; | ||||||
|  |  | ||||||
| const chartLimit = 90; | const chartLimit = 90; | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| <template> | <template> | ||||||
| <div class="_section"> | <div class="_section"> | ||||||
| 	<div class="_inputs"> | 	<div class="_inputs"> | ||||||
| 		<MkInput v-model:value="logDomain" :debounce="true"> | 		<MkInput v-model="logDomain" :debounce="true"> | ||||||
| 			<span>{{ $ts.domain }}</span> | 			<template #label>{{ $ts.domain }}</template> | ||||||
| 		</MkInput> | 		</MkInput> | ||||||
| 		<MkSelect v-model:value="logLevel"> | 		<MkSelect v-model="logLevel"> | ||||||
| 			<template #label>Level</template> | 			<template #label>Level</template> | ||||||
| 			<option value="all">All</option> | 			<option value="all">All</option> | ||||||
| 			<option value="info">Info</option> | 			<option value="info">Info</option> | ||||||
|   | |||||||
| @@ -60,8 +60,8 @@ import MkContainer from '@client/components/ui/container.vue'; | |||||||
| import MkFolder from '@client/components/ui/folder.vue'; | import MkFolder from '@client/components/ui/folder.vue'; | ||||||
| import MkwFederation from '../../widgets/federation.vue'; | import MkwFederation from '../../widgets/federation.vue'; | ||||||
| import { version, url } from '@client/config'; | import { version, url } from '@client/config'; | ||||||
| import bytes from '../../filters/bytes'; | import bytes from '@client/filters/bytes'; | ||||||
| import number from '../../filters/number'; | import number from '@client/filters/number'; | ||||||
| import MkInstanceInfo from './instance.vue'; | import MkInstanceInfo from './instance.vue'; | ||||||
|  |  | ||||||
| const alpha = (hex, a) => { | const alpha = (hex, a) => { | ||||||
| @@ -90,7 +90,7 @@ export default defineComponent({ | |||||||
| 			stats: null, | 			stats: null, | ||||||
| 			serverInfo: null, | 			serverInfo: null, | ||||||
| 			connection: null, | 			connection: null, | ||||||
| 			queueConnection: os.stream.useChannel('queueStats'), | 			queueConnection: markRaw(os.stream.useChannel('queueStats')), | ||||||
| 			memUsage: 0, | 			memUsage: 0, | ||||||
| 			chartCpuMem: null, | 			chartCpuMem: null, | ||||||
| 			chartNet: null, | 			chartNet: null, | ||||||
| @@ -121,7 +121,7 @@ export default defineComponent({ | |||||||
| 		os.api('admin/server-info', {}).then(res => { | 		os.api('admin/server-info', {}).then(res => { | ||||||
| 			this.serverInfo = res; | 			this.serverInfo = res; | ||||||
|  |  | ||||||
| 			this.connection = os.stream.useChannel('serverStats'); | 			this.connection = markRaw(os.stream.useChannel('serverStats')); | ||||||
| 			this.connection.on('stats', this.onStats); | 			this.connection.on('stats', this.onStats); | ||||||
| 			this.connection.on('statsLog', this.onStatsLog); | 			this.connection.on('statsLog', this.onStatsLog); | ||||||
| 			this.connection.send('requestLog', { | 			this.connection.send('requestLog', { | ||||||
|   | |||||||
| @@ -62,8 +62,8 @@ import MkInput from '@client/components/ui/input.vue'; | |||||||
| import MkContainer from '@client/components/ui/container.vue'; | import MkContainer from '@client/components/ui/container.vue'; | ||||||
| import MkFolder from '@client/components/ui/folder.vue'; | import MkFolder from '@client/components/ui/folder.vue'; | ||||||
| import { version, url } from '@client/config'; | import { version, url } from '@client/config'; | ||||||
| import bytes from '../../filters/bytes'; | import bytes from '@client/filters/bytes'; | ||||||
| import number from '../../filters/number'; | import number from '@client/filters/number'; | ||||||
| import MkInstanceInfo from './instance.vue'; | import MkInstanceInfo from './instance.vue'; | ||||||
| import XMetrics from './metrics.vue'; | import XMetrics from './metrics.vue'; | ||||||
| import * as os from '@client/os'; | import * as os from '@client/os'; | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent, markRaw } from 'vue'; | import { defineComponent, markRaw } from 'vue'; | ||||||
| import Chart from 'chart.js'; | import Chart from 'chart.js'; | ||||||
| import number from '../../filters/number'; | import number from '@client/filters/number'; | ||||||
|  |  | ||||||
| const alpha = (hex, a) => { | const alpha = (hex, a) => { | ||||||
| 	const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!; | 	const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!; | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user