Compare commits

...

42 Commits

Author SHA1 Message Date
50824a7245 10.49.0 2018-11-13 01:12:27 +09:00
6f2953f3a7 [Client] Clear cached locale data when shouldFlush is true 2018-11-13 01:11:36 +09:00
dd3f007582 [Client] Improve post-form widget 2018-11-13 01:04:15 +09:00
a4b2b093fc 🎨 2018-11-13 00:21:49 +09:00
0fbf56219f [Client] Emoji picker
Closes #3130
2018-11-13 00:12:55 +09:00
0acacf7a8e 10.48.1 2018-11-12 06:12:45 +09:00
c84500d914 Clean up 2018-11-12 06:12:22 +09:00
a9cfbda858 10.48.0 2018-11-12 05:48:09 +09:00
33e79e4bb8 [Client] Split some components to reduce bundle size 2018-11-12 05:35:09 +09:00
fab389e624 [Client] Stop generate scripts for each languages
Resolve #3172
2018-11-12 05:03:12 +09:00
b1b02d0e32 [Client] Enable code splitting
And some optimizations
2018-11-12 04:09:02 +09:00
0b40194d31 🎨 2018-11-12 01:20:26 +09:00
c2038bec73 Improve streaming 2018-11-12 00:31:09 +09:00
8674d55c8e Hotfix typo 2018-11-11 21:34:50 +09:00
c7c0c9e79d 10.47.0 2018-11-11 21:24:10 +09:00
2dff48167c 🎨 2018-11-11 21:18:24 +09:00
71d42f64dc [Client] Implement word mute
Closes #1739
2018-11-11 21:17:51 +09:00
1b4072610a [Client] Fix i18n 2018-11-11 19:15:08 +09:00
3826a820bb [Client] Fix i18n
Closes #3192
2018-11-11 19:13:10 +09:00
625eb376ae Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-11-11 19:10:24 +09:00
72cbab6514 [Clinet] Fix i18n 2018-11-11 19:10:15 +09:00
c7a7059e26 New Crowdin translations (#3186)
* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (English)
2018-11-11 18:52:23 +09:00
550593b208 Update @types/mongodb requirement from 3.1.12 to 3.1.14 (#3193)
Updates the requirements on [@types/mongodb](https://github.com/DefinitelyTyped/DefinitelyTyped) to permit the latest version.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-11-11 18:30:38 +09:00
f76255fa63 Update @types/webpack requirement from 4.4.17 to 4.4.18 (#3194)
Updates the requirements on [@types/webpack](https://github.com/DefinitelyTyped/DefinitelyTyped) to permit the latest version.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-11-11 18:30:27 +09:00
15a2881083 [API] Fix #3203 2018-11-11 18:26:09 +09:00
37bfb79123 Update setup.en.md (#3202)
初期状態のDebianやUbuntuだとpythonがないとインストールできなかったため(ConoHa, Vultr, DigitalOceanで検証済み)
2018-11-11 14:47:53 +09:00
b62203b1f1 Check MongoDB version (#3185)
* Check MongoDB version

* Fix bug
2018-11-11 14:27:00 +09:00
16136c252a fix self host detection (#3201) 2018-11-11 13:11:16 +09:00
75864a5125 Fix #3190 2018-11-11 13:08:48 +09:00
a59f53e6da Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-11-11 12:43:52 +09:00
2ceaccf9ab Fix chart engine 2018-11-11 12:43:35 +09:00
036d46c459 show self host in unicode (#3200) 2018-11-11 12:35:30 +09:00
5d3d78a73e Better text 2018-11-11 02:44:54 +09:00
6012e98ae6 Improve streaming API 2018-11-11 02:22:34 +09:00
9c0e990568 Better chart generation 2018-11-10 23:25:09 +09:00
6167ed4c9f Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-11-10 17:10:29 +09:00
988d5405c3 Clean up: Remove unused dependencies 2018-11-10 17:10:22 +09:00
ad0ea2fab2 Update config.yml (#3195) 2018-11-10 10:42:12 +09:00
d62c67208f [Client] Show domain in admin page 2018-11-10 04:28:56 +09:00
2da1432e52 Update CONTRIBUTING.md 2018-11-10 00:47:36 +09:00
69eefc1425 [Client] Fix bug 2018-11-10 00:30:58 +09:00
27bdb26202 Fix #3187 2018-11-10 00:28:50 +09:00
104 changed files with 1075 additions and 468 deletions

View File

@ -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

View File

@ -11,6 +11,9 @@ Please use [Crowdin](https://crowdin.com/project/misskey) for localization.
![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)
## 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`.

View File

@ -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:

View File

@ -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())

View File

@ -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: "画像"

View File

@ -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: "画像"

View File

@ -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"

View File

@ -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: "画像"

View File

@ -128,12 +128,12 @@ common:
my-turn: "Cest votre tour"
opponent-turn: "Tour de ladversaire"
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 lapplication. 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 linstance"
instance-description: "Description de linstance"
host: "Hôte"
banner-url: "Url de limage de la bannière"
languages: "インスタンスの対象言語"
languages-desc: "スペースで区切って複数設定できます。"
languages: "Langue de linstance"
languages-desc: "Vous pouvez en définir plus dune, séparées par des espaces."
maintainer-config: "Informations de ladministrateur"
maintainer-name: "Nom de ladministrateur"
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: "LURL 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 dauthentification GitHub"
github-integration-info: "LURL callback est définit sur /api/gh/cb"
enable-github-integration: "Activer lauthentification 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 dutilisateur du compte proxy"
proxy-account-username-desc: "Spécifiez le nom dutilisateur du compte utilisé comme proxy."
proxy-account-warn: "アカウントは自動で作られないため、そのユーザー名のアカウントを予め作成しておく必要があります。"
proxy-account-warn: "Avant dentammer cette action, vous devez au préalable avoir créé un compte avec ce nom dutilisateur."
max-note-text-length: "Nombre maximal de caractères pour les messages"
disable-registration: "Désactiver les inscriptions"
disable-local-timeline: "Désactiver lheure 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 linstance."
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} » na é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} » na é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: "LUrl de callback (facultatif)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
callback-url-desc: "Vous pouvez définir lURL de redirection lorsque lutilisateur sest authentifié via formulaire dauthentification."
authority: "Autorisations "
authority-desc: "Sont accessibles via lAPI, uniquement les fonctionnalités demandées ici."
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"

View File

@ -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: "画像"

View File

@ -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: "最近の投稿"

View File

@ -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: "画像"

View File

@ -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: "画像"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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: "画像"

View File

@ -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: "画像"

View File

@ -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",

View File

@ -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));
}
}

View File

@ -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,

View File

@ -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));

View File

@ -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 })
});
}

