Compare commits

..

42 Commits

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

* Fix bug
2018-11-11 14:27:00 +09:00
MeiMei
16136c252a fix self host detection (#3201) 2018-11-11 13:11:16 +09:00
syuilo
75864a5125 Fix #3190 2018-11-11 13:08:48 +09:00
syuilo
a59f53e6da Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-11-11 12:43:52 +09:00
syuilo
2ceaccf9ab Fix chart engine 2018-11-11 12:43:35 +09:00
MeiMei
036d46c459 show self host in unicode (#3200) 2018-11-11 12:35:30 +09:00
syuilo
5d3d78a73e Better text 2018-11-11 02:44:54 +09:00
syuilo
6012e98ae6 Improve streaming API 2018-11-11 02:22:34 +09:00
syuilo
9c0e990568 Better chart generation 2018-11-10 23:25:09 +09:00
syuilo
6167ed4c9f Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-11-10 17:10:29 +09:00
syuilo
988d5405c3 Clean up: Remove unused dependencies 2018-11-10 17:10:22 +09:00
Acid Chicken (硫酸鶏)
ad0ea2fab2 Update config.yml (#3195) 2018-11-10 10:42:12 +09:00
syuilo
d62c67208f [Client] Show domain in admin page 2018-11-10 04:28:56 +09:00
syuilo
2da1432e52 Update CONTRIBUTING.md 2018-11-10 00:47:36 +09:00
syuilo
69eefc1425 [Client] Fix bug 2018-11-10 00:30:58 +09:00
syuilo
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: | command: |
if [ "$DOCKERHUB_USERNAME$DOCKERHUB_PASSWORD" ] if [ "$DOCKERHUB_USERNAME$DOCKERHUB_PASSWORD" ]
then then
curl -LSs 'https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64' > jq apk update && apk add jq
chmod +x jq
docker tag misskey/misskey misskey/misskey:$(cat package.json | jq -r .version) docker tag misskey/misskey misskey/misskey:$(cat package.json | jq -r .version)
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
docker push misskey/misskey 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) ![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)
## Internationalization (i18n)
Misskey uses [vue-i18n](https://github.com/kazupon/vue-i18n).
## Documentation ## Documentation
* Documents for contributors are located in `/docs`. * Documents for contributors are located in `/docs`.
* Documents for instance admins 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` `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: 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 gulp from 'gulp';
import * as gutil from 'gulp-util'; import * as gutil from 'gulp-util';
import * as ts from 'gulp-typescript'; import * as ts from 'gulp-typescript';
const yaml = require('gulp-yaml');
const sourcemaps = require('gulp-sourcemaps'); const sourcemaps = require('gulp-sourcemaps');
import tslint from 'gulp-tslint'; import tslint from 'gulp-tslint';
const cssnano = require('gulp-cssnano'); const cssnano = require('gulp-cssnano');
@@ -39,6 +40,7 @@ gulp.task('build', [
'build:ts', 'build:ts',
'build:copy', 'build:copy',
'build:client', 'build:client',
'locales',
'doc' 'doc'
]); ]);
@@ -57,16 +59,7 @@ gulp.task('build:copy:views', () =>
gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/views')) gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/views'))
); );
// 互換性のため gulp.task('build:copy', ['build:copy: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.src([ gulp.src([
'./build/Release/crypto_key.node', './build/Release/crypto_key.node',
'./src/const.json', './src/const.json',
@@ -201,6 +194,12 @@ gulp.task('build:client:pug', [
.pipe(gulp.dest('./built/client/app/')) .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.task('doc', () =>
gulp.src('./src/docs/**/*.styl') gulp.src('./src/docs/**/*.styl')
.pipe(stylus()) .pipe(stylus())

View File

@@ -920,6 +920,7 @@ admin/views/instance.vue:
instance: "インスタンス" instance: "インスタンス"
instance-name: "インスタンス名" instance-name: "インスタンス名"
instance-description: "インスタンスの紹介" instance-description: "インスタンスの紹介"
host: "ホスト"
banner-url: "バナー画像URL" banner-url: "バナー画像URL"
languages: "インスタンスの対象言語" languages: "インスタンスの対象言語"
languages-desc: "スペースで区切って複数設定できます。" languages-desc: "スペースで区切って複数設定できます。"
@@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー" following: "フォロー"
followers: "フォロワー" followers: "フォロワー"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
years-old: "歳" years-old: "{age}歳"
year: "年" year: "年"
month: "月" month: "月"
day: "日" day: "日"
@@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
unmute: "ミュート解除" unmute: "ミュート解除"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
years-old: "{age}歳"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿" recent-notes: "最近の投稿"
images: "画像" images: "画像"

View File

@@ -920,6 +920,7 @@ admin/views/instance.vue:
instance: "インスタンス" instance: "インスタンス"
instance-name: "インスタンス名" instance-name: "インスタンス名"
instance-description: "インスタンスの紹介" instance-description: "インスタンスの紹介"
host: "ホスト"
banner-url: "バナー画像URL" banner-url: "バナー画像URL"
languages: "インスタンスの対象言語" languages: "インスタンスの対象言語"
languages-desc: "スペースで区切って複数設定できます。" languages-desc: "スペースで区切って複数設定できます。"
@@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー" following: "フォロー"
followers: "フォロワー" followers: "フォロワー"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
years-old: "歳" years-old: "{age}歳"
year: "年" year: "年"
month: "月" month: "月"
day: "日" day: "日"
@@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
unmute: "ミュート解除" unmute: "ミュート解除"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
years-old: "{age}歳"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿" recent-notes: "最近の投稿"
images: "画像" images: "画像"

View File

@@ -920,6 +920,7 @@ admin/views/instance.vue:
instance: "Instance" instance: "Instance"
instance-name: "Instance name" instance-name: "Instance name"
instance-description: "Instance description" instance-description: "Instance description"
host: "Host"
banner-url: "Banner image URL" banner-url: "Banner image URL"
languages: "Language of this instance" languages: "Language of this instance"
languages-desc: "You can add more than one, separated by spaces." languages-desc: "You can add more than one, separated by spaces."
@@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
following: "Following" following: "Following"
followers: "Followers" followers: "Followers"
is-bot: "This account is a Bot" is-bot: "This account is a Bot"
years-old: " years old" years-old: "{age} years old"
year: "/" year: "/"
month: "/" month: "/"
day: "-" day: "-"
@@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
unmute: "Unmute" unmute: "Unmute"
block: "Block" block: "Block"
unblock: "Unblock" unblock: "Unblock"
years-old: "{age} years old"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "Recent notes" recent-notes: "Recent notes"
images: "Images" images: "Images"

View File

@@ -920,6 +920,7 @@ admin/views/instance.vue:
instance: "インスタンス" instance: "インスタンス"
instance-name: "インスタンス名" instance-name: "インスタンス名"
instance-description: "インスタンスの紹介" instance-description: "インスタンスの紹介"
host: "ホスト"
banner-url: "バナー画像URL" banner-url: "バナー画像URL"
languages: "インスタンスの対象言語" languages: "インスタンスの対象言語"
languages-desc: "スペースで区切って複数設定できます。" languages-desc: "スペースで区切って複数設定できます。"
@@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー" following: "フォロー"
followers: "フォロワー" followers: "フォロワー"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
years-old: "歳" years-old: "{age}歳"
year: "年" year: "年"
month: "月" month: "月"
day: "日" day: "日"
@@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
unmute: "ミュート解除" unmute: "ミュート解除"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
years-old: "{age}歳"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿" recent-notes: "最近の投稿"
images: "画像" images: "画像"

View File

@@ -128,12 +128,12 @@ common:
my-turn: "Cest votre tour" my-turn: "Cest votre tour"
opponent-turn: "Tour de ladversaire" opponent-turn: "Tour de ladversaire"
turn-of: "Tour de {name}" turn-of: "Tour de {name}"
past-turn-of: "{name}のターン" past-turn-of: "Tour de {name}"
won: "{name} a gagné" won: "{name} a gagné"
black: "Noirs" black: "Noirs"
white: "Blancs" white: "Blancs"
total: "Total" total: "Total"
this-turn: "{count}ターン目" this-turn: "Tour {count}"
widgets: widgets:
analog-clock: "Horloge analogique" analog-clock: "Horloge analogique"
profile: "Profil" profile: "Profil"
@@ -160,7 +160,7 @@ common:
dev: "Échec lors de la création de lapplication. Veuillez réessayer." dev: "Échec lors de la création de lapplication. Veuillez réessayer."
ai-chan-kawaii: "Ai-Chan est mignone !" ai-chan-kawaii: "Ai-Chan est mignone !"
auth/views/form.vue: 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 :" permission-ask: "Cette application nécessite les autorisations suivantes :"
account-read: "Afficher les informations du compte :" account-read: "Afficher les informations du compte :"
account-write: "Modifications des informations du compte :" account-write: "Modifications des informations du compte :"
@@ -519,7 +519,7 @@ desktop/views/components/calendar.vue:
next: "Mois prochain" next: "Mois prochain"
go: "Cliquez pour naviguer" go: "Cliquez pour naviguer"
desktop/views/components/choose-file-from-drive-window.vue: 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" upload: "Téléverser des fichiers à partir de votre ordinateur"
cancel: "Annuler" cancel: "Annuler"
ok: "OK" ok: "OK"
@@ -687,8 +687,8 @@ desktop/views/components/renote-form.vue:
desktop/views/components/renote-form-window.vue: desktop/views/components/renote-form-window.vue:
title: "Êtes vous sûr de vouloir renote cette note?" title: "Êtes vous sûr de vouloir renote cette note?"
desktop/views/pages/user-following-or-followers.vue: desktop/views/pages/user-following-or-followers.vue:
following: "{user}のフォロー" following: "{user} suit"
followers: "{user}のフォロワー" followers: "Abonné·e·s de {user}"
desktop/views/components/settings-window.vue: desktop/views/components/settings-window.vue:
settings: "Paramètres" settings: "Paramètres"
desktop/views/components/settings.vue: desktop/views/components/settings.vue:
@@ -717,7 +717,7 @@ desktop/views/components/settings.vue:
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。" api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
deck-nav: "デッキ内ナビゲーション" deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。" deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
deck-default: "デッキをデフォルトのUIにする" deck-default: "Utiliser le Deck comme IU par défaut"
display: "Affichage et design" display: "Affichage et design"
customize: "Personnaliser l'Accueil" customize: "Personnaliser l'Accueil"
wallpaper: "Arrière plan" wallpaper: "Arrière plan"
@@ -737,7 +737,7 @@ desktop/views/components/settings.vue:
show-renoted-my-notes: "Afficher mes republications dans les fils" show-renoted-my-notes: "Afficher mes republications dans les fils"
show-local-renotes: "Afficher les partages locaux sur les fils" show-local-renotes: "Afficher les partages locaux sur les fils"
show-maps: "Afficher la carte" show-maps: "Afficher la carte"
deck-column-align: "デッキのカラムの位置" deck-column-align: "Alignement des colonnes du Deck"
deck-column-align-center: "Centrer" deck-column-align-center: "Centrer"
deck-column-align-left: "À gauche" deck-column-align-left: "À gauche"
sound: "Son" sound: "Son"
@@ -870,7 +870,7 @@ desktop/views/components/ui.header.account.vue:
dark: "Fall in dark" dark: "Fall in dark"
desktop/views/components/ui.header.nav.vue: desktop/views/components/ui.header.nav.vue:
home: "Accueil" home: "Accueil"
deck: "デッキ" deck: "Deck"
game: "Jeux" game: "Jeux"
desktop/views/components/ui.header.notifications.vue: desktop/views/components/ui.header.notifications.vue:
title: "Notifications" title: "Notifications"
@@ -920,30 +920,31 @@ admin/views/instance.vue:
instance: "Instance" instance: "Instance"
instance-name: "Nom de linstance" instance-name: "Nom de linstance"
instance-description: "Description de linstance" instance-description: "Description de linstance"
host: "Hôte"
banner-url: "Url de limage de la bannière" banner-url: "Url de limage de la bannière"
languages: "インスタンスの対象言語" languages: "Langue de linstance"
languages-desc: "スペースで区切って複数設定できます。" languages-desc: "Vous pouvez en définir plus dune, séparées par des espaces."
maintainer-config: "Informations de ladministrateur" maintainer-config: "Informations de ladministrateur"
maintainer-name: "Nom de ladministrateur" maintainer-name: "Nom de ladministrateur"
maintainer-email: "Contact administratif" maintainer-email: "Contact administratif"
drive-config: "Paramètres du lecteur" drive-config: "Paramètres du lecteur"
cache-remote-files: "Mettre en cache des fichiers distants" cache-remote-files: "Mettre en cache des fichiers distants"
cache-remote-files-desc: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。そのためサーバーのストレージを節約できますが、プライバシー設定で直リンクを無効にしているユーザーにはファイルが見えなくなったり、サムネイルが生成されないので通信量が増加します。通常はこの設定をオンにしておくことをおすすめします。" cache-remote-files-desc: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。そのためサーバーのストレージを節約できますが、プライバシー設定で直リンクを無効にしているユーザーにはファイルが見えなくなったり、サムネイルが生成されないので通信量が増加します。通常はこの設定をオンにしておくことをおすすめします。"
local-drive-capacity-mb: "ローカルユーザーひとりあたりのドライブ容量" local-drive-capacity-mb: "Volume du lecteur par utilisateur"
remote-drive-capacity-mb: "リモートユーザーひとりあたりのドライブ容量" remote-drive-capacity-mb: "Volume du lecteur par utilisateur distant"
mb: "en mégaoctets" mb: "en mégaoctets"
recaptcha-config: "Paramètres de reCAPTCHA" 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" enable-recaptcha: "Activation de reCAPTCHA"
recaptcha-site-key: "Clé reCAPTCHA du site" recaptcha-site-key: "Clé reCAPTCHA du site"
recaptcha-secret-key: "Clé secrète reCAPTCHA" recaptcha-secret-key: "Clé secrète reCAPTCHA"
twitter-integration-config: "Paramètres de connexion à Twitter" 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" enable-twitter-integration: "Activer la connection à Twitter"
twitter-integration-consumer-key: "Clé du consommateur" twitter-integration-consumer-key: "Clé du consommateur"
twitter-integration-consumer-secret: "Secret du consommateur" twitter-integration-consumer-secret: "Secret du consommateur"
github-integration-config: "GitHub連携の設定" github-integration-config: "Paramètres dauthentification GitHub"
github-integration-info: "コールバックURLは /api/gh/cb に設定します。" github-integration-info: "LURL callback est définit sur /api/gh/cb"
enable-github-integration: "Activer lauthentification avec Github" enable-github-integration: "Activer lauthentification avec Github"
github-integration-client-id: "ID client" github-integration-client-id: "ID client"
github-integration-client-secret: "Secret client" github-integration-client-secret: "Secret client"
@@ -951,7 +952,7 @@ admin/views/instance.vue:
proxy-account-info: "プロキシアカウントは、特定の条件下でユーザーのリモートフォローを代行するアカウントです。例えば、ユーザーがリモートユーザーをリストに入れたとき、リストに入れられたユーザーを誰もフォローしていないとアクティビティがサーバーに配達されないため、代わりにプロキシアカウントがフォローするようにします。" proxy-account-info: "プロキシアカウントは、特定の条件下でユーザーのリモートフォローを代行するアカウントです。例えば、ユーザーがリモートユーザーをリストに入れたとき、リストに入れられたユーザーを誰もフォローしていないとアクティビティがサーバーに配達されないため、代わりにプロキシアカウントがフォローするようにします。"
proxy-account-username: "Nom dutilisateur du compte proxy" proxy-account-username: "Nom dutilisateur du compte proxy"
proxy-account-username-desc: "Spécifiez le nom dutilisateur du compte utilisé comme 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" max-note-text-length: "Nombre maximal de caractères pour les messages"
disable-registration: "Désactiver les inscriptions" disable-registration: "Désactiver les inscriptions"
disable-local-timeline: "Désactiver lheure locale" disable-local-timeline: "Désactiver lheure locale"
@@ -1024,7 +1025,7 @@ admin/views/announcements.vue:
text: "Contenu" text: "Contenu"
saved: "Sauvegardé" saved: "Sauvegardé"
_remove: _remove:
are-you-sure: "「$1」を削除しますか" are-you-sure: "Supprimer « %1$s»?"
removed: "Supprimé" removed: "Supprimé"
admin/views/hashtags.vue: admin/views/hashtags.vue:
hided-tags: "Tags cachés" hided-tags: "Tags cachés"
@@ -1054,11 +1055,11 @@ desktop/views/pages/selectdrive.vue:
upload: "Téléverser des fichiers à partir de votre ordinateur" upload: "Téléverser des fichiers à partir de votre ordinateur"
desktop/views/pages/search.vue: desktop/views/pages/search.vue:
not-available: "La fonction de recherche est désactivée dans les paramètres de linstance." 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: desktop/views/pages/share.vue:
share-with: "Partager avec {name}" share-with: "Partager avec {name}"
desktop/views/pages/tag.vue: 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: desktop/views/pages/user-list.users.vue:
users: "Utilisateurs" users: "Utilisateurs"
add-user: "Ajouter un utilisateur" add-user: "Ajouter un utilisateur"
@@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
following: "Suit" following: "Suit"
followers: "Abonné·e·s" followers: "Abonné·e·s"
is-bot: "Ce compte est un Bot" is-bot: "Ce compte est un Bot"
years-old: "ans dâge" years-old: "{age} ans"
year: "Année" year: "Année"
month: "/" month: "/"
day: "-" day: "-"
@@ -1244,7 +1245,7 @@ mobile/views/pages/signup.vue:
mobile/views/pages/followers.vue: mobile/views/pages/followers.vue:
followers-of: "Abonné·e·s de {name}" followers-of: "Abonné·e·s de {name}"
mobile/views/pages/following.vue: mobile/views/pages/following.vue:
following-of: "{name}のフォロー" following-of: "Abonné·e·s de {name}"
mobile/views/pages/home.vue: mobile/views/pages/home.vue:
home: "Accueil" home: "Accueil"
local: "Local" local: "Local"
@@ -1253,7 +1254,7 @@ mobile/views/pages/home.vue:
mentions: "Mentions" mentions: "Mentions"
messages: "Messages" messages: "Messages"
mobile/views/pages/tag.vue: 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: mobile/views/pages/welcome.vue:
signup: "S'enregistrer" signup: "S'enregistrer"
mobile/views/pages/widgets.vue: mobile/views/pages/widgets.vue:
@@ -1280,7 +1281,7 @@ mobile/views/pages/games/reversi.vue:
reversi: "Reversi" reversi: "Reversi"
mobile/views/pages/search.vue: mobile/views/pages/search.vue:
search: "Chercher" search: "Chercher"
not-found: "「{q}」に関する投稿は見つかりませんでした。" not-found: "Aucune publication trouvée pour « {q} »."
mobile/views/pages/selectdrive.vue: mobile/views/pages/selectdrive.vue:
select-file: "Choisissez un fichier" select-file: "Choisissez un fichier"
mobile/views/pages/settings.vue: mobile/views/pages/settings.vue:
@@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
unmute: "Enlever la sourdine" unmute: "Enlever la sourdine"
block: "Bloquer" block: "Bloquer"
unblock: "Débloquer" unblock: "Débloquer"
years-old: "{age} ans"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "Notes récentes" recent-notes: "Notes récentes"
images: "Images" images: "Images"
@@ -1391,7 +1393,7 @@ deck:
deck/deck.tl-column.vue: deck/deck.tl-column.vue:
is-media-only: "Les publications médias uniquement" is-media-only: "Les publications médias uniquement"
is-media-view: "Vue média" is-media-view: "Vue média"
edit: "オプション" edit: "Option"
deck/deck.user-column.vue: deck/deck.user-column.vue:
posts: "Notes" posts: "Notes"
following: "Suit" following: "Suit"
@@ -1438,7 +1440,7 @@ dev/views/new-app.vue:
app-desc: "Brève description introductive à votre application." app-desc: "Brève description introductive à votre application."
app-desc-ex: "p. ex) Misskey pour iOS" app-desc-ex: "p. ex) Misskey pour iOS"
callback-url: "LUrl de callback (facultatif)" 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: "Autorisations "
authority-desc: "Sont accessibles via lAPI, uniquement les fonctionnalités demandées ici." authority-desc: "Sont accessibles via lAPI, uniquement les fonctionnalités demandées ici."
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。" authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"

View File

@@ -920,6 +920,7 @@ admin/views/instance.vue:
instance: "インスタンス" instance: "インスタンス"
instance-name: "インスタンス名" instance-name: "インスタンス名"
instance-description: "インスタンスの紹介" instance-description: "インスタンスの紹介"
host: "ホスト"
banner-url: "バナー画像URL" banner-url: "バナー画像URL"
languages: "インスタンスの対象言語" languages: "インスタンスの対象言語"
languages-desc: "スペースで区切って複数設定できます。" languages-desc: "スペースで区切って複数設定できます。"
@@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー" following: "フォロー"
followers: "フォロワー" followers: "フォロワー"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
years-old: "歳" years-old: "{age}歳"
year: "年" year: "年"
month: "月" month: "月"
day: "日" day: "日"
@@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
unmute: "ミュート解除" unmute: "ミュート解除"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
years-old: "{age}歳"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿" recent-notes: "最近の投稿"
images: "画像" images: "画像"

View File

@@ -379,6 +379,17 @@ common/views/components/poll-editor.vue:
common/views/components/reaction-picker.vue: common/views/components/reaction-picker.vue:
choose-reaction: "リアクションを選択" 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: common/views/components/signin.vue:
username: "ユーザー名" username: "ユーザー名"
password: "パスワード" password: "パスワード"
@@ -935,6 +946,10 @@ common/views/components/mute-and-block.vue:
block: "ブロック" block: "ブロック"
no-muted-users: "ミュートしているユーザーはいません" no-muted-users: "ミュートしているユーザーはいません"
no-blocked-users: "ブロックしているユーザーはいません" no-blocked-users: "ブロックしているユーザーはいません"
word-mute: "ワードミュート"
muted-words: "ミュートされたキーワード"
muted-words-description: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
save: "保存"
common/views/components/password-settings.vue: common/views/components/password-settings.vue:
reset: "パスワードを変更する" reset: "パスワードを変更する"
@@ -1045,6 +1060,7 @@ admin/views/instance.vue:
instance: "インスタンス" instance: "インスタンス"
instance-name: "インスタンス名" instance-name: "インスタンス名"
instance-description: "インスタンスの紹介" instance-description: "インスタンスの紹介"
host: "ホスト"
banner-url: "バナー画像URL" banner-url: "バナー画像URL"
languages: "インスタンスの対象言語" languages: "インスタンスの対象言語"
languages-desc: "スペースで区切って複数設定できます。" languages-desc: "スペースで区切って複数設定できます。"
@@ -1237,7 +1253,7 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー" following: "フォロー"
followers: "フォロワー" followers: "フォロワー"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
years-old: "歳" years-old: "{age}歳"
year: "年" year: "年"
month: "月" month: "月"
day: "日" day: "日"
@@ -1543,6 +1559,7 @@ mobile/views/pages/user.vue:
unmute: "ミュート解除" unmute: "ミュート解除"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
years-old: "{age}歳"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿" recent-notes: "最近の投稿"

View File

@@ -920,6 +920,7 @@ admin/views/instance.vue:
instance: "インスタンス" instance: "インスタンス"
instance-name: "インスタンス名" instance-name: "インスタンス名"
instance-description: "インスタンスの紹介" instance-description: "インスタンスの紹介"
host: "ホスト"
banner-url: "バナー画像URL" banner-url: "バナー画像URL"
languages: "インスタンスの対象言語" languages: "インスタンスの対象言語"
languages-desc: "スペースで区切って複数設定できます。" languages-desc: "スペースで区切って複数設定できます。"
@@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー" following: "フォロー"
followers: "フォロワー" followers: "フォロワー"
is-bot: "このアカウントはBotや" is-bot: "このアカウントはBotや"
years-old: "歳" years-old: "{age}歳"
year: "年" year: "年"
month: "月" month: "月"
day: "日" day: "日"
@@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
unmute: "ミュート解除" unmute: "ミュート解除"
block: "ブロック" block: "ブロック"
unblock: "ブロックやめたる" unblock: "ブロックやめたる"
years-old: "{age}歳"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "最近儲かりまっか?" recent-notes: "最近儲かりまっか?"
images: "画像" images: "画像"

View File

@@ -920,6 +920,7 @@ admin/views/instance.vue:
instance: "インスタンス" instance: "インスタンス"
instance-name: "インスタンス名" instance-name: "インスタンス名"
instance-description: "インスタンスの紹介" instance-description: "インスタンスの紹介"
host: "ホスト"
banner-url: "バナー画像URL" banner-url: "バナー画像URL"
languages: "インスタンスの対象言語" languages: "インスタンスの対象言語"
languages-desc: "スペースで区切って複数設定できます。" languages-desc: "スペースで区切って複数設定できます。"
@@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー" following: "フォロー"
followers: "フォロワー" followers: "フォロワー"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
years-old: "歳" years-old: "{age}歳"
year: "年" year: "年"
month: "月" month: "月"
day: "日" day: "日"
@@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
unmute: "ミュート解除" unmute: "ミュート解除"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
years-old: "{age}歳"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿" recent-notes: "最近の投稿"
images: "画像" images: "画像"

View File

@@ -920,6 +920,7 @@ admin/views/instance.vue:
instance: "インスタンス" instance: "インスタンス"
instance-name: "インスタンス名" instance-name: "インスタンス名"
instance-description: "インスタンスの紹介" instance-description: "インスタンスの紹介"
host: "ホスト"
banner-url: "バナー画像URL" banner-url: "バナー画像URL"
languages: "インスタンスの対象言語" languages: "インスタンスの対象言語"
languages-desc: "スペースで区切って複数設定できます。" languages-desc: "スペースで区切って複数設定できます。"
@@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー" following: "フォロー"
followers: "フォロワー" followers: "フォロワー"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
years-old: "歳" years-old: "{age}歳"
year: "年" year: "年"
month: "月" month: "月"
day: "日" day: "日"
@@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
unmute: "ミュート解除" unmute: "ミュート解除"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
years-old: "{age}歳"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "Recente notities" recent-notes: "Recente notities"
images: "Afbeeldingen" images: "Afbeeldingen"

View File

@@ -920,6 +920,7 @@ admin/views/instance.vue:
instance: "インスタンス" instance: "インスタンス"
instance-name: "インスタンス名" instance-name: "インスタンス名"
instance-description: "インスタンスの紹介" instance-description: "インスタンスの紹介"
host: "ホスト"
banner-url: "バナー画像URL" banner-url: "バナー画像URL"
languages: "インスタンスの対象言語" languages: "インスタンスの対象言語"
languages-desc: "スペースで区切って複数設定できます。" languages-desc: "スペースで区切って複数設定できます。"
@@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
following: "Følger" following: "Følger"
followers: "フォロワー" followers: "フォロワー"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
years-old: "歳" years-old: "{age}歳"
year: "年" year: "年"
month: "月" month: "月"
day: "日" day: "日"
@@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
unmute: "ミュート解除" unmute: "ミュート解除"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
years-old: "{age}歳"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "Nylige innlegg" recent-notes: "Nylige innlegg"
images: "Bilder" images: "Bilder"

View File

@@ -920,6 +920,7 @@ admin/views/instance.vue:
instance: "インスタンス" instance: "インスタンス"
instance-name: "インスタンス名" instance-name: "インスタンス名"
instance-description: "インスタンスの紹介" instance-description: "インスタンスの紹介"
host: "ホスト"
banner-url: "バナー画像URL" banner-url: "バナー画像URL"
languages: "インスタンスの対象言語" languages: "インスタンスの対象言語"
languages-desc: "スペースで区切って複数設定できます。" languages-desc: "スペースで区切って複数設定できます。"
@@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
following: "Śledzeni" following: "Śledzeni"
followers: "Śledzący" followers: "Śledzący"
is-bot: "To konto jest botem" is-bot: "To konto jest botem"
years-old: "歳" years-old: "{age}歳"
year: "年" year: "年"
month: "月" month: "月"
day: "日" day: "日"
@@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
unmute: "ミュート解除" unmute: "ミュート解除"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
years-old: "{age}歳"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "Ostatnie wpisy" recent-notes: "Ostatnie wpisy"
images: "Zdjęcia" images: "Zdjęcia"

View File

@@ -920,6 +920,7 @@ admin/views/instance.vue:
instance: "インスタンス" instance: "インスタンス"
instance-name: "インスタンス名" instance-name: "インスタンス名"
instance-description: "インスタンスの紹介" instance-description: "インスタンスの紹介"
host: "ホスト"
banner-url: "バナー画像URL" banner-url: "バナー画像URL"
languages: "インスタンスの対象言語" languages: "インスタンスの対象言語"
languages-desc: "スペースで区切って複数設定できます。" languages-desc: "スペースで区切って複数設定できます。"
@@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー" following: "フォロー"
followers: "フォロワー" followers: "フォロワー"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
years-old: "歳" years-old: "{age}歳"
year: "年" year: "年"
month: "月" month: "月"
day: "日" day: "日"
@@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
unmute: "ミュート解除" unmute: "ミュート解除"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
years-old: "{age}歳"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "Notas recentes" recent-notes: "Notas recentes"
images: "Imagens" images: "Imagens"

View File

@@ -920,6 +920,7 @@ admin/views/instance.vue:
instance: "インスタンス" instance: "インスタンス"
instance-name: "インスタンス名" instance-name: "インスタンス名"
instance-description: "インスタンスの紹介" instance-description: "インスタンスの紹介"
host: "ホスト"
banner-url: "バナー画像URL" banner-url: "バナー画像URL"
languages: "インスタンスの対象言語" languages: "インスタンスの対象言語"
languages-desc: "スペースで区切って複数設定できます。" languages-desc: "スペースで区切って複数設定できます。"
@@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー" following: "フォロー"
followers: "フォロワー" followers: "フォロワー"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
years-old: "歳" years-old: "{age}歳"
year: "年" year: "年"
month: "月" month: "月"
day: "日" day: "日"
@@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
unmute: "ミュート解除" unmute: "ミュート解除"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
years-old: "{age}歳"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿" recent-notes: "最近の投稿"
images: "画像" images: "画像"

View File

@@ -920,6 +920,7 @@ admin/views/instance.vue:
instance: "インスタンス" instance: "インスタンス"
instance-name: "インスタンス名" instance-name: "インスタンス名"
instance-description: "インスタンスの紹介" instance-description: "インスタンスの紹介"
host: "ホスト"
banner-url: "バナー画像URL" banner-url: "バナー画像URL"
languages: "インスタンスの対象言語" languages: "インスタンスの対象言語"
languages-desc: "スペースで区切って複数設定できます。" languages-desc: "スペースで区切って複数設定できます。"
@@ -1093,7 +1094,7 @@ desktop/views/pages/user/user.header.vue:
following: "フォロー" following: "フォロー"
followers: "フォロワー" followers: "フォロワー"
is-bot: "このアカウントはBotです" is-bot: "このアカウントはBotです"
years-old: "歳" years-old: "{age}歳"
year: "年" year: "年"
month: "月" month: "月"
day: "日" day: "日"
@@ -1351,6 +1352,7 @@ mobile/views/pages/user.vue:
unmute: "ミュート解除" unmute: "ミュート解除"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
years-old: "{age}歳"
mobile/views/pages/user/home.vue: mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿" recent-notes: "最近の投稿"
images: "画像" images: "画像"

View File

@@ -1,8 +1,8 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "10.46.2", "version": "10.49.0",
"clientVersion": "1.0.11698", "clientVersion": "2.0.11740",
"codename": "nighthike", "codename": "nighthike",
"main": "./built/index.js", "main": "./built/index.js",
"private": true, "private": true,
@@ -60,7 +60,7 @@
"@types/minio": "7.0.1", "@types/minio": "7.0.1",
"@types/mkdirp": "0.5.2", "@types/mkdirp": "0.5.2",
"@types/mocha": "5.2.5", "@types/mocha": "5.2.5",
"@types/mongodb": "3.1.12", "@types/mongodb": "3.1.14",
"@types/ms": "0.7.30", "@types/ms": "0.7.30",
"@types/node": "10.12.2", "@types/node": "10.12.2",
"@types/oauth": "0.9.1", "@types/oauth": "0.9.1",
@@ -75,13 +75,12 @@
"@types/seedrandom": "2.4.27", "@types/seedrandom": "2.4.27",
"@types/sharp": "0.21.0", "@types/sharp": "0.21.0",
"@types/showdown": "1.7.5", "@types/showdown": "1.7.5",
"@types/single-line-log": "1.1.0",
"@types/speakeasy": "2.0.3", "@types/speakeasy": "2.0.3",
"@types/systeminformation": "3.23.0", "@types/systeminformation": "3.23.0",
"@types/tinycolor2": "1.4.1", "@types/tinycolor2": "1.4.1",
"@types/tmp": "0.0.33", "@types/tmp": "0.0.33",
"@types/uuid": "3.4.4", "@types/uuid": "3.4.4",
"@types/webpack": "4.4.17", "@types/webpack": "4.4.18",
"@types/webpack-stream": "3.2.10", "@types/webpack-stream": "3.2.10",
"@types/websocket": "0.0.40", "@types/websocket": "0.0.40",
"@types/ws": "6.0.1", "@types/ws": "6.0.1",
@@ -100,6 +99,7 @@
"commander": "2.19.0", "commander": "2.19.0",
"crc-32": "1.2.0", "crc-32": "1.2.0",
"css-loader": "1.0.1", "css-loader": "1.0.1",
"cssnano": "4.1.7",
"dateformat": "3.0.3", "dateformat": "3.0.3",
"debug": "4.1.0", "debug": "4.1.0",
"deep-equal": "1.0.1", "deep-equal": "1.0.1",
@@ -129,6 +129,7 @@
"gulp-typescript": "4.0.2", "gulp-typescript": "4.0.2",
"gulp-uglify": "3.0.1", "gulp-uglify": "3.0.1",
"gulp-util": "3.0.8", "gulp-util": "3.0.8",
"gulp-yaml": "2.0.2",
"hard-source-webpack-plugin": "0.12.0", "hard-source-webpack-plugin": "0.12.0",
"html-minifier": "3.5.21", "html-minifier": "3.5.21",
"http-signature": "1.2.0", "http-signature": "1.2.0",
@@ -152,12 +153,11 @@
"koa-slow": "2.1.0", "koa-slow": "2.1.0",
"koa-views": "6.1.4", "koa-views": "6.1.4",
"loader-utils": "1.1.0", "loader-utils": "1.1.0",
"mecab-async": "0.1.2",
"merge-options": "1.0.1",
"minio": "7.0.1", "minio": "7.0.1",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"mocha": "5.2.0", "mocha": "5.2.0",
"moji": "0.5.1", "moji": "0.5.1",
"moment": "2.22.2",
"mongodb": "3.1.8", "mongodb": "3.1.8",
"monk": "6.0.6", "monk": "6.0.6",
"ms": "2.1.1", "ms": "2.1.1",
@@ -169,6 +169,7 @@
"os-utils": "0.0.14", "os-utils": "0.0.14",
"parse5": "5.1.0", "parse5": "5.1.0",
"portscanner": "2.2.0", "portscanner": "2.2.0",
"postcss-loader": "3.0.0",
"progress-bar-webpack-plugin": "1.11.0", "progress-bar-webpack-plugin": "1.11.0",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
"promise-sequential": "1.1.1", "promise-sequential": "1.1.1",
@@ -185,12 +186,10 @@
"rimraf": "2.6.2", "rimraf": "2.6.2",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"s-age": "1.1.2", "s-age": "1.1.2",
"sass-loader": "7.1.0",
"seedrandom": "2.4.4", "seedrandom": "2.4.4",
"sharp": "0.21.0", "sharp": "0.21.0",
"showdown": "1.8.7", "showdown": "1.8.7",
"showdown-highlightjs-extension": "0.1.2", "showdown-highlightjs-extension": "0.1.2",
"single-line-log": "1.1.2",
"speakeasy": "2.0.0", "speakeasy": "2.0.0",
"stringz": "1.0.0", "stringz": "1.0.0",
"style-loader": "0.23.1", "style-loader": "0.23.1",

View File

@@ -2,12 +2,15 @@
* チャートエンジン * チャートエンジン
*/ */
import * as moment from 'moment';
const nestedProperty = require('nested-property'); const nestedProperty = require('nested-property');
import autobind from 'autobind-decorator'; import autobind from 'autobind-decorator';
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import db from '../db/mongodb'; import db from '../db/mongodb';
import { ICollection } from 'monk'; import { ICollection } from 'monk';
const utc = moment.utc;
export type Obj = { [key: string]: any }; export type Obj = { [key: string]: any };
export type Partial<T> = { export type Partial<T> = {
@@ -87,12 +90,12 @@ export default abstract class Chart<T> {
@autobind @autobind
private getCurrentDate(): [number, number, number, number] { private getCurrentDate(): [number, number, number, number] {
const now = new Date(); const now = moment().utc();
const y = now.getFullYear(); const y = now.year();
const m = now.getMonth(); const m = now.month();
const d = now.getDate(); const d = now.date();
const h = now.getHours(); const h = now.hour();
return [y, m, d, h]; return [y, m, d, h];
} }
@@ -114,15 +117,15 @@ export default abstract class Chart<T> {
const [y, m, d, h] = this.getCurrentDate(); const [y, m, d, h] = this.getCurrentDate();
const current = const current =
span == 'day' ? new Date(y, m, d) : span == 'day' ? utc([y, m, d]) :
span == 'hour' ? new Date(y, m, d, h) : span == 'hour' ? utc([y, m, d, h]) :
null; null;
// 現在(今日または今のHour)のログ // 現在(今日または今のHour)のログ
const currentLog = await this.collection.findOne({ const currentLog = await this.collection.findOne({
group: group, group: group,
span: span, span: span,
date: current date: current.toDate()
}); });
// ログがあればそれを返して終了 // ログがあればそれを返して終了
@@ -158,7 +161,7 @@ export default abstract class Chart<T> {
log = await this.collection.insert({ log = await this.collection.insert({
group: group, group: group,
span: span, span: span,
date: current, date: current.toDate(),
data: data data: data
}); });
} catch (e) { } catch (e) {
@@ -225,8 +228,8 @@ export default abstract class Chart<T> {
const [y, m, d, h] = this.getCurrentDate(); const [y, m, d, h] = this.getCurrentDate();
const gt = const gt =
span == 'day' ? new Date(y, m, d - range) : span == 'day' ? utc([y, m, d]).subtract(range, 'days') :
span == 'hour' ? new Date(y, m, d, h - range) : span == 'hour' ? utc([y, m, d, h]).subtract(range, 'hours') :
null; null;
// ログ取得 // ログ取得
@@ -234,7 +237,7 @@ export default abstract class Chart<T> {
group: group, group: group,
span: span, span: span,
date: { date: {
$gt: gt $gte: gt.toDate()
} }
}, { }, {
sort: { sort: {
@@ -264,22 +267,45 @@ export default abstract class Chart<T> {
if (recentLog) { if (recentLog) {
logs = [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--) { for (let i = (range - 1); i >= 0; i--) {
const current = const current =
span == 'day' ? new Date(y, m, d - i) : span == 'day' ? utc([y, m, d]).subtract(i, 'days') :
span == 'hour' ? new Date(y, m, d, h - i) : span == 'hour' ? utc([y, m, d, h]).subtract(i, 'hours') :
null; null;
const log = logs.find(l => l.date.getTime() == current.getTime()); const log = logs.find(l => utc(l.date).isSame(current));
if (log) { if (log) {
promisedChart.unshift(Promise.resolve(log.data)); promisedChart.unshift(Promise.resolve(log.data));
} else { } 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)); promisedChart.unshift(this.getTemplate(false, latest ? latest.data : null));
} }
} }

View File

@@ -3,6 +3,7 @@
<ui-card> <ui-card>
<div slot="title"><fa icon="cog"/> {{ $t('instance') }}</div> <div slot="title"><fa icon="cog"/> {{ $t('instance') }}</div>
<section class="fit-top fit-bottom"> <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-input v-model="name">{{ $t('instance-name') }}</ui-input>
<ui-textarea v-model="description">{{ $t('instance-description') }}</ui-textarea> <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> <ui-input v-model="bannerUrl"><i slot="icon"><fa icon="link"/></i>{{ $t('banner-url') }}</ui-input>
@@ -81,11 +82,14 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../i18n'; import i18n from '../../i18n';
import { host } from '../../config';
import { toUnicode } from 'punycode';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('admin/views/instance.vue'), i18n: i18n('admin/views/instance.vue'),
data() { data() {
return { return {
host: toUnicode(host),
maintainerName: null, maintainerName: null,
maintainerEmail: null, maintainerEmail: null,
disableRegistration: false, disableRegistration: false,

View File

@@ -3,15 +3,9 @@
* (ENTRY POINT) * (ENTRY POINT)
*/ */
/**
* ドメインに基づいて適切なスクリプトを読み込みます。
* ユーザーの言語およびモバイル端末か否かも考慮します。
* webpackは介さないためrequireやimportは使えません。
*/
'use strict'; 'use strict';
(function() { (async function() {
// キャッシュ削除要求があれば従う // キャッシュ削除要求があれば従う
if (localStorage.getItem('shouldFlush') == 'true') { if (localStorage.getItem('shouldFlush') == 'true') {
refresh(); refresh();
@@ -67,8 +61,18 @@
langs.includes(settings.device.lang)) { langs.includes(settings.device.lang)) {
lang = settings.device.lang; lang = settings.device.lang;
} }
window.lang = lang;
//#endregion //#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 // Detect the user agent
const ua = navigator.userAgent.toLowerCase(); const ua = navigator.userAgent.toLowerCase();
const isMobile = /mobile|iphone|ipad|android/.test(ua); const isMobile = /mobile|iphone|ipad|android/.test(ua);
@@ -106,7 +110,7 @@
// Note: 'async' make it possible to load the script asyncly. // Note: 'async' make it possible to load the script asyncly.
// 'defer' make it possible to run the script when the dom loaded. // 'defer' make it possible to run the script when the dom loaded.
const script = document.createElement('script'); 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('async', 'true');
script.setAttribute('defer', 'true'); script.setAttribute('defer', 'true');
head.appendChild(script); head.appendChild(script);
@@ -142,6 +146,8 @@
function refresh() { function refresh() {
localStorage.setItem('shouldFlush', 'false'); localStorage.setItem('shouldFlush', 'false');
localStorage.removeItem('locale');
// Random // Random
localStorage.setItem('salt', Math.random().toString().substr(2, 8)); 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) { if (!silent) {
$root.$dialog({ $root.$dialog({
title: '%i18n:common.update-available-title%', title: $root.$t('@.update-available-title'),
text: '%i18n:common.update-available%'.replace('{newer}', newer).replace('{current}', current) text: $root.$t('@.update-available', { newer, current })
}); });
} }

View File

@@ -5,8 +5,8 @@ export default ($root: any) => {
function adBlockDetected() { function adBlockDetected() {
$root.$dialog({ $root.$dialog({
title: '%fa:exclamation-triangle%%i18n:common.adblock.detected%', title: $root.$t('@.adblock.detected'),
text: '%i18n:common.adblock.warning%', text: $root.$t('@.adblock.warning'),
actins: [{ actins: [{
text: 'OK' text: 'OK'
}] }]

View File

@@ -1,8 +1,10 @@
const crypto = require('crypto'); // スクリプトサイズがデカい
//const crypto = require('crypto');
export default (data: ArrayBuffer) => { export default (data: ArrayBuffer) => {
const buf = new Buffer(data); //const buf = new Buffer(data);
const hash = crypto.createHash("md5"); //const hash = crypto.createHash("md5");
hash.update(buf); //hash.update(buf);
return hash.digest("hex"); //return hash.digest("hex");
return '';
}; };

View File

@@ -1,5 +1,6 @@
import parse from '../../../../mfm/parse'; import parse from '../../../../mfm/parse';
import { sum } from '../../../../prelude/array'; import { sum } from '../../../../prelude/array';
import shouldMuteNote from './should-mute-note';
import MkNoteMenu from '../views/components/note-menu.vue'; import MkNoteMenu from '../views/components/note-menu.vue';
import MkReactionPicker from '../views/components/reaction-picker.vue'; import MkReactionPicker from '../views/components/reaction-picker.vue';
import Ok from '../views/components/ok.vue'; import Ok from '../views/components/ok.vue';
@@ -22,7 +23,8 @@ type Opts = {
export default (opts: Opts = {}) => ({ export default (opts: Opts = {}) => ({
data() { data() {
return { 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: { methods: {
reply(viaKeyboard = false) { reply(viaKeyboard = false) {
this.$root.$post({ 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"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { host } from '../../../config'; import { host } from '../../../config';
import { toUnicode } from 'punycode';
export default Vue.extend({ export default Vue.extend({
props: ['user', 'detail'], props: ['user', 'detail'],
data() { data() {
return { 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"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { lib } from 'emojilib'; // スクリプトサイズがデカい
//import { lib } from 'emojilib';
export default Vue.extend({ export default Vue.extend({
props: { props: {
@@ -21,7 +22,7 @@ export default Vue.extend({
}, },
customEmojis: { customEmojis: {
required: false, required: false,
default: [] default: () => []
} }
}, },
@@ -50,10 +51,10 @@ export default Vue.extend({
this.customEmoji = customEmoji; this.customEmoji = customEmoji;
this.url = customEmoji.url; this.url = customEmoji.url;
} else { } else {
const emoji = lib[this.name]; //const emoji = lib[this.name];
if (emoji) { //if (emoji) {
this.char = emoji.char; // this.char = emoji.char;
} //}
} }
} else { } else {
this.char = this.emoji; this.char = this.emoji;

View File

@@ -32,8 +32,6 @@ import mediaList from './media-list.vue';
import uploader from './uploader.vue'; import uploader from './uploader.vue';
import streamIndicator from './stream-indicator.vue'; import streamIndicator from './stream-indicator.vue';
import ellipsis from './ellipsis.vue'; import ellipsis from './ellipsis.vue';
import messaging from './messaging.vue';
import messagingRoom from './messaging-room.vue';
import urlPreview from './url-preview.vue'; import urlPreview from './url-preview.vue';
import twitterSetting from './twitter-setting.vue'; import twitterSetting from './twitter-setting.vue';
import githubSetting from './github-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-uploader', uploader);
Vue.component('mk-stream-indicator', streamIndicator); Vue.component('mk-stream-indicator', streamIndicator);
Vue.component('mk-ellipsis', ellipsis); 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-url-preview', urlPreview);
Vue.component('mk-twitter-setting', twitterSetting); Vue.component('mk-twitter-setting', twitterSetting);
Vue.component('mk-github-setting', githubSetting); Vue.component('mk-github-setting', githubSetting);

View File

@@ -21,6 +21,14 @@
</div> </div>
</div> </div>
</section> </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> </ui-card>
</template> </template>
@@ -30,16 +38,27 @@ import i18n from '../../../i18n';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('common/views/components/mute-and-block.vue'), i18n: i18n('common/views/components/mute-and-block.vue'),
data() { data() {
return { return {
muteFetching: true, muteFetching: true,
blockFetching: true, blockFetching: true,
mute: [], mute: [],
block: [] block: [],
mutedWords: ''
}; };
}, },
computed: {
_mutedWords: {
get() { return this.$store.state.settings.mutedWords; },
set(value) { this.$store.dispatch('settings/set', { key: 'mutedWords', value }); }
},
},
mounted() { mounted() {
this.mutedWords = this._mutedWords.map(words => words.join(' ')).join('\n');
this.$root.api('mute/list').then(mute => { this.$root.api('mute/list').then(mute => {
this.mute = mute.map(x => x.mutee); this.mute = mute.map(x => x.mutee);
this.muteFetching = false; this.muteFetching = false;
@@ -49,6 +68,12 @@ export default Vue.extend({
this.block = blocking.map(x => x.blockee); this.block = blocking.map(x => x.blockee);
this.blockFetching = false; this.blockFetching = false;
}); });
},
methods: {
save() {
this._mutedWords = this.mutedWords.split('\n').map(line => line.split(' '));
}
} }
}); });
</script> </script>

View File

@@ -73,12 +73,13 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import { apiUrl, host } from '../../../config'; import { apiUrl, host } from '../../../config';
import { toUnicode } from 'punycode';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('common/views/components/profile-editor.vue'), i18n: i18n('common/views/components/profile-editor.vue'),
data() { data() {
return { return {
host, host: toUnicode(host),
name: null, name: null,
username: null, username: null,
location: null, location: null,

View File

@@ -21,6 +21,7 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import { apiUrl, host } from '../../../config'; import { apiUrl, host } from '../../../config';
import { toUnicode } from 'punycode';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('common/views/components/signin.vue'), i18n: i18n('common/views/components/signin.vue'),
@@ -39,7 +40,7 @@ export default Vue.extend({
password: '', password: '',
token: '', token: '',
apiUrl, apiUrl,
host host: toUnicode(host)
}; };
}, },
methods: { methods: {

View File

@@ -46,12 +46,13 @@ import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
const getPasswordStrength = require('syuilo-password-strength'); const getPasswordStrength = require('syuilo-password-strength');
import { host, url } from '../../../config'; import { host, url } from '../../../config';
import { toUnicode } from 'punycode';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('common/views/components/signup.vue'), i18n: i18n('common/views/components/signup.vue'),
data() { data() {
return { return {
host, host: toUnicode(host),
username: '', username: '',
password: '', password: '',
retypedPassword: '', retypedPassword: '',

View File

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

View File

@@ -1,5 +1,4 @@
import * as getCaretCoordinates from 'textarea-caret'; import * as getCaretCoordinates from 'textarea-caret';
import MkAutocomplete from '../components/autocomplete.vue';
import { toASCII } from 'punycode'; import { toASCII } from 'punycode';
export default { export default {
@@ -123,7 +122,7 @@ class Autocomplete {
/** /**
* サジェストを提示します。 * サジェストを提示します。
*/ */
private open(type, q) { private async open(type, q) {
if (type != this.currentType) { if (type != this.currentType) {
this.close(); this.close();
} }
@@ -143,6 +142,8 @@ class Autocomplete {
this.suggestion.y = y; this.suggestion.y = y;
this.suggestion.q = q; this.suggestion.q = q;
} else { } else {
const MkAutocomplete = await import('../components/autocomplete.vue').then(m => m.default);
// サジェスト要素作成 // サジェスト要素作成
this.suggestion = new MkAutocomplete({ this.suggestion = new MkAutocomplete({
parent: this.vm, parent: this.vm,

View File

@@ -1,6 +1,4 @@
declare const _LANG_: string; declare const _LANGS_: string[];
declare const _LANGS_: string;
declare const _LOCALE_: { [key: string]: any };
declare const _THEME_COLOR_: string; declare const _THEME_COLOR_: string;
declare const _COPYRIGHT_: string; declare const _COPYRIGHT_: string;
declare const _VERSION_: string; declare const _VERSION_: string;
@@ -15,9 +13,9 @@ export const hostname = address.hostname;
export const url = address.origin; export const url = address.origin;
export const apiUrl = url + '/api'; export const apiUrl = url + '/api';
export const wsUrl = url.replace('http://', 'ws://').replace('https://', 'wss://') + '/streaming'; 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 langs = _LANGS_;
export const locale = _LOCALE_; export const locale = JSON.parse(localStorage.getItem('locale'));
export const themeColor = _THEME_COLOR_; export const themeColor = _THEME_COLOR_;
export const copyright = _COPYRIGHT_; export const copyright = _COPYRIGHT_;
export const version = _VERSION_; 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 :class="$style.count" v-if="multiple && files.length > 0">({{ $t('chosen-files', { count: files.length }) }})</span>
</span> </span>
<mk-drive <x-drive
ref="browser" ref="browser"
:class="$style.browser" :class="$style.browser"
:multiple="multiple" :multiple="multiple"
@@ -25,6 +25,9 @@ import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/components/choose-file-from-drive-window.vue'), i18n: i18n('desktop/views/components/choose-file-from-drive-window.vue'),
components: {
XDrive: () => import('./drive.vue').then(m => m.default),
},
props: { props: {
multiple: { multiple: {
default: false default: false

View File

@@ -4,7 +4,7 @@
<span :class="$style.title">{{ $t('choose-prompt') }}</span> <span :class="$style.title">{{ $t('choose-prompt') }}</span>
</span> </span>
<mk-drive <x-drive
ref="browser" ref="browser"
:class="$style.browser" :class="$style.browser"
:multiple="false" :multiple="false"
@@ -21,6 +21,9 @@ import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/components/choose-folder-from-drive-window.vue'), i18n: i18n('desktop/views/components/choose-folder-from-drive-window.vue'),
components: {
XDrive: () => import('./drive.vue').then(m => m.default),
},
methods: { methods: {
ok() { ok() {
this.$emit('selected', (this.$refs.browser as any).folder); 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> <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> <span :class="$style.title"><fa icon="cloud"/>{{ $t('@.drive') }}</span>
</template> </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> </mk-window>
</template> </template>
@@ -15,6 +15,9 @@ import { url } from '../../../config';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/components/drive-window.vue'), i18n: i18n('desktop/views/components/drive-window.vue'),
components: {
XDrive: () => import('./drive.vue').then(m => m.default),
},
props: ['folder'], props: ['folder'],
data() { data() {
return { return {

View File

@@ -322,7 +322,7 @@ export default Vue.extend({
}); });
break; break;
default: 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({ export default Vue.extend({
i18n: i18n('desktop/views/components/game-window.vue'), i18n: i18n('desktop/views/components/game-window.vue'),
components: { components: {
XReversi: () => import('../../../common/views/components/games/reversi/reversi.vue') XReversi: () => import('../../../common/views/components/games/reversi/reversi.vue').then(m => m.default)
}, },
data() { data() {
return { return {

View File

@@ -16,7 +16,6 @@ import noteForm from './post-form.vue';
import renoteForm from './renote-form.vue'; import renoteForm from './renote-form.vue';
import followButton from './follow-button.vue'; import followButton from './follow-button.vue';
import notePreview from './note-preview.vue'; import notePreview from './note-preview.vue';
import drive from './drive.vue';
import noteDetail from './note-detail.vue'; import noteDetail from './note-detail.vue';
import settings from './settings.vue'; import settings from './settings.vue';
import calendar from './calendar.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-renote-form', renoteForm);
Vue.component('mk-follow-button', followButton); Vue.component('mk-follow-button', followButton);
Vue.component('mk-note-preview', notePreview); Vue.component('mk-note-preview', notePreview);
Vue.component('mk-drive', drive);
Vue.component('mk-note-detail', noteDetail); Vue.component('mk-note-detail', noteDetail);
Vue.component('mk-settings', settings); Vue.component('mk-settings', settings);
Vue.component('mk-calendar', calendar); Vue.component('mk-calendar', calendar);

View File

@@ -1,7 +1,7 @@
<template> <template>
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom"> <mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
<span slot="header" :class="$style.header"><fa icon="comments"/>{{ $t('title') }} {{ user | userName }}</span> <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> </mk-window>
</template> </template>
@@ -13,6 +13,9 @@ import getAcct from '../../../../../misc/acct/render';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/components/messaging-room-window.vue'), i18n: i18n('desktop/views/components/messaging-room-window.vue'),
components: {
XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
},
props: ['user'], props: ['user'],
computed: { computed: {
popout(): string { popout(): string {

View File

@@ -1,7 +1,7 @@
<template> <template>
<mk-window ref="window" width="500px" height="560px" @closed="destroyDom"> <mk-window ref="window" width="500px" height="560px" @closed="destroyDom">
<span slot="header" :class="$style.header"><fa icon="comments"/>{{ $t('title') }}</span> <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> </mk-window>
</template> </template>
@@ -12,6 +12,9 @@ import MkMessagingRoomWindow from './messaging-room-window.vue';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/components/messaging-window.vue'), i18n: i18n('desktop/views/components/messaging-window.vue'),
components: {
XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
},
methods: { methods: {
navigate(user) { navigate(user) {
this.$root.new(MkMessagingRoomWindow, { this.$root.new(MkMessagingRoomWindow, {

View File

@@ -2,7 +2,7 @@
<div <div
class="note" class="note"
:class="{ mini }" :class="{ mini }"
v-show="appearNote.deletedAt == null" v-show="appearNote.deletedAt == null && !hideThisNote"
:tabindex="appearNote.deletedAt == null ? '-1' : null" :tabindex="appearNote.deletedAt == null ? '-1' : null"
v-hotkey="keymap" v-hotkey="keymap"
:title="title" :title="title"

View File

@@ -36,7 +36,7 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import * as config from '../../../config'; import * as config from '../../../config';
import shouldMuteNote from '../../../common/scripts/should-mute-note';
import XNote from './note.vue'; import XNote from './note.vue';
const displayLimit = 30; const displayLimit = 30;
@@ -119,28 +119,8 @@ export default Vue.extend({
}, },
prepend(note, silent = false) { prepend(note, silent = false) {
//#region 弾く // 弾く
const isMyNote = note.userId == this.$store.state.i.id; if (shouldMuteNote(this.$store.state.i, this.$store.state.settings, note)) return;
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 (document.hidden || !this.isScrollTop()) { 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> <a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" :title="$t('click-to-tagging')">#{{ tag }}</a>
</div> </div>
<input v-show="useCw" v-model="cw" :placeholder="$t('annotations')"> <input v-show="useCw" v-model="cw" :placeholder="$t('annotations')">
<textarea :class="{ with: (files.length != 0 || poll) }" <div class="textarea">
ref="text" v-model="text" :disabled="posting" <textarea :class="{ with: (files.length != 0 || poll) }"
@keydown="onKeydown" @paste="onPaste" :placeholder="placeholder" ref="text" v-model="text" :disabled="posting"
v-autocomplete="'text'" @keydown="onKeydown" @paste="onPaste" :placeholder="placeholder"
></textarea> v-autocomplete="'text'"
<div class="files" :class="{ with: poll }" v-show="files.length != 0"> ></textarea>
<x-draggable :list="files" :options="{ animation: 150 }"> <button class="emoji" @click="emoji" ref="emoji">
<div v-for="file in files" :key="file.id"> <fa :icon="['far', 'laugh']"/>
<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div> </button>
<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" :title="$t('attach-cancel')" alt=""/> <div class="files" :class="{ with: poll }" v-show="files.length != 0">
</div> <x-draggable :list="files" :options="{ animation: 150 }">
</x-draggable> <div v-for="file in files" :key="file.id">
<p class="remain">{{ 4 - files.length }}/4</p> <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> </div>
<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="saveDraft()"/>
</div> </div>
<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/> <mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
<button class="upload" :title="$t('attach-media-from-local')" @click="chooseFile"><fa icon="upload"/></button> <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); 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() { post() {
this.posting = true; this.posting = true;
@@ -469,7 +487,7 @@ export default Vue.extend({
> .content > .content
> input > input
> textarea > .textarea > textarea
display block display block
width 100% width 100%
padding 12px padding 12px
@@ -498,27 +516,108 @@ export default Vue.extend({
> input > input
margin-bottom 8px margin-bottom 8px
> textarea > .textarea
margin 0 > .emoji
max-width 100% position absolute
min-width 100% top 0
min-height 84px right 0
padding 10px
font-size 18px
color var(--text)
opacity 0.5
&:hover &:hover
& + * color var(--textHighlighted)
& + * + * opacity 1
border-color var(--primaryAlpha02)
transition border-color .1s ease
&:focus &:active
& + * color var(--primary)
& + * + * opacity 1
border-color var(--primaryAlpha05)
transition border-color 0s ease
&.with > textarea
border-bottom solid 1px var(--primaryAlpha01) !important margin 0
border-radius 4px 4px 0 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 > .visibleUsers
margin-bottom 8px margin-bottom 8px
@@ -541,66 +640,6 @@ export default Vue.extend({
margin-right 8px margin-right 8px
white-space nowrap 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 > .mk-uploader
margin 8px 0 0 0 margin 8px 0 0 0
padding 8px padding 8px

View File

@@ -40,6 +40,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../../i18n'; import i18n from '../../../../i18n';
import shouldMuteNote from '../../../../common/scripts/should-mute-note';
import XNote from '../../components/note.vue'; import XNote from '../../components/note.vue';
@@ -135,28 +136,8 @@ export default Vue.extend({
}, },
prepend(note, silent = false) { prepend(note, silent = false) {
//#region 弾く // 弾く
const isMyNote = note.userId == this.$store.state.i.id; if (shouldMuteNote(this.$store.state.i, this.$store.state.settings, note)) return;
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 (document.hidden) { if (document.hidden) {

View File

@@ -25,9 +25,6 @@ import i18n from '../../../../i18n';
import XColumnCore from './deck.column-core.vue'; import XColumnCore from './deck.column-core.vue';
import Menu from '../../../../common/views/components/menu.vue'; import Menu from '../../../../common/views/components/menu.vue';
import MkUserListsWindow from '../../components/user-lists-window.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'; import * as uuid from 'uuid';
@@ -35,9 +32,9 @@ export default Vue.extend({
i18n: i18n('deck'), i18n: i18n('deck'),
components: { components: {
XColumnCore, XColumnCore,
XUserColumn, XUserColumn: () => import('./deck.user-column.vue').then(m => m.default),
XNoteColumn, XNoteColumn: () => import('./deck.note-column.vue').then(m => m.default),
XHashtagColumn XHashtagColumn: () => import('./deck.hashtag-column.vue').then(m => m.default)
}, },
computed: { computed: {

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="mk-drive-page"> <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> </div>
</template> </template>
@@ -10,6 +10,9 @@ import i18n from '../../../i18n';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/pages/drive.vue'), i18n: i18n('desktop/views/pages/drive.vue'),
components: {
XDrive: () => import('../components/drive.vue').then(m => m.default),
},
data() { data() {
return { return {
folder: null folder: null

View File

@@ -4,7 +4,9 @@
<template v-for="favorite in favorites"> <template v-for="favorite in favorites">
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/> <mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
</template> </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> </main>
</mk-ui> </mk-ui>
</template> </template>
@@ -75,4 +77,9 @@ main
> .post > .post
margin-bottom 16px margin-bottom 16px
> .more
margin 32px 16px 16px 16px
text-align center
</style> </style>

View File

@@ -9,7 +9,7 @@ import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {
XReversi: () => import('../../../../common/views/components/games/reversi/reversi.vue') XReversi: () => import('../../../../common/views/components/games/reversi/reversi.vue').then(m => m.default)
}, },
props: { props: {
ui: { ui: {

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="mk-messaging-room-page"> <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> </div>
</template> </template>
@@ -13,6 +13,9 @@ import getUserName from '../../../../../misc/get-user-name';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('.vue'), i18n: i18n('.vue'),
components: {
XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
},
data() { data() {
return { return {
fetching: true, fetching: true,

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="mkp-selectdrive"> <div class="mkp-selectdrive">
<mk-drive ref="browser" <x-drive ref="browser"
:multiple="multiple" :multiple="multiple"
@selected="onSelected" @selected="onSelected"
@change-selection="onChangeSelection" @change-selection="onChangeSelection"
@@ -19,6 +19,9 @@ import i18n from '../../../i18n';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/pages/selectdrive.vue'), i18n: i18n('desktop/views/pages/selectdrive.vue'),
components: {
XDrive: () => import('../components/drive.vue').then(m => m.default),
},
data() { data() {
return { return {
files: [] files: []

View File

@@ -18,7 +18,7 @@
</div> </div>
<div class="info"> <div class="info">
<span class="location" v-if="user.host === null && user.profile.location"><fa icon="map-marker"/> {{ user.profile.location }}</span> <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>
<div class="status"> <div class="status">
<span class="notes-count"><b>{{ user.notesCount | number }}</b>{{ $t('posts') }}</span> <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"/> <mk-follow-button :user="user" size="big"/>
<p class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</p> <p class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</p>
<p class="stalk" v-if="user.isFollowing"> <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> <span v-if="!user.isStalking"><a @click="stalk"><fa icon="user-secret"/> {{ $t('stalk') }}</a></span>
</p> </p>
</div> </div>

View File

@@ -151,6 +151,7 @@ import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import { host, copyright } from '../../../config'; import { host, copyright } from '../../../config';
import { concat } from '../../../../../prelude/array'; import { concat } from '../../../../../prelude/array';
import { toUnicode } from 'punycode';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/pages/welcome.vue'), i18n: i18n('desktop/views/pages/welcome.vue'),
@@ -160,7 +161,7 @@ export default Vue.extend({
stats: null, stats: null,
banner: null, banner: null,
copyright, copyright,
host, host: toUnicode(host),
name: 'Misskey', name: 'Misskey',
description: '', description: '',
announcements: [], announcements: [],

View File

@@ -4,7 +4,7 @@
<template slot="header"><fa icon="comments"/>{{ $t('title') }}</template> <template slot="header"><fa icon="comments"/>{{ $t('title') }}</template>
<button slot="func" @click="add"><fa icon="plus"/></button> <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> </mk-widget-container>
</div> </div>
</template> </template>
@@ -22,6 +22,9 @@ export default define({
}) })
}).extend({ }).extend({
i18n: i18n('desktop/views/widgets/messaging.vue'), i18n: i18n('desktop/views/widgets/messaging.vue'),
components: {
XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
},
methods: { methods: {
navigate(user) { navigate(user) {
this.$root.new(MkMessagingRoomWindow, { this.$root.new(MkMessagingRoomWindow, {

View File

@@ -1,16 +1,48 @@
<template> <template>
<div class="mkw-post-form"> <div>
<template v-if="props.design == 0"> <mk-widget-container :show-header="props.design == 0">
<p class="title"><fa icon="pencil-alt"/>{{ $t('title') }}</p> <template slot="header"><fa icon="pencil-alt"/>{{ $t('title') }}</template>
</template>
<textarea :disabled="posting" v-model="text" @keydown="onKeydown" :placeholder="placeholder"></textarea> <div class="lhcuptdmcdkfwmipgazeawoiuxpzaclc-body">
<button @click="post" :disabled="posting">{{ $t('note') }}</button> <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> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import define from '../../../common/define-widget'; import define from '../../../common/define-widget';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import insertTextAtCursor from 'insert-text-at-cursor';
import * as XDraggable from 'vuedraggable';
export default define({ export default define({
name: 'post-form', name: 'post-form',
@@ -19,12 +51,19 @@ export default define({
}) })
}).extend({ }).extend({
i18n: i18n('desktop/views/widgets/post-form.vue'), i18n: i18n('desktop/views/widgets/post-form.vue'),
components: {
XDraggable
},
data() { data() {
return { return {
posting: false, posting: false,
text: '' text: '',
files: [],
}; };
}, },
computed: { computed: {
placeholder(): string { placeholder(): string {
const xs = [ const xs = [
@@ -38,6 +77,7 @@ export default define({
return xs[Math.floor(Math.random() * xs.length)]; return xs[Math.floor(Math.random() * xs.length)];
} }
}, },
methods: { methods: {
func() { func() {
if (this.props.design == 1) { if (this.props.design == 1) {
@@ -47,14 +87,68 @@ export default define({
} }
this.save(); 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) { onKeydown(e) {
if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && !this.posting && this.text) this.post(); 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() { post() {
this.posting = true; this.posting = true;
this.$root.api('notes/create', { 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 => { }).then(data => {
this.clear(); this.clear();
}).catch(err => { }).catch(err => {
@@ -63,66 +157,114 @@ export default define({
this.posting = false; this.posting = false;
}); });
}, },
clear() { clear() {
this.text = ''; this.text = '';
this.files = [];
} }
} }
}); });
</script> </script>
<style lang="stylus" scoped> <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 &:active
background #fff color var(--primary)
overflow hidden opacity 1
border solid 1px rgba(#000, 0.075)
border-radius 6px
> .title > textarea
z-index 1 display block
margin 0 width 100%
padding 0 16px max-width 100%
line-height 42px min-width 100%
font-size 0.9em padding 16px
font-weight bold color var(--desktopPostFormTextareaFg)
color #888 outline none
box-shadow 0 1px rgba(#000, 0.07) background var(--desktopPostFormTextareaBg)
border none
border-bottom solid 1px var(--faceDivider)
> [data-icon] &:focus
margin-right 4px & + .emoji
opacity 0.7
> textarea > .files
display block > div
width 100% padding 4px
max-width 100%
min-width 100%
padding 16px
margin-bottom 28px + 16px
border none
border-bottom solid 1px #eee
> button &:after
display block content ""
position absolute display block
bottom 8px clear both
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
&:hover > div
background var(--primaryLighten10) !important float left
border solid 4px transparent
cursor move
&:active &:hover > .remove
background var(--primaryDarken10) !important display block
transition background 0s ease
> .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> </style>

View File

@@ -6,7 +6,7 @@
<button class="close" @click="cancel"><fa icon="times"/></button> <button class="close" @click="cancel"><fa icon="times"/></button>
<button v-if="multiple" class="ok" @click="ok"><fa icon="check"/></button> <button v-if="multiple" class="ok" @click="ok"><fa icon="check"/></button>
</header> </header>
<mk-drive class="drive" ref="browser" <x-drive class="drive" ref="browser"
:select-file="true" :select-file="true"
:multiple="multiple" :multiple="multiple"
@change-selection="onChangeSelection" @change-selection="onChangeSelection"
@@ -22,6 +22,9 @@ import i18n from '../../../i18n';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('mobile/views/components/drive-file-chooser.vue'), i18n: i18n('mobile/views/components/drive-file-chooser.vue'),
components: {
XDrive: () => import('./drive.vue').then(m => m.default),
},
props: ['multiple'], props: ['multiple'],
data() { data() {
return { return {

View File

@@ -6,7 +6,7 @@
<button class="close" @click="cancel"><fa icon="times"/></button> <button class="close" @click="cancel"><fa icon="times"/></button>
<button class="ok" @click="ok"><fa icon="check"/></button> <button class="ok" @click="ok"><fa icon="check"/></button>
</header> </header>
<mk-drive ref="browser" <x-drive ref="browser"
select-folder select-folder
/> />
</div> </div>
@@ -18,6 +18,9 @@ import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('mobile/views/components/drive-folder-chooser.vue'), i18n: i18n('mobile/views/components/drive-folder-chooser.vue'),
components: {
XDrive: () => import('./drive.vue').then(m => m.default),
},
methods: { methods: {
cancel() { cancel() {
this.$emit('canceled'); this.$emit('canceled');

View File

@@ -5,7 +5,6 @@ import note from './note.vue';
import notes from './notes.vue'; import notes from './notes.vue';
import mediaImage from './media-image.vue'; import mediaImage from './media-image.vue';
import mediaVideo from './media-video.vue'; import mediaVideo from './media-video.vue';
import drive from './drive.vue';
import notePreview from './note-preview.vue'; import notePreview from './note-preview.vue';
import subNoteContent from './sub-note-content.vue'; import subNoteContent from './sub-note-content.vue';
import noteCard from './note-card.vue'; import noteCard from './note-card.vue';
@@ -29,7 +28,6 @@ Vue.component('mk-note', note);
Vue.component('mk-notes', notes); Vue.component('mk-notes', notes);
Vue.component('mk-media-image', mediaImage); Vue.component('mk-media-image', mediaImage);
Vue.component('mk-media-video', mediaVideo); Vue.component('mk-media-video', mediaVideo);
Vue.component('mk-drive', drive);
Vue.component('mk-note-preview', notePreview); Vue.component('mk-note-preview', notePreview);
Vue.component('mk-sub-note-content', subNoteContent); Vue.component('mk-sub-note-content', subNoteContent);
Vue.component('mk-note-card', noteCard); Vue.component('mk-note-card', noteCard);

View File

@@ -1,7 +1,7 @@
<template> <template>
<div <div
class="note" class="note"
v-show="appearNote.deletedAt == null" v-show="appearNote.deletedAt == null && !hideThisNote"
:tabindex="appearNote.deletedAt == null ? '-1' : null" :tabindex="appearNote.deletedAt == null ? '-1' : null"
:class="{ renote: isRenote, smart: $store.state.device.postStyle == 'smart' }" :class="{ renote: isRenote, smart: $store.state.device.postStyle == 'smart' }"
v-hotkey="keymap" v-hotkey="keymap"

View File

@@ -35,6 +35,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import shouldMuteNote from '../../../common/scripts/should-mute-note';
const displayLimit = 30; const displayLimit = 30;
@@ -118,28 +119,8 @@ export default Vue.extend({
}, },
prepend(note, silent = false) { prepend(note, silent = false) {
//#region 弾く // 弾く
const isMyNote = note.userId == this.$store.state.i.id; if (shouldMuteNote(this.$store.state.i, this.$store.state.settings, note)) return;
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 (document.hidden || !this.isScrollTop()) { 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> <template v-if="!folder && !file"><span style="margin-right:4px;"><fa icon="cloud"/></span>{{ $t('@.drive') }}</template>
</span> </span>
<template slot="func"><button @click="fn"><fa icon="ellipsis-h"/></button></template> <template slot="func"><button @click="fn"><fa icon="ellipsis-h"/></button></template>
<mk-drive <x-drive
ref="browser" ref="browser"
:init-folder="initFolder" :init-folder="initFolder"
:init-file="initFile" :init-file="initFile"
@@ -29,6 +29,9 @@ import Progress from '../../../common/scripts/loading';
export default Vue.extend({ export default Vue.extend({
i18n: i18n(), i18n: i18n(),
components: {
XDrive: () => import('../components/drive.vue').then(m => m.default),
},
data() { data() {
return { return {
Progress, Progress,

View File

@@ -6,7 +6,7 @@
<template v-for="favorite in favorites"> <template v-for="favorite in favorites">
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/> <mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
</template> </template>
<a v-if="existMore" @click="more">{{ $t('@.load-more') }}</a> <ui-button v-if="existMore" @click="more">{{ $t('@.load-more') }}</ui-button>
</main> </main>
</mk-ui> </mk-ui>
</template> </template>
@@ -73,8 +73,6 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
main main
width 100% width 100%
max-width 680px max-width 680px

View File

@@ -12,7 +12,7 @@ import i18n from '../../../../i18n';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('mobile/views/pages/games/reversi.vue'), i18n: i18n('mobile/views/pages/games/reversi.vue'),
components: { components: {
XReversi: () => import('../../../../common/views/components/games/reversi/reversi.vue') XReversi: () => import('../../../../common/views/components/games/reversi/reversi.vue').then(m => m.default)
}, },
mounted() { mounted() {
document.title = `${this.$root.instanceName} %i18n:@reversi%`; 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-if="user"><span style="margin-right:4px;"><fa :icon="['far', 'comments']"/></span>{{ user | userName }}</template>
<template v-else><mk-ellipsis/></template> <template v-else><mk-ellipsis/></template>
</span> </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> </mk-ui>
</template> </template>
@@ -15,6 +15,9 @@ import parseAcct from '../../../../../misc/acct/parse';
export default Vue.extend({ export default Vue.extend({
i18n: i18n(), i18n: i18n(),
components: {
XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
},
data() { data() {
return { return {
fetching: true, fetching: true,

View File

@@ -1,7 +1,7 @@
<template> <template>
<mk-ui> <mk-ui>
<span slot="header"><span style="margin-right:4px;"><fa :icon="['far', 'comments']"/></span>{{ $t('@.messaging') }}</span> <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> </mk-ui>
</template> </template>
@@ -12,6 +12,9 @@ import getAcct from '../../../../../misc/acct/render';
export default Vue.extend({ export default Vue.extend({
i18n: i18n(), i18n: i18n(),
components: {
XMessaging: () => import('../../../common/views/components/messaging.vue').then(m => m.default)
},
mounted() { mounted() {
document.title = `${this.$root.instanceName} ${this.$t('@.messaging')}`; document.title = `${this.$root.instanceName} ${this.$t('@.messaging')}`;
}, },

View File

@@ -5,7 +5,7 @@
<button class="upload" @click="upload"><fa icon="upload"/></button> <button class="upload" @click="upload"><fa icon="upload"/></button>
<button v-if="multiple" class="ok" @click="ok"><fa icon="check"/></button> <button v-if="multiple" class="ok" @click="ok"><fa icon="check"/></button>
</header> </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> </div>
</template> </template>
@@ -15,6 +15,9 @@ import i18n from '../../../i18n';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('mobile/views/pages/selectdrive.vue'), i18n: i18n('mobile/views/pages/selectdrive.vue'),
components: {
XDrive: () => import('../components/drive.vue').then(m => m.default),
},
data() { data() {
return { return {
files: [] files: []

View File

@@ -27,7 +27,7 @@
<fa icon="map-marker"/>{{ user.profile.location }} <fa icon="map-marker"/>{{ user.profile.location }}
</p> </p>
<p class="birthday" v-if="user.host === null && user.profile.birthday"> <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> </p>
</div> </div>
<div class="status"> <div class="status">

View File

@@ -76,6 +76,7 @@ import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import { copyright, host } from '../../../config'; import { copyright, host } from '../../../config';
import { concat } from '../../../../../prelude/array'; import { concat } from '../../../../../prelude/array';
import { toUnicode } from 'punycode';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('mobile/views/pages/welcome.vue'), i18n: i18n('mobile/views/pages/welcome.vue'),
@@ -85,7 +86,7 @@ export default Vue.extend({
copyright, copyright,
stats: null, stats: null,
banner: null, banner: null,
host, host: toUnicode(host),
name: 'Misskey', name: 'Misskey',
description: '', description: '',
photos: [], photos: [],

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ import * as portscanner from 'portscanner';
import isRoot = require('is-root'); import isRoot = require('is-root');
import Xev from 'xev'; import Xev from 'xev';
import * as program from 'commander'; import * as program from 'commander';
import mongo from './db/mongodb'; import mongo, { nativeDbConn } from './db/mongodb';
import Logger from './misc/logger'; import Logger from './misc/logger';
import EnvironmentInfo from './misc/environmentInfo'; import EnvironmentInfo from './misc/environmentInfo';
@@ -23,6 +23,7 @@ import serverStats from './daemons/server-stats';
import notesStats from './daemons/notes-stats'; import notesStats from './daemons/notes-stats';
import loadConfig from './config/load'; import loadConfig from './config/load';
import { Config } from './config/types'; import { Config } from './config/types';
import { lessThan } from './prelude/array';
const clusterLog = debug('misskey:cluster'); const clusterLog = debug('misskey:cluster');
const ev = new Xev(); const ev = new Xev();
@@ -158,11 +159,19 @@ function checkMongoDb(config: Config) {
mongoDBLogger.info(`Connecting to ${uri}`); mongoDBLogger.info(`Connecting to ${uri}`);
mongo.then(() => { 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'); mongoDBLogger.succ('Connectivity confirmed');
}) })
.catch(err => { .catch(err => {
mongoDBLogger.error(err.message); mongoDBLogger.error(err.message);
}); });
} }
function spawnWorkers(limit: number) { 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[][] { export function groupOn<T, S>(f: (x: T) => S, xs: T[]): T[][] {
return groupBy((a, b) => f(a) === f(b), xs); 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 }); return await User.findOne({ usernameLower, host: null });
} }
const configHostAscii = toASCII(config.host).toLowerCase();
const configHost = toUnicode(configHostAscii);
const hostAscii = toASCII(_host).toLowerCase(); const hostAscii = toASCII(_host).toLowerCase();
const host = toUnicode(hostAscii); const host = toUnicode(hostAscii);
if (config.host == host) { if (configHost == host) {
log(`return local user: ${usernameLower}`); log(`return local user: ${usernameLower}`);
return await User.findOne({ usernameLower, host: null }); return await User.findOne({ usernameLower, host: null });
} }

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ export default abstract class Channel {
public id: string; public id: string;
public abstract readonly chName: string; public abstract readonly chName: string;
public static readonly shouldShare: boolean; public static readonly shouldShare: boolean;
public static readonly requireCredential: boolean;
protected get user() { protected get user() {
return this.connection.user; return this.connection.user;

View File

@@ -4,6 +4,7 @@ import Channel from '../channel';
export default class extends Channel { export default class extends Channel {
public readonly chName = 'apLog'; public readonly chName = 'apLog';
public static shouldShare = true; public static shouldShare = true;
public static requireCredential = false;
@autobind @autobind
public async init(params: any) { public async init(params: any) {

View File

@@ -4,6 +4,7 @@ import Channel from '../channel';
export default class extends Channel { export default class extends Channel {
public readonly chName = 'drive'; public readonly chName = 'drive';
public static shouldShare = true; public static shouldShare = true;
public static requireCredential = true;
@autobind @autobind
public async init(params: any) { public async init(params: any) {

View File

@@ -10,6 +10,7 @@ import Channel from '../../channel';
export default class extends Channel { export default class extends Channel {
public readonly chName = 'gamesReversiGame'; public readonly chName = 'gamesReversiGame';
public static shouldShare = false; public static shouldShare = false;
public static requireCredential = false;
private gameId: mongo.ObjectID; private gameId: mongo.ObjectID;

View File

@@ -7,6 +7,7 @@ import Channel from '../../channel';
export default class extends Channel { export default class extends Channel {
public readonly chName = 'gamesReversi'; public readonly chName = 'gamesReversi';
public static shouldShare = true; public static shouldShare = true;
public static requireCredential = true;
@autobind @autobind
public async init(params: any) { public async init(params: any) {

View File

@@ -7,6 +7,7 @@ import Channel from '../channel';
export default class extends Channel { export default class extends Channel {
public readonly chName = 'globalTimeline'; public readonly chName = 'globalTimeline';
public static shouldShare = true; public static shouldShare = true;
public static requireCredential = false;
private mutedUserIds: string[] = []; private mutedUserIds: string[] = [];

View File

@@ -7,6 +7,7 @@ import Channel from '../channel';
export default class extends Channel { export default class extends Channel {
public readonly chName = 'hashtag'; public readonly chName = 'hashtag';
public static shouldShare = false; public static shouldShare = false;
public static requireCredential = false;
@autobind @autobind
public async init(params: any) { public async init(params: any) {

View File

@@ -7,6 +7,7 @@ import Channel from '../channel';
export default class extends Channel { export default class extends Channel {
public readonly chName = 'homeTimeline'; public readonly chName = 'homeTimeline';
public static shouldShare = true; public static shouldShare = true;
public static requireCredential = true;
private mutedUserIds: string[] = []; private mutedUserIds: string[] = [];

View File

@@ -7,6 +7,7 @@ import Channel from '../channel';
export default class extends Channel { export default class extends Channel {
public readonly chName = 'hybridTimeline'; public readonly chName = 'hybridTimeline';
public static shouldShare = true; public static shouldShare = true;
public static requireCredential = true;
private mutedUserIds: string[] = []; private mutedUserIds: string[] = [];

View File

@@ -7,6 +7,7 @@ import Channel from '../channel';
export default class extends Channel { export default class extends Channel {
public readonly chName = 'localTimeline'; public readonly chName = 'localTimeline';
public static shouldShare = true; public static shouldShare = true;
public static requireCredential = false;
private mutedUserIds: string[] = []; private mutedUserIds: string[] = [];

View File

@@ -5,6 +5,7 @@ import Channel from '../channel';
export default class extends Channel { export default class extends Channel {
public readonly chName = 'main'; public readonly chName = 'main';
public static shouldShare = true; public static shouldShare = true;
public static requireCredential = true;
@autobind @autobind
public async init(params: any) { public async init(params: any) {

View File

@@ -4,6 +4,7 @@ import Channel from '../channel';
export default class extends Channel { export default class extends Channel {
public readonly chName = 'messagingIndex'; public readonly chName = 'messagingIndex';
public static shouldShare = true; public static shouldShare = true;
public static requireCredential = true;
@autobind @autobind
public async init(params: any) { public async init(params: any) {

View File

@@ -5,6 +5,7 @@ import Channel from '../channel';
export default class extends Channel { export default class extends Channel {
public readonly chName = 'messaging'; public readonly chName = 'messaging';
public static shouldShare = false; public static shouldShare = false;
public static requireCredential = true;
private otherpartyId: string; private otherpartyId: string;

View File

@@ -7,6 +7,7 @@ const ev = new Xev();
export default class extends Channel { export default class extends Channel {
public readonly chName = 'notesStats'; public readonly chName = 'notesStats';
public static shouldShare = true; public static shouldShare = true;
public static requireCredential = false;
@autobind @autobind
public async init(params: any) { public async init(params: any) {

View File

@@ -7,6 +7,7 @@ const ev = new Xev();
export default class extends Channel { export default class extends Channel {
public readonly chName = 'serverStats'; public readonly chName = 'serverStats';
public static shouldShare = true; public static shouldShare = true;
public static requireCredential = false;
@autobind @autobind
public async init(params: any) { public async init(params: any) {

Some files were not shown because too many files have changed in this diff Show More