Compare commits

..

24 Commits

Author SHA1 Message Date
95ce8dce3d 8.28.1 2018-09-07 05:32:18 +09:00
0b5eec4ca8 Fix bug 2018-09-07 05:32:09 +09:00
6d9716f90e 8.28.0 2018-09-07 04:24:08 +09:00
aa31061d90 fix(package): update node-sass-json-importer to version 4.0.1 (#2645) 2018-09-07 04:23:26 +09:00
acc7797dff New Crowdin translations (#2615)
* 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 (English)
2018-09-07 04:22:16 +09:00
7959196dc7 Add sum function (#2653) 2018-09-07 04:21:04 +09:00
c6ff6939a5 Add capitalize function (#2651) 2018-09-07 03:22:55 +09:00
769960f29e Encode fetch URI if needed (#2649) 2018-09-07 02:26:31 +09:00
d92e9759f3 Refactor analog clock widget (#2648) 2018-09-07 01:20:23 +09:00
bf7e19b288 🎨 2018-09-07 01:18:47 +09:00
98954cd6d4 Trim image 2018-09-07 01:02:31 +09:00
538bb978ed Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-09-07 00:52:21 +09:00
10232c5866 Fix bug & some refactor 2018-09-07 00:52:13 +09:00
5cd6a0db16 Fix typo: serive -> service (#2647) 2018-09-07 00:44:57 +09:00
ff0a05a2d6 Add unique function (#2644) 2018-09-07 00:10:03 +09:00
e34b264af2 Fix bug (#2643) 2018-09-07 00:03:44 +09:00
00d79487cd Add erase function (#2641) 2018-09-07 00:02:55 +09:00
3cace734c7 Add concat function (#2640) 2018-09-06 21:31:15 +09:00
f428372869 Refactor effects function (#2639) 2018-09-06 20:06:16 +09:00
5dd2feba9b fix(package): update @types/minio to version 7.0.0 (#2626) 2018-09-06 19:55:29 +09:00
a1b026239e fix(package): update @types/ws to version 6.0.1 (#2636) 2018-09-06 19:55:20 +09:00
40735ce76b Fix bug (#2638) 2018-09-06 19:28:52 +09:00
4a00c13b33 🎨 2018-09-06 16:03:00 +09:00
8e359d54bd if elimination (#2635) 2018-09-06 06:06:22 +09:00
46 changed files with 215 additions and 136 deletions

View File

@ -865,6 +865,8 @@ desktop/views/pages/welcome.vue:
signin-button: "やってる" signin-button: "やってる"
signup-button: "やる" signup-button: "やる"
timeline: "タイムライン" timeline: "タイムライン"
announcements: "お知らせ"
photos: "最近の画像"
powered-by-misskey: "Powered by <b>Misskey</b>." powered-by-misskey: "Powered by <b>Misskey</b>."
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Misskey Drive" title: "Misskey Drive"
@ -1161,6 +1163,9 @@ mobile/views/pages/settings.vue:
post-style: "投稿の表示スタイル" post-style: "投稿の表示スタイル"
post-style-standard: "標準" post-style-standard: "標準"
post-style-smart: "スマート" post-style-smart: "スマート"
notification-position: "通知の表示"
notification-position-bottom: "下"
notification-position-top: "上"
behavior: "動作" behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
disable-via-mobile: "「モバイルからの投稿」フラグを付けない" disable-via-mobile: "「モバイルからの投稿」フラグを付けない"

View File

@ -865,6 +865,8 @@ desktop/views/pages/welcome.vue:
signin-button: "やってる" signin-button: "やってる"
signup-button: "やる" signup-button: "やる"
timeline: "タイムライン" timeline: "タイムライン"
announcements: "お知らせ"
photos: "最近の画像"
powered-by-misskey: "Powered by <b>Misskey</b>." powered-by-misskey: "Powered by <b>Misskey</b>."
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Misskey Drive" title: "Misskey Drive"
@ -1161,6 +1163,9 @@ mobile/views/pages/settings.vue:
post-style: "投稿の表示スタイル" post-style: "投稿の表示スタイル"
post-style-standard: "標準" post-style-standard: "標準"
post-style-smart: "スマート" post-style-smart: "スマート"
notification-position: "通知の表示"
notification-position-bottom: "下"
notification-position-top: "上"
behavior: "動作" behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
disable-via-mobile: "「モバイルからの投稿」フラグを付けない" disable-via-mobile: "「モバイルからの投稿」フラグを付けない"

View File

@ -865,6 +865,8 @@ desktop/views/pages/welcome.vue:
signin-button: "Logging in..." signin-button: "Logging in..."
signup-button: "Sign up" signup-button: "Sign up"
timeline: "Timeline" timeline: "Timeline"
announcements: "Announcements"
photos: "Recent uploaded"
powered-by-misskey: "Powered by <b>Misskey</b>." powered-by-misskey: "Powered by <b>Misskey</b>."
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Misskey storage" title: "Misskey storage"
@ -1161,6 +1163,9 @@ mobile/views/pages/settings.vue:
post-style: "Post design" post-style: "Post design"
post-style-standard: "Standard" post-style-standard: "Standard"
post-style-smart: "Smart" post-style-smart: "Smart"
notification-position: "Notification style"
notification-position-bottom: "Bottom"
notification-position-top: "Top"
behavior: "Behavior" behavior: "Behavior"
fetch-on-scroll: "Endless loading on scroll" fetch-on-scroll: "Endless loading on scroll"
disable-via-mobile: "Don't mark the post as 'from mobile'" disable-via-mobile: "Don't mark the post as 'from mobile'"

View File

@ -865,6 +865,8 @@ desktop/views/pages/welcome.vue:
signin-button: "やってる" signin-button: "やってる"
signup-button: "やる" signup-button: "やる"
timeline: "タイムライン" timeline: "タイムライン"
announcements: "お知らせ"
photos: "最近の画像"
powered-by-misskey: "Powered by <b>Misskey</b>." powered-by-misskey: "Powered by <b>Misskey</b>."
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Misskey Drive" title: "Misskey Drive"
@ -1161,6 +1163,9 @@ mobile/views/pages/settings.vue:
post-style: "投稿の表示スタイル" post-style: "投稿の表示スタイル"
post-style-standard: "標準" post-style-standard: "標準"
post-style-smart: "スマート" post-style-smart: "スマート"
notification-position: "通知の表示"
notification-position-bottom: "下"
notification-position-top: "上"
behavior: "動作" behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
disable-via-mobile: "「モバイルからの投稿」フラグを付けない" disable-via-mobile: "「モバイルからの投稿」フラグを付けない"

View File

@ -865,6 +865,8 @@ desktop/views/pages/welcome.vue:
signin-button: "Se connecter" signin-button: "Se connecter"
signup-button: "S'inscrire" signup-button: "S'inscrire"
timeline: "Fil d'actualité" timeline: "Fil d'actualité"
announcements: "お知らせ"
photos: "最近の画像"
powered-by-misskey: "Propulsé par <b>Misskey</b>." powered-by-misskey: "Propulsé par <b>Misskey</b>."
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Lecteur de Misskey" title: "Lecteur de Misskey"
@ -1161,6 +1163,9 @@ mobile/views/pages/settings.vue:
post-style: "Style de la publication" post-style: "Style de la publication"
post-style-standard: "Standard" post-style-standard: "Standard"
post-style-smart: "Intelligent" post-style-smart: "Intelligent"
notification-position: "通知の表示"
notification-position-bottom: "下"
notification-position-top: "上"
behavior: "Comportement" behavior: "Comportement"
fetch-on-scroll: "Chargement lors du défilement" fetch-on-scroll: "Chargement lors du défilement"
disable-via-mobile: "Ne pas mentionner que ma publication provient d'un 'périphérique mobile'" disable-via-mobile: "Ne pas mentionner que ma publication provient d'un 'périphérique mobile'"

View File

@ -865,6 +865,8 @@ desktop/views/pages/welcome.vue:
signin-button: "やってる" signin-button: "やってる"
signup-button: "やる" signup-button: "やる"
timeline: "タイムライン" timeline: "タイムライン"
announcements: "お知らせ"
photos: "最近の画像"
powered-by-misskey: "Powered by <b>Misskey</b>." powered-by-misskey: "Powered by <b>Misskey</b>."
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Misskey Drive" title: "Misskey Drive"
@ -1161,6 +1163,9 @@ mobile/views/pages/settings.vue:
post-style: "投稿の表示スタイル" post-style: "投稿の表示スタイル"
post-style-standard: "標準" post-style-standard: "標準"
post-style-smart: "スマート" post-style-smart: "スマート"
notification-position: "通知の表示"
notification-position-bottom: "下"
notification-position-top: "上"
behavior: "動作" behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
disable-via-mobile: "「モバイルからの投稿」フラグを付けない" disable-via-mobile: "「モバイルからの投稿」フラグを付けない"

View File

@ -865,6 +865,8 @@ desktop/views/pages/welcome.vue:
signin-button: "サインイン中…" signin-button: "サインイン中…"
signup-button: "サインアップ" signup-button: "サインアップ"
timeline: "タイムライン" timeline: "タイムライン"
announcements: "お知らせ"
photos: "最近の画像"
powered-by-misskey: "Powered by <b>Misskey</b>." powered-by-misskey: "Powered by <b>Misskey</b>."
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "ドライブ" title: "ドライブ"
@ -1161,6 +1163,9 @@ mobile/views/pages/settings.vue:
post-style: "投稿の表示スタイル" post-style: "投稿の表示スタイル"
post-style-standard: "標準" post-style-standard: "標準"
post-style-smart: "べっぴんさん" post-style-smart: "べっぴんさん"
notification-position: "通知の表示"
notification-position-bottom: "下"
notification-position-top: "上"
behavior: "動作" behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
disable-via-mobile: "「モバイルからの投稿」フラグを付けない" disable-via-mobile: "「モバイルからの投稿」フラグを付けない"

View File

@ -865,6 +865,8 @@ desktop/views/pages/welcome.vue:
signin-button: "やってる" signin-button: "やってる"
signup-button: "やる" signup-button: "やる"
timeline: "タイムライン" timeline: "タイムライン"
announcements: "お知らせ"
photos: "最近の画像"
powered-by-misskey: "Powered by <b>Misskey</b>." powered-by-misskey: "Powered by <b>Misskey</b>."
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Misskey Drive" title: "Misskey Drive"
@ -1161,6 +1163,9 @@ mobile/views/pages/settings.vue:
post-style: "投稿の表示スタイル" post-style: "投稿の表示スタイル"
post-style-standard: "標準" post-style-standard: "標準"
post-style-smart: "スマート" post-style-smart: "スマート"
notification-position: "通知の表示"
notification-position-bottom: "下"
notification-position-top: "上"
behavior: "動作" behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
disable-via-mobile: "「モバイルからの投稿」フラグを付けない" disable-via-mobile: "「モバイルからの投稿」フラグを付けない"

View File

@ -865,6 +865,8 @@ desktop/views/pages/welcome.vue:
signin-button: "Inloggen" signin-button: "Inloggen"
signup-button: "Registreren" signup-button: "Registreren"
timeline: "Tijdlijn" timeline: "Tijdlijn"
announcements: "お知らせ"
photos: "最近の画像"
powered-by-misskey: "Powered by <b>Misskey</b>." powered-by-misskey: "Powered by <b>Misskey</b>."
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Misskey Drive" title: "Misskey Drive"
@ -1161,6 +1163,9 @@ mobile/views/pages/settings.vue:
post-style: "Berichtontwerp" post-style: "Berichtontwerp"
post-style-standard: "Standaard" post-style-standard: "Standaard"
post-style-smart: "Slim" post-style-smart: "Slim"
notification-position: "通知の表示"
notification-position-bottom: "下"
notification-position-top: "上"
behavior: "Gedrag" behavior: "Gedrag"
fetch-on-scroll: "Ophalen bij scrollen" fetch-on-scroll: "Ophalen bij scrollen"
disable-via-mobile: "Zonder 'mobiele berichten'" disable-via-mobile: "Zonder 'mobiele berichten'"

View File

@ -865,6 +865,8 @@ desktop/views/pages/welcome.vue:
signin-button: "やってる" signin-button: "やってる"
signup-button: "やる" signup-button: "やる"
timeline: "タイムライン" timeline: "タイムライン"
announcements: "お知らせ"
photos: "最近の画像"
powered-by-misskey: "Powered by <b>Misskey</b>." powered-by-misskey: "Powered by <b>Misskey</b>."
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Misskey Drive" title: "Misskey Drive"
@ -1161,6 +1163,9 @@ mobile/views/pages/settings.vue:
post-style: "投稿の表示スタイル" post-style: "投稿の表示スタイル"
post-style-standard: "標準" post-style-standard: "標準"
post-style-smart: "スマート" post-style-smart: "スマート"
notification-position: "通知の表示"
notification-position-bottom: "下"
notification-position-top: "上"
behavior: "動作" behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
disable-via-mobile: "「モバイルからの投稿」フラグを付けない" disable-via-mobile: "「モバイルからの投稿」フラグを付けない"

View File

@ -865,6 +865,8 @@ desktop/views/pages/welcome.vue:
signin-button: "Zaloguj się" signin-button: "Zaloguj się"
signup-button: "Zarejestruj się" signup-button: "Zarejestruj się"
timeline: "Oś czasu" timeline: "Oś czasu"
announcements: "お知らせ"
photos: "最近の画像"
powered-by-misskey: "Oparto o <b>Misskey</b>." powered-by-misskey: "Oparto o <b>Misskey</b>."
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Dysk Misskey" title: "Dysk Misskey"
@ -1161,6 +1163,9 @@ mobile/views/pages/settings.vue:
post-style: "Styl wpisów" post-style: "Styl wpisów"
post-style-standard: "Standardowy" post-style-standard: "Standardowy"
post-style-smart: "Inteligentny" post-style-smart: "Inteligentny"
notification-position: "通知の表示"
notification-position-bottom: "下"
notification-position-top: "上"
behavior: "Zachowanie" behavior: "Zachowanie"
fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół" fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół"
disable-via-mobile: "Nie oznaczaj wpisów jako „wysłane z telefonu”" disable-via-mobile: "Nie oznaczaj wpisów jako „wysłane z telefonu”"

View File

@ -865,6 +865,8 @@ desktop/views/pages/welcome.vue:
signin-button: "やってる" signin-button: "やってる"
signup-button: "やる" signup-button: "やる"
timeline: "Timeline" timeline: "Timeline"
announcements: "お知らせ"
photos: "最近の画像"
powered-by-misskey: "Desenvolvido por <b>Misskey</b>." powered-by-misskey: "Desenvolvido por <b>Misskey</b>."
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Drive Misskey" title: "Drive Misskey"
@ -1161,6 +1163,9 @@ mobile/views/pages/settings.vue:
post-style: "投稿の表示スタイル" post-style: "投稿の表示スタイル"
post-style-standard: "標準" post-style-standard: "標準"
post-style-smart: "スマート" post-style-smart: "スマート"
notification-position: "通知の表示"
notification-position-bottom: "下"
notification-position-top: "上"
behavior: "動作" behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
disable-via-mobile: "「モバイルからの投稿」フラグを付けない" disable-via-mobile: "「モバイルからの投稿」フラグを付けない"

View File

@ -865,6 +865,8 @@ desktop/views/pages/welcome.vue:
signin-button: "やってる" signin-button: "やってる"
signup-button: "やる" signup-button: "やる"
timeline: "タイムライン" timeline: "タイムライン"
announcements: "お知らせ"
photos: "最近の画像"
powered-by-misskey: "Powered by <b>Misskey</b>." powered-by-misskey: "Powered by <b>Misskey</b>."
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Misskey Drive" title: "Misskey Drive"
@ -1161,6 +1163,9 @@ mobile/views/pages/settings.vue:
post-style: "投稿の表示スタイル" post-style: "投稿の表示スタイル"
post-style-standard: "標準" post-style-standard: "標準"
post-style-smart: "スマート" post-style-smart: "スマート"
notification-position: "通知の表示"
notification-position-bottom: "下"
notification-position-top: "上"
behavior: "動作" behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
disable-via-mobile: "「モバイルからの投稿」フラグを付けない" disable-via-mobile: "「モバイルからの投稿」フラグを付けない"

View File

@ -865,6 +865,8 @@ desktop/views/pages/welcome.vue:
signin-button: "やってる" signin-button: "やってる"
signup-button: "やる" signup-button: "やる"
timeline: "タイムライン" timeline: "タイムライン"
announcements: "お知らせ"
photos: "最近の画像"
powered-by-misskey: "Powered by <b>Misskey</b>." powered-by-misskey: "Powered by <b>Misskey</b>."
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Misskey Drive" title: "Misskey Drive"
@ -1161,6 +1163,9 @@ mobile/views/pages/settings.vue:
post-style: "投稿の表示スタイル" post-style: "投稿の表示スタイル"
post-style-standard: "標準" post-style-standard: "標準"
post-style-smart: "スマート" post-style-smart: "スマート"
notification-position: "通知の表示"
notification-position-bottom: "下"
notification-position-top: "上"
behavior: "動作" behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
disable-via-mobile: "「モバイルからの投稿」フラグを付けない" disable-via-mobile: "「モバイルからの投稿」フラグを付けない"

View File

@ -1,8 +1,8 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "8.27.0", "version": "8.28.1",
"clientVersion": "1.0.9378", "clientVersion": "1.0.9400",
"codename": "nighthike", "codename": "nighthike",
"main": "./built/index.js", "main": "./built/index.js",
"private": true, "private": true,
@ -55,7 +55,7 @@
"@types/koa-send": "4.1.1", "@types/koa-send": "4.1.1",
"@types/koa-views": "2.0.3", "@types/koa-views": "2.0.3",
"@types/koa__cors": "2.2.3", "@types/koa__cors": "2.2.3",
"@types/minio": "6.0.2", "@types/minio": "7.0.0",
"@types/mkdirp": "0.5.2", "@types/mkdirp": "0.5.2",
"@types/mocha": "5.2.3", "@types/mocha": "5.2.3",
"@types/mongodb": "3.1.4", "@types/mongodb": "3.1.4",
@ -80,7 +80,7 @@
"@types/webpack": "4.4.11", "@types/webpack": "4.4.11",
"@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.0", "@types/ws": "6.0.1",
"animejs": "2.2.0", "animejs": "2.2.0",
"autosize": "4.0.2", "autosize": "4.0.2",
"autwh": "0.1.0", "autwh": "0.1.0",
@ -161,7 +161,7 @@
"nan": "2.11.0", "nan": "2.11.0",
"nested-property": "0.0.7", "nested-property": "0.0.7",
"node-sass": "4.9.3", "node-sass": "4.9.3",
"node-sass-json-importer": "4.0.0", "node-sass-json-importer": "4.0.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"object-assign-deep": "0.4.0", "object-assign-deep": "0.4.0",
"on-build-webpack": "0.1.0", "on-build-webpack": "0.1.0",

View File

@ -140,7 +140,7 @@
// Random // Random
localStorage.setItem('salt', Math.random().toString()); localStorage.setItem('salt', Math.random().toString());
// Clear cache (serive worker) // Clear cache (service worker)
try { try {
navigator.serviceWorker.controller.postMessage('clear'); navigator.serviceWorker.controller.postMessage('clear');

View File

@ -9,7 +9,7 @@ export default async function(mios: MiOS, force = false, silent = false) {
localStorage.setItem('should-refresh', 'true'); localStorage.setItem('should-refresh', 'true');
localStorage.setItem('v', newer); localStorage.setItem('v', newer);
// Clear cache (serive worker) // Clear cache (service worker)
try { try {
if (navigator.serviceWorker.controller) { if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage('clear'); navigator.serviceWorker.controller.postMessage('clear');

View File

@ -1,6 +1,7 @@
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import * as uuid from 'uuid'; import * as uuid from 'uuid';
import Connection from './stream'; import Connection from './stream';
import { erase } from '../../../../../prelude/array';
/** /**
* ストリーム接続を管理するクラス * ストリーム接続を管理するクラス
@ -89,7 +90,7 @@ export default abstract class StreamManager<T extends Connection> extends EventE
* @param userId use で発行したユーザーID * @param userId use で発行したユーザーID
*/ */
public dispose(userId) { public dispose(userId) {
this.users = this.users.filter(id => id != userId); this.users = erase(userId, this.users);
this._connection.user = `Managed (${ this.users.length })`; this._connection.user = `Managed (${ this.users.length })`;

View File

@ -20,6 +20,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { erase } from '../../../../../prelude/array';
export default Vue.extend({ export default Vue.extend({
data() { data() {
return { return {
@ -53,7 +54,7 @@ export default Vue.extend({
get() { get() {
return { return {
choices: this.choices.filter(choice => choice != '') choices: erase('', this.choices)
} }
}, },

View File

@ -21,6 +21,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { sum } from '../../../../../prelude/array';
export default Vue.extend({ export default Vue.extend({
props: ['note'], props: ['note'],
data() { data() {
@ -33,7 +34,7 @@ export default Vue.extend({
return this.note.poll; return this.note.poll;
}, },
total(): number { total(): number {
return this.poll.choices.reduce((a, b) => a + b.votes, 0); return sum(this.poll.choices.map(x => x.votes));
}, },
isVoted(): boolean { isVoted(): boolean {
return this.poll.choices.some(c => c.isVoted); return this.poll.choices.some(c => c.isVoted);

View File

@ -1,8 +1,8 @@
<template> <template>
<div class="mkw-analog-clock"> <div class="mkw-analog-clock">
<mk-widget-container :naked="!(props.design % 2)" :show-header="false"> <mk-widget-container :naked="props.style % 2 === 0" :show-header="false">
<div class="mkw-analog-clock--body"> <div class="mkw-analog-clock--body">
<mk-analog-clock :dark="$store.state.device.darkmode" :smooth="!(props.design && ~props.design)"/> <mk-analog-clock :dark="$store.state.device.darkmode" :smooth="props.style < 2"/>
</div> </div>
</mk-widget-container> </mk-widget-container>
</div> </div>
@ -13,13 +13,12 @@ import define from '../../../common/define-widget';
export default define({ export default define({
name: 'analog-clock', name: 'analog-clock',
props: () => ({ props: () => ({
design: -1 style: 0
}) })
}).extend({ }).extend({
methods: { methods: {
func() { func() {
if (++this.props.design > 2) this.props.style = (this.props.style + 1) % 4;
this.props.design = -1;
this.save(); this.save();
} }
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="anltbovirfeutcigvwgmgxipejaeozxi" <div class="anltbovirfeutcigvwgmgxipejaeozxi"
:data-found="broadcasts.length != 0" :data-found="announcements && announcements.length != 0"
:data-melt="props.design == 1" :data-melt="props.design == 1"
:data-mobile="platform == 'mobile'" :data-mobile="platform == 'mobile'"
> >
@ -14,12 +14,12 @@
</svg> </svg>
</div> </div>
<p class="fetching" v-if="fetching">%i18n:@fetching%<mk-ellipsis/></p> <p class="fetching" v-if="fetching">%i18n:@fetching%<mk-ellipsis/></p>
<h1 v-if="!fetching">{{ broadcasts.length == 0 ? '%i18n:@no-broadcasts%' : broadcasts[i].title }}</h1> <h1 v-if="!fetching">{{ announcements.length == 0 ? '%i18n:@no-broadcasts%' : announcements[i].title }}</h1>
<p v-if="!fetching"> <p v-if="!fetching">
<span v-if="broadcasts.length != 0" v-html="broadcasts[i].text"></span> <span v-if="announcements.length != 0" v-html="announcements[i].text"></span>
<template v-if="broadcasts.length == 0">%i18n:@have-a-nice-day%</template> <template v-if="announcements.length == 0">%i18n:@have-a-nice-day%</template>
</p> </p>
<a v-if="broadcasts.length > 1" @click="next">%i18n:@next% &gt;&gt;</a> <a v-if="announcements.length > 1" @click="next">%i18n:@next% &gt;&gt;</a>
</div> </div>
</template> </template>
@ -36,18 +36,18 @@ export default define({
return { return {
i: 0, i: 0,
fetching: true, fetching: true,
broadcasts: [] announcements: []
}; };
}, },
mounted() { mounted() {
(this as any).os.getMeta().then(meta => { (this as any).os.getMeta().then(meta => {
this.broadcasts = meta.broadcasts; this.announcements = meta.broadcasts;
this.fetching = false; this.fetching = false;
}); });
}, },
methods: { methods: {
next() { next() {
if (this.i == this.broadcasts.length - 1) { if (this.i == this.announcements.length - 1) {
this.i = 0; this.i = 0;
} else { } else {
this.i++; this.i++;

View File

@ -86,6 +86,7 @@ import MkRenoteFormWindow from './renote-form-window.vue';
import MkNoteMenu from '../../../common/views/components/note-menu.vue'; import MkNoteMenu from '../../../common/views/components/note-menu.vue';
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
import XSub from './notes.note.sub.vue'; import XSub from './notes.note.sub.vue';
import { sum } from '../../../../../prelude/array';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -122,9 +123,7 @@ export default Vue.extend({
}, },
reactionsCount(): number { reactionsCount(): number {
return this.p.reactionCounts return this.p.reactionCounts
? Object.keys(this.p.reactionCounts) ? sum(Object.values(this.p.reactionCounts))
.map(key => this.p.reactionCounts[key])
.reduce((a, b) => a + b)
: 0; : 0;
}, },
title(): string { title(): string {

View File

@ -78,6 +78,7 @@ import MkRenoteFormWindow from './renote-form-window.vue';
import MkNoteMenu from '../../../common/views/components/note-menu.vue'; import MkNoteMenu from '../../../common/views/components/note-menu.vue';
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
import XSub from './notes.note.sub.vue'; import XSub from './notes.note.sub.vue';
import { sum } from '../../../../../prelude/array';
function focus(el, fn) { function focus(el, fn) {
const target = fn(el); const target = fn(el);
@ -120,9 +121,7 @@ export default Vue.extend({
reactionsCount(): number { reactionsCount(): number {
return this.p.reactionCounts return this.p.reactionCounts
? Object.keys(this.p.reactionCounts) ? sum(Object.values(this.p.reactionCounts))
.map(key => this.p.reactionCounts[key])
.reduce((a, b) => a + b)
: 0; : 0;
}, },

View File

@ -62,6 +62,7 @@ import getFace from '../../../common/scripts/get-face';
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue'; import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
import parse from '../../../../../mfm/parse'; import parse from '../../../../../mfm/parse';
import { host } from '../../../config'; import { host } from '../../../config';
import { erase } from '../../../../../prelude/array';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -346,7 +347,7 @@ export default Vue.extend({
}, },
removeVisibleUser(user) { removeVisibleUser(user) {
this.visibleUsers = this.visibleUsers.filter(u => u != user); this.visibleUsers = erase(user, this.visibleUsers);
}, },
post() { post() {

View File

@ -28,12 +28,14 @@
<span class="divider">|</span> <span class="divider">|</span>
<span class="signin" @click="signin">%i18n:@signin%</span> <span class="signin" @click="signin">%i18n:@signin%</span>
</p> </p>
<img src="/assets/pointer.png" alt="" class="char">
</div> </div>
</div> </div>
<div class="announcements block"> <div class="announcements block">
<header>%fa:broadcast-tower% %i18n:@announcements%</header> <header>%fa:broadcast-tower% %i18n:@announcements%</header>
<div> <div v-if="announcements && announcements.length > 0">
<div v-for="announcement in announcements"> <div v-for="announcement in announcements">
<h1 v-html="announcement.title"></h1> <h1 v-html="announcement.title"></h1>
<div v-html="announcement.text"></div> <div v-html="announcement.text"></div>
@ -85,6 +87,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { host, copyright } from '../../../config'; import { host, copyright } from '../../../config';
import { concat } from '../../../../../prelude/array';
export default Vue.extend({ export default Vue.extend({
data() { data() {
@ -119,8 +122,8 @@ export default Vue.extend({
(this as any).api('notes/local-timeline', { (this as any).api('notes/local-timeline', {
fileType: image, fileType: image,
limit: 6 limit: 6
}).then(notes => { }).then((notes: any[]) => {
const files = [].concat(...notes.map(n => n.files)); const files = concat(notes.map((n: any): any[] => n.files));
this.photos = files.filter(f => image.includes(f.type)).slice(0, 6); this.photos = files.filter(f => image.includes(f.type)).slice(0, 6);
}); });
}, },
@ -213,7 +216,7 @@ root(isDark)
width 100% width 100%
max-width 1200px max-width 1200px
height 100vh height 100vh
min-height 1000px min-height 950px
margin 0 auto margin 0 auto
padding 64px padding 64px
@ -246,6 +249,7 @@ root(isDark)
> div > div
padding 32px padding 32px
min-height 100%
> h1 > h1
margin 0 margin 0
@ -280,6 +284,17 @@ root(isDark)
&:hover &:hover
color $theme-color color $theme-color
> .char
display block
position absolute
right 0
bottom 0
width 180px
opacity 0.3
> *:not(.char)
z-index 1
> .announcements > .announcements
grid-row 2 grid-row 2
grid-column 1 grid-column 1

View File

@ -17,6 +17,7 @@ import Err from './common/views/components/connect-failed.vue';
import { LocalTimelineStreamManager } from './common/scripts/streaming/local-timeline'; import { LocalTimelineStreamManager } from './common/scripts/streaming/local-timeline';
import { HybridTimelineStreamManager } from './common/scripts/streaming/hybrid-timeline'; import { HybridTimelineStreamManager } from './common/scripts/streaming/hybrid-timeline';
import { GlobalTimelineStreamManager } from './common/scripts/streaming/global-timeline'; import { GlobalTimelineStreamManager } from './common/scripts/streaming/global-timeline';
import { erase } from '../../prelude/array';
//#region api requests //#region api requests
let spinner = null; let spinner = null;
@ -537,7 +538,7 @@ export default class MiOS extends EventEmitter {
} }
public unregisterStreamConnection(connection: Connection) { public unregisterStreamConnection(connection: Connection) {
this.connections = this.connections.filter(c => c != connection); this.connections = erase(connection, this.connections);
} }
} }

View File

@ -85,6 +85,7 @@ import parse from '../../../../../mfm/parse';
import MkNoteMenu from '../../../common/views/components/note-menu.vue'; import MkNoteMenu from '../../../common/views/components/note-menu.vue';
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
import XSub from './note.sub.vue'; import XSub from './note.sub.vue';
import { sum } from '../../../../../prelude/array';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -123,9 +124,7 @@ export default Vue.extend({
reactionsCount(): number { reactionsCount(): number {
return this.p.reactionCounts return this.p.reactionCounts
? Object.keys(this.p.reactionCounts) ? sum(Object.values(this.p.reactionCounts))
.map(key => this.p.reactionCounts[key])
.reduce((a, b) => a + b)
: 0; : 0;
}, },

View File

@ -70,6 +70,7 @@ import parse from '../../../../../mfm/parse';
import MkNoteMenu from '../../../common/views/components/note-menu.vue'; import MkNoteMenu from '../../../common/views/components/note-menu.vue';
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
import XSub from './note.sub.vue'; import XSub from './note.sub.vue';
import { sum } from '../../../../../prelude/array';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -100,9 +101,7 @@ export default Vue.extend({
reactionsCount(): number { reactionsCount(): number {
return this.p.reactionCounts return this.p.reactionCounts
? Object.keys(this.p.reactionCounts) ? sum(Object.values(this.p.reactionCounts))
.map(key => this.p.reactionCounts[key])
.reduce((a, b) => a + b)
: 0; : 0;
}, },

View File

@ -59,6 +59,7 @@ import MkVisibilityChooser from '../../../common/views/components/visibility-cho
import getFace from '../../../common/scripts/get-face'; import getFace from '../../../common/scripts/get-face';
import parse from '../../../../../mfm/parse'; import parse from '../../../../../mfm/parse';
import { host } from '../../../config'; import { host } from '../../../config';
import { erase } from '../../../../../prelude/array';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -262,7 +263,7 @@ export default Vue.extend({
}, },
removeVisibleUser(user) { removeVisibleUser(user) {
this.visibleUsers = this.visibleUsers.filter(u => u != user); this.visibleUsers = erase(user, this.visibleUsers);
}, },
clear() { clear() {

View File

@ -34,7 +34,7 @@
<li @click="dark"><p><template v-if="$store.state.device.darkmode">%fa:moon%</template><template v-else>%fa:R moon%</template><span>%i18n:@darkmode%</span></p></li> <li @click="dark"><p><template v-if="$store.state.device.darkmode">%fa:moon%</template><template v-else>%fa:R moon%</template><span>%i18n:@darkmode%</span></p></li>
</ul> </ul>
</div> </div>
<div class="announcements" v-if="announcements.length > 0"> <div class="announcements" v-if="announcements && announcements.length > 0">
<article v-for="announcement in announcements"> <article v-for="announcement in announcements">
<span v-html="announcement.title" class="title"></span> <span v-html="announcement.title" class="title"></span>
<div v-html="announcement.text"></div> <div v-html="announcement.text"></div>

View File

@ -40,6 +40,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { apiUrl, copyright, host } from '../../../config'; import { apiUrl, copyright, host } from '../../../config';
import { concat } from '../../../../../prelude/array';
export default Vue.extend({ export default Vue.extend({
data() { data() {
@ -79,8 +80,8 @@ export default Vue.extend({
(this as any).api('notes/local-timeline', { (this as any).api('notes/local-timeline', {
fileType: image, fileType: image,
limit: 6 limit: 6
}).then(notes => { }).then((notes: any[]) => {
const files = [].concat(...notes.map(n => n.files)); const files = concat(notes.map((n: any): any[] => n.files));
this.photos = files.filter(f => image.includes(f.type)).slice(0, 6); this.photos = files.filter(f => image.includes(f.type)).slice(0, 6);
}); });
} }

View File

@ -4,6 +4,7 @@ import * as nestedProperty from 'nested-property';
import MiOS from './mios'; import MiOS from './mios';
import { hostname } from './config'; import { hostname } from './config';
import { erase } from '../../prelude/array';
const defaultSettings = { const defaultSettings = {
home: null, home: null,
@ -195,7 +196,7 @@ export default (os: MiOS) => new Vuex.Store({
removeDeckColumn(state, id) { removeDeckColumn(state, id) {
state.deck.columns = state.deck.columns.filter(c => c.id != id); state.deck.columns = state.deck.columns.filter(c => c.id != id);
state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id)); state.deck.layout = state.deck.layout.map(ids => erase(id, ids));
state.deck.layout = state.deck.layout.filter(ids => ids.length > 0); state.deck.layout = state.deck.layout.filter(ids => ids.length > 0);
}, },
@ -266,7 +267,7 @@ export default (os: MiOS) => new Vuex.Store({
stackLeftDeckColumn(state, id) { stackLeftDeckColumn(state, id) {
const i = state.deck.layout.findIndex(ids => ids.indexOf(id) != -1); const i = state.deck.layout.findIndex(ids => ids.indexOf(id) != -1);
state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id)); state.deck.layout = state.deck.layout.map(ids => erase(id, ids));
const left = state.deck.layout[i - 1]; const left = state.deck.layout[i - 1];
if (left) state.deck.layout[i - 1].push(id); if (left) state.deck.layout[i - 1].push(id);
state.deck.layout = state.deck.layout.filter(ids => ids.length > 0); state.deck.layout = state.deck.layout.filter(ids => ids.length > 0);
@ -274,7 +275,7 @@ export default (os: MiOS) => new Vuex.Store({
popRightDeckColumn(state, id) { popRightDeckColumn(state, id) {
const i = state.deck.layout.findIndex(ids => ids.indexOf(id) != -1); const i = state.deck.layout.findIndex(ids => ids.indexOf(id) != -1);
state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id)); state.deck.layout = state.deck.layout.map(ids => erase(id, ids));
state.deck.layout.splice(i + 1, 0, [id]); state.deck.layout.splice(i + 1, 0, [id]);
state.deck.layout = state.deck.layout.filter(ids => ids.length > 0); state.deck.layout = state.deck.layout.filter(ids => ids.length > 0);
}, },

View File

@ -3,6 +3,7 @@
*/ */
import composeNotification from './common/scripts/compose-notification'; import composeNotification from './common/scripts/compose-notification';
import { erase } from '../../prelude/array';
// キャッシュするリソース // キャッシュするリソース
const cachee = [ const cachee = [
@ -24,8 +25,7 @@ self.addEventListener('activate', ev => {
// Clean up old caches // Clean up old caches
ev.waitUntil( ev.waitUntil(
caches.keys().then(keys => Promise.all( caches.keys().then(keys => Promise.all(
keys erase(_VERSION_, keys)
.filter(key => key != _VERSION_)
.map(key => caches.delete(key)) .map(key => caches.delete(key))
)) ))
); );

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 274 KiB

View File

@ -1,4 +1,4 @@
import { count, countIf } from "../../prelude/array"; import { count, concat } from "../../prelude/array";
// MISSKEY REVERSI ENGINE // MISSKEY REVERSI ENGINE
@ -110,7 +110,7 @@ export default class Reversi {
* 白石の数 * 白石の数
*/ */
public get whiteCount() { public get whiteCount() {
return count(BLACK, this.board); return count(WHITE, this.board);
} }
/** /**
@ -238,87 +238,55 @@ export default class Reversi {
/** /**
* 指定のマスに石を置いた時の、反転させられる石を取得します * 指定のマスに石を置いた時の、反転させられる石を取得します
* @param color 自分の色 * @param color 自分の色
* @param pos 位置 * @param initPos 位置
*/ */
public effects(color: Color, pos: number): number[] { public effects(color: Color, initPos: number): number[] {
const enemyColor = !color; const enemyColor = !color;
// ひっくり返せる石(の位置)リスト const diffVectors: [number, number][] = [
let stones: number[] = []; [ 0, -1], // 上
[ +1, -1], // 右上
[ +1, 0], // 右
[ +1, +1], // 右下
[ 0, +1], // 下
[ -1, +1], // 左下
[ -1, 0], // 左
[ -1, -1] // 左上
];
const initPos = pos; const effectsInLine = ([dx, dy]: [number, number]): number[] => {
const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy];
// 走査
const iterate = (fn: (i: number) => number[]) => {
let i = 1;
const found = [];
const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列
let [x, y] = this.transformPosToXy(initPos);
while (true) { while (true) {
let [x, y] = fn(i); [x, y] = nextPos(x, y);
// 座標が指し示す位置がボード外に出たとき // 座標が指し示す位置がボード外に出たとき
if (this.opts.loopedBoard) { if (this.opts.loopedBoard) {
if (x < 0 ) x = this.mapWidth - ((-x) % this.mapWidth); x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth;
if (y < 0 ) y = this.mapHeight - ((-y) % this.mapHeight); y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight;
if (x >= this.mapWidth ) x = x % this.mapWidth;
if (y >= this.mapHeight) y = y % this.mapHeight;
// for debug
//if (x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight) {
// console.log(x, y);
//}
// 一周して自分に帰ってきたら
if (this.transformXyToPos(x, y) == initPos) { if (this.transformXyToPos(x, y) == initPos) {
// ↓のコメントアウトを外すと、「現時点で自分の石が隣接していないが、 // 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
// そこに置いたとするとループして最終的に挟んだことになる」というケースを有効化します。(Test4のマップで違いが分かります) return found;
// このケースを有効にした方が良いのか無効にした方が良いのか判断がつかなかったためとりあえず無効としておきます
// (あと無効な方がゲームとしておもしろそうだった)
stones = stones.concat(found);
break;
} }
} else { } else {
if (x == -1 || y == -1 || x == this.mapWidth || y == this.mapHeight) break; if (x == -1 || y == -1 || x == this.mapWidth || y == this.mapHeight) {
return []; // 挟めないことが確定 (盤面外に到達)
}
} }
const pos = this.transformXyToPos(x, y); const pos = this.transformXyToPos(x, y);
if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達)
//#region 「配置不能」マスに当たった場合走査終了
const pixel = this.mapDataGet(pos);
if (pixel == 'null') break;
//#endregion
// 石取得
const stone = this.board[pos]; const stone = this.board[pos];
if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達)
// 石が置かれていないマスなら走査終了 if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見)
if (stone === null) break; if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見)
// 相手の石なら「ひっくり返せるかもリスト」に入れておく
if (stone === enemyColor) found.push(pos);
// 自分の石なら「ひっくり返せるかもリスト」を「ひっくり返せるリスト」に入れ、走査終了
if (stone === color) {
stones = stones.concat(found);
break;
}
i++;
} }
}; };
const [x, y] = this.transformPosToXy(pos); return concat(diffVectors.map(effectsInLine));
iterate(i => [x , y - i]); // 上
iterate(i => [x + i, y - i]); // 右上
iterate(i => [x + i, y ]); // 右
iterate(i => [x + i, y + i]); // 右下
iterate(i => [x , y + i]); // 下
iterate(i => [x - i, y + i]); // 左下
iterate(i => [x - i, y ]); // 左
iterate(i => [x - i, y - i]); // 左上
return stones;
} }
/** /**

View File

@ -1,3 +1,5 @@
import { capitalize } from "../../../prelude/string";
function escape(text: string) { function escape(text: string) {
return text return text
.replace(/>/g, '&gt;') .replace(/>/g, '&gt;')
@ -89,7 +91,7 @@ const _keywords = [
]; ];
const keywords = _keywords const keywords = _keywords
.concat(_keywords.map(k => k[0].toUpperCase() + k.substr(1))) .concat(_keywords.map(capitalize))
.concat(_keywords.map(k => k.toUpperCase())) .concat(_keywords.map(k => k.toUpperCase()))
.sort((a, b) => b.length - a.length); .sort((a, b) => b.length - a.length);

View File

@ -6,6 +6,22 @@ export function count<T>(x: T, xs: T[]): number {
return countIf(y => x === y, xs); return countIf(y => x === y, xs);
} }
export function intersperse<T>(sep: T, xs: T[]): T[] { export function concat<T>(xss: T[][]): T[] {
return [].concat(...xs.map(x => [sep, x])).slice(1); return ([] as T[]).concat(...xss);
}
export function intersperse<T>(sep: T, xs: T[]): T[] {
return concat(xs.map(x => [sep, x])).slice(1);
}
export function erase<T>(x: T, xs: T[]): T[] {
return xs.filter(y => x !== y);
}
export function unique<T>(xs: T[]): T[] {
return [...new Set(xs)];
}
export function sum(xs: number[]): number {
return xs.reduce((a, b) => a + b, 0);
} }

3
src/prelude/string.ts Normal file
View File

@ -0,0 +1,3 @@
export function capitalize(s: string): string {
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
}

View File

@ -25,10 +25,8 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
return rej('YOU_ARE_NOT_ADMIN'); return rej('YOU_ARE_NOT_ADMIN');
} }
if (app && ep.meta.kind) { if (app && ep.meta.kind && !app.permission.some(p => p === ep.meta.kind)) {
if (!app.permission.some(p => p === ep.meta.kind)) { return rej('PERMISSION_DENIED');
return rej('PERMISSION_DENIED');
}
} }
if (ep.meta.requireCredential && ep.meta.limit) { if (ep.meta.requireCredential && ep.meta.limit) {

View File

@ -1,4 +1,5 @@
import Note from '../../../../models/note'; import Note from '../../../../models/note';
import { erase } from '../../../../prelude/array';
/* /*
トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要 トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要
@ -85,8 +86,7 @@ export default () => new Promise(async (res, rej) => {
//#endregion //#endregion
// タグを人気順に並べ替え // タグを人気順に並べ替え
let hots = (await Promise.all(hotsPromises)) let hots = erase(null, await Promise.all(hotsPromises))
.filter(x => x != null)
.sort((a, b) => b.count - a.count) .sort((a, b) => b.count - a.count)
.map(tag => tag.name) .map(tag => tag.name)
.slice(0, max); .slice(0, max);

View File

@ -5,6 +5,7 @@ import Mute from '../../../../models/mute';
import { getFriendIds } from '../../common/get-friends'; import { getFriendIds } from '../../common/get-friends';
import { pack } from '../../../../models/note'; import { pack } from '../../../../models/note';
import getParams from '../../get-params'; import getParams from '../../get-params';
import { erase } from '../../../../prelude/array';
export const meta = { export const meta = {
desc: { desc: {
@ -103,23 +104,23 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
if (psErr) throw psErr; if (psErr) throw psErr;
if (ps.includeUserUsernames != null) { if (ps.includeUserUsernames != null) {
const ids = (await Promise.all(ps.includeUserUsernames.map(async (username) => { const ids = erase(null, await Promise.all(ps.includeUserUsernames.map(async (username) => {
const _user = await User.findOne({ const _user = await User.findOne({
usernameLower: username.toLowerCase() usernameLower: username.toLowerCase()
}); });
return _user ? _user._id : null; return _user ? _user._id : null;
}))).filter(id => id != null); })));
ids.forEach(id => ps.includeUserIds.push(id)); ids.forEach(id => ps.includeUserIds.push(id));
} }
if (ps.excludeUserUsernames != null) { if (ps.excludeUserUsernames != null) {
const ids = (await Promise.all(ps.excludeUserUsernames.map(async (username) => { const ids = erase(null, await Promise.all(ps.excludeUserUsernames.map(async (username) => {
const _user = await User.findOne({ const _user = await User.findOne({
usernameLower: username.toLowerCase() usernameLower: username.toLowerCase()
}); });
return _user ? _user._id : null; return _user ? _user._id : null;
}))).filter(id => id != null); })));
ids.forEach(id => ps.excludeUserIds.push(id)); ids.forEach(id => ps.excludeUserIds.push(id));
} }

View File

@ -34,8 +34,9 @@ export default async (url: string, user: IUser, folderId: mongodb.ObjectID = nul
// write content at URL to temp file // write content at URL to temp file
await new Promise((res, rej) => { await new Promise((res, rej) => {
const writable = fs.createWriteStream(path); const writable = fs.createWriteStream(path);
const requestUrl = URL.parse(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
request({ request({
url, url: requestUrl,
headers: { headers: {
'User-Agent': config.user_agent 'User-Agent': config.user_agent
} }

View File

@ -24,6 +24,7 @@ import isQuote from '../../misc/is-quote';
import { TextElementMention } from '../../mfm/parse/elements/mention'; import { TextElementMention } from '../../mfm/parse/elements/mention';
import { TextElementHashtag } from '../../mfm/parse/elements/hashtag'; import { TextElementHashtag } from '../../mfm/parse/elements/hashtag';
import { updateNoteStats } from '../update-chart'; import { updateNoteStats } from '../update-chart';
import { erase, unique } from '../../prelude/array';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -103,7 +104,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
if (data.viaMobile == null) data.viaMobile = false; if (data.viaMobile == null) data.viaMobile = false;
if (data.visibleUsers) { if (data.visibleUsers) {
data.visibleUsers = data.visibleUsers.filter(x => x != null); data.visibleUsers = erase(null, data.visibleUsers);
} }
if (data.reply && data.reply.deletedAt != null) { if (data.reply && data.reply.deletedAt != null) {
@ -384,7 +385,7 @@ function extractHashtags(tokens: ReturnType<typeof parse>): string[] {
.map(t => (t as TextElementHashtag).hashtag) .map(t => (t as TextElementHashtag).hashtag)
.filter(tag => tag.length <= 100); .filter(tag => tag.length <= 100);
return [...new Set(hashtags)]; return unique(hashtags);
} }
function index(note: INote) { function index(note: INote) {
@ -541,20 +542,20 @@ function incNotesCount(user: IUser) {
async function extractMentionedUsers(tokens: ReturnType<typeof parse>): Promise<IUser[]> { async function extractMentionedUsers(tokens: ReturnType<typeof parse>): Promise<IUser[]> {
if (tokens == null) return []; if (tokens == null) return [];
const mentionTokens = [...new Set( const mentionTokens = unique(
tokens tokens
.filter(t => t.type == 'mention') as TextElementMention[] .filter(t => t.type == 'mention') as TextElementMention[]
)]; );
const mentionedUsers = [...new Set( const mentionedUsers = unique(
(await Promise.all(mentionTokens.map(async m => { erase(null, await Promise.all(mentionTokens.map(async m => {
try { try {
return await resolveUser(m.username, m.host); return await resolveUser(m.username, m.host);
} catch (e) { } catch (e) {
return null; return null;
} }
}))).filter(x => x != null) })))
)]; );
return mentionedUsers; return mentionedUsers;
} }

View File

@ -17,6 +17,7 @@
"no-empty":false, "no-empty":false,
"ordered-imports": [false], "ordered-imports": [false],
"arrow-parens": false, "arrow-parens": false,
"array-type": false,
"object-literal-shorthand": false, "object-literal-shorthand": false,
"object-literal-key-quotes": false, "object-literal-key-quotes": false,
"triple-equals": [false], "triple-equals": [false],

View File

@ -196,7 +196,7 @@ module.exports = {
}, { }, {
loader: 'sass-loader', loader: 'sass-loader',
options: { options: {
importer: jsonImporter, importer: jsonImporter(),
} }
}] }]
}, { }, {