Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d002f67140 | ||
|
|
da3447765b | ||
|
|
cbf5663179 | ||
|
|
b217fba235 | ||
|
|
7f7e6d5aba | ||
|
|
87c5a9d9a6 | ||
|
|
8ca1fe3f0a | ||
|
|
763ae8f1a6 | ||
|
|
c65256d02b | ||
|
|
bd2ac515d1 | ||
|
|
681f372889 | ||
|
|
c2eec272e6 | ||
|
|
bd720491a9 | ||
|
|
a408226509 | ||
|
|
c015e99e6e | ||
|
|
de47a17be7 | ||
|
|
d38fc490ad | ||
|
|
662167e792 | ||
|
|
36c91f03d9 | ||
|
|
33ccee26b5 | ||
|
|
ed5cb991e3 | ||
|
|
bea84ec2bf | ||
|
|
08c176e549 | ||
|
|
810ed50976 | ||
|
|
2684541693 | ||
|
|
a5b12bac54 | ||
|
|
fea1b06e43 |
22
CHANGELOG.md
22
CHANGELOG.md
@@ -5,6 +5,28 @@ If you encounter any problems with updating, please try the following:
|
|||||||
1. `npm run clean` or `npm run cleanall`
|
1. `npm run clean` or `npm run cleanall`
|
||||||
2. Retry update (Don't forget `npm i`)
|
2. Retry update (Don't forget `npm i`)
|
||||||
|
|
||||||
|
10.98.0
|
||||||
|
----------
|
||||||
|
* ドライブのファイルダウンロード時に元のファイル名を尊重するように
|
||||||
|
* ドライブで画像以外のファイルを分かりやすく表示するように
|
||||||
|
* TwemojiのCDNを変更
|
||||||
|
* モバイルで通知の設定がない問題を修正
|
||||||
|
* デザインの調整
|
||||||
|
|
||||||
|
10.97.2
|
||||||
|
----------
|
||||||
|
* ビルド時に警告が出ないように修正
|
||||||
|
|
||||||
|
10.97.1
|
||||||
|
----------
|
||||||
|
* デザインの調整
|
||||||
|
|
||||||
|
10.97.0
|
||||||
|
----------
|
||||||
|
* リアクションに絵文字やカスタム絵文字を使えるように
|
||||||
|
* 不明なリアクションのフォールバックに star を使えるように
|
||||||
|
* デザインの調整
|
||||||
|
|
||||||
10.96.0
|
10.96.0
|
||||||
----------
|
----------
|
||||||
* 連合ユーザーの投稿に対してActivityPubオブジェクトを要求されたら元のインスタンスにリダイレクトするように
|
* 連合ユーザーの投稿に対してActivityPubオブジェクトを要求されたら元のインスタンスにリダイレクトするように
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
meta:
|
meta:
|
||||||
lang: "Čeština"
|
lang: "Čeština"
|
||||||
common:
|
common:
|
||||||
misskey: "⭐ ve fediverse"
|
misskey: "⭐ ve fedivesmíru"
|
||||||
about-title: "⭐ ve fediverse."
|
about-title: "⭐ ve fedivesmíru."
|
||||||
about: "Děkujeme, že jste našli Misskey. Misskey je <b>decentralizovaná mikroblogovací platforma</b> zrozená na Zemi. Neboť existuje ve Fediverse (vesmíru, kde jsou organizovány různé sociální sítě), je vzájemně propojena s jinými sociálními sítěmi. Co takhle si chvilku odpočinout od ruchu a shonu města a ponořit se do nového internetu?"
|
about: "Děkujeme, že jste našli Misskey. Misskey je <b>decentralizovaná mikroblogovací platforma</b> zrozená na Zemi. Neboť existuje ve fedivesmíru (vesmíru, kde jsou organizovány různé sociální sítě), je vzájemně propojena s jinými sociálními sítěmi. Co takhle si chvilku odpočinout od ruchu a shonu města a ponořit se do nového internetu?"
|
||||||
intro:
|
intro:
|
||||||
title: "Co je Misskey?"
|
title: "Co je Misskey?"
|
||||||
about: "Misskey je open-source <b>decentralizovaný mikroblogovací software</b>. Má sofistikované, zcela přizpůsobitelné uživatelské rozhraní, různé způsoby reagování na příspěvky, bezplatné úložiště souborů nabízející integrovaný management system, a další pokročilé vlastnosti. Misskey je navíc připojeno k systému sítí zvanému „fediverse“, který nám dovoluje komunikovat s uživateli na jiných sociálních sítí. Pokud například něco napíšete, nebude to posláno pouze uživatelů Misskey, ale také lidem na sítích Mastodon a Pleroma. Jen si představte, že planeta posílá jiné planetě rádiový signál, aby s ní komunikovala."
|
about: "Misskey je open-source <b>decentralizovaný mikroblogovací software</b>. Má sofistikované, zcela přizpůsobitelné uživatelské rozhraní, různé způsoby reagování na příspěvky, bezplatné úložiště souborů nabízející integrovaný management system, a další pokročilé vlastnosti. Misskey je navíc připojeno k systému sítí zvanému „fedivesmír“ nebo „fediverse“, který nám dovoluje komunikovat s uživateli na jiných sociálních sítí. Pokud například něco napíšete, nebude to posláno pouze uživatelů Misskey, ale také lidem na sítích Mastodon a Pleroma. Jen si představte, že planeta posílá jiné planetě rádiový signál, aby s ní komunikovala."
|
||||||
features: "Vlastnosti"
|
features: "Vlastnosti"
|
||||||
rich-contents: "Příspěvky"
|
rich-contents: "Příspěvky"
|
||||||
rich-contents-desc: "Pouze napište svoje nápady, žhavá témata a cokoliv, co chcete sdílet. Můžete ozdobit svá slova, připojit vaše oblíbené obrázky, posílat soubory včetně videí či vytvořit hlasování – to je jen několik věcí, co můžete dělat s Misskey!"
|
rich-contents-desc: "Pouze napište svoje nápady, žhavá témata a cokoliv, co chcete sdílet. Můžete ozdobit svá slova, připojit vaše oblíbené obrázky, posílat soubory včetně videí či vytvořit hlasování – to je jen několik věcí, co můžete dělat s Misskey!"
|
||||||
@@ -300,7 +300,7 @@ common/views/pages/explore.vue:
|
|||||||
recently-updated-users: "Nedávno aktívni uživatelé"
|
recently-updated-users: "Nedávno aktívni uživatelé"
|
||||||
recently-registered-users: "Nedávno registrovaní uživatelé"
|
recently-registered-users: "Nedávno registrovaní uživatelé"
|
||||||
popular-tags: "Populární tagy"
|
popular-tags: "Populární tagy"
|
||||||
federated: "Z fediverse"
|
federated: "Z fedivesmíru"
|
||||||
explore: "Prozkoumat {host}"
|
explore: "Prozkoumat {host}"
|
||||||
common/views/components/url-preview.vue:
|
common/views/components/url-preview.vue:
|
||||||
enable-player: "Otevřít v přehrávači"
|
enable-player: "Otevřít v přehrávači"
|
||||||
@@ -836,7 +836,7 @@ admin/views/index.vue:
|
|||||||
emoji: "Emoji"
|
emoji: "Emoji"
|
||||||
moderators: "Moderátoři"
|
moderators: "Moderátoři"
|
||||||
users: "Uživatelé"
|
users: "Uživatelé"
|
||||||
federation: "Z fediversu"
|
federation: "Federovaná"
|
||||||
announcements: "Oznámení"
|
announcements: "Oznámení"
|
||||||
hashtags: "Hashtagy"
|
hashtags: "Hashtagy"
|
||||||
queue: "Fronta úloh"
|
queue: "Fronta úloh"
|
||||||
@@ -848,7 +848,7 @@ admin/views/dashboard.vue:
|
|||||||
drive: "Disk"
|
drive: "Disk"
|
||||||
instances: "Instance"
|
instances: "Instance"
|
||||||
this-instance: "Tato instance"
|
this-instance: "Tato instance"
|
||||||
federated: "Z fediversu"
|
federated: "Federovaná"
|
||||||
admin/views/abuse.vue:
|
admin/views/abuse.vue:
|
||||||
details: "Popis"
|
details: "Popis"
|
||||||
remove-report: "Odstranit"
|
remove-report: "Odstranit"
|
||||||
@@ -995,14 +995,15 @@ admin/views/announcements.vue:
|
|||||||
admin/views/hashtags.vue:
|
admin/views/hashtags.vue:
|
||||||
hided-tags: "Skryté tagy"
|
hided-tags: "Skryté tagy"
|
||||||
admin/views/federation.vue:
|
admin/views/federation.vue:
|
||||||
federation: "Z fediversu"
|
instance: "Instance"
|
||||||
host: "Hostitel"
|
host: "Hostitel"
|
||||||
notes: "Poznámky"
|
notes: "Poznámky"
|
||||||
users: "Uživatelé"
|
users: "Uživatelé"
|
||||||
|
caught-at: "Vytvořeno"
|
||||||
status: "Status"
|
status: "Status"
|
||||||
latest-request-received-at: "Poslední požadavek přijat"
|
latest-request-received-at: "Poslední požadavek přijat"
|
||||||
block: "Blokován"
|
block: "Blokován"
|
||||||
instances: "Instance"
|
instances: "Federovaná"
|
||||||
states:
|
states:
|
||||||
all: "Všechny"
|
all: "Všechny"
|
||||||
blocked: "Blokován"
|
blocked: "Blokován"
|
||||||
|
|||||||
@@ -1113,6 +1113,7 @@ admin/views/instance.vue:
|
|||||||
disable-local-timeline: "Disable the Local Timeline"
|
disable-local-timeline: "Disable the Local Timeline"
|
||||||
disable-global-timeline: "Disable global timeline"
|
disable-global-timeline: "Disable global timeline"
|
||||||
disabling-timelines-info: "Even if you disable these timelines, the administrator as well as moderators can use them continually."
|
disabling-timelines-info: "Even if you disable these timelines, the administrator as well as moderators can use them continually."
|
||||||
|
enable-emoji-reaction: "Enable pictograms for reactions"
|
||||||
invite: "Invite"
|
invite: "Invite"
|
||||||
save: "Save"
|
save: "Save"
|
||||||
saved: "Saved"
|
saved: "Saved"
|
||||||
@@ -1275,12 +1276,13 @@ admin/views/announcements.vue:
|
|||||||
admin/views/hashtags.vue:
|
admin/views/hashtags.vue:
|
||||||
hided-tags: "Hidden Tags"
|
hided-tags: "Hidden Tags"
|
||||||
admin/views/federation.vue:
|
admin/views/federation.vue:
|
||||||
federation: "Federation"
|
instance: "Instance"
|
||||||
host: "Host"
|
host: "Host"
|
||||||
notes: "Notes"
|
notes: "Notes"
|
||||||
users: "Users"
|
users: "Users"
|
||||||
following: "Following"
|
following: "Following"
|
||||||
followers: "Followers"
|
followers: "Followers"
|
||||||
|
caught-at: "Created at"
|
||||||
status: "Statuses"
|
status: "Statuses"
|
||||||
latest-request-sent-at: "Time of last request sent"
|
latest-request-sent-at: "Time of last request sent"
|
||||||
latest-request-received-at: "Last request received at"
|
latest-request-received-at: "Last request received at"
|
||||||
@@ -1289,7 +1291,7 @@ admin/views/federation.vue:
|
|||||||
block: "Block"
|
block: "Block"
|
||||||
marked-as-closed: "Marked as closed"
|
marked-as-closed: "Marked as closed"
|
||||||
lookup: "Look up"
|
lookup: "Look up"
|
||||||
instances: "Instances"
|
instances: "Federated"
|
||||||
instance-not-registered: "The instance has not been discovered"
|
instance-not-registered: "The instance has not been discovered"
|
||||||
sort: "Sort by"
|
sort: "Sort by"
|
||||||
sorts:
|
sorts:
|
||||||
|
|||||||
@@ -815,11 +815,11 @@ admin/views/announcements.vue:
|
|||||||
remove: "eliminar"
|
remove: "eliminar"
|
||||||
add: "Agregar"
|
add: "Agregar"
|
||||||
admin/views/federation.vue:
|
admin/views/federation.vue:
|
||||||
|
instance: "Instancia"
|
||||||
host: "Host"
|
host: "Host"
|
||||||
following: "Siguiendo"
|
following: "Siguiendo"
|
||||||
status: "Estado"
|
status: "Estado"
|
||||||
block: "Bloquear"
|
block: "Bloquear"
|
||||||
instances: "Instancia"
|
|
||||||
states:
|
states:
|
||||||
all: "Todo"
|
all: "Todo"
|
||||||
blocked: "Bloquear"
|
blocked: "Bloquear"
|
||||||
|
|||||||
@@ -1142,12 +1142,13 @@ admin/views/announcements.vue:
|
|||||||
admin/views/hashtags.vue:
|
admin/views/hashtags.vue:
|
||||||
hided-tags: "Tags cachés"
|
hided-tags: "Tags cachés"
|
||||||
admin/views/federation.vue:
|
admin/views/federation.vue:
|
||||||
federation: "Fédération"
|
instance: "Instance"
|
||||||
host: "Hôte"
|
host: "Hôte"
|
||||||
notes: "Notes"
|
notes: "Notes"
|
||||||
users: "Utilisateur·rice·s"
|
users: "Utilisateur·rice·s"
|
||||||
following: "Abonnements"
|
following: "Abonnements"
|
||||||
followers: "Abonné·e·s"
|
followers: "Abonné·e·s"
|
||||||
|
caught-at: "Créé le"
|
||||||
status: "Statuts"
|
status: "Statuts"
|
||||||
latest-request-sent-at: "Dernière requête envoyée"
|
latest-request-sent-at: "Dernière requête envoyée"
|
||||||
latest-request-received-at: "Dernière requête reçue"
|
latest-request-received-at: "Dernière requête reçue"
|
||||||
@@ -1155,7 +1156,7 @@ admin/views/federation.vue:
|
|||||||
block: "Bloquer"
|
block: "Bloquer"
|
||||||
marked-as-closed: "Marquées comme fermées"
|
marked-as-closed: "Marquées comme fermées"
|
||||||
lookup: "Recherche"
|
lookup: "Recherche"
|
||||||
instances: "Instances"
|
instances: "Fédérées"
|
||||||
sort: "Trier par"
|
sort: "Trier par"
|
||||||
sorts:
|
sorts:
|
||||||
caughtAtAsc: "Date d’inscription (Ascendant)"
|
caughtAtAsc: "Date d’inscription (Ascendant)"
|
||||||
|
|||||||
@@ -925,12 +925,6 @@ desktop/views/input-dialog.vue:
|
|||||||
cancel: "キャンセル"
|
cancel: "キャンセル"
|
||||||
ok: "決定"
|
ok: "決定"
|
||||||
|
|
||||||
desktop/views/components/messaging-room-window.vue:
|
|
||||||
title: "メッセージ:"
|
|
||||||
|
|
||||||
desktop/views/components/messaging-window.vue:
|
|
||||||
title: "メッセージ"
|
|
||||||
|
|
||||||
desktop/views/components/note-detail.vue:
|
desktop/views/components/note-detail.vue:
|
||||||
private: "この投稿は非公開です"
|
private: "この投稿は非公開です"
|
||||||
deleted: "この投稿は削除されました"
|
deleted: "この投稿は削除されました"
|
||||||
@@ -1100,7 +1094,7 @@ desktop/views/components/timeline.vue:
|
|||||||
hybrid: "ソーシャル"
|
hybrid: "ソーシャル"
|
||||||
global: "グローバル"
|
global: "グローバル"
|
||||||
mentions: "あなた宛て"
|
mentions: "あなた宛て"
|
||||||
messages: "メッセージ"
|
messages: "ダイレクト投稿"
|
||||||
list: "リスト"
|
list: "リスト"
|
||||||
hashtag: "ハッシュタグ"
|
hashtag: "ハッシュタグ"
|
||||||
add-tag-timeline: "ハッシュタグを追加"
|
add-tag-timeline: "ハッシュタグを追加"
|
||||||
@@ -1238,6 +1232,8 @@ admin/views/instance.vue:
|
|||||||
disable-local-timeline: "ローカルタイムラインを無効にする"
|
disable-local-timeline: "ローカルタイムラインを無効にする"
|
||||||
disable-global-timeline: "グローバルタイムラインを無効にする"
|
disable-global-timeline: "グローバルタイムラインを無効にする"
|
||||||
disabling-timelines-info: "これらのタイムラインを無効にしても、管理者およびモデレーターは引き続き利用できます。"
|
disabling-timelines-info: "これらのタイムラインを無効にしても、管理者およびモデレーターは引き続き利用できます。"
|
||||||
|
enable-emoji-reaction: "リアクションに絵文字を使えるようにする"
|
||||||
|
use-star-for-reaction-fallback: "不明なリアクションのフォールバックに star を使う"
|
||||||
invite: "招待"
|
invite: "招待"
|
||||||
save: "保存"
|
save: "保存"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
@@ -1533,9 +1529,6 @@ desktop/views/pages/user/user.timeline.vue:
|
|||||||
with-media: "メディア"
|
with-media: "メディア"
|
||||||
my-posts: "私の投稿"
|
my-posts: "私の投稿"
|
||||||
|
|
||||||
desktop/views/widgets/messaging.vue:
|
|
||||||
title: "メッセージ"
|
|
||||||
|
|
||||||
desktop/views/widgets/notifications.vue:
|
desktop/views/widgets/notifications.vue:
|
||||||
title: "通知"
|
title: "通知"
|
||||||
|
|
||||||
@@ -1683,7 +1676,7 @@ mobile/views/pages/home.vue:
|
|||||||
hybrid: "ソーシャル"
|
hybrid: "ソーシャル"
|
||||||
global: "グローバル"
|
global: "グローバル"
|
||||||
mentions: "あなた宛て"
|
mentions: "あなた宛て"
|
||||||
messages: "メッセージ"
|
messages: "ダイレクト投稿"
|
||||||
|
|
||||||
mobile/views/pages/tag.vue:
|
mobile/views/pages/tag.vue:
|
||||||
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
|
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
|
||||||
|
|||||||
@@ -988,7 +988,7 @@ admin/views/announcements.vue:
|
|||||||
add: "増やす"
|
add: "増やす"
|
||||||
saved: "保存したで!"
|
saved: "保存したで!"
|
||||||
admin/views/federation.vue:
|
admin/views/federation.vue:
|
||||||
federation: "連合"
|
instance: "インスタンス"
|
||||||
host: "ホスト"
|
host: "ホスト"
|
||||||
notes: "投稿"
|
notes: "投稿"
|
||||||
users: "ユーザー"
|
users: "ユーザー"
|
||||||
@@ -997,7 +997,7 @@ admin/views/federation.vue:
|
|||||||
status: "ステータス"
|
status: "ステータス"
|
||||||
block: "ブロック"
|
block: "ブロック"
|
||||||
lookup: "照会"
|
lookup: "照会"
|
||||||
instances: "インスタンス"
|
instances: "連合"
|
||||||
states:
|
states:
|
||||||
all: "すべて"
|
all: "すべて"
|
||||||
blocked: "ブロック"
|
blocked: "ブロック"
|
||||||
|
|||||||
@@ -1275,12 +1275,13 @@ admin/views/announcements.vue:
|
|||||||
admin/views/hashtags.vue:
|
admin/views/hashtags.vue:
|
||||||
hided-tags: "Hidden Tags"
|
hided-tags: "Hidden Tags"
|
||||||
admin/views/federation.vue:
|
admin/views/federation.vue:
|
||||||
federation: "연합"
|
instance: "인스턴스"
|
||||||
host: "호스트"
|
host: "호스트"
|
||||||
notes: "글"
|
notes: "글"
|
||||||
users: "사용자"
|
users: "사용자"
|
||||||
following: "팔로우 중"
|
following: "팔로우 중"
|
||||||
followers: "팔로워"
|
followers: "팔로워"
|
||||||
|
caught-at: "등록 날짜"
|
||||||
status: "상태"
|
status: "상태"
|
||||||
latest-request-sent-at: "마지막으로 요청을 전송한 시간"
|
latest-request-sent-at: "마지막으로 요청을 전송한 시간"
|
||||||
latest-request-received-at: "마지막으로 요청을 받은 시간"
|
latest-request-received-at: "마지막으로 요청을 받은 시간"
|
||||||
@@ -1289,7 +1290,7 @@ admin/views/federation.vue:
|
|||||||
block: "차단"
|
block: "차단"
|
||||||
marked-as-closed: "폐쇄된 것으로 표시"
|
marked-as-closed: "폐쇄된 것으로 표시"
|
||||||
lookup: "조회"
|
lookup: "조회"
|
||||||
instances: "인스턴스"
|
instances: "연합"
|
||||||
instance-not-registered: "해당 인스턴스가 등록되어 있지 않습니다"
|
instance-not-registered: "해당 인스턴스가 등록되어 있지 않습니다"
|
||||||
sort: "정렬"
|
sort: "정렬"
|
||||||
sorts:
|
sorts:
|
||||||
|
|||||||
@@ -124,10 +124,12 @@ common:
|
|||||||
fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół"
|
fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół"
|
||||||
note-visibility: "Widoczność wpisów"
|
note-visibility: "Widoczność wpisów"
|
||||||
web-search-engine: "Wyszukiwarka internetowa"
|
web-search-engine: "Wyszukiwarka internetowa"
|
||||||
|
line-width: "Szerokości linii"
|
||||||
line-width-thin: "Cienka"
|
line-width-thin: "Cienka"
|
||||||
line-width-normal: "Normalna"
|
line-width-normal: "Normalna"
|
||||||
line-width-thick: "Gruba"
|
line-width-thick: "Gruba"
|
||||||
font-size: "Rozmiar tekstu"
|
font-size: "Rozmiar tekstu"
|
||||||
|
font-size-x-small: "Małe"
|
||||||
font-size-medium: "Normalna"
|
font-size-medium: "Normalna"
|
||||||
font-size-large: "Trochę duży"
|
font-size-large: "Trochę duży"
|
||||||
font-size-x-large: "Duży"
|
font-size-x-large: "Duży"
|
||||||
@@ -143,6 +145,10 @@ common:
|
|||||||
wallpaper: "Tapeta"
|
wallpaper: "Tapeta"
|
||||||
choose-wallpaper: "Wybierz tapetę"
|
choose-wallpaper: "Wybierz tapetę"
|
||||||
timeline: "Oś czasu"
|
timeline: "Oś czasu"
|
||||||
|
sound: "Dźwięk"
|
||||||
|
test: "Test"
|
||||||
|
update: "Aktualizacja Misskey"
|
||||||
|
version: "Wersja:"
|
||||||
navbar-position-left: "Z lewej"
|
navbar-position-left: "Z lewej"
|
||||||
search: "Szukaj"
|
search: "Szukaj"
|
||||||
delete: "Usuń"
|
delete: "Usuń"
|
||||||
@@ -941,13 +947,14 @@ admin/views/announcements.vue:
|
|||||||
are-you-sure: "Usunąć \"$1\"?"
|
are-you-sure: "Usunąć \"$1\"?"
|
||||||
removed: "Usunięto"
|
removed: "Usunięto"
|
||||||
admin/views/federation.vue:
|
admin/views/federation.vue:
|
||||||
|
instance: "Instancja"
|
||||||
notes: "Wpis"
|
notes: "Wpis"
|
||||||
users: "Użytkownicy"
|
users: "Użytkownicy"
|
||||||
following: "Śledzisz"
|
following: "Śledzisz"
|
||||||
followers: "Śledzący"
|
followers: "Śledzący"
|
||||||
|
caught-at: "Utworzono"
|
||||||
status: "Stan"
|
status: "Stan"
|
||||||
block: "Zablokuj"
|
block: "Zablokuj"
|
||||||
instances: "Instancja"
|
|
||||||
sort: "Sortuj"
|
sort: "Sortuj"
|
||||||
states:
|
states:
|
||||||
all: "Wszyscy"
|
all: "Wszyscy"
|
||||||
|
|||||||
@@ -1275,12 +1275,13 @@ admin/views/announcements.vue:
|
|||||||
admin/views/hashtags.vue:
|
admin/views/hashtags.vue:
|
||||||
hided-tags: "隐藏标签"
|
hided-tags: "隐藏标签"
|
||||||
admin/views/federation.vue:
|
admin/views/federation.vue:
|
||||||
federation: "联合"
|
instance: "例"
|
||||||
host: "主机名"
|
host: "主机名"
|
||||||
notes: "帖子"
|
notes: "帖子"
|
||||||
users: "用户"
|
users: "用户"
|
||||||
following: "正在关注"
|
following: "正在关注"
|
||||||
followers: "关注者"
|
followers: "关注者"
|
||||||
|
caught-at: "注册日期"
|
||||||
status: "状态"
|
status: "状态"
|
||||||
latest-request-sent-at: "上次发送的请求"
|
latest-request-sent-at: "上次发送的请求"
|
||||||
latest-request-received-at: "上次收到的请求"
|
latest-request-received-at: "上次收到的请求"
|
||||||
@@ -1289,7 +1290,7 @@ admin/views/federation.vue:
|
|||||||
block: "拉黑"
|
block: "拉黑"
|
||||||
marked-as-closed: "标记为已关闭"
|
marked-as-closed: "标记为已关闭"
|
||||||
lookup: "查询"
|
lookup: "查询"
|
||||||
instances: "实例"
|
instances: "联合"
|
||||||
instance-not-registered: "实例未注册"
|
instance-not-registered: "实例未注册"
|
||||||
sort: "排序"
|
sort: "排序"
|
||||||
sorts:
|
sorts:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "10.96.0",
|
"version": "10.98.0",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -77,6 +77,7 @@
|
|||||||
"@types/qrcode": "1.3.0",
|
"@types/qrcode": "1.3.0",
|
||||||
"@types/ratelimiter": "2.1.28",
|
"@types/ratelimiter": "2.1.28",
|
||||||
"@types/redis": "2.8.10",
|
"@types/redis": "2.8.10",
|
||||||
|
"@types/rename": "1.0.1",
|
||||||
"@types/request": "2.48.1",
|
"@types/request": "2.48.1",
|
||||||
"@types/request-promise-native": "1.0.15",
|
"@types/request-promise-native": "1.0.15",
|
||||||
"@types/request-stats": "3.0.0",
|
"@types/request-stats": "3.0.0",
|
||||||
@@ -107,6 +108,7 @@
|
|||||||
"chai-http": "4.2.1",
|
"chai-http": "4.2.1",
|
||||||
"chalk": "2.4.2",
|
"chalk": "2.4.2",
|
||||||
"commander": "2.19.0",
|
"commander": "2.19.0",
|
||||||
|
"content-disposition": "0.5.3",
|
||||||
"crc-32": "1.2.0",
|
"crc-32": "1.2.0",
|
||||||
"css-loader": "2.1.1",
|
"css-loader": "2.1.1",
|
||||||
"cssnano": "4.1.10",
|
"cssnano": "4.1.10",
|
||||||
@@ -193,6 +195,7 @@
|
|||||||
"recaptcha-promise": "0.1.3",
|
"recaptcha-promise": "0.1.3",
|
||||||
"reconnecting-websocket": "4.1.10",
|
"reconnecting-websocket": "4.1.10",
|
||||||
"redis": "2.8.0",
|
"redis": "2.8.0",
|
||||||
|
"rename": "1.0.4",
|
||||||
"request": "2.88.0",
|
"request": "2.88.0",
|
||||||
"request-promise-native": "1.0.7",
|
"request-promise-native": "1.0.7",
|
||||||
"request-stats": "3.0.0",
|
"request-stats": "3.0.0",
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
<ui-switch v-model="disableLocalTimeline">{{ $t('disable-local-timeline') }}</ui-switch>
|
<ui-switch v-model="disableLocalTimeline">{{ $t('disable-local-timeline') }}</ui-switch>
|
||||||
<ui-switch v-model="disableGlobalTimeline">{{ $t('disable-global-timeline') }}</ui-switch>
|
<ui-switch v-model="disableGlobalTimeline">{{ $t('disable-global-timeline') }}</ui-switch>
|
||||||
<ui-info>{{ $t('disabling-timelines-info') }}</ui-info>
|
<ui-info>{{ $t('disabling-timelines-info') }}</ui-info>
|
||||||
|
<ui-switch v-model="enableEmojiReaction">{{ $t('enable-emoji-reaction') }}</ui-switch>
|
||||||
|
<ui-switch v-model="useStarForReactionFallback">{{ $t('use-star-for-reaction-fallback') }}</ui-switch>
|
||||||
</section>
|
</section>
|
||||||
<section class="fit-bottom">
|
<section class="fit-bottom">
|
||||||
<header><fa icon="cloud"/> {{ $t('drive-config') }}</header>
|
<header><fa icon="cloud"/> {{ $t('drive-config') }}</header>
|
||||||
@@ -155,6 +157,8 @@ export default Vue.extend({
|
|||||||
disableRegistration: false,
|
disableRegistration: false,
|
||||||
disableLocalTimeline: false,
|
disableLocalTimeline: false,
|
||||||
disableGlobalTimeline: false,
|
disableGlobalTimeline: false,
|
||||||
|
enableEmojiReaction: true,
|
||||||
|
useStarForReactionFallback: false,
|
||||||
mascotImageUrl: null,
|
mascotImageUrl: null,
|
||||||
bannerUrl: null,
|
bannerUrl: null,
|
||||||
errorImageUrl: null,
|
errorImageUrl: null,
|
||||||
@@ -206,6 +210,8 @@ export default Vue.extend({
|
|||||||
this.disableRegistration = meta.disableRegistration;
|
this.disableRegistration = meta.disableRegistration;
|
||||||
this.disableLocalTimeline = meta.disableLocalTimeline;
|
this.disableLocalTimeline = meta.disableLocalTimeline;
|
||||||
this.disableGlobalTimeline = meta.disableGlobalTimeline;
|
this.disableGlobalTimeline = meta.disableGlobalTimeline;
|
||||||
|
this.enableEmojiReaction = meta.enableEmojiReaction;
|
||||||
|
this.useStarForReactionFallback = meta.useStarForReactionFallback;
|
||||||
this.mascotImageUrl = meta.mascotImageUrl;
|
this.mascotImageUrl = meta.mascotImageUrl;
|
||||||
this.bannerUrl = meta.bannerUrl;
|
this.bannerUrl = meta.bannerUrl;
|
||||||
this.errorImageUrl = meta.errorImageUrl;
|
this.errorImageUrl = meta.errorImageUrl;
|
||||||
@@ -267,6 +273,8 @@ export default Vue.extend({
|
|||||||
disableRegistration: this.disableRegistration,
|
disableRegistration: this.disableRegistration,
|
||||||
disableLocalTimeline: this.disableLocalTimeline,
|
disableLocalTimeline: this.disableLocalTimeline,
|
||||||
disableGlobalTimeline: this.disableGlobalTimeline,
|
disableGlobalTimeline: this.disableGlobalTimeline,
|
||||||
|
enableEmojiReaction: this.enableEmojiReaction,
|
||||||
|
useStarForReactionFallback: this.useStarForReactionFallback,
|
||||||
mascotImageUrl: this.mascotImageUrl,
|
mascotImageUrl: this.mascotImageUrl,
|
||||||
bannerUrl: this.bannerUrl,
|
bannerUrl: this.bannerUrl,
|
||||||
errorImageUrl: this.errorImageUrl,
|
errorImageUrl: this.errorImageUrl,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import * as emojilib from 'emojilib';
|
import * as emojilib from 'emojilib';
|
||||||
import contains from '../../../common/scripts/contains';
|
import contains from '../../../common/scripts/contains';
|
||||||
|
import { twemojiBase } from '../../../../../misc/twemoji-base';
|
||||||
|
|
||||||
type EmojiDef = {
|
type EmojiDef = {
|
||||||
emoji: string;
|
emoji: string;
|
||||||
@@ -54,7 +55,7 @@ const emjdb: EmojiDef[] = lib.map((x: any) => ({
|
|||||||
emoji: x[1].char,
|
emoji: x[1].char,
|
||||||
name: x[0],
|
name: x[0],
|
||||||
aliasOf: null,
|
aliasOf: null,
|
||||||
url: `https://twemoji.maxcdn.com/2/svg/${char2file(x[1].char)}.svg`
|
url: `${twemojiBase}/2/svg/${char2file(x[1].char)}.svg`
|
||||||
}));
|
}));
|
||||||
|
|
||||||
for (const x of lib as any) {
|
for (const x of lib as any) {
|
||||||
@@ -64,7 +65,7 @@ for (const x of lib as any) {
|
|||||||
emoji: x[1].char,
|
emoji: x[1].char,
|
||||||
name: k,
|
name: k,
|
||||||
aliasOf: x[0],
|
aliasOf: x[0],
|
||||||
url: `https://twemoji.maxcdn.com/2/svg/${char2file(x[1].char)}.svg`
|
url: `${twemojiBase}/2/svg/${char2file(x[1].char)}.svg`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
174
src/client/app/common/views/components/drive-file-thumbnail.vue
Normal file
174
src/client/app/common/views/components/drive-file-thumbnail.vue
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<template>
|
||||||
|
<div class="zdjebgpv" :class="{ detail }" ref="thumbnail" :style="`background-color: ${ background }`">
|
||||||
|
<img
|
||||||
|
:src="file.url"
|
||||||
|
:alt="file.name"
|
||||||
|
:title="file.name"
|
||||||
|
v-if="detail && is === 'image'"/>
|
||||||
|
<video
|
||||||
|
:src="file.url"
|
||||||
|
ref="volumectrl"
|
||||||
|
preload="metadata"
|
||||||
|
controls
|
||||||
|
v-else-if="detail && is === 'video'"/>
|
||||||
|
<img :src="file.thumbnailUrl" alt="" @load="onThumbnailLoaded" :style="`object-fit: ${ fit }`" v-else-if="isThumbnailAvailable"/>
|
||||||
|
<fa :icon="faFileImage" class="icon" v-else-if="is === 'image'"/>
|
||||||
|
<fa :icon="faFileVideo" class="icon" v-else-if="is === 'video'"/>
|
||||||
|
|
||||||
|
<audio
|
||||||
|
:src="file.url"
|
||||||
|
ref="volumectrl"
|
||||||
|
preload="metadata"
|
||||||
|
controls
|
||||||
|
v-else-if="detail && is === 'audio'"/>
|
||||||
|
<fa :icon="faMusic" class="icon" v-else-if="is === 'audio' || is === 'midi'"/>
|
||||||
|
|
||||||
|
<fa :icon="faFileCsv" class="icon" v-else-if="is === 'csv'"/>
|
||||||
|
<fa :icon="faFilePdf" class="icon" v-else-if="is === 'pdf'"/>
|
||||||
|
<fa :icon="faFileAlt" class="icon" v-else-if="is === 'textfile'"/>
|
||||||
|
<fa :icon="faFileArchive" class="icon" v-else-if="is === 'archive'"/>
|
||||||
|
<fa :icon="faFile" class="icon" v-else/>
|
||||||
|
|
||||||
|
<fa :icon="faFilm" class="icon-sub" v-if="!detail && isThumbnailAvailable && is === 'video'"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import anime from 'animejs';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
import {
|
||||||
|
faFile,
|
||||||
|
faFileAlt,
|
||||||
|
faFileImage,
|
||||||
|
faMusic,
|
||||||
|
faFileVideo,
|
||||||
|
faFileCsv,
|
||||||
|
faFilePdf,
|
||||||
|
faFileArchive,
|
||||||
|
faFilm
|
||||||
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
file: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
fit: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isContextmenuShowing: false,
|
||||||
|
isDragging: false,
|
||||||
|
|
||||||
|
faFile,
|
||||||
|
faFileAlt,
|
||||||
|
faFileImage,
|
||||||
|
faMusic,
|
||||||
|
faFileVideo,
|
||||||
|
faFileCsv,
|
||||||
|
faFilePdf,
|
||||||
|
faFileArchive,
|
||||||
|
faFilm
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
is(): 'image' | 'video' | 'midi' | 'audio' | 'csv' | 'pdf' | 'textfile' | 'archive' | 'unknown' {
|
||||||
|
if (this.file.type.startsWith('image/')) return 'image';
|
||||||
|
if (this.file.type.startsWith('video/')) return 'video';
|
||||||
|
if (this.file.type === 'audio/midi') return 'midi';
|
||||||
|
if (this.file.type.startsWith('audio/')) return 'audio';
|
||||||
|
if (this.file.type.endsWith('/csv')) return 'csv';
|
||||||
|
if (this.file.type.endsWith('/pdf')) return 'pdf';
|
||||||
|
if (this.file.type.startsWith('text/')) return 'textfile';
|
||||||
|
if ([
|
||||||
|
"application/zip",
|
||||||
|
"application/x-cpio",
|
||||||
|
"application/x-bzip",
|
||||||
|
"application/x-bzip2",
|
||||||
|
"application/java-archive",
|
||||||
|
"application/x-rar-compressed",
|
||||||
|
"application/x-tar",
|
||||||
|
"application/gzip",
|
||||||
|
"application/x-7z-compressed"
|
||||||
|
].some(e => e === this.file.type)) return 'archive';
|
||||||
|
return 'unknown';
|
||||||
|
},
|
||||||
|
isThumbnailAvailable(): boolean {
|
||||||
|
return this.file.thumbnailUrl.endsWith('?thumbnail') ? (this.is === 'image' || this.is === 'video') : true;
|
||||||
|
},
|
||||||
|
background(): string {
|
||||||
|
return this.file.properties.avgColor && this.file.properties.avgColor.length == 3
|
||||||
|
? `rgb(${this.file.properties.avgColor.join(',')})`
|
||||||
|
: 'transparent';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const audioTag = this.$refs.volumectrl as HTMLAudioElement;
|
||||||
|
if (audioTag) audioTag.volume = this.$store.state.device.mediaVolume;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onThumbnailLoaded() {
|
||||||
|
if (this.file.properties.avgColor && this.file.properties.avgColor.length == 3) {
|
||||||
|
anime({
|
||||||
|
targets: this.$refs.thumbnail,
|
||||||
|
backgroundColor: `rgba(${this.file.properties.avgColor.join(',')}, 0)`,
|
||||||
|
duration: 100,
|
||||||
|
easing: 'linear'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
volumechange() {
|
||||||
|
const audioTag = this.$refs.volumectrl as HTMLAudioElement;
|
||||||
|
this.$store.commit('device/set', { key: 'mediaVolume', value: audioTag.volume });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.zdjebgpv
|
||||||
|
display flex
|
||||||
|
|
||||||
|
> img,
|
||||||
|
> .icon
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
> img
|
||||||
|
height 100%
|
||||||
|
width 100%
|
||||||
|
margin auto
|
||||||
|
object-fit cover
|
||||||
|
|
||||||
|
> .icon
|
||||||
|
height 65%
|
||||||
|
width 65%
|
||||||
|
margin auto
|
||||||
|
|
||||||
|
> video,
|
||||||
|
> audio
|
||||||
|
width 100%
|
||||||
|
|
||||||
|
> .icon-sub
|
||||||
|
position absolute
|
||||||
|
width 30%
|
||||||
|
height auto
|
||||||
|
margin 0
|
||||||
|
right 4%
|
||||||
|
bottom 4%
|
||||||
|
|
||||||
|
&.detail
|
||||||
|
> .icon
|
||||||
|
height 100px
|
||||||
|
margin 16px auto
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -10,6 +10,7 @@ import Vue from 'vue';
|
|||||||
// スクリプトサイズがデカい
|
// スクリプトサイズがデカい
|
||||||
//import { lib } from 'emojilib';
|
//import { lib } from 'emojilib';
|
||||||
import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url';
|
import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url';
|
||||||
|
import { twemojiBase } from '../../../../../misc/twemoji-base';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: {
|
props: {
|
||||||
@@ -29,7 +30,11 @@ export default Vue.extend({
|
|||||||
customEmojis: {
|
customEmojis: {
|
||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
}
|
},
|
||||||
|
isReaction: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@@ -46,7 +51,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
useOsDefaultEmojis(): boolean {
|
useOsDefaultEmojis(): boolean {
|
||||||
return this.$store.state.device.useOsDefaultEmojis;
|
return this.$store.state.device.useOsDefaultEmojis && !this.isReaction;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -73,7 +78,7 @@ export default Vue.extend({
|
|||||||
if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f');
|
if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f');
|
||||||
codes = codes.filter(x => x && x.length);
|
codes = codes.filter(x => x && x.length);
|
||||||
|
|
||||||
this.url = `https://twemoji.maxcdn.com/2/svg/${codes.join('-')}.svg`;
|
this.url = `${twemojiBase}/2/svg/${codes.join('-')}.svg`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,19 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="mk-reaction-icon">
|
<mk-emoji :emoji="str.startsWith(':') ? null : str" :name="str.startsWith(':') ? str.substr(1, str.length - 2) : null" :is-reaction="true" :custom-emojis="customEmojis" :normal="true"/>
|
||||||
<img v-if="reaction == 'like'" src="https://twemoji.maxcdn.com/2/svg/1f44d.svg" :alt="$t('@.reactions.like')">
|
|
||||||
<img v-if="reaction == 'love'" src="https://twemoji.maxcdn.com/2/svg/2764.svg" :alt="$t('@.reactions.love')">
|
|
||||||
<img v-if="reaction == 'laugh'" src="https://twemoji.maxcdn.com/2/svg/1f606.svg" :alt="$t('@.reactions.laugh')">
|
|
||||||
<img v-if="reaction == 'hmm'" src="https://twemoji.maxcdn.com/2/svg/1f914.svg" :alt="$t('@.reactions.hmm')">
|
|
||||||
<img v-if="reaction == 'surprise'" src="https://twemoji.maxcdn.com/2/svg/1f62e.svg" :alt="$t('@.reactions.surprise')">
|
|
||||||
<img v-if="reaction == 'congrats'" src="https://twemoji.maxcdn.com/2/svg/1f389.svg" :alt="$t('@.reactions.congrats')">
|
|
||||||
<img v-if="reaction == 'angry'" src="https://twemoji.maxcdn.com/2/svg/1f4a2.svg" :alt="$t('@.reactions.angry')">
|
|
||||||
<img v-if="reaction == 'confused'" src="https://twemoji.maxcdn.com/2/svg/1f625.svg" :alt="$t('@.reactions.confused')">
|
|
||||||
<img v-if="reaction == 'rip'" src="https://twemoji.maxcdn.com/2/svg/1f607.svg" :alt="$t('@.reactions.rip')">
|
|
||||||
<template v-if="reaction == 'pudding'">
|
|
||||||
<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="https://twemoji.maxcdn.com/2/svg/1f363.svg" :alt="$t('@.reactions.pudding')">
|
|
||||||
<img v-else src="https://twemoji.maxcdn.com/2/svg/1f36e.svg" :alt="$t('@.reactions.pudding')">
|
|
||||||
</template>
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -21,7 +7,35 @@ import Vue from 'vue';
|
|||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n(),
|
i18n: i18n(),
|
||||||
props: ['reaction']
|
props: {
|
||||||
|
reaction: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
customEmojis: (this.$root.getMetaSync() || { emojis: [] }).emojis || []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
str(): any {
|
||||||
|
switch (this.reaction) {
|
||||||
|
case 'like': return '👍';
|
||||||
|
case 'love': return '❤';
|
||||||
|
case 'laugh': return '😆';
|
||||||
|
case 'hmm': return '🤔';
|
||||||
|
case 'surprise': return '😮';
|
||||||
|
case 'congrats': return '🎉';
|
||||||
|
case 'angry': return '💢';
|
||||||
|
case 'confused': return '😥';
|
||||||
|
case 'rip': return '😇';
|
||||||
|
case 'pudding': return (this.$store.getters.isSignedIn && this.$store.state.settings.iLikeSushi) ? '🍣' : '🍮';
|
||||||
|
case 'star': return '⭐';
|
||||||
|
default: return this.reaction;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="backdrop" ref="backdrop" @click="close"></div>
|
<div class="backdrop" ref="backdrop" @click="close"></div>
|
||||||
<div class="popover" :class="{ isMobile: $root.isMobile }" ref="popover">
|
<div class="popover" :class="{ isMobile: $root.isMobile }" ref="popover">
|
||||||
<p v-if="!$root.isMobile">{{ title }}</p>
|
<p v-if="!$root.isMobile">{{ title }}</p>
|
||||||
<div ref="buttons" :class="{ showFocus }">
|
<div class="buttons" ref="buttons" :class="{ showFocus }">
|
||||||
<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" :title="$t('@.reactions.like')" v-particle><mk-reaction-icon reaction="like"/></button>
|
<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" :title="$t('@.reactions.like')" v-particle><mk-reaction-icon reaction="like"/></button>
|
||||||
<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" :title="$t('@.reactions.love')" v-particle><mk-reaction-icon reaction="love"/></button>
|
<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" :title="$t('@.reactions.love')" v-particle><mk-reaction-icon reaction="love"/></button>
|
||||||
<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" :title="$t('@.reactions.laugh')" v-particle><mk-reaction-icon reaction="laugh"/></button>
|
<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" :title="$t('@.reactions.laugh')" v-particle><mk-reaction-icon reaction="laugh"/></button>
|
||||||
@@ -15,6 +15,9 @@
|
|||||||
<button @click="react('rip')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="9" :title="$t('@.reactions.rip')" v-particle><mk-reaction-icon reaction="rip"/></button>
|
<button @click="react('rip')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="9" :title="$t('@.reactions.rip')" v-particle><mk-reaction-icon reaction="rip"/></button>
|
||||||
<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" :title="$t('@.reactions.pudding')" v-particle><mk-reaction-icon reaction="pudding"/></button>
|
<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" :title="$t('@.reactions.pudding')" v-particle><mk-reaction-icon reaction="pudding"/></button>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="enableEmojiReaction" class="text">
|
||||||
|
<input v-model="text" placeholder="または絵文字を入力" @keyup.enter="reactText" @input="tryReactText" v-autocomplete="{ model: 'text' }">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -23,6 +26,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import anime from 'animejs';
|
import anime from 'animejs';
|
||||||
|
import { emojiRegex } from '../../../../../misc/emoji-regex';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('common/views/components/reaction-picker.vue'),
|
i18n: i18n('common/views/components/reaction-picker.vue'),
|
||||||
@@ -56,6 +60,8 @@ export default Vue.extend({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
title: this.$t('choose-reaction'),
|
title: this.$t('choose-reaction'),
|
||||||
|
text: null,
|
||||||
|
enableEmojiReaction: false,
|
||||||
focus: null
|
focus: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -94,6 +100,10 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.$root.getMeta().then(meta => {
|
||||||
|
this.enableEmojiReaction = meta.enableEmojiReaction;
|
||||||
|
});
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.focus = 0;
|
this.focus = 0;
|
||||||
|
|
||||||
@@ -143,6 +153,17 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
reactText() {
|
||||||
|
if (!this.text) return;
|
||||||
|
this.react(this.text);
|
||||||
|
},
|
||||||
|
|
||||||
|
tryReactText() {
|
||||||
|
if (!this.text) return;
|
||||||
|
if (!this.text.match(emojiRegex)) return;
|
||||||
|
this.reactText();
|
||||||
|
},
|
||||||
|
|
||||||
onMouseover(e) {
|
onMouseover(e) {
|
||||||
this.title = e.target.title;
|
this.title = e.target.title;
|
||||||
},
|
},
|
||||||
@@ -256,9 +277,9 @@ export default Vue.extend({
|
|||||||
color var(--popupFg)
|
color var(--popupFg)
|
||||||
border-bottom solid var(--lineWidth) var(--faceDivider)
|
border-bottom solid var(--lineWidth) var(--faceDivider)
|
||||||
|
|
||||||
> div
|
> .buttons
|
||||||
padding 4px
|
padding 4px 4px 8px 4px
|
||||||
width 240px
|
width 216px
|
||||||
text-align center
|
text-align center
|
||||||
|
|
||||||
&.showFocus
|
&.showFocus
|
||||||
@@ -283,6 +304,9 @@ export default Vue.extend({
|
|||||||
font-size 24px
|
font-size 24px
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
|
|
||||||
|
> *
|
||||||
|
height 1em
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
background var(--reactionPickerButtonHoverBg)
|
background var(--reactionPickerButtonHoverBg)
|
||||||
|
|
||||||
@@ -290,4 +314,29 @@ export default Vue.extend({
|
|||||||
background var(--primary)
|
background var(--primary)
|
||||||
box-shadow inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15)
|
box-shadow inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15)
|
||||||
|
|
||||||
|
> .text
|
||||||
|
width 216px
|
||||||
|
padding 0 8px 8px 8px
|
||||||
|
|
||||||
|
> input
|
||||||
|
width 100%
|
||||||
|
padding 10px
|
||||||
|
margin 0
|
||||||
|
text-align center
|
||||||
|
font-size 16px
|
||||||
|
color var(--desktopPostFormTextareaFg)
|
||||||
|
background var(--desktopPostFormTextareaBg)
|
||||||
|
outline none
|
||||||
|
border solid 1px var(--primaryAlpha01)
|
||||||
|
border-radius 4px
|
||||||
|
transition border-color .2s ease
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
border-color var(--primaryAlpha02)
|
||||||
|
transition border-color .1s ease
|
||||||
|
|
||||||
|
&:focus
|
||||||
|
border-color var(--primaryAlpha05)
|
||||||
|
transition border-color 0s ease
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -136,12 +136,8 @@ export default Vue.extend({
|
|||||||
&:hover
|
&:hover
|
||||||
background var(--reactionViewerButtonHoverBg)
|
background var(--reactionViewerButtonHoverBg)
|
||||||
|
|
||||||
> .mk-reaction-icon
|
|
||||||
font-size 1.4em
|
|
||||||
|
|
||||||
> span
|
> span
|
||||||
font-size 1.1em
|
font-size 1.1em
|
||||||
line-height 32px
|
line-height 32px
|
||||||
vertical-align middle
|
|
||||||
color var(--text)
|
color var(--text)
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -159,7 +159,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="page == null || page == 'notification'">
|
<template v-if="page == null || page == 'notification'">
|
||||||
<x-notification v-show="page == 'notification'"/>
|
<x-notification/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="page == null || page == 'drive'">
|
<template v-if="page == null || page == 'drive'">
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
<ol v-if="uploads.length > 0">
|
<ol v-if="uploads.length > 0">
|
||||||
<li v-for="ctx in uploads" :key="ctx.id">
|
<li v-for="ctx in uploads" :key="ctx.id">
|
||||||
<div class="img" :style="{ backgroundImage: `url(${ ctx.img })` }"></div>
|
<div class="img" :style="{ backgroundImage: `url(${ ctx.img })` }"></div>
|
||||||
<p class="name"><fa icon="spinner" pulse/>{{ ctx.name }}</p>
|
<div class="top">
|
||||||
<p class="status">
|
<p class="name"><fa icon="spinner" pulse/>{{ ctx.name }}</p>
|
||||||
<span class="initing" v-if="ctx.progress == undefined">{{ $t('waiting') }}<mk-ellipsis/></span>
|
<p class="status">
|
||||||
<span class="kb" v-if="ctx.progress != undefined">{{ String(Math.floor(ctx.progress.value / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progress.max / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span>
|
<span class="initing" v-if="ctx.progress == undefined">{{ $t('waiting') }}<mk-ellipsis/></span>
|
||||||
<span class="percentage" v-if="ctx.progress != undefined">{{ Math.floor((ctx.progress.value / ctx.progress.max) * 100) }}</span>
|
<span class="kb" v-if="ctx.progress != undefined">{{ String(Math.floor(ctx.progress.value / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progress.max / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span>
|
||||||
</p>
|
<span class="percentage" v-if="ctx.progress != undefined">{{ Math.floor((ctx.progress.value / ctx.progress.max) * 100) }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<progress v-if="ctx.progress != undefined && ctx.progress.value != ctx.progress.max" :value="ctx.progress.value" :max="ctx.progress.max"></progress>
|
<progress v-if="ctx.progress != undefined && ctx.progress.value != ctx.progress.max" :value="ctx.progress.value" :max="ctx.progress.max"></progress>
|
||||||
<div class="progress initing" v-if="ctx.progress == undefined"></div>
|
<div class="progress initing" v-if="ctx.progress == undefined"></div>
|
||||||
<div class="progress waiting" v-if="ctx.progress != undefined && ctx.progress.value == ctx.progress.max"></div>
|
<div class="progress waiting" v-if="ctx.progress != undefined && ctx.progress.value == ctx.progress.max"></div>
|
||||||
@@ -116,12 +118,17 @@ export default Vue.extend({
|
|||||||
list-style none
|
list-style none
|
||||||
|
|
||||||
> li
|
> li
|
||||||
display block
|
display grid
|
||||||
margin 8px 0 0 0
|
margin 8px 0 0 0
|
||||||
padding 0
|
padding 0
|
||||||
height 36px
|
height 36px
|
||||||
|
width: 100%
|
||||||
box-shadow 0 -1px 0 var(--primaryAlpha01)
|
box-shadow 0 -1px 0 var(--primaryAlpha01)
|
||||||
border-top solid 8px transparent
|
border-top solid 8px transparent
|
||||||
|
grid-template-columns 36px calc(100% - 44px)
|
||||||
|
grid-template-rows 1fr 8px
|
||||||
|
column-gap 8px
|
||||||
|
box-sizing content-box
|
||||||
|
|
||||||
&:first-child
|
&:first-child
|
||||||
margin 0
|
margin 0
|
||||||
@@ -130,68 +137,62 @@ export default Vue.extend({
|
|||||||
|
|
||||||
> .img
|
> .img
|
||||||
display block
|
display block
|
||||||
position absolute
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
width 36px
|
|
||||||
height 36px
|
|
||||||
background-size cover
|
background-size cover
|
||||||
background-position center center
|
background-position center center
|
||||||
|
grid-column 1 / 2
|
||||||
|
grid-row 1 / 3
|
||||||
|
|
||||||
> .name
|
> .top
|
||||||
display block
|
display flex
|
||||||
position absolute
|
grid-column 2 / 3
|
||||||
top 0
|
grid-row 1 / 2
|
||||||
left 44px
|
|
||||||
margin 0
|
|
||||||
padding 0
|
|
||||||
max-width 256px
|
|
||||||
font-size 0.8em
|
|
||||||
color var(--primaryAlpha07)
|
|
||||||
white-space nowrap
|
|
||||||
text-overflow ellipsis
|
|
||||||
overflow hidden
|
|
||||||
|
|
||||||
> [data-icon]
|
|
||||||
margin-right 4px
|
|
||||||
|
|
||||||
> .status
|
|
||||||
display block
|
|
||||||
position absolute
|
|
||||||
top 0
|
|
||||||
right 0
|
|
||||||
margin 0
|
|
||||||
padding 0
|
|
||||||
font-size 0.8em
|
|
||||||
|
|
||||||
> .initing
|
|
||||||
color var(--primaryAlpha05)
|
|
||||||
|
|
||||||
> .kb
|
|
||||||
color var(--primaryAlpha05)
|
|
||||||
|
|
||||||
> .percentage
|
|
||||||
display inline-block
|
|
||||||
width 48px
|
|
||||||
text-align right
|
|
||||||
|
|
||||||
|
> .name
|
||||||
|
display block
|
||||||
|
padding 0 8px 0 0
|
||||||
|
margin 0
|
||||||
|
font-size 0.8em
|
||||||
color var(--primaryAlpha07)
|
color var(--primaryAlpha07)
|
||||||
|
white-space nowrap
|
||||||
|
text-overflow ellipsis
|
||||||
|
overflow hidden
|
||||||
|
flex-shrink 1
|
||||||
|
|
||||||
&:after
|
> [data-icon]
|
||||||
content '%'
|
margin-right 4px
|
||||||
|
|
||||||
|
> .status
|
||||||
|
display block
|
||||||
|
margin 0 0 0 auto
|
||||||
|
padding 0
|
||||||
|
font-size 0.8em
|
||||||
|
flex-shrink 0
|
||||||
|
|
||||||
|
> .initing
|
||||||
|
color var(--primaryAlpha05)
|
||||||
|
|
||||||
|
> .kb
|
||||||
|
color var(--primaryAlpha05)
|
||||||
|
|
||||||
|
> .percentage
|
||||||
|
display inline-block
|
||||||
|
width 48px
|
||||||
|
text-align right
|
||||||
|
|
||||||
|
color var(--primaryAlpha07)
|
||||||
|
|
||||||
|
&:after
|
||||||
|
content '%'
|
||||||
|
|
||||||
> progress
|
> progress
|
||||||
display block
|
display block
|
||||||
position absolute
|
|
||||||
bottom 0
|
|
||||||
right 0
|
|
||||||
margin 0
|
|
||||||
width calc(100% - 44px)
|
|
||||||
height 8px
|
|
||||||
background transparent
|
background transparent
|
||||||
border none
|
border none
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
grid-column 2 / 3
|
||||||
|
grid-row 2 / 3
|
||||||
|
z-index 2
|
||||||
|
|
||||||
&::-webkit-progress-value
|
&::-webkit-progress-value
|
||||||
background var(--primary)
|
background var(--primary)
|
||||||
@@ -201,12 +202,6 @@ export default Vue.extend({
|
|||||||
|
|
||||||
> .progress
|
> .progress
|
||||||
display block
|
display block
|
||||||
position absolute
|
|
||||||
bottom 0
|
|
||||||
right 0
|
|
||||||
margin 0
|
|
||||||
width calc(100% - 44px)
|
|
||||||
height 8px
|
|
||||||
border none
|
border none
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
background linear-gradient(
|
background linear-gradient(
|
||||||
@@ -221,6 +216,9 @@ export default Vue.extend({
|
|||||||
)
|
)
|
||||||
background-size 32px 32px
|
background-size 32px 32px
|
||||||
animation bg 1.5s linear infinite
|
animation bg 1.5s linear infinite
|
||||||
|
grid-column 2 / 3
|
||||||
|
grid-row 2 / 3
|
||||||
|
z-index 1
|
||||||
|
|
||||||
&.initing
|
&.initing
|
||||||
opacity 0.3
|
opacity 0.3
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<p @click="click(item)"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</p>
|
<p @click="click(item)"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</p>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="item.type == 'link'">
|
<template v-else-if="item.type == 'link'">
|
||||||
<a :href="item.href" :target="item.target" @click="click(item)"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</a>
|
<a :href="item.href" :target="item.target" @click="click(item)" :download="item.download"><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}</a>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="item.type == 'nest'">
|
<template v-else-if="item.type == 'nest'">
|
||||||
<p><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}...<span class="caret"><fa icon="caret-right"/></span></p>
|
<p><i v-if="item.icon" :class="$style.icon"><fa :icon="item.icon"/></i>{{ item.text }}...<span class="caret"><fa icon="caret-right"/></span></p>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<template #header><fa icon="crop"/>{{ title }}</template>
|
<template #header><fa icon="crop"/>{{ title }}</template>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<vue-cropper ref="cropper"
|
<vue-cropper ref="cropper"
|
||||||
:src="image.url"
|
:src="imageUrl"
|
||||||
:view-mode="1"
|
:view-mode="1"
|
||||||
:aspect-ratio="aspectRatio"
|
:aspect-ratio="aspectRatio"
|
||||||
:container-style="{ width: '100%', 'max-height': '400px' }"
|
:container-style="{ width: '100%', 'max-height': '400px' }"
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import VueCropper from 'vue-cropperjs';
|
import VueCropper from 'vue-cropperjs';
|
||||||
|
import * as url from '../../../../../prelude/url';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('desktop/views/components/crop-window.vue'),
|
i18n: i18n('desktop/views/components/crop-window.vue'),
|
||||||
@@ -41,6 +42,13 @@ export default Vue.extend({
|
|||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
imageUrl() {
|
||||||
|
return `/proxy/?${url.query({
|
||||||
|
url: this.image.url
|
||||||
|
})}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
ok() {
|
ok() {
|
||||||
(this.$refs.cropper as any).getCroppedCanvas().toBlob(blob => {
|
(this.$refs.cropper as any).getCroppedCanvas().toBlob(blob => {
|
||||||
|
|||||||
@@ -21,9 +21,9 @@
|
|||||||
<img src="/assets/label-red.svg"/>
|
<img src="/assets/label-red.svg"/>
|
||||||
<p>{{ $t('nsfw') }}</p>
|
<p>{{ $t('nsfw') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="thumbnail" ref="thumbnail" :style="`background-color: ${ background }`">
|
|
||||||
<img :src="file.thumbnailUrl" alt="" @load="onThumbnailLoaded"/>
|
<x-file-thumbnail class="thumbnail" :file="file" fit="contain"/>
|
||||||
</div>
|
|
||||||
<p class="name">
|
<p class="name">
|
||||||
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
|
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
|
||||||
<span class="ext" v-if="file.name.lastIndexOf('.') != -1">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span>
|
<span class="ext" v-if="file.name.lastIndexOf('.') != -1">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span>
|
||||||
@@ -34,14 +34,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import anime from 'animejs';
|
|
||||||
import copyToClipboard from '../../../common/scripts/copy-to-clipboard';
|
import copyToClipboard from '../../../common/scripts/copy-to-clipboard';
|
||||||
import updateAvatar from '../../api/update-avatar';
|
import updateAvatar from '../../api/update-avatar';
|
||||||
import updateBanner from '../../api/update-banner';
|
import updateBanner from '../../api/update-banner';
|
||||||
|
import { appendQuery } from '../../../../../prelude/url';
|
||||||
|
import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('desktop/views/components/drive.file.vue'),
|
i18n: i18n('desktop/views/components/drive.file.vue'),
|
||||||
props: ['file'],
|
props: ['file'],
|
||||||
|
components: {
|
||||||
|
XFileThumbnail
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isContextmenuShowing: false,
|
isContextmenuShowing: false,
|
||||||
@@ -57,11 +61,6 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
title(): string {
|
title(): string {
|
||||||
return `${this.file.name}\n${this.file.type} ${Vue.filter('bytes')(this.file.datasize)}`;
|
return `${this.file.name}\n${this.file.type} ${Vue.filter('bytes')(this.file.datasize)}`;
|
||||||
},
|
|
||||||
background(): string {
|
|
||||||
return this.file.properties.avgColor && this.file.properties.avgColor.length == 3
|
|
||||||
? `rgb(${this.file.properties.avgColor.join(',')})`
|
|
||||||
: 'transparent';
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -88,9 +87,10 @@ export default Vue.extend({
|
|||||||
action: this.copyUrl
|
action: this.copyUrl
|
||||||
}, {
|
}, {
|
||||||
type: 'link',
|
type: 'link',
|
||||||
href: `${this.file.url}?download`,
|
href: appendQuery(this.file.url, 'download'),
|
||||||
text: this.$t('contextmenu.download'),
|
text: this.$t('contextmenu.download'),
|
||||||
icon: 'download',
|
icon: 'download',
|
||||||
|
download: this.file.name
|
||||||
}, null, {
|
}, null, {
|
||||||
type: 'item',
|
type: 'item',
|
||||||
text: this.$t('@.delete'),
|
text: this.$t('@.delete'),
|
||||||
@@ -205,7 +205,7 @@ export default Vue.extend({
|
|||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.gvfdktuvdgwhmztnuekzkswkjygptfcv
|
.gvfdktuvdgwhmztnuekzkswkjygptfcv
|
||||||
padding 8px 0 0 0
|
padding 8px 0 0 0
|
||||||
height 180px
|
min-height 180px
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
|
|
||||||
&, *
|
&, *
|
||||||
@@ -254,6 +254,9 @@ export default Vue.extend({
|
|||||||
> .name
|
> .name
|
||||||
color var(--primaryForeground)
|
color var(--primaryForeground)
|
||||||
|
|
||||||
|
> .thumbnail
|
||||||
|
color var(--primaryForeground)
|
||||||
|
|
||||||
&[data-is-contextmenu-showing]
|
&[data-is-contextmenu-showing]
|
||||||
&:after
|
&:after
|
||||||
content ""
|
content ""
|
||||||
@@ -319,18 +322,7 @@ export default Vue.extend({
|
|||||||
width 128px
|
width 128px
|
||||||
height 128px
|
height 128px
|
||||||
margin auto
|
margin auto
|
||||||
|
color var(--driveFileIcon)
|
||||||
> img
|
|
||||||
display block
|
|
||||||
position absolute
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
right 0
|
|
||||||
bottom 0
|
|
||||||
margin auto
|
|
||||||
max-width 128px
|
|
||||||
max-height 128px
|
|
||||||
pointer-events none
|
|
||||||
|
|
||||||
> .name
|
> .name
|
||||||
display block
|
display block
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
|
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
|
||||||
<template #header><fa icon="comments"/> {{ $t('title') }} <mk-user-name :user="user"/></template>
|
<template #header><fa icon="comments"/> {{ $t('@.messaging') }}: <mk-user-name :user="user"/></template>
|
||||||
<x-messaging-room :user="user" :class="$style.content"/>
|
<x-messaging-room :user="user" :class="$style.content"/>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
</template>
|
</template>
|
||||||
@@ -12,7 +12,7 @@ import { url } from '../../../config';
|
|||||||
import getAcct from '../../../../../misc/acct/render';
|
import getAcct from '../../../../../misc/acct/render';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('desktop/views/components/messaging-room-window.vue'),
|
i18n: i18n(),
|
||||||
components: {
|
components: {
|
||||||
XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
|
XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<mk-window ref="window" width="500px" height="560px" @closed="destroyDom">
|
<mk-window ref="window" width="500px" height="560px" @closed="destroyDom">
|
||||||
<template #header :class="$style.header"><fa icon="comments"/>{{ $t('title') }}</template>
|
<template #header :class="$style.header"><fa icon="comments"/>{{ $t('@.messaging') }}</template>
|
||||||
<x-messaging :class="$style.content" @navigate="navigate"/>
|
<x-messaging :class="$style.content" @navigate="navigate"/>
|
||||||
</mk-window>
|
</mk-window>
|
||||||
</template>
|
</template>
|
||||||
@@ -11,7 +11,7 @@ import i18n from '../../../i18n';
|
|||||||
import MkMessagingRoomWindow from './messaging-room-window.vue';
|
import MkMessagingRoomWindow from './messaging-room-window.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('desktop/views/components/messaging-window.vue'),
|
i18n: i18n(),
|
||||||
components: {
|
components: {
|
||||||
XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
|
XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import parseAcct from '../../../../../misc/acct/parse';
|
|||||||
import getUserName from '../../../../../misc/get-user-name';
|
import getUserName from '../../../../../misc/get-user-name';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('.vue'),
|
i18n: i18n(),
|
||||||
components: {
|
components: {
|
||||||
XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
|
XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
|
||||||
},
|
},
|
||||||
@@ -51,7 +51,7 @@ export default Vue.extend({
|
|||||||
this.user = user;
|
this.user = user;
|
||||||
this.fetching = false;
|
this.fetching = false;
|
||||||
|
|
||||||
document.title = `メッセージ: ${getUserName(this.user)}`;
|
document.title = this.$t('@.messaging') + ': ' + getUserName(this.user);
|
||||||
|
|
||||||
Progress.done();
|
Progress.done();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mkw-messaging">
|
<div class="mkw-messaging">
|
||||||
<ui-container :show-header="props.design == 0">
|
<ui-container :show-header="props.design == 0">
|
||||||
<template #header><fa icon="comments"/>{{ $t('title') }}</template>
|
<template #header><fa icon="comments"/>{{ $t('@.messaging') }}</template>
|
||||||
<template #func><button @click="add"><fa icon="plus"/></button></template>
|
<template #func><button @click="add"><fa icon="plus"/></button></template>
|
||||||
|
|
||||||
<x-messaging ref="index" compact @navigate="navigate"/>
|
<x-messaging ref="index" compact @navigate="navigate"/>
|
||||||
@@ -21,7 +21,7 @@ export default define({
|
|||||||
design: 0
|
design: 0
|
||||||
})
|
})
|
||||||
}).extend({
|
}).extend({
|
||||||
i18n: i18n('desktop/views/widgets/messaging.vue'),
|
i18n: i18n(''),
|
||||||
components: {
|
components: {
|
||||||
XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
|
XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ import App from './app.vue';
|
|||||||
import checkForUpdate from './common/scripts/check-for-update';
|
import checkForUpdate from './common/scripts/check-for-update';
|
||||||
import MiOS from './mios';
|
import MiOS from './mios';
|
||||||
import { version, codename, lang, locale } from './config';
|
import { version, codename, lang, locale } from './config';
|
||||||
import { builtinThemes, applyTheme, darkTheme } from './theme';
|
import { builtinThemes, applyTheme, futureTheme } from './theme';
|
||||||
import Dialog from './common/views/components/dialog.vue';
|
import Dialog from './common/views/components/dialog.vue';
|
||||||
|
|
||||||
if (localStorage.getItem('theme') == null) {
|
if (localStorage.getItem('theme') == null) {
|
||||||
applyTheme(darkTheme);
|
applyTheme(futureTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region FontAwesome
|
//#region FontAwesome
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="pyvicwrksnfyhpfgkjwqknuururpaztw">
|
<div class="pyvicwrksnfyhpfgkjwqknuururpaztw">
|
||||||
<div class="preview">
|
<div class="preview">
|
||||||
<img v-if="kind == 'image'" ref="img"
|
<x-file-thumbnail class="preview" :file="file" fit="cover" :detail="true"/>
|
||||||
:src="file.url"
|
|
||||||
:alt="file.name"
|
|
||||||
:title="file.name"
|
|
||||||
:style="style">
|
|
||||||
<template v-if="kind != 'image'"><fa icon="file"/></template>
|
<template v-if="kind != 'image'"><fa icon="file"/></template>
|
||||||
<footer v-if="kind == 'image' && file.properties && file.properties.width && file.properties.height">
|
<footer v-if="kind == 'image' && file.properties && file.properties.width && file.properties.height">
|
||||||
<span class="size">
|
<span class="size">
|
||||||
@@ -38,7 +34,7 @@
|
|||||||
<div class="menu">
|
<div class="menu">
|
||||||
<div>
|
<div>
|
||||||
<ui-input readonly :value="file.url">URL</ui-input>
|
<ui-input readonly :value="file.url">URL</ui-input>
|
||||||
<ui-button link :href="`${file.url}?download`" :download="file.name"><fa icon="download"/> {{ $t('download') }}</ui-button>
|
<ui-button link :href="dlUrl" :download="file.name"><fa icon="download"/> {{ $t('download') }}</ui-button>
|
||||||
<ui-button @click="rename"><fa icon="pencil-alt"/> {{ $t('rename') }}</ui-button>
|
<ui-button @click="rename"><fa icon="pencil-alt"/> {{ $t('rename') }}</ui-button>
|
||||||
<ui-button @click="move"><fa :icon="['far', 'folder-open']"/> {{ $t('move') }}</ui-button>
|
<ui-button @click="move"><fa :icon="['far', 'folder-open']"/> {{ $t('move') }}</ui-button>
|
||||||
<ui-button @click="toggleSensitive" v-if="file.isSensitive"><fa :icon="['far', 'eye']"/> {{ $t('unmark-as-sensitive') }}</ui-button>
|
<ui-button @click="toggleSensitive" v-if="file.isSensitive"><fa :icon="['far', 'eye']"/> {{ $t('unmark-as-sensitive') }}</ui-button>
|
||||||
@@ -61,11 +57,17 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import { gcd } from '../../../../../prelude/math';
|
import { gcd } from '../../../../../prelude/math';
|
||||||
|
import { appendQuery } from '../../../../../prelude/url';
|
||||||
|
import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('mobile/views/components/drive.file-detail.vue'),
|
i18n: i18n('mobile/views/components/drive.file-detail.vue'),
|
||||||
props: ['file'],
|
props: ['file'],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
XFileThumbnail
|
||||||
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
gcd,
|
gcd,
|
||||||
@@ -86,6 +88,10 @@ export default Vue.extend({
|
|||||||
return this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? {
|
return this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? {
|
||||||
'background-color': `rgb(${ this.file.properties.avgColor.join(',') })`
|
'background-color': `rgb(${ this.file.properties.avgColor.join(',') })`
|
||||||
} : {};
|
} : {};
|
||||||
|
},
|
||||||
|
|
||||||
|
dlUrl(): string {
|
||||||
|
return appendQuery(this.file.url, 'download');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -142,8 +148,7 @@ export default Vue.extend({
|
|||||||
padding 8px
|
padding 8px
|
||||||
background var(--bg)
|
background var(--bg)
|
||||||
|
|
||||||
> img
|
> .preview
|
||||||
display block
|
|
||||||
max-width 100%
|
max-width 100%
|
||||||
max-height 300px
|
max-height 300px
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a class="vupkuhvjnjyqaqhsiogfbywvjxynrgsm" @click.prevent="onClick" :href="`/i/drive/file/${ file.id }`" :data-is-selected="isSelected">
|
<a class="vupkuhvjnjyqaqhsiogfbywvjxynrgsm" @click.prevent="onClick" :href="`/i/drive/file/${ file.id }`" :data-is-selected="isSelected">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="thumbnail" :style="thumbnail"></div>
|
<x-file-thumbnail class="thumbnail" :file="file" fit="cover"/>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<p class="name">
|
<p class="name">
|
||||||
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
|
<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
|
||||||
@@ -26,9 +26,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
|
import XFileThumbnail from '../../../common/views/components/drive-file-thumbnail.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('mobile/views/components/drive.file.vue'),
|
i18n: i18n('mobile/views/components/drive.file.vue'),
|
||||||
props: ['file'],
|
props: ['file'],
|
||||||
|
components: {
|
||||||
|
XFileThumbnail
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isSelected: false
|
isSelected: false
|
||||||
@@ -37,12 +42,6 @@ export default Vue.extend({
|
|||||||
computed: {
|
computed: {
|
||||||
browser(): any {
|
browser(): any {
|
||||||
return this.$parent;
|
return this.$parent;
|
||||||
},
|
|
||||||
thumbnail(): any {
|
|
||||||
return {
|
|
||||||
'background-color': this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? `rgb(${this.file.properties.avgColor.join(',')})` : 'transparent',
|
|
||||||
'background-image': `url(${this.file.thumbnailUrl})`
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -74,9 +73,12 @@ export default Vue.extend({
|
|||||||
pointer-events none
|
pointer-events none
|
||||||
|
|
||||||
> .container
|
> .container
|
||||||
|
display grid
|
||||||
max-width 500px
|
max-width 500px
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
padding 16px
|
padding 16px
|
||||||
|
grid-template-columns 64px 1fr
|
||||||
|
grid-column-gap 10px
|
||||||
|
|
||||||
&:after
|
&:after
|
||||||
content ""
|
content ""
|
||||||
@@ -84,18 +86,13 @@ export default Vue.extend({
|
|||||||
clear both
|
clear both
|
||||||
|
|
||||||
> .thumbnail
|
> .thumbnail
|
||||||
display block
|
|
||||||
float left
|
|
||||||
width 64px
|
width 64px
|
||||||
height 64px
|
height 64px
|
||||||
background-size cover
|
color var(--driveFileIcon)
|
||||||
background-position center center
|
|
||||||
|
|
||||||
> .body
|
> .body
|
||||||
display block
|
display block
|
||||||
float left
|
word-break break-all
|
||||||
width calc(100% - 74px)
|
|
||||||
margin-left 10px
|
|
||||||
|
|
||||||
> .name
|
> .name
|
||||||
display block
|
display block
|
||||||
@@ -104,8 +101,7 @@ export default Vue.extend({
|
|||||||
font-size 0.9em
|
font-size 0.9em
|
||||||
font-weight bold
|
font-weight bold
|
||||||
color var(--text)
|
color var(--text)
|
||||||
text-overflow ellipsis
|
word-break break-word
|
||||||
overflow-wrap break-word
|
|
||||||
|
|
||||||
> .ext
|
> .ext
|
||||||
opacity 0.5
|
opacity 0.5
|
||||||
@@ -154,6 +150,6 @@ export default Vue.extend({
|
|||||||
background var(--primary)
|
background var(--primary)
|
||||||
|
|
||||||
&, *
|
&, *
|
||||||
color #fff !important
|
color var(--primaryForeground) !important
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const defaultDeviceSettings = {
|
|||||||
roundedCorners: true,
|
roundedCorners: true,
|
||||||
reduceMotion: false,
|
reduceMotion: false,
|
||||||
darkmode: true,
|
darkmode: true,
|
||||||
darkTheme: 'dark',
|
darkTheme: 'bb5a8287-a072-4b0a-8ae5-ea2a0d33f4f2',
|
||||||
lightTheme: 'light',
|
lightTheme: 'light',
|
||||||
lineWidth: 1,
|
lineWidth: 1,
|
||||||
fontSize: 0,
|
fontSize: 0,
|
||||||
|
|||||||
@@ -10,26 +10,26 @@ export type Theme = {
|
|||||||
props: { [key: string]: string };
|
props: { [key: string]: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const lightTheme: Theme = require('../theme/light.json5');
|
export const lightTheme: Theme = require('../themes/light.json5');
|
||||||
export const darkTheme: Theme = require('../theme/dark.json5');
|
export const darkTheme: Theme = require('../themes/dark.json5');
|
||||||
export const pinkTheme: Theme = require('../theme/pink.json5');
|
export const lavenderTheme: Theme = require('../themes/lavender.json5');
|
||||||
export const blackTheme: Theme = require('../theme/black.json5');
|
export const futureTheme: Theme = require('../themes/future.json5');
|
||||||
export const halloweenTheme: Theme = require('../theme/halloween.json5');
|
export const halloweenTheme: Theme = require('../themes/halloween.json5');
|
||||||
export const cafeTheme: Theme = require('../theme/cafe.json5');
|
export const cafeTheme: Theme = require('../themes/cafe.json5');
|
||||||
export const japaneseSushiSetTheme: Theme = require('../theme/japanese-sushi-set.json5');
|
export const japaneseSushiSetTheme: Theme = require('../themes/japanese-sushi-set.json5');
|
||||||
export const gruvboxDarkTheme: Theme = require('../theme/gruvbox-dark.json5');
|
export const gruvboxDarkTheme: Theme = require('../themes/gruvbox-dark.json5');
|
||||||
export const monokaiTheme: Theme = require('../theme/monokai.json5');
|
export const monokaiTheme: Theme = require('../themes/monokai.json5');
|
||||||
export const colorfulTheme: Theme = require('../theme/colorful.json5');
|
export const colorfulTheme: Theme = require('../themes/colorful.json5');
|
||||||
export const rainyTheme: Theme = require('../theme/rainy.json5');
|
export const rainyTheme: Theme = require('../themes/rainy.json5');
|
||||||
export const mauveTheme: Theme = require('../theme/mauve.json5');
|
export const mauveTheme: Theme = require('../themes/mauve.json5');
|
||||||
export const grayTheme: Theme = require('../theme/gray.json5');
|
export const grayTheme: Theme = require('../themes/gray.json5');
|
||||||
export const tweetDeckTheme: Theme = require('../theme/tweet-deck.json5');
|
export const tweetDeckTheme: Theme = require('../themes/tweet-deck.json5');
|
||||||
|
|
||||||
export const builtinThemes = [
|
export const builtinThemes = [
|
||||||
lightTheme,
|
lightTheme,
|
||||||
darkTheme,
|
darkTheme,
|
||||||
pinkTheme,
|
lavenderTheme,
|
||||||
blackTheme,
|
futureTheme,
|
||||||
halloweenTheme,
|
halloweenTheme,
|
||||||
cafeTheme,
|
cafeTheme,
|
||||||
japaneseSushiSetTheme,
|
japaneseSushiSetTheme,
|
||||||
@@ -42,41 +42,41 @@ export const builtinThemes = [
|
|||||||
tweetDeckTheme,
|
tweetDeckTheme,
|
||||||
];
|
];
|
||||||
|
|
||||||
export function applyTheme(theme: Theme, persisted = true) {
|
export function applyTheme(themes: Theme, persisted = true) {
|
||||||
document.documentElement.classList.add('changing-theme');
|
document.documentElement.classList.add('changing-themes');
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.documentElement.classList.remove('changing-theme');
|
document.documentElement.classList.remove('changing-themes');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
// Deep copy
|
// Deep copy
|
||||||
const _theme = JSON.parse(JSON.stringify(theme));
|
const _themes = JSON.parse(JSON.stringify(themes));
|
||||||
|
|
||||||
if (_theme.base) {
|
if (_themes.base) {
|
||||||
const base = [lightTheme, darkTheme].find(x => x.id == _theme.base);
|
const base = [lightTheme, darkTheme].find(x => x.id == _themes.base);
|
||||||
_theme.vars = Object.assign({}, base.vars, _theme.vars);
|
_themes.vars = Object.assign({}, base.vars, _themes.vars);
|
||||||
_theme.props = Object.assign({}, base.props, _theme.props);
|
_themes.props = Object.assign({}, base.props, _themes.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = compile(_theme);
|
const props = compile(_themes);
|
||||||
|
|
||||||
for (const [k, v] of Object.entries(props)) {
|
for (const [k, v] of Object.entries(props)) {
|
||||||
document.documentElement.style.setProperty(`--${k}`, v.toString());
|
document.documentElement.style.setProperty(`--${k}`, v.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (persisted) {
|
if (persisted) {
|
||||||
localStorage.setItem('theme', JSON.stringify(props));
|
localStorage.setItem('themes', JSON.stringify(props));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function compile(theme: Theme): { [key: string]: string } {
|
function compile(themes: Theme): { [key: string]: string } {
|
||||||
function getColor(code: string): tinycolor.Instance {
|
function getColor(code: string): tinycolor.Instance {
|
||||||
// ref
|
// ref
|
||||||
if (code[0] == '@') {
|
if (code[0] == '@') {
|
||||||
return getColor(theme.props[code.substr(1)]);
|
return getColor(themes.props[code.substr(1)]);
|
||||||
}
|
}
|
||||||
if (code[0] == '$') {
|
if (code[0] == '$') {
|
||||||
return getColor(theme.vars[code.substr(1)]);
|
return getColor(themes.vars[code.substr(1)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// func
|
// func
|
||||||
@@ -98,7 +98,7 @@ function compile(theme: Theme): { [key: string]: string } {
|
|||||||
|
|
||||||
const props = {};
|
const props = {};
|
||||||
|
|
||||||
for (const [k, v] of Object.entries(theme.props)) {
|
for (const [k, v] of Object.entries(themes.props)) {
|
||||||
props[k] = genValue(getColor(v));
|
props[k] = genValue(getColor(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
id: 'bb5a8287-a072-4b0a-8ae5-ea2a0d33f4f2',
|
|
||||||
|
|
||||||
name: 'Future',
|
|
||||||
author: 'syuilo',
|
|
||||||
|
|
||||||
base: 'dark',
|
|
||||||
|
|
||||||
vars: {
|
|
||||||
primary: 'rgb(94, 158, 185)',
|
|
||||||
secondary: 'rgb(22, 24, 30)',
|
|
||||||
text: 'rgb(214, 218, 224)',
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
renoteGradient: '#0a2d3c',
|
|
||||||
renoteText: '$primary',
|
|
||||||
quoteBorder: '$primary',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -153,6 +153,8 @@
|
|||||||
messagingRoomMessageBg: '$secondary',
|
messagingRoomMessageBg: '$secondary',
|
||||||
messagingRoomMessageFg: '#fff',
|
messagingRoomMessageFg: '#fff',
|
||||||
|
|
||||||
|
driveFileIcon: '$text',
|
||||||
|
|
||||||
formButtonBorder: 'rgba(255, 255, 255, 0.1)',
|
formButtonBorder: 'rgba(255, 255, 255, 0.1)',
|
||||||
formButtonHoverBg: ':alpha<0.2<$primary',
|
formButtonHoverBg: ':alpha<0.2<$primary',
|
||||||
formButtonHoverBorder: ':alpha<0.5<$primary',
|
formButtonHoverBorder: ':alpha<0.5<$primary',
|
||||||
39
src/client/themes/future.json5
Normal file
39
src/client/themes/future.json5
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
id: 'bb5a8287-a072-4b0a-8ae5-ea2a0d33f4f2',
|
||||||
|
|
||||||
|
name: 'Future',
|
||||||
|
author: 'syuilo',
|
||||||
|
desc: 'Sci-fi flavored',
|
||||||
|
|
||||||
|
base: 'dark',
|
||||||
|
|
||||||
|
vars: {
|
||||||
|
c0: '#0e0e0e',
|
||||||
|
c1: 'rgb(255, 105, 78)',
|
||||||
|
c2: 'rgb(99, 197, 210)',
|
||||||
|
c4: 'rgb(253, 254, 214)',
|
||||||
|
c3: 'rgb(204, 254, 253)',
|
||||||
|
primary: '$c1',
|
||||||
|
secondary: '#191919',
|
||||||
|
text: '$c3',
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
bg: '$c0',
|
||||||
|
noteText: '$c4',
|
||||||
|
noteHeaderAcct: ':alpha<0.65<$c4',
|
||||||
|
noteHeaderInfo: ':alpha<0.5<$c4',
|
||||||
|
subNoteText: ':alpha<0.7<$c4',
|
||||||
|
renoteGradient: 'rgba(0, 0, 0, 0)',
|
||||||
|
renoteText: '$c2',
|
||||||
|
quoteBorder: '$c2',
|
||||||
|
mfmHashtag: '$c1',
|
||||||
|
mfmUrl: '$c2',
|
||||||
|
mfmLink: '$c2',
|
||||||
|
mfmMention: '$c1',
|
||||||
|
mfmMentionForeground: '#fff',
|
||||||
|
notificationIndicator: '$c2',
|
||||||
|
link: '$c2',
|
||||||
|
desktopHeaderBg: '$secondary',
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -153,6 +153,8 @@
|
|||||||
messagingRoomMessageBg: '#eee',
|
messagingRoomMessageBg: '#eee',
|
||||||
messagingRoomMessageFg: '#333',
|
messagingRoomMessageFg: '#333',
|
||||||
|
|
||||||
|
driveFileIcon: '$text',
|
||||||
|
|
||||||
formButtonBorder: 'rgba(0, 0, 0, 0.1)',
|
formButtonBorder: 'rgba(0, 0, 0, 0.1)',
|
||||||
formButtonHoverBg: ':alpha<0.12<$primary',
|
formButtonHoverBg: ':alpha<0.12<$primary',
|
||||||
formButtonHoverBorder: ':alpha<0.3<$primary',
|
formButtonHoverBorder: ':alpha<0.3<$primary',
|
||||||
@@ -179,7 +181,7 @@
|
|||||||
desktopTimelineSrcHover: ':darken<7<$text',
|
desktopTimelineSrcHover: ':darken<7<$text',
|
||||||
desktopWindowTitle: '$text',
|
desktopWindowTitle: '$text',
|
||||||
desktopWindowShadow: 'rgba(0, 0, 0, 0.2)',
|
desktopWindowShadow: 'rgba(0, 0, 0, 0.2)',
|
||||||
desktopDriveBg: '#fff',
|
desktopDriveBg: '@bg',
|
||||||
desktopDriveFolderBg: ':lighten<31<$primary',
|
desktopDriveFolderBg: ':lighten<31<$primary',
|
||||||
desktopDriveFolderHoverBg: ':lighten<27<$primary',
|
desktopDriveFolderHoverBg: ':lighten<27<$primary',
|
||||||
desktopDriveFolderActiveBg: ':lighten<25<$primary',
|
desktopDriveFolderActiveBg: ':lighten<25<$primary',
|
||||||
File diff suppressed because one or more lines are too long
6
src/misc/content-disposition.ts
Normal file
6
src/misc/content-disposition.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const cd = require('content-disposition');
|
||||||
|
|
||||||
|
export function contentDisposition(type: 'inline' | 'attachment', filename: string): string {
|
||||||
|
const fallback = filename.replace(/[^\w.-]/g, '_');
|
||||||
|
return cd(filename, { type, fallback });
|
||||||
|
}
|
||||||
1
src/misc/emoji-regex.ts
Normal file
1
src/misc/emoji-regex.ts
Normal file
File diff suppressed because one or more lines are too long
@@ -13,6 +13,7 @@ const defaultMeta: any = {
|
|||||||
originalUsersCount: 0
|
originalUsersCount: 0
|
||||||
},
|
},
|
||||||
maxNoteTextLength: 1000,
|
maxNoteTextLength: 1000,
|
||||||
|
enableEmojiReaction: true,
|
||||||
enableTwitterIntegration: false,
|
enableTwitterIntegration: false,
|
||||||
enableGithubIntegration: false,
|
enableGithubIntegration: false,
|
||||||
enableDiscordIntegration: false,
|
enableDiscordIntegration: false,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export default function(reaction: string): string {
|
|||||||
case 'confused': return '😥';
|
case 'confused': return '😥';
|
||||||
case 'rip': return '😇';
|
case 'rip': return '😇';
|
||||||
case 'pudding': return '🍮';
|
case 'pudding': return '🍮';
|
||||||
default: return '';
|
case 'star': return '⭐';
|
||||||
|
default: return reaction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
61
src/misc/reaction-lib.ts
Normal file
61
src/misc/reaction-lib.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import Emoji from '../models/emoji';
|
||||||
|
import { emojiRegex } from './emoji-regex';
|
||||||
|
import fetchMeta from './fetch-meta';
|
||||||
|
|
||||||
|
const basic10: Record<string, string> = {
|
||||||
|
'👍': 'like',
|
||||||
|
'❤': 'love', // ここに記述する場合は異体字セレクタを入れない
|
||||||
|
'😆': 'laugh',
|
||||||
|
'🤔': 'hmm',
|
||||||
|
'😮': 'surprise',
|
||||||
|
'🎉': 'congrats',
|
||||||
|
'💢': 'angry',
|
||||||
|
'😥': 'confused',
|
||||||
|
'😇': 'rip',
|
||||||
|
'🍮': 'pudding',
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getFallbackReaction(): Promise<string> {
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
return meta.useStarForReactionFallback ? 'star' : 'like';
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function toDbReaction(reaction: string, enableEmoji = true): Promise<string> {
|
||||||
|
if (reaction == null) return await getFallbackReaction();
|
||||||
|
|
||||||
|
// 既存の文字列リアクションはそのまま
|
||||||
|
if (Object.values(basic10).includes(reaction)) return reaction;
|
||||||
|
|
||||||
|
if (!enableEmoji) return await getFallbackReaction();
|
||||||
|
|
||||||
|
// Unicode絵文字
|
||||||
|
const match = emojiRegex.exec(reaction);
|
||||||
|
if (match) {
|
||||||
|
// 合字を含む1つの絵文字
|
||||||
|
const unicode = match[0];
|
||||||
|
|
||||||
|
// 異体字セレクタ除去後の絵文字
|
||||||
|
const normalized = unicode.match('\u200d') ? unicode : unicode.replace(/\ufe0f/g, '');
|
||||||
|
|
||||||
|
// Unicodeプリンは寿司化不能とするため文字列化しない
|
||||||
|
if (normalized === '🍮') return normalized;
|
||||||
|
|
||||||
|
// プリン以外の既存のリアクションは文字列化する
|
||||||
|
if (basic10[normalized]) return basic10[normalized];
|
||||||
|
|
||||||
|
// それ以外はUnicodeのまま
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
const custom = reaction.match(/^:([\w+-]+):$/);
|
||||||
|
if (custom) {
|
||||||
|
const emoji = await Emoji.findOne({
|
||||||
|
host: null,
|
||||||
|
name: custom[1],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (emoji) return reaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await getFallbackReaction();
|
||||||
|
}
|
||||||
4
src/misc/twemoji-base.ts
Normal file
4
src/misc/twemoji-base.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export const twemojiBase = 'https://cdn.jsdelivr.net/npm/twemoji@11.3.0';
|
||||||
|
// https://cdn.jsdelivr.net/npm/twemoji@11.3.0
|
||||||
|
// https://cdnjs.cloudflare.com/ajax/libs/twemoji/11.3.0
|
||||||
|
// https://twemoji.maxcdn.com
|
||||||
@@ -194,6 +194,8 @@ export type IMeta = {
|
|||||||
disableRegistration?: boolean;
|
disableRegistration?: boolean;
|
||||||
disableLocalTimeline?: boolean;
|
disableLocalTimeline?: boolean;
|
||||||
disableGlobalTimeline?: boolean;
|
disableGlobalTimeline?: boolean;
|
||||||
|
enableEmojiReaction?: boolean;
|
||||||
|
useStarForReactionFallback?: boolean;
|
||||||
hidedTags?: string[];
|
hidedTags?: string[];
|
||||||
mascotImageUrl?: string;
|
mascotImageUrl?: string;
|
||||||
bannerUrl?: string;
|
bannerUrl?: string;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as mongo from 'mongodb';
|
import * as mongo from 'mongodb';
|
||||||
import $ from 'cafy';
|
|
||||||
import * as deepcopy from 'deepcopy';
|
import * as deepcopy from 'deepcopy';
|
||||||
import db from '../db/mongodb';
|
import db from '../db/mongodb';
|
||||||
import isObjectId from '../misc/is-objectid';
|
import isObjectId from '../misc/is-objectid';
|
||||||
@@ -20,19 +19,6 @@ export interface INoteReaction {
|
|||||||
reaction: string;
|
reaction: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const validateReaction = $.str.or([
|
|
||||||
'like',
|
|
||||||
'love',
|
|
||||||
'laugh',
|
|
||||||
'hmm',
|
|
||||||
'surprise',
|
|
||||||
'congrats',
|
|
||||||
'angry',
|
|
||||||
'confused',
|
|
||||||
'rip',
|
|
||||||
'pudding'
|
|
||||||
]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pack a reaction for API response
|
* Pack a reaction for API response
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { packMany as packFileMany, IDriveFile } from './drive-file';
|
|||||||
import Following from './following';
|
import Following from './following';
|
||||||
import Emoji from './emoji';
|
import Emoji from './emoji';
|
||||||
import { dbLogger } from '../db/logger';
|
import { dbLogger } from '../db/logger';
|
||||||
|
import { unique, concat } from '../prelude/array';
|
||||||
|
|
||||||
const Note = db.get<INote>('notes');
|
const Note = db.get<INote>('notes');
|
||||||
Note.createIndex('uri', { sparse: true, unique: true });
|
Note.createIndex('uri', { sparse: true, unique: true });
|
||||||
@@ -242,6 +243,11 @@ export const pack = async (
|
|||||||
|
|
||||||
const id = _note._id;
|
const id = _note._id;
|
||||||
|
|
||||||
|
// Some counts
|
||||||
|
_note.renoteCount = _note.renoteCount || 0;
|
||||||
|
_note.repliesCount = _note.repliesCount || 0;
|
||||||
|
_note.reactionCounts = _note.reactionCounts || {};
|
||||||
|
|
||||||
// _note._userを消す前か、_note.userを解決した後でないとホストがわからない
|
// _note._userを消す前か、_note.userを解決した後でないとホストがわからない
|
||||||
if (_note._user) {
|
if (_note._user) {
|
||||||
const host = _note._user.host;
|
const host = _note._user.host;
|
||||||
@@ -253,6 +259,8 @@ export const pack = async (
|
|||||||
fields: { _id: false }
|
fields: { _id: false }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
_note.emojis = unique(concat([_note.emojis, Object.keys(_note.reactionCounts)]));
|
||||||
|
|
||||||
_note.emojis = Emoji.find({
|
_note.emojis = Emoji.find({
|
||||||
name: { $in: _note.emojis },
|
name: { $in: _note.emojis },
|
||||||
host: host
|
host: host
|
||||||
@@ -290,11 +298,6 @@ export const pack = async (
|
|||||||
// Populate files
|
// Populate files
|
||||||
_note.files = packFileMany(_note.fileIds || []);
|
_note.files = packFileMany(_note.fileIds || []);
|
||||||
|
|
||||||
// Some counts
|
|
||||||
_note.renoteCount = _note.renoteCount || 0;
|
|
||||||
_note.repliesCount = _note.repliesCount || 0;
|
|
||||||
_note.reactionCounts = _note.reactionCounts || {};
|
|
||||||
|
|
||||||
// 後方互換性のため
|
// 後方互換性のため
|
||||||
_note.mediaIds = _note.fileIds;
|
_note.mediaIds = _note.fileIds;
|
||||||
_note.media = _note.files;
|
_note.media = _note.files;
|
||||||
|
|||||||
@@ -5,3 +5,7 @@ export function query(obj: {}): string {
|
|||||||
.filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined)
|
.filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined)
|
||||||
.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>));
|
.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function appendQuery(url: string, query: string): string {
|
||||||
|
return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import Note from '../../../models/note';
|
|||||||
import { IRemoteUser } from '../../../models/user';
|
import { IRemoteUser } from '../../../models/user';
|
||||||
import { ILike } from '../type';
|
import { ILike } from '../type';
|
||||||
import create from '../../../services/note/reaction/create';
|
import create from '../../../services/note/reaction/create';
|
||||||
import { validateReaction } from '../../../models/note-reaction';
|
|
||||||
|
|
||||||
export default async (actor: IRemoteUser, activity: ILike) => {
|
export default async (actor: IRemoteUser, activity: ILike) => {
|
||||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
||||||
@@ -18,12 +17,5 @@ export default async (actor: IRemoteUser, activity: ILike) => {
|
|||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
let reaction = 'like';
|
await create(actor, note, activity._misskey_reaction);
|
||||||
|
|
||||||
// 他のMisskeyインスタンスからのリアクション
|
|
||||||
if (activity._misskey_reaction && validateReaction.ok(activity._misskey_reaction)) {
|
|
||||||
reaction = activity._misskey_reaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
await create(actor, note, reaction);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,6 +41,20 @@ export const meta = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
enableEmojiReaction: {
|
||||||
|
validator: $.optional.nullable.bool,
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '絵文字リアクションを有効にするか否か'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
useStarForReactionFallback: {
|
||||||
|
validator: $.optional.nullable.bool,
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '不明なリアクションのフォールバックに star リアクションを使うか'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
hidedTags: {
|
hidedTags: {
|
||||||
validator: $.optional.nullable.arr($.str),
|
validator: $.optional.nullable.arr($.str),
|
||||||
desc: {
|
desc: {
|
||||||
@@ -351,6 +365,14 @@ export default define(meta, async (ps) => {
|
|||||||
set.disableGlobalTimeline = ps.disableGlobalTimeline;
|
set.disableGlobalTimeline = ps.disableGlobalTimeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof ps.enableEmojiReaction === 'boolean') {
|
||||||
|
set.enableEmojiReaction = ps.enableEmojiReaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof ps.useStarForReactionFallback === 'boolean') {
|
||||||
|
set.useStarForReactionFallback = ps.useStarForReactionFallback;
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(ps.hidedTags)) {
|
if (Array.isArray(ps.hidedTags)) {
|
||||||
set.hidedTags = ps.hidedTags;
|
set.hidedTags = ps.hidedTags;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ export const meta = {
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'Whether disabled GTL.',
|
description: 'Whether disabled GTL.',
|
||||||
},
|
},
|
||||||
|
enableEmojiReaction: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Whether enabled emoji reaction.',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -107,6 +111,7 @@ export default define(meta, async (ps, me) => {
|
|||||||
disableRegistration: instance.disableRegistration,
|
disableRegistration: instance.disableRegistration,
|
||||||
disableLocalTimeline: instance.disableLocalTimeline,
|
disableLocalTimeline: instance.disableLocalTimeline,
|
||||||
disableGlobalTimeline: instance.disableGlobalTimeline,
|
disableGlobalTimeline: instance.disableGlobalTimeline,
|
||||||
|
enableEmojiReaction: instance.enableEmojiReaction,
|
||||||
driveCapacityPerLocalUserMb: instance.localDriveCapacityMb,
|
driveCapacityPerLocalUserMb: instance.localDriveCapacityMb,
|
||||||
driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb,
|
driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb,
|
||||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||||
@@ -147,6 +152,7 @@ export default define(meta, async (ps, me) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (me && (me.isAdmin || me.isModerator)) {
|
if (me && (me.isAdmin || me.isModerator)) {
|
||||||
|
response.useStarForReactionFallback = instance.useStarForReactionFallback;
|
||||||
response.hidedTags = instance.hidedTags;
|
response.hidedTags = instance.hidedTags;
|
||||||
response.recaptchaSecretKey = instance.recaptchaSecretKey;
|
response.recaptchaSecretKey = instance.recaptchaSecretKey;
|
||||||
response.proxyAccount = instance.proxyAccount;
|
response.proxyAccount = instance.proxyAccount;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import ID, { transform } from '../../../../../misc/cafy-id';
|
import ID, { transform } from '../../../../../misc/cafy-id';
|
||||||
import createReaction from '../../../../../services/note/reaction/create';
|
import createReaction from '../../../../../services/note/reaction/create';
|
||||||
import { validateReaction } from '../../../../../models/note-reaction';
|
|
||||||
import define from '../../../define';
|
import define from '../../../define';
|
||||||
import { getNote } from '../../../common/getters';
|
import { getNote } from '../../../common/getters';
|
||||||
import { ApiError } from '../../../error';
|
import { ApiError } from '../../../error';
|
||||||
@@ -30,7 +29,7 @@ export const meta = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
reaction: {
|
reaction: {
|
||||||
validator: $.str.pipe(validateReaction.ok),
|
validator: $.str,
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': 'リアクションの種類'
|
'ja-JP': 'リアクションの種類'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const meta = {
|
|||||||
|
|
||||||
limit: {
|
limit: {
|
||||||
duration: ms('1hour'),
|
duration: ms('1hour'),
|
||||||
max: 5,
|
max: 60,
|
||||||
minInterval: ms('3sec')
|
minInterval: ms('3sec')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -392,7 +392,8 @@ export const schemas = {
|
|||||||
'angry',
|
'angry',
|
||||||
'confused',
|
'confused',
|
||||||
'rip',
|
'rip',
|
||||||
'pudding'
|
'pudding',
|
||||||
|
'star'
|
||||||
],
|
],
|
||||||
description: 'The reaction type.'
|
description: 'The reaction type.'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import * as Koa from 'koa';
|
import * as Koa from 'koa';
|
||||||
import * as send from 'koa-send';
|
import * as send from 'koa-send';
|
||||||
import * as mongodb from 'mongodb';
|
import * as mongodb from 'mongodb';
|
||||||
|
import * as rename from 'rename';
|
||||||
import DriveFile, { getDriveFileBucket } from '../../models/drive-file';
|
import DriveFile, { getDriveFileBucket } from '../../models/drive-file';
|
||||||
import DriveFileThumbnail, { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
|
import DriveFileThumbnail, { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
|
||||||
import DriveFileWebpublic, { getDriveFileWebpublicBucket } from '../../models/drive-file-webpublic';
|
import DriveFileWebpublic, { getDriveFileWebpublicBucket } from '../../models/drive-file-webpublic';
|
||||||
import { serverLogger } from '..';
|
import { serverLogger } from '..';
|
||||||
|
import { contentDisposition } from '../../misc/content-disposition';
|
||||||
|
|
||||||
const assets = `${__dirname}/../../server/file/assets/`;
|
const assets = `${__dirname}/../../server/file/assets/`;
|
||||||
|
|
||||||
@@ -62,10 +64,12 @@ export default async function(ctx: Koa.BaseContext) {
|
|||||||
|
|
||||||
if (thumb != null) {
|
if (thumb != null) {
|
||||||
ctx.set('Content-Type', 'image/jpeg');
|
ctx.set('Content-Type', 'image/jpeg');
|
||||||
|
ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.filename, { suffix: '-thumb', extname: '.jpeg' })}"`));
|
||||||
const bucket = await getDriveFileThumbnailBucket();
|
const bucket = await getDriveFileThumbnailBucket();
|
||||||
ctx.body = bucket.openDownloadStream(thumb._id);
|
ctx.body = bucket.openDownloadStream(thumb._id);
|
||||||
} else {
|
} else {
|
||||||
if (file.contentType.startsWith('image/')) {
|
if (file.contentType.startsWith('image/')) {
|
||||||
|
ctx.set('Content-Disposition', contentDisposition('inline', `${file.filename}"`));
|
||||||
await sendRaw();
|
await sendRaw();
|
||||||
} else {
|
} else {
|
||||||
ctx.status = 404;
|
ctx.status = 404;
|
||||||
@@ -79,15 +83,17 @@ export default async function(ctx: Koa.BaseContext) {
|
|||||||
|
|
||||||
if (web != null) {
|
if (web != null) {
|
||||||
ctx.set('Content-Type', file.contentType);
|
ctx.set('Content-Type', file.contentType);
|
||||||
|
ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.filename, { suffix: '-web' })}"`));
|
||||||
|
|
||||||
const bucket = await getDriveFileWebpublicBucket();
|
const bucket = await getDriveFileWebpublicBucket();
|
||||||
ctx.body = bucket.openDownloadStream(web._id);
|
ctx.body = bucket.openDownloadStream(web._id);
|
||||||
} else {
|
} else {
|
||||||
|
ctx.set('Content-Disposition', contentDisposition('inline', `${file.filename}"`));
|
||||||
await sendRaw();
|
await sendRaw();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ('download' in ctx.query) {
|
if ('download' in ctx.query) {
|
||||||
ctx.set('Content-Disposition', 'attachment');
|
ctx.set('Content-Disposition', contentDisposition('attachment', `${file.filename}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendRaw();
|
await sendRaw();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { driveLogger } from './logger';
|
|||||||
import { IImage, ConvertToJpeg, ConvertToWebp, ConvertToPng } from './image-processor';
|
import { IImage, ConvertToJpeg, ConvertToWebp, ConvertToPng } from './image-processor';
|
||||||
import Instance from '../../models/instance';
|
import Instance from '../../models/instance';
|
||||||
import checkSvg from '../../misc/check-svg';
|
import checkSvg from '../../misc/check-svg';
|
||||||
|
import { contentDisposition } from '../../misc/content-disposition';
|
||||||
|
|
||||||
const logger = driveLogger.createSubLogger('register', 'yellow');
|
const logger = driveLogger.createSubLogger('register', 'yellow');
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ async function save(path: string, name: string, type: string, hash: string, size
|
|||||||
//#region Uploads
|
//#region Uploads
|
||||||
logger.info(`uploading original: ${key}`);
|
logger.info(`uploading original: ${key}`);
|
||||||
const uploads = [
|
const uploads = [
|
||||||
upload(key, fs.createReadStream(path), type)
|
upload(key, fs.createReadStream(path), type, name)
|
||||||
];
|
];
|
||||||
|
|
||||||
if (alts.webpublic) {
|
if (alts.webpublic) {
|
||||||
@@ -77,7 +78,7 @@ async function save(path: string, name: string, type: string, hash: string, size
|
|||||||
webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
|
webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
|
||||||
|
|
||||||
logger.info(`uploading webpublic: ${webpublicKey}`);
|
logger.info(`uploading webpublic: ${webpublicKey}`);
|
||||||
uploads.push(upload(webpublicKey, alts.webpublic.data, alts.webpublic.type));
|
uploads.push(upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alts.thumbnail) {
|
if (alts.thumbnail) {
|
||||||
@@ -198,13 +199,17 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
|
|||||||
/**
|
/**
|
||||||
* Upload to ObjectStorage
|
* Upload to ObjectStorage
|
||||||
*/
|
*/
|
||||||
async function upload(key: string, stream: fs.ReadStream | Buffer, type: string) {
|
async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) {
|
||||||
const minio = new Minio.Client(config.drive.config);
|
const minio = new Minio.Client(config.drive.config);
|
||||||
|
|
||||||
await minio.putObject(config.drive.bucket, key, stream, null, {
|
const metadata = {
|
||||||
'Content-Type': type,
|
'Content-Type': type,
|
||||||
'Cache-Control': 'max-age=31536000, immutable'
|
'Cache-Control': 'max-age=31536000, immutable'
|
||||||
});
|
} as Minio.ItemBucketMetadata;
|
||||||
|
|
||||||
|
if (filename) metadata['Content-Disposition'] = contentDisposition('inline', filename);
|
||||||
|
|
||||||
|
await minio.putObject(config.drive.bucket, key, stream, null, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { deliver } from '../../../queue';
|
|||||||
import { renderActivity } from '../../../remote/activitypub/renderer';
|
import { renderActivity } from '../../../remote/activitypub/renderer';
|
||||||
import perUserReactionsChart from '../../../services/chart/per-user-reactions';
|
import perUserReactionsChart from '../../../services/chart/per-user-reactions';
|
||||||
import { IdentifiableError } from '../../../misc/identifiable-error';
|
import { IdentifiableError } from '../../../misc/identifiable-error';
|
||||||
|
import { toDbReaction } from '../../../misc/reaction-lib';
|
||||||
|
import fetchMeta from '../../../misc/fetch-meta';
|
||||||
|
|
||||||
export default async (user: IUser, note: INote, reaction: string) => {
|
export default async (user: IUser, note: INote, reaction: string) => {
|
||||||
// Myself
|
// Myself
|
||||||
@@ -17,6 +19,9 @@ export default async (user: IUser, note: INote, reaction: string) => {
|
|||||||
throw new IdentifiableError('2d8e7297-1873-4c00-8404-792c68d7bef0', 'cannot react to my note');
|
throw new IdentifiableError('2d8e7297-1873-4c00-8404-792c68d7bef0', 'cannot react to my note');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
reaction = await toDbReaction(reaction, meta.enableEmojiReaction);
|
||||||
|
|
||||||
// Create reaction
|
// Create reaction
|
||||||
await NoteReaction.insert({
|
await NoteReaction.insert({
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
|||||||
91
test/reaction-lib.ts
Normal file
91
test/reaction-lib.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Tests of MFM
|
||||||
|
*
|
||||||
|
* How to run the tests:
|
||||||
|
* > mocha test/reaction-lib.ts --require ts-node/register
|
||||||
|
*
|
||||||
|
* To specify test:
|
||||||
|
* > mocha test/reaction-lib.ts --require ts-node/register -g 'test name'
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as assert from 'assert';
|
||||||
|
|
||||||
|
import { toDbReaction } from '../src/misc/reaction-lib';
|
||||||
|
|
||||||
|
describe('toDbReaction', async () => {
|
||||||
|
it('既存の文字列リアクションはそのまま', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('like'), 'like');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Unicodeプリンは寿司化不能とするため文字列化しない', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('🍮'), '🍮');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('プリン以外の既存のリアクションは文字列化する like', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('👍'), 'like');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('プリン以外の既存のリアクションは文字列化する love', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('❤️'), 'love');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('プリン以外の既存のリアクションは文字列化する love 異体字セレクタなし', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('❤'), 'love');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('プリン以外の既存のリアクションは文字列化する laugh', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('😆'), 'laugh');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('プリン以外の既存のリアクションは文字列化する hmm', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('🤔'), 'hmm');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('プリン以外の既存のリアクションは文字列化する surprise', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('😮'), 'surprise');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('プリン以外の既存のリアクションは文字列化する congrats', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('🎉'), 'congrats');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('プリン以外の既存のリアクションは文字列化する angry', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('💢'), 'angry');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('プリン以外の既存のリアクションは文字列化する confused', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('😥'), 'confused');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('プリン以外の既存のリアクションは文字列化する rip', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('😇'), 'rip');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('それ以外はUnicodeのまま', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('🍅'), '🍅');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('異体字セレクタ除去', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('㊗️'), '㊗');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('異体字セレクタ除去 必要なし', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('㊗'), '㊗');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fallback - undefined', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction(undefined), 'like');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fallback - null', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction(null), 'like');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fallback - empty', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction(''), 'like');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fallback - unknown', async () => {
|
||||||
|
assert.strictEqual(await toDbReaction('unknown'), 'like');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user