Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
7b44727b23 | |||
debe648a98 | |||
fda8cf77ed | |||
8aaab195c6 | |||
4096ddcbd0 | |||
ae6cc11ad2 | |||
dab7e527de | |||
8b92feac71 | |||
4beb3e5755 | |||
55f63229cd | |||
7827aeb695 | |||
cce768aaac | |||
80b83c0624 | |||
73b683bb4d | |||
d78a5c0863 | |||
683e5b6abe | |||
653b8f6352 | |||
9ec6afa375 | |||
adff5382ca | |||
704aabd703 | |||
f7b1ef0690 | |||
929982117f | |||
56a530d769 | |||
7ef75fb06b | |||
112a72abdf | |||
71813e03ee | |||
9b05b6ef28 | |||
54a87b25b3 | |||
55e97864bd | |||
95733c9490 | |||
e19ae644f1 | |||
ac4ea25267 | |||
611e4f34dc | |||
faf017f333 | |||
5eec896615 | |||
17f35174ea | |||
bf71b31123 | |||
9399a44c82 | |||
c96418806f | |||
7945eddef6 | |||
0ede390fef | |||
85959a3b9b | |||
946c3a25b9 | |||
f9d697128a |
@ -56,22 +56,10 @@ jobs:
|
|||||||
executor:
|
executor:
|
||||||
type: string
|
type: string
|
||||||
default: "default"
|
default: "default"
|
||||||
without_redis:
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
executor: <<parameters.executor>>
|
executor: <<parameters.executor>>
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: /tmp/workspace
|
at: /tmp/workspace
|
||||||
- when:
|
|
||||||
condition: <<parameters.without_redis>>
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Configure
|
|
||||||
command: |
|
|
||||||
mv .config/test.yml .config/test_redis.yml
|
|
||||||
touch .config/test.yml
|
|
||||||
cat .config/test_redis.yml | while IFS= read line; do if [[ "$line" = '# __REDIS__' ]]; then break; else echo "$line" >> .config/test.yml; fi; done
|
|
||||||
- run:
|
- run:
|
||||||
name: Test
|
name: Test
|
||||||
command: |
|
command: |
|
||||||
@ -134,32 +122,14 @@ workflows:
|
|||||||
branches:
|
branches:
|
||||||
only: master
|
only: master
|
||||||
- test:
|
- test:
|
||||||
name: manual-test-with-redis
|
name: manual-test
|
||||||
executor: with-redis
|
|
||||||
requires:
|
requires:
|
||||||
- manual-build
|
- manual-build
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
ignore: master
|
ignore: master
|
||||||
- test:
|
- test:
|
||||||
name: auto-test-without-redis
|
name: auto-test
|
||||||
executor: with-redis
|
|
||||||
requires:
|
|
||||||
- auto-build
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only: master
|
|
||||||
- test:
|
|
||||||
name: manual-test-with-redis
|
|
||||||
without_redis: true
|
|
||||||
requires:
|
|
||||||
- manual-build
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
ignore: master
|
|
||||||
- test:
|
|
||||||
name: auto-test-without-redis
|
|
||||||
without_redis: true
|
|
||||||
requires:
|
requires:
|
||||||
- auto-build
|
- auto-build
|
||||||
filters:
|
filters:
|
||||||
|
31
CHANGELOG.md
31
CHANGELOG.md
@ -5,6 +5,37 @@ 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`)
|
||||||
|
|
||||||
|
11.2.0 (2019/04/18)
|
||||||
|
-------------------
|
||||||
|
### Improvements
|
||||||
|
* 検索で日付(日時)を入力するとタイムラインをその時点まで遡るように
|
||||||
|
* APIコンソールでエンドポイントをサジェストするように
|
||||||
|
* モバイル版でドライブのメニューを使いやすく
|
||||||
|
* サイレンス時に確認を表示するように
|
||||||
|
* ユーザーメニューでブロックなどの操作を行う時に確認するように
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
* アプリケーション連携画面でパーミッションが表示されない問題を修正
|
||||||
|
* アンケートウィジットでもMFMを使用するように
|
||||||
|
* フォローしてないユーザーのホーム投稿がSTLに流れてくる問題を修正
|
||||||
|
* モバイル版でウィジェットを設定できない問題を修正
|
||||||
|
* スプラッシュがクリックに反応するように
|
||||||
|
|
||||||
|
11.1.6 (2019/04/18)
|
||||||
|
-------------------
|
||||||
|
### Fixes
|
||||||
|
* 未認知ユーザーからActivityが飛んできた場合に処理できない問題を修正
|
||||||
|
* その投稿を見たのにも関わらずメンションインジケーターが点灯し続ける問題を修正
|
||||||
|
* ハッシュタグの判定を改善
|
||||||
|
* サーバーのエラーハンドリングを改善
|
||||||
|
|
||||||
|
11.1.5 (2019/04/17)
|
||||||
|
-------------------
|
||||||
|
### Fixes
|
||||||
|
* ユーザー名に含まれているカスタム絵文字が表示されないことがある問題を修正
|
||||||
|
* 壁紙の設定ができない問題を修正
|
||||||
|
* デザインの調整
|
||||||
|
|
||||||
11.1.4 (2019/04/17)
|
11.1.4 (2019/04/17)
|
||||||
-------------------
|
-------------------
|
||||||
### Fixes
|
### Fixes
|
||||||
|
@ -46,10 +46,40 @@ Convert な(na) to にゃ(nya)
|
|||||||
Revert Nyaize
|
Revert Nyaize
|
||||||
|
|
||||||
## Code style
|
## Code style
|
||||||
### Use semicolon
|
### セミコロンを省略しない
|
||||||
To avoid ASI Hazard
|
ASI Hazardを避けるためでもある
|
||||||
|
|
||||||
|
### 中括弧を省略しない
|
||||||
|
Bad:
|
||||||
|
``` ts
|
||||||
|
if (foo)
|
||||||
|
bar;
|
||||||
|
else
|
||||||
|
baz;
|
||||||
|
```
|
||||||
|
|
||||||
|
Good:
|
||||||
|
``` ts
|
||||||
|
if (foo) {
|
||||||
|
bar;
|
||||||
|
} else {
|
||||||
|
baz;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
ただし**`if`が一行**の時だけは省略しても良い
|
||||||
|
Good:
|
||||||
|
``` ts
|
||||||
|
if (foo) bar;
|
||||||
|
```
|
||||||
|
|
||||||
|
### `export default`を使わない
|
||||||
|
インテリセンスと相性が悪かったりするため
|
||||||
|
|
||||||
|
参考:
|
||||||
|
* https://gfx.hatenablog.com/entry/2017/11/24/135343
|
||||||
|
* https://basarat.gitbooks.io/typescript/docs/tips/defaultIsBad.html
|
||||||
|
|
||||||
### Don't use `export default`
|
|
||||||
Bad:
|
Bad:
|
||||||
``` ts
|
``` ts
|
||||||
export default function(foo: string): string {
|
export default function(foo: string): string {
|
||||||
|
@ -21,12 +21,11 @@ RUN apk add --no-cache \
|
|||||||
pkgconfig \
|
pkgconfig \
|
||||||
python \
|
python \
|
||||||
zlib-dev
|
zlib-dev
|
||||||
RUN npm i -g yarn
|
|
||||||
|
|
||||||
COPY package.json ./
|
COPY package.json ./
|
||||||
RUN yarn install
|
RUN npm i
|
||||||
COPY . ./
|
COPY . ./
|
||||||
RUN yarn build
|
RUN npm run build
|
||||||
|
|
||||||
FROM base AS runner
|
FROM base AS runner
|
||||||
|
|
||||||
|
@ -70,8 +70,14 @@ common:
|
|||||||
followers: "Sledující"
|
followers: "Sledující"
|
||||||
favorites: "Oblíbené"
|
favorites: "Oblíbené"
|
||||||
permissions:
|
permissions:
|
||||||
'read:drive': "Prohlížet Disk"
|
"read:account": "Zobrazit informace o účtu"
|
||||||
'write:drive': "Pracovat s Diskem"
|
"read:drive": "Prohlížet Disk"
|
||||||
|
"write:drive": "Pracovat s Diskem"
|
||||||
|
"read:favorites": "Prohlížet oblíbené"
|
||||||
|
"read:messaging": "Prohlížet konverzaci"
|
||||||
|
"write:messaging": "Pracovat s konverzaci"
|
||||||
|
"read:mutes": "Prohlížet ztlumené"
|
||||||
|
"write:votes": "Hlasovat"
|
||||||
empty-timeline-info:
|
empty-timeline-info:
|
||||||
follow-users-to-make-your-timeline: "Poznámky sledujících se zobrazí ve vaší časové ose"
|
follow-users-to-make-your-timeline: "Poznámky sledujících se zobrazí ve vaší časové ose"
|
||||||
explore: "Najít uživatele"
|
explore: "Najít uživatele"
|
||||||
@ -1114,7 +1120,7 @@ mobile/views/components/post-form.vue:
|
|||||||
reply: "Odpovědět"
|
reply: "Odpovědět"
|
||||||
renote: "Renotovat"
|
renote: "Renotovat"
|
||||||
reply-placeholder: "Odpovědět na tento příspěvěk"
|
reply-placeholder: "Odpovědět na tento příspěvěk"
|
||||||
location-alert: "Vaše zařízení nepodporuje lokační službu"
|
geolocation-alert: "Vaše zařízení nepodporuje lokační službu"
|
||||||
error: "Chyba"
|
error: "Chyba"
|
||||||
username-prompt: "Zadejte uživatelské jméno"
|
username-prompt: "Zadejte uživatelské jméno"
|
||||||
mobile/views/components/sub-note-content.vue:
|
mobile/views/components/sub-note-content.vue:
|
||||||
@ -1185,4 +1191,3 @@ deck/deck.user-column.vue:
|
|||||||
activity: "Aktivita"
|
activity: "Aktivita"
|
||||||
dev/views/new-app.vue:
|
dev/views/new-app.vue:
|
||||||
app-name-desc: "Jméno vaší aplikace"
|
app-name-desc: "Jméno vaší aplikace"
|
||||||
app-desc: "Stručný popis nebo představení vaší aplikace."
|
|
||||||
|
@ -62,10 +62,14 @@ common:
|
|||||||
followers: "Folgende"
|
followers: "Folgende"
|
||||||
favorites: "Diesen Beitrag favorisieren"
|
favorites: "Diesen Beitrag favorisieren"
|
||||||
permissions:
|
permissions:
|
||||||
'read:account': "Accountinformationen anzeigen."
|
"read:account": "Accountinformationen anzeigen."
|
||||||
'write:account': "Accountinformationen bearbeiten."
|
"write:account": "Accountinformationen bearbeiten."
|
||||||
'read:drive': "Dateien anzeigen"
|
"read:drive": "Dateien anzeigen"
|
||||||
'write:drive': "Dateien bearbeiten"
|
"write:drive": "Dateien bearbeiten"
|
||||||
|
"read:favorites": "Favoriten anzeigen"
|
||||||
|
"read:messaging": "Unterhaltung anzeigen"
|
||||||
|
"write:messaging": "Unterhaltung bearbeiten"
|
||||||
|
"write:votes": "Abstimmen"
|
||||||
empty-timeline-info:
|
empty-timeline-info:
|
||||||
follow-users-to-make-your-timeline: "Beiträge von Benutzern, denen du folgst, werden in der Zeitleiste angezeigt."
|
follow-users-to-make-your-timeline: "Beiträge von Benutzern, denen du folgst, werden in der Zeitleiste angezeigt."
|
||||||
explore: "Benutzer finden"
|
explore: "Benutzer finden"
|
||||||
@ -739,10 +743,7 @@ dev/views/new-app.vue:
|
|||||||
create-app: "Erstelle Anwendung"
|
create-app: "Erstelle Anwendung"
|
||||||
app-name: "Name der Anwendung"
|
app-name: "Name der Anwendung"
|
||||||
app-name-desc: "Der Name der Anwendung"
|
app-name-desc: "Der Name der Anwendung"
|
||||||
app-name-ex: "z.B. Misskey für iOS"
|
|
||||||
app-overview: "Beschreibung der Anwendung"
|
app-overview: "Beschreibung der Anwendung"
|
||||||
app-desc: "Eine kurze Beschreibung oder Einführung der Anwendung."
|
|
||||||
app-desc-ex: "z.B. Ein iOS-Client für Misskey."
|
|
||||||
callback-url: "Callback-URL (optional)"
|
callback-url: "Callback-URL (optional)"
|
||||||
callback-url-desc: "Die URL, auf die nach erfolgreicher Authentifizierung umgeleitet werden soll."
|
callback-url-desc: "Die URL, auf die nach erfolgreicher Authentifizierung umgeleitet werden soll."
|
||||||
authority: "Berechtigungen"
|
authority: "Berechtigungen"
|
||||||
|
@ -70,10 +70,21 @@ common:
|
|||||||
followers: "Followers"
|
followers: "Followers"
|
||||||
favorites: "Favorites"
|
favorites: "Favorites"
|
||||||
permissions:
|
permissions:
|
||||||
'read:account': "View account information"
|
"read:account": "View account information"
|
||||||
'write:account': "Update your account information"
|
"write:account": "Update your account information"
|
||||||
'read:drive': "Browse the Drive"
|
"read:blocks": "View Blocks"
|
||||||
'write:drive': "Work with the Drive"
|
"write:blocks": "Work with Blocks"
|
||||||
|
"read:drive": "Browse the Drive"
|
||||||
|
"write:drive": "Work with the Drive"
|
||||||
|
"read:favorites": "View Favorites"
|
||||||
|
"write:favorites": "Work with Favorites"
|
||||||
|
"read:following": "View Follower info"
|
||||||
|
"write:following": "Work with Follow info"
|
||||||
|
"read:messaging": "View Messaging"
|
||||||
|
"write:messaging": "Work with Messaging"
|
||||||
|
"read:mutes": "View Muted"
|
||||||
|
"write:mutes": "Work with Muted"
|
||||||
|
"write:votes": "Vote"
|
||||||
empty-timeline-info:
|
empty-timeline-info:
|
||||||
follow-users-to-make-your-timeline: "Following users will show their posts in your timeline."
|
follow-users-to-make-your-timeline: "Following users will show their posts in your timeline."
|
||||||
explore: "Find users"
|
explore: "Find users"
|
||||||
@ -281,6 +292,7 @@ common:
|
|||||||
nav: "Navigation"
|
nav: "Navigation"
|
||||||
tips: "Tips"
|
tips: "Tips"
|
||||||
hashtags: "Hashtags"
|
hashtags: "Hashtags"
|
||||||
|
queue: "Queue"
|
||||||
dev: "Failed to create the application. Please try again."
|
dev: "Failed to create the application. Please try again."
|
||||||
ai-chan-kawaii: "Ai-chan kawaii!"
|
ai-chan-kawaii: "Ai-chan kawaii!"
|
||||||
you: "You"
|
you: "You"
|
||||||
@ -1462,7 +1474,7 @@ mobile/views/components/post-form.vue:
|
|||||||
quote-placeholder: "Quote this post... (optional)"
|
quote-placeholder: "Quote this post... (optional)"
|
||||||
reply-placeholder: "Reply to this note..."
|
reply-placeholder: "Reply to this note..."
|
||||||
cw-placeholder: "Comments for the post (optional)"
|
cw-placeholder: "Comments for the post (optional)"
|
||||||
location-alert: "Your device does not provide location services"
|
geolocation-alert: "Your device does not provide location services."
|
||||||
error: "Error"
|
error: "Error"
|
||||||
username-prompt: "Enter user name"
|
username-prompt: "Enter user name"
|
||||||
mobile/views/components/sub-note-content.vue:
|
mobile/views/components/sub-note-content.vue:
|
||||||
@ -1615,10 +1627,7 @@ dev/views/new-app.vue:
|
|||||||
create-app: "Creating application"
|
create-app: "Creating application"
|
||||||
app-name: "Application name"
|
app-name: "Application name"
|
||||||
app-name-desc: "The name of your app"
|
app-name-desc: "The name of your app"
|
||||||
app-name-ex: "ex) Misskey for iOS"
|
|
||||||
app-overview: "Application summary"
|
app-overview: "Application summary"
|
||||||
app-desc: "A brief description or introduction of your app."
|
|
||||||
app-desc-ex: "ex) Misskey iOS client."
|
|
||||||
callback-url: "The callback URL (optional)"
|
callback-url: "The callback URL (optional)"
|
||||||
callback-url-desc: "The URL to redirect to after the user is authenticated via the authentication form."
|
callback-url-desc: "The URL to redirect to after the user is authenticated via the authentication form."
|
||||||
authority: "Permissions"
|
authority: "Permissions"
|
||||||
|
@ -20,8 +20,15 @@ common:
|
|||||||
application-authorization: "Autorizaciones de la aplicación."
|
application-authorization: "Autorizaciones de la aplicación."
|
||||||
close: "Cerrar"
|
close: "Cerrar"
|
||||||
do-not-copy-paste: "Por favor no copies código aquí. Tu cuenta puede resultar comprometida."
|
do-not-copy-paste: "Por favor no copies código aquí. Tu cuenta puede resultar comprometida."
|
||||||
|
load-more: "Leer más"
|
||||||
enter-password: "Escribe una contraseña"
|
enter-password: "Escribe una contraseña"
|
||||||
2fa: "Autenticación de dos factores"
|
2fa: "Autenticación de dos factores"
|
||||||
|
customize-home: "Personaliza la página principal"
|
||||||
|
featured-notes: "Destacados"
|
||||||
|
dark-mode: "Modo oscuro"
|
||||||
|
signin: "Iniciar sesión"
|
||||||
|
signup: "¡Regístrate!"
|
||||||
|
signout: "Cerrar sesión"
|
||||||
got-it: "¡Listo!"
|
got-it: "¡Listo!"
|
||||||
customization-tips:
|
customization-tips:
|
||||||
title: "Consejos de personalización"
|
title: "Consejos de personalización"
|
||||||
@ -50,8 +57,22 @@ common:
|
|||||||
drive: "Drive"
|
drive: "Drive"
|
||||||
messaging: "Conversación"
|
messaging: "Conversación"
|
||||||
home: "Inicio"
|
home: "Inicio"
|
||||||
|
deck: "Deck"
|
||||||
|
timeline: "Timeline"
|
||||||
|
explore: "Explorar"
|
||||||
following: "Siguiendo"
|
following: "Siguiendo"
|
||||||
|
followers: "Seguidores"
|
||||||
favorites: "Me gusta esta nota"
|
favorites: "Me gusta esta nota"
|
||||||
|
permissions:
|
||||||
|
"read:account": "Ver información de la cuenta"
|
||||||
|
"write:account": "Editar información de la cuenta"
|
||||||
|
"read:blocks": "Ver bloques"
|
||||||
|
"write:blocks": "Editar bloques"
|
||||||
|
"read:favorites": "Ver favoritos"
|
||||||
|
"write:favorites": "Editar favoritos"
|
||||||
|
"read:messaging": "Ver conversación"
|
||||||
|
"read:notifications": "Ver notificaciones"
|
||||||
|
"write:votes": "Vota"
|
||||||
weekday-short:
|
weekday-short:
|
||||||
sunday: "domingo"
|
sunday: "domingo"
|
||||||
monday: "lunes"
|
monday: "lunes"
|
||||||
@ -97,12 +118,24 @@ common:
|
|||||||
d: "¿Quieres decir algo?"
|
d: "¿Quieres decir algo?"
|
||||||
e: "¡Escribe aquí!"
|
e: "¡Escribe aquí!"
|
||||||
f: "Esperando a que escribas algo..."
|
f: "Esperando a que escribas algo..."
|
||||||
|
settings: "Configuración"
|
||||||
_settings:
|
_settings:
|
||||||
profile: "Tu perfil"
|
profile: "Tu perfil"
|
||||||
notification: "Notificaciones"
|
notification: "Notificaciones"
|
||||||
|
apps: "Aplicaciones"
|
||||||
tags: "Etiquetas"
|
tags: "Etiquetas"
|
||||||
|
mute-and-block: "Silenciar/Bloquear"
|
||||||
blocking: "Bloquear"
|
blocking: "Bloquear"
|
||||||
|
security: "Seguridad"
|
||||||
password: "Contraseña"
|
password: "Contraseña"
|
||||||
|
other: "Otros"
|
||||||
|
appearance: "Diseño"
|
||||||
|
behavior: "Comportamiento"
|
||||||
|
fetch-on-scroll-desc: "Cuando te deslizas al final de la página nuevo contenido se carga automáticamente."
|
||||||
|
note-visibility: "Visibilidad de la publicación"
|
||||||
|
default-note-visibility: "Rango de publicación predeterminado"
|
||||||
|
web-search-engine: "Buscador web"
|
||||||
|
web-search-engine-desc: "Ejemplo: https://www.google.com/?#q={{query}}"
|
||||||
use-os-default-emojis: "Usar los emoticonos estándar del sistema operativo"
|
use-os-default-emojis: "Usar los emoticonos estándar del sistema operativo"
|
||||||
line-width: "Grosor de línea"
|
line-width: "Grosor de línea"
|
||||||
line-width-thick: "Grosor"
|
line-width-thick: "Grosor"
|
||||||
@ -128,6 +161,19 @@ common:
|
|||||||
contrasted-acct: "Añadir contraste al nombre de usuario"
|
contrasted-acct: "Añadir contraste al nombre de usuario"
|
||||||
wallpaper: "Fondo de pantalla"
|
wallpaper: "Fondo de pantalla"
|
||||||
choose-wallpaper: "Escoge un fondo de pantalla"
|
choose-wallpaper: "Escoge un fondo de pantalla"
|
||||||
|
delete-wallpaper: "Quitar fondo de pantalla"
|
||||||
|
show-clock-on-header: "Muestra el reloj en la parte superior derecha"
|
||||||
|
timeline: "Timeline"
|
||||||
|
sound: "Sonido"
|
||||||
|
enable-sounds: "Habilitar sonido"
|
||||||
|
volume: "Volúmen"
|
||||||
|
test: "Prueba"
|
||||||
|
version: "Versión"
|
||||||
|
no-updates: "No hay actualizaciones disponibles"
|
||||||
|
no-updates-desc: "Tu Misskey está actualizado"
|
||||||
|
update-available: "¡Una nueva versión está disponible!"
|
||||||
|
update-available-desc: "Las actualizaciones se aplicarán cuando la página se vuelva a cargar."
|
||||||
|
advanced-settings: "Configuraciones avanzadas"
|
||||||
navbar-position-left: "Izquierda"
|
navbar-position-left: "Izquierda"
|
||||||
search: "Buscar"
|
search: "Buscar"
|
||||||
delete: "eliminar"
|
delete: "eliminar"
|
||||||
@ -869,6 +915,7 @@ mobile/views/components/post-form.vue:
|
|||||||
reply: "Responder"
|
reply: "Responder"
|
||||||
renote: "Republicar"
|
renote: "Republicar"
|
||||||
reply-placeholder: "Responder a esta nota..."
|
reply-placeholder: "Responder a esta nota..."
|
||||||
|
geolocation-alert: "Tu dispositivo no tiene soporte de geolocalización."
|
||||||
mobile/views/components/sub-note-content.vue:
|
mobile/views/components/sub-note-content.vue:
|
||||||
private: "Esta publicación es privada"
|
private: "Esta publicación es privada"
|
||||||
deleted: "Esta publicación ha sido removida"
|
deleted: "Esta publicación ha sido removida"
|
||||||
|
@ -68,10 +68,11 @@ common:
|
|||||||
followers: "Abonné·e·s"
|
followers: "Abonné·e·s"
|
||||||
favorites: "Mettre cette note en favoris"
|
favorites: "Mettre cette note en favoris"
|
||||||
permissions:
|
permissions:
|
||||||
'read:account': "Afficher les informations du compte"
|
"read:account": "Afficher les informations du compte"
|
||||||
'write:account': "Mettre à jour les informations de votre compte"
|
"write:account": "Mettre à jour les informations de votre compte"
|
||||||
'read:drive': "Parcourir le Drive"
|
"read:drive": "Parcourir le Drive"
|
||||||
'write:drive': "Écrire sur le Drive"
|
"write:drive": "Écrire sur le Drive"
|
||||||
|
"write:votes": "Vote"
|
||||||
empty-timeline-info:
|
empty-timeline-info:
|
||||||
follow-users-to-make-your-timeline: "Les utilisateurs suivants afficheront leurs publications sur votre fil."
|
follow-users-to-make-your-timeline: "Les utilisateurs suivants afficheront leurs publications sur votre fil."
|
||||||
explore: "Trouver des utilisateurs"
|
explore: "Trouver des utilisateurs"
|
||||||
@ -274,6 +275,7 @@ common:
|
|||||||
nav: "Navigation"
|
nav: "Navigation"
|
||||||
tips: "Conseils"
|
tips: "Conseils"
|
||||||
hashtags: "Hashtags"
|
hashtags: "Hashtags"
|
||||||
|
queue: "File d'attente"
|
||||||
dev: "Échec lors de la création de l’application. Veuillez réessayer."
|
dev: "Échec lors de la création de l’application. Veuillez réessayer."
|
||||||
ai-chan-kawaii: "Ai-Chan est mignonne !"
|
ai-chan-kawaii: "Ai-Chan est mignonne !"
|
||||||
you: "Vous"
|
you: "Vous"
|
||||||
@ -1434,7 +1436,7 @@ mobile/views/components/post-form.vue:
|
|||||||
quote-placeholder: "Citer ce billet ... (Facultatif)"
|
quote-placeholder: "Citer ce billet ... (Facultatif)"
|
||||||
reply-placeholder: "Répondre à cette note"
|
reply-placeholder: "Répondre à cette note"
|
||||||
cw-placeholder: "Commenter le contenu (optionnel)"
|
cw-placeholder: "Commenter le contenu (optionnel)"
|
||||||
location-alert: "Votre appareil ne prend pas en charge les services de localisation"
|
geolocation-alert: "Votre appareil ne prend pas en charge les services de localisation"
|
||||||
error: "Erreur"
|
error: "Erreur"
|
||||||
username-prompt: "Saisir un nom d'utilisateur"
|
username-prompt: "Saisir un nom d'utilisateur"
|
||||||
mobile/views/components/sub-note-content.vue:
|
mobile/views/components/sub-note-content.vue:
|
||||||
@ -1586,10 +1588,7 @@ dev/views/new-app.vue:
|
|||||||
create-app: "Création d’une application"
|
create-app: "Création d’une application"
|
||||||
app-name: "Nom de l’application"
|
app-name: "Nom de l’application"
|
||||||
app-name-desc: "Le nom de votre application"
|
app-name-desc: "Le nom de votre application"
|
||||||
app-name-ex: "p. ex. Misskey pour iOS"
|
|
||||||
app-overview: "Description courte de l’application"
|
app-overview: "Description courte de l’application"
|
||||||
app-desc: "Brève description introductive à votre application."
|
|
||||||
app-desc-ex: "p. ex) Misskey pour iOS"
|
|
||||||
callback-url: "L’Url de callback (facultatif)"
|
callback-url: "L’Url de callback (facultatif)"
|
||||||
callback-url-desc: "Vous pouvez définir l’URL de redirection lorsque l’utilisateur s’est authentifié via formulaire d’authentification."
|
callback-url-desc: "Vous pouvez définir l’URL de redirection lorsque l’utilisateur s’est authentifié via formulaire d’authentification."
|
||||||
authority: "Autorisations "
|
authority: "Autorisations "
|
||||||
|
@ -35,6 +35,7 @@ common:
|
|||||||
signup: "新規登録"
|
signup: "新規登録"
|
||||||
signout: "ログアウト"
|
signout: "ログアウト"
|
||||||
reload-to-apply-the-setting: "この設定を反映するにはページをリロードする必要があります。今すぐリロードしますか?"
|
reload-to-apply-the-setting: "この設定を反映するにはページをリロードする必要があります。今すぐリロードしますか?"
|
||||||
|
fetching-as-ap-object: "連合に照会中"
|
||||||
|
|
||||||
got-it: "わかった"
|
got-it: "わかった"
|
||||||
customization-tips:
|
customization-tips:
|
||||||
@ -527,8 +528,12 @@ common/views/components/user-menu.vue:
|
|||||||
mention: "メンション"
|
mention: "メンション"
|
||||||
mute: "ミュート"
|
mute: "ミュート"
|
||||||
unmute: "ミュート解除"
|
unmute: "ミュート解除"
|
||||||
|
mute-confirm: "このユーザーをミュートしますか?"
|
||||||
|
unmute-confirm: "このユーザーをミュート解除しますか?"
|
||||||
block: "ブロック"
|
block: "ブロック"
|
||||||
unblock: "ブロック解除"
|
unblock: "ブロック解除"
|
||||||
|
block-confirm: "このユーザーをブロックしますか?"
|
||||||
|
unblock-confirm: "このユーザーをブロック解除しますか?"
|
||||||
push-to-list: "リストに追加"
|
push-to-list: "リストに追加"
|
||||||
select-list: "リストを選択してください"
|
select-list: "リストを選択してください"
|
||||||
report-abuse: "スパムを報告"
|
report-abuse: "スパムを報告"
|
||||||
@ -536,8 +541,12 @@ common/views/components/user-menu.vue:
|
|||||||
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
|
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
|
||||||
silence: "サイレンス"
|
silence: "サイレンス"
|
||||||
unsilence: "サイレンス解除"
|
unsilence: "サイレンス解除"
|
||||||
|
silence-confirm: "このユーザーをサイレンスしますか?"
|
||||||
|
unsilence-confirm: "このユーザーをサイレンス解除しますか?"
|
||||||
suspend: "凍結"
|
suspend: "凍結"
|
||||||
unsuspend: "凍結解除"
|
unsuspend: "凍結解除"
|
||||||
|
suspend-confirm: "このユーザーを凍結しますか?"
|
||||||
|
unsuspend-confirm: "このユーザーを凍結解除しますか?"
|
||||||
|
|
||||||
common/views/components/poll.vue:
|
common/views/components/poll.vue:
|
||||||
vote-to: "「{}」に投票する"
|
vote-to: "「{}」に投票する"
|
||||||
@ -739,6 +748,10 @@ common/views/components/user-list-editor.vue:
|
|||||||
delete-are-you-sure: "リスト「$1」を削除しますか?"
|
delete-are-you-sure: "リスト「$1」を削除しますか?"
|
||||||
deleted: "削除しました"
|
deleted: "削除しました"
|
||||||
|
|
||||||
|
common/views/components/user-lists.vue:
|
||||||
|
create-list: "リストを作成"
|
||||||
|
list-name: "リスト名"
|
||||||
|
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "確認中"
|
fetching: "確認中"
|
||||||
no-broadcasts: "お知らせはありません"
|
no-broadcasts: "お知らせはありません"
|
||||||
@ -1145,8 +1158,6 @@ desktop/views/components/received-follow-requests-window.vue:
|
|||||||
|
|
||||||
desktop/views/components/user-lists-window.vue:
|
desktop/views/components/user-lists-window.vue:
|
||||||
title: "リスト"
|
title: "リスト"
|
||||||
create-list: "リストを作成"
|
|
||||||
list-name: "リスト名"
|
|
||||||
|
|
||||||
desktop/views/components/user-preview.vue:
|
desktop/views/components/user-preview.vue:
|
||||||
notes: "投稿"
|
notes: "投稿"
|
||||||
@ -1336,7 +1347,9 @@ admin/views/users.vue:
|
|||||||
unsuspend-confirm: "凍結を解除しますか?"
|
unsuspend-confirm: "凍結を解除しますか?"
|
||||||
unsuspended: "凍結を解除しました"
|
unsuspended: "凍結を解除しました"
|
||||||
make-silence: "サイレンス"
|
make-silence: "サイレンス"
|
||||||
|
silence-confirm: "サイレンスしますか?"
|
||||||
unmake-silence: "サイレンスの解除"
|
unmake-silence: "サイレンスの解除"
|
||||||
|
unsilence-confirm: "サイレンスを解除しますか?"
|
||||||
verify: "公式アカウントにする"
|
verify: "公式アカウントにする"
|
||||||
verify-confirm: "公式アカウントにしますか?"
|
verify-confirm: "公式アカウントにしますか?"
|
||||||
verified: "公式アカウントにしました"
|
verified: "公式アカウントにしました"
|
||||||
@ -1573,12 +1586,11 @@ mobile/views/components/drive.vue:
|
|||||||
file-count: "ファイル"
|
file-count: "ファイル"
|
||||||
nothing-in-drive: "ドライブには何もありません"
|
nothing-in-drive: "ドライブには何もありません"
|
||||||
folder-is-empty: "このフォルダは空です"
|
folder-is-empty: "このフォルダは空です"
|
||||||
prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
|
|
||||||
deletion-alert: "ごめんなさい!フォルダの削除は未実装です...。"
|
|
||||||
folder-name: "フォルダー名"
|
folder-name: "フォルダー名"
|
||||||
here-is-root: "現在いる場所はルートで、フォルダではありません。"
|
here-is-root: "現在いる場所はルートで、フォルダではありません。"
|
||||||
url-prompt: "アップロードしたいファイルのURL"
|
url-prompt: "アップロードしたいファイルのURL"
|
||||||
uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
|
uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
|
||||||
|
folder-name-cannot-empty: "フォルダ名を空白にすることはできません。"
|
||||||
|
|
||||||
mobile/views/components/drive-file-chooser.vue:
|
mobile/views/components/drive-file-chooser.vue:
|
||||||
select-file: "ファイルを選択"
|
select-file: "ファイルを選択"
|
||||||
@ -1668,9 +1680,17 @@ mobile/views/components/ui.nav.vue:
|
|||||||
admin: "管理"
|
admin: "管理"
|
||||||
about: "Misskeyについて"
|
about: "Misskeyについて"
|
||||||
|
|
||||||
|
mobile/views/pages/drive.vue:
|
||||||
|
contextmenu:
|
||||||
|
upload: "ファイルをアップロード"
|
||||||
|
url-upload: "ファイルをURLでアップロード"
|
||||||
|
create-folder: "フォルダーを作成"
|
||||||
|
rename-folder: "フォルダー名を変更"
|
||||||
|
move-folder: "このフォルダを移動"
|
||||||
|
delete-folder: "このフォルダを削除"
|
||||||
|
|
||||||
mobile/views/pages/user-lists.vue:
|
mobile/views/pages/user-lists.vue:
|
||||||
title: "リスト"
|
title: "リスト"
|
||||||
enter-list-name: "リスト名を入力してください"
|
|
||||||
|
|
||||||
mobile/views/pages/signup.vue:
|
mobile/views/pages/signup.vue:
|
||||||
lets-start: "📦 始めましょう"
|
lets-start: "📦 始めましょう"
|
||||||
|
@ -60,6 +60,8 @@ common:
|
|||||||
following: "フォローしとる"
|
following: "フォローしとる"
|
||||||
followers: "フォロワー"
|
followers: "フォロワー"
|
||||||
favorites: "お気に入り"
|
favorites: "お気に入り"
|
||||||
|
permissions:
|
||||||
|
"write:votes": "投票するで"
|
||||||
weekday-short:
|
weekday-short:
|
||||||
sunday: "日"
|
sunday: "日"
|
||||||
monday: "月"
|
monday: "月"
|
||||||
@ -1126,7 +1128,7 @@ mobile/views/components/post-form.vue:
|
|||||||
quote-placeholder: "この投稿を持ってくる(オプション)"
|
quote-placeholder: "この投稿を持ってくる(オプション)"
|
||||||
reply-placeholder: "この投稿への返信..."
|
reply-placeholder: "この投稿への返信..."
|
||||||
cw-placeholder: "内容への注釈 (オプション)"
|
cw-placeholder: "内容への注釈 (オプション)"
|
||||||
location-alert: "あんさんのつことる端末は位置情報に対応しとらんみたいやわ、知らんけど。"
|
geolocation-alert: "あんさんのつことる端末は位置情報に対応しとらんみたいやわ、知らんけど。"
|
||||||
error: "エラー"
|
error: "エラー"
|
||||||
username-prompt: "ユーザー名を入力してや"
|
username-prompt: "ユーザー名を入力してや"
|
||||||
mobile/views/components/sub-note-content.vue:
|
mobile/views/components/sub-note-content.vue:
|
||||||
@ -1272,10 +1274,7 @@ dev/views/new-app.vue:
|
|||||||
create-app: "アプリケーション作る"
|
create-app: "アプリケーション作る"
|
||||||
app-name: "アプリケーションの名前"
|
app-name: "アプリケーションの名前"
|
||||||
app-name-desc: "あんたのアプリの名前。"
|
app-name-desc: "あんたのアプリの名前。"
|
||||||
app-name-ex: "ex) 関西ミスキー保安協会"
|
|
||||||
app-overview: "このアプリどんなん?"
|
app-overview: "このアプリどんなん?"
|
||||||
app-desc: "あんたのアプリどんなんか教えて"
|
|
||||||
app-desc-ex: "ex) 関西人なら誰でも口ずさめるこのCMがついにMisskeyへ。"
|
|
||||||
callback-url: "コールバックURL (無くてもええで)"
|
callback-url: "コールバックURL (無くてもええで)"
|
||||||
callback-url-desc: "ユーザーが認証フォームで認証した後どこに連れてくかを設定できるで"
|
callback-url-desc: "ユーザーが認証フォームで認証した後どこに連れてくかを設定できるで"
|
||||||
authority: "権限"
|
authority: "権限"
|
||||||
|
@ -70,10 +70,11 @@ common:
|
|||||||
followers: "팔로워"
|
followers: "팔로워"
|
||||||
favorites: "즐겨찾기"
|
favorites: "즐겨찾기"
|
||||||
permissions:
|
permissions:
|
||||||
'read:account': "계정 정보 보기"
|
"read:account": "계정 정보 보기"
|
||||||
'write:account': "계정 정보 변경"
|
"write:account": "계정 정보 변경"
|
||||||
'read:drive': "드라이브 보기"
|
"read:drive": "드라이브 보기"
|
||||||
'write:drive': "드라이브 수정"
|
"write:drive": "드라이브 수정"
|
||||||
|
"write:votes": "투표하기"
|
||||||
empty-timeline-info:
|
empty-timeline-info:
|
||||||
follow-users-to-make-your-timeline: "사용자를 팔로우하면 글이 타임라인에 표시됩니다."
|
follow-users-to-make-your-timeline: "사용자를 팔로우하면 글이 타임라인에 표시됩니다."
|
||||||
explore: "사용자 탐색"
|
explore: "사용자 탐색"
|
||||||
@ -281,6 +282,7 @@ common:
|
|||||||
nav: "내비게이션"
|
nav: "내비게이션"
|
||||||
tips: "팁"
|
tips: "팁"
|
||||||
hashtags: "해시태그"
|
hashtags: "해시태그"
|
||||||
|
queue: "큐"
|
||||||
dev: "앱을 만드는 데 실패했습니다. 다시 시도하시기 바랍니다."
|
dev: "앱을 만드는 데 실패했습니다. 다시 시도하시기 바랍니다."
|
||||||
ai-chan-kawaii: "아이쨩 귀여워"
|
ai-chan-kawaii: "아이쨩 귀여워"
|
||||||
you: "당신"
|
you: "당신"
|
||||||
@ -1462,7 +1464,7 @@ mobile/views/components/post-form.vue:
|
|||||||
quote-placeholder: "이 글을 인용... (선택적)"
|
quote-placeholder: "이 글을 인용... (선택적)"
|
||||||
reply-placeholder: "이 글에 답글..."
|
reply-placeholder: "이 글에 답글..."
|
||||||
cw-placeholder: "내용 주석 (선택적)"
|
cw-placeholder: "내용 주석 (선택적)"
|
||||||
location-alert: "사용하시는 장치가 위치정보 기능에 대응하지 않습니다"
|
geolocation-alert: "사용하시는 장치가 위치정보 기능에 대응하지 않습니다"
|
||||||
error: "오류"
|
error: "오류"
|
||||||
username-prompt: "사용자명을 입력하여 주십시오"
|
username-prompt: "사용자명을 입력하여 주십시오"
|
||||||
mobile/views/components/sub-note-content.vue:
|
mobile/views/components/sub-note-content.vue:
|
||||||
@ -1615,10 +1617,7 @@ dev/views/new-app.vue:
|
|||||||
create-app: "어플리케이션 생성"
|
create-app: "어플리케이션 생성"
|
||||||
app-name: "어플리케이션 이름"
|
app-name: "어플리케이션 이름"
|
||||||
app-name-desc: "앱의 이름."
|
app-name-desc: "앱의 이름."
|
||||||
app-name-ex: "ex) Misskey for iOS"
|
|
||||||
app-overview: "앱 개요"
|
app-overview: "앱 개요"
|
||||||
app-desc: "어플리케이션에 대한 간단한 설명."
|
|
||||||
app-desc-ex: "ex) Misskey iOS 클라이언트."
|
|
||||||
callback-url: "콜백 URL (옵션)"
|
callback-url: "콜백 URL (옵션)"
|
||||||
callback-url-desc: "사용자가 인증 폼에서 인증한 뒤 리다이렉트할 URL을 설정합니다."
|
callback-url-desc: "사용자가 인증 폼에서 인증한 뒤 리다이렉트할 URL을 설정합니다."
|
||||||
authority: "권한"
|
authority: "권한"
|
||||||
|
@ -23,6 +23,8 @@ common:
|
|||||||
timeline: "Tijdlijn"
|
timeline: "Tijdlijn"
|
||||||
followers: "Volgers"
|
followers: "Volgers"
|
||||||
favorites: "Deze notitie toevoegen aan favorieten"
|
favorites: "Deze notitie toevoegen aan favorieten"
|
||||||
|
permissions:
|
||||||
|
"write:votes": "Stemmen"
|
||||||
weekday-short:
|
weekday-short:
|
||||||
sunday: "Z"
|
sunday: "Z"
|
||||||
monday: "M"
|
monday: "M"
|
||||||
|
@ -37,6 +37,8 @@ common:
|
|||||||
home: "Hjem"
|
home: "Hjem"
|
||||||
followers: "Følgere"
|
followers: "Følgere"
|
||||||
favorites: "Merket som favoritt"
|
favorites: "Merket som favoritt"
|
||||||
|
permissions:
|
||||||
|
"write:votes": "Stem"
|
||||||
weekday-short:
|
weekday-short:
|
||||||
sunday: "S"
|
sunday: "S"
|
||||||
monday: "M"
|
monday: "M"
|
||||||
|
@ -63,7 +63,8 @@ common:
|
|||||||
followers: "Śledzący"
|
followers: "Śledzący"
|
||||||
favorites: "Moje ulubione"
|
favorites: "Moje ulubione"
|
||||||
permissions:
|
permissions:
|
||||||
'read:drive': "Wyświetl dysk"
|
"read:drive": "Wyświetl dysk"
|
||||||
|
"write:votes": "Zagłosuj"
|
||||||
empty-timeline-info:
|
empty-timeline-info:
|
||||||
explore: "Poznaj"
|
explore: "Poznaj"
|
||||||
weekday-short:
|
weekday-short:
|
||||||
@ -1082,7 +1083,7 @@ mobile/views/components/post-form.vue:
|
|||||||
quote-placeholder: "Zacytuj ten wpis… (nieobowiązkowe)"
|
quote-placeholder: "Zacytuj ten wpis… (nieobowiązkowe)"
|
||||||
reply-placeholder: "Odpowiedź na ten wpis…"
|
reply-placeholder: "Odpowiedź na ten wpis…"
|
||||||
cw-placeholder: "Treść ostrzeżenia (opcjonalnie)"
|
cw-placeholder: "Treść ostrzeżenia (opcjonalnie)"
|
||||||
location-alert: "Twoje urządzenie nie pozwala na przekazywanie informacji o lokalizacji"
|
geolocation-alert: "Twoje urządzenie nie obsługuje geolokalizacji."
|
||||||
error: "Błąd"
|
error: "Błąd"
|
||||||
username-prompt: "Wprowadź nazwę użytkownika"
|
username-prompt: "Wprowadź nazwę użytkownika"
|
||||||
mobile/views/components/sub-note-content.vue:
|
mobile/views/components/sub-note-content.vue:
|
||||||
|
@ -70,10 +70,26 @@ common:
|
|||||||
followers: "关注者"
|
followers: "关注者"
|
||||||
favorites: "最爱"
|
favorites: "最爱"
|
||||||
permissions:
|
permissions:
|
||||||
'read:account': "查看账户信息"
|
"read:account": "查看账户信息"
|
||||||
'write:account': "更改我的帐户信息"
|
"write:account": "更改我的帐户信息"
|
||||||
'read:drive': "查看网盘"
|
"read:blocks": "查看黑名单"
|
||||||
'write:drive': "管理网盘文件"
|
"write:blocks": "编辑黑名单"
|
||||||
|
"read:drive": "查看网盘"
|
||||||
|
"write:drive": "管理网盘文件"
|
||||||
|
"read:favorites": "查看收藏夹"
|
||||||
|
"write:favorites": "编辑收藏夹"
|
||||||
|
"read:following": "查看关注信息"
|
||||||
|
"write:following": "关注/取消关注"
|
||||||
|
"read:messaging": "查看对话"
|
||||||
|
"write:messaging": "对话操作"
|
||||||
|
"read:mutes": "查看屏蔽列表"
|
||||||
|
"write:mutes": "编辑屏蔽列表"
|
||||||
|
"write:notes": "创建或删除帖子"
|
||||||
|
"read:notifications": "查看通知"
|
||||||
|
"write:notifications": "管理通知"
|
||||||
|
"read:reactions": "查看回应"
|
||||||
|
"write:reactions": "回应操作"
|
||||||
|
"write:votes": "投票"
|
||||||
empty-timeline-info:
|
empty-timeline-info:
|
||||||
follow-users-to-make-your-timeline: "关注其他用户时,帖子将显示在时间线中。"
|
follow-users-to-make-your-timeline: "关注其他用户时,帖子将显示在时间线中。"
|
||||||
explore: "查找用户"
|
explore: "查找用户"
|
||||||
@ -129,7 +145,7 @@ common:
|
|||||||
apps: "应用程序"
|
apps: "应用程序"
|
||||||
tags: "标签"
|
tags: "标签"
|
||||||
mute-and-block: "屏蔽/拉黑"
|
mute-and-block: "屏蔽/拉黑"
|
||||||
blocking: "屏蔽"
|
blocking: "拉黑"
|
||||||
security: "安全性"
|
security: "安全性"
|
||||||
signin: "登录历史"
|
signin: "登录历史"
|
||||||
password: "密码"
|
password: "密码"
|
||||||
@ -281,6 +297,7 @@ common:
|
|||||||
nav: "导航"
|
nav: "导航"
|
||||||
tips: "提示"
|
tips: "提示"
|
||||||
hashtags: "标签"
|
hashtags: "标签"
|
||||||
|
queue: "队列"
|
||||||
dev: "构建应用程序失败,请再试一次。"
|
dev: "构建应用程序失败,请再试一次。"
|
||||||
ai-chan-kawaii: "小蓝真可爱"
|
ai-chan-kawaii: "小蓝真可爱"
|
||||||
you: "您"
|
you: "您"
|
||||||
@ -966,6 +983,7 @@ common/views/components/password-settings.vue:
|
|||||||
changed: "密码已更改"
|
changed: "密码已更改"
|
||||||
failed: "更改密码失败"
|
failed: "更改密码失败"
|
||||||
common/views/components/post-form-attaches.vue:
|
common/views/components/post-form-attaches.vue:
|
||||||
|
attach-cancel: "删除附件"
|
||||||
mark-as-sensitive: "标记为“敏感”"
|
mark-as-sensitive: "标记为“敏感”"
|
||||||
unmark-as-sensitive: "取消标记为“敏感”"
|
unmark-as-sensitive: "取消标记为“敏感”"
|
||||||
desktop/views/components/sub-note-content.vue:
|
desktop/views/components/sub-note-content.vue:
|
||||||
@ -1385,6 +1403,7 @@ desktop/views/widgets/polls.vue:
|
|||||||
desktop/views/widgets/post-form.vue:
|
desktop/views/widgets/post-form.vue:
|
||||||
title: "帖子"
|
title: "帖子"
|
||||||
note: "帖子"
|
note: "帖子"
|
||||||
|
something-happened: "由于某种原因无法发帖。"
|
||||||
desktop/views/widgets/profile.vue:
|
desktop/views/widgets/profile.vue:
|
||||||
update-banner: "点击来剪辑背景"
|
update-banner: "点击来剪辑背景"
|
||||||
update-avatar: "点击来剪辑头像"
|
update-avatar: "点击来剪辑头像"
|
||||||
@ -1461,7 +1480,7 @@ mobile/views/components/post-form.vue:
|
|||||||
quote-placeholder: "引用这个帖子t... (可选)"
|
quote-placeholder: "引用这个帖子t... (可选)"
|
||||||
reply-placeholder: "回复这个帖子"
|
reply-placeholder: "回复这个帖子"
|
||||||
cw-placeholder: "评论帖子(可选)"
|
cw-placeholder: "评论帖子(可选)"
|
||||||
location-alert: "您的设备不提供定位服务"
|
geolocation-alert: "您的设备不提供位置服务"
|
||||||
error: "错误"
|
error: "错误"
|
||||||
username-prompt: "请输入用户名"
|
username-prompt: "请输入用户名"
|
||||||
mobile/views/components/sub-note-content.vue:
|
mobile/views/components/sub-note-content.vue:
|
||||||
@ -1611,14 +1630,17 @@ dev/views/apps.vue:
|
|||||||
create-app: "创建应用"
|
create-app: "创建应用"
|
||||||
app-missing: "没有应用"
|
app-missing: "没有应用"
|
||||||
dev/views/new-app.vue:
|
dev/views/new-app.vue:
|
||||||
|
new-app: "新应用"
|
||||||
|
new-app-info: "可以从 API 中创建应用。 (app/create)"
|
||||||
create-app: "正在创建应用"
|
create-app: "正在创建应用"
|
||||||
app-name: "应用名称"
|
app-name: "应用名称"
|
||||||
|
app-name-placeholder: "ex) iOS版Misskey"
|
||||||
app-name-desc: "您应用的名称"
|
app-name-desc: "您应用的名称"
|
||||||
app-name-ex: "ex) iOS版本的Misskey"
|
|
||||||
app-overview: "应用摘要"
|
app-overview: "应用摘要"
|
||||||
app-desc: "您的应用的简要说明或介绍。"
|
app-overview-placeholder: " ex) iOS版Misskey客户端."
|
||||||
app-desc-ex: "ex) iOS版Misskey客户端."
|
app-overview-desc: "您的应用的简要说明或介绍。"
|
||||||
callback-url: "回应URL (optional)"
|
callback-url: "回应URL (optional)"
|
||||||
|
callback-url-placeholder: "ex) https://your.app.example.com/callback.php"
|
||||||
callback-url-desc: "通过身份验证表单对用户进行身份验证后重定向到的URL。"
|
callback-url-desc: "通过身份验证表单对用户进行身份验证后重定向到的URL。"
|
||||||
authority: "权限"
|
authority: "权限"
|
||||||
authority-desc: "只能通过API访问此处请求的功能。"
|
authority-desc: "只能通过API访问此处请求的功能。"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "11.1.4",
|
"version": "11.2.0",
|
||||||
"codename": "daybreak",
|
"codename": "daybreak",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -232,6 +232,8 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async silenceUser() {
|
async silenceUser() {
|
||||||
|
if (!await this.getConfirmed(this.$t('silence-confirm'))) return;
|
||||||
|
|
||||||
const process = async () => {
|
const process = async () => {
|
||||||
await this.$root.api('admin/silence-user', { userId: this.user.id });
|
await this.$root.api('admin/silence-user', { userId: this.user.id });
|
||||||
this.$root.dialog({
|
this.$root.dialog({
|
||||||
@ -251,6 +253,8 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async unsilenceUser() {
|
async unsilenceUser() {
|
||||||
|
if (!await this.getConfirmed(this.$t('unsilence-confirm'))) return;
|
||||||
|
|
||||||
const process = async () => {
|
const process = async () => {
|
||||||
await this.$root.api('admin/unsilence-user', { userId: this.user.id });
|
await this.$root.api('admin/unsilence-user', { userId: this.user.id });
|
||||||
this.$root.dialog({
|
this.$root.dialog({
|
||||||
|
@ -137,7 +137,6 @@ export default prop => ({
|
|||||||
Vue.set(this.$_ns_target, 'deletedAt', body.deletedAt);
|
Vue.set(this.$_ns_target, 'deletedAt', body.deletedAt);
|
||||||
Vue.set(this.$_ns_target, 'renote', null);
|
Vue.set(this.$_ns_target, 'renote', null);
|
||||||
this.$_ns_target.text = null;
|
this.$_ns_target.text = null;
|
||||||
this.$_ns_target.tags = [];
|
|
||||||
this.$_ns_target.fileIds = [];
|
this.$_ns_target.fileIds = [];
|
||||||
this.$_ns_target.poll = null;
|
this.$_ns_target.poll = null;
|
||||||
this.$_ns_target.geo = null;
|
this.$_ns_target.geo = null;
|
||||||
|
64
src/client/app/common/scripts/search.ts
Normal file
64
src/client/app/common/scripts/search.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { faHistory } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
export async function search(v: any, q: string) {
|
||||||
|
q = q.trim();
|
||||||
|
|
||||||
|
if (q.startsWith('@')) {
|
||||||
|
v.$router.push(`/${q}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q.startsWith('#')) {
|
||||||
|
v.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// like 2018/03/12
|
||||||
|
if (/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}/.test(q.replace(/-/g, '/'))) {
|
||||||
|
const date = new Date(q.replace(/-/g, '/'));
|
||||||
|
|
||||||
|
// 日付しか指定されてない場合、例えば 2018/03/12 ならユーザーは
|
||||||
|
// 2018/03/12 のコンテンツを「含む」結果になることを期待するはずなので
|
||||||
|
// 23時間59分進める(そのままだと 2018/03/12 00:00:00 「まで」の
|
||||||
|
// 結果になってしまい、2018/03/12 のコンテンツは含まれない)
|
||||||
|
if (q.replace(/-/g, '/').match(/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}$/)) {
|
||||||
|
date.setHours(23, 59, 59, 999);
|
||||||
|
}
|
||||||
|
|
||||||
|
v.$root.$emit('warp', date);
|
||||||
|
v.$root.dialog({
|
||||||
|
icon: faHistory,
|
||||||
|
splash: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q.startsWith('https://')) {
|
||||||
|
const dialog = v.$root.dialog({
|
||||||
|
type: 'waiting',
|
||||||
|
text: v.$t('@.fetching-as-ap-object'),
|
||||||
|
showOkButton: false,
|
||||||
|
showCancelButton: false,
|
||||||
|
cancelableByBgClick: false
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await v.$root.api('ap/show', {
|
||||||
|
uri: q
|
||||||
|
});
|
||||||
|
dialog.close();
|
||||||
|
if (res.type == 'User') {
|
||||||
|
v.$router.push(`/@${res.object.username}@${res.object.host}`);
|
||||||
|
} else if (res.type == 'Note') {
|
||||||
|
v.$router.push(`/notes/${res.object.id}`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
dialog.close();
|
||||||
|
// TODO: Show error
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
v.$router.push(`/search?q=${encodeURIComponent(q)}`);
|
||||||
|
}
|
@ -6,7 +6,17 @@
|
|||||||
<mk-signin/>
|
<mk-signin/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="icon" v-if="!input && !select && !user" :class="type"><fa :icon="icon"/></div>
|
<div class="icon" v-if="icon">
|
||||||
|
<fa :icon="icon"/>
|
||||||
|
</div>
|
||||||
|
<div class="icon" v-else-if="!input && !select && !user" :class="type">
|
||||||
|
<fa icon="check" v-if="type === 'success'"/>
|
||||||
|
<fa :icon="faTimesCircle" v-if="type === 'error'"/>
|
||||||
|
<fa icon="exclamation-triangle" v-if="type === 'warning'"/>
|
||||||
|
<fa icon="info-circle" v-if="type === 'info'"/>
|
||||||
|
<fa :icon="faQuestionCircle" v-if="type === 'question'"/>
|
||||||
|
<fa icon="spinner" pulse v-if="type === 'waiting'"/>
|
||||||
|
</div>
|
||||||
<header v-if="title" v-html="title"></header>
|
<header v-if="title" v-html="title"></header>
|
||||||
<div class="body" v-if="text" v-html="text"></div>
|
<div class="body" v-if="text" v-html="text"></div>
|
||||||
<ui-input v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></ui-input>
|
<ui-input v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></ui-input>
|
||||||
@ -14,8 +24,8 @@
|
|||||||
<ui-select v-if="select" v-model="selectedValue" autofocus>
|
<ui-select v-if="select" v-model="selectedValue" autofocus>
|
||||||
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
|
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
|
||||||
</ui-select>
|
</ui-select>
|
||||||
<ui-horizon-group no-grow class="buttons fit-bottom" v-if="!splash">
|
<ui-horizon-group no-grow class="buttons fit-bottom" v-if="!splash && (showOkButton || showCancelButton)">
|
||||||
<ui-button @click="ok" primary :autofocus="!input && !select && !user">{{ (showCancelButton || input || select || user) ? $t('@.ok') : $t('@.got-it') }}</ui-button>
|
<ui-button @click="ok" v-if="showOkButton" primary :autofocus="!input && !select && !user">{{ (showCancelButton || input || select || user) ? $t('@.ok') : $t('@.got-it') }}</ui-button>
|
||||||
<ui-button @click="cancel" v-if="showCancelButton || input || select || user">{{ $t('@.cancel') }}</ui-button>
|
<ui-button @click="cancel" v-if="showCancelButton || input || select || user">{{ $t('@.cancel') }}</ui-button>
|
||||||
</ui-horizon-group>
|
</ui-horizon-group>
|
||||||
</template>
|
</template>
|
||||||
@ -55,10 +65,21 @@ export default Vue.extend({
|
|||||||
user: {
|
user: {
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
|
icon: {
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
showOkButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
showCancelButton: {
|
showCancelButton: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
cancelableByBgClick: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
splash: {
|
splash: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
@ -69,22 +90,11 @@ export default Vue.extend({
|
|||||||
return {
|
return {
|
||||||
inputValue: this.input && this.input.default ? this.input.default : null,
|
inputValue: this.input && this.input.default ? this.input.default : null,
|
||||||
userInputValue: null,
|
userInputValue: null,
|
||||||
selectedValue: null
|
selectedValue: null,
|
||||||
|
faTimesCircle, faQuestionCircle
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
icon(): any {
|
|
||||||
switch (this.type) {
|
|
||||||
case 'success': return 'check';
|
|
||||||
case 'error': return faTimesCircle;
|
|
||||||
case 'warning': return 'exclamation-triangle';
|
|
||||||
case 'info': return 'info-circle';
|
|
||||||
case 'question': return faQuestionCircle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
(this.$refs.bg as any).style.pointerEvents = 'auto';
|
(this.$refs.bg as any).style.pointerEvents = 'auto';
|
||||||
@ -113,6 +123,8 @@ export default Vue.extend({
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async ok() {
|
async ok() {
|
||||||
|
if (!this.showOkButton) return;
|
||||||
|
|
||||||
if (this.user) {
|
if (this.user) {
|
||||||
const user = await this.$root.api('users/show', parseAcct(this.userInputValue));
|
const user = await this.$root.api('users/show', parseAcct(this.userInputValue));
|
||||||
if (user) {
|
if (user) {
|
||||||
@ -156,7 +168,9 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onBgClick() {
|
onBgClick() {
|
||||||
|
if (this.cancelableByBgClick) {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onInputKeydown(e) {
|
onInputKeydown(e) {
|
||||||
@ -183,9 +197,6 @@ export default Vue.extend({
|
|||||||
height 100%
|
height 100%
|
||||||
|
|
||||||
&.splash
|
&.splash
|
||||||
&, *
|
|
||||||
pointer-events none !important
|
|
||||||
|
|
||||||
> .main
|
> .main
|
||||||
min-width 0
|
min-width 0
|
||||||
width initial
|
width initial
|
||||||
@ -243,7 +254,7 @@ export default Vue.extend({
|
|||||||
margin-top 8px
|
margin-top 8px
|
||||||
|
|
||||||
> .body
|
> .body
|
||||||
margin 16px 0
|
margin 16px 0 0 0
|
||||||
|
|
||||||
> .buttons
|
> .buttons
|
||||||
margin-top 16px
|
margin-top 16px
|
||||||
|
@ -121,7 +121,7 @@ export default Vue.extend({
|
|||||||
if (this.file.properties.avgColor) {
|
if (this.file.properties.avgColor) {
|
||||||
anime({
|
anime({
|
||||||
targets: this.$refs.thumbnail,
|
targets: this.$refs.thumbnail,
|
||||||
backgroundColor: this.file.properties.avgColor.replace('255)', '0)'),
|
backgroundColor: 'transparent', // TODO fade
|
||||||
duration: 100,
|
duration: 100,
|
||||||
easing: 'linear'
|
easing: 'linear'
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<div class="body">
|
<div class="body">
|
||||||
<p class="init" v-if="init"><fa icon="spinner .spin"/>{{ $t('@.loading') }}</p>
|
<p class="init" v-if="init"><fa icon="spinner .spin"/>{{ $t('@.loading') }}</p>
|
||||||
<p class="empty" v-if="!init && messages.length == 0"><fa icon="info-circle"/>{{ $t('empty') }}</p>
|
<p class="empty" v-if="!init && messages.length == 0"><fa icon="info-circle"/>{{ $t('empty') }}</p>
|
||||||
<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa icon="flag"/>{{ $t('no-history') }}</p>
|
<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa :icon="faFlag"/>{{ $t('no-history') }}</p>
|
||||||
<button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
|
<button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
|
||||||
<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
|
<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
|
||||||
</button>
|
</button>
|
||||||
@ -35,6 +35,7 @@ import XMessage from './messaging-room.message.vue';
|
|||||||
import XForm from './messaging-room.form.vue';
|
import XForm from './messaging-room.form.vue';
|
||||||
import { url } from '../../../config';
|
import { url } from '../../../config';
|
||||||
import { faArrowCircleDown } from '@fortawesome/free-solid-svg-icons';
|
import { faArrowCircleDown } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { faFlag } from '@fortawesome/free-regular-svg-icons';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('common/views/components/messaging-room.vue'),
|
i18n: i18n('common/views/components/messaging-room.vue'),
|
||||||
@ -54,7 +55,7 @@ export default Vue.extend({
|
|||||||
connection: null,
|
connection: null,
|
||||||
showIndicator: false,
|
showIndicator: false,
|
||||||
timer: null,
|
timer: null,
|
||||||
faArrowCircleDown
|
faArrowCircleDown, faFlag
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ export default Vue.extend({
|
|||||||
|
|
||||||
props: {
|
props: {
|
||||||
files: {
|
files: {
|
||||||
type: Object,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
detachMediaFn: {
|
detachMediaFn: {
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<section>
|
<section>
|
||||||
<header><fa icon="terminal"/> {{ $t('console.title') }}</header>
|
<header><fa icon="terminal"/> {{ $t('console.title') }}</header>
|
||||||
<ui-input v-model="endpoint">
|
<ui-input v-model="endpoint" :datalist="endpoints">
|
||||||
<span>{{ $t('console.endpoint') }}</span>
|
<span>{{ $t('console.endpoint') }}</span>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-textarea v-model="body">
|
<ui-textarea v-model="body">
|
||||||
@ -39,15 +39,23 @@ import * as JSON5 from 'json5';
|
|||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('common/views/components/api-settings.vue'),
|
i18n: i18n('common/views/components/api-settings.vue'),
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
endpoint: '',
|
endpoint: '',
|
||||||
body: '{}',
|
body: '{}',
|
||||||
res: null,
|
res: null,
|
||||||
sending: false
|
sending: false,
|
||||||
|
endpoints: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.$root.api('endpoints').then(endpoints => {
|
||||||
|
this.endpoints = endpoints;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
regenerateToken() {
|
regenerateToken() {
|
||||||
this.$root.dialog({
|
this.$root.dialog({
|
||||||
|
@ -525,15 +525,11 @@ export default Vue.extend({
|
|||||||
this.$chooseDriveFile({
|
this.$chooseDriveFile({
|
||||||
multiple: false
|
multiple: false
|
||||||
}).then(file => {
|
}).then(file => {
|
||||||
this.$root.api('i/update', {
|
this.$store.dispatch('settings/set', { key: 'wallpaper', value: file.url });
|
||||||
wallpaperId: file.id
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
deleteWallpaper() {
|
deleteWallpaper() {
|
||||||
this.$root.api('i/update', {
|
this.$store.dispatch('settings/set', { key: 'wallpaper', value: null });
|
||||||
wallpaperId: null
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
checkForUpdate() {
|
checkForUpdate() {
|
||||||
this.checkingForUpdate = true;
|
this.checkingForUpdate = true;
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
@focus="focused = true"
|
@focus="focused = true"
|
||||||
@blur="focused = false"
|
@blur="focused = false"
|
||||||
@keydown="$emit('keydown', $event)"
|
@keydown="$emit('keydown', $event)"
|
||||||
|
:list="id"
|
||||||
>
|
>
|
||||||
<input v-else ref="input"
|
<input v-else ref="input"
|
||||||
:type="type"
|
:type="type"
|
||||||
@ -37,7 +38,11 @@
|
|||||||
@focus="focused = true"
|
@focus="focused = true"
|
||||||
@blur="focused = false"
|
@blur="focused = false"
|
||||||
@keydown="$emit('keydown', $event)"
|
@keydown="$emit('keydown', $event)"
|
||||||
|
:list="id"
|
||||||
>
|
>
|
||||||
|
<datalist :id="id" v-if="datalist">
|
||||||
|
<option v-for="data in datalist" :value="data"/>
|
||||||
|
</datalist>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<input ref="input"
|
<input ref="input"
|
||||||
@ -130,6 +135,10 @@ export default Vue.extend({
|
|||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
datalist: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
inline: {
|
inline: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
@ -147,7 +156,8 @@ export default Vue.extend({
|
|||||||
return {
|
return {
|
||||||
v: this.value,
|
v: this.value,
|
||||||
focused: false,
|
focused: false,
|
||||||
passwordStrength: ''
|
passwordStrength: '',
|
||||||
|
id: Math.random().toString()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
95
src/client/app/common/views/components/user-lists.vue
Normal file
95
src/client/app/common/views/components/user-lists.vue
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<div class="xkxvokkjlptzyewouewmceqcxhpgzprp">
|
||||||
|
<button class="ui" @click="add">{{ $t('create-list') }}</button>
|
||||||
|
<a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.name }}</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
i18n: i18n('common/views/components/user-lists.vue'),
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fetching: true,
|
||||||
|
lists: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$root.api('users/lists/list').then(lists => {
|
||||||
|
this.fetching = false;
|
||||||
|
this.lists = lists;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
add() {
|
||||||
|
this.$root.dialog({
|
||||||
|
title: this.$t('list-name'),
|
||||||
|
input: true
|
||||||
|
}).then(async ({ canceled, result: title }) => {
|
||||||
|
if (canceled) return;
|
||||||
|
const list = await this.$root.api('users/lists/create', {
|
||||||
|
title
|
||||||
|
});
|
||||||
|
|
||||||
|
this.lists.push(list)
|
||||||
|
this.$emit('choosen', list);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
choice(list) {
|
||||||
|
this.$emit('choosen', list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.xkxvokkjlptzyewouewmceqcxhpgzprp
|
||||||
|
padding 16px
|
||||||
|
background: var(--bg)
|
||||||
|
|
||||||
|
> button
|
||||||
|
display block
|
||||||
|
margin-bottom 16px
|
||||||
|
color var(--primaryForeground)
|
||||||
|
background var(--primary)
|
||||||
|
width 100%
|
||||||
|
border-radius 38px
|
||||||
|
user-select none
|
||||||
|
cursor pointer
|
||||||
|
padding 0 16px
|
||||||
|
min-width 100px
|
||||||
|
line-height 38px
|
||||||
|
font-size 14px
|
||||||
|
font-weight 700
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background var(--primaryLighten10)
|
||||||
|
|
||||||
|
&:active
|
||||||
|
background var(--primaryDarken10)
|
||||||
|
|
||||||
|
a
|
||||||
|
display block
|
||||||
|
margin 8px 0
|
||||||
|
padding 8px
|
||||||
|
color var(--text)
|
||||||
|
background var(--face)
|
||||||
|
box-shadow 0 2px 16px var(--reversiListItemShadow)
|
||||||
|
border-radius 6px
|
||||||
|
cursor pointer
|
||||||
|
line-height 32px
|
||||||
|
|
||||||
|
*
|
||||||
|
pointer-events none
|
||||||
|
user-select none
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.05)
|
||||||
|
|
||||||
|
&:active
|
||||||
|
box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.1)
|
||||||
|
|
||||||
|
</style>
|
@ -89,8 +89,10 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleMute() {
|
async toggleMute() {
|
||||||
if (this.user.isMuted) {
|
if (this.user.isMuted) {
|
||||||
|
if (!await this.getConfirmed(this.$t('unmute-confirm'))) return;
|
||||||
|
|
||||||
this.$root.api('mute/delete', {
|
this.$root.api('mute/delete', {
|
||||||
userId: this.user.id
|
userId: this.user.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@ -102,6 +104,8 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
if (!await this.getConfirmed(this.$t('mute-confirm'))) return;
|
||||||
|
|
||||||
this.$root.api('mute/create', {
|
this.$root.api('mute/create', {
|
||||||
userId: this.user.id
|
userId: this.user.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@ -115,8 +119,10 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleBlock() {
|
async toggleBlock() {
|
||||||
if (this.user.isBlocking) {
|
if (this.user.isBlocking) {
|
||||||
|
if (!await this.getConfirmed(this.$t('unblock-confirm'))) return;
|
||||||
|
|
||||||
this.$root.api('blocking/delete', {
|
this.$root.api('blocking/delete', {
|
||||||
userId: this.user.id
|
userId: this.user.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@ -128,6 +134,8 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
if (!await this.getConfirmed(this.$t('block-confirm'))) return;
|
||||||
|
|
||||||
this.$root.api('blocking/create', {
|
this.$root.api('blocking/create', {
|
||||||
userId: this.user.id
|
userId: this.user.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@ -164,7 +172,9 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleSilence() {
|
async toggleSilence() {
|
||||||
|
if (!await this.getConfirmed(this.$t(this.user.isSilenced ? 'unsilence-confirm' : 'silence-confirm'))) return;
|
||||||
|
|
||||||
this.$root.api(this.user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', {
|
this.$root.api(this.user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', {
|
||||||
userId: this.user.id
|
userId: this.user.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@ -181,7 +191,9 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleSuspend() {
|
async toggleSuspend() {
|
||||||
|
if (!await this.getConfirmed(this.$t(this.user.isSuspended ? 'unsuspend-confirm' : 'suspend-confirm'))) return;
|
||||||
|
|
||||||
this.$root.api(this.user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', {
|
this.$root.api(this.user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', {
|
||||||
userId: this.user.id
|
userId: this.user.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@ -196,7 +208,18 @@ export default Vue.extend({
|
|||||||
text: e
|
text: e
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
|
async getConfirmed(text: string): Promise<Boolean> {
|
||||||
|
const confirm = await this.$root.dialog({
|
||||||
|
type: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
title: 'confirm',
|
||||||
|
text,
|
||||||
|
});
|
||||||
|
|
||||||
|
return !confirm.canceled;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<fa :icon="['far', 'laugh']"/>
|
<fa :icon="['far', 'laugh']"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<x-post-form-attaches class="files" :files="files" :detachMediaFn="detachMedia"/>
|
<x-post-form-attaches class="files" :files="files" :detach-media-fn="detachMedia"/>
|
||||||
<input ref="file" type="file" multiple="multiple" tabindex="-1" @change="onChangeFile"/>
|
<input ref="file" type="file" multiple="multiple" tabindex="-1" @change="onChangeFile"/>
|
||||||
<mk-uploader ref="uploader" @uploaded="attachMedia"/>
|
<mk-uploader ref="uploader" @uploaded="attachMedia"/>
|
||||||
<footer>
|
<footer>
|
||||||
|
@ -142,7 +142,7 @@ export default Vue.extend({
|
|||||||
if (this.file.properties.avgColor) {
|
if (this.file.properties.avgColor) {
|
||||||
anime({
|
anime({
|
||||||
targets: this.$refs.thumbnail,
|
targets: this.$refs.thumbnail,
|
||||||
backgroundColor: this.file.properties.avgColor.replace('255)', '0)'),
|
backgroundColor: 'transparent', // TODO fade
|
||||||
duration: 100,
|
duration: 100,
|
||||||
easing: 'linear'
|
easing: 'linear'
|
||||||
});
|
});
|
||||||
|
@ -123,7 +123,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
fetchMore() {
|
fetchMore() {
|
||||||
if (!this.more || this.moreFetching) return;
|
if (!this.more || this.moreFetching || this.notes.length === 0) return;
|
||||||
this.moreFetching = true;
|
this.moreFetching = true;
|
||||||
this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
|
this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
|
||||||
this.notes = this.notes.concat(x.notes);
|
this.notes = this.notes.concat(x.notes);
|
||||||
|
@ -87,6 +87,5 @@ export default Vue.extend({
|
|||||||
height 100%
|
height 100%
|
||||||
flex auto
|
flex auto
|
||||||
overflow auto
|
overflow auto
|
||||||
background var(--bg)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -90,9 +90,8 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import MkUserListsWindow from './user-lists-window.vue';
|
import MkUserListsWindow from './user-lists-window.vue';
|
||||||
import MkUserListWindow from './user-list-window.vue';
|
|
||||||
import MkFollowRequestsWindow from './received-follow-requests-window.vue';
|
import MkFollowRequestsWindow from './received-follow-requests-window.vue';
|
||||||
import MkSettingsWindow from './settings-window.vue';
|
// import MkSettingsWindow from './settings-window.vue';
|
||||||
import MkDriveWindow from './drive-window.vue';
|
import MkDriveWindow from './drive-window.vue';
|
||||||
import contains from '../../../common/scripts/contains';
|
import contains from '../../../common/scripts/contains';
|
||||||
import { faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
|
import { faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
|
||||||
@ -143,12 +142,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
list() {
|
list() {
|
||||||
this.close();
|
this.close();
|
||||||
const w = this.$root.new(MkUserListsWindow);
|
this.$root.new(MkUserListsWindow);
|
||||||
w.$once('choosen', list => {
|
|
||||||
this.$root.new(MkUserListWindow, {
|
|
||||||
list
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
followRequests() {
|
followRequests() {
|
||||||
this.close();
|
this.close();
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
|
import { search } from '../../../common/scripts/search';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('desktop/views/components/ui.header.search.vue'),
|
i18n: i18n('desktop/views/components/ui.header.search.vue'),
|
||||||
@ -22,29 +23,11 @@ export default Vue.extend({
|
|||||||
async onSubmit() {
|
async onSubmit() {
|
||||||
if (this.wait) return;
|
if (this.wait) return;
|
||||||
|
|
||||||
const q = this.q.trim();
|
|
||||||
if (q.startsWith('@')) {
|
|
||||||
this.$router.push(`/${q}`);
|
|
||||||
} else if (q.startsWith('#')) {
|
|
||||||
this.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
|
|
||||||
} else if (q.startsWith('https://')) {
|
|
||||||
this.wait = true;
|
this.wait = true;
|
||||||
try {
|
search(this, this.q).finally(() => {
|
||||||
const res = await this.$root.api('ap/show', {
|
|
||||||
uri: q
|
|
||||||
});
|
|
||||||
if (res.type == 'User') {
|
|
||||||
this.$router.push(`/@${res.object.username}@${res.object.host}`);
|
|
||||||
} else if (res.type == 'Note') {
|
|
||||||
this.$router.push(`/notes/${res.object.id}`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
this.wait = false;
|
this.wait = false;
|
||||||
} else {
|
this.q = '';
|
||||||
this.$router.push(`/search?q=${encodeURIComponent(q)}`);
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -148,10 +148,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
list() {
|
list() {
|
||||||
const w = this.$root.new(MkUserListsWindow);
|
this.$root.new(MkUserListsWindow);
|
||||||
w.$once('choosen', list => {
|
|
||||||
this.$router.push(`i/lists/${ list.id }`);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
followRequests() {
|
followRequests() {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mk-ui" v-hotkey.global="keymap">
|
<div class="mk-ui" v-hotkey.global="keymap">
|
||||||
<div class="bg" v-if="$store.getters.isSignedIn && $store.state.i.wallpaperUrl" :style="style"></div>
|
<div class="bg" v-if="$store.getters.isSignedIn && $store.state.settings.wallpaper" :style="style"></div>
|
||||||
<x-header class="header" v-if="navbar == 'top'" v-show="!zenMode" ref="header"/>
|
<x-header class="header" v-if="navbar == 'top'" v-show="!zenMode" ref="header"/>
|
||||||
<x-sidebar class="sidebar" v-if="navbar != 'top'" v-show="!zenMode" ref="sidebar"/>
|
<x-sidebar class="sidebar" v-if="navbar != 'top'" v-show="!zenMode" ref="sidebar"/>
|
||||||
<div class="content" :class="[{ sidebar: navbar != 'top', zen: zenMode }, navbar]">
|
<div class="content" :class="[{ sidebar: navbar != 'top', zen: zenMode }, navbar]">
|
||||||
@ -33,10 +33,9 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
style(): any {
|
style(): any {
|
||||||
if (!this.$store.getters.isSignedIn || this.$store.state.i.wallpaperUrl == null) return {};
|
if (!this.$store.getters.isSignedIn || this.$store.state.settings.wallpaper == null) return {};
|
||||||
return {
|
return {
|
||||||
backgroundColor: this.$store.state.i.wallpaperColor && this.$store.state.i.wallpaperColor.length == 3 ? `rgb(${ this.$store.state.i.wallpaperColor.join(',') })` : null,
|
backgroundImage: `url(${ this.$store.state.settings.wallpaper })`
|
||||||
backgroundImage: `url(${ this.$store.state.i.wallpaperUrl })`
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -96,7 +95,6 @@ export default Vue.extend({
|
|||||||
background-size cover
|
background-size cover
|
||||||
background-position center
|
background-position center
|
||||||
background-attachment fixed
|
background-attachment fixed
|
||||||
opacity 0.3
|
|
||||||
|
|
||||||
> .content.sidebar.left
|
> .content.sidebar.left
|
||||||
padding-left 68px
|
padding-left 68px
|
||||||
|
@ -18,10 +18,12 @@ export default Vue.extend({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
connection: null,
|
connection: null,
|
||||||
|
date: null,
|
||||||
makePromise: cursor => this.$root.api('notes/user-list-timeline', {
|
makePromise: cursor => this.$root.api('notes/user-list-timeline', {
|
||||||
listId: this.list.id,
|
listId: this.list.id,
|
||||||
limit: fetchLimit + 1,
|
limit: fetchLimit + 1,
|
||||||
untilId: cursor ? cursor : undefined,
|
untilId: cursor ? cursor : undefined,
|
||||||
|
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||||
@ -46,6 +48,10 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.init();
|
this.init();
|
||||||
|
this.$root.$on('warp', this.warp);
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
|
this.$root.$off('warp', this.warp);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.connection.dispose();
|
this.connection.dispose();
|
||||||
@ -68,6 +74,10 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
onUserRemoved() {
|
onUserRemoved() {
|
||||||
(this.$refs.timeline as any).reload();
|
(this.$refs.timeline as any).reload();
|
||||||
|
},
|
||||||
|
warp(date) {
|
||||||
|
this.date = date;
|
||||||
|
(this.$refs.timeline as any).reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,85 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
|
<mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
|
||||||
<template #header><fa icon="list"/> {{ $t('title') }}</template>
|
<template #header><fa icon="list"/> {{ $t('title') }}</template>
|
||||||
|
<x-lists :class="$style.content" @choosen="choosen"/>
|
||||||
<div class="xkxvokkjlptzyewouewmceqcxhpgzprp">
|
|
||||||
<button class="ui" @click="add">{{ $t('create-list') }}</button>
|
|
||||||
<a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.name }}</a>
|
|
||||||
</div>
|
|
||||||
</mk-window>
|
</mk-window>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
|
import MkUserListWindow from './user-list-window.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('desktop/views/components/user-lists-window.vue'),
|
i18n: i18n('desktop/views/components/user-lists-window.vue'),
|
||||||
data() {
|
components: {
|
||||||
return {
|
XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default)
|
||||||
fetching: true,
|
|
||||||
lists: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.$root.api('users/lists/list').then(lists => {
|
|
||||||
this.fetching = false;
|
|
||||||
this.lists = lists;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
add() {
|
|
||||||
this.$root.dialog({
|
|
||||||
title: this.$t('list-name'),
|
|
||||||
input: true
|
|
||||||
}).then(async ({ canceled, result: title }) => {
|
|
||||||
if (canceled) return;
|
|
||||||
const list = await this.$root.api('users/lists/create', {
|
|
||||||
title
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$emit('choosen', list);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
choice(list) {
|
|
||||||
this.$emit('choosen', list);
|
|
||||||
},
|
|
||||||
close() {
|
close() {
|
||||||
(this as any).$refs.window.close();
|
(this as any).$refs.window.close();
|
||||||
|
},
|
||||||
|
choosen(list) {
|
||||||
|
this.$root.new(MkUserListWindow, {
|
||||||
|
list
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" module>
|
||||||
.xkxvokkjlptzyewouewmceqcxhpgzprp
|
.content
|
||||||
padding 16px
|
height 100%
|
||||||
|
overflow auto
|
||||||
> button
|
|
||||||
display block
|
|
||||||
margin-bottom 16px
|
|
||||||
color var(--primaryForeground)
|
|
||||||
background var(--primary)
|
|
||||||
width 100%
|
|
||||||
border-radius 38px
|
|
||||||
user-select none
|
|
||||||
cursor pointer
|
|
||||||
padding 0 16px
|
|
||||||
min-width 100px
|
|
||||||
line-height 38px
|
|
||||||
font-size 14px
|
|
||||||
font-weight 700
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
background var(--primaryLighten10)
|
|
||||||
|
|
||||||
&:active
|
|
||||||
background var(--primaryDarken10)
|
|
||||||
|
|
||||||
> a
|
|
||||||
display block
|
|
||||||
padding 16px
|
|
||||||
border solid 1px var(--faceDivider)
|
|
||||||
border-radius 4px
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -53,6 +53,12 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
|
this.$root.$on('warp', this.warp);
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
|
this.$root.$off('warp', this.warp);
|
||||||
|
this.connection.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
const prepend = note => {
|
const prepend = note => {
|
||||||
(this.$refs.timeline as any).prepend(note);
|
(this.$refs.timeline as any).prepend(note);
|
||||||
};
|
};
|
||||||
@ -124,13 +130,14 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
|
||||||
this.connection.dispose();
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
focus() {
|
focus() {
|
||||||
(this.$refs.timeline as any).focus();
|
(this.$refs.timeline as any).focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
warp(date) {
|
||||||
|
this.date = date;
|
||||||
|
(this.$refs.timeline as any).reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -36,7 +36,8 @@ export default Vue.extend({
|
|||||||
includeReplies: this.mode == 'with-replies',
|
includeReplies: this.mode == 'with-replies',
|
||||||
includeMyRenotes: this.mode != 'my-posts',
|
includeMyRenotes: this.mode != 'my-posts',
|
||||||
withFiles: this.mode == 'with-media',
|
withFiles: this.mode == 'with-media',
|
||||||
untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365
|
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||||
|
untilId: cursor ? cursor : undefined
|
||||||
}).then(notes => {
|
}).then(notes => {
|
||||||
if (notes.length == fetchLimit + 1) {
|
if (notes.length == fetchLimit + 1) {
|
||||||
notes.pop();
|
notes.pop();
|
||||||
@ -62,10 +63,11 @@ export default Vue.extend({
|
|||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
document.addEventListener('keydown', this.onDocumentKeydown);
|
document.addEventListener('keydown', this.onDocumentKeydown);
|
||||||
},
|
this.$root.$on('warp', this.warp);
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
beforeDestroy() {
|
this.$root.$off('warp', this.warp);
|
||||||
document.removeEventListener('keydown', this.onDocumentKeydown);
|
document.removeEventListener('keydown', this.onDocumentKeydown);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -11,7 +11,9 @@
|
|||||||
|
|
||||||
<div class="mkw-polls--body">
|
<div class="mkw-polls--body">
|
||||||
<div class="poll" v-if="!fetching && poll != null">
|
<div class="poll" v-if="!fetching && poll != null">
|
||||||
<p v-if="poll.text"><router-link :to="poll | notePage">{{ poll.text }}</router-link></p>
|
<p v-if="poll.text"><router-link :to="poll | notePage">
|
||||||
|
<mfm :text="poll.text" :author="poll.user" :custom-emojis="poll.emojis"/>
|
||||||
|
</router-link></p>
|
||||||
<p v-if="!poll.text"><router-link :to="poll | notePage"><fa icon="link"/></router-link></p>
|
<p v-if="!poll.text"><router-link :to="poll | notePage"><fa icon="link"/></router-link></p>
|
||||||
<mk-poll :note="poll"/>
|
<mk-poll :note="poll"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,7 +14,7 @@ export default define({
|
|||||||
}).extend({
|
}).extend({
|
||||||
methods: {
|
methods: {
|
||||||
chosen(date) {
|
chosen(date) {
|
||||||
this.$emit('chosen', date);
|
this.$root.$emit('warp', date);
|
||||||
},
|
},
|
||||||
func() {
|
func() {
|
||||||
if (this.props.design == 5) {
|
if (this.props.design == 5) {
|
||||||
|
@ -458,10 +458,14 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS)
|
|||||||
},
|
},
|
||||||
dialog(opts) {
|
dialog(opts) {
|
||||||
const vm = this.new(Dialog, opts);
|
const vm = this.new(Dialog, opts);
|
||||||
return new Promise((res) => {
|
const p: any = new Promise((res) => {
|
||||||
vm.$once('ok', result => res({ canceled: false, result }));
|
vm.$once('ok', result => res({ canceled: false, result }));
|
||||||
vm.$once('cancel', () => res({ canceled: true }));
|
vm.$once('cancel', () => res({ canceled: true }));
|
||||||
});
|
});
|
||||||
|
p.close = () => {
|
||||||
|
vm.close();
|
||||||
|
};
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
router,
|
router,
|
||||||
|
@ -379,44 +379,31 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
openContextMenu() {
|
|
||||||
const fn = window.prompt(this.$t('prompt'));
|
|
||||||
if (fn == null || fn == '') return;
|
|
||||||
switch (fn) {
|
|
||||||
case '1':
|
|
||||||
this.selectLocalFile();
|
|
||||||
break;
|
|
||||||
case '2':
|
|
||||||
this.urlUpload();
|
|
||||||
break;
|
|
||||||
case '3':
|
|
||||||
this.createFolder();
|
|
||||||
break;
|
|
||||||
case '4':
|
|
||||||
this.renameFolder();
|
|
||||||
break;
|
|
||||||
case '5':
|
|
||||||
this.moveFolder();
|
|
||||||
break;
|
|
||||||
case '6':
|
|
||||||
this.deleteFolder();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
selectLocalFile() {
|
selectLocalFile() {
|
||||||
(this.$refs.file as any).click();
|
(this.$refs.file as any).click();
|
||||||
},
|
},
|
||||||
|
|
||||||
createFolder() {
|
createFolder() {
|
||||||
const name = window.prompt(this.$t('folder-name'));
|
this.$root.dialog({
|
||||||
if (name == null || name == '') return;
|
title: this.$t('folder-name')
|
||||||
|
input: {
|
||||||
|
default: this.folder.name
|
||||||
|
}
|
||||||
|
}).then(({ result: name }) => {
|
||||||
|
if (!name) {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'error',
|
||||||
|
text: this.$t('folder-name-cannot-empty')
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.$root.api('drive/folders/create', {
|
this.$root.api('drive/folders/create', {
|
||||||
name: name,
|
name: name,
|
||||||
parentId: this.folder ? this.folder.id : undefined
|
parentId: this.folder ? this.folder.id : undefined
|
||||||
}).then(folder => {
|
}).then(folder => {
|
||||||
this.addFolder(folder, true);
|
this.addFolder(folder, true);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
renameFolder() {
|
renameFolder() {
|
||||||
@ -427,14 +414,26 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const name = window.prompt(this.$t('folder-name'), this.folder.name);
|
this.$root.dialog({
|
||||||
if (name == null || name == '') return;
|
title: this.$t('folder-name')
|
||||||
|
input: {
|
||||||
|
default: this.folder.name
|
||||||
|
}
|
||||||
|
}).then(({ result: name }) => {
|
||||||
|
if (!name) {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'error',
|
||||||
|
text: this.$t('cannot-empty')
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.$root.api('drive/folders/update', {
|
this.$root.api('drive/folders/update', {
|
||||||
name: name,
|
name: name,
|
||||||
folderId: this.folder.id
|
folderId: this.folder.id
|
||||||
}).then(folder => {
|
}).then(folder => {
|
||||||
this.cd(folder);
|
this.cd(folder);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
moveFolder() {
|
moveFolder() {
|
||||||
|
@ -124,7 +124,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
fetchMore() {
|
fetchMore() {
|
||||||
if (!this.more || this.moreFetching) return;
|
if (!this.more || this.moreFetching || this.notes.length === 0) return;
|
||||||
this.moreFetching = true;
|
this.moreFetching = true;
|
||||||
this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
|
this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
|
||||||
this.notes = this.notes.concat(x.notes);
|
this.notes = this.notes.concat(x.notes);
|
||||||
|
@ -66,6 +66,7 @@ import i18n from '../../../i18n';
|
|||||||
import { lang } from '../../../config';
|
import { lang } from '../../../config';
|
||||||
import { faNewspaper, faHashtag, faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
|
import { faNewspaper, faHashtag, faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons';
|
import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons';
|
||||||
|
import { search } from '../../../common/scripts/search';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('mobile/views/components/ui.nav.vue'),
|
i18n: i18n('mobile/views/components/ui.nav.vue'),
|
||||||
@ -133,29 +134,10 @@ export default Vue.extend({
|
|||||||
}).then(async ({ canceled, result: query }) => {
|
}).then(async ({ canceled, result: query }) => {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
const q = query.trim();
|
|
||||||
if (q.startsWith('@')) {
|
|
||||||
this.$router.push(`/${q}`);
|
|
||||||
} else if (q.startsWith('#')) {
|
|
||||||
this.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
|
|
||||||
} else if (q.startsWith('https://')) {
|
|
||||||
this.searching = true;
|
this.searching = true;
|
||||||
try {
|
search(this, query).finally(() => {
|
||||||
const res = await this.$root.api('ap/show', {
|
|
||||||
uri: q
|
|
||||||
});
|
|
||||||
if (res.type == 'User') {
|
|
||||||
this.$router.push(`/@${res.object.username}@${res.object.host}`);
|
|
||||||
} else if (res.type == 'Note') {
|
|
||||||
this.$router.push(`/notes/${res.object.id}`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
this.searching = false;
|
this.searching = false;
|
||||||
} else {
|
});
|
||||||
this.$router.push(`/search?q=${encodeURIComponent(q)}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -15,10 +15,12 @@ export default Vue.extend({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
connection: null,
|
connection: null,
|
||||||
|
date: null,
|
||||||
makePromise: cursor => this.$root.api('notes/user-list-timeline', {
|
makePromise: cursor => this.$root.api('notes/user-list-timeline', {
|
||||||
listId: this.list.id,
|
listId: this.list.id,
|
||||||
limit: fetchLimit + 1,
|
limit: fetchLimit + 1,
|
||||||
untilId: cursor ? cursor : undefined,
|
untilId: cursor ? cursor : undefined,
|
||||||
|
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||||
@ -45,6 +47,11 @@ export default Vue.extend({
|
|||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.init();
|
this.init();
|
||||||
|
|
||||||
|
this.$root.$on('warp', this.warp);
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
|
this.$root.$off('warp', this.warp);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
@ -73,6 +80,11 @@ export default Vue.extend({
|
|||||||
|
|
||||||
onUserRemoved() {
|
onUserRemoved() {
|
||||||
(this.$refs.timeline as any).reload();
|
(this.$refs.timeline as any).reload();
|
||||||
|
},
|
||||||
|
|
||||||
|
warp(date) {
|
||||||
|
this.date = date;
|
||||||
|
(this.$refs.timeline as any).reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -17,11 +17,13 @@ export default Vue.extend({
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
date: null,
|
||||||
makePromise: cursor => this.$root.api('users/notes', {
|
makePromise: cursor => this.$root.api('users/notes', {
|
||||||
userId: this.user.id,
|
userId: this.user.id,
|
||||||
limit: fetchLimit + 1,
|
limit: fetchLimit + 1,
|
||||||
withFiles: this.withMedia,
|
withFiles: this.withMedia,
|
||||||
untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365
|
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||||
|
untilId: cursor ? cursor : undefined
|
||||||
}).then(notes => {
|
}).then(notes => {
|
||||||
if (notes.length == fetchLimit + 1) {
|
if (notes.length == fetchLimit + 1) {
|
||||||
notes.pop();
|
notes.pop();
|
||||||
@ -37,6 +39,20 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.$root.$on('warp', this.warp);
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
|
this.$root.$off('warp', this.warp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
warp(date) {
|
||||||
|
this.date = date;
|
||||||
|
(this.$refs.timeline as any).reload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<template v-if="file"><mk-file-type-icon data-icon :type="file.type" style="margin-right:4px;"/>{{ file.name }}</template>
|
<template v-if="file"><mk-file-type-icon data-icon :type="file.type" style="margin-right:4px;"/>{{ file.name }}</template>
|
||||||
<template v-if="!folder && !file"><span style="margin-right:4px;"><fa icon="cloud"/></span>{{ $t('@.drive') }}</template>
|
<template v-if="!folder && !file"><span style="margin-right:4px;"><fa icon="cloud"/></span>{{ $t('@.drive') }}</template>
|
||||||
</template>
|
</template>
|
||||||
<template #func><button @click="fn"><fa icon="ellipsis-h"/></button></template>
|
<template #func v-if="folder || (!folder && !file)"><button @click="openContextMenu" ref="contextSource"><fa icon="ellipsis-h"/></button></template>
|
||||||
<x-drive
|
<x-drive
|
||||||
ref="browser"
|
ref="browser"
|
||||||
:init-folder="initFolder"
|
:init-folder="initFolder"
|
||||||
@ -26,9 +26,12 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import Progress from '../../../common/scripts/loading';
|
import Progress from '../../../common/scripts/loading';
|
||||||
|
import XMenu from '../../../common/views/components/menu.vue';
|
||||||
|
import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
|
||||||
|
import { faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n(),
|
i18n: i18n('mobile/views/pages/drive.vue'),
|
||||||
components: {
|
components: {
|
||||||
XDrive: () => import('../components/drive.vue').then(m => m.default),
|
XDrive: () => import('../components/drive.vue').then(m => m.default),
|
||||||
},
|
},
|
||||||
@ -63,9 +66,6 @@ export default Vue.extend({
|
|||||||
(this.$refs as any).browser.goRoot(true);
|
(this.$refs as any).browser.goRoot(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fn() {
|
|
||||||
(this.$refs as any).browser.openContextMenu();
|
|
||||||
},
|
|
||||||
onMoveRoot(silent) {
|
onMoveRoot(silent) {
|
||||||
const title = `${this.$root.instanceName} Drive`;
|
const title = `${this.$root.instanceName} Drive`;
|
||||||
|
|
||||||
@ -104,6 +104,42 @@ export default Vue.extend({
|
|||||||
|
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.folder = null;
|
this.folder = null;
|
||||||
|
},
|
||||||
|
openContextMenu() {
|
||||||
|
this.$root.new(XMenu, {
|
||||||
|
items: [{
|
||||||
|
type: 'item',
|
||||||
|
text: this.$t('contextmenu.upload'),
|
||||||
|
icon: 'upload',
|
||||||
|
action: this.$refs.browser.selectLocalFile
|
||||||
|
}, {
|
||||||
|
type: 'item',
|
||||||
|
text: this.$t('contextmenu.url-upload'),
|
||||||
|
icon: faCloudUploadAlt,
|
||||||
|
action: this.$refs.browser.urlUpload
|
||||||
|
}, {
|
||||||
|
type: 'item',
|
||||||
|
text: this.$t('contextmenu.create-folder'),
|
||||||
|
icon: ['far', 'folder'],
|
||||||
|
action: this.$refs.browser.createFolder
|
||||||
|
}, ...(this.folder ? [{
|
||||||
|
type: 'item',
|
||||||
|
text: this.$t('contextmenu.rename-folder'),
|
||||||
|
icon: 'i-cursor',
|
||||||
|
action: this.$refs.browser.renameFolder
|
||||||
|
}, {
|
||||||
|
type: 'item',
|
||||||
|
text: this.$t('contextmenu.move-folder'),
|
||||||
|
icon: ['far', 'folder-open'],
|
||||||
|
action: this.$refs.browser.moveFolder
|
||||||
|
}, {
|
||||||
|
type: 'item',
|
||||||
|
text: this.$t('contextmenu.delete-folder'),
|
||||||
|
icon: faTrashAlt,
|
||||||
|
action: this.$refs.browser.deleteFolder
|
||||||
|
}] : [])],
|
||||||
|
source: this.$refs.contextSource,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -54,6 +54,12 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
|
this.$root.$on('warp', this.warp);
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
|
this.$root.$off('warp', this.warp);
|
||||||
|
this.connection.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
const prepend = note => {
|
const prepend = note => {
|
||||||
(this.$refs.timeline as any).prepend(note);
|
(this.$refs.timeline as any).prepend(note);
|
||||||
};
|
};
|
||||||
@ -125,10 +131,6 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
|
||||||
this.connection.dispose();
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
focus() {
|
focus() {
|
||||||
(this.$refs.timeline as any).focus();
|
(this.$refs.timeline as any).focus();
|
||||||
|
@ -1,20 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<mk-ui>
|
<mk-ui>
|
||||||
<template #header><fa icon="list"/>{{ $t('title') }}</template>
|
<template #header><fa icon="list"/>{{ $t('title') }}</template>
|
||||||
<template #func><button @click="fn"><fa icon="plus"/></button></template>
|
<template #func><button @click="$refs.lists.add()"><fa icon="plus"/></button></template>
|
||||||
|
|
||||||
<main>
|
<x-lists ref="lists" @choosen="choosen"/>
|
||||||
<ul>
|
|
||||||
<li v-for="list in lists" :key="list.id"><router-link :to="`/i/lists/${list.id}`">{{ list.name }}</router-link></li>
|
|
||||||
</ul>
|
|
||||||
</main>
|
|
||||||
</mk-ui>
|
</mk-ui>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import Progress from '../../../common/scripts/loading';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('mobile/views/pages/user-lists.vue'),
|
i18n: i18n('mobile/views/pages/user-lists.vue'),
|
||||||
@ -24,31 +19,16 @@ export default Vue.extend({
|
|||||||
lists: []
|
lists: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default)
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
document.title = this.$t('title');
|
document.title = this.$t('title');
|
||||||
|
|
||||||
Progress.start();
|
|
||||||
|
|
||||||
this.$root.api('users/lists/list').then(lists => {
|
|
||||||
this.fetching = false;
|
|
||||||
this.lists = lists;
|
|
||||||
|
|
||||||
Progress.done();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fn() {
|
choosen(list) {
|
||||||
this.$root.dialog({
|
if (!list) return;
|
||||||
title: this.$t('enter-list-name'),
|
|
||||||
input: true
|
|
||||||
}).then(async ({ canceled, result: title }) => {
|
|
||||||
if (canceled) return;
|
|
||||||
const list = await this.$root.api('users/lists/create', {
|
|
||||||
title
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$router.push(`/i/lists/${list.id}`);
|
this.$router.push(`/i/lists/${list.id}`);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -72,13 +72,13 @@ export default Vue.extend({
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
widgets(): any[] {
|
widgets(): any[] {
|
||||||
return this.$store.state.settings.mobileHome;
|
return this.$store.state.device.mobileHome;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
if (this.widgets.length == 0) {
|
if (this.widgets.length == 0) {
|
||||||
this.widgets = [{
|
this.$store.commit('device/setMobileHome', [{
|
||||||
name: 'calendar',
|
name: 'calendar',
|
||||||
id: 'a', data: {}
|
id: 'a', data: {}
|
||||||
}, {
|
}, {
|
||||||
@ -96,8 +96,7 @@ export default Vue.extend({
|
|||||||
}, {
|
}, {
|
||||||
name: 'version',
|
name: 'version',
|
||||||
id: 'g', data: {}
|
id: 'g', data: {}
|
||||||
}];
|
}]);
|
||||||
this.saveHome();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -123,7 +122,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
addWidget() {
|
addWidget() {
|
||||||
this.$store.commit('settings/addMobileHomeWidget', {
|
this.$store.commit('device/addMobileHomeWidget', {
|
||||||
name: this.widgetAdderSelected,
|
name: this.widgetAdderSelected,
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
data: {}
|
data: {}
|
||||||
@ -131,11 +130,11 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
removeWidget(widget) {
|
removeWidget(widget) {
|
||||||
this.$store.commit('settings/removeMobileHomeWidget', widget);
|
this.$store.commit('device/removeMobileHomeWidget', widget);
|
||||||
},
|
},
|
||||||
|
|
||||||
saveHome() {
|
saveHome() {
|
||||||
this.$store.commit('settings/setMobileHome', this.widgets);
|
this.$store.commit('device/setMobileHome', this.widgets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -28,6 +28,7 @@ const defaultSettings = {
|
|||||||
iLikeSushi: false,
|
iLikeSushi: false,
|
||||||
rememberNoteVisibility: false,
|
rememberNoteVisibility: false,
|
||||||
defaultNoteVisibility: 'public',
|
defaultNoteVisibility: 'public',
|
||||||
|
wallpaper: null,
|
||||||
webSearchEngine: 'https://www.google.com/?#q={{query}}',
|
webSearchEngine: 'https://www.google.com/?#q={{query}}',
|
||||||
mutedWords: [],
|
mutedWords: [],
|
||||||
games: {
|
games: {
|
||||||
@ -357,7 +358,7 @@ export default (os: MiOS) => new Vuex.Store({
|
|||||||
ctx.commit('set', x);
|
ctx.commit('set', x);
|
||||||
|
|
||||||
if (ctx.rootGetters.isSignedIn) {
|
if (ctx.rootGetters.isSignedIn) {
|
||||||
os.api('i/update_client_setting', {
|
os.api('i/update-client-setting', {
|
||||||
name: x.key,
|
name: x.key,
|
||||||
value: x.value
|
value: x.value
|
||||||
});
|
});
|
||||||
|
@ -76,8 +76,6 @@ class MyCustomLogger implements Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function initDb(justBorrow = false, sync = false, log = false) {
|
export function initDb(justBorrow = false, sync = false, log = false) {
|
||||||
const enableLogging = log || !['production', 'test'].includes(process.env.NODE_ENV || '');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const conn = getConnection();
|
const conn = getConnection();
|
||||||
return Promise.resolve(conn);
|
return Promise.resolve(conn);
|
||||||
@ -92,8 +90,8 @@ export function initDb(justBorrow = false, sync = false, log = false) {
|
|||||||
database: config.db.db,
|
database: config.db.db,
|
||||||
synchronize: process.env.NODE_ENV === 'test' || sync,
|
synchronize: process.env.NODE_ENV === 'test' || sync,
|
||||||
dropSchema: process.env.NODE_ENV === 'test' && !justBorrow,
|
dropSchema: process.env.NODE_ENV === 'test' && !justBorrow,
|
||||||
logging: enableLogging,
|
logging: log,
|
||||||
logger: enableLogging ? new MyCustomLogger() : undefined,
|
logger: log ? new MyCustomLogger() : undefined,
|
||||||
entities: [
|
entities: [
|
||||||
Meta,
|
Meta,
|
||||||
Instance,
|
Instance,
|
||||||
|
@ -141,7 +141,7 @@ export const mfmLanguage = P.createLanguage({
|
|||||||
},
|
},
|
||||||
hashtag: () => P((input, i) => {
|
hashtag: () => P((input, i) => {
|
||||||
const text = input.substr(i);
|
const text = input.substr(i);
|
||||||
const match = text.match(/^#([^\s\.,!\?'"#:\/\[\]]+)/i);
|
const match = text.match(/^#([^\s\.,!\?'"#:\/\[\]【】]+)/i);
|
||||||
if (!match) return P.makeFailure(i, 'not a hashtag');
|
if (!match) return P.makeFailure(i, 'not a hashtag');
|
||||||
let hashtag = match[1];
|
let hashtag = match[1];
|
||||||
hashtag = removeOrphanedBrackets(hashtag);
|
hashtag = removeOrphanedBrackets(hashtag);
|
||||||
|
@ -26,6 +26,7 @@ export class AppRepository extends Repository<App> {
|
|||||||
id: app.id,
|
id: app.id,
|
||||||
name: app.name,
|
name: app.name,
|
||||||
callbackUrl: app.callbackUrl,
|
callbackUrl: app.callbackUrl,
|
||||||
|
permission: app.permission,
|
||||||
...(opts.includeSecret ? { secret: app.secret } : {}),
|
...(opts.includeSecret ? { secret: app.secret } : {}),
|
||||||
...(me ? {
|
...(me ? {
|
||||||
isAuthorized: await AccessTokens.count({
|
isAuthorized: await AccessTokens.count({
|
||||||
|
@ -178,12 +178,12 @@ export class NoteRepository extends Repository<Note> {
|
|||||||
name: In(reactionEmojis),
|
name: In(reactionEmojis),
|
||||||
host: host
|
host: host
|
||||||
}) : [],
|
}) : [],
|
||||||
tags: note.tags,
|
|
||||||
fileIds: note.fileIds,
|
fileIds: note.fileIds,
|
||||||
files: DriveFiles.packMany(note.fileIds),
|
files: DriveFiles.packMany(note.fileIds),
|
||||||
replyId: note.replyId,
|
replyId: note.replyId,
|
||||||
renoteId: note.renoteId,
|
renoteId: note.renoteId,
|
||||||
uri: note.uri,
|
mentions: note.mentions.length > 0 ? note.mentions : undefined,
|
||||||
|
uri: note.uri || undefined,
|
||||||
|
|
||||||
...(opts.detail ? {
|
...(opts.detail ? {
|
||||||
reply: note.replyId ? this.pack(note.replyId, meId, {
|
reply: note.replyId ? this.pack(note.replyId, meId, {
|
||||||
|
@ -82,7 +82,7 @@ export class UserRepository extends Repository<User> {
|
|||||||
|
|
||||||
const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null;
|
const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null;
|
||||||
const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : [];
|
const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : [];
|
||||||
const profile = opts.detail ? await UserProfiles.findOne({ userId: user.id }).then(ensure) : null;
|
const profile = opts.detail ? await UserProfiles.findOne(user.id).then(ensure) : null;
|
||||||
|
|
||||||
const falsy = opts.detail ? false : undefined;
|
const falsy = opts.detail ? false : undefined;
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import * as Bull from 'bull';
|
import * as Bull from 'bull';
|
||||||
import * as httpSignature from 'http-signature';
|
import * as httpSignature from 'http-signature';
|
||||||
import parseAcct from '../../misc/acct/parse';
|
|
||||||
import { IRemoteUser } from '../../models/entities/user';
|
import { IRemoteUser } from '../../models/entities/user';
|
||||||
import perform from '../../remote/activitypub/perform';
|
import perform from '../../remote/activitypub/perform';
|
||||||
import { resolvePerson, updatePerson } from '../../remote/activitypub/models/person';
|
import { resolvePerson, updatePerson } from '../../remote/activitypub/models/person';
|
||||||
@ -12,7 +11,7 @@ import { Instances, Users, UserPublickeys } from '../../models';
|
|||||||
import { instanceChart } from '../../services/chart';
|
import { instanceChart } from '../../services/chart';
|
||||||
import { UserPublickey } from '../../models/entities/user-publickey';
|
import { UserPublickey } from '../../models/entities/user-publickey';
|
||||||
import fetchMeta from '../../misc/fetch-meta';
|
import fetchMeta from '../../misc/fetch-meta';
|
||||||
import { toPuny, toPunyNullable } from '../../misc/convert-host';
|
import { toPuny } from '../../misc/convert-host';
|
||||||
import { validActor } from '../../remote/activitypub/type';
|
import { validActor } from '../../remote/activitypub/type';
|
||||||
import { ensure } from '../../prelude/ensure';
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@ -35,38 +34,10 @@ export default async (job: Bull.Job): Promise<void> => {
|
|||||||
let key: UserPublickey;
|
let key: UserPublickey;
|
||||||
|
|
||||||
if (keyIdLower.startsWith('acct:')) {
|
if (keyIdLower.startsWith('acct:')) {
|
||||||
const acct = parseAcct(keyIdLower.slice('acct:'.length));
|
logger.warn(`Old keyId is no longer supported. ${keyIdLower}`);
|
||||||
const host = toPunyNullable(acct.host);
|
|
||||||
const username = toPuny(acct.username);
|
|
||||||
|
|
||||||
if (host === null) {
|
|
||||||
logger.warn(`request was made by local user: @${username}`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// アクティビティ内のホストの検証
|
|
||||||
try {
|
|
||||||
ValidateActivity(activity, host);
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn(e.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ブロックしてたら中断
|
|
||||||
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
|
|
||||||
const meta = await fetchMeta();
|
|
||||||
if (meta.blockedHosts.includes(host)) {
|
|
||||||
logger.info(`Blocked request: ${host}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
user = await Users.findOne({
|
|
||||||
usernameLower: username.toLowerCase(),
|
|
||||||
host: host
|
|
||||||
}) as IRemoteUser;
|
|
||||||
|
|
||||||
key = await UserPublickeys.findOne(user.id).then(ensure);
|
|
||||||
} else {
|
|
||||||
// アクティビティ内のホストの検証
|
// アクティビティ内のホストの検証
|
||||||
const host = toPuny(new URL(signature.keyId).hostname);
|
const host = toPuny(new URL(signature.keyId).hostname);
|
||||||
try {
|
try {
|
||||||
@ -84,19 +55,28 @@ export default async (job: Bull.Job): Promise<void> => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
key = await UserPublickeys.findOne({
|
const _key = await UserPublickeys.findOne({
|
||||||
keyId: signature.keyId
|
keyId: signature.keyId
|
||||||
}).then(ensure);
|
});
|
||||||
|
|
||||||
user = await Users.findOne(key.userId) as IRemoteUser;
|
if (_key) {
|
||||||
|
// 登録済みユーザー
|
||||||
|
user = await Users.findOne(_key.userId) as IRemoteUser;
|
||||||
|
key = _key;
|
||||||
|
} else {
|
||||||
|
// 未登録ユーザーの場合はリモート解決
|
||||||
|
user = await resolvePerson(activity.actor) as IRemoteUser;
|
||||||
|
if (user == null) {
|
||||||
|
throw new Error('failed to resolve user');
|
||||||
|
}
|
||||||
|
|
||||||
|
key = await UserPublickeys.findOne(user.id).then(ensure);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Person activityの場合は、ここで署名検証/更新処理まで実施して終了
|
// Update Person activityの場合は、ここで署名検証/更新処理まで実施して終了
|
||||||
if (activity.type === 'Update') {
|
if (activity.type === 'Update') {
|
||||||
if (activity.object && validActor.includes(activity.object.type)) {
|
if (activity.object && validActor.includes(activity.object.type)) {
|
||||||
if (user == null) {
|
if (!httpSignature.verifySignature(signature, key.keyPem)) {
|
||||||
logger.warn('Update activity received, but user not registed.');
|
|
||||||
} else if (!httpSignature.verifySignature(signature, key.keyPem)) {
|
|
||||||
logger.warn('Update activity received, but signature verification failed.');
|
logger.warn('Update activity received, but signature verification failed.');
|
||||||
} else {
|
} else {
|
||||||
updatePerson(activity.actor, null, activity.object);
|
updatePerson(activity.actor, null, activity.object);
|
||||||
@ -105,15 +85,6 @@ export default async (job: Bull.Job): Promise<void> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する
|
|
||||||
if (user == null) {
|
|
||||||
user = await resolvePerson(activity.actor) as IRemoteUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user == null) {
|
|
||||||
throw new Error('failed to resolve user');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!httpSignature.verifySignature(signature, key.keyPem)) {
|
if (!httpSignature.verifySignature(signature, key.keyPem)) {
|
||||||
logger.error('signature verification failed');
|
logger.error('signature verification failed');
|
||||||
return;
|
return;
|
||||||
|
@ -2,6 +2,7 @@ import { IRemoteUser } from '../../../models/entities/user';
|
|||||||
import { ILike } from '../type';
|
import { ILike } from '../type';
|
||||||
import create from '../../../services/note/reaction/create';
|
import create from '../../../services/note/reaction/create';
|
||||||
import { Notes } from '../../../models';
|
import { Notes } from '../../../models';
|
||||||
|
import { apLogger } from '../logger';
|
||||||
|
|
||||||
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;
|
||||||
@ -14,7 +15,8 @@ export default async (actor: IRemoteUser, activity: ILike) => {
|
|||||||
|
|
||||||
const note = await Notes.findOne(noteId);
|
const note = await Notes.findOne(noteId);
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
throw new Error();
|
apLogger.warn(`Like activity recivied, but no such note: ${id}`, { id });
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await create(actor, note, activity._misskey_reaction);
|
await create(actor, note, activity._misskey_reaction);
|
||||||
|
@ -17,7 +17,7 @@ export async function renderPerson(user: ILocalUser) {
|
|||||||
const [avatar, banner, profile] = await Promise.all([
|
const [avatar, banner, profile] = await Promise.all([
|
||||||
user.avatarId ? DriveFiles.findOne(user.avatarId) : Promise.resolve(undefined),
|
user.avatarId ? DriveFiles.findOne(user.avatarId) : Promise.resolve(undefined),
|
||||||
user.bannerId ? DriveFiles.findOne(user.bannerId) : Promise.resolve(undefined),
|
user.bannerId ? DriveFiles.findOne(user.bannerId) : Promise.resolve(undefined),
|
||||||
UserProfiles.findOne({ userId: user.id }).then(ensure)
|
UserProfiles.findOne(user.id).then(ensure)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const attachment: {
|
const attachment: {
|
||||||
|
15
src/server/api/endpoints/endpoints.ts
Normal file
15
src/server/api/endpoints/endpoints.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import define from '../define';
|
||||||
|
import endpoints from '../endpoints';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
requireCredential: false,
|
||||||
|
|
||||||
|
tags: ['meta'],
|
||||||
|
|
||||||
|
params: {
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default define(meta, async () => {
|
||||||
|
return endpoints.map(x => x.name);
|
||||||
|
});
|
@ -19,7 +19,7 @@ export const meta = {
|
|||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
const token = ps.token.replace(/\s/g, '');
|
const token = ps.token.replace(/\s/g, '');
|
||||||
|
|
||||||
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure);
|
const profile = await UserProfiles.findOne(user.id).then(ensure);
|
||||||
|
|
||||||
if (profile.twoFactorTempSecret == null) {
|
if (profile.twoFactorTempSecret == null) {
|
||||||
throw new Error('二段階認証の設定が開始されていません');
|
throw new Error('二段階認証の設定が開始されていません');
|
||||||
|
@ -20,7 +20,7 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure);
|
const profile = await UserProfiles.findOne(user.id).then(ensure);
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||||
|
@ -17,7 +17,7 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure);
|
const profile = await UserProfiles.findOne(user.id).then(ensure);
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||||
|
@ -21,7 +21,7 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure);
|
const profile = await UserProfiles.findOne(user.id).then(ensure);
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
const same = await bcrypt.compare(ps.currentPassword, profile.password!);
|
const same = await bcrypt.compare(ps.currentPassword, profile.password!);
|
||||||
|
@ -17,7 +17,7 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure);
|
const profile = await UserProfiles.findOne(user.id).then(ensure);
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||||
|
@ -19,7 +19,7 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure);
|
const profile = await UserProfiles.findOne(user.id).then(ensure);
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||||
|
@ -33,7 +33,7 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure);
|
const profile = await UserProfiles.findOne(user.id).then(ensure);
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
const same = await bcrypt.compare(ps.password, profile.password!);
|
const same = await bcrypt.compare(ps.password, profile.password!);
|
||||||
|
@ -13,6 +13,7 @@ import { ApiError } from '../../error';
|
|||||||
import { Users, DriveFiles, UserProfiles } from '../../../../models';
|
import { Users, DriveFiles, UserProfiles } from '../../../../models';
|
||||||
import { User } from '../../../../models/entities/user';
|
import { User } from '../../../../models/entities/user';
|
||||||
import { UserProfile } from '../../../../models/entities/user-profile';
|
import { UserProfile } from '../../../../models/entities/user-profile';
|
||||||
|
import { ensure } from '../../../../prelude/ensure';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
@ -157,22 +158,24 @@ export default define(meta, async (ps, user, app) => {
|
|||||||
const isSecure = user != null && app == null;
|
const isSecure = user != null && app == null;
|
||||||
|
|
||||||
const updates = {} as Partial<User>;
|
const updates = {} as Partial<User>;
|
||||||
const profile = {} as Partial<UserProfile>;
|
const profileUpdates = {} as Partial<UserProfile>;
|
||||||
|
|
||||||
|
const profile = await UserProfiles.findOne(user.id).then(ensure);
|
||||||
|
|
||||||
if (ps.name !== undefined) updates.name = ps.name;
|
if (ps.name !== undefined) updates.name = ps.name;
|
||||||
if (ps.description !== undefined) profile.description = ps.description;
|
if (ps.description !== undefined) profileUpdates.description = ps.description;
|
||||||
//if (ps.lang !== undefined) updates.lang = ps.lang;
|
//if (ps.lang !== undefined) updates.lang = ps.lang;
|
||||||
if (ps.location !== undefined) profile.location = ps.location;
|
if (ps.location !== undefined) profileUpdates.location = ps.location;
|
||||||
if (ps.birthday !== undefined) profile.birthday = ps.birthday;
|
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
|
||||||
if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
|
if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
|
||||||
if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
|
if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
|
||||||
if (typeof ps.isLocked == 'boolean') updates.isLocked = ps.isLocked;
|
if (typeof ps.isLocked == 'boolean') updates.isLocked = ps.isLocked;
|
||||||
if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot;
|
if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot;
|
||||||
if (typeof ps.carefulBot == 'boolean') profile.carefulBot = ps.carefulBot;
|
if (typeof ps.carefulBot == 'boolean') profileUpdates.carefulBot = ps.carefulBot;
|
||||||
if (typeof ps.autoAcceptFollowed == 'boolean') profile.autoAcceptFollowed = ps.autoAcceptFollowed;
|
if (typeof ps.autoAcceptFollowed == 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
||||||
if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat;
|
if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat;
|
||||||
if (typeof ps.autoWatch == 'boolean') profile.autoWatch = ps.autoWatch;
|
if (typeof ps.autoWatch == 'boolean') profileUpdates.autoWatch = ps.autoWatch;
|
||||||
if (typeof ps.alwaysMarkNsfw == 'boolean') profile.alwaysMarkNsfw = ps.alwaysMarkNsfw;
|
if (typeof ps.alwaysMarkNsfw == 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
|
||||||
|
|
||||||
if (ps.avatarId) {
|
if (ps.avatarId) {
|
||||||
const avatar = await DriveFiles.findOne(ps.avatarId);
|
const avatar = await DriveFiles.findOne(ps.avatarId);
|
||||||
@ -201,16 +204,20 @@ export default define(meta, async (ps, user, app) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//#region emojis/tags
|
//#region emojis/tags
|
||||||
|
|
||||||
let emojis = [] as string[];
|
let emojis = [] as string[];
|
||||||
let tags = [] as string[];
|
let tags = [] as string[];
|
||||||
|
|
||||||
if (updates.name != null) {
|
const newName = updates.name === undefined ? user.name : updates.name;
|
||||||
const tokens = parsePlain(updates.name);
|
const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description;
|
||||||
|
|
||||||
|
if (newName != null) {
|
||||||
|
const tokens = parsePlain(newName);
|
||||||
emojis = emojis.concat(extractEmojis(tokens!));
|
emojis = emojis.concat(extractEmojis(tokens!));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profile.description != null) {
|
if (newDescription != null) {
|
||||||
const tokens = parse(profile.description);
|
const tokens = parse(newDescription);
|
||||||
emojis = emojis.concat(extractEmojis(tokens!));
|
emojis = emojis.concat(extractEmojis(tokens!));
|
||||||
tags = extractHashtags(tokens!).map(tag => tag.toLowerCase());
|
tags = extractHashtags(tokens!).map(tag => tag.toLowerCase());
|
||||||
}
|
}
|
||||||
@ -224,7 +231,7 @@ export default define(meta, async (ps, user, app) => {
|
|||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
if (Object.keys(updates).length > 0) await Users.update(user.id, updates);
|
if (Object.keys(updates).length > 0) await Users.update(user.id, updates);
|
||||||
if (Object.keys(profile).length > 0) await UserProfiles.update({ userId: user.id }, profile);
|
if (Object.keys(profileUpdates).length > 0) await UserProfiles.update({ userId: user.id }, profileUpdates);
|
||||||
|
|
||||||
const iObj = await Users.pack(user.id, user, {
|
const iObj = await Users.pack(user.id, user, {
|
||||||
detail: true,
|
detail: true,
|
||||||
|
@ -17,6 +17,8 @@ export const meta = {
|
|||||||
|
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
limit: {
|
limit: {
|
||||||
validator: $.optional.num.range(1, 100),
|
validator: $.optional.num.range(1, 100),
|
||||||
|
@ -150,7 +150,7 @@ export default define(meta, async (ps, user) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure);
|
const profile = await UserProfiles.findOne(user.id).then(ensure);
|
||||||
|
|
||||||
// この投稿をWatchする
|
// この投稿をWatchする
|
||||||
if (profile.autoWatch !== false) {
|
if (profile.autoWatch !== false) {
|
||||||
|
@ -142,7 +142,7 @@ export default define(meta, async (ps, me) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//#region Construct query
|
//#region Construct query
|
||||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||||
.andWhere('note.userId = :userId', { userId: user.id })
|
.andWhere('note.userId = :userId', { userId: user.id })
|
||||||
.leftJoinAndSelect('note.user', 'user');
|
.leftJoinAndSelect('note.user', 'user');
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
export const schemas = {
|
export const schemas = {
|
||||||
Error: {
|
Error: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
@ -46,7 +46,7 @@ export default async (ctx: Koa.BaseContext) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure);
|
const profile = await UserProfiles.findOne(user.id).then(ensure);
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
const same = await bcrypt.compare(password, profile.password!);
|
const same = await bcrypt.compare(password, profile.password!);
|
||||||
|
@ -20,11 +20,11 @@ export default class extends Channel {
|
|||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async onNote(note: any) {
|
private async onNote(note: any) {
|
||||||
// 自分自身の投稿 または その投稿のユーザーをフォローしている または ローカルの投稿 の場合だけ
|
// 自分自身の投稿 または その投稿のユーザーをフォローしている または 全体公開のローカルの投稿 の場合だけ
|
||||||
if (!(
|
if (!(
|
||||||
this.user!.id === note.userId ||
|
this.user!.id === note.userId ||
|
||||||
this.following.includes(note.userId) ||
|
this.following.includes(note.userId) ||
|
||||||
note.user.host == null
|
(note.user.host == null && note.visibility === 'public')
|
||||||
)) return;
|
)) return;
|
||||||
|
|
||||||
if (['followers', 'specified'].includes(note.visibility)) {
|
if (['followers', 'specified'].includes(note.visibility)) {
|
||||||
|
@ -11,7 +11,7 @@ export default async function(user: User) {
|
|||||||
name: user.name || user.username
|
name: user.name || user.username
|
||||||
};
|
};
|
||||||
|
|
||||||
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure);
|
const profile = await UserProfiles.findOne(user.id).then(ensure);
|
||||||
|
|
||||||
const notes = await Notes.find({
|
const notes = await Notes.find({
|
||||||
where: {
|
where: {
|
||||||
|
@ -363,16 +363,14 @@ export default async function(
|
|||||||
|
|
||||||
logger.debug(`average color is calculated: ${r}, ${g}, ${b}`);
|
logger.debug(`average color is calculated: ${r}, ${g}, ${b}`);
|
||||||
|
|
||||||
const value = info.isOpaque ? `rgba(${r},${g},${b},0)` : `rgba(${r},${g},${b},255)`;
|
properties['avgColor'] = `rgb(${r},${g},${b})`;
|
||||||
|
|
||||||
properties['avgColor'] = value;
|
|
||||||
} catch (e) { }
|
} catch (e) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
propPromises = [calcWh(), calcAvg()];
|
propPromises = [calcWh(), calcAvg()];
|
||||||
}
|
}
|
||||||
|
|
||||||
const profile = await UserProfiles.findOne({ userId: user.id });
|
const profile = await UserProfiles.findOne(user.id);
|
||||||
|
|
||||||
const [folder] = await Promise.all([fetchFolder(), Promise.all(propPromises)]);
|
const [folder] = await Promise.all([fetchFolder(), Promise.all(propPromises)]);
|
||||||
|
|
||||||
|
@ -253,7 +253,7 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
|
|||||||
deliverNoteToMentionedRemoteUsers(mentionedUsers, user, noteActivity);
|
deliverNoteToMentionedRemoteUsers(mentionedUsers, user, noteActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure);
|
const profile = await UserProfiles.findOne(user.id).then(ensure);
|
||||||
|
|
||||||
// If has in reply to note
|
// If has in reply to note
|
||||||
if (data.reply) {
|
if (data.reply) {
|
||||||
|
@ -67,7 +67,7 @@ export default async function(user: User, note: Note, choice: number) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const profile = await UserProfiles.findOne({ userId: user.id });
|
const profile = await UserProfiles.findOne(user.id);
|
||||||
|
|
||||||
// ローカルユーザーが投票した場合この投稿をWatchする
|
// ローカルユーザーが投票した場合この投稿をWatchする
|
||||||
if (Users.isLocalUser(user) && profile!.autoWatch) {
|
if (Users.isLocalUser(user) && profile!.autoWatch) {
|
||||||
|
@ -80,7 +80,7 @@ export default async (user: User, note: Note, reaction?: string) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const profile = await UserProfiles.findOne({ userId: user.id });
|
const profile = await UserProfiles.findOne(user.id);
|
||||||
|
|
||||||
// ユーザーがローカルユーザーかつ自動ウォッチ設定がオンならばこの投稿をWatchする
|
// ユーザーがローカルユーザーかつ自動ウォッチ設定がオンならばこの投稿をWatchする
|
||||||
if (Users.isLocalUser(user) && profile!.autoWatch) {
|
if (Users.isLocalUser(user) && profile!.autoWatch) {
|
||||||
|
@ -562,6 +562,14 @@ describe('MFM', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('ignore 】', () => {
|
||||||
|
const tokens = parse('#foo】');
|
||||||
|
assert.deepStrictEqual(tokens, [
|
||||||
|
leaf('hashtag', { hashtag: 'foo' }),
|
||||||
|
text('】'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('allow including number', () => {
|
it('allow including number', () => {
|
||||||
const tokens = parse('#foo123');
|
const tokens = parse('#foo123');
|
||||||
assert.deepStrictEqual(tokens, [
|
assert.deepStrictEqual(tokens, [
|
||||||
|
@ -32,7 +32,7 @@ describe('Streaming', () => {
|
|||||||
p.on('message', message => {
|
p.on('message', message => {
|
||||||
if (message === 'ok') {
|
if (message === 'ok') {
|
||||||
(p.channel as any).onread = () => {};
|
(p.channel as any).onread = () => {};
|
||||||
initDb(true).then(async connection => {
|
initDb(true).then(async (connection: any) => {
|
||||||
Followings = connection.getRepository(Following);
|
Followings = connection.getRepository(Following);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -44,7 +44,7 @@ describe('Streaming', () => {
|
|||||||
p.kill();
|
p.kill();
|
||||||
});
|
});
|
||||||
|
|
||||||
const follow = async (follower, followee) => {
|
const follow = async (follower: any, followee: any) => {
|
||||||
await Followings.save({
|
await Followings.save({
|
||||||
id: 'a',
|
id: 'a',
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
@ -484,6 +484,56 @@ describe('Streaming', () => {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('フォローしているユーザーのホーム投稿が流れる', () => new Promise(async done => {
|
||||||
|
const alice = await signup({ username: 'alice' });
|
||||||
|
const bob = await signup({ username: 'bob' });
|
||||||
|
|
||||||
|
// Alice が Bob をフォロー
|
||||||
|
await request('/following/create', {
|
||||||
|
userId: bob.id
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => {
|
||||||
|
if (type == 'note') {
|
||||||
|
assert.deepStrictEqual(body.userId, bob.id);
|
||||||
|
assert.deepStrictEqual(body.text, 'foo');
|
||||||
|
ws.close();
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ホーム投稿
|
||||||
|
post(bob, {
|
||||||
|
text: 'foo',
|
||||||
|
visibility: 'home'
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('フォローしていないローカルユーザーのホーム投稿は流れない', () => new Promise(async done => {
|
||||||
|
const alice = await signup({ username: 'alice' });
|
||||||
|
const bob = await signup({ username: 'bob' });
|
||||||
|
|
||||||
|
let fired = false;
|
||||||
|
|
||||||
|
const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => {
|
||||||
|
if (type == 'note') {
|
||||||
|
fired = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ホーム投稿
|
||||||
|
post(bob, {
|
||||||
|
text: 'foo',
|
||||||
|
visibility: 'home'
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
assert.strictEqual(fired, false);
|
||||||
|
ws.close();
|
||||||
|
done();
|
||||||
|
}, 3000);
|
||||||
|
}));
|
||||||
|
|
||||||
it('フォローしていないローカルユーザーのフォロワー宛て投稿は流れない', () => new Promise(async done => {
|
it('フォローしていないローカルユーザーのフォロワー宛て投稿は流れない', () => new Promise(async done => {
|
||||||
const alice = await signup({ username: 'alice' });
|
const alice = await signup({ username: 'alice' });
|
||||||
const bob = await signup({ username: 'bob' });
|
const bob = await signup({ username: 'bob' });
|
||||||
|
@ -76,7 +76,7 @@ export const uploadFile = (user: any, path?: string): Promise<any> => new Promis
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function connectStream(user: any, channel: string, listener: any, params?: any): Promise<WebSocket> {
|
export function connectStream(user: any, channel: string, listener: (message: Record<string, any>) => any, params?: any): Promise<WebSocket> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
const ws = new WebSocket(`ws://localhost/streaming?i=${user.token}`);
|
const ws = new WebSocket(`ws://localhost/streaming?i=${user.token}`);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user