View File

@ -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'
}]

View File

@ -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 '';
};

View File

@ -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({

View 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;
}

View File

@ -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)
};
}
});

View 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>

View File

@ -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;

View File

@ -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);

View File

@ -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>

View File

@ -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,

View File

@ -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: {

View File

@ -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: '',

View File

@ -141,6 +141,7 @@ root(fill)
> .desc
margin 6px 0
font-size 13px
opacity 0.7
*
margin 0

View File

@ -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,

View File

@ -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_;

View File

@ -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

View File

@ -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);

View File

@ -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 {

View File

@ -322,7 +322,7 @@ export default Vue.extend({
});
break;
default:
alert(`%i18n:@unhandled-error% ${err}`);
alert(this.$t('unhandled-error'));
}
});
}

View File

@ -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>

View File

@ -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 {

View File

@ -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);

View File

@ -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 {

View File

@ -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, {

View File

@ -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"

View File

@ -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()) {

View File

@ -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

View File

@ -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) {

View File

@ -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: {

View File

@ -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

View File

@ -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>

View File

@ -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: {

View File

@ -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,

View File

@ -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: []

View File

@ -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>

View File

@ -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>

View File

@ -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: [],

View File

@ -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, {

View File

@ -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>

View File

@ -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 {

View File

@ -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');

View File

@ -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);

View File

@ -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"

View File

@ -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()) {

View File

@ -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,

View File

@ -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

View File

@ -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%`;

View File

@ -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,

View File

@ -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')}`;
},

View File

@ -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: []

View File

@ -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">

View File

@ -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: [],

View File

@ -34,6 +34,7 @@ const defaultSettings = {
iLikeSushi: false,
rememberNoteVisibility: false,
defaultNoteVisibility: 'public',
mutedWords: [],
games: {
reversi: {
showBoardLabels: false,

View File

@ -10,7 +10,7 @@
"declaration": false,
"sourceMap": false,
"target": "es2017",
"module": "commonjs",
"module": "esnext",
"removeComments": false,
"noLib": false,
"strict": true,

View File

@ -18,6 +18,7 @@
secondary: '$secondary',
bg: ':darken<8<$secondary',
text: '$text',
textHighlighted: ':lighten<7<$text',
scrollbarTrack: ':darken<5<$secondary',
scrollbarHandle: ':lighten<5<$secondary',

View File

@ -18,6 +18,7 @@
secondary: '$secondary',
bg: ':darken<8<$secondary',
text: '$text',
textHighlighted: ':darken<7<$text',
scrollbarTrack: '#fff',
scrollbarHandle: '#00000033',

View File

@ -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) {

View File

@ -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;
}

View File

@ -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 });
}

View File

@ -1,5 +1,6 @@
import { toUnicode } from 'punycode';
export default (host: string) => {
if (host == null) return null;
return toUnicode(host).toLowerCase();
};

View File

@ -28,7 +28,7 @@ export const meta = {
},
host: {
validator: $.str.optional,
validator: $.str.optional.nullable,
},
includeReplies: {

View File

@ -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;

View File

@ -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) {

View File

@ -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) {

View File

@ -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;

View File

@ -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) {

View File

@ -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[] = [];

View File

@ -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) {

View File

@ -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[] = [];

View File

@ -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[] = [];

View File

@ -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[] = [];

View File

@ -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) {

View File

@ -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) {

View File

@ -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;

View File

@ -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) {

View File

@ -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