Compare commits
42 Commits
Author | SHA1 | Date | |
---|---|---|---|
50824a7245 | |||
6f2953f3a7 | |||
dd3f007582 | |||
a4b2b093fc | |||
0fbf56219f | |||
0acacf7a8e | |||
c84500d914 | |||
a9cfbda858 | |||
33e79e4bb8 | |||
fab389e624 | |||
b1b02d0e32 | |||
0b40194d31 | |||
c2038bec73 | |||
8674d55c8e | |||
c7c0c9e79d | |||
2dff48167c | |||
71d42f64dc | |||
1b4072610a | |||
3826a820bb | |||
625eb376ae | |||
72cbab6514 | |||
c7a7059e26 | |||
550593b208 | |||
f76255fa63 | |||
15a2881083 | |||
37bfb79123 | |||
b62203b1f1 | |||
16136c252a | |||
75864a5125 | |||
a59f53e6da | |||
2ceaccf9ab | |||
036d46c459 | |||
5d3d78a73e | |||
6012e98ae6 | |||
9c0e990568 | |||
6167ed4c9f | |||
988d5405c3 | |||
ad0ea2fab2 | |||
d62c67208f | |||
2da1432e52 | |||
69eefc1425 | |||
27bdb26202 |
@ -117,8 +117,7 @@ jobs:
|
||||
command: |
|
||||
if [ "$DOCKERHUB_USERNAME$DOCKERHUB_PASSWORD" ]
|
||||
then
|
||||
curl -LSs 'https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64' > jq
|
||||
chmod +x jq
|
||||
apk update && apk add jq
|
||||
docker tag misskey/misskey misskey/misskey:$(cat package.json | jq -r .version)
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
|
||||
docker push misskey/misskey
|
||||
|
@ -11,6 +11,9 @@ Please use [Crowdin](https://crowdin.com/project/misskey) for localization.
|
||||
|
||||

|
||||
|
||||
## Internationalization (i18n)
|
||||
Misskey uses [vue-i18n](https://github.com/kazupon/vue-i18n).
|
||||
|
||||
## Documentation
|
||||
* Documents for contributors are located in `/docs`.
|
||||
* Documents for instance admins are located in `/docs`.
|
||||
|
@ -69,7 +69,7 @@ Build misskey with the following:
|
||||
|
||||
`npm run build`
|
||||
|
||||
If you're on Debian, you will need to install the `build-essential` package.
|
||||
If you're on Debian, you will need to install the `build-essential`, `python` package.
|
||||
|
||||
If you're still encountering errors about some modules, use node-gyp:
|
||||
|
||||
|
19
gulpfile.ts
19
gulpfile.ts
@ -5,6 +5,7 @@
|
||||
import * as gulp from 'gulp';
|
||||
import * as gutil from 'gulp-util';
|
||||
import * as ts from 'gulp-typescript';
|
||||
const yaml = require('gulp-yaml');
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
import tslint from 'gulp-tslint';
|
||||
const cssnano = require('gulp-cssnano');
|
||||
@ -39,6 +40,7 @@ gulp.task('build', [
|
||||
'build:ts',
|
||||
'build:copy',
|
||||
'build:client',
|
||||
'locales',
|
||||
'doc'
|
||||
]);
|
||||
|
||||
@ -57,16 +59,7 @@ gulp.task('build:copy:views', () =>
|
||||
gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/views'))
|
||||
);
|
||||
|
||||
// 互換性のため
|
||||
gulp.task('build:copy:lang', () =>
|
||||
gulp.src(['./built/client/assets/*.*-*.js'])
|
||||
.pipe(rename(path => {
|
||||
path.basename = path.basename.replace(/\-(.*)$/, '');
|
||||
}))
|
||||
.pipe(gulp.dest('./built/client/assets/'))
|
||||
);
|
||||
|
||||
gulp.task('build:copy', ['build:copy:views', 'build:copy:lang'], () =>
|
||||
gulp.task('build:copy', ['build:copy:views'], () =>
|
||||
gulp.src([
|
||||
'./build/Release/crypto_key.node',
|
||||
'./src/const.json',
|
||||
@ -201,6 +194,12 @@ gulp.task('build:client:pug', [
|
||||
.pipe(gulp.dest('./built/client/app/'))
|
||||
);
|
||||
|
||||
gulp.task('locales', () =>
|
||||
gulp.src('./locales/*.yml')
|
||||
.pipe(yaml({ schema: 'DEFAULT_SAFE_SCHEMA' }))
|
||||
.pipe(gulp.dest('./built/client/assets/locales/'))
|
||||
);
|
||||
|
||||
gulp.task('doc', () =>
|
||||
gulp.src('./src/docs/**/*.styl')
|
||||
.pipe(stylus())
|
||||
|
@ -920,6 +920,7 @@ admin/views/instance.vue:
|
||||
instance: "インスタンス"
|
||||
instance-name: "インスタンス名"
|
||||
instance-description: "インスタンスの紹介"
|
||||
host: "ホスト"
|
||||
banner-url: "バナー画像URL"
|
||||
languages: "インスタンスの対象言語"
|
||||
languages-desc: "スペースで区切って複数設定できます。"
|
||||
@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
is-bot: "このアカウントはBotです"
|
||||
years-old: "歳"
|
||||
years-old: "{age}歳"
|
||||
year: "年"
|
||||
month: "月"
|
||||
day: "日"
|
||||
@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "ミュート解除"
|
||||
block: "ブロック"
|
||||
unblock: "ブロック解除"
|
||||
years-old: "{age}歳"
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "最近の投稿"
|
||||
images: "画像"
|
||||
|
@ -920,6 +920,7 @@ admin/views/instance.vue:
|
||||
instance: "インスタンス"
|
||||
instance-name: "インスタンス名"
|
||||
instance-description: "インスタンスの紹介"
|
||||
host: "ホスト"
|
||||
banner-url: "バナー画像URL"
|
||||
languages: "インスタンスの対象言語"
|
||||
languages-desc: "スペースで区切って複数設定できます。"
|
||||
@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
is-bot: "このアカウントはBotです"
|
||||
years-old: "歳"
|
||||
years-old: "{age}歳"
|
||||
year: "年"
|
||||
month: "月"
|
||||
day: "日"
|
||||
@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "ミュート解除"
|
||||
block: "ブロック"
|
||||
unblock: "ブロック解除"
|
||||
years-old: "{age}歳"
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "最近の投稿"
|
||||
images: "画像"
|
||||
|
@ -920,6 +920,7 @@ admin/views/instance.vue:
|
||||
instance: "Instance"
|
||||
instance-name: "Instance name"
|
||||
instance-description: "Instance description"
|
||||
host: "Host"
|
||||
banner-url: "Banner image URL"
|
||||
languages: "Language of this instance"
|
||||
languages-desc: "You can add more than one, separated by spaces."
|
||||
@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "Following"
|
||||
followers: "Followers"
|
||||
is-bot: "This account is a Bot"
|
||||
years-old: " years old"
|
||||
years-old: "{age} years old"
|
||||
year: "/"
|
||||
month: "/"
|
||||
day: "-"
|
||||
@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "Unmute"
|
||||
block: "Block"
|
||||
unblock: "Unblock"
|
||||
years-old: "{age} years old"
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "Recent notes"
|
||||
images: "Images"
|
||||
|
@ -920,6 +920,7 @@ admin/views/instance.vue:
|
||||
instance: "インスタンス"
|
||||
instance-name: "インスタンス名"
|
||||
instance-description: "インスタンスの紹介"
|
||||
host: "ホスト"
|
||||
banner-url: "バナー画像URL"
|
||||
languages: "インスタンスの対象言語"
|
||||
languages-desc: "スペースで区切って複数設定できます。"
|
||||
@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
is-bot: "このアカウントはBotです"
|
||||
years-old: "歳"
|
||||
years-old: "{age}歳"
|
||||
year: "年"
|
||||
month: "月"
|
||||
day: "日"
|
||||
@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "ミュート解除"
|
||||
block: "ブロック"
|
||||
unblock: "ブロック解除"
|
||||
years-old: "{age}歳"
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "最近の投稿"
|
||||
images: "画像"
|
||||
|
@ -128,12 +128,12 @@ common:
|
||||
my-turn: "C’est votre tour"
|
||||
opponent-turn: "Tour de l’adversaire"
|
||||
turn-of: "Tour de {name}"
|
||||
past-turn-of: "{name}のターン"
|
||||
past-turn-of: "Tour de {name}"
|
||||
won: "{name} a gagné"
|
||||
black: "Noirs"
|
||||
white: "Blancs"
|
||||
total: "Total"
|
||||
this-turn: "{count}ターン目"
|
||||
this-turn: "Tour {count}"
|
||||
widgets:
|
||||
analog-clock: "Horloge analogique"
|
||||
profile: "Profil"
|
||||
@ -160,7 +160,7 @@ common:
|
||||
dev: "Échec lors de la création de l’application. Veuillez réessayer."
|
||||
ai-chan-kawaii: "Ai-Chan est mignone !"
|
||||
auth/views/form.vue:
|
||||
share-access: "<i>{name}</i>があなたのアカウントにアクセスすることを許可しますか?"
|
||||
share-access: "Désirez-vous autoriser <i>{name}</i> à avoir accès à votre compte ?"
|
||||
permission-ask: "Cette application nécessite les autorisations suivantes :"
|
||||
account-read: "Afficher les informations du compte :"
|
||||
account-write: "Modifications des informations du compte :"
|
||||
@ -519,7 +519,7 @@ desktop/views/components/calendar.vue:
|
||||
next: "Mois prochain"
|
||||
go: "Cliquez pour naviguer"
|
||||
desktop/views/components/choose-file-from-drive-window.vue:
|
||||
chosen-files: "{count}ファイル選択中"
|
||||
chosen-files: "{count} fichier·s sélectionné·s"
|
||||
upload: "Téléverser des fichiers à partir de votre ordinateur"
|
||||
cancel: "Annuler"
|
||||
ok: "OK"
|
||||
@ -687,8 +687,8 @@ desktop/views/components/renote-form.vue:
|
||||
desktop/views/components/renote-form-window.vue:
|
||||
title: "Êtes vous sûr de vouloir renote cette note?"
|
||||
desktop/views/pages/user-following-or-followers.vue:
|
||||
following: "{user}のフォロー"
|
||||
followers: "{user}のフォロワー"
|
||||
following: "{user} suit"
|
||||
followers: "Abonné·e·s de {user}"
|
||||
desktop/views/components/settings-window.vue:
|
||||
settings: "Paramètres"
|
||||
desktop/views/components/settings.vue:
|
||||
@ -717,7 +717,7 @@ desktop/views/components/settings.vue:
|
||||
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
|
||||
deck-nav: "デッキ内ナビゲーション"
|
||||
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
|
||||
deck-default: "デッキをデフォルトのUIにする"
|
||||
deck-default: "Utiliser le Deck comme IU par défaut"
|
||||
display: "Affichage et design"
|
||||
customize: "Personnaliser l'Accueil"
|
||||
wallpaper: "Arrière plan"
|
||||
@ -737,7 +737,7 @@ desktop/views/components/settings.vue:
|
||||
show-renoted-my-notes: "Afficher mes republications dans les fils"
|
||||
show-local-renotes: "Afficher les partages locaux sur les fils"
|
||||
show-maps: "Afficher la carte"
|
||||
deck-column-align: "デッキのカラムの位置"
|
||||
deck-column-align: "Alignement des colonnes du Deck"
|
||||
deck-column-align-center: "Centrer"
|
||||
deck-column-align-left: "À gauche"
|
||||
sound: "Son"
|
||||
@ -870,7 +870,7 @@ desktop/views/components/ui.header.account.vue:
|
||||
dark: "Fall in dark"
|
||||
desktop/views/components/ui.header.nav.vue:
|
||||
home: "Accueil"
|
||||
deck: "デッキ"
|
||||
deck: "Deck"
|
||||
game: "Jeux"
|
||||
desktop/views/components/ui.header.notifications.vue:
|
||||
title: "Notifications"
|
||||
@ -920,30 +920,31 @@ admin/views/instance.vue:
|
||||
instance: "Instance"
|
||||
instance-name: "Nom de l’instance"
|
||||
instance-description: "Description de l’instance"
|
||||
host: "Hôte"
|
||||
banner-url: "Url de l’image de la bannière"
|
||||
languages: "インスタンスの対象言語"
|
||||
languages-desc: "スペースで区切って複数設定できます。"
|
||||
languages: "Langue de l’instance"
|
||||
languages-desc: "Vous pouvez en définir plus d’une, séparées par des espaces."
|
||||
maintainer-config: "Informations de l’administrateur"
|
||||
maintainer-name: "Nom de l’administrateur"
|
||||
maintainer-email: "Contact administratif"
|
||||
drive-config: "Paramètres du lecteur"
|
||||
cache-remote-files: "Mettre en cache des fichiers distants"
|
||||
cache-remote-files-desc: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。そのためサーバーのストレージを節約できますが、プライバシー設定で直リンクを無効にしているユーザーにはファイルが見えなくなったり、サムネイルが生成されないので通信量が増加します。通常はこの設定をオンにしておくことをおすすめします。"
|
||||
local-drive-capacity-mb: "ローカルユーザーひとりあたりのドライブ容量"
|
||||
remote-drive-capacity-mb: "リモートユーザーひとりあたりのドライブ容量"
|
||||
local-drive-capacity-mb: "Volume du lecteur par utilisateur"
|
||||
remote-drive-capacity-mb: "Volume du lecteur par utilisateur distant"
|
||||
mb: "en mégaoctets"
|
||||
recaptcha-config: "Paramètres de reCAPTCHA"
|
||||
recaptcha-info: "reCAPTCHAを有効にする場合、reCAPTCHAトークンを取得する必要があります。https://www.google.com/recaptcha/intro/ にアクセスしてトークンを取得してください。"
|
||||
recaptcha-info: "Si activé, un jeton reCAPTCHA est requis. Vous pouvez en obtenir un sur https://www.google.com/recaptcha/intro/"
|
||||
enable-recaptcha: "Activation de reCAPTCHA"
|
||||
recaptcha-site-key: "Clé reCAPTCHA du site"
|
||||
recaptcha-secret-key: "Clé secrète reCAPTCHA"
|
||||
twitter-integration-config: "Paramètres de connexion à Twitter"
|
||||
twitter-integration-info: "コールバックURLは /api/tw/cb に設定します。"
|
||||
twitter-integration-info: "L’URL callback est définit sur /api/tw/cb"
|
||||
enable-twitter-integration: "Activer la connection à Twitter"
|
||||
twitter-integration-consumer-key: "Clé du consommateur"
|
||||
twitter-integration-consumer-secret: "Secret du consommateur"
|
||||
github-integration-config: "GitHub連携の設定"
|
||||
github-integration-info: "コールバックURLは /api/gh/cb に設定します。"
|
||||
github-integration-config: "Paramètres d’authentification GitHub"
|
||||
github-integration-info: "L’URL callback est définit sur /api/gh/cb"
|
||||
enable-github-integration: "Activer l’authentification avec Github"
|
||||
github-integration-client-id: "ID client"
|
||||
github-integration-client-secret: "Secret client"
|
||||
@ -951,7 +952,7 @@ admin/views/instance.vue:
|
||||
proxy-account-info: "プロキシアカウントは、特定の条件下でユーザーのリモートフォローを代行するアカウントです。例えば、ユーザーがリモートユーザーをリストに入れたとき、リストに入れられたユーザーを誰もフォローしていないとアクティビティがサーバーに配達されないため、代わりにプロキシアカウントがフォローするようにします。"
|
||||
proxy-account-username: "Nom d’utilisateur du compte proxy"
|
||||
proxy-account-username-desc: "Spécifiez le nom d’utilisateur du compte utilisé comme proxy."
|
||||
proxy-account-warn: "アカウントは自動で作られないため、そのユーザー名のアカウントを予め作成しておく必要があります。"
|
||||
proxy-account-warn: "Avant d’entammer cette action, vous devez au préalable avoir créé un compte avec ce nom d’utilisateur."
|
||||
max-note-text-length: "Nombre maximal de caractères pour les messages"
|
||||
disable-registration: "Désactiver les inscriptions"
|
||||
disable-local-timeline: "Désactiver l’heure locale"
|
||||
@ -1024,7 +1025,7 @@ admin/views/announcements.vue:
|
||||
text: "Contenu"
|
||||
saved: "Sauvegardé"
|
||||
_remove:
|
||||
are-you-sure: "「$1」を削除しますか?"
|
||||
are-you-sure: "Supprimer « %1$s » ?"
|
||||
removed: "Supprimé"
|
||||
admin/views/hashtags.vue:
|
||||
hided-tags: "Tags cachés"
|
||||
@ -1054,11 +1055,11 @@ desktop/views/pages/selectdrive.vue:
|
||||
upload: "Téléverser des fichiers à partir de votre ordinateur"
|
||||
desktop/views/pages/search.vue:
|
||||
not-available: "La fonction de recherche est désactivée dans les paramètres de l’instance."
|
||||
not-found: "「{q}」に関する投稿は見つかりませんでした。"
|
||||
not-found: "Aucune publication trouvée pour « {q} »."
|
||||
desktop/views/pages/share.vue:
|
||||
share-with: "Partager avec {name}"
|
||||
desktop/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
|
||||
no-posts-found: "Aucune publication contenant « {q} » n’a été trouvée."
|
||||
desktop/views/pages/user-list.users.vue:
|
||||
users: "Utilisateurs"
|
||||
add-user: "Ajouter un utilisateur"
|
||||
@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "Suit"
|
||||
followers: "Abonné·e·s"
|
||||
is-bot: "Ce compte est un Bot"
|
||||
years-old: "ans d’âge"
|
||||
years-old: "{age} ans"
|
||||
year: "Année"
|
||||
month: "/"
|
||||
day: "-"
|
||||
@ -1244,7 +1245,7 @@ mobile/views/pages/signup.vue:
|
||||
mobile/views/pages/followers.vue:
|
||||
followers-of: "Abonné·e·s de {name}"
|
||||
mobile/views/pages/following.vue:
|
||||
following-of: "{name}のフォロー"
|
||||
following-of: "Abonné·e·s de {name}"
|
||||
mobile/views/pages/home.vue:
|
||||
home: "Accueil"
|
||||
local: "Local"
|
||||
@ -1253,7 +1254,7 @@ mobile/views/pages/home.vue:
|
||||
mentions: "Mentions"
|
||||
messages: "Messages"
|
||||
mobile/views/pages/tag.vue:
|
||||
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
|
||||
no-posts-found: "Aucune publication ayant pour hashtag « {q} » n’a été trouvée."
|
||||
mobile/views/pages/welcome.vue:
|
||||
signup: "S'enregistrer"
|
||||
mobile/views/pages/widgets.vue:
|
||||
@ -1280,7 +1281,7 @@ mobile/views/pages/games/reversi.vue:
|
||||
reversi: "Reversi"
|
||||
mobile/views/pages/search.vue:
|
||||
search: "Chercher"
|
||||
not-found: "「{q}」に関する投稿は見つかりませんでした。"
|
||||
not-found: "Aucune publication trouvée pour « {q} »."
|
||||
mobile/views/pages/selectdrive.vue:
|
||||
select-file: "Choisissez un fichier"
|
||||
mobile/views/pages/settings.vue:
|
||||
@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "Enlever la sourdine"
|
||||
block: "Bloquer"
|
||||
unblock: "Débloquer"
|
||||
years-old: "{age} ans"
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "Notes récentes"
|
||||
images: "Images"
|
||||
@ -1391,7 +1393,7 @@ deck:
|
||||
deck/deck.tl-column.vue:
|
||||
is-media-only: "Les publications médias uniquement"
|
||||
is-media-view: "Vue média"
|
||||
edit: "オプション"
|
||||
edit: "Option"
|
||||
deck/deck.user-column.vue:
|
||||
posts: "Notes"
|
||||
following: "Suit"
|
||||
@ -1438,7 +1440,7 @@ dev/views/new-app.vue:
|
||||
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-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
|
||||
callback-url-desc: "Vous pouvez définir l’URL de redirection lorsque l’utilisateur s’est authentifié via formulaire d’authentification."
|
||||
authority: "Autorisations "
|
||||
authority-desc: "Sont accessibles via l’API, uniquement les fonctionnalités demandées ici."
|
||||
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
|
||||
|
@ -920,6 +920,7 @@ admin/views/instance.vue:
|
||||
instance: "インスタンス"
|
||||
instance-name: "インスタンス名"
|
||||
instance-description: "インスタンスの紹介"
|
||||
host: "ホスト"
|
||||
banner-url: "バナー画像URL"
|
||||
languages: "インスタンスの対象言語"
|
||||
languages-desc: "スペースで区切って複数設定できます。"
|
||||
@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
is-bot: "このアカウントはBotです"
|
||||
years-old: "歳"
|
||||
years-old: "{age}歳"
|
||||
year: "年"
|
||||
month: "月"
|
||||
day: "日"
|
||||
@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "ミュート解除"
|
||||
block: "ブロック"
|
||||
unblock: "ブロック解除"
|
||||
years-old: "{age}歳"
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "最近の投稿"
|
||||
images: "画像"
|
||||
|
@ -379,6 +379,17 @@ common/views/components/poll-editor.vue:
|
||||
common/views/components/reaction-picker.vue:
|
||||
choose-reaction: "リアクションを選択"
|
||||
|
||||
common/views/components/emoji-picker.vue:
|
||||
custom-emoji: "カスタム絵文字"
|
||||
people: "人"
|
||||
animals-and-nature: "動物&自然"
|
||||
food-and-drink: "食べ物&飲み物"
|
||||
activity: "アクティビティ"
|
||||
travel-and-places: "場所"
|
||||
objects: "物"
|
||||
symbols: "記号"
|
||||
flags: "旗"
|
||||
|
||||
common/views/components/signin.vue:
|
||||
username: "ユーザー名"
|
||||
password: "パスワード"
|
||||
@ -935,6 +946,10 @@ common/views/components/mute-and-block.vue:
|
||||
block: "ブロック"
|
||||
no-muted-users: "ミュートしているユーザーはいません"
|
||||
no-blocked-users: "ブロックしているユーザーはいません"
|
||||
word-mute: "ワードミュート"
|
||||
muted-words: "ミュートされたキーワード"
|
||||
muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
|
||||
save: "保存"
|
||||
|
||||
common/views/components/password-settings.vue:
|
||||
reset: "パスワードを変更する"
|
||||
@ -1045,6 +1060,7 @@ admin/views/instance.vue:
|
||||
instance: "インスタンス"
|
||||
instance-name: "インスタンス名"
|
||||
instance-description: "インスタンスの紹介"
|
||||
host: "ホスト"
|
||||
banner-url: "バナー画像URL"
|
||||
languages: "インスタンスの対象言語"
|
||||
languages-desc: "スペースで区切って複数設定できます。"
|
||||
@ -1237,7 +1253,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
is-bot: "このアカウントはBotです"
|
||||
years-old: "歳"
|
||||
years-old: "{age}歳"
|
||||
year: "年"
|
||||
month: "月"
|
||||
day: "日"
|
||||
@ -1543,6 +1559,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "ミュート解除"
|
||||
block: "ブロック"
|
||||
unblock: "ブロック解除"
|
||||
years-old: "{age}歳"
|
||||
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "最近の投稿"
|
||||
|
@ -920,6 +920,7 @@ admin/views/instance.vue:
|
||||
instance: "インスタンス"
|
||||
instance-name: "インスタンス名"
|
||||
instance-description: "インスタンスの紹介"
|
||||
host: "ホスト"
|
||||
banner-url: "バナー画像URL"
|
||||
languages: "インスタンスの対象言語"
|
||||
languages-desc: "スペースで区切って複数設定できます。"
|
||||
@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
is-bot: "このアカウントはBotや"
|
||||
years-old: "歳"
|
||||
years-old: "{age}歳"
|
||||
year: "年"
|
||||
month: "月"
|
||||
day: "日"
|
||||
@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "ミュート解除"
|
||||
block: "ブロック"
|
||||
unblock: "ブロックやめたる"
|
||||
years-old: "{age}歳"
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "最近儲かりまっか?"
|
||||
images: "画像"
|
||||
|
@ -920,6 +920,7 @@ admin/views/instance.vue:
|
||||
instance: "インスタンス"
|
||||
instance-name: "インスタンス名"
|
||||
instance-description: "インスタンスの紹介"
|
||||
host: "ホスト"
|
||||
banner-url: "バナー画像URL"
|
||||
languages: "インスタンスの対象言語"
|
||||
languages-desc: "スペースで区切って複数設定できます。"
|
||||
@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
is-bot: "このアカウントはBotです"
|
||||
years-old: "歳"
|
||||
years-old: "{age}歳"
|
||||
year: "年"
|
||||
month: "月"
|
||||
day: "日"
|
||||
@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "ミュート解除"
|
||||
block: "ブロック"
|
||||
unblock: "ブロック解除"
|
||||
years-old: "{age}歳"
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "最近の投稿"
|
||||
images: "画像"
|
||||
|
@ -920,6 +920,7 @@ admin/views/instance.vue:
|
||||
instance: "インスタンス"
|
||||
instance-name: "インスタンス名"
|
||||
instance-description: "インスタンスの紹介"
|
||||
host: "ホスト"
|
||||
banner-url: "バナー画像URL"
|
||||
languages: "インスタンスの対象言語"
|
||||
languages-desc: "スペースで区切って複数設定できます。"
|
||||
@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
is-bot: "このアカウントはBotです"
|
||||
years-old: "歳"
|
||||
years-old: "{age}歳"
|
||||
year: "年"
|
||||
month: "月"
|
||||
day: "日"
|
||||
@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "ミュート解除"
|
||||
block: "ブロック"
|
||||
unblock: "ブロック解除"
|
||||
years-old: "{age}歳"
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "Recente notities"
|
||||
images: "Afbeeldingen"
|
||||
|
@ -920,6 +920,7 @@ admin/views/instance.vue:
|
||||
instance: "インスタンス"
|
||||
instance-name: "インスタンス名"
|
||||
instance-description: "インスタンスの紹介"
|
||||
host: "ホスト"
|
||||
banner-url: "バナー画像URL"
|
||||
languages: "インスタンスの対象言語"
|
||||
languages-desc: "スペースで区切って複数設定できます。"
|
||||
@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "Følger"
|
||||
followers: "フォロワー"
|
||||
is-bot: "このアカウントはBotです"
|
||||
years-old: "歳"
|
||||
years-old: "{age}歳"
|
||||
year: "年"
|
||||
month: "月"
|
||||
day: "日"
|
||||
@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "ミュート解除"
|
||||
block: "ブロック"
|
||||
unblock: "ブロック解除"
|
||||
years-old: "{age}歳"
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "Nylige innlegg"
|
||||
images: "Bilder"
|
||||
|
@ -920,6 +920,7 @@ admin/views/instance.vue:
|
||||
instance: "インスタンス"
|
||||
instance-name: "インスタンス名"
|
||||
instance-description: "インスタンスの紹介"
|
||||
host: "ホスト"
|
||||
banner-url: "バナー画像URL"
|
||||
languages: "インスタンスの対象言語"
|
||||
languages-desc: "スペースで区切って複数設定できます。"
|
||||
@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "Śledzeni"
|
||||
followers: "Śledzący"
|
||||
is-bot: "To konto jest botem"
|
||||
years-old: "歳"
|
||||
years-old: "{age}歳"
|
||||
year: "年"
|
||||
month: "月"
|
||||
day: "日"
|
||||
@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "ミュート解除"
|
||||
block: "ブロック"
|
||||
unblock: "ブロック解除"
|
||||
years-old: "{age}歳"
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "Ostatnie wpisy"
|
||||
images: "Zdjęcia"
|
||||
|
@ -920,6 +920,7 @@ admin/views/instance.vue:
|
||||
instance: "インスタンス"
|
||||
instance-name: "インスタンス名"
|
||||
instance-description: "インスタンスの紹介"
|
||||
host: "ホスト"
|
||||
banner-url: "バナー画像URL"
|
||||
languages: "インスタンスの対象言語"
|
||||
languages-desc: "スペースで区切って複数設定できます。"
|
||||
@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
is-bot: "このアカウントはBotです"
|
||||
years-old: "歳"
|
||||
years-old: "{age}歳"
|
||||
year: "年"
|
||||
month: "月"
|
||||
day: "日"
|
||||
@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "ミュート解除"
|
||||
block: "ブロック"
|
||||
unblock: "ブロック解除"
|
||||
years-old: "{age}歳"
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "Notas recentes"
|
||||
images: "Imagens"
|
||||
|
@ -920,6 +920,7 @@ admin/views/instance.vue:
|
||||
instance: "インスタンス"
|
||||
instance-name: "インスタンス名"
|
||||
instance-description: "インスタンスの紹介"
|
||||
host: "ホスト"
|
||||
banner-url: "バナー画像URL"
|
||||
languages: "インスタンスの対象言語"
|
||||
languages-desc: "スペースで区切って複数設定できます。"
|
||||
@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
is-bot: "このアカウントはBotです"
|
||||
years-old: "歳"
|
||||
years-old: "{age}歳"
|
||||
year: "年"
|
||||
month: "月"
|
||||
day: "日"
|
||||
@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "ミュート解除"
|
||||
block: "ブロック"
|
||||
unblock: "ブロック解除"
|
||||
years-old: "{age}歳"
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "最近の投稿"
|
||||
images: "画像"
|
||||
|
@ -920,6 +920,7 @@ admin/views/instance.vue:
|
||||
instance: "インスタンス"
|
||||
instance-name: "インスタンス名"
|
||||
instance-description: "インスタンスの紹介"
|
||||
host: "ホスト"
|
||||
banner-url: "バナー画像URL"
|
||||
languages: "インスタンスの対象言語"
|
||||
languages-desc: "スペースで区切って複数設定できます。"
|
||||
@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
is-bot: "このアカウントはBotです"
|
||||
years-old: "歳"
|
||||
years-old: "{age}歳"
|
||||
year: "年"
|
||||
month: "月"
|
||||
day: "日"
|
||||
@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
|
||||
unmute: "ミュート解除"
|
||||
block: "ブロック"
|
||||
unblock: "ブロック解除"
|
||||
years-old: "{age}歳"
|
||||
mobile/views/pages/user/home.vue:
|
||||
recent-notes: "最近の投稿"
|
||||
images: "画像"
|
||||
|
17
package.json
17
package.json
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "10.46.2",
|
||||
"clientVersion": "1.0.11698",
|
||||
"version": "10.49.0",
|
||||
"clientVersion": "2.0.11740",
|
||||
"codename": "nighthike",
|
||||
"main": "./built/index.js",
|
||||
"private": true,
|
||||
@ -60,7 +60,7 @@
|
||||
"@types/minio": "7.0.1",
|
||||
"@types/mkdirp": "0.5.2",
|
||||
"@types/mocha": "5.2.5",
|
||||
"@types/mongodb": "3.1.12",
|
||||
"@types/mongodb": "3.1.14",
|
||||
"@types/ms": "0.7.30",
|
||||
"@types/node": "10.12.2",
|
||||
"@types/oauth": "0.9.1",
|
||||
@ -75,13 +75,12 @@
|
||||
"@types/seedrandom": "2.4.27",
|
||||
"@types/sharp": "0.21.0",
|
||||
"@types/showdown": "1.7.5",
|
||||
"@types/single-line-log": "1.1.0",
|
||||
"@types/speakeasy": "2.0.3",
|
||||
"@types/systeminformation": "3.23.0",
|
||||
"@types/tinycolor2": "1.4.1",
|
||||
"@types/tmp": "0.0.33",
|
||||
"@types/uuid": "3.4.4",
|
||||
"@types/webpack": "4.4.17",
|
||||
"@types/webpack": "4.4.18",
|
||||
"@types/webpack-stream": "3.2.10",
|
||||
"@types/websocket": "0.0.40",
|
||||
"@types/ws": "6.0.1",
|
||||
@ -100,6 +99,7 @@
|
||||
"commander": "2.19.0",
|
||||
"crc-32": "1.2.0",
|
||||
"css-loader": "1.0.1",
|
||||
"cssnano": "4.1.7",
|
||||
"dateformat": "3.0.3",
|
||||
"debug": "4.1.0",
|
||||
"deep-equal": "1.0.1",
|
||||
@ -129,6 +129,7 @@
|
||||
"gulp-typescript": "4.0.2",
|
||||
"gulp-uglify": "3.0.1",
|
||||
"gulp-util": "3.0.8",
|
||||
"gulp-yaml": "2.0.2",
|
||||
"hard-source-webpack-plugin": "0.12.0",
|
||||
"html-minifier": "3.5.21",
|
||||
"http-signature": "1.2.0",
|
||||
@ -152,12 +153,11 @@
|
||||
"koa-slow": "2.1.0",
|
||||
"koa-views": "6.1.4",
|
||||
"loader-utils": "1.1.0",
|
||||
"mecab-async": "0.1.2",
|
||||
"merge-options": "1.0.1",
|
||||
"minio": "7.0.1",
|
||||
"mkdirp": "0.5.1",
|
||||
"mocha": "5.2.0",
|
||||
"moji": "0.5.1",
|
||||
"moment": "2.22.2",
|
||||
"mongodb": "3.1.8",
|
||||
"monk": "6.0.6",
|
||||
"ms": "2.1.1",
|
||||
@ -169,6 +169,7 @@
|
||||
"os-utils": "0.0.14",
|
||||
"parse5": "5.1.0",
|
||||
"portscanner": "2.2.0",
|
||||
"postcss-loader": "3.0.0",
|
||||
"progress-bar-webpack-plugin": "1.11.0",
|
||||
"promise-limit": "2.7.0",
|
||||
"promise-sequential": "1.1.1",
|
||||
@ -185,12 +186,10 @@
|
||||
"rimraf": "2.6.2",
|
||||
"rndstr": "1.0.0",
|
||||
"s-age": "1.1.2",
|
||||
"sass-loader": "7.1.0",
|
||||
"seedrandom": "2.4.4",
|
||||
"sharp": "0.21.0",
|
||||
"showdown": "1.8.7",
|
||||
"showdown-highlightjs-extension": "0.1.2",
|
||||
"single-line-log": "1.1.2",
|
||||
"speakeasy": "2.0.0",
|
||||
"stringz": "1.0.0",
|
||||
"style-loader": "0.23.1",
|
||||
|
@ -2,12 +2,15 @@
|
||||
* チャートエンジン
|
||||
*/
|
||||
|
||||
import * as moment from 'moment';
|
||||
const nestedProperty = require('nested-property');
|
||||
import autobind from 'autobind-decorator';
|
||||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
import { ICollection } from 'monk';
|
||||
|
||||
const utc = moment.utc;
|
||||
|
||||
export type Obj = { [key: string]: any };
|
||||
|
||||
export type Partial<T> = {
|
||||
@ -87,12 +90,12 @@ export default abstract class Chart<T> {
|
||||
|
||||
@autobind
|
||||
private getCurrentDate(): [number, number, number, number] {
|
||||
const now = new Date();
|
||||
const now = moment().utc();
|
||||
|
||||
const y = now.getFullYear();
|
||||
const m = now.getMonth();
|
||||
const d = now.getDate();
|
||||
const h = now.getHours();
|
||||
const y = now.year();
|
||||
const m = now.month();
|
||||
const d = now.date();
|
||||
const h = now.hour();
|
||||
|
||||
return [y, m, d, h];
|
||||
}
|
||||
@ -114,15 +117,15 @@ export default abstract class Chart<T> {
|
||||
const [y, m, d, h] = this.getCurrentDate();
|
||||
|
||||
const current =
|
||||
span == 'day' ? new Date(y, m, d) :
|
||||
span == 'hour' ? new Date(y, m, d, h) :
|
||||
span == 'day' ? utc([y, m, d]) :
|
||||
span == 'hour' ? utc([y, m, d, h]) :
|
||||
null;
|
||||
|
||||
// 現在(今日または今のHour)のログ
|
||||
const currentLog = await this.collection.findOne({
|
||||
group: group,
|
||||
span: span,
|
||||
date: current
|
||||
date: current.toDate()
|
||||
});
|
||||
|
||||
// ログがあればそれを返して終了
|
||||
@ -158,7 +161,7 @@ export default abstract class Chart<T> {
|
||||
log = await this.collection.insert({
|
||||
group: group,
|
||||
span: span,
|
||||
date: current,
|
||||
date: current.toDate(),
|
||||
data: data
|
||||
});
|
||||
} catch (e) {
|
||||
@ -225,8 +228,8 @@ export default abstract class Chart<T> {
|
||||
const [y, m, d, h] = this.getCurrentDate();
|
||||
|
||||
const gt =
|
||||
span == 'day' ? new Date(y, m, d - range) :
|
||||
span == 'hour' ? new Date(y, m, d, h - range) :
|
||||
span == 'day' ? utc([y, m, d]).subtract(range, 'days') :
|
||||
span == 'hour' ? utc([y, m, d, h]).subtract(range, 'hours') :
|
||||
null;
|
||||
|
||||
// ログ取得
|
||||
@ -234,7 +237,7 @@ export default abstract class Chart<T> {
|
||||
group: group,
|
||||
span: span,
|
||||
date: {
|
||||
$gt: gt
|
||||
$gte: gt.toDate()
|
||||
}
|
||||
}, {
|
||||
sort: {
|
||||
@ -264,22 +267,45 @@ export default abstract class Chart<T> {
|
||||
if (recentLog) {
|
||||
logs = [recentLog];
|
||||
}
|
||||
|
||||
// 要求された範囲の最も古い箇所に位置するログが存在しなかったら
|
||||
} else if (!utc(logs[logs.length - 1].date).isSame(gt)) {
|
||||
// 要求された範囲の最も古い箇所時点での最も新しいログを持ってきて末尾に追加する
|
||||
// (隙間埋めできないため)
|
||||
const outdatedLog = await this.collection.findOne({
|
||||
group: group,
|
||||
span: span,
|
||||
date: {
|
||||
$lt: gt.toDate()
|
||||
}
|
||||
}, {
|
||||
sort: {
|
||||
date: -1
|
||||
},
|
||||
fields: {
|
||||
_id: 0
|
||||
}
|
||||
});
|
||||
|
||||
if (outdatedLog) {
|
||||
logs.push(outdatedLog);
|
||||
}
|
||||
}
|
||||
|
||||
// 整形
|
||||
for (let i = (range - 1); i >= 0; i--) {
|
||||
const current =
|
||||
span == 'day' ? new Date(y, m, d - i) :
|
||||
span == 'hour' ? new Date(y, m, d, h - i) :
|
||||
span == 'day' ? utc([y, m, d]).subtract(i, 'days') :
|
||||
span == 'hour' ? utc([y, m, d, h]).subtract(i, 'hours') :
|
||||
null;
|
||||
|
||||
const log = logs.find(l => l.date.getTime() == current.getTime());
|
||||
const log = logs.find(l => utc(l.date).isSame(current));
|
||||
|
||||
if (log) {
|
||||
promisedChart.unshift(Promise.resolve(log.data));
|
||||
} else {
|
||||
// 隙間埋め
|
||||
const latest = logs.find(l => l.date.getTime() < current.getTime());
|
||||
const latest = logs.find(l => utc(l.date).isBefore(current));
|
||||
promisedChart.unshift(this.getTemplate(false, latest ? latest.data : null));
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
<ui-card>
|
||||
<div slot="title"><fa icon="cog"/> {{ $t('instance') }}</div>
|
||||
<section class="fit-top fit-bottom">
|
||||
<ui-input :value="host" readonly>{{ $t('host') }}</ui-input>
|
||||
<ui-input v-model="name">{{ $t('instance-name') }}</ui-input>
|
||||
<ui-textarea v-model="description">{{ $t('instance-description') }}</ui-textarea>
|
||||
<ui-input v-model="bannerUrl"><i slot="icon"><fa icon="link"/></i>{{ $t('banner-url') }}</ui-input>
|
||||
@ -81,11 +82,14 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../i18n';
|
||||
import { host } from '../../config';
|
||||
import { toUnicode } from 'punycode';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('admin/views/instance.vue'),
|
||||
data() {
|
||||
return {
|
||||
host: toUnicode(host),
|
||||
maintainerName: null,
|
||||
maintainerEmail: null,
|
||||
disableRegistration: false,
|
||||
|
@ -3,15 +3,9 @@
|
||||
* (ENTRY POINT)
|
||||
*/
|
||||
|
||||
/**
|
||||
* ドメインに基づいて適切なスクリプトを読み込みます。
|
||||
* ユーザーの言語およびモバイル端末か否かも考慮します。
|
||||
* webpackは介さないためrequireやimportは使えません。
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
(async function() {
|
||||
// キャッシュ削除要求があれば従う
|
||||
if (localStorage.getItem('shouldFlush') == 'true') {
|
||||
refresh();
|
||||
@ -67,8 +61,18 @@
|
||||
langs.includes(settings.device.lang)) {
|
||||
lang = settings.device.lang;
|
||||
}
|
||||
|
||||
window.lang = lang;
|
||||
//#endregion
|
||||
|
||||
let locale = localStorage.getItem('locale');
|
||||
if (locale == null) {
|
||||
const locale = await fetch(`/assets/locales/${lang}.json`)
|
||||
.then(response => response.json());
|
||||
|
||||
localStorage.setItem('locale', JSON.stringify(locale));
|
||||
}
|
||||
|
||||
// Detect the user agent
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
const isMobile = /mobile|iphone|ipad|android/.test(ua);
|
||||
@ -106,7 +110,7 @@
|
||||
// Note: 'async' make it possible to load the script asyncly.
|
||||
// 'defer' make it possible to run the script when the dom loaded.
|
||||
const script = document.createElement('script');
|
||||
script.setAttribute('src', `/assets/${app}.${ver}.${lang}.js${salt}`);
|
||||
script.setAttribute('src', `/assets/${app}.${ver}.js${salt}`);
|
||||
script.setAttribute('async', 'true');
|
||||
script.setAttribute('defer', 'true');
|
||||
head.appendChild(script);
|
||||
@ -142,6 +146,8 @@
|
||||
function refresh() {
|
||||
localStorage.setItem('shouldFlush', 'false');
|
||||
|
||||
localStorage.removeItem('locale');
|
||||
|
||||
// Random
|
||||
localStorage.setItem('salt', Math.random().toString().substr(2, 8));
|
||||
|
||||
|
@ -23,8 +23,8 @@ export default async function($root: any, force = false, silent = false) {
|
||||
|
||||
if (!silent) {
|
||||
$root.$dialog({
|
||||
title: '%i18n:common.update-available-title%',
|
||||
text: '%i18n:common.update-available%'.replace('{newer}', newer).replace('{current}', current)
|
||||
title: $root.$t('@.update-available-title'),
|
||||
text: $root.$t('@.update-available', { newer, current })
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,8 @@ export default ($root: any) => {
|
||||
|
||||
function adBlockDetected() {
|
||||
$root.$dialog({
|
||||
title: '%fa:exclamation-triangle%%i18n:common.adblock.detected%',
|
||||
text: '%i18n:common.adblock.warning%',
|
||||
title: $root.$t('@.adblock.detected'),
|
||||
text: $root.$t('@.adblock.warning'),
|
||||
actins: [{
|
||||
text: 'OK'
|
||||
}]
|
||||
|
@ -1,8 +1,10 @@
|
||||
const crypto = require('crypto');
|
||||
// スクリプトサイズがデカい
|
||||
//const crypto = require('crypto');
|
||||
|
||||
export default (data: ArrayBuffer) => {
|
||||
const buf = new Buffer(data);
|
||||
const hash = crypto.createHash("md5");
|
||||
hash.update(buf);
|
||||
return hash.digest("hex");
|
||||
};
|
||||
//const buf = new Buffer(data);
|
||||
//const hash = crypto.createHash("md5");
|
||||
//hash.update(buf);
|
||||
//return hash.digest("hex");
|
||||
return '';
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
import parse from '../../../../mfm/parse';
|
||||
import { sum } from '../../../../prelude/array';
|
||||
import shouldMuteNote from './should-mute-note';
|
||||
import MkNoteMenu from '../views/components/note-menu.vue';
|
||||
import MkReactionPicker from '../views/components/reaction-picker.vue';
|
||||
import Ok from '../views/components/ok.vue';
|
||||
@ -22,7 +23,8 @@ type Opts = {
|
||||
export default (opts: Opts = {}) => ({
|
||||
data() {
|
||||
return {
|
||||
showContent: false
|
||||
showContent: false,
|
||||
hideThisNote: false
|
||||
};
|
||||
},
|
||||
|
||||
@ -86,6 +88,10 @@ export default (opts: Opts = {}) => ({
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.hideThisNote = shouldMuteNote(this.$store.state.i, this.$store.state.settings, this.appearNote);
|
||||
},
|
||||
|
||||
methods: {
|
||||
reply(viaKeyboard = false) {
|
||||
this.$root.$post({
|
||||
|
28
src/client/app/common/scripts/should-mute-note.ts
Normal file
28
src/client/app/common/scripts/should-mute-note.ts
Normal file
@ -0,0 +1,28 @@
|
||||
export default function(me, settings, note) {
|
||||
const isMyNote = note.userId == me.id;
|
||||
const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
||||
|
||||
if (settings.showMyRenotes === false) {
|
||||
if (isMyNote && isPureRenote) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.showRenotedMyNotes === false) {
|
||||
if (isPureRenote && (note.renote.userId == me.id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.showLocalRenotes === false) {
|
||||
if (isPureRenote && (note.renote.user.host == null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMyNote && note.text && settings.mutedWords.some(q => !q.some(word => !note.text.includes(word)))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
@ -8,11 +8,12 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { host } from '../../../config';
|
||||
import { toUnicode } from 'punycode';
|
||||
export default Vue.extend({
|
||||
props: ['user', 'detail'],
|
||||
data() {
|
||||
return {
|
||||
host
|
||||
host: toUnicode(host)
|
||||
};
|
||||
}
|
||||
});
|
||||
|
200
src/client/app/common/views/components/emoji-picker.vue
Normal file
200
src/client/app/common/views/components/emoji-picker.vue
Normal file
@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div class="prlncendiewqqkrevzeruhndoakghvtx">
|
||||
<header>
|
||||
<button v-for="category in categories"
|
||||
:title="category.text"
|
||||
@click="go(category.ref)"
|
||||
:class="{ active: category.isActive }"
|
||||
>
|
||||
<fa :icon="category.icon" fixed-width/>
|
||||
</button>
|
||||
</header>
|
||||
<div class="emojis" ref="emojis" @scroll.passive="onScroll">
|
||||
<section v-for="category in categories" :ref="category.ref">
|
||||
<header><fa :icon="category.icon" fixed-width/> {{ category.text }}</header>
|
||||
<div v-if="category.name">
|
||||
<button v-for="emoji in Object.entries(lib).filter(([k, v]) => v.category === category.name)"
|
||||
:title="emoji[0]"
|
||||
@click="chosen(emoji[1].char)"
|
||||
>
|
||||
<mk-emoji :emoji="emoji[1].char"/>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<button v-for="emoji in customEmojis"
|
||||
:title="emoji.name"
|
||||
@click="chosen(`:${emoji.name}:`)"
|
||||
>
|
||||
<img :src="emoji.url" :alt="emoji.name"/>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { lib } from 'emojilib';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/emoji-picker.vue'),
|
||||
|
||||
data() {
|
||||
return {
|
||||
lib,
|
||||
customEmojis: [],
|
||||
categories: [{
|
||||
ref: 'customEmojiSection',
|
||||
text: this.$t('custom-emoji'),
|
||||
icon: ['fas', 'asterisk'],
|
||||
isActive: true
|
||||
}, {
|
||||
name: 'people',
|
||||
ref: 'peopleSection',
|
||||
text: this.$t('people'),
|
||||
icon: ['far', 'laugh'],
|
||||
isActive: false
|
||||
}, {
|
||||
name: 'animals_and_nature',
|
||||
ref: 'animalsAndNatureSection',
|
||||
text: this.$t('animals-and-nature'),
|
||||
icon: ['fas', 'leaf'],
|
||||
isActive: false
|
||||
}, {
|
||||
name: 'food_and_drink',
|
||||
ref: 'foodAndDrinkSection',
|
||||
text: this.$t('food-and-drink'),
|
||||
icon: ['fas', 'utensils'],
|
||||
isActive: false
|
||||
}, {
|
||||
name: 'activity',
|
||||
ref: 'activitySection',
|
||||
text: this.$t('activity'),
|
||||
icon: ['fas', 'futbol'],
|
||||
isActive: false
|
||||
}, {
|
||||
name: 'travel_and_places',
|
||||
ref: 'travelAndPlacesSection',
|
||||
text: this.$t('travel-and-places'),
|
||||
icon: ['fas', 'city'],
|
||||
isActive: false
|
||||
}, {
|
||||
name: 'objects',
|
||||
ref: 'objectsSection',
|
||||
text: this.$t('objects'),
|
||||
icon: ['fas', 'poo-storm'],
|
||||
isActive: false
|
||||
}, {
|
||||
name: 'symbols',
|
||||
ref: 'symbolsSection',
|
||||
text: this.$t('symbols'),
|
||||
icon: ['far', 'heart'],
|
||||
isActive: false
|
||||
}, {
|
||||
name: 'flags',
|
||||
ref: 'flagsSection',
|
||||
text: this.$t('flags'),
|
||||
icon: ['far', 'flag'],
|
||||
isActive: false
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.customEmojis = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
|
||||
},
|
||||
|
||||
methods: {
|
||||
go(ref) {
|
||||
this.$refs.emojis.scrollTop = this.$refs[ref][0].offsetTop;
|
||||
},
|
||||
|
||||
onScroll(e) {
|
||||
const section = this.categories.forEach(x => {
|
||||
const top = e.target.scrollTop;
|
||||
const el = this.$refs[x.ref][0];
|
||||
x.isActive = el.offsetTop <= top && el.offsetTop + el.offsetHeight > top;
|
||||
});
|
||||
},
|
||||
|
||||
chosen(emoji) {
|
||||
this.$emit('chosen', emoji);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.prlncendiewqqkrevzeruhndoakghvtx
|
||||
width 350px
|
||||
background var(--face)
|
||||
|
||||
> header
|
||||
display flex
|
||||
|
||||
> button
|
||||
flex 1
|
||||
padding 10px 0
|
||||
font-size 16px
|
||||
color var(--text)
|
||||
transition color 0.2s ease
|
||||
|
||||
&:hover
|
||||
color var(--textHighlighted)
|
||||
transition color 0s
|
||||
|
||||
&.active
|
||||
color var(--primary)
|
||||
transition color 0s
|
||||
|
||||
> .emojis
|
||||
height 300px
|
||||
overflow-y auto
|
||||
overflow-x hidden
|
||||
|
||||
> section
|
||||
> header
|
||||
position sticky
|
||||
top 0
|
||||
left 0
|
||||
z-index 1
|
||||
padding 8px
|
||||
background var(--faceHeader)
|
||||
color var(--text)
|
||||
font-size 12px
|
||||
|
||||
> div
|
||||
display grid
|
||||
grid-template-columns 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr
|
||||
gap 4px
|
||||
padding 8px
|
||||
|
||||
> button
|
||||
padding 0
|
||||
width 100%
|
||||
|
||||
&:before
|
||||
content ''
|
||||
display block
|
||||
width 1px
|
||||
height 0
|
||||
padding-bottom 100%
|
||||
|
||||
&:hover
|
||||
> *
|
||||
transform scale(1.2)
|
||||
transition transform 0s
|
||||
|
||||
> *
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
font-size 28px
|
||||
transition transform 0.2s ease
|
||||
pointer-events none
|
||||
|
||||
</style>
|
@ -7,7 +7,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { lib } from 'emojilib';
|
||||
// スクリプトサイズがデカい
|
||||
//import { lib } from 'emojilib';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
@ -21,7 +22,7 @@ export default Vue.extend({
|
||||
},
|
||||
customEmojis: {
|
||||
required: false,
|
||||
default: []
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
|
||||
@ -50,10 +51,10 @@ export default Vue.extend({
|
||||
this.customEmoji = customEmoji;
|
||||
this.url = customEmoji.url;
|
||||
} else {
|
||||
const emoji = lib[this.name];
|
||||
if (emoji) {
|
||||
this.char = emoji.char;
|
||||
}
|
||||
//const emoji = lib[this.name];
|
||||
//if (emoji) {
|
||||
// this.char = emoji.char;
|
||||
//}
|
||||
}
|
||||
} else {
|
||||
this.char = this.emoji;
|
||||
|
@ -32,8 +32,6 @@ import mediaList from './media-list.vue';
|
||||
import uploader from './uploader.vue';
|
||||
import streamIndicator from './stream-indicator.vue';
|
||||
import ellipsis from './ellipsis.vue';
|
||||
import messaging from './messaging.vue';
|
||||
import messagingRoom from './messaging-room.vue';
|
||||
import urlPreview from './url-preview.vue';
|
||||
import twitterSetting from './twitter-setting.vue';
|
||||
import githubSetting from './github-setting.vue';
|
||||
@ -85,8 +83,6 @@ Vue.component('mk-media-list', mediaList);
|
||||
Vue.component('mk-uploader', uploader);
|
||||
Vue.component('mk-stream-indicator', streamIndicator);
|
||||
Vue.component('mk-ellipsis', ellipsis);
|
||||
Vue.component('mk-messaging', messaging);
|
||||
Vue.component('mk-messaging-room', messagingRoom);
|
||||
Vue.component('mk-url-preview', urlPreview);
|
||||
Vue.component('mk-twitter-setting', twitterSetting);
|
||||
Vue.component('mk-github-setting', githubSetting);
|
||||
|
@ -21,6 +21,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header>{{ $t('word-mute') }}</header>
|
||||
<ui-textarea v-model="mutedWords">
|
||||
{{ $t('muted-words') }}<span slot="desc">{{ $t('muted-words-description') }}</span>
|
||||
</ui-textarea>
|
||||
<ui-button @click="save">{{ $t('save') }}</ui-button>
|
||||
</section>
|
||||
</ui-card>
|
||||
</template>
|
||||
|
||||
@ -30,16 +38,27 @@ import i18n from '../../../i18n';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/mute-and-block.vue'),
|
||||
|
||||
data() {
|
||||
return {
|
||||
muteFetching: true,
|
||||
blockFetching: true,
|
||||
mute: [],
|
||||
block: []
|
||||
block: [],
|
||||
mutedWords: ''
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
_mutedWords: {
|
||||
get() { return this.$store.state.settings.mutedWords; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'mutedWords', value }); }
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.mutedWords = this._mutedWords.map(words => words.join(' ')).join('\n');
|
||||
|
||||
this.$root.api('mute/list').then(mute => {
|
||||
this.mute = mute.map(x => x.mutee);
|
||||
this.muteFetching = false;
|
||||
@ -49,6 +68,12 @@ export default Vue.extend({
|
||||
this.block = blocking.map(x => x.blockee);
|
||||
this.blockFetching = false;
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
save() {
|
||||
this._mutedWords = this.mutedWords.split('\n').map(line => line.split(' '));
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -73,12 +73,13 @@
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { apiUrl, host } from '../../../config';
|
||||
import { toUnicode } from 'punycode';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/profile-editor.vue'),
|
||||
data() {
|
||||
return {
|
||||
host,
|
||||
host: toUnicode(host),
|
||||
name: null,
|
||||
username: null,
|
||||
location: null,
|
||||
|
@ -21,6 +21,7 @@
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { apiUrl, host } from '../../../config';
|
||||
import { toUnicode } from 'punycode';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/signin.vue'),
|
||||
@ -39,7 +40,7 @@ export default Vue.extend({
|
||||
password: '',
|
||||
token: '',
|
||||
apiUrl,
|
||||
host
|
||||
host: toUnicode(host)
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -46,12 +46,13 @@ import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
const getPasswordStrength = require('syuilo-password-strength');
|
||||
import { host, url } from '../../../config';
|
||||
import { toUnicode } from 'punycode';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/signup.vue'),
|
||||
data() {
|
||||
return {
|
||||
host,
|
||||
host: toUnicode(host),
|
||||
username: '',
|
||||
password: '',
|
||||
retypedPassword: '',
|
||||
|
@ -141,6 +141,7 @@ root(fill)
|
||||
> .desc
|
||||
margin 6px 0
|
||||
font-size 13px
|
||||
opacity 0.7
|
||||
|
||||
*
|
||||
margin 0
|
||||
|
@ -1,5 +1,4 @@
|
||||
import * as getCaretCoordinates from 'textarea-caret';
|
||||
import MkAutocomplete from '../components/autocomplete.vue';
|
||||
import { toASCII } from 'punycode';
|
||||
|
||||
export default {
|
||||
@ -123,7 +122,7 @@ class Autocomplete {
|
||||
/**
|
||||
* サジェストを提示します。
|
||||
*/
|
||||
private open(type, q) {
|
||||
private async open(type, q) {
|
||||
if (type != this.currentType) {
|
||||
this.close();
|
||||
}
|
||||
@ -143,6 +142,8 @@ class Autocomplete {
|
||||
this.suggestion.y = y;
|
||||
this.suggestion.q = q;
|
||||
} else {
|
||||
const MkAutocomplete = await import('../components/autocomplete.vue').then(m => m.default);
|
||||
|
||||
// サジェスト要素作成
|
||||
this.suggestion = new MkAutocomplete({
|
||||
parent: this.vm,
|
||||
|
@ -1,6 +1,4 @@
|
||||
declare const _LANG_: string;
|
||||
declare const _LANGS_: string;
|
||||
declare const _LOCALE_: { [key: string]: any };
|
||||
declare const _LANGS_: string[];
|
||||
declare const _THEME_COLOR_: string;
|
||||
declare const _COPYRIGHT_: string;
|
||||
declare const _VERSION_: string;
|
||||
@ -15,9 +13,9 @@ export const hostname = address.hostname;
|
||||
export const url = address.origin;
|
||||
export const apiUrl = url + '/api';
|
||||
export const wsUrl = url.replace('http://', 'ws://').replace('https://', 'wss://') + '/streaming';
|
||||
export const lang = _LANG_;
|
||||
export const lang = window.lang;
|
||||
export const langs = _LANGS_;
|
||||
export const locale = _LOCALE_;
|
||||
export const locale = JSON.parse(localStorage.getItem('locale'));
|
||||
export const themeColor = _THEME_COLOR_;
|
||||
export const copyright = _COPYRIGHT_;
|
||||
export const version = _VERSION_;
|
||||
|
@ -5,7 +5,7 @@
|
||||
<span :class="$style.count" v-if="multiple && files.length > 0">({{ $t('chosen-files', { count: files.length }) }})</span>
|
||||
</span>
|
||||
|
||||
<mk-drive
|
||||
<x-drive
|
||||
ref="browser"
|
||||
:class="$style.browser"
|
||||
:multiple="multiple"
|
||||
@ -25,6 +25,9 @@ import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/choose-file-from-drive-window.vue'),
|
||||
components: {
|
||||
XDrive: () => import('./drive.vue').then(m => m.default),
|
||||
},
|
||||
props: {
|
||||
multiple: {
|
||||
default: false
|
||||
|
@ -4,7 +4,7 @@
|
||||
<span :class="$style.title">{{ $t('choose-prompt') }}</span>
|
||||
</span>
|
||||
|
||||
<mk-drive
|
||||
<x-drive
|
||||
ref="browser"
|
||||
:class="$style.browser"
|
||||
:multiple="false"
|
||||
@ -21,6 +21,9 @@ import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/choose-folder-from-drive-window.vue'),
|
||||
components: {
|
||||
XDrive: () => import('./drive.vue').then(m => m.default),
|
||||
},
|
||||
methods: {
|
||||
ok() {
|
||||
this.$emit('selected', (this.$refs.browser as any).folder);
|
||||
|
@ -4,7 +4,7 @@
|
||||
<p v-if="usage" :class="$style.info"><b>{{ usage.toFixed(1) }}%</b> {{ $t('used') }}</p>
|
||||
<span :class="$style.title"><fa icon="cloud"/>{{ $t('@.drive') }}</span>
|
||||
</template>
|
||||
<mk-drive :class="$style.browser" multiple :init-folder="folder" ref="browser"/>
|
||||
<x-drive :class="$style.browser" multiple :init-folder="folder" ref="browser"/>
|
||||
</mk-window>
|
||||
</template>
|
||||
|
||||
@ -15,6 +15,9 @@ import { url } from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/drive-window.vue'),
|
||||
components: {
|
||||
XDrive: () => import('./drive.vue').then(m => m.default),
|
||||
},
|
||||
props: ['folder'],
|
||||
data() {
|
||||
return {
|
||||
|
@ -322,7 +322,7 @@ export default Vue.extend({
|
||||
});
|
||||
break;
|
||||
default:
|
||||
alert(`%i18n:@unhandled-error% ${err}`);
|
||||
alert(this.$t('unhandled-error'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div class="gcafiosrssbtbnbzqupfmglvzgiaipyv">
|
||||
<x-picker @chosen="chosen"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import contains from '../../../common/scripts/contains';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XPicker: () => import('../../../common/views/components/emoji-picker.vue').then(m => m.default)
|
||||
},
|
||||
|
||||
props: {
|
||||
x: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
y: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
const width = this.$el.offsetWidth;
|
||||
const height = this.$el.offsetHeight;
|
||||
|
||||
let x = this.x;
|
||||
let y = this.y;
|
||||
|
||||
if (x + width - window.pageXOffset > window.innerWidth) {
|
||||
x = window.innerWidth - width + window.pageXOffset;
|
||||
}
|
||||
|
||||
if (y + height - window.pageYOffset > window.innerHeight) {
|
||||
y = window.innerHeight - height + window.pageYOffset;
|
||||
}
|
||||
|
||||
this.$el.style.left = x + 'px';
|
||||
this.$el.style.top = y + 'px';
|
||||
|
||||
Array.from(document.querySelectorAll('body *')).forEach(el => {
|
||||
el.addEventListener('mousedown', this.onMousedown);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
onMousedown(e) {
|
||||
e.preventDefault();
|
||||
if (!contains(this.$el, e.target) && (this.$el != e.target)) this.close();
|
||||
return false;
|
||||
},
|
||||
|
||||
chosen(emoji) {
|
||||
this.$emit('chosen', emoji);
|
||||
this.close();
|
||||
},
|
||||
|
||||
close() {
|
||||
Array.from(document.querySelectorAll('body *')).forEach(el => {
|
||||
el.removeEventListener('mousedown', this.onMousedown);
|
||||
});
|
||||
|
||||
this.$emit('closed');
|
||||
this.destroyDom();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.gcafiosrssbtbnbzqupfmglvzgiaipyv
|
||||
position fixed
|
||||
top 0
|
||||
left 0
|
||||
z-index 3000
|
||||
box-shadow 0 2px 12px 0 rgba(0, 0, 0, 0.3)
|
||||
|
||||
</style>
|
@ -13,7 +13,7 @@ import { url } from '../../../config';
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/game-window.vue'),
|
||||
components: {
|
||||
XReversi: () => import('../../../common/views/components/games/reversi/reversi.vue')
|
||||
XReversi: () => import('../../../common/views/components/games/reversi/reversi.vue').then(m => m.default)
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -16,7 +16,6 @@ import noteForm from './post-form.vue';
|
||||
import renoteForm from './renote-form.vue';
|
||||
import followButton from './follow-button.vue';
|
||||
import notePreview from './note-preview.vue';
|
||||
import drive from './drive.vue';
|
||||
import noteDetail from './note-detail.vue';
|
||||
import settings from './settings.vue';
|
||||
import calendar from './calendar.vue';
|
||||
@ -42,7 +41,6 @@ Vue.component('mk-post-form', noteForm);
|
||||
Vue.component('mk-renote-form', renoteForm);
|
||||
Vue.component('mk-follow-button', followButton);
|
||||
Vue.component('mk-note-preview', notePreview);
|
||||
Vue.component('mk-drive', drive);
|
||||
Vue.component('mk-note-detail', noteDetail);
|
||||
Vue.component('mk-settings', settings);
|
||||
Vue.component('mk-calendar', calendar);
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
|
||||
<span slot="header" :class="$style.header"><fa icon="comments"/>{{ $t('title') }} {{ user | userName }}</span>
|
||||
<mk-messaging-room :user="user" :class="$style.content"/>
|
||||
<x-messaging-room :user="user" :class="$style.content"/>
|
||||
</mk-window>
|
||||
</template>
|
||||
|
||||
@ -13,6 +13,9 @@ import getAcct from '../../../../../misc/acct/render';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/messaging-room-window.vue'),
|
||||
components: {
|
||||
XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
|
||||
},
|
||||
props: ['user'],
|
||||
computed: {
|
||||
popout(): string {
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<mk-window ref="window" width="500px" height="560px" @closed="destroyDom">
|
||||
<span slot="header" :class="$style.header"><fa icon="comments"/>{{ $t('title') }}</span>
|
||||
<mk-messaging :class="$style.content" @navigate="navigate"/>
|
||||
<x-messaging :class="$style.content" @navigate="navigate"/>
|
||||
</mk-window>
|
||||
</template>
|
||||
|
||||
@ -12,6 +12,9 @@ import MkMessagingRoomWindow from './messaging-room-window.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/components/messaging-window.vue'),
|
||||
components: {
|
||||
XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
|
||||
},
|
||||
methods: {
|
||||
navigate(user) {
|
||||
this.$root.new(MkMessagingRoomWindow, {
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div
|
||||
class="note"
|
||||
:class="{ mini }"
|
||||
v-show="appearNote.deletedAt == null"
|
||||
v-show="appearNote.deletedAt == null && !hideThisNote"
|
||||
:tabindex="appearNote.deletedAt == null ? '-1' : null"
|
||||
v-hotkey="keymap"
|
||||
:title="title"
|
||||
|
@ -36,7 +36,7 @@
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import * as config from '../../../config';
|
||||
|
||||
import shouldMuteNote from '../../../common/scripts/should-mute-note';
|
||||
import XNote from './note.vue';
|
||||
|
||||
const displayLimit = 30;
|
||||
@ -119,28 +119,8 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
prepend(note, silent = false) {
|
||||
//#region 弾く
|
||||
const isMyNote = note.userId == this.$store.state.i.id;
|
||||
const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
||||
|
||||
if (this.$store.state.settings.showMyRenotes === false) {
|
||||
if (isMyNote && isPureRenote) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.$store.state.settings.showRenotedMyNotes === false) {
|
||||
if (isPureRenote && (note.renote.userId == this.$store.state.i.id)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.$store.state.settings.showLocalRenotes === false) {
|
||||
if (isPureRenote && (note.renote.user.host == null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
// 弾く
|
||||
if (shouldMuteNote(this.$store.state.i, this.$store.state.settings, note)) return;
|
||||
|
||||
// タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
|
||||
if (document.hidden || !this.isScrollTop()) {
|
||||
|
@ -15,21 +15,26 @@
|
||||
<a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" :title="$t('click-to-tagging')">#{{ tag }}</a>
|
||||
</div>
|
||||
<input v-show="useCw" v-model="cw" :placeholder="$t('annotations')">
|
||||
<textarea :class="{ with: (files.length != 0 || poll) }"
|
||||
ref="text" v-model="text" :disabled="posting"
|
||||
@keydown="onKeydown" @paste="onPaste" :placeholder="placeholder"
|
||||
v-autocomplete="'text'"
|
||||
></textarea>
|
||||
<div class="files" :class="{ with: poll }" v-show="files.length != 0">
|
||||
<x-draggable :list="files" :options="{ animation: 150 }">
|
||||
<div v-for="file in files" :key="file.id">
|
||||
<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div>
|
||||
<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" :title="$t('attach-cancel')" alt=""/>
|
||||
</div>
|
||||
</x-draggable>
|
||||
<p class="remain">{{ 4 - files.length }}/4</p>
|
||||
<div class="textarea">
|
||||
<textarea :class="{ with: (files.length != 0 || poll) }"
|
||||
ref="text" v-model="text" :disabled="posting"
|
||||
@keydown="onKeydown" @paste="onPaste" :placeholder="placeholder"
|
||||
v-autocomplete="'text'"
|
||||
></textarea>
|
||||
<button class="emoji" @click="emoji" ref="emoji">
|
||||
<fa :icon="['far', 'laugh']"/>
|
||||
</button>
|
||||
<div class="files" :class="{ with: poll }" v-show="files.length != 0">
|
||||
<x-draggable :list="files" :options="{ animation: 150 }">
|
||||
<div v-for="file in files" :key="file.id">
|
||||
<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div>
|
||||
<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" :title="$t('attach-cancel')" alt=""/>
|
||||
</div>
|
||||
</x-draggable>
|
||||
<p class="remain">{{ 4 - files.length }}/4</p>
|
||||
</div>
|
||||
<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="saveDraft()"/>
|
||||
</div>
|
||||
<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="saveDraft()"/>
|
||||
</div>
|
||||
<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
|
||||
<button class="upload" :title="$t('attach-media-from-local')" @click="chooseFile"><fa icon="upload"/></button>
|
||||
@ -377,6 +382,19 @@ export default Vue.extend({
|
||||
this.visibleUsers = erase(user, this.visibleUsers);
|
||||
},
|
||||
|
||||
async emoji() {
|
||||
const Picker = await import('./emoji-picker-dialog.vue').then(m => m.default);
|
||||
const button = this.$refs.emoji;
|
||||
const rect = button.getBoundingClientRect();
|
||||
const vm = this.$root.new(Picker, {
|
||||
x: button.offsetWidth + rect.left + window.pageXOffset,
|
||||
y: rect.top + window.pageYOffset
|
||||
});
|
||||
vm.$once('chosen', emoji => {
|
||||
insertTextAtCursor(this.$refs.text, emoji);
|
||||
});
|
||||
},
|
||||
|
||||
post() {
|
||||
this.posting = true;
|
||||
|
||||
@ -469,7 +487,7 @@ export default Vue.extend({
|
||||
|
||||
> .content
|
||||
> input
|
||||
> textarea
|
||||
> .textarea > textarea
|
||||
display block
|
||||
width 100%
|
||||
padding 12px
|
||||
@ -498,27 +516,108 @@ export default Vue.extend({
|
||||
> input
|
||||
margin-bottom 8px
|
||||
|
||||
> textarea
|
||||
margin 0
|
||||
max-width 100%
|
||||
min-width 100%
|
||||
min-height 84px
|
||||
> .textarea
|
||||
> .emoji
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
padding 10px
|
||||
font-size 18px
|
||||
color var(--text)
|
||||
opacity 0.5
|
||||
|
||||
&:hover
|
||||
& + *
|
||||
& + * + *
|
||||
border-color var(--primaryAlpha02)
|
||||
transition border-color .1s ease
|
||||
&:hover
|
||||
color var(--textHighlighted)
|
||||
opacity 1
|
||||
|
||||
&:focus
|
||||
& + *
|
||||
& + * + *
|
||||
border-color var(--primaryAlpha05)
|
||||
transition border-color 0s ease
|
||||
&:active
|
||||
color var(--primary)
|
||||
opacity 1
|
||||
|
||||
&.with
|
||||
border-bottom solid 1px var(--primaryAlpha01) !important
|
||||
border-radius 4px 4px 0 0
|
||||
> textarea
|
||||
margin 0
|
||||
max-width 100%
|
||||
min-width 100%
|
||||
min-height 84px
|
||||
|
||||
&:hover
|
||||
& + * + *
|
||||
& + * + * + *
|
||||
border-color var(--primaryAlpha02)
|
||||
transition border-color .1s ease
|
||||
|
||||
&:focus
|
||||
& + * + *
|
||||
& + * + * + *
|
||||
border-color var(--primaryAlpha05)
|
||||
transition border-color 0s ease
|
||||
|
||||
& + .emoji
|
||||
opacity 0.7
|
||||
|
||||
&.with
|
||||
border-bottom solid 1px var(--primaryAlpha01) !important
|
||||
border-radius 4px 4px 0 0
|
||||
|
||||
> .files
|
||||
margin 0
|
||||
padding 0
|
||||
background var(--desktopPostFormTextareaBg)
|
||||
border solid 1px var(--primaryAlpha01)
|
||||
border-top none
|
||||
border-radius 0 0 4px 4px
|
||||
transition border-color .3s ease
|
||||
|
||||
&.with
|
||||
border-bottom solid 1px var(--primaryAlpha01) !important
|
||||
border-radius 0
|
||||
|
||||
> .remain
|
||||
display block
|
||||
position absolute
|
||||
top 8px
|
||||
right 8px
|
||||
margin 0
|
||||
padding 0
|
||||
color var(--primaryAlpha04)
|
||||
|
||||
> div
|
||||
padding 4px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
> div
|
||||
float left
|
||||
border solid 4px transparent
|
||||
cursor move
|
||||
|
||||
&:hover > .remove
|
||||
display block
|
||||
|
||||
> .img
|
||||
width 64px
|
||||
height 64px
|
||||
background-size cover
|
||||
background-position center center
|
||||
|
||||
> .remove
|
||||
display none
|
||||
position absolute
|
||||
top -6px
|
||||
right -6px
|
||||
width 16px
|
||||
height 16px
|
||||
cursor pointer
|
||||
|
||||
> .mk-poll-editor
|
||||
background var(--desktopPostFormTextareaBg)
|
||||
border solid 1px var(--primaryAlpha01)
|
||||
border-top none
|
||||
border-radius 0 0 4px 4px
|
||||
transition border-color .3s ease
|
||||
|
||||
> .visibleUsers
|
||||
margin-bottom 8px
|
||||
@ -541,66 +640,6 @@ export default Vue.extend({
|
||||
margin-right 8px
|
||||
white-space nowrap
|
||||
|
||||
> .files
|
||||
margin 0
|
||||
padding 0
|
||||
background var(--desktopPostFormTextareaBg)
|
||||
border solid 1px var(--primaryAlpha01)
|
||||
border-top none
|
||||
border-radius 0 0 4px 4px
|
||||
transition border-color .3s ease
|
||||
|
||||
&.with
|
||||
border-bottom solid 1px var(--primaryAlpha01) !important
|
||||
border-radius 0
|
||||
|
||||
> .remain
|
||||
display block
|
||||
position absolute
|
||||
top 8px
|
||||
right 8px
|
||||
margin 0
|
||||
padding 0
|
||||
color var(--primaryAlpha04)
|
||||
|
||||
> div
|
||||
padding 4px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
> div
|
||||
float left
|
||||
border solid 4px transparent
|
||||
cursor move
|
||||
|
||||
&:hover > .remove
|
||||
display block
|
||||
|
||||
> .img
|
||||
width 64px
|
||||
height 64px
|
||||
background-size cover
|
||||
background-position center center
|
||||
|
||||
> .remove
|
||||
display none
|
||||
position absolute
|
||||
top -6px
|
||||
right -6px
|
||||
width 16px
|
||||
height 16px
|
||||
cursor pointer
|
||||
|
||||
> .mk-poll-editor
|
||||
background var(--desktopPostFormTextareaBg)
|
||||
border solid 1px var(--primaryAlpha01)
|
||||
border-top none
|
||||
border-radius 0 0 4px 4px
|
||||
transition border-color .3s ease
|
||||
|
||||
> .mk-uploader
|
||||
margin 8px 0 0 0
|
||||
padding 8px
|
||||
|
@ -40,6 +40,7 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import shouldMuteNote from '../../../../common/scripts/should-mute-note';
|
||||
|
||||
import XNote from '../../components/note.vue';
|
||||
|
||||
@ -135,28 +136,8 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
prepend(note, silent = false) {
|
||||
//#region 弾く
|
||||
const isMyNote = note.userId == this.$store.state.i.id;
|
||||
const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
||||
|
||||
if (this.$store.state.settings.showMyRenotes === false) {
|
||||
if (isMyNote && isPureRenote) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.$store.state.settings.showRenotedMyNotes === false) {
|
||||
if (isPureRenote && (note.renote.userId == this.$store.state.i.id)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.$store.state.settings.showLocalRenotes === false) {
|
||||
if (isPureRenote && (note.renote.user.host == null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
// 弾く
|
||||
if (shouldMuteNote(this.$store.state.i, this.$store.state.settings, note)) return;
|
||||
|
||||
// タブが非表示ならタイトルで通知
|
||||
if (document.hidden) {
|
||||
|
@ -25,9 +25,6 @@ import i18n from '../../../../i18n';
|
||||
import XColumnCore from './deck.column-core.vue';
|
||||
import Menu from '../../../../common/views/components/menu.vue';
|
||||
import MkUserListsWindow from '../../components/user-lists-window.vue';
|
||||
import XUserColumn from './deck.user-column.vue';
|
||||
import XNoteColumn from './deck.note-column.vue';
|
||||
import XHashtagColumn from './deck.hashtag-column.vue';
|
||||
|
||||
import * as uuid from 'uuid';
|
||||
|
||||
@ -35,9 +32,9 @@ export default Vue.extend({
|
||||
i18n: i18n('deck'),
|
||||
components: {
|
||||
XColumnCore,
|
||||
XUserColumn,
|
||||
XNoteColumn,
|
||||
XHashtagColumn
|
||||
XUserColumn: () => import('./deck.user-column.vue').then(m => m.default),
|
||||
XNoteColumn: () => import('./deck.note-column.vue').then(m => m.default),
|
||||
XHashtagColumn: () => import('./deck.hashtag-column.vue').then(m => m.default)
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="mk-drive-page">
|
||||
<mk-drive :init-folder="folder" @move-root="onMoveRoot" @open-folder="onOpenFolder"/>
|
||||
<x-drive :init-folder="folder" @move-root="onMoveRoot" @open-folder="onOpenFolder"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -10,6 +10,9 @@ import i18n from '../../../i18n';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/pages/drive.vue'),
|
||||
components: {
|
||||
XDrive: () => import('../components/drive.vue').then(m => m.default),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
folder: null
|
||||
|
@ -4,7 +4,9 @@
|
||||
<template v-for="favorite in favorites">
|
||||
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
|
||||
</template>
|
||||
<a v-if="existMore" @click="more">{{ $t('@.load-more') }}</a>
|
||||
<div class="more" v-if="existMore">
|
||||
<ui-button inline @click="more">{{ $t('@.load-more') }}</ui-button>
|
||||
</div>
|
||||
</main>
|
||||
</mk-ui>
|
||||
</template>
|
||||
@ -75,4 +77,9 @@ main
|
||||
|
||||
> .post
|
||||
margin-bottom 16px
|
||||
|
||||
> .more
|
||||
margin 32px 16px 16px 16px
|
||||
text-align center
|
||||
|
||||
</style>
|
||||
|
@ -9,7 +9,7 @@ import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
XReversi: () => import('../../../../common/views/components/games/reversi/reversi.vue')
|
||||
XReversi: () => import('../../../../common/views/components/games/reversi/reversi.vue').then(m => m.default)
|
||||
},
|
||||
props: {
|
||||
ui: {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="mk-messaging-room-page">
|
||||
<mk-messaging-room v-if="user" :user="user" :is-naked="true"/>
|
||||
<x-messaging-room v-if="user" :user="user" :is-naked="true"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -13,6 +13,9 @@ import getUserName from '../../../../../misc/get-user-name';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('.vue'),
|
||||
components: {
|
||||
XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="mkp-selectdrive">
|
||||
<mk-drive ref="browser"
|
||||
<x-drive ref="browser"
|
||||
:multiple="multiple"
|
||||
@selected="onSelected"
|
||||
@change-selection="onChangeSelection"
|
||||
@ -19,6 +19,9 @@ import i18n from '../../../i18n';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/pages/selectdrive.vue'),
|
||||
components: {
|
||||
XDrive: () => import('../components/drive.vue').then(m => m.default),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
files: []
|
||||
|
@ -18,7 +18,7 @@
|
||||
</div>
|
||||
<div class="info">
|
||||
<span class="location" v-if="user.host === null && user.profile.location"><fa icon="map-marker"/> {{ user.profile.location }}</span>
|
||||
<span class="birthday" v-if="user.host === null && user.profile.birthday"><fa icon="birthday-cake"/> {{ user.profile.birthday.replace('-', this.$t('year')).replace('-', this.$t('month')) + this.$t('day') }} ({{ age }}%i18n:@years-old%)</span>
|
||||
<span class="birthday" v-if="user.host === null && user.profile.birthday"><fa icon="birthday-cake"/> {{ user.profile.birthday.replace('-', $t('year')).replace('-', $t('month')) + $t('day') }} ({{ $t('years-old', { age }) }})</span>
|
||||
</div>
|
||||
<div class="status">
|
||||
<span class="notes-count"><b>{{ user.notesCount | number }}</b>{{ $t('posts') }}</span>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<mk-follow-button :user="user" size="big"/>
|
||||
<p class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</p>
|
||||
<p class="stalk" v-if="user.isFollowing">
|
||||
<span v-if="user.isStalking">{{ $t('stalking% <a @click="unstalk"><fa icon="meh"/> %i18n:@unstalk') }}</a></span>
|
||||
<span v-if="user.isStalking">{{ $t('stalking') }} <a @click="unstalk"><fa icon="meh"/> {{ $t('unstalk') }}</a></span>
|
||||
<span v-if="!user.isStalking"><a @click="stalk"><fa icon="user-secret"/> {{ $t('stalk') }}</a></span>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -151,6 +151,7 @@ import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { host, copyright } from '../../../config';
|
||||
import { concat } from '../../../../../prelude/array';
|
||||
import { toUnicode } from 'punycode';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('desktop/views/pages/welcome.vue'),
|
||||
@ -160,7 +161,7 @@ export default Vue.extend({
|
||||
stats: null,
|
||||
banner: null,
|
||||
copyright,
|
||||
host,
|
||||
host: toUnicode(host),
|
||||
name: 'Misskey',
|
||||
description: '',
|
||||
announcements: [],
|
||||
|
@ -4,7 +4,7 @@
|
||||
<template slot="header"><fa icon="comments"/>{{ $t('title') }}</template>
|
||||
<button slot="func" @click="add"><fa icon="plus"/></button>
|
||||
|
||||
<mk-messaging ref="index" compact @navigate="navigate"/>
|
||||
<x-messaging ref="index" compact @navigate="navigate"/>
|
||||
</mk-widget-container>
|
||||
</div>
|
||||
</template>
|
||||
@ -22,6 +22,9 @@ export default define({
|
||||
})
|
||||
}).extend({
|
||||
i18n: i18n('desktop/views/widgets/messaging.vue'),
|
||||
components: {
|
||||
XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
|
||||
},
|
||||
methods: {
|
||||
navigate(user) {
|
||||
this.$root.new(MkMessagingRoomWindow, {
|
||||
|
@ -1,16 +1,48 @@
|
||||
<template>
|
||||
<div class="mkw-post-form">
|
||||
<template v-if="props.design == 0">
|
||||
<p class="title"><fa icon="pencil-alt"/>{{ $t('title') }}</p>
|
||||
</template>
|
||||
<textarea :disabled="posting" v-model="text" @keydown="onKeydown" :placeholder="placeholder"></textarea>
|
||||
<button @click="post" :disabled="posting">{{ $t('note') }}</button>
|
||||
<div>
|
||||
<mk-widget-container :show-header="props.design == 0">
|
||||
<template slot="header"><fa icon="pencil-alt"/>{{ $t('title') }}</template>
|
||||
|
||||
<div class="lhcuptdmcdkfwmipgazeawoiuxpzaclc-body">
|
||||
<div class="textarea">
|
||||
<textarea
|
||||
:disabled="posting"
|
||||
v-model="text"
|
||||
@keydown="onKeydown"
|
||||
@paste="onPaste"
|
||||
:placeholder="placeholder"
|
||||
ref="text"
|
||||
v-autocomplete="'text'"
|
||||
></textarea>
|
||||
<button class="emoji" @click="emoji" ref="emoji">
|
||||
<fa :icon="['far', 'laugh']"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="files" v-show="files.length != 0">
|
||||
<x-draggable :list="files" :options="{ animation: 150 }">
|
||||
<div v-for="file in files" :key="file.id">
|
||||
<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div>
|
||||
<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" :title="$t('attach-cancel')" alt=""/>
|
||||
</div>
|
||||
</x-draggable>
|
||||
</div>
|
||||
<input ref="file" type="file" multiple="multiple" tabindex="-1" @change="onChangeFile"/>
|
||||
<mk-uploader ref="uploader" @uploaded="attachMedia"/>
|
||||
<footer>
|
||||
<button @click="chooseFile"><fa icon="upload"/></button>
|
||||
<button @click="chooseFileFromDrive"><fa icon="cloud"/></button>
|
||||
<button @click="post" :disabled="posting" class="post">{{ $t('note') }}</button>
|
||||
</footer>
|
||||
</div>
|
||||
</mk-widget-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import define from '../../../common/define-widget';
|
||||
import i18n from '../../../i18n';
|
||||
import insertTextAtCursor from 'insert-text-at-cursor';
|
||||
import * as XDraggable from 'vuedraggable';
|
||||
|
||||
export default define({
|
||||
name: 'post-form',
|
||||
@ -19,12 +51,19 @@ export default define({
|
||||
})
|
||||
}).extend({
|
||||
i18n: i18n('desktop/views/widgets/post-form.vue'),
|
||||
|
||||
components: {
|
||||
XDraggable
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
posting: false,
|
||||
text: ''
|
||||
text: '',
|
||||
files: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
placeholder(): string {
|
||||
const xs = [
|
||||
@ -38,6 +77,7 @@ export default define({
|
||||
return xs[Math.floor(Math.random() * xs.length)];
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
func() {
|
||||
if (this.props.design == 1) {
|
||||
@ -47,14 +87,68 @@ export default define({
|
||||
}
|
||||
this.save();
|
||||
},
|
||||
|
||||
chooseFile() {
|
||||
(this.$refs.file as any).click();
|
||||
},
|
||||
|
||||
chooseFileFromDrive() {
|
||||
this.$chooseDriveFile({
|
||||
multiple: true
|
||||
}).then(files => {
|
||||
files.forEach(this.attachMedia);
|
||||
});
|
||||
},
|
||||
|
||||
attachMedia(driveFile) {
|
||||
this.files.push(driveFile);
|
||||
this.$emit('change-attached-files', this.files);
|
||||
},
|
||||
|
||||
detachMedia(id) {
|
||||
this.files = this.files.filter(x => x.id != id);
|
||||
this.$emit('change-attached-files', this.files);
|
||||
},
|
||||
|
||||
onKeydown(e) {
|
||||
if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && !this.posting && this.text) this.post();
|
||||
},
|
||||
|
||||
onPaste(e) {
|
||||
Array.from(e.clipboardData.items).forEach((item: any) => {
|
||||
if (item.kind == 'file') {
|
||||
this.upload(item.getAsFile());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onChangeFile() {
|
||||
Array.from((this.$refs.file as any).files).forEach(this.upload);
|
||||
},
|
||||
|
||||
upload(file) {
|
||||
(this.$refs.uploader as any).upload(file);
|
||||
},
|
||||
|
||||
async emoji() {
|
||||
const Picker = await import('../components/emoji-picker-dialog.vue').then(m => m.default);
|
||||
const button = this.$refs.emoji;
|
||||
const rect = button.getBoundingClientRect();
|
||||
const vm = this.$root.new(Picker, {
|
||||
x: button.offsetWidth + rect.left + window.pageXOffset,
|
||||
y: rect.top + window.pageYOffset
|
||||
});
|
||||
vm.$once('chosen', emoji => {
|
||||
insertTextAtCursor(this.$refs.text, emoji);
|
||||
});
|
||||
},
|
||||
|
||||
post() {
|
||||
this.posting = true;
|
||||
|
||||
this.$root.api('notes/create', {
|
||||
text: this.text
|
||||
text: this.text == '' ? undefined : this.text,
|
||||
fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
|
||||
}).then(data => {
|
||||
this.clear();
|
||||
}).catch(err => {
|
||||
@ -63,66 +157,114 @@ export default define({
|
||||
this.posting = false;
|
||||
});
|
||||
},
|
||||
|
||||
clear() {
|
||||
this.text = '';
|
||||
this.files = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.lhcuptdmcdkfwmipgazeawoiuxpzaclc-body
|
||||
> .textarea
|
||||
> .emoji
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
padding 10px
|
||||
font-size 18px
|
||||
color var(--text)
|
||||
opacity 0.5
|
||||
|
||||
&:hover
|
||||
color var(--textHighlighted)
|
||||
opacity 1
|
||||
|
||||
.mkw-post-form
|
||||
background #fff
|
||||
overflow hidden
|
||||
border solid 1px rgba(#000, 0.075)
|
||||
border-radius 6px
|
||||
&:active
|
||||
color var(--primary)
|
||||
opacity 1
|
||||
|
||||
> .title
|
||||
z-index 1
|
||||
margin 0
|
||||
padding 0 16px
|
||||
line-height 42px
|
||||
font-size 0.9em
|
||||
font-weight bold
|
||||
color #888
|
||||
box-shadow 0 1px rgba(#000, 0.07)
|
||||
> textarea
|
||||
display block
|
||||
width 100%
|
||||
max-width 100%
|
||||
min-width 100%
|
||||
padding 16px
|
||||
color var(--desktopPostFormTextareaFg)
|
||||
outline none
|
||||
background var(--desktopPostFormTextareaBg)
|
||||
border none
|
||||
border-bottom solid 1px var(--faceDivider)
|
||||
|
||||
> [data-icon]
|
||||
margin-right 4px
|
||||
&:focus
|
||||
& + .emoji
|
||||
opacity 0.7
|
||||
|
||||
> textarea
|
||||
display block
|
||||
width 100%
|
||||
max-width 100%
|
||||
min-width 100%
|
||||
padding 16px
|
||||
margin-bottom 28px + 16px
|
||||
border none
|
||||
border-bottom solid 1px #eee
|
||||
> .files
|
||||
> div
|
||||
padding 4px
|
||||
|
||||
> button
|
||||
display block
|
||||
position absolute
|
||||
bottom 8px
|
||||
right 8px
|
||||
margin 0
|
||||
padding 0 10px
|
||||
height 28px
|
||||
color var(--primaryForeground)
|
||||
background var(--primary) !important
|
||||
outline none
|
||||
border none
|
||||
border-radius 4px
|
||||
transition background 0.1s ease
|
||||
cursor pointer
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
&:hover
|
||||
background var(--primaryLighten10) !important
|
||||
> div
|
||||
float left
|
||||
border solid 4px transparent
|
||||
cursor move
|
||||
|
||||
&:active
|
||||
background var(--primaryDarken10) !important
|
||||
transition background 0s ease
|
||||
&:hover > .remove
|
||||
display block
|
||||
|
||||
> .img
|
||||
width 64px
|
||||
height 64px
|
||||
background-size cover
|
||||
background-position center center
|
||||
|
||||
> .remove
|
||||
display none
|
||||
position absolute
|
||||
top -6px
|
||||
right -6px
|
||||
width 16px
|
||||
height 16px
|
||||
cursor pointer
|
||||
|
||||
> input[type=file]
|
||||
display none
|
||||
|
||||
> footer
|
||||
display flex
|
||||
padding 8px
|
||||
|
||||
> button:not(.post)
|
||||
color var(--text)
|
||||
|
||||
&:hover
|
||||
color var(--textHighlighted)
|
||||
|
||||
> .post
|
||||
display block
|
||||
margin 0 0 0 auto
|
||||
padding 0 10px
|
||||
height 28px
|
||||
color var(--primaryForeground)
|
||||
background var(--primary) !important
|
||||
outline none
|
||||
border none
|
||||
border-radius 4px
|
||||
transition background 0.1s ease
|
||||
cursor pointer
|
||||
|
||||
&:hover
|
||||
background var(--primaryLighten10) !important
|
||||
|
||||
&:active
|
||||
background var(--primaryDarken10) !important
|
||||
transition background 0s ease
|
||||
|
||||
</style>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<button class="close" @click="cancel"><fa icon="times"/></button>
|
||||
<button v-if="multiple" class="ok" @click="ok"><fa icon="check"/></button>
|
||||
</header>
|
||||
<mk-drive class="drive" ref="browser"
|
||||
<x-drive class="drive" ref="browser"
|
||||
:select-file="true"
|
||||
:multiple="multiple"
|
||||
@change-selection="onChangeSelection"
|
||||
@ -22,6 +22,9 @@ import i18n from '../../../i18n';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('mobile/views/components/drive-file-chooser.vue'),
|
||||
components: {
|
||||
XDrive: () => import('./drive.vue').then(m => m.default),
|
||||
},
|
||||
props: ['multiple'],
|
||||
data() {
|
||||
return {
|
||||
|
@ -6,7 +6,7 @@
|
||||
<button class="close" @click="cancel"><fa icon="times"/></button>
|
||||
<button class="ok" @click="ok"><fa icon="check"/></button>
|
||||
</header>
|
||||
<mk-drive ref="browser"
|
||||
<x-drive ref="browser"
|
||||
select-folder
|
||||
/>
|
||||
</div>
|
||||
@ -18,6 +18,9 @@ import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
export default Vue.extend({
|
||||
i18n: i18n('mobile/views/components/drive-folder-chooser.vue'),
|
||||
components: {
|
||||
XDrive: () => import('./drive.vue').then(m => m.default),
|
||||
},
|
||||
methods: {
|
||||
cancel() {
|
||||
this.$emit('canceled');
|
||||
|
@ -5,7 +5,6 @@ import note from './note.vue';
|
||||
import notes from './notes.vue';
|
||||
import mediaImage from './media-image.vue';
|
||||
import mediaVideo from './media-video.vue';
|
||||
import drive from './drive.vue';
|
||||
import notePreview from './note-preview.vue';
|
||||
import subNoteContent from './sub-note-content.vue';
|
||||
import noteCard from './note-card.vue';
|
||||
@ -29,7 +28,6 @@ Vue.component('mk-note', note);
|
||||
Vue.component('mk-notes', notes);
|
||||
Vue.component('mk-media-image', mediaImage);
|
||||
Vue.component('mk-media-video', mediaVideo);
|
||||
Vue.component('mk-drive', drive);
|
||||
Vue.component('mk-note-preview', notePreview);
|
||||
Vue.component('mk-sub-note-content', subNoteContent);
|
||||
Vue.component('mk-note-card', noteCard);
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="note"
|
||||
v-show="appearNote.deletedAt == null"
|
||||
v-show="appearNote.deletedAt == null && !hideThisNote"
|
||||
:tabindex="appearNote.deletedAt == null ? '-1' : null"
|
||||
:class="{ renote: isRenote, smart: $store.state.device.postStyle == 'smart' }"
|
||||
v-hotkey="keymap"
|
||||
|
@ -35,6 +35,7 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import shouldMuteNote from '../../../common/scripts/should-mute-note';
|
||||
|
||||
const displayLimit = 30;
|
||||
|
||||
@ -118,28 +119,8 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
prepend(note, silent = false) {
|
||||
//#region 弾く
|
||||
const isMyNote = note.userId == this.$store.state.i.id;
|
||||
const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
||||
|
||||
if (this.$store.state.settings.showMyRenotes === false) {
|
||||
if (isMyNote && isPureRenote) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.$store.state.settings.showRenotedMyNotes === false) {
|
||||
if (isPureRenote && (note.renote.userId == this.$store.state.i.id)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.$store.state.settings.showLocalRenotes === false) {
|
||||
if (isPureRenote && (note.renote.user.host == null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
// 弾く
|
||||
if (shouldMuteNote(this.$store.state.i, this.$store.state.settings, note)) return;
|
||||
|
||||
// タブが非表示またはスクロール位置が最上部ではないならタイトルで通知
|
||||
if (document.hidden || !this.isScrollTop()) {
|
||||
|
@ -6,7 +6,7 @@
|
||||
<template v-if="!folder && !file"><span style="margin-right:4px;"><fa icon="cloud"/></span>{{ $t('@.drive') }}</template>
|
||||
</span>
|
||||
<template slot="func"><button @click="fn"><fa icon="ellipsis-h"/></button></template>
|
||||
<mk-drive
|
||||
<x-drive
|
||||
ref="browser"
|
||||
:init-folder="initFolder"
|
||||
:init-file="initFile"
|
||||
@ -29,6 +29,9 @@ import Progress from '../../../common/scripts/loading';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
components: {
|
||||
XDrive: () => import('../components/drive.vue').then(m => m.default),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
Progress,
|
||||
|
@ -6,7 +6,7 @@
|
||||
<template v-for="favorite in favorites">
|
||||
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
|
||||
</template>
|
||||
<a v-if="existMore" @click="more">{{ $t('@.load-more') }}</a>
|
||||
<ui-button v-if="existMore" @click="more">{{ $t('@.load-more') }}</ui-button>
|
||||
</main>
|
||||
</mk-ui>
|
||||
</template>
|
||||
@ -73,8 +73,6 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
||||
|
||||
main
|
||||
width 100%
|
||||
max-width 680px
|
||||
|
@ -12,7 +12,7 @@ import i18n from '../../../../i18n';
|
||||
export default Vue.extend({
|
||||
i18n: i18n('mobile/views/pages/games/reversi.vue'),
|
||||
components: {
|
||||
XReversi: () => import('../../../../common/views/components/games/reversi/reversi.vue')
|
||||
XReversi: () => import('../../../../common/views/components/games/reversi/reversi.vue').then(m => m.default)
|
||||
},
|
||||
mounted() {
|
||||
document.title = `${this.$root.instanceName} %i18n:@reversi%`;
|
||||
|
@ -4,7 +4,7 @@
|
||||
<template v-if="user"><span style="margin-right:4px;"><fa :icon="['far', 'comments']"/></span>{{ user | userName }}</template>
|
||||
<template v-else><mk-ellipsis/></template>
|
||||
</span>
|
||||
<mk-messaging-room v-if="!fetching" :user="user" :is-naked="true"/>
|
||||
<x-messaging-room v-if="!fetching" :user="user" :is-naked="true"/>
|
||||
</mk-ui>
|
||||
</template>
|
||||
|
||||
@ -15,6 +15,9 @@ import parseAcct from '../../../../../misc/acct/parse';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
components: {
|
||||
XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<mk-ui>
|
||||
<span slot="header"><span style="margin-right:4px;"><fa :icon="['far', 'comments']"/></span>{{ $t('@.messaging') }}</span>
|
||||
<mk-messaging @navigate="navigate" :header-top="48"/>
|
||||
<x-messaging @navigate="navigate" :header-top="48"/>
|
||||
</mk-ui>
|
||||
</template>
|
||||
|
||||
@ -12,6 +12,9 @@ import getAcct from '../../../../../misc/acct/render';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
components: {
|
||||
XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
|
||||
},
|
||||
mounted() {
|
||||
document.title = `${this.$root.instanceName} ${this.$t('@.messaging')}`;
|
||||
},
|
||||
|
@ -5,7 +5,7 @@
|
||||
<button class="upload" @click="upload"><fa icon="upload"/></button>
|
||||
<button v-if="multiple" class="ok" @click="ok"><fa icon="check"/></button>
|
||||
</header>
|
||||
<mk-drive ref="browser" select-file :multiple="multiple" is-naked :top="$store.state.uiHeaderHeight"/>
|
||||
<x-drive ref="browser" select-file :multiple="multiple" is-naked :top="$store.state.uiHeaderHeight"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -15,6 +15,9 @@ import i18n from '../../../i18n';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('mobile/views/pages/selectdrive.vue'),
|
||||
components: {
|
||||
XDrive: () => import('../components/drive.vue').then(m => m.default),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
files: []
|
||||
|
@ -27,7 +27,7 @@
|
||||
<fa icon="map-marker"/>{{ user.profile.location }}
|
||||
</p>
|
||||
<p class="birthday" v-if="user.host === null && user.profile.birthday">
|
||||
<fa icon="birthday-cake"/>{{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)
|
||||
<fa icon="birthday-cake"/>{{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ $t('years-old', { age }) }})
|
||||
</p>
|
||||
</div>
|
||||
<div class="status">
|
||||
|
@ -76,6 +76,7 @@ import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import { copyright, host } from '../../../config';
|
||||
import { concat } from '../../../../../prelude/array';
|
||||
import { toUnicode } from 'punycode';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('mobile/views/pages/welcome.vue'),
|
||||
@ -85,7 +86,7 @@ export default Vue.extend({
|
||||
copyright,
|
||||
stats: null,
|
||||
banner: null,
|
||||
host,
|
||||
host: toUnicode(host),
|
||||
name: 'Misskey',
|
||||
description: '',
|
||||
photos: [],
|
||||
|
@ -34,6 +34,7 @@ const defaultSettings = {
|
||||
iLikeSushi: false,
|
||||
rememberNoteVisibility: false,
|
||||
defaultNoteVisibility: 'public',
|
||||
mutedWords: [],
|
||||
games: {
|
||||
reversi: {
|
||||
showBoardLabels: false,
|
||||
|
@ -10,7 +10,7 @@
|
||||
"declaration": false,
|
||||
"sourceMap": false,
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"module": "esnext",
|
||||
"removeComments": false,
|
||||
"noLib": false,
|
||||
"strict": true,
|
||||
|
@ -18,6 +18,7 @@
|
||||
secondary: '$secondary',
|
||||
bg: ':darken<8<$secondary',
|
||||
text: '$text',
|
||||
textHighlighted: ':lighten<7<$text',
|
||||
|
||||
scrollbarTrack: ':darken<5<$secondary',
|
||||
scrollbarHandle: ':lighten<5<$secondary',
|
||||
|
@ -18,6 +18,7 @@
|
||||
secondary: '$secondary',
|
||||
bg: ':darken<8<$secondary',
|
||||
text: '$text',
|
||||
textHighlighted: ':darken<7<$text',
|
||||
|
||||
scrollbarTrack: '#fff',
|
||||
scrollbarHandle: '#00000033',
|
||||
|
17
src/index.ts
17
src/index.ts
@ -14,7 +14,7 @@ import * as portscanner from 'portscanner';
|
||||
import isRoot = require('is-root');
|
||||
import Xev from 'xev';
|
||||
import * as program from 'commander';
|
||||
import mongo from './db/mongodb';
|
||||
import mongo, { nativeDbConn } from './db/mongodb';
|
||||
|
||||
import Logger from './misc/logger';
|
||||
import EnvironmentInfo from './misc/environmentInfo';
|
||||
@ -23,6 +23,7 @@ import serverStats from './daemons/server-stats';
|
||||
import notesStats from './daemons/notes-stats';
|
||||
import loadConfig from './config/load';
|
||||
import { Config } from './config/types';
|
||||
import { lessThan } from './prelude/array';
|
||||
|
||||
const clusterLog = debug('misskey:cluster');
|
||||
const ev = new Xev();
|
||||
@ -158,11 +159,19 @@ function checkMongoDb(config: Config) {
|
||||
mongoDBLogger.info(`Connecting to ${uri}`);
|
||||
|
||||
mongo.then(() => {
|
||||
nativeDbConn().then(db => db.admin().serverInfo()).then(x => x.version).then((version: string) => {
|
||||
mongoDBLogger.info(`Version: ${version}`);
|
||||
if (lessThan(version.split('.').map(x => parseInt(x, 10)), [3, 6])) {
|
||||
mongoDBLogger.error(`MongoDB version is less than 3.6. Please upgrade it.`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
mongoDBLogger.succ('Connectivity confirmed');
|
||||
})
|
||||
.catch(err => {
|
||||
mongoDBLogger.error(err.message);
|
||||
});
|
||||
.catch(err => {
|
||||
mongoDBLogger.error(err.message);
|
||||
});
|
||||
}
|
||||
|
||||
function spawnWorkers(limit: number) {
|
||||
|
@ -49,3 +49,11 @@ export function groupBy<T>(f: (x: T, y: T) => boolean, xs: T[]): T[][] {
|
||||
export function groupOn<T, S>(f: (x: T) => S, xs: T[]): T[][] {
|
||||
return groupBy((a, b) => f(a) === f(b), xs);
|
||||
}
|
||||
|
||||
export function lessThan(xs: number[], ys: number[]): boolean {
|
||||
for (let i = 0; i < Math.min(xs.length, ys.length); i++) {
|
||||
if (xs[i] < ys[i]) return true;
|
||||
if (xs[i] > ys[i]) return false;
|
||||
}
|
||||
return xs.length < ys.length;
|
||||
}
|
||||
|
@ -16,10 +16,13 @@ export default async (username: string, _host: string, option?: any, resync?: bo
|
||||
return await User.findOne({ usernameLower, host: null });
|
||||
}
|
||||
|
||||
const configHostAscii = toASCII(config.host).toLowerCase();
|
||||
const configHost = toUnicode(configHostAscii);
|
||||
|
||||
const hostAscii = toASCII(_host).toLowerCase();
|
||||
const host = toUnicode(hostAscii);
|
||||
|
||||
if (config.host == host) {
|
||||
if (configHost == host) {
|
||||
log(`return local user: ${usernameLower}`);
|
||||
return await User.findOne({ usernameLower, host: null });
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { toUnicode } from 'punycode';
|
||||
|
||||
export default (host: string) => {
|
||||
if (host == null) return null;
|
||||
return toUnicode(host).toLowerCase();
|
||||
};
|
||||
|
@ -28,7 +28,7 @@ export const meta = {
|
||||
},
|
||||
|
||||
host: {
|
||||
validator: $.str.optional,
|
||||
validator: $.str.optional.nullable,
|
||||
},
|
||||
|
||||
includeReplies: {
|
||||
|
@ -9,6 +9,7 @@ export default abstract class Channel {
|
||||
public id: string;
|
||||
public abstract readonly chName: string;
|
||||
public static readonly shouldShare: boolean;
|
||||
public static readonly requireCredential: boolean;
|
||||
|
||||
protected get user() {
|
||||
return this.connection.user;
|
||||
|
@ -4,6 +4,7 @@ import Channel from '../channel';
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'apLog';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = false;
|
||||
|
||||
@autobind
|
||||
public async init(params: any) {
|
||||
|
@ -4,6 +4,7 @@ import Channel from '../channel';
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'drive';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = true;
|
||||
|
||||
@autobind
|
||||
public async init(params: any) {
|
||||
|
@ -10,6 +10,7 @@ import Channel from '../../channel';
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'gamesReversiGame';
|
||||
public static shouldShare = false;
|
||||
public static requireCredential = false;
|
||||
|
||||
private gameId: mongo.ObjectID;
|
||||
|
||||
|
@ -7,6 +7,7 @@ import Channel from '../../channel';
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'gamesReversi';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = true;
|
||||
|
||||
@autobind
|
||||
public async init(params: any) {
|
||||
|
@ -7,6 +7,7 @@ import Channel from '../channel';
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'globalTimeline';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = false;
|
||||
|
||||
private mutedUserIds: string[] = [];
|
||||
|
||||
|
@ -7,6 +7,7 @@ import Channel from '../channel';
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'hashtag';
|
||||
public static shouldShare = false;
|
||||
public static requireCredential = false;
|
||||
|
||||
@autobind
|
||||
public async init(params: any) {
|
||||
|
@ -7,6 +7,7 @@ import Channel from '../channel';
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'homeTimeline';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = true;
|
||||
|
||||
private mutedUserIds: string[] = [];
|
||||
|
||||
|
@ -7,6 +7,7 @@ import Channel from '../channel';
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'hybridTimeline';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = true;
|
||||
|
||||
private mutedUserIds: string[] = [];
|
||||
|
||||
|
@ -7,6 +7,7 @@ import Channel from '../channel';
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'localTimeline';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = false;
|
||||
|
||||
private mutedUserIds: string[] = [];
|
||||
|
||||
|
@ -5,6 +5,7 @@ import Channel from '../channel';
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'main';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = true;
|
||||
|
||||
@autobind
|
||||
public async init(params: any) {
|
||||
|
@ -4,6 +4,7 @@ import Channel from '../channel';
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'messagingIndex';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = true;
|
||||
|
||||
@autobind
|
||||
public async init(params: any) {
|
||||
|
@ -5,6 +5,7 @@ import Channel from '../channel';
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'messaging';
|
||||
public static shouldShare = false;
|
||||
public static requireCredential = true;
|
||||
|
||||
private otherpartyId: string;
|
||||
|
||||
|
@ -7,6 +7,7 @@ const ev = new Xev();
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'notesStats';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = false;
|
||||
|
||||
@autobind
|
||||
public async init(params: any) {
|
||||
|
@ -7,6 +7,7 @@ const ev = new Xev();
|
||||
export default class extends Channel {
|
||||
public readonly chName = 'serverStats';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = false;
|
||||
|
||||
@autobind
|
||||
public async init(params: any) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user