Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71bada97df | ||
|
|
62509edcbe | ||
|
|
f97cdfaa20 | ||
|
|
67ec10e86d | ||
|
|
7d599a68ea | ||
|
|
7ccff732b8 | ||
|
|
7587c896d5 | ||
|
|
91297f1ab3 | ||
|
|
d872a16fe0 | ||
|
|
60aa35adf8 | ||
|
|
5035b66773 | ||
|
|
fa9da8ecab | ||
|
|
1f9bca7188 | ||
|
|
ffa5bdeb50 | ||
|
|
e6bfb7398e | ||
|
|
6def0c776f | ||
|
|
24bae9eaed | ||
|
|
fb5175a283 | ||
|
|
6e49437154 | ||
|
|
2511ed56ac | ||
|
|
c4bfc99cf5 | ||
|
|
4efe38440d | ||
|
|
4a5f2c3c40 | ||
|
|
109738ccb9 | ||
|
|
433dbe179d | ||
|
|
b21b33831a | ||
|
|
020cc471da | ||
|
|
43b47c4494 | ||
|
|
8751d91794 | ||
|
|
374b276f5c | ||
|
|
6138a74231 | ||
|
|
25438c4d64 | ||
|
|
ae6ce19886 | ||
|
|
e17a9bfd6f | ||
|
|
dc2055f5bc | ||
|
|
afeb8058b1 | ||
|
|
9299f99ac3 | ||
|
|
858fc7ebcc | ||
|
|
35089c65d3 | ||
|
|
643ca42829 | ||
|
|
935dc4fe33 | ||
|
|
3a9e74feb1 | ||
|
|
92e66fbf0c | ||
|
|
a50515f569 | ||
|
|
2f8f47acea | ||
|
|
dcb296db93 | ||
|
|
0bdae9ede7 | ||
|
|
11290c2a0f | ||
|
|
428b8f8669 | ||
|
|
7ced10f84e | ||
|
|
8ac54139c9 | ||
|
|
32afe77a26 | ||
|
|
6db8e33662 | ||
|
|
569561f247 | ||
|
|
d132d82acf | ||
|
|
9ba0db9372 | ||
|
|
5d468b542d | ||
|
|
32273165c7 | ||
|
|
46fdb75bf4 | ||
|
|
baf381814b | ||
|
|
e90387c14e | ||
|
|
876790d499 | ||
|
|
8b56edda4b | ||
|
|
33352256d6 | ||
|
|
e368ef11fa | ||
|
|
045f7c3185 | ||
|
|
bf40e5a5c5 | ||
|
|
cda3635d97 | ||
|
|
2eb561f132 | ||
|
|
b5f6465d61 | ||
|
|
9725076c46 |
@@ -112,7 +112,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
enable-animations: "アニメーションを使用"
|
||||
reduce-motion: "UIの動きを減らす"
|
||||
this-setting-is-this-device-only: "このデバイスのみ"
|
||||
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||
reversi:
|
||||
@@ -155,7 +155,9 @@ common:
|
||||
home: "ホーム"
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
@@ -807,7 +809,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "おかえりなさい、"
|
||||
adjective: "さん"
|
||||
@@ -1132,6 +1139,7 @@ mobile/views/pages/home.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
||||
@@ -112,7 +112,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
enable-animations: "アニメーションを使用"
|
||||
reduce-motion: "UIの動きを減らす"
|
||||
this-setting-is-this-device-only: "このデバイスのみ"
|
||||
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||
reversi:
|
||||
@@ -155,7 +155,9 @@ common:
|
||||
home: "Startseite"
|
||||
local: "Lokal"
|
||||
hybrid: "ソーシャル"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "Global"
|
||||
mentions: "あなた宛て"
|
||||
notifications: "Mitteilungen"
|
||||
list: "Listen"
|
||||
swap-left: "Nach links"
|
||||
@@ -807,7 +809,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "Lokal"
|
||||
hybrid: "ソーシャル"
|
||||
global: "Global"
|
||||
mentions: "あなた宛て"
|
||||
list: "Listen"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "おかえりなさい、"
|
||||
adjective: "さん"
|
||||
@@ -1132,6 +1139,7 @@ mobile/views/pages/home.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
||||
@@ -112,7 +112,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "Do not omit the hostname from the username"
|
||||
enable-animations: "Enable animations"
|
||||
reduce-motion: "Reduce motion in UI"
|
||||
this-setting-is-this-device-only: "Only for this device"
|
||||
do-not-use-in-production: 'As this is for development, do not use this in production.'
|
||||
reversi:
|
||||
@@ -155,7 +155,9 @@ common:
|
||||
home: "Home"
|
||||
local: "Local"
|
||||
hybrid: "Social"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
notifications: "Notifications"
|
||||
list: "Lists"
|
||||
swap-left: "Move to the left"
|
||||
@@ -807,7 +809,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "Local"
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
list: "Lists"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "Welcome back,"
|
||||
adjective: "-san"
|
||||
@@ -1132,6 +1139,7 @@ mobile/views/pages/home.vue:
|
||||
local: "Local"
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "No posts \"{}\" found."
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
||||
@@ -112,7 +112,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
enable-animations: "アニメーションを使用"
|
||||
reduce-motion: "UIの動きを減らす"
|
||||
this-setting-is-this-device-only: "このデバイスのみ"
|
||||
do-not-use-in-production: 'Esto está en desarrollo, no usarlo para producción.'
|
||||
reversi:
|
||||
@@ -155,7 +155,9 @@ common:
|
||||
home: "Inicio"
|
||||
local: "Local"
|
||||
hybrid: "Social"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "Global"
|
||||
mentions: "あなた宛て"
|
||||
notifications: "Notificaciones"
|
||||
list: "Listado"
|
||||
swap-left: "Desplazar a la izq."
|
||||
@@ -807,7 +809,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "Bienvenido/a de vuelta,"
|
||||
adjective: "-san"
|
||||
@@ -1132,6 +1139,7 @@ mobile/views/pages/home.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
||||
@@ -112,7 +112,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
enable-animations: "アニメーションを使用"
|
||||
reduce-motion: "Réduire les animations dans l’interface utilisateur"
|
||||
this-setting-is-this-device-only: "Uniquement sur cet appareil"
|
||||
do-not-use-in-production: 'Il s’agit d’une version de développement. Ne pas utiliser dans un environnement de production.'
|
||||
reversi:
|
||||
@@ -155,7 +155,9 @@ common:
|
||||
home: "Accueil"
|
||||
local: "Local"
|
||||
hybrid: "Social"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
notifications: "Notifications"
|
||||
list: "Liste"
|
||||
swap-left: "Déplacer à gauche"
|
||||
@@ -259,8 +261,8 @@ common/views/components/connect-failed.troubleshooter.vue:
|
||||
flush: "Vider le cache"
|
||||
set-version: "Choisissez une version"
|
||||
common/views/components/media-banner.vue:
|
||||
sensitive: "閲覧注意"
|
||||
click-to-show: "クリックして表示"
|
||||
sensitive: "Contenu sensible"
|
||||
click-to-show: "Cliquer pour afficher"
|
||||
common/views/components/cw-button.vue:
|
||||
hide: "Masquer"
|
||||
show: "Voir plus"
|
||||
@@ -483,7 +485,7 @@ desktop/views/components/charts.vue:
|
||||
drive-files-total: "ドライブのファイル数の累計"
|
||||
network-requests: "Requêtes"
|
||||
network-time: "Temps de réponse"
|
||||
network-usage: "通信量"
|
||||
network-usage: "Traffic"
|
||||
desktop/views/components/choose-file-from-drive-window.vue:
|
||||
choose-file: "Sélection de fichiers"
|
||||
upload: "Téléverser des fichiers à partir de votre ordinateur"
|
||||
@@ -790,7 +792,7 @@ desktop/views/components/settings.profile.vue:
|
||||
birthday: "Date de naissance"
|
||||
save: "Mettre à jour le profil"
|
||||
locked-account: "Protéger votre compte"
|
||||
is-locked: "フォローを承認制にする"
|
||||
is-locked: "Demande d’abonnement en attente d’approbation"
|
||||
other: "Autre"
|
||||
is-bot: "Ce compte est un Bot"
|
||||
is-cat: "Ce compte est un Chat"
|
||||
@@ -807,7 +809,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "Local"
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
list: "Listes"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "Content de vous revoir !"
|
||||
adjective: "さん"
|
||||
@@ -1132,6 +1139,7 @@ mobile/views/pages/home.vue:
|
||||
local: "Local"
|
||||
hybrid: "Social"
|
||||
global: "Global"
|
||||
mentions: "Mentions"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "Pas de message avec un hashtag {} trouvé."
|
||||
mobile/views/pages/welcome.vue:
|
||||
@@ -1172,7 +1180,7 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||
avatar: "Avatar"
|
||||
banner: "Bannière"
|
||||
is-cat: "Ce compte est un Bot"
|
||||
is-locked: "フォローを承認制にする"
|
||||
is-locked: "Demande d’abonnement en attente d’approbation"
|
||||
advanced: "Avancé"
|
||||
privacy: "Vie privée"
|
||||
save: "Mettre à jour le profil"
|
||||
|
||||
@@ -112,7 +112,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
enable-animations: "アニメーションを使用"
|
||||
reduce-motion: "UIの動きを減らす"
|
||||
this-setting-is-this-device-only: "このデバイスのみ"
|
||||
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||
reversi:
|
||||
@@ -155,7 +155,9 @@ common:
|
||||
home: "ホーム"
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
@@ -807,7 +809,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "おかえりなさい、"
|
||||
adjective: "さん"
|
||||
@@ -1132,6 +1139,7 @@ mobile/views/pages/home.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
||||
@@ -119,7 +119,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
enable-animations: "アニメーションを使用"
|
||||
reduce-motion: "UIの動きを減らす"
|
||||
this-setting-is-this-device-only: "このデバイスのみ"
|
||||
|
||||
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||
@@ -166,7 +166,9 @@ common:
|
||||
home: "ホーム"
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
@@ -913,7 +915,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "おかえりなさい、"
|
||||
@@ -1314,6 +1321,7 @@ mobile/views/pages/home.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
|
||||
@@ -112,7 +112,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
enable-animations: "アニメーションを使用"
|
||||
reduce-motion: "UIの動きを減らす"
|
||||
this-setting-is-this-device-only: "このデバイスのみ"
|
||||
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||
reversi:
|
||||
@@ -155,7 +155,9 @@ common:
|
||||
home: "うち"
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動や!"
|
||||
@@ -807,7 +809,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "おかえり、"
|
||||
adjective: "さん"
|
||||
@@ -1132,6 +1139,7 @@ mobile/views/pages/home.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿はあらへんで。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
||||
@@ -112,7 +112,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
enable-animations: "アニメーションを使用"
|
||||
reduce-motion: "UIの動きを減らす"
|
||||
this-setting-is-this-device-only: "このデバイスのみ"
|
||||
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||
reversi:
|
||||
@@ -155,7 +155,9 @@ common:
|
||||
home: "홈"
|
||||
local: "로컬"
|
||||
hybrid: "소셜"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "글로벌"
|
||||
mentions: "あなた宛て"
|
||||
notifications: "통지"
|
||||
list: "목록"
|
||||
swap-left: "左に移動"
|
||||
@@ -807,7 +809,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "おかえりなさい、"
|
||||
adjective: "さん"
|
||||
@@ -1132,6 +1139,7 @@ mobile/views/pages/home.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
||||
@@ -112,7 +112,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
enable-animations: "アニメーションを使用"
|
||||
reduce-motion: "UIの動きを減らす"
|
||||
this-setting-is-this-device-only: "このデバイスのみ"
|
||||
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||
reversi:
|
||||
@@ -155,7 +155,9 @@ common:
|
||||
home: "ホーム"
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
@@ -807,7 +809,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "Lokaal"
|
||||
hybrid: "ソーシャル"
|
||||
global: "Algemeen"
|
||||
mentions: "あなた宛て"
|
||||
list: "Lijsten"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "おかえりなさい、"
|
||||
adjective: "さん"
|
||||
@@ -1132,6 +1139,7 @@ mobile/views/pages/home.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
||||
@@ -112,7 +112,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
enable-animations: "アニメーションを使用"
|
||||
reduce-motion: "UIの動きを減らす"
|
||||
this-setting-is-this-device-only: "このデバイスのみ"
|
||||
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||
reversi:
|
||||
@@ -155,7 +155,9 @@ common:
|
||||
home: "ホーム"
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
@@ -807,7 +809,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "おかえりなさい、"
|
||||
adjective: "さん"
|
||||
@@ -1132,6 +1139,7 @@ mobile/views/pages/home.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
||||
@@ -112,7 +112,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
enable-animations: "アニメーションを使用"
|
||||
reduce-motion: "UIの動きを減らす"
|
||||
this-setting-is-this-device-only: "このデバイスのみ"
|
||||
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||
reversi:
|
||||
@@ -155,7 +155,9 @@ common:
|
||||
home: "Strona główna"
|
||||
local: "Lokalne"
|
||||
hybrid: "Społeczność"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "Globalne"
|
||||
mentions: "あなた宛て"
|
||||
notifications: "Powiadomienia"
|
||||
list: "Listy"
|
||||
swap-left: "Przesuń w lewo"
|
||||
@@ -807,7 +809,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "Lokalne"
|
||||
hybrid: "Społeczność"
|
||||
global: "Globalne"
|
||||
mentions: "あなた宛て"
|
||||
list: "Listy"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "Witaj ponownie,"
|
||||
adjective: "さん"
|
||||
@@ -1132,6 +1139,7 @@ mobile/views/pages/home.vue:
|
||||
local: "Lokalne"
|
||||
hybrid: "Społeczność"
|
||||
global: "Globalne"
|
||||
mentions: "あなた宛て"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "Nie znaleziono wpisów zawierających „{}”."
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
||||
@@ -7,12 +7,12 @@ common:
|
||||
about-title: "Uma ⭐ do fediverso."
|
||||
about: "Obrigado por encontrar Misskey. Uma <b>plataforma descentralizada de microblog</b> nascida na Terra. Já que ela existe no Fediverso (um universo onde várias plataformas de mídia social são organizadas), ela é ligada com outras plataformas.Por que você não tira uma folga do agito e confusão da cidade, e mergulha em uma nova internet?"
|
||||
intro:
|
||||
title: "Misskeyって?"
|
||||
about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
|
||||
features: "特徴"
|
||||
rich-contents: "投稿"
|
||||
rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
|
||||
reaction: "リアクション"
|
||||
title: "O que é Misskey?"
|
||||
about: "Misskey é um <b>serviço de microblog descentralizado</b>. Personalização sofisticada da interface, variedade de reações a posts, armazenamento de arquivos grátis com gerenciamento integrado e outras funções avançadas estão disponíveis. Um sistema em rede chamado \"Fediverso\" permite que nos comuniquemos com usuários em outras redes sociais. Se você postar algo, por exemplo, seu post não será mandado apenas para o Misskey, mas também para o Mastodon. Apenas imagine que o planeta está enviando ondas de rádio para outros planetas para se comunicar."
|
||||
features: "Recursos"
|
||||
rich-contents: "Post"
|
||||
rich-contents-desc: "Apenas poste suas ideias, temas do momento e qualquer coisa que você queira compartilhar. Você pode querer decorar suas palavras, anexar suas imagens favoritas, enviar arquivos, inclusive vídeos ou criar uma enquete. Essas são as coisas que você pode fazer em Misskey."
|
||||
reaction: "Reações"
|
||||
reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
|
||||
ui: "インターフェース"
|
||||
ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
|
||||
@@ -112,7 +112,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
enable-animations: "アニメーションを使用"
|
||||
reduce-motion: "UIの動きを減らす"
|
||||
this-setting-is-this-device-only: "このデバイスのみ"
|
||||
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||
reversi:
|
||||
@@ -155,7 +155,9 @@ common:
|
||||
home: "Início"
|
||||
local: "Local"
|
||||
hybrid: "Social"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "Global"
|
||||
mentions: "あなた宛て"
|
||||
notifications: "Notificações"
|
||||
list: "Listas"
|
||||
swap-left: "Mover para a esquerda"
|
||||
@@ -807,7 +809,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "おかえりなさい、"
|
||||
adjective: "さん"
|
||||
@@ -1132,6 +1139,7 @@ mobile/views/pages/home.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
@@ -1164,23 +1172,23 @@ mobile/views/pages/games/reversi.vue:
|
||||
reversi: "リバーシ"
|
||||
mobile/views/pages/settings/settings.profile.vue:
|
||||
title: "プロフィール"
|
||||
name: "名前"
|
||||
account: "アカウント"
|
||||
location: "場所"
|
||||
description: "自己紹介"
|
||||
birthday: "誕生日"
|
||||
avatar: "アイコン"
|
||||
banner: "バナー"
|
||||
is-cat: "このアカウントはCatです"
|
||||
is-locked: "フォローを承認制にする"
|
||||
advanced: "その他"
|
||||
privacy: "プライバシー"
|
||||
save: "保存"
|
||||
saved: "プロフィールを保存しました"
|
||||
uploading: "アップロード中"
|
||||
upload-failed: "アップロードに失敗しました"
|
||||
name: "Nome"
|
||||
account: "Conta"
|
||||
location: "Lugar"
|
||||
description: "Biografia"
|
||||
birthday: "Data de nascimento"
|
||||
avatar: "Avatar"
|
||||
banner: "Capa"
|
||||
is-cat: "Esta conta é gato"
|
||||
is-locked: "Pedido para seguir precisa ser aprovado"
|
||||
advanced: "Avançado"
|
||||
privacy: "Provacidade"
|
||||
save: "Atualizar perfil"
|
||||
saved: "Perfil atualizado"
|
||||
uploading: "Enviando"
|
||||
upload-failed: "Falha ao enviar"
|
||||
mobile/views/pages/search.vue:
|
||||
search: "検索"
|
||||
search: "Pesquisar"
|
||||
empty: "「{}」に関する投稿は見つかりませんでした。"
|
||||
not-found: "「{}」に関する投稿は見つかりませんでした。"
|
||||
mobile/views/pages/selectdrive.vue:
|
||||
@@ -1217,47 +1225,47 @@ mobile/views/pages/settings.vue:
|
||||
load-raw-images: "添付された画像を高画質で表示する"
|
||||
load-remote-media: "リモートサーバーのメディアを表示する"
|
||||
twitter: "Twitter連携"
|
||||
twitter-connect: "Twitterアカウントに接続する"
|
||||
twitter-reconnect: "再接続する"
|
||||
twitter-disconnect: "切断する"
|
||||
update: "Misskey Update"
|
||||
version: "バージョン:"
|
||||
latest-version: "最新のバージョン:"
|
||||
update-checking: "アップデートを確認中"
|
||||
check-for-updates: "アップデートを確認"
|
||||
no-updates: "利用可能な更新はありません"
|
||||
no-updates-desc: "お使いのMisskeyは最新です。"
|
||||
update-available: "新しいバージョンが利用可能です"
|
||||
update-available-desc: "ページを再度読み込みすると更新が適用されます。"
|
||||
settings: "設定"
|
||||
signout: "サインアウト"
|
||||
sound: "サウンド"
|
||||
enable-sounds: "サウンドを有効にする"
|
||||
twitter-connect: "Conectar à sua conta no Twitter"
|
||||
twitter-reconnect: "Reconectar"
|
||||
twitter-disconnect: "Desconectar"
|
||||
update: "Atualizar Misskey"
|
||||
version: "Versão atual;"
|
||||
latest-version: "Última versão:"
|
||||
update-checking: "Verificando atualizações"
|
||||
check-for-updates: "Verificar atualizações"
|
||||
no-updates: "Sem atualizações"
|
||||
no-updates-desc: "Seu Misskey está atualizado"
|
||||
update-available: "Uma nova versão está disponível"
|
||||
update-available-desc: "Atualizações vão ser aplicadas depois de recarregar a página"
|
||||
settings: "Configurações"
|
||||
signout: "Sair"
|
||||
sound: "Sons"
|
||||
enable-sounds: "Ativar sons"
|
||||
mobile/views/pages/user.vue:
|
||||
follows-you: "フォローされています"
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
notes: "投稿"
|
||||
follows-you: "Te segue"
|
||||
following: "Seguindo"
|
||||
followers: "Seguidores"
|
||||
notes: "Posts"
|
||||
overview: "概要"
|
||||
timeline: "タイムライン"
|
||||
media: "メディア"
|
||||
is-suspended: "このユーザーは凍結されています。"
|
||||
timeline: "Linha do tempo"
|
||||
media: "Mídia"
|
||||
is-suspended: "Esta conta foi suspensa"
|
||||
is-remote: "Este é uma usuário remoto. O perfil que vê aqui pode não estar completo."
|
||||
view-remote: "Ver o perfil completo."
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "Notas recentes"
|
||||
images: "Imagens"
|
||||
activity: "Atividade"
|
||||
keywords: "キーワード"
|
||||
domains: "頻出ドメイン"
|
||||
frequently-replied-users: "よく会話するユーザー"
|
||||
keywords: "Palavras chave"
|
||||
domains: "Domínios"
|
||||
frequently-replied-users: "Perguntas frequentes"
|
||||
followers-you-know: "Seguidores que você conhece"
|
||||
last-used-at: "Ativo pela última vez:"
|
||||
mobile/views/pages/user/home.followers-you-know.vue:
|
||||
loading: "Carregando"
|
||||
no-users: "知り合いのユーザーはいません"
|
||||
mobile/views/pages/user/home.friends.vue:
|
||||
loading: "読み込み中"
|
||||
loading: "Carregando"
|
||||
no-users: "よく会話するユーザーはいません"
|
||||
mobile/views/pages/user/home.notes.vue:
|
||||
loading: "Carregando"
|
||||
@@ -1267,14 +1275,14 @@ mobile/views/pages/user/home.photos.vue:
|
||||
no-photos: "Sem fotos"
|
||||
docs:
|
||||
edit-this-page-on-github: "間違いや改善点を見つけましたか?"
|
||||
edit-this-page-on-github-link: "このページをGitHubで編集"
|
||||
edit-this-page-on-github-link: "Edite esta página no GitHub!"
|
||||
api:
|
||||
entities:
|
||||
properties: "プロパティ"
|
||||
properties: "Propriedades"
|
||||
endpoints:
|
||||
params: "パラメータ"
|
||||
no-params: "パラメータはありません"
|
||||
res: "レスポンス"
|
||||
params: "Parâmetros"
|
||||
no-params: "Sem parâmetros"
|
||||
res: "Resposta"
|
||||
require-credential: "このエンドポイントは認証情報が必須です。"
|
||||
require-permission: "このエンドポイントは{permission}の権限を必要とします。"
|
||||
has-limit: "レートリミットがあります。"
|
||||
|
||||
@@ -112,7 +112,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
enable-animations: "アニメーションを使用"
|
||||
reduce-motion: "UIの動きを減らす"
|
||||
this-setting-is-this-device-only: "このデバイスのみ"
|
||||
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||
reversi:
|
||||
@@ -155,7 +155,9 @@ common:
|
||||
home: "ホーム"
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
@@ -807,7 +809,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "おかえりなさい、"
|
||||
adjective: "さん"
|
||||
@@ -1132,6 +1139,7 @@ mobile/views/pages/home.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
||||
@@ -112,7 +112,7 @@ common:
|
||||
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||
show-full-acct: "ユーザー名のホストを省略しない"
|
||||
enable-animations: "アニメーションを使用"
|
||||
reduce-motion: "UIの動きを減らす"
|
||||
this-setting-is-this-device-only: "このデバイスのみ"
|
||||
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||
reversi:
|
||||
@@ -155,7 +155,9 @@ common:
|
||||
home: "ホーム"
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
hashtag: "ハッシュタグ"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
@@ -807,7 +809,12 @@ desktop/views/components/timeline.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
list: "リスト"
|
||||
hashtag: "ハッシュタグ"
|
||||
add-tag-timeline: "ハッシュタグを追加"
|
||||
add-list: "リストを追加"
|
||||
list-name: "リスト名"
|
||||
desktop/views/components/ui.header.vue:
|
||||
welcome-back: "おかえりなさい、"
|
||||
adjective: "さん"
|
||||
@@ -1132,6 +1139,7 @@ mobile/views/pages/home.vue:
|
||||
local: "ローカル"
|
||||
hybrid: "ソーシャル"
|
||||
global: "グローバル"
|
||||
mentions: "あなた宛て"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||
mobile/views/pages/welcome.vue:
|
||||
|
||||
12
package.json
12
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "8.42.0",
|
||||
"clientVersion": "1.0.9769",
|
||||
"version": "8.45.1",
|
||||
"clientVersion": "1.0.9840",
|
||||
"codename": "nighthike",
|
||||
"main": "./built/index.js",
|
||||
"private": true,
|
||||
@@ -20,10 +20,10 @@
|
||||
"format": "gulp format"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome": "1.1.8",
|
||||
"@fortawesome/fontawesome-free-brands": "5.0.13",
|
||||
"@fortawesome/fontawesome-free-regular": "5.0.13",
|
||||
"@fortawesome/fontawesome-free-solid": "5.0.13",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.4",
|
||||
"@fortawesome/free-brands-svg-icons": "5.3.1",
|
||||
"@fortawesome/free-regular-svg-icons": "5.3.1",
|
||||
"@fortawesome/free-solid-svg-icons": "5.3.1",
|
||||
"@koa/cors": "2.2.2",
|
||||
"@prezzemolo/rap": "0.1.2",
|
||||
"@prezzemolo/zip": "0.0.3",
|
||||
|
||||
13
src/client/app/common/scripts/streaming/hashtag.ts
Normal file
13
src/client/app/common/scripts/streaming/hashtag.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import Stream from './stream';
|
||||
import MiOS from '../../../mios';
|
||||
|
||||
export class HashtagStream extends Stream {
|
||||
constructor(os: MiOS, me, q) {
|
||||
super(os, 'hashtag', me ? {
|
||||
i: me.token,
|
||||
q: JSON.stringify(q)
|
||||
} : {
|
||||
q: JSON.stringify(q)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export default Vue.extend({
|
||||
|
||||
created() {
|
||||
(this as any).api('chart', {
|
||||
limit: 32
|
||||
limit: 35
|
||||
}).then(chart => {
|
||||
this.chart = chart;
|
||||
});
|
||||
@@ -681,6 +681,6 @@ export default Vue.extend({
|
||||
> div
|
||||
> *
|
||||
display block
|
||||
height 320px
|
||||
height 350px
|
||||
|
||||
</style>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<component :is="$store.state.device.animations ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div">
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div">
|
||||
<template v-for="(note, i) in _notes">
|
||||
<x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
|
||||
<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="mk-notifications">
|
||||
<div class="notifications" v-if="notifications.length != 0">
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<component :is="$store.state.device.animations ? 'transition-group' : 'div'" name="mk-notifications" class="transition" tag="div">
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition" tag="div">
|
||||
<template v-for="(notification, i) in _notifications">
|
||||
<div class="notification" :class="notification.type" :key="notification.id">
|
||||
<mk-time :time="notification.createdAt"/>
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
<template>
|
||||
<mk-window ref="window" is-modal width="700px" height="550px" @closed="$destroy">
|
||||
<span slot="header" :class="$style.header">%fa:cog%%i18n:@settings%</span>
|
||||
<mk-settings @done="close"/>
|
||||
<mk-settings :initial-page="initialPage" @done="close"/>
|
||||
</mk-window>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
initialPage: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
(this as any).$refs.window.close();
|
||||
|
||||
65
src/client/app/desktop/views/components/settings.tags.vue
Normal file
65
src/client/app/desktop/views/components/settings.tags.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="vfcitkilproprqtbnpoertpsziierwzi">
|
||||
<div v-for="timeline in timelines" class="timeline">
|
||||
<ui-input v-model="timeline.title" @change="save">
|
||||
<span>%i18n:@title%</span>
|
||||
</ui-input>
|
||||
<ui-textarea :value="timeline.query ? timeline.query.map(tags => tags.join(' ')).join('\n') : ''" @input="onQueryChange(timeline, $event)">
|
||||
<span>%i18n:@query%</span>
|
||||
</ui-textarea>
|
||||
<ui-button class="save" @click="save">%i18n:@save%</ui-button>
|
||||
</div>
|
||||
<ui-button class="add" @click="add">%i18n:@add%</ui-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import * as uuid from 'uuid';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
timelines: this.$store.state.settings.tagTimelines
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
add() {
|
||||
this.timelines.push({
|
||||
id: uuid(),
|
||||
title: '',
|
||||
query: ''
|
||||
});
|
||||
|
||||
this.save();
|
||||
},
|
||||
|
||||
save() {
|
||||
this.$store.dispatch('settings/set', { key: 'tagTimelines', value: this.timelines });
|
||||
},
|
||||
|
||||
onQueryChange(timeline, value) {
|
||||
timeline.query = value.split('\n').map(tags => tags.split(' '));
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
||||
root(isDark)
|
||||
> .timeline
|
||||
padding-bottom 16px
|
||||
border-bottom solid 1px rgba(#000, 0.1)
|
||||
|
||||
> .add
|
||||
margin-top 16px
|
||||
|
||||
.vfcitkilproprqtbnpoertpsziierwzi[data-darkmode]
|
||||
root(true)
|
||||
|
||||
.vfcitkilproprqtbnpoertpsziierwzi:not([data-darkmode])
|
||||
root(false)
|
||||
|
||||
</style>
|
||||
@@ -5,6 +5,7 @@
|
||||
<p :class="{ active: page == 'web' }" @mousedown="page = 'web'">%fa:desktop .fw%Web</p>
|
||||
<p :class="{ active: page == 'notification' }" @mousedown="page = 'notification'">%fa:R bell .fw%%i18n:@notification%</p>
|
||||
<p :class="{ active: page == 'drive' }" @mousedown="page = 'drive'">%fa:cloud .fw%%i18n:@drive%</p>
|
||||
<p :class="{ active: page == 'hashtags' }" @mousedown="page = 'hashtags'">%fa:hashtag .fw%%i18n:@tags%</p>
|
||||
<p :class="{ active: page == 'mute' }" @mousedown="page = 'mute'">%fa:ban .fw%%i18n:@mute%</p>
|
||||
<p :class="{ active: page == 'apps' }" @mousedown="page = 'apps'">%fa:puzzle-piece .fw%%i18n:@apps%</p>
|
||||
<p :class="{ active: page == 'twitter' }" @mousedown="page = 'twitter'">%fa:B twitter .fw%Twitter</p>
|
||||
@@ -60,7 +61,7 @@
|
||||
<button class="ui" @click="deleteWallpaper">%i18n:@delete-wallpaper%</button>
|
||||
<mk-switch v-model="darkmode" text="%i18n:@dark-mode%"/>
|
||||
<mk-switch v-model="circleIcons" text="%i18n:@circle-icons%"/>
|
||||
<mk-switch v-model="animations" text="%i18n:common.enable-animations%"/>
|
||||
<mk-switch v-model="reduceMotion" text="%i18n:common.reduce-motion%"/>
|
||||
<mk-switch v-model="contrastedAcct" text="%i18n:@contrasted-acct%"/>
|
||||
<mk-switch v-model="showFullAcct" text="%i18n:common.show-full-acct%"/>
|
||||
<mk-switch v-model="gradientWindowHeader" text="%i18n:@gradient-window-header%"/>
|
||||
@@ -138,6 +139,11 @@
|
||||
<x-drive/>
|
||||
</section>
|
||||
|
||||
<section class="hashtags" v-show="page == 'hashtags'">
|
||||
<h1>%i18n:@tags%</h1>
|
||||
<x-tags/>
|
||||
</section>
|
||||
|
||||
<section class="mute" v-show="page == 'mute'">
|
||||
<h1>%i18n:@mute%</h1>
|
||||
<x-mute/>
|
||||
@@ -222,6 +228,7 @@ import XApi from './settings.api.vue';
|
||||
import XApps from './settings.apps.vue';
|
||||
import XSignins from './settings.signins.vue';
|
||||
import XDrive from './settings.drive.vue';
|
||||
import XTags from './settings.tags.vue';
|
||||
import { url, langs, version } from '../../../config';
|
||||
import checkForUpdate from '../../../common/scripts/check-for-update';
|
||||
|
||||
@@ -234,11 +241,18 @@ export default Vue.extend({
|
||||
XApi,
|
||||
XApps,
|
||||
XSignins,
|
||||
XDrive
|
||||
XDrive,
|
||||
XTags
|
||||
},
|
||||
props: {
|
||||
initialPage: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: 'profile',
|
||||
page: this.initialPage || 'profile',
|
||||
meta: null,
|
||||
version,
|
||||
langs,
|
||||
@@ -247,9 +261,9 @@ export default Vue.extend({
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
animations: {
|
||||
get() { return this.$store.state.device.animations; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'animations', value }); }
|
||||
reduceMotion: {
|
||||
get() { return this.$store.state.device.reduceMotion; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'reduceMotion', value }); }
|
||||
},
|
||||
|
||||
apiViaStream: {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { HashtagStream } from '../../../common/scripts/streaming/hashtag';
|
||||
|
||||
const fetchLimit = 10;
|
||||
|
||||
@@ -23,6 +24,9 @@ export default Vue.extend({
|
||||
src: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
tagTl: {
|
||||
required: false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -31,6 +35,7 @@ export default Vue.extend({
|
||||
fetching: true,
|
||||
moreFetching: false,
|
||||
existMore: false,
|
||||
streamManager: null,
|
||||
connection: null,
|
||||
connectionId: null,
|
||||
date: null
|
||||
@@ -42,21 +47,14 @@ export default Vue.extend({
|
||||
return this.$store.state.i.followingCount == 0;
|
||||
},
|
||||
|
||||
stream(): any {
|
||||
switch (this.src) {
|
||||
case 'home': return (this as any).os.stream;
|
||||
case 'local': return (this as any).os.streams.localTimelineStream;
|
||||
case 'hybrid': return (this as any).os.streams.hybridTimelineStream;
|
||||
case 'global': return (this as any).os.streams.globalTimelineStream;
|
||||
}
|
||||
},
|
||||
|
||||
endpoint(): string {
|
||||
switch (this.src) {
|
||||
case 'home': return 'notes/timeline';
|
||||
case 'local': return 'notes/local-timeline';
|
||||
case 'hybrid': return 'notes/hybrid-timeline';
|
||||
case 'global': return 'notes/global-timeline';
|
||||
case 'mentions': return 'notes/mentions';
|
||||
case 'tag': return 'notes/search_by_tag';
|
||||
}
|
||||
},
|
||||
|
||||
@@ -66,13 +64,36 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.connection = this.stream.getConnection();
|
||||
this.connectionId = this.stream.use();
|
||||
|
||||
if (this.src == 'tag') {
|
||||
this.connection = new HashtagStream((this as any).os, this.$store.state.i, this.tagTl.query);
|
||||
this.connection.on('note', this.onNote);
|
||||
} else if (this.src == 'home') {
|
||||
this.streamManager = (this as any).os.stream;
|
||||
this.connection = this.streamManager.getConnection();
|
||||
this.connectionId = this.streamManager.use();
|
||||
this.connection.on('note', this.onNote);
|
||||
if (this.src == 'home') {
|
||||
this.connection.on('follow', this.onChangeFollowing);
|
||||
this.connection.on('unfollow', this.onChangeFollowing);
|
||||
} else if (this.src == 'local') {
|
||||
this.streamManager = (this as any).os.streams.localTimelineStream;
|
||||
this.connection = this.streamManager.getConnection();
|
||||
this.connectionId = this.streamManager.use();
|
||||
this.connection.on('note', this.onNote);
|
||||
} else if (this.src == 'hybrid') {
|
||||
this.streamManager = (this as any).os.streams.hybridTimelineStream;
|
||||
this.connection = this.streamManager.getConnection();
|
||||
this.connectionId = this.streamManager.use();
|
||||
this.connection.on('note', this.onNote);
|
||||
} else if (this.src == 'global') {
|
||||
this.streamManager = (this as any).os.streams.globalTimelineStream;
|
||||
this.connection = this.streamManager.getConnection();
|
||||
this.connectionId = this.streamManager.use();
|
||||
this.connection.on('note', this.onNote);
|
||||
} else if (this.src == 'mentions') {
|
||||
this.streamManager = (this as any).os.stream;
|
||||
this.connection = this.streamManager.getConnection();
|
||||
this.connectionId = this.streamManager.use();
|
||||
this.connection.on('mention', this.onNote);
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', this.onKeydown);
|
||||
@@ -81,12 +102,27 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if (this.src == 'tag') {
|
||||
this.connection.off('note', this.onNote);
|
||||
this.connection.close();
|
||||
} else if (this.src == 'home') {
|
||||
this.connection.off('note', this.onNote);
|
||||
if (this.src == 'home') {
|
||||
this.connection.off('follow', this.onChangeFollowing);
|
||||
this.connection.off('unfollow', this.onChangeFollowing);
|
||||
this.streamManager.dispose(this.connectionId);
|
||||
} else if (this.src == 'local') {
|
||||
this.connection.off('note', this.onNote);
|
||||
this.streamManager.dispose(this.connectionId);
|
||||
} else if (this.src == 'hybrid') {
|
||||
this.connection.off('note', this.onNote);
|
||||
this.streamManager.dispose(this.connectionId);
|
||||
} else if (this.src == 'global') {
|
||||
this.connection.off('note', this.onNote);
|
||||
this.streamManager.dispose(this.connectionId);
|
||||
} else if (this.src == 'mentions') {
|
||||
this.connection.off('mention', this.onNote);
|
||||
this.streamManager.dispose(this.connectionId);
|
||||
}
|
||||
this.stream.dispose(this.connectionId);
|
||||
|
||||
document.removeEventListener('keydown', this.onKeydown);
|
||||
},
|
||||
@@ -101,7 +137,8 @@ export default Vue.extend({
|
||||
untilDate: this.date ? this.date.getTime() : undefined,
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
|
||||
query: this.tagTl ? this.tagTl.query : undefined
|
||||
}).then(notes => {
|
||||
if (notes.length == fetchLimit + 1) {
|
||||
notes.pop();
|
||||
@@ -124,7 +161,8 @@ export default Vue.extend({
|
||||
untilId: (this.$refs.timeline as any).tail().id,
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
|
||||
query: this.tagTl ? this.tagTl.query : undefined
|
||||
});
|
||||
|
||||
promise.then(notes => {
|
||||
|
||||
@@ -5,13 +5,20 @@
|
||||
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span>
|
||||
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span>
|
||||
<span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span>
|
||||
<span :data-active="src == 'mentions'" @click="src = 'mentions'">%fa:at% %i18n:@mentions%</span>
|
||||
<span :data-active="src == 'tag'" @click="src = 'tag'" v-if="tagTl">%fa:hashtag% {{ tagTl.title }}</span>
|
||||
<span :data-active="src == 'list'" @click="src = 'list'" v-if="list">%fa:list% {{ list.title }}</span>
|
||||
<button @click="chooseList" title="%i18n:@list%">%fa:list%</button>
|
||||
<div class="buttons">
|
||||
<button @click="chooseTag" title="%i18n:@hashtag%" ref="tagButton">%fa:hashtag%</button>
|
||||
<button @click="chooseList" title="%i18n:@list%" ref="listButton">%fa:list%</button>
|
||||
</div>
|
||||
</header>
|
||||
<x-core v-if="src == 'home'" ref="tl" key="home" src="home"/>
|
||||
<x-core v-if="src == 'local'" ref="tl" key="local" src="local"/>
|
||||
<x-core v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
|
||||
<x-core v-if="src == 'global'" ref="tl" key="global" src="global"/>
|
||||
<x-core v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
|
||||
<x-core v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
|
||||
<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -19,7 +26,8 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import XCore from './timeline.core.vue';
|
||||
import MkUserListsWindow from './user-lists-window.vue';
|
||||
import Menu from '../../../common/views/components/menu.vue';
|
||||
import MkSettingsWindow from './settings-window.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@@ -30,6 +38,7 @@ export default Vue.extend({
|
||||
return {
|
||||
src: 'home',
|
||||
list: null,
|
||||
tagTl: null,
|
||||
enableLocalTimeline: false
|
||||
};
|
||||
},
|
||||
@@ -39,8 +48,14 @@ export default Vue.extend({
|
||||
this.saveSrc();
|
||||
},
|
||||
|
||||
list() {
|
||||
list(x) {
|
||||
this.saveSrc();
|
||||
if (x != null) this.tagTl = null;
|
||||
},
|
||||
|
||||
tagTl(x) {
|
||||
this.saveSrc();
|
||||
if (x != null) this.list = null;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -53,6 +68,8 @@ export default Vue.extend({
|
||||
this.src = this.$store.state.device.tl.src;
|
||||
if (this.src == 'list') {
|
||||
this.list = this.$store.state.device.tl.arg;
|
||||
} else if (this.src == 'tag') {
|
||||
this.tagTl = this.$store.state.device.tl.arg;
|
||||
}
|
||||
} else if (this.$store.state.i.followingCount == 0) {
|
||||
this.src = 'hybrid';
|
||||
@@ -69,7 +86,7 @@ export default Vue.extend({
|
||||
saveSrc() {
|
||||
this.$store.commit('device/setTl', {
|
||||
src: this.src,
|
||||
arg: this.list
|
||||
arg: this.src == 'list' ? this.list : this.tagTl
|
||||
});
|
||||
},
|
||||
|
||||
@@ -77,12 +94,74 @@ export default Vue.extend({
|
||||
(this.$refs.tl as any).warp(date);
|
||||
},
|
||||
|
||||
chooseList() {
|
||||
const w = (this as any).os.new(MkUserListsWindow);
|
||||
w.$once('choosen', list => {
|
||||
async chooseList() {
|
||||
const lists = await (this as any).api('users/lists/list');
|
||||
|
||||
let menu = [{
|
||||
icon: '%fa:plus%',
|
||||
text: '%i18n:@add-list%',
|
||||
action: () => {
|
||||
(this as any).apis.input({
|
||||
title: '%i18n:@list-name%',
|
||||
}).then(async title => {
|
||||
const list = await (this as any).api('users/lists/create', {
|
||||
title
|
||||
});
|
||||
|
||||
this.list = list;
|
||||
this.src = 'list';
|
||||
w.close();
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
if (lists.length > 0) {
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
menu = menu.concat(lists.map(list => ({
|
||||
icon: '%fa:list%',
|
||||
text: list.title,
|
||||
action: () => {
|
||||
this.list = list;
|
||||
this.src = 'list';
|
||||
}
|
||||
})));
|
||||
|
||||
this.os.new(Menu, {
|
||||
source: this.$refs.listButton,
|
||||
compact: false,
|
||||
items: menu
|
||||
});
|
||||
},
|
||||
|
||||
chooseTag() {
|
||||
let menu = [{
|
||||
icon: '%fa:plus%',
|
||||
text: '%i18n:@add-tag-timeline%',
|
||||
action: () => {
|
||||
(this as any).os.new(MkSettingsWindow, {
|
||||
initialPage: 'hashtags'
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
if (this.$store.state.settings.tagTimelines.length > 0) {
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
menu = menu.concat(this.$store.state.settings.tagTimelines.map(t => ({
|
||||
icon: '%fa:hashtag%',
|
||||
text: t.title,
|
||||
action: () => {
|
||||
this.tagTl = t;
|
||||
this.src = 'tag';
|
||||
}
|
||||
})));
|
||||
|
||||
this.os.new(Menu, {
|
||||
source: this.$refs.tagButton,
|
||||
compact: false,
|
||||
items: menu
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -104,13 +183,15 @@ root(isDark)
|
||||
border-radius 6px 6px 0 0
|
||||
box-shadow 0 1px isDark ? rgba(#000, 0.15) : rgba(#000, 0.08)
|
||||
|
||||
> button
|
||||
> .buttons
|
||||
position absolute
|
||||
z-index 2
|
||||
top 0
|
||||
right 0
|
||||
padding 0
|
||||
width 42px
|
||||
padding-right 8px
|
||||
|
||||
> button
|
||||
padding 0 8px
|
||||
font-size 0.9em
|
||||
line-height 42px
|
||||
color isDark ? #9baec8 : #ccc
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<x-tl-column v-else-if="column.type == 'hybrid'" :column="column" :is-stacked="isStacked"/>
|
||||
<x-tl-column v-else-if="column.type == 'global'" :column="column" :is-stacked="isStacked"/>
|
||||
<x-tl-column v-else-if="column.type == 'list'" :column="column" :is-stacked="isStacked"/>
|
||||
<x-tl-column v-else-if="column.type == 'hashtag'" :column="column" :is-stacked="isStacked"/>
|
||||
<x-mentions-column v-else-if="column.type == 'mentions'" :column="column" :is-stacked="isStacked"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -13,12 +15,14 @@ import Vue from 'vue';
|
||||
import XTlColumn from './deck.tl-column.vue';
|
||||
import XNotificationsColumn from './deck.notifications-column.vue';
|
||||
import XWidgetsColumn from './deck.widgets-column.vue';
|
||||
import XMentionsColumn from './deck.mentions-column.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XTlColumn,
|
||||
XNotificationsColumn,
|
||||
XWidgetsColumn
|
||||
XWidgetsColumn,
|
||||
XMentionsColumn
|
||||
},
|
||||
|
||||
props: {
|
||||
|
||||
117
src/client/app/desktop/views/pages/deck/deck.hashtag-tl.vue
Normal file
117
src/client/app/desktop/views/pages/deck/deck.hashtag-tl.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import XNotes from './deck.notes.vue';
|
||||
import { HashtagStream } from '../../../../common/scripts/streaming/hashtag';
|
||||
|
||||
const fetchLimit = 10;
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XNotes
|
||||
},
|
||||
|
||||
props: {
|
||||
tagTl: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
mediaOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
mediaView: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
moreFetching: false,
|
||||
existMore: false,
|
||||
connection: null
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
mediaOnly() {
|
||||
this.fetch();
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.connection) this.connection.close();
|
||||
this.connection = new HashtagStream((this as any).os, this.$store.state.i, this.tagTl.query);
|
||||
this.connection.on('note', this.onNote);
|
||||
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.connection.close();
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetch() {
|
||||
this.fetching = true;
|
||||
|
||||
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
|
||||
(this as any).api('notes/search_by_tag', {
|
||||
limit: fetchLimit + 1,
|
||||
withFiles: this.mediaOnly,
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
|
||||
query: this.tagTl.query
|
||||
}).then(notes => {
|
||||
if (notes.length == fetchLimit + 1) {
|
||||
notes.pop();
|
||||
this.existMore = true;
|
||||
}
|
||||
res(notes);
|
||||
this.fetching = false;
|
||||
this.$emit('loaded');
|
||||
}, rej);
|
||||
}));
|
||||
},
|
||||
more() {
|
||||
this.moreFetching = true;
|
||||
|
||||
const promise = (this as any).api('notes/search_by_tag', {
|
||||
limit: fetchLimit + 1,
|
||||
untilId: (this.$refs.timeline as any).tail().id,
|
||||
withFiles: this.mediaOnly,
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
|
||||
query: this.tagTl.query
|
||||
});
|
||||
|
||||
promise.then(notes => {
|
||||
if (notes.length == fetchLimit + 1) {
|
||||
notes.pop();
|
||||
} else {
|
||||
this.existMore = false;
|
||||
}
|
||||
notes.forEach(n => (this.$refs.timeline as any).append(n));
|
||||
this.moreFetching = false;
|
||||
});
|
||||
|
||||
return promise;
|
||||
},
|
||||
onNote(note) {
|
||||
if (this.mediaOnly && note.files.length == 0) return;
|
||||
|
||||
// Prepend a note
|
||||
(this.$refs.timeline as any).prepend(note);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<x-column :name="name" :column="column" :is-stacked="isStacked">
|
||||
<span slot="header">%fa:at%{{ name }}</span>
|
||||
|
||||
<x-mentions/>
|
||||
</x-column>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import XColumn from './deck.column.vue';
|
||||
import XMentions from './deck.mentions.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XColumn,
|
||||
XMentions
|
||||
},
|
||||
|
||||
props: {
|
||||
column: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isStacked: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
name(): string {
|
||||
if (this.column.name) return this.column.name;
|
||||
return '%i18n:common.deck.mentions%';
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
93
src/client/app/desktop/views/pages/deck/deck.mentions.vue
Normal file
93
src/client/app/desktop/views/pages/deck/deck.mentions.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<x-notes ref="timeline" :more="existMore ? more : null"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import XNotes from './deck.notes.vue';
|
||||
|
||||
const fetchLimit = 10;
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XNotes
|
||||
},
|
||||
|
||||
props: {
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
moreFetching: false,
|
||||
existMore: false,
|
||||
connection: null,
|
||||
connectionId: null
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.connection = (this as any).os.stream.getConnection();
|
||||
this.connectionId = (this as any).os.stream.use();
|
||||
|
||||
this.connection.on('mention', this.onNote);
|
||||
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.connection.off('mention', this.onNote);
|
||||
(this as any).os.stream.dispose(this.connectionId);
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetch() {
|
||||
this.fetching = true;
|
||||
|
||||
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
|
||||
(this as any).api('notes/mentions', {
|
||||
limit: fetchLimit + 1,
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||
}).then(notes => {
|
||||
if (notes.length == fetchLimit + 1) {
|
||||
notes.pop();
|
||||
this.existMore = true;
|
||||
}
|
||||
res(notes);
|
||||
this.fetching = false;
|
||||
this.$emit('loaded');
|
||||
}, rej);
|
||||
}));
|
||||
},
|
||||
more() {
|
||||
this.moreFetching = true;
|
||||
|
||||
const promise = (this as any).api('notes/mentions', {
|
||||
limit: fetchLimit + 1,
|
||||
untilId: (this.$refs.timeline as any).tail().id,
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||
});
|
||||
|
||||
promise.then(notes => {
|
||||
if (notes.length == fetchLimit + 1) {
|
||||
notes.pop();
|
||||
} else {
|
||||
this.existMore = false;
|
||||
}
|
||||
notes.forEach(n => (this.$refs.timeline as any).append(n));
|
||||
this.moreFetching = false;
|
||||
});
|
||||
|
||||
return promise;
|
||||
},
|
||||
onNote(note) {
|
||||
// Prepend a note
|
||||
(this.$refs.timeline as any).prepend(note);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="oxynyeqmfvracxnglgulyqfgqxnxmehl">
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<component :is="$store.state.device.animations ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
|
||||
<template v-for="(notification, i) in _notifications">
|
||||
<x-notification class="notification" :notification="notification" :key="notification.id"/>
|
||||
<p class="date" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date" :key="notification.id + '-time'">
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<template v-if="column.type == 'hybrid'">%fa:share-alt%</template>
|
||||
<template v-if="column.type == 'global'">%fa:globe%</template>
|
||||
<template v-if="column.type == 'list'">%fa:list%</template>
|
||||
<template v-if="column.type == 'hashtag'">%fa:hashtag%</template>
|
||||
<span>{{ name }}</span>
|
||||
</span>
|
||||
|
||||
@@ -14,6 +15,7 @@
|
||||
<mk-switch v-model="column.isMediaView" @change="onChangeSettings" text="%i18n:@is-media-view%"/>
|
||||
</div>
|
||||
<x-list-tl v-if="column.type == 'list'" :list="column.list" :media-only="column.isMediaOnly" :media-view="column.isMediaView"/>
|
||||
<x-hashtag-tl v-if="column.type == 'hashtag'" :tag-tl="$store.state.settings.tagTimelines.find(x => x.id == column.tagTlId)" :media-only="column.isMediaOnly" :media-view="column.isMediaView"/>
|
||||
<x-tl v-else :src="column.type" :media-only="column.isMediaOnly" :media-view="column.isMediaView"/>
|
||||
</x-column>
|
||||
</template>
|
||||
@@ -23,12 +25,14 @@ import Vue from 'vue';
|
||||
import XColumn from './deck.column.vue';
|
||||
import XTl from './deck.tl.vue';
|
||||
import XListTl from './deck.list-tl.vue';
|
||||
import XHashtagTl from './deck.hashtag-tl.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XColumn,
|
||||
XTl,
|
||||
XListTl
|
||||
XListTl,
|
||||
XHashtagTl
|
||||
},
|
||||
|
||||
props: {
|
||||
@@ -65,6 +69,7 @@ export default Vue.extend({
|
||||
case 'hybrid': return '%i18n:common.deck.hybrid%';
|
||||
case 'global': return '%i18n:common.deck.global%';
|
||||
case 'list': return this.column.list.title;
|
||||
case 'hashtag': return this.$store.state.settings.tagTimelines.find(x => x.id == this.column.tagTlId).title;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -138,6 +138,15 @@ export default Vue.extend({
|
||||
type: 'global'
|
||||
});
|
||||
}
|
||||
}, {
|
||||
icon: '%fa:at%',
|
||||
text: '%i18n:common.deck.mentions%',
|
||||
action: () => {
|
||||
this.$store.dispatch('settings/addDeckColumn', {
|
||||
id: uuid(),
|
||||
type: 'mentions'
|
||||
});
|
||||
}
|
||||
}, {
|
||||
icon: '%fa:list%',
|
||||
text: '%i18n:common.deck.list%',
|
||||
@@ -152,6 +161,20 @@ export default Vue.extend({
|
||||
w.close();
|
||||
});
|
||||
}
|
||||
}, {
|
||||
icon: '%fa:hashtag%',
|
||||
text: '%i18n:common.deck.hashtag%',
|
||||
action: () => {
|
||||
(this as any).apis.input({
|
||||
title: '%i18n:@enter-hashtag-tl-title%'
|
||||
}).then(title => {
|
||||
this.$store.dispatch('settings/addDeckColumn', {
|
||||
id: uuid(),
|
||||
type: 'hashtag',
|
||||
tagTlId: this.$store.state.settings.tagTimelines.find(x => x.title == title).id
|
||||
});
|
||||
});
|
||||
}
|
||||
}, {
|
||||
icon: '%fa:bell R%',
|
||||
text: '%i18n:common.deck.notifications%',
|
||||
|
||||
@@ -43,7 +43,7 @@ export default Vue.extend({
|
||||
> .stats
|
||||
display flex
|
||||
justify-content center
|
||||
margin-bottom 16px
|
||||
margin 0 auto 16px auto
|
||||
padding 32px
|
||||
background #fff
|
||||
box-shadow 0 2px 8px rgba(#000, 0.1)
|
||||
@@ -60,5 +60,6 @@ export default Vue.extend({
|
||||
font-size 70%
|
||||
|
||||
> div
|
||||
max-width 850px
|
||||
max-width 950px
|
||||
margin 0 auto
|
||||
</style>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</div>
|
||||
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<component :is="$store.state.device.animations ? 'transition-group' : 'div'" name="mk-notes" class="transition" tag="div">
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="transition" tag="div">
|
||||
<template v-for="(note, i) in _notes">
|
||||
<mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
|
||||
<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="mk-notifications">
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<component :is="$store.state.device.animations ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
|
||||
<component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notifications" class="transition notifications">
|
||||
<template v-for="(notification, i) in _notifications">
|
||||
<mk-notification :notification="notification" :key="notification.id"/>
|
||||
<p class="date" :key="notification.id + '_date'" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date">
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { HashtagStream } from '../../../common/scripts/streaming/hashtag';
|
||||
|
||||
const fetchLimit = 10;
|
||||
|
||||
@@ -21,6 +22,9 @@ export default Vue.extend({
|
||||
src: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
tagTl: {
|
||||
required: false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -29,6 +33,7 @@ export default Vue.extend({
|
||||
fetching: true,
|
||||
moreFetching: false,
|
||||
existMore: false,
|
||||
streamManager: null,
|
||||
connection: null,
|
||||
connectionId: null,
|
||||
unreadCount: 0,
|
||||
@@ -41,21 +46,14 @@ export default Vue.extend({
|
||||
return this.$store.state.i.followingCount == 0;
|
||||
},
|
||||
|
||||
stream(): any {
|
||||
switch (this.src) {
|
||||
case 'home': return (this as any).os.stream;
|
||||
case 'local': return (this as any).os.streams.localTimelineStream;
|
||||
case 'hybrid': return (this as any).os.streams.hybridTimelineStream;
|
||||
case 'global': return (this as any).os.streams.globalTimelineStream;
|
||||
}
|
||||
},
|
||||
|
||||
endpoint(): string {
|
||||
switch (this.src) {
|
||||
case 'home': return 'notes/timeline';
|
||||
case 'local': return 'notes/local-timeline';
|
||||
case 'hybrid': return 'notes/hybrid-timeline';
|
||||
case 'global': return 'notes/global-timeline';
|
||||
case 'mentions': return 'notes/mentions';
|
||||
case 'tag': return 'notes/search_by_tag';
|
||||
}
|
||||
},
|
||||
|
||||
@@ -65,25 +63,63 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.connection = this.stream.getConnection();
|
||||
this.connectionId = this.stream.use();
|
||||
|
||||
if (this.src == 'tag') {
|
||||
this.connection = new HashtagStream((this as any).os, this.$store.state.i, this.tagTl.query);
|
||||
this.connection.on('note', this.onNote);
|
||||
} else if (this.src == 'home') {
|
||||
this.streamManager = (this as any).os.stream;
|
||||
this.connection = this.streamManager.getConnection();
|
||||
this.connectionId = this.streamManager.use();
|
||||
this.connection.on('note', this.onNote);
|
||||
if (this.src == 'home') {
|
||||
this.connection.on('follow', this.onChangeFollowing);
|
||||
this.connection.on('unfollow', this.onChangeFollowing);
|
||||
} else if (this.src == 'local') {
|
||||
this.streamManager = (this as any).os.streams.localTimelineStream;
|
||||
this.connection = this.streamManager.getConnection();
|
||||
this.connectionId = this.streamManager.use();
|
||||
this.connection.on('note', this.onNote);
|
||||
} else if (this.src == 'hybrid') {
|
||||
this.streamManager = (this as any).os.streams.hybridTimelineStream;
|
||||
this.connection = this.streamManager.getConnection();
|
||||
this.connectionId = this.streamManager.use();
|
||||
this.connection.on('note', this.onNote);
|
||||
} else if (this.src == 'global') {
|
||||
this.streamManager = (this as any).os.streams.globalTimelineStream;
|
||||
this.connection = this.streamManager.getConnection();
|
||||
this.connectionId = this.streamManager.use();
|
||||
this.connection.on('note', this.onNote);
|
||||
} else if (this.src == 'mentions') {
|
||||
this.streamManager = (this as any).os.stream;
|
||||
this.connection = this.streamManager.getConnection();
|
||||
this.connectionId = this.streamManager.use();
|
||||
this.connection.on('mention', this.onNote);
|
||||
}
|
||||
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if (this.src == 'tag') {
|
||||
this.connection.off('note', this.onNote);
|
||||
this.connection.close();
|
||||
} else if (this.src == 'home') {
|
||||
this.connection.off('note', this.onNote);
|
||||
if (this.src == 'home') {
|
||||
this.connection.off('follow', this.onChangeFollowing);
|
||||
this.connection.off('unfollow', this.onChangeFollowing);
|
||||
this.streamManager.dispose(this.connectionId);
|
||||
} else if (this.src == 'local') {
|
||||
this.connection.off('note', this.onNote);
|
||||
this.streamManager.dispose(this.connectionId);
|
||||
} else if (this.src == 'hybrid') {
|
||||
this.connection.off('note', this.onNote);
|
||||
this.streamManager.dispose(this.connectionId);
|
||||
} else if (this.src == 'global') {
|
||||
this.connection.off('note', this.onNote);
|
||||
this.streamManager.dispose(this.connectionId);
|
||||
} else if (this.src == 'mentions') {
|
||||
this.connection.off('mention', this.onNote);
|
||||
this.streamManager.dispose(this.connectionId);
|
||||
}
|
||||
this.stream.dispose(this.connectionId);
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -96,7 +132,8 @@ export default Vue.extend({
|
||||
untilDate: this.date ? this.date.getTime() : undefined,
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
|
||||
query: this.tagTl ? this.tagTl.query : undefined
|
||||
}).then(notes => {
|
||||
if (notes.length == fetchLimit + 1) {
|
||||
notes.pop();
|
||||
@@ -119,7 +156,8 @@ export default Vue.extend({
|
||||
untilId: (this.$refs.timeline as any).tail().id,
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
|
||||
query: this.tagTl ? this.tagTl.query : undefined
|
||||
});
|
||||
|
||||
promise.then(notes => {
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
<span v-if="src == 'local'">%fa:R comments%%i18n:@local%</span>
|
||||
<span v-if="src == 'hybrid'">%fa:share-alt%%i18n:@hybrid%</span>
|
||||
<span v-if="src == 'global'">%fa:globe%%i18n:@global%</span>
|
||||
<span v-if="src == 'mentions'">%fa:at%%i18n:@mentions%</span>
|
||||
<span v-if="src == 'list'">%fa:list%{{ list.title }}</span>
|
||||
<span v-if="src == 'tag'">%fa:hashtag%{{ tagTl.title }}</span>
|
||||
</span>
|
||||
<span style="margin-left:8px">
|
||||
<template v-if="!showNav">%fa:angle-down%</template>
|
||||
@@ -27,9 +29,11 @@
|
||||
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span>
|
||||
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span>
|
||||
<span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span>
|
||||
<span :data-active="src == 'mentions'" @click="src = 'mentions'">%fa:at% %i18n:@mentions%</span>
|
||||
<template v-if="lists">
|
||||
<span v-for="l in lists" :data-active="src == 'list' && list == l" @click="src = 'list'; list = l" :key="l.id">%fa:list% {{ l.title }}</span>
|
||||
</template>
|
||||
<span v-for="tl in $store.state.settings.tagTimelines" :data-active="src == 'tag' && tagTl == tl" @click="src = 'tag'; tagTl = tl" :key="tl.id">%fa:hashtag% {{ tl.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,6 +43,8 @@
|
||||
<x-tl v-if="src == 'local'" ref="tl" key="local" src="local"/>
|
||||
<x-tl v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
|
||||
<x-tl v-if="src == 'global'" ref="tl" key="global" src="global"/>
|
||||
<x-tl v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
|
||||
<x-tl v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
|
||||
<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
|
||||
</div>
|
||||
</main>
|
||||
@@ -60,6 +66,7 @@ export default Vue.extend({
|
||||
src: 'home',
|
||||
list: null,
|
||||
lists: null,
|
||||
tagTl: null,
|
||||
showNav: false,
|
||||
enableLocalTimeline: false
|
||||
};
|
||||
@@ -71,9 +78,16 @@ export default Vue.extend({
|
||||
this.saveSrc();
|
||||
},
|
||||
|
||||
list() {
|
||||
list(x) {
|
||||
this.showNav = false;
|
||||
this.saveSrc();
|
||||
if (x != null) this.tagTl = null;
|
||||
},
|
||||
|
||||
tagTl(x) {
|
||||
this.showNav = false;
|
||||
this.saveSrc();
|
||||
if (x != null) this.list = null;
|
||||
},
|
||||
|
||||
showNav(v) {
|
||||
@@ -94,6 +108,8 @@ export default Vue.extend({
|
||||
this.src = this.$store.state.device.tl.src;
|
||||
if (this.src == 'list') {
|
||||
this.list = this.$store.state.device.tl.arg;
|
||||
} else if (this.src == 'tag') {
|
||||
this.tagTl = this.$store.state.device.tl.arg;
|
||||
}
|
||||
} else if (this.$store.state.i.followingCount == 0) {
|
||||
this.src = 'hybrid';
|
||||
@@ -118,7 +134,7 @@ export default Vue.extend({
|
||||
saveSrc() {
|
||||
this.$store.commit('device/setTl', {
|
||||
src: this.src,
|
||||
arg: this.list
|
||||
arg: this.src == 'list' ? this.list : this.tagTl
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<section>
|
||||
<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
|
||||
<ui-switch v-model="circleIcons">%i18n:@circle-icons%</ui-switch>
|
||||
<ui-switch v-model="animations">%i18n:common.enable-animations% (%i18n:common.this-setting-is-this-device-only%)</ui-switch>
|
||||
<ui-switch v-model="reduceMotion">%i18n:common.reduce-motion% (%i18n:common.this-setting-is-this-device-only%)</ui-switch>
|
||||
<ui-switch v-model="contrastedAcct">%i18n:@contrasted-acct%</ui-switch>
|
||||
<ui-switch v-model="showFullAcct">%i18n:common.show-full-acct%</ui-switch>
|
||||
<ui-switch v-model="iLikeSushi">%i18n:common.i-like-sushi%</ui-switch>
|
||||
@@ -169,9 +169,9 @@ export default Vue.extend({
|
||||
set(value) { this.$store.commit('device/set', { key: 'darkmode', value }); }
|
||||
},
|
||||
|
||||
animations: {
|
||||
get() { return this.$store.state.device.animations; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'animations', value }); }
|
||||
reduceMotion: {
|
||||
get() { return this.$store.state.device.reduceMotion; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'reduceMotion', value }); }
|
||||
},
|
||||
|
||||
alwaysShowNsfw: {
|
||||
|
||||
@@ -10,6 +10,7 @@ const defaultSettings = {
|
||||
home: null,
|
||||
mobileHome: [],
|
||||
deck: null,
|
||||
tagTimelines: [],
|
||||
fetchOnScroll: true,
|
||||
showMaps: true,
|
||||
showPostFormOnTopOfTl: false,
|
||||
@@ -38,7 +39,7 @@ const defaultSettings = {
|
||||
};
|
||||
|
||||
const defaultDeviceSettings = {
|
||||
animations: true,
|
||||
reduceMotion: false,
|
||||
apiViaStream: true,
|
||||
autoPopout: false,
|
||||
darkmode: false,
|
||||
|
||||
@@ -9,7 +9,7 @@ html(lang= lang)
|
||||
link(rel="stylesheet" href="/docs/assets/style.css")
|
||||
link(rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css")
|
||||
script(src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js")
|
||||
link(rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous")
|
||||
link(rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous")
|
||||
block meta
|
||||
|
||||
body
|
||||
|
||||
@@ -82,9 +82,13 @@ const handlers: { [key: string]: (window: any, token: any, mentionedRemoteUsers:
|
||||
|
||||
text({ document }, { content }) {
|
||||
const nodes = (content as string).split('\n').map(x => document.createTextNode(x));
|
||||
for (const x of intersperse(document.createElement('br'), nodes)) {
|
||||
for (const x of intersperse('br', nodes)) {
|
||||
if (x === 'br') {
|
||||
document.body.appendChild(document.createElement('br'));
|
||||
} else {
|
||||
document.body.appendChild(x);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
url({ document }, { url }) {
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
* Replace fontawesome symbols
|
||||
*/
|
||||
|
||||
import * as fontawesome from '@fortawesome/fontawesome';
|
||||
import regular from '@fortawesome/fontawesome-free-regular';
|
||||
import solid from '@fortawesome/fontawesome-free-solid';
|
||||
import brands from '@fortawesome/fontawesome-free-brands';
|
||||
import * as fontawesome from '@fortawesome/fontawesome-svg-core';
|
||||
import { far } from '@fortawesome/free-regular-svg-icons';
|
||||
import { fas } from '@fortawesome/free-solid-svg-icons';
|
||||
import { fab } from '@fortawesome/free-brands-svg-icons';
|
||||
|
||||
fontawesome.library.add(regular, solid, brands);
|
||||
fontawesome.library.add(far, fas, fab);
|
||||
|
||||
export const pattern = /%fa:(.+?)%/g;
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ import Following from './following';
|
||||
const Note = db.get<INote>('notes');
|
||||
Note.createIndex('uri', { sparse: true, unique: true });
|
||||
Note.createIndex('userId');
|
||||
Note.createIndex('mentions');
|
||||
Note.createIndex('visibleUserIds');
|
||||
Note.createIndex('tagsLower');
|
||||
Note.createIndex('_files.contentType');
|
||||
Note.createIndex({
|
||||
@@ -24,6 +26,21 @@ Note.createIndex({
|
||||
});
|
||||
export default Note;
|
||||
|
||||
// 後方互換性のため
|
||||
Note.findOne({
|
||||
fileIds: { $exists: true }
|
||||
}).then(n => {
|
||||
if (n == null) {
|
||||
Note.update({}, {
|
||||
$rename: {
|
||||
mediaIds: 'fileIds'
|
||||
}
|
||||
}, {
|
||||
multi: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export function isValidText(text: string): boolean {
|
||||
return length(text.trim()) <= 1000 && text.trim() != '';
|
||||
}
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
import { INote } from '../../../models/note';
|
||||
import toHtml from '../../../mfm/html';
|
||||
import parse from '../../../mfm/parse';
|
||||
import config from '../../../config';
|
||||
|
||||
export default function(note: INote) {
|
||||
let html = toHtml(parse(note.text), note.mentionedRemoteUsers);
|
||||
if (html == null) html = '';
|
||||
|
||||
if (note.poll != null) {
|
||||
const url = `${config.url}/notes/${note._id}`;
|
||||
// TODO: i18n
|
||||
html += `<p><a href="${url}">【Misskeyで投票を見る】</a></p>`;
|
||||
}
|
||||
|
||||
if (note.renoteId != null) {
|
||||
const url = `${config.url}/notes/${note.renoteId}`;
|
||||
html += `<p>RE: <a href="${url}">${url}</a></p>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import DriveFile, { IDriveFile } from '../../../models/drive-file';
|
||||
import Note, { INote } from '../../../models/note';
|
||||
import User from '../../../models/user';
|
||||
import toHtml from '../misc/get-note-html';
|
||||
import parseMfm from '../../../mfm/parse';
|
||||
|
||||
export default async function renderNote(note: INote, dive = true): Promise<any> {
|
||||
const promisedFiles: Promise<IDriveFile[]> = note.fileIds
|
||||
@@ -81,13 +82,39 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
|
||||
|
||||
const files = await promisedFiles;
|
||||
|
||||
let text = note.text;
|
||||
|
||||
if (note.poll != null) {
|
||||
if (text == null) text = '';
|
||||
const url = `${config.url}/notes/${note._id}`;
|
||||
// TODO: i18n
|
||||
text += `\n\n[投票を見る](${url})`;
|
||||
}
|
||||
|
||||
if (note.renoteId != null) {
|
||||
if (text == null) text = '';
|
||||
const url = `${config.url}/notes/${note.renoteId}`;
|
||||
text += `\n\nRE: ${url}`;
|
||||
}
|
||||
|
||||
// 省略されたメンションのホストを復元する
|
||||
if (text != null) {
|
||||
text = parseMfm(text).map(x => {
|
||||
if (x.type == 'mention' && x.host == null) {
|
||||
return `${x.content}@${config.host}`;
|
||||
} else {
|
||||
return x.content;
|
||||
}
|
||||
}).join('');
|
||||
}
|
||||
|
||||
return {
|
||||
id: `${config.url}/notes/${note._id}`,
|
||||
type: 'Note',
|
||||
attributedTo,
|
||||
summary: note.cw,
|
||||
content: toHtml(note),
|
||||
_misskey_content_: note.text,
|
||||
content: toHtml(Object.assign({}, note, { text })),
|
||||
_misskey_content: text,
|
||||
published: note.createdAt.toISOString(),
|
||||
to,
|
||||
cc,
|
||||
|
||||
@@ -3,6 +3,7 @@ import Note from '../../../../models/note';
|
||||
import { getFriendIds } from '../../common/get-friends';
|
||||
import { pack } from '../../../../models/note';
|
||||
import { ILocalUser } from '../../../../models/user';
|
||||
import getParams from '../../get-params';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
@@ -10,42 +11,48 @@ export const meta = {
|
||||
'en-US': 'Get mentions of myself.'
|
||||
},
|
||||
|
||||
requireCredential: true
|
||||
requireCredential: true,
|
||||
|
||||
params: {
|
||||
following: $.bool.optional.note({
|
||||
default: false
|
||||
}),
|
||||
|
||||
limit: $.num.optional.range(1, 100).note({
|
||||
default: 10
|
||||
}),
|
||||
|
||||
sinceId: $.type(ID).optional.note({
|
||||
}),
|
||||
|
||||
untilId: $.type(ID).optional.note({
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
|
||||
// Get 'following' parameter
|
||||
const [following = false, followingError] =
|
||||
$.bool.optional.get(params.following);
|
||||
if (followingError) return rej('invalid following param');
|
||||
|
||||
// Get 'limit' parameter
|
||||
const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
|
||||
if (limitErr) return rej('invalid limit param');
|
||||
|
||||
// Get 'sinceId' parameter
|
||||
const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
|
||||
if (sinceIdErr) return rej('invalid sinceId param');
|
||||
|
||||
// Get 'untilId' parameter
|
||||
const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
|
||||
if (untilIdErr) return rej('invalid untilId param');
|
||||
const [ps, psErr] = getParams(meta, params);
|
||||
if (psErr) throw psErr;
|
||||
|
||||
// Check if both of sinceId and untilId is specified
|
||||
if (sinceId && untilId) {
|
||||
if (ps.sinceId && ps.untilId) {
|
||||
return rej('cannot set sinceId and untilId');
|
||||
}
|
||||
|
||||
// Construct query
|
||||
const query = {
|
||||
$or: [{
|
||||
mentions: user._id
|
||||
}, {
|
||||
visibleUserIds: user._id
|
||||
}]
|
||||
} as any;
|
||||
|
||||
const sort = {
|
||||
_id: -1
|
||||
};
|
||||
|
||||
if (following) {
|
||||
if (ps.following) {
|
||||
const followingIds = await getFriendIds(user._id);
|
||||
|
||||
query.userId = {
|
||||
@@ -53,26 +60,24 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
|
||||
};
|
||||
}
|
||||
|
||||
if (sinceId) {
|
||||
if (ps.sinceId) {
|
||||
sort._id = 1;
|
||||
query._id = {
|
||||
$gt: sinceId
|
||||
$gt: ps.sinceId
|
||||
};
|
||||
} else if (untilId) {
|
||||
} else if (ps.untilId) {
|
||||
query._id = {
|
||||
$lt: untilId
|
||||
$lt: ps.untilId
|
||||
};
|
||||
}
|
||||
|
||||
// Issue query
|
||||
const mentions = await Note
|
||||
.find(query, {
|
||||
limit: limit,
|
||||
limit: ps.limit,
|
||||
sort: sort
|
||||
});
|
||||
|
||||
// Serialize
|
||||
res(await Promise.all(mentions.map(async mention =>
|
||||
await pack(mention, user)
|
||||
)));
|
||||
res(await Promise.all(mentions.map(mention => pack(mention, user))));
|
||||
});
|
||||
|
||||
@@ -13,12 +13,18 @@ export const meta = {
|
||||
},
|
||||
|
||||
params: {
|
||||
tag: $.str.note({
|
||||
tag: $.str.optional.note({
|
||||
desc: {
|
||||
'ja-JP': 'タグ'
|
||||
}
|
||||
}),
|
||||
|
||||
query: $.arr($.arr($.str)).optional.note({
|
||||
desc: {
|
||||
'ja-JP': 'クエリ'
|
||||
}
|
||||
}),
|
||||
|
||||
includeUserIds: $.arr($.type(ID)).optional.note({
|
||||
default: []
|
||||
}),
|
||||
@@ -59,11 +65,9 @@ export const meta = {
|
||||
}
|
||||
}),
|
||||
|
||||
withFiles: $.bool.optional.nullable.note({
|
||||
default: null,
|
||||
|
||||
withFiles: $.bool.optional.note({
|
||||
desc: {
|
||||
'ja-JP': 'ファイルが添付された投稿に限定するか否か'
|
||||
'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -83,6 +87,12 @@ export const meta = {
|
||||
}
|
||||
}),
|
||||
|
||||
untilId: $.type(ID).optional.note({
|
||||
desc: {
|
||||
'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します'
|
||||
}
|
||||
}),
|
||||
|
||||
sinceDate: $.num.optional.note({
|
||||
}),
|
||||
|
||||
@@ -126,8 +136,14 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
||||
}
|
||||
|
||||
const q: any = {
|
||||
$and: [{
|
||||
$and: [ps.tag ? {
|
||||
tagsLower: ps.tag.toLowerCase()
|
||||
} : {
|
||||
$or: ps.query.map(tags => ({
|
||||
$and: tags.map(t => ({
|
||||
tagsLower: t.toLowerCase()
|
||||
}))
|
||||
}))
|
||||
}],
|
||||
deletedAt: { $exists: false }
|
||||
};
|
||||
@@ -281,25 +297,10 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
||||
|
||||
const withFiles = ps.withFiles != null ? ps.withFiles : ps.media;
|
||||
|
||||
if (withFiles != null) {
|
||||
if (withFiles) {
|
||||
push({
|
||||
fileIds: {
|
||||
$exists: true,
|
||||
$ne: null
|
||||
}
|
||||
fileIds: { $exists: true, $ne: [] }
|
||||
});
|
||||
} else {
|
||||
push({
|
||||
$or: [{
|
||||
fileIds: {
|
||||
$exists: false
|
||||
}
|
||||
}, {
|
||||
fileIds: null
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (ps.poll != null) {
|
||||
@@ -323,6 +324,14 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
|
||||
}
|
||||
}
|
||||
|
||||
if (ps.untilId) {
|
||||
push({
|
||||
_id: {
|
||||
$lt: ps.untilId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (ps.sinceDate) {
|
||||
push({
|
||||
createdAt: {
|
||||
|
||||
48
src/server/api/stream/hashtag.ts
Normal file
48
src/server/api/stream/hashtag.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import * as websocket from 'websocket';
|
||||
import Xev from 'xev';
|
||||
|
||||
import { IUser } from '../../../models/user';
|
||||
import Mute from '../../../models/mute';
|
||||
import { pack } from '../../../models/note';
|
||||
|
||||
export default async function(
|
||||
request: websocket.request,
|
||||
connection: websocket.connection,
|
||||
subscriber: Xev,
|
||||
user?: IUser
|
||||
) {
|
||||
const mute = user ? await Mute.find({ muterId: user._id }) : null;
|
||||
const mutedUserIds = mute ? mute.map(m => m.muteeId.toString()) : [];
|
||||
|
||||
const q: Array<string[]> = JSON.parse((request.resourceURL.query as any).q);
|
||||
|
||||
// Subscribe stream
|
||||
subscriber.on('hashtag', async note => {
|
||||
const matched = q.some(tags => tags.every(tag => note.tags.map((t: string) => t.toLowerCase()).includes(tag.toLowerCase())));
|
||||
if (!matched) return;
|
||||
|
||||
// Renoteなら再pack
|
||||
if (note.renoteId != null) {
|
||||
note.renote = await pack(note.renoteId, user, {
|
||||
detail: true
|
||||
});
|
||||
}
|
||||
|
||||
//#region 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (mutedUserIds.indexOf(note.userId) != -1) {
|
||||
return;
|
||||
}
|
||||
if (note.reply != null && mutedUserIds.indexOf(note.reply.userId) != -1) {
|
||||
return;
|
||||
}
|
||||
if (note.renote != null && mutedUserIds.indexOf(note.renote.userId) != -1) {
|
||||
return;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
connection.send(JSON.stringify({
|
||||
type: 'note',
|
||||
body: note
|
||||
}));
|
||||
});
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import reversiGameStream from './stream/games/reversi-game';
|
||||
import reversiStream from './stream/games/reversi';
|
||||
import serverStatsStream from './stream/server-stats';
|
||||
import notesStatsStream from './stream/notes-stats';
|
||||
import hashtagStream from './stream/hashtag';
|
||||
import { ParsedUrlQuery } from 'querystring';
|
||||
import authenticate from './authenticate';
|
||||
|
||||
@@ -44,6 +45,12 @@ module.exports = (server: http.Server) => {
|
||||
ev.removeAllListeners();
|
||||
});
|
||||
|
||||
connection.on('message', async (data) => {
|
||||
if (data.utf8Data == 'ping') {
|
||||
connection.send('pong');
|
||||
}
|
||||
});
|
||||
|
||||
const q = request.resourceURL.query as ParsedUrlQuery;
|
||||
const [user, app] = await authenticate(q.i as string);
|
||||
|
||||
@@ -57,6 +64,11 @@ module.exports = (server: http.Server) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.resourceURL.pathname === '/hashtag') {
|
||||
hashtagStream(request, connection, ev, user);
|
||||
return;
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
connection.send('authentication-failed');
|
||||
connection.close();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import es from '../../db/elasticsearch';
|
||||
import Note, { pack, INote } from '../../models/note';
|
||||
import User, { isLocalUser, IUser, isRemoteUser, IRemoteUser, ILocalUser } from '../../models/user';
|
||||
import { publishUserStream, publishLocalTimelineStream, publishHybridTimelineStream, publishGlobalTimelineStream, publishUserListStream } from '../../stream';
|
||||
import { publishUserStream, publishLocalTimelineStream, publishHybridTimelineStream, publishGlobalTimelineStream, publishUserListStream, publishHashtagStream } from '../../stream';
|
||||
import Following from '../../models/following';
|
||||
import { deliver } from '../../queue';
|
||||
import renderNote from '../../remote/activitypub/renderer/note';
|
||||
@@ -138,6 +138,10 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
|
||||
|
||||
const mentionedUsers = await extractMentionedUsers(tokens);
|
||||
|
||||
if (data.reply && !user._id.equals(data.reply.userId) && !mentionedUsers.some(u => u._id.equals(data.reply.userId))) {
|
||||
mentionedUsers.push(await User.findOne({ _id: data.reply.userId }));
|
||||
}
|
||||
|
||||
const note = await insertNote(user, data, tags, mentionedUsers);
|
||||
|
||||
res(note);
|
||||
@@ -177,6 +181,10 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
|
||||
noteObj.isFirstNote = true;
|
||||
}
|
||||
|
||||
if (tags.length > 0) {
|
||||
publishHashtagStream(noteObj);
|
||||
}
|
||||
|
||||
const nm = new NotificationManager(user, note);
|
||||
const nmRelatedPromises = [];
|
||||
|
||||
|
||||
@@ -78,6 +78,10 @@ class Publisher {
|
||||
public publishGlobalTimelineStream = (note: any): void => {
|
||||
this.publish('global-timeline', null, note);
|
||||
}
|
||||
|
||||
public publishHashtagStream = (note: any): void => {
|
||||
this.publish('hashtag', null, note);
|
||||
}
|
||||
}
|
||||
|
||||
const publisher = new Publisher();
|
||||
@@ -95,3 +99,4 @@ export const publishReversiGameStream = publisher.publishReversiGameStream;
|
||||
export const publishLocalTimelineStream = publisher.publishLocalTimelineStream;
|
||||
export const publishHybridTimelineStream = publisher.publishHybridTimelineStream;
|
||||
export const publishGlobalTimelineStream = publisher.publishGlobalTimelineStream;
|
||||
export const publishHashtagStream = publisher.publishHashtagStream;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as assert from 'assert';
|
||||
|
||||
import analyze from '../src/mfm/parse';
|
||||
import toHtml from '../src/mfm/html';
|
||||
import syntaxhighlighter from '../src/mfm/parse/core/syntax-highlighter';
|
||||
|
||||
describe('Text', () => {
|
||||
@@ -170,4 +171,12 @@ describe('Text', () => {
|
||||
assert.equal(html, '<span class="symbol">/</span>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toHtml', () => {
|
||||
it('br', () => {
|
||||
const input = 'foo\nbar\nbaz';
|
||||
const output = '<p>foo<br>bar<br>baz</p>';
|
||||
assert.equal(toHtml(analyze(input)), output);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user