Merge branch 'master' into l10n_master

This commit is contained in:
syuilo 2018-05-18 15:34:38 +09:00 committed by GitHub
commit 0d0c45a4cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 575 additions and 1526 deletions

View file

@ -7,7 +7,7 @@ notifications:
language: node_js language: node_js
node_js: node_js:
- 9.8.0 - 10.1.0
env: env:
- CXX=g++-4.8 NODE_ENV=production - CXX=g++-4.8 NODE_ENV=production
@ -22,19 +22,14 @@ addons:
cache: cache:
directories: directories:
# パッケージをキャッシュすると本来は動かないはずなのに動いてしまう - node_modules
# 場合があり危険なのでキャッシュはしない:
#- node_modules
services: services:
- mongodb - mongodb
- redis-server - redis-server
before_script: before_script:
# Travisはproduction環境なので(10行目により)、 - npm install
# npm install しただけでは devDependencies はインストールされないので、
# --only=dev オプションを付けてそれらもインストールされるようにする:
- npm install --only=dev
# 設定ファイルを配置 # 設定ファイルを配置
- cp ./.travis/default.yml ./.config - cp ./.travis/default.yml ./.config

41
appveyor.yml Normal file
View file

@ -0,0 +1,41 @@
# appveyor file
# http://www.appveyor.com/docs/appveyor-yml
environment:
matrix:
- nodejs_version: 10.1.0
cache:
- node_modules
build: off
install:
# Update Node.js
# 標準で入っている Node.js を更新します (2014/11/13 時点では、v0.10.32 が標準)
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
- node --version
# Update NPM
- npm install -g npm
- npm --version
# Update node-gyp
# 必須! node-gyp のバージョンを上げないと、ネイティブモジュールのコンパイルに失敗します
- npm install -g node-gyp
- npm install
init:
# git clone の際の改行を変換しないようにします
- git config --global core.autocrlf false
before_test:
# 設定ファイルを配置
- cp ./.travis/default.yml ./.config
- cp ./.travis/test.yml ./.config
- npm run build
test_script:
- npm test

View file

@ -20,6 +20,7 @@ import * as replace from 'gulp-replace';
import * as htmlmin from 'gulp-htmlmin'; import * as htmlmin from 'gulp-htmlmin';
const uglifyes = require('uglify-es'); const uglifyes = require('uglify-es');
import locales from './locales';
import { fa } from './src/build/fa'; import { fa } from './src/build/fa';
const client = require('./built/client/meta.json'); const client = require('./built/client/meta.json');
import config from './src/config'; import config from './src/config';
@ -122,6 +123,7 @@ gulp.task('build:client:script', () =>
.pipe(replace('VERSION', JSON.stringify(client.version))) .pipe(replace('VERSION', JSON.stringify(client.version)))
.pipe(replace('API', JSON.stringify(config.api_url))) .pipe(replace('API', JSON.stringify(config.api_url)))
.pipe(replace('ENV', JSON.stringify(env))) .pipe(replace('ENV', JSON.stringify(env)))
.pipe(replace('LANGS', JSON.stringify(Object.keys(locales))))
.pipe(isProduction ? uglify({ .pipe(isProduction ? uglify({
toplevel: true toplevel: true
} as any) : gutil.noop()) } as any) : gutil.noop())

View file

@ -1,7 +1,7 @@
--- ---
meta: meta:
lang: "日本語" lang: "English"
divider: "" divider: " "
common: common:
misskey: "Share everything with others using Misskey." misskey: "Share everything with others using Misskey."
time: time:

View file

@ -11,6 +11,7 @@ const loadLang = lang => yaml.safeLoad(
const native = loadLang('ja'); const native = loadLang('ja');
const langs = { const langs = {
'de': loadLang('de'),
'en': loadLang('en'), 'en': loadLang('en'),
'fr': loadLang('fr'), 'fr': loadLang('fr'),
'ja': native, 'ja': native,

View file

@ -1,3 +1,7 @@
meta:
lang: "日本語"
divider: ""
common: common:
misskey: "Misskeyで皆と共有しよう。" misskey: "Misskeyで皆と共有しよう。"
@ -253,6 +257,32 @@ desktop/views/components/drive.vue:
upload: "ファイルをアップロード" upload: "ファイルをアップロード"
url-upload: "URLからアップロード" url-upload: "URLからアップロード"
desktop/views/components/home.vue:
done: "完了"
add-widget: "ウィジェットを追加:"
profile: "プロフィール"
calendar: "カレンダー"
timemachine: "カレンダー(タイムマシン)"
activity: "アクティビティ"
rss: "RSSリーダー"
trends: "トレンド"
photostream: "フォトストリーム"
slideshow: "スライドショー"
version: "バージョン"
broadcast: "ブロードキャスト"
notifications: "通知"
users: "おすすめユーザー"
polls: "投票"
post-form: "投稿フォーム"
messaging: "メッセージ"
channel: "チャンネル"
access-log: "アクセスログ"
server: "サーバー情報"
donation: "寄付のお願い"
nav: "ナビゲーション"
tips: "ヒント"
add: "追加"
desktop/views/components/messaging-window.vue: desktop/views/components/messaging-window.vue:
title: "メッセージ" title: "メッセージ"
@ -312,6 +342,7 @@ desktop/views/components/settings.vue:
mute: "ミュート" mute: "ミュート"
drive: "ドライブ" drive: "ドライブ"
security: "セキュリティ" security: "セキュリティ"
signin: "サインイン履歴"
password: "パスワード" password: "パスワード"
2fa: "二段階認証" 2fa: "二段階認証"
other: "その他" other: "その他"
@ -341,6 +372,7 @@ desktop/views/components/settings.api.vue:
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-token: "トークンを再生成" regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください" enter-password: "パスワードを入力してください"
desktop/views/components/settings.app.vue: desktop/views/components/settings.app.vue:
@ -396,6 +428,20 @@ desktop/views/components/ui.header.post.vue:
desktop/views/components/ui.header.search.vue: desktop/views/components/ui.header.search.vue:
placeholder: "検索" placeholder: "検索"
desktop/views/components/user-lists-window.vue:
create-list: "リストを作成"
desktop/views/components/user-preview.vue:
notes: "投稿"
following: "フォロー"
followers: "フォロワー"
desktop/views/components/users-list.vue:
all: "すべて"
iknow: "知り合い"
load-more: "もっと"
fetching: "読み込んでいます"
desktop/views/pages/note.vue: desktop/views/pages/note.vue:
prev: "前の投稿" prev: "前の投稿"
next: "次の投稿" next: "次の投稿"
@ -510,6 +556,9 @@ mobile/views/components/notifications.vue:
mobile/views/components/post-form.vue: mobile/views/components/post-form.vue:
submit: "投稿" submit: "投稿"
reply: "返信"
renote: "Renote"
renote-placeholder: "この投稿を引用... (オプション)"
reply-placeholder: "この投稿への返信..." reply-placeholder: "この投稿への返信..."
note-placeholder: "いまどうしてる?" note-placeholder: "いまどうしてる?"

View file

@ -1,8 +1,8 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "2.6.2", "version": "2.10.0",
"clientVersion": "1.0.5260", "clientVersion": "1.0.5406",
"codename": "nighthike", "codename": "nighthike",
"main": "./built/index.js", "main": "./built/index.js",
"private": true, "private": true,
@ -31,13 +31,11 @@
"@prezzemolo/rap": "0.1.2", "@prezzemolo/rap": "0.1.2",
"@prezzemolo/zip": "0.0.3", "@prezzemolo/zip": "0.0.3",
"@types/bcryptjs": "2.4.1", "@types/bcryptjs": "2.4.1",
"@types/chai": "4.1.3",
"@types/chai-http": "3.0.4",
"@types/debug": "0.0.30", "@types/debug": "0.0.30",
"@types/deep-equal": "1.0.1", "@types/deep-equal": "1.0.1",
"@types/elasticsearch": "5.0.22", "@types/elasticsearch": "5.0.23",
"@types/eventemitter3": "2.0.2", "@types/eventemitter3": "2.0.2",
"@types/gm": "1.17.33", "@types/gm": "1.18.0",
"@types/gulp": "3.8.36", "@types/gulp": "3.8.36",
"@types/gulp-htmlmin": "1.3.32", "@types/gulp-htmlmin": "1.3.32",
"@types/gulp-mocha": "0.0.32", "@types/gulp-mocha": "0.0.32",
@ -58,18 +56,18 @@
"@types/koa-multer": "1.0.0", "@types/koa-multer": "1.0.0",
"@types/koa-router": "7.0.28", "@types/koa-router": "7.0.28",
"@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.2", "@types/koa__cors": "2.2.2",
"@types/kue": "0.11.8", "@types/kue": "0.11.8",
"@types/license-checker": "15.0.0", "@types/license-checker": "15.0.0",
"@types/mkdirp": "0.5.2", "@types/mkdirp": "0.5.2",
"@types/mocha": "5.2.0", "@types/mocha": "5.2.0",
"@types/mongodb": "3.0.15", "@types/mongodb": "3.0.18",
"@types/monk": "6.0.0", "@types/monk": "6.0.0",
"@types/ms": "0.7.30", "@types/ms": "0.7.30",
"@types/node": "9.6.6", "@types/node": "10.1.0",
"@types/nopt": "3.0.29", "@types/nopt": "3.0.29",
"@types/parse5": "^3.0.0", "@types/parse5": "3.0.0",
"@types/pug": "2.0.4", "@types/pug": "2.0.4",
"@types/qrcode": "0.8.1", "@types/qrcode": "0.8.1",
"@types/ratelimiter": "2.1.28", "@types/ratelimiter": "2.1.28",
@ -78,22 +76,20 @@
"@types/request-promise-native": "1.0.14", "@types/request-promise-native": "1.0.14",
"@types/rimraf": "2.0.2", "@types/rimraf": "2.0.2",
"@types/seedrandom": "2.4.27", "@types/seedrandom": "2.4.27",
"@types/single-line-log": "^1.1.0", "@types/single-line-log": "1.1.0",
"@types/speakeasy": "2.0.2", "@types/speakeasy": "2.0.2",
"@types/tmp": "0.0.33", "@types/tmp": "0.0.33",
"@types/uuid": "3.4.3", "@types/uuid": "3.4.3",
"@types/webpack": "4.1.4", "@types/webpack": "4.1.7",
"@types/webpack-stream": "3.2.10", "@types/webpack-stream": "3.2.10",
"@types/websocket": "0.0.38", "@types/websocket": "0.0.39",
"@types/ws": "4.0.2", "@types/ws": "5.1.1",
"animejs": "2.2.0", "animejs": "2.2.0",
"autosize": "4.0.1", "autosize": "4.0.2",
"autwh": "0.1.0", "autwh": "0.1.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"bootstrap-vue": "2.0.0-rc.6", "bootstrap-vue": "2.0.0-rc.6",
"cafy": "8.0.0", "cafy": "8.0.0",
"chai": "4.1.2",
"chai-http": "4.0.0",
"chalk": "2.4.1", "chalk": "2.4.1",
"crc-32": "1.2.0", "crc-32": "1.2.0",
"css-loader": "0.28.11", "css-loader": "0.28.11",
@ -101,9 +97,9 @@
"deep-equal": "1.0.1", "deep-equal": "1.0.1",
"deepcopy": "0.6.3", "deepcopy": "0.6.3",
"diskusage": "0.2.4", "diskusage": "0.2.4",
"dompurify": "1.0.3", "dompurify": "1.0.4",
"elasticsearch": "14.2.2", "elasticsearch": "14.2.2",
"element-ui": "2.3.6", "element-ui": "2.3.8",
"emojilib": "2.2.12", "emojilib": "2.2.12",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eslint": "4.19.1", "eslint": "4.19.1",
@ -111,7 +107,7 @@
"eventemitter3": "3.1.0", "eventemitter3": "3.1.0",
"exif-js": "2.3.0", "exif-js": "2.3.0",
"file-loader": "1.1.11", "file-loader": "1.1.11",
"file-type": "7.6.0", "file-type": "8.0.0",
"fuckadblock": "3.2.1", "fuckadblock": "3.2.1",
"gm": "1.23.1", "gm": "1.23.1",
"gulp": "3.9.1", "gulp": "3.9.1",
@ -120,15 +116,15 @@
"gulp-imagemin": "4.1.0", "gulp-imagemin": "4.1.0",
"gulp-mocha": "5.0.0", "gulp-mocha": "5.0.0",
"gulp-pug": "4.0.1", "gulp-pug": "4.0.1",
"gulp-rename": "1.2.2", "gulp-rename": "1.2.3",
"gulp-replace": "0.6.1", "gulp-replace": "1.0.0",
"gulp-sourcemaps": "2.6.4", "gulp-sourcemaps": "2.6.4",
"gulp-stylus": "2.7.0", "gulp-stylus": "2.7.0",
"gulp-tslint": "8.1.3", "gulp-tslint": "8.1.3",
"gulp-typescript": "4.0.2", "gulp-typescript": "4.0.2",
"gulp-uglify": "3.0.0", "gulp-uglify": "3.0.0",
"gulp-util": "3.0.8", "gulp-util": "3.0.8",
"hard-source-webpack-plugin": "0.6.4", "hard-source-webpack-plugin": "0.6.7",
"highlight.js": "9.12.0", "highlight.js": "9.12.0",
"html-minifier": "3.5.15", "html-minifier": "3.5.15",
"http-signature": "1.2.0", "http-signature": "1.2.0",
@ -136,7 +132,7 @@
"is-root": "2.0.0", "is-root": "2.0.0",
"is-url": "1.2.4", "is-url": "1.2.4",
"js-yaml": "3.11.0", "js-yaml": "3.11.0",
"jsdom": "11.9.0", "jsdom": "11.10.0",
"koa": "2.5.1", "koa": "2.5.1",
"koa-bodyparser": "4.2.0", "koa-bodyparser": "4.2.0",
"koa-compress": "3.0.0", "koa-compress": "3.0.0",
@ -148,16 +144,16 @@
"koa-router": "7.4.0", "koa-router": "7.4.0",
"koa-send": "4.1.3", "koa-send": "4.1.3",
"koa-slow": "2.1.0", "koa-slow": "2.1.0",
"koa-views": "^6.1.4", "koa-views": "6.1.4",
"kue": "0.11.6", "kue": "0.11.6",
"license-checker": "18.0.0", "license-checker": "19.0.0",
"loader-utils": "1.1.0", "loader-utils": "1.1.0",
"mecab-async": "0.1.2", "mecab-async": "0.1.2",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"mocha": "5.1.1", "mocha": "5.1.1",
"moji": "0.5.1", "moji": "0.5.1",
"mongodb": "3.0.7", "mongodb": "3.0.8",
"monk": "6.0.5", "monk": "6.0.6",
"ms": "2.1.1", "ms": "2.1.1",
"nan": "2.10.0", "nan": "2.10.0",
"node-sass": "4.9.0", "node-sass": "4.9.0",
@ -167,10 +163,10 @@
"object-assign-deep": "0.4.0", "object-assign-deep": "0.4.0",
"on-build-webpack": "0.1.0", "on-build-webpack": "0.1.0",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"parse5": "^4.0.0", "parse5": "4.0.0",
"progress-bar-webpack-plugin": "1.11.0", "progress-bar-webpack-plugin": "1.11.0",
"prominence": "0.2.0", "prominence": "0.2.0",
"promise-sequential": "^1.1.1", "promise-sequential": "1.1.1",
"pug": "2.0.3", "pug": "2.0.3",
"punycode": "2.1.0", "punycode": "2.1.0",
"qrcode": "1.2.0", "qrcode": "1.2.0",
@ -178,14 +174,14 @@
"recaptcha-promise": "0.1.3", "recaptcha-promise": "0.1.3",
"reconnecting-websocket": "3.2.2", "reconnecting-websocket": "3.2.2",
"redis": "2.8.0", "redis": "2.8.0",
"request": "2.85.0", "request": "2.86.0",
"request-promise-native": "1.0.5", "request-promise-native": "1.0.5",
"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.0.1", "sass-loader": "7.0.1",
"seedrandom": "2.4.3", "seedrandom": "2.4.3",
"single-line-log": "^1.1.2", "single-line-log": "1.1.2",
"speakeasy": "2.0.0", "speakeasy": "2.0.0",
"style-loader": "0.21.0", "style-loader": "0.21.0",
"stylus": "0.54.5", "stylus": "0.54.5",
@ -196,9 +192,9 @@
"tcp-port-used": "0.1.2", "tcp-port-used": "0.1.2",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"tmp": "0.0.33", "tmp": "0.0.33",
"ts-loader": "4.2.0", "ts-loader": "4.3.0",
"ts-node": "6.0.1", "ts-node": "6.0.3",
"tslint": "5.9.1", "tslint": "5.10.0",
"typescript": "2.8.3", "typescript": "2.8.3",
"typescript-eslint-parser": "15.0.0", "typescript-eslint-parser": "15.0.0",
"uglify-es": "3.3.9", "uglify-es": "3.3.9",
@ -209,16 +205,15 @@
"vue-cropperjs": "2.2.0", "vue-cropperjs": "2.2.0",
"vue-js-modal": "1.3.13", "vue-js-modal": "1.3.13",
"vue-json-tree-view": "2.1.4", "vue-json-tree-view": "2.1.4",
"vue-loader": "15.0.3", "vue-loader": "15.0.11",
"vue-router": "3.0.1", "vue-router": "3.0.1",
"vue-template-compiler": "2.5.16", "vue-template-compiler": "2.5.16",
"vuedraggable": "2.16.0", "vuedraggable": "2.16.0",
"vuex": "3.0.1", "vuex": "3.0.1",
"web-push": "3.3.0", "web-push": "3.3.1",
"webfinger.js": "2.6.6", "webfinger.js": "2.6.6",
"webpack": "4.6.0", "webpack": "4.8.3",
"webpack-cli": "2.0.15", "webpack-cli": "2.1.3",
"webpack-replace-loader": "1.3.0",
"websocket": "1.0.26", "websocket": "1.0.26",
"ws": "5.1.1", "ws": "5.1.1",
"xev": "2.0.0" "xev": "2.0.0"

View file

@ -7,10 +7,7 @@ import * as regular from '@fortawesome/fontawesome-free-regular';
import * as solid from '@fortawesome/fontawesome-free-solid'; import * as solid from '@fortawesome/fontawesome-free-solid';
import * as brands from '@fortawesome/fontawesome-free-brands'; import * as brands from '@fortawesome/fontawesome-free-brands';
// Add icons fontawesome.library.add(regular, solid, brands);
fontawesome.library.add(regular);
fontawesome.library.add(solid);
fontawesome.library.add(brands);
export const pattern = /%fa:(.+?)%/g; export const pattern = /%fa:(.+?)%/g;

View file

@ -32,7 +32,7 @@
// Detect the user language // Detect the user language
// Note: The default language is Japanese // Note: The default language is Japanese
let lang = navigator.language.split('-')[0]; let lang = navigator.language.split('-')[0];
if (!/^(en|ja)$/.test(lang)) lang = 'ja'; if (!LANGS.includes(lang)) lang = 'en';
if (localStorage.getItem('lang')) lang = localStorage.getItem('lang'); if (localStorage.getItem('lang')) lang = localStorage.getItem('lang');
// Detect the user agent // Detect the user agent

View file

@ -0,0 +1,16 @@
export default function(note) {
if (note.text == null) return true;
let txt = note.text;
if (note.media) {
note.media.forEach(file => {
txt = txt.replace(file.url, '');
if (file.src) txt = txt.replace(file.src, '');
});
if (txt == '') return true;
}
return false;
}

View file

@ -48,6 +48,17 @@ export class HomeStream extends Stream {
} }
}); });
this.on('mobile_home_updated', x => {
if (x.home) {
os.store.commit('settings/setMobileHome', x.home);
} else {
os.store.commit('settings/setMobileHomeWidget', {
id: x.id,
data: x.data
});
}
});
// トークンが再生成されたとき // トークンが再生成されたとき
// このままではMisskeyが利用できないので強制的にサインアウトさせる // このままではMisskeyが利用できないので強制的にサインアウトさせる
this.on('my_token_regenerated', () => { this.on('my_token_regenerated', () => {

View file

@ -0,0 +1,19 @@
<template>
<span class="mk-acct">
<span class="name">@{{ user.username }}</span>
<span class="host" v-if="user.host">@{{ user.host }}</span>
</span>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: ['user']
});
</script>
<style lang="stylus" scoped>
.mk-acct
> .host
opacity 0.5
</style>

View file

@ -23,7 +23,7 @@ export default Vue.extend({
computed: { computed: {
style(): any { style(): any {
return { return {
backgroundColor: this.user.avatarColor ? `rgb(${ this.user.avatarColor.join(',') })` : null, backgroundColor: this.user.avatarColor && this.user.avatarColor.length == 3 ? `rgb(${ this.user.avatarColor.join(',') })` : null,
backgroundImage: `url(${ this.user.avatarUrl }?thumbnail)`, backgroundImage: `url(${ this.user.avatarUrl }?thumbnail)`,
borderRadius: (this as any).clientSettings.circleIcons ? '100%' : null borderRadius: (this as any).clientSettings.circleIcons ? '100%' : null
}; };

View file

@ -3,6 +3,7 @@ import Vue from 'vue';
import signin from './signin.vue'; import signin from './signin.vue';
import signup from './signup.vue'; import signup from './signup.vue';
import forkit from './forkit.vue'; import forkit from './forkit.vue';
import acct from './acct.vue';
import avatar from './avatar.vue'; import avatar from './avatar.vue';
import nav from './nav.vue'; import nav from './nav.vue';
import noteHtml from './note-html'; import noteHtml from './note-html';
@ -29,6 +30,7 @@ import welcomeTimeline from './welcome-timeline.vue';
Vue.component('mk-signin', signin); Vue.component('mk-signin', signin);
Vue.component('mk-signup', signup); Vue.component('mk-signup', signup);
Vue.component('mk-forkit', forkit); Vue.component('mk-forkit', forkit);
Vue.component('mk-acct', acct);
Vue.component('mk-avatar', avatar); Vue.component('mk-avatar', avatar);
Vue.component('mk-nav', nav); Vue.component('mk-nav', nav);
Vue.component('mk-note-html', noteHtml); Vue.component('mk-note-html', noteHtml);

View file

@ -126,16 +126,21 @@ root(isDark)
line-height 16px line-height 16px
vertical-align top vertical-align top
@media (max-width 500px) @media (max-width 700px)
font-size 8px
border none
> .thumbnail > .thumbnail
width 70px position relative
width 100%
height 100px
& + article & + article
left 70px left 0
width calc(100% - 70px) width 100%
@media (max-width 500px)
font-size 8px
> .thumbnail
height 70px
> article > article
padding 8px padding 8px

View file

@ -1,37 +1,37 @@
<template> <template>
<div class="mkw-calendar" <div class="mkw-calendar" :data-special="special" :data-mobile="isMobile">
:data-melt="props.design == 1" <mk-widget-container :naked="props.design == 1" :show-header="false">
:data-special="special" <div class="mkw-calendar--body">
:data-mobile="isMobile" <div class="calendar" :data-is-holiday="isHoliday">
> <p class="month-and-year">
<div class="calendar" :data-is-holiday="isHoliday"> <span class="year">{{ year }}</span>
<p class="month-and-year"> <span class="month">{{ month }}</span>
<span class="year">{{ year }}</span> </p>
<span class="month">{{ month }}</span> <p class="day">{{ day }}</p>
</p> <p class="week-day">{{ weekDay }}曜日</p>
<p class="day">{{ day }}</p> </div>
<p class="week-day">{{ weekDay }}曜日</p> <div class="info">
</div> <div>
<div class="info"> <p>今日:<b>{{ dayP.toFixed(1) }}%</b></p>
<div> <div class="meter">
<p>今日:<b>{{ dayP.toFixed(1) }}%</b></p> <div class="val" :style="{ width: `${dayP}%` }"></div>
<div class="meter"> </div>
<div class="val" :style="{ width: `${dayP}%` }"></div> </div>
<div>
<p>今月:<b>{{ monthP.toFixed(1) }}%</b></p>
<div class="meter">
<div class="val" :style="{ width: `${monthP}%` }"></div>
</div>
</div>
<div>
<p>今年:<b>{{ yearP.toFixed(1) }}%</b></p>
<div class="meter">
<div class="val" :style="{ width: `${yearP}%` }"></div>
</div>
</div>
</div> </div>
</div> </div>
<div> </mk-widget-container>
<p>今月:<b>{{ monthP.toFixed(1) }}%</b></p>
<div class="meter">
<div class="val" :style="{ width: `${monthP}%` }"></div>
</div>
</div>
<div>
<p>今年:<b>{{ yearP.toFixed(1) }}%</b></p>
<div class="meter">
<div class="val" :style="{ width: `${yearP}%` }"></div>
</div>
</div>
</div>
</div> </div>
</template> </template>
@ -111,93 +111,82 @@ export default define({
@import '~const.styl' @import '~const.styl'
root(isDark) root(isDark)
padding 16px 0
color isDark ? #c5ced6 :#777
background isDark ? #282C37 : #fff
border solid 1px rgba(#000, 0.075)
border-radius 6px
&[data-special='on-new-years-day'] &[data-special='on-new-years-day']
border-color #ef95a0 border-color #ef95a0
&[data-melt] .mkw-calendar--body
background transparent padding 16px 0
border none color isDark ? #c5ced6 : #777
&[data-mobile] &:after
border none content ""
border-radius 8px display block
box-shadow 0 0 0 1px rgba(#000, 0.2) clear both
&:after > .calendar
content "" float left
display block width 60%
clear both text-align center
> .calendar &[data-is-holiday]
float left > .day
width 60% color #ef95a0
text-align center
&[data-is-holiday]
> .day
color #ef95a0
> p
margin 0
line-height 18px
font-size 14px
> span
margin 0 4px
> .day
margin 10px 0
line-height 32px
font-size 28px
> .info
display block
float left
width 40%
padding 0 16px 0 0
> div
margin-bottom 8px
&:last-child
margin-bottom 4px
> p > p
margin 0 0 2px 0 margin 0
font-size 12px
line-height 18px line-height 18px
color isDark ? #7a8692 : #888 font-size 14px
> b > span
margin-left 2px margin 0 4px
> .meter > .day
width 100% margin 10px 0
overflow hidden line-height 32px
background isDark ? #1c1f25 : #eee font-size 28px
border-radius 8px
> .val > .info
height 4px display block
background $theme-color float left
width 40%
padding 0 16px 0 0
&:nth-child(1) > div
> .meter > .val margin-bottom 8px
background #f7796c
&:nth-child(2) &:last-child
> .meter > .val margin-bottom 4px
background #a1de41
&:nth-child(3) > p
> .meter > .val margin 0 0 2px 0
background #41ddde font-size 12px
line-height 18px
color isDark ? #7a8692 : #888
> b
margin-left 2px
> .meter
width 100%
overflow hidden
background isDark ? #1c1f25 : #eee
border-radius 8px
> .val
height 4px
background $theme-color
&:nth-child(1)
> .meter > .val
background #f7796c
&:nth-child(2)
> .meter > .val
background #a1de41
&:nth-child(3)
> .meter > .val
background #41ddde
.mkw-calendar[data-darkmode] .mkw-calendar[data-darkmode]
root(true) root(true)

View file

@ -1,10 +1,10 @@
<template> <template>
<div class="mkw-rss" :data-mobile="isMobile"> <div class="mkw-rss">
<mk-widget-container :show-header="!props.compact"> <mk-widget-container :show-header="!props.compact">
<template slot="header">%fa:rss-square%RSS</template> <template slot="header">%fa:rss-square%RSS</template>
<button slot="func" title="設定" @click="setting">%fa:cog%</button> <button slot="func" title="設定" @click="setting">%fa:cog%</button>
<div class="mkw-rss--body"> <div class="mkw-rss--body" :data-mobile="isMobile">
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
<div class="feed" v-else> <div class="feed" v-else>
<a v-for="item in items" :href="item.link" target="_blank">{{ item.title }}</a> <a v-for="item in items" :href="item.link" target="_blank">{{ item.title }}</a>
@ -85,15 +85,17 @@ root(isDark)
margin-right 4px margin-right 4px
&[data-mobile] &[data-mobile]
background isDark ? #21242f : #f3f3f3
.feed .feed
padding 0 padding 0
font-size 1em
> a > a
padding 8px 16px padding 8px 16px
border-bottom none
&:nth-child(even) &:nth-child(even)
background rgba(#000, 0.05) background isDark ? rgba(#000, 0.05) : rgba(#fff, 0.7)
.mkw-rss[data-darkmode] .mkw-rss[data-darkmode]
root(true) root(true)

View file

@ -50,7 +50,7 @@ export default Vue.extend({
return `${this.file.name}\n${this.file.type} ${Vue.filter('bytes')(this.file.datasize)}`; return `${this.file.name}\n${this.file.type} ${Vue.filter('bytes')(this.file.datasize)}`;
}, },
background(): string { background(): string {
return this.file.properties.avgColor return this.file.properties.avgColor && this.file.properties.avgColor.length == 3
? `rgb(${this.file.properties.avgColor.join(',')})` ? `rgb(${this.file.properties.avgColor.join(',')})`
: 'transparent'; : 'transparent';
} }
@ -129,7 +129,7 @@ export default Vue.extend({
}, },
onThumbnailLoaded() { onThumbnailLoaded() {
if (this.file.properties.avgColor) { if (this.file.properties.avgColor && this.file.properties.avgColor.length == 3) {
anime({ anime({
targets: this.$refs.thumbnail, targets: this.$refs.thumbnail,
backgroundColor: `rgba(${this.file.properties.avgColor.join(',')}, 0)`, backgroundColor: `rgba(${this.file.properties.avgColor.join(',')}, 0)`,

View file

@ -1,34 +1,34 @@
<template> <template>
<div class="mk-home" :data-customize="customize"> <div class="mk-home" :data-customize="customize">
<div class="customize" v-if="customize"> <div class="customize" v-if="customize">
<router-link to="/">%fa:check%完了</router-link> <router-link to="/">%fa:check%%i18n:@done%</router-link>
<div> <div>
<div class="adder"> <div class="adder">
<p>ウィジェットを追加:</p> <p>%i18n:@add-widget%</p>
<select v-model="widgetAdderSelected"> <select v-model="widgetAdderSelected">
<option value="profile">プロフィール</option> <option value="profile">%i18n:@profile%</option>
<option value="calendar">カレンダー</option> <option value="calendar">%i18n:@calendar%</option>
<option value="timemachine">カレンダー(タイムマシン)</option> <option value="timemachine">%i18n:@timemachine%</option>
<option value="activity">アクティビティ</option> <option value="activity">%i18n:@activity%</option>
<option value="rss">RSSリーダー</option> <option value="rss">%i18n:@rss%</option>
<option value="trends">トレンド</option> <option value="trends">%i18n:@trends%</option>
<option value="photo-stream">フォトストリーム</option> <option value="photo-stream">%i18n:@photo-stream%</option>
<option value="slideshow">スライドショー</option> <option value="slideshow">%i18n:@slideshow%</option>
<option value="version">バージョン</option> <option value="version">%i18n:@version%</option>
<option value="broadcast">ブロードキャスト</option> <option value="broadcast">%i18n:@broadcast%</option>
<option value="notifications">通知</option> <option value="notifications">%i18n:@notifications%</option>
<option value="users">おすすめユーザー</option> <option value="users">%i18n:@users%</option>
<option value="polls">投票</option> <option value="polls">%i18n:@polls%</option>
<option value="post-form">投稿フォーム</option> <option value="post-form">%i18n:@post-form%</option>
<option value="messaging">メッセージ</option> <option value="messaging">%i18n:@messaging%</option>
<option value="channel">チャンネル</option> <option value="channel">%i18n:@channel%</option>
<option value="access-log">アクセスログ</option> <option value="access-log">%i18n:@access-log%</option>
<option value="server">サーバー情報</option> <option value="server">%i18n:@server%</option>
<option value="donation">寄付のお願い</option> <option value="donation">%i18n:@donation%</option>
<option value="nav">ナビゲーション</option> <option value="nav">%i18n:@nav%</option>
<option value="tips">ヒント</option> <option value="tips">%i18n:@tips%</option>
</select> </select>
<button @click="addWidget">追加</button> <button @click="addWidget">%i18n:@add%</button>
</div> </div>
<div class="trash"> <div class="trash">
<x-draggable v-model="trash" :options="{ group: 'x' }" @add="onTrash"></x-draggable> <x-draggable v-model="trash" :options="{ group: 'x' }" @add="onTrash"></x-draggable>

View file

@ -26,7 +26,7 @@ export default Vue.extend({
computed: { computed: {
style(): any { style(): any {
return { return {
'background-color': this.image.properties.avgColor ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent', 'background-color': this.image.properties.avgColor && this.image.properties.avgColor.length == 3 ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent',
'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.url}?thumbnail&size=512)` 'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.url}?thumbnail&size=512)`
}; };
} }

View file

@ -5,7 +5,7 @@
<header> <header>
<div class="left"> <div class="left">
<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link> <router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link>
<span class="username">@{{ note.user | acct }}</span> <span class="username"><mk-acct :user="note.user"/></span>
</div> </div>
<div class="right"> <div class="right">
<router-link class="time" :to="note | notePage"> <router-link class="time" :to="note | notePage">

View file

@ -28,7 +28,7 @@
<mk-avatar class="avatar" :user="p.user"/> <mk-avatar class="avatar" :user="p.user"/>
<header> <header>
<router-link class="name" :to="p.user | userPage" v-user-preview="p.user.id">{{ p.user | userName }}</router-link> <router-link class="name" :to="p.user | userPage" v-user-preview="p.user.id">{{ p.user | userName }}</router-link>
<span class="username">@{{ p.user | acct }}</span> <span class="username"><mk-acct :user="p.user"/></span>
<router-link class="time" :to="p | notePage"> <router-link class="time" :to="p | notePage">
<mk-time :time="p.createdAt"/> <mk-time :time="p.createdAt"/>
</router-link> </router-link>

View file

@ -4,7 +4,7 @@
<div class="main"> <div class="main">
<header> <header>
<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link> <router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link>
<span class="username">@{{ note.user | acct }}</span> <span class="username"><mk-acct :user="note.user"/></span>
<router-link class="time" :to="note | notePage"> <router-link class="time" :to="note | notePage">
<mk-time :time="note.createdAt"/> <mk-time :time="note.createdAt"/>
</router-link> </router-link>
@ -59,17 +59,20 @@ root(isDark)
> .name > .name
margin 0 .5em 0 0 margin 0 .5em 0 0
padding 0 padding 0
overflow hidden
color isDark ? #fff : #607073 color isDark ? #fff : #607073
font-size 1em font-size 1em
font-weight bold font-weight bold
text-decoration none text-decoration none
white-space normal text-overflow ellipsis
&:hover &:hover
text-decoration underline text-decoration underline
> .username > .username
margin 0 .5em 0 0 margin 0 .5em 0 0
overflow hidden
text-overflow ellipsis
color isDark ? #606984 : #d1d8da color isDark ? #606984 : #d1d8da
> .time > .time

View file

@ -4,7 +4,7 @@
<div class="main"> <div class="main">
<header> <header>
<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link> <router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link>
<span class="username">@{{ note.user | acct }}</span> <span class="username"><mk-acct :user="note.user"/></span>
<div class="info"> <div class="info">
<span class="mobile" v-if="note.viaMobile">%fa:mobile-alt%</span> <span class="mobile" v-if="note.viaMobile">%fa:mobile-alt%</span>
<router-link class="created-at" :to="note | notePage"> <router-link class="created-at" :to="note | notePage">

View file

@ -17,7 +17,7 @@
<header> <header>
<router-link class="name" :to="p.user | userPage" v-user-preview="p.user.id">{{ p.user | userName }}</router-link> <router-link class="name" :to="p.user | userPage" v-user-preview="p.user.id">{{ p.user | userName }}</router-link>
<span class="is-bot" v-if="p.user.host === null && p.user.isBot">bot</span> <span class="is-bot" v-if="p.user.host === null && p.user.isBot">bot</span>
<span class="username">@{{ p.user | acct }}</span> <span class="username"><mk-acct :user="p.user"/></span>
<div class="info"> <div class="info">
<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span> <span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span> <span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span>
@ -44,7 +44,7 @@
<div class="text"> <div class="text">
<span v-if="p.isHidden" style="opacity: 0.5">(この投稿は非公開です)</span> <span v-if="p.isHidden" style="opacity: 0.5">(この投稿は非公開です)</span>
<a class="reply" v-if="p.reply">%fa:reply%</a> <a class="reply" v-if="p.reply">%fa:reply%</a>
<mk-note-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/> <mk-note-html v-if="p.text && !canHideText(p)" :text="p.text" :i="os.i" :class="$style.text"/>
<a class="rp" v-if="p.renote">RP:</a> <a class="rp" v-if="p.renote">RP:</a>
</div> </div>
<div class="media" v-if="p.media.length > 0"> <div class="media" v-if="p.media.length > 0">
@ -94,6 +94,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import dateStringify from '../../../common/scripts/date-stringify'; import dateStringify from '../../../common/scripts/date-stringify';
import canHideText from '../../../common/scripts/can-hide-text';
import parse from '../../../../../text/parse'; import parse from '../../../../../text/parse';
import MkPostFormWindow from './post-form-window.vue'; import MkPostFormWindow from './post-form-window.vue';
@ -130,16 +131,17 @@ export default Vue.extend({
}, },
computed: { computed: {
isRenote(): boolean { isRenote(): boolean {
return (this.note.renote && return (this.note.renote &&
this.note.text == null && this.note.text == null &&
this.note.mediaIds.length == 0 && this.note.mediaIds.length == 0 &&
this.note.poll == null); this.note.poll == null);
}, },
p(): any { p(): any {
return this.isRenote ? this.note.renote : this.note; return this.isRenote ? this.note.renote : this.note;
}, },
reactionsCount(): number { reactionsCount(): number {
return this.p.reactionCounts return this.p.reactionCounts
? Object.keys(this.p.reactionCounts) ? Object.keys(this.p.reactionCounts)
@ -147,9 +149,11 @@ export default Vue.extend({
.reduce((a, b) => a + b) .reduce((a, b) => a + b)
: 0; : 0;
}, },
title(): string { title(): string {
return dateStringify(this.p.createdAt); return dateStringify(this.p.createdAt);
}, },
urls(): string[] { urls(): string[] {
if (this.p.text) { if (this.p.text) {
const ast = parse(this.p.text); const ast = parse(this.p.text);
@ -205,6 +209,8 @@ export default Vue.extend({
}, },
methods: { methods: {
canHideText,
capture(withHandler = false) { capture(withHandler = false) {
if ((this as any).os.isSignedIn) { if ((this as any).os.isSignedIn) {
this.connection.send({ this.connection.send({
@ -214,6 +220,7 @@ export default Vue.extend({
if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated); if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated);
} }
}, },
decapture(withHandler = false) { decapture(withHandler = false) {
if ((this as any).os.isSignedIn) { if ((this as any).os.isSignedIn) {
this.connection.send({ this.connection.send({
@ -223,9 +230,11 @@ export default Vue.extend({
if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated); if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated);
} }
}, },
onStreamConnected() { onStreamConnected() {
this.capture(); this.capture();
}, },
onStreamNoteUpdated(data) { onStreamNoteUpdated(data) {
const note = data.note; const note = data.note;
if (note.id == this.note.id) { if (note.id == this.note.id) {
@ -234,28 +243,33 @@ export default Vue.extend({
this.note.renote = note; this.note.renote = note;
} }
}, },
reply() { reply() {
(this as any).os.new(MkPostFormWindow, { (this as any).os.new(MkPostFormWindow, {
reply: this.p reply: this.p
}); });
}, },
renote() { renote() {
(this as any).os.new(MkRenoteFormWindow, { (this as any).os.new(MkRenoteFormWindow, {
note: this.p note: this.p
}); });
}, },
react() { react() {
(this as any).os.new(MkReactionPicker, { (this as any).os.new(MkReactionPicker, {
source: this.$refs.reactButton, source: this.$refs.reactButton,
note: this.p note: this.p
}); });
}, },
menu() { menu() {
(this as any).os.new(MkNoteMenu, { (this as any).os.new(MkNoteMenu, {
source: this.$refs.menuButton, source: this.$refs.menuButton,
note: this.p note: this.p
}); });
}, },
onKeydown(e) { onKeydown(e) {
let shouldBeCancel = true; let shouldBeCancel = true;
@ -336,6 +350,7 @@ root(isDark)
align-items center align-items center
padding 16px 32px padding 16px 32px
line-height 28px line-height 28px
white-space pre
color #9dbb00 color #9dbb00
background isDark ? linear-gradient(to bottom, #314027 0%, #282c37 100%) : linear-gradient(to bottom, #edfde2 0%, #fff 100%) background isDark ? linear-gradient(to bottom, #314027 0%, #282c37 100%) : linear-gradient(to bottom, #edfde2 0%, #fff 100%)

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="root api"> <div class="root api">
<p>Token: <code>{{ os.i.token }}</code></p> <p>%i18n:@token% <code>{{ os.i.token }}</code></p>
<p>%i18n:@intro%</p> <p>%i18n:@intro%</p>
<div class="ui info warn"><p>%fa:exclamation-triangle%%i18n:@caution%</p></div> <div class="ui info warn"><p>%fa:exclamation-triangle%%i18n:@caution%</p></div>
<p>%i18n:@regeneration-of-token%</p> <p>%i18n:@regeneration-of-token%</p>

View file

@ -80,10 +80,11 @@
<el-option label="自動" value=""/> <el-option label="自動" value=""/>
</el-option-group> </el-option-group>
<el-option-group label="言語を指定"> <el-option-group label="言語を指定">
<el-option label="ja-JP" value="ja"/> <el-option label="ja" value="ja"/>
<el-option label="en-US" value="en"/> <el-option label="en" value="en"/>
<el-option label="fr" value="fr"/> <el-option label="fr" value="fr"/>
<el-option label="pl" value="pl"/> <el-option label="pl" value="pl"/>
<el-option label="de" value="de"/>
</el-option-group> </el-option-group>
</el-select> </el-select>
<div class="none ui info"> <div class="none ui info">
@ -100,7 +101,7 @@
</section> </section>
<section class="notification" v-show="page == 'notification'"> <section class="notification" v-show="page == 'notification'">
<h1>通知</h1> <h1>%i18n:@notification%</h1>
<mk-switch v-model="os.i.settings.autoWatch" @change="onChangeAutoWatch" text="投稿の自動ウォッチ"> <mk-switch v-model="os.i.settings.autoWatch" @change="onChangeAutoWatch" text="投稿の自動ウォッチ">
<span>リアクションしたり返信したりした投稿に関する通知を自動的に受け取るようにします</span> <span>リアクションしたり返信したりした投稿に関する通知を自動的に受け取るようにします</span>
</mk-switch> </mk-switch>
@ -117,7 +118,7 @@
</section> </section>
<section class="apps" v-show="page == 'apps'"> <section class="apps" v-show="page == 'apps'">
<h1>アプリケーション</h1> <h1>%i18n:@apps%</h1>
<x-apps/> <x-apps/>
</section> </section>
@ -137,7 +138,7 @@
</section> </section>
<section class="signin" v-show="page == 'security'"> <section class="signin" v-show="page == 'security'">
<h1>サインイン履歴</h1> <h1>%i18n:@signin%</h1>
<x-signins/> <x-signins/>
</section> </section>

View file

@ -3,7 +3,7 @@
<span slot="header">%fa:list% リスト</span> <span slot="header">%fa:list% リスト</span>
<div data-id="6e4caea3-d8f9-4ab7-96de-ab67fe8d5c82" :data-darkmode="_darkmode_"> <div data-id="6e4caea3-d8f9-4ab7-96de-ab67fe8d5c82" :data-darkmode="_darkmode_">
<button class="ui" @click="add">リストを作成</button> <button class="ui" @click="add">%i18n:@create-list%</button>
<a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.title }}</a> <a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.title }}</a>
</div> </div>
</mk-window> </mk-window>

View file

@ -10,13 +10,13 @@
<div class="description">{{ u.description }}</div> <div class="description">{{ u.description }}</div>
<div class="status"> <div class="status">
<div> <div>
<p>投稿</p><a>{{ u.notesCount }}</a> <p>%i18n:@notes%</p><a>{{ u.notesCount }}</a>
</div> </div>
<div> <div>
<p>フォロー</p><a>{{ u.followingCount }}</a> <p>%i18n:@following%</p><a>{{ u.followingCount }}</a>
</div> </div>
<div> <div>
<p>フォロワー</p><a>{{ u.followersCount }}</a> <p>%i18n:@followers%</p><a>{{ u.followersCount }}</a>
</div> </div>
</div> </div>
<mk-follow-button v-if="os.isSignedIn && user.id != os.i.id" :user="u"/> <mk-follow-button v-if="os.isSignedIn && user.id != os.i.id" :user="u"/>

View file

@ -2,8 +2,8 @@
<div class="mk-users-list"> <div class="mk-users-list">
<nav> <nav>
<div> <div>
<span :data-active="mode == 'all'" @click="mode = 'all'">すべて<span>{{ count }}</span></span> <span :data-active="mode == 'all'" @click="mode = 'all'">%i18n:@all%<span>{{ count }}</span></span>
<span v-if="os.isSignedIn && youKnowCount" :data-active="mode == 'iknow'" @click="mode = 'iknow'">知り合い<span>{{ youKnowCount }}</span></span> <span v-if="os.isSignedIn && youKnowCount" :data-active="mode == 'iknow'" @click="mode = 'iknow'">%i18n:@iknow%<span>{{ youKnowCount }}</span></span>
</div> </div>
</nav> </nav>
<div class="users" v-if="!fetching && users.length != 0"> <div class="users" v-if="!fetching && users.length != 0">
@ -12,13 +12,13 @@
</div> </div>
</div> </div>
<button class="more" v-if="!fetching && next != null" @click="more" :disabled="moreFetching"> <button class="more" v-if="!fetching && next != null" @click="more" :disabled="moreFetching">
<span v-if="!moreFetching">もっと</span> <span v-if="!moreFetching">%i18n:@load-more%</span>
<span v-if="moreFetching">読み込み中<mk-ellipsis/></span> <span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span>
</button> </button>
<p class="no" v-if="!fetching && users.length == 0"> <p class="no" v-if="!fetching && users.length == 0">
<slot></slot> <slot></slot>
</p> </p>
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%読み込んでいます<mk-ellipsis/></p> <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@fetching%<mk-ellipsis/></p>
</div> </div>
</template> </template>

View file

@ -29,7 +29,7 @@ export default Vue.extend({
style(): any { style(): any {
if (this.user.bannerUrl == null) return {}; if (this.user.bannerUrl == null) return {};
return { return {
backgroundColor: this.user.bannerColor ? `rgb(${ this.user.bannerColor.join(',') })` : null, backgroundColor: this.user.bannerColor && this.user.bannerColor.length == 3 ? `rgb(${ this.user.bannerColor.join(',') })` : null,
backgroundImage: `url(${ this.user.bannerUrl })` backgroundImage: `url(${ this.user.bannerUrl })`
}; };
} }

View file

@ -1,43 +1,24 @@
import PostForm from '../views/components/post-form.vue'; import PostForm from '../views/components/post-form.vue';
//import RenoteForm from '../views/components/renote-form.vue';
import getNoteSummary from '../../../../renderers/get-note-summary';
export default (os) => (opts) => { export default (os) => (opts) => {
const o = opts || {}; const o = opts || {};
if (o.renote) { const app = document.getElementById('app');
/*const vm = new RenoteForm({ app.style.display = 'none';
propsData: {
renote: o.renote
}
}).$mount();
vm.$once('cancel', recover);
vm.$once('note', recover);
document.body.appendChild(vm.$el);*/
const text = window.prompt(`${getNoteSummary(o.renote)}」をRenote`); function recover() {
if (text == null) return; app.style.display = 'block';
os.api('notes/create', {
renoteId: o.renote.id,
text: text == '' ? undefined : text
});
} else {
const app = document.getElementById('app');
app.style.display = 'none';
function recover() {
app.style.display = 'block';
}
const vm = new PostForm({
parent: os.app,
propsData: {
reply: o.reply
}
}).$mount();
vm.$once('cancel', recover);
vm.$once('note', recover);
document.body.appendChild(vm.$el);
(vm as any).focus();
} }
const vm = new PostForm({
parent: os.app,
propsData: {
reply: o.reply,
renote: o.renote
}
}).$mount();
vm.$once('cancel', recover);
vm.$once('note', recover);
document.body.appendChild(vm.$el);
(vm as any).focus();
}; };

View file

@ -23,6 +23,7 @@ import MkUser from './views/pages/user.vue';
import MkSelectDrive from './views/pages/selectdrive.vue'; import MkSelectDrive from './views/pages/selectdrive.vue';
import MkDrive from './views/pages/drive.vue'; import MkDrive from './views/pages/drive.vue';
import MkNotifications from './views/pages/notifications.vue'; import MkNotifications from './views/pages/notifications.vue';
import MkWidgets from './views/pages/widgets.vue';
import MkMessaging from './views/pages/messaging.vue'; import MkMessaging from './views/pages/messaging.vue';
import MkMessagingRoom from './views/pages/messaging-room.vue'; import MkMessagingRoom from './views/pages/messaging-room.vue';
import MkNote from './views/pages/note.vue'; import MkNote from './views/pages/note.vue';
@ -56,6 +57,7 @@ init((launch) => {
{ path: '/i/settings', component: MkSettings }, { path: '/i/settings', component: MkSettings },
{ path: '/i/settings/profile', component: MkProfileSetting }, { path: '/i/settings/profile', component: MkProfileSetting },
{ path: '/i/notifications', name: 'notifications', component: MkNotifications }, { path: '/i/notifications', name: 'notifications', component: MkNotifications },
{ path: '/i/widgets', name: 'widgets', component: MkWidgets },
{ path: '/i/messaging', name: 'messaging', component: MkMessaging }, { path: '/i/messaging', name: 'messaging', component: MkMessaging },
{ path: '/i/messaging/:user', component: MkMessagingRoom }, { path: '/i/messaging/:user', component: MkMessagingRoom },
{ path: '/i/drive', name: 'drive', component: MkDrive }, { path: '/i/drive', name: 'drive', component: MkDrive },

View file

@ -86,7 +86,7 @@ export default Vue.extend({
return this.file.type.split('/')[0]; return this.file.type.split('/')[0];
}, },
style(): any { style(): any {
return this.file.properties.avgColor ? { return this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? {
'background-color': `rgb(${ this.file.properties.avgColor.join(',') })` 'background-color': `rgb(${ this.file.properties.avgColor.join(',') })`
} : {}; } : {};
} }

View file

@ -42,7 +42,7 @@ export default Vue.extend({
}, },
thumbnail(): any { thumbnail(): any {
return { return {
'background-color': this.file.properties.avgColor ? `rgb(${this.file.properties.avgColor.join(',')})` : 'transparent', 'background-color': this.file.properties.avgColor && this.file.properties.avgColor.length == 3 ? `rgb(${this.file.properties.avgColor.join(',')})` : 'transparent',
'background-image': `url(${this.file.url}?thumbnail&size=128)` 'background-image': `url(${this.file.url}?thumbnail&size=128)`
}; };
} }

View file

@ -18,7 +18,7 @@ export default Vue.extend({
computed: { computed: {
style(): any { style(): any {
return { return {
'background-color': this.image.properties.avgColor ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent', 'background-color': this.image.properties.avgColor && this.image.properties.avgColor.length == 3 ? `rgb(${this.image.properties.avgColor.join(',')})` : 'transparent',
'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.url}?thumbnail&size=512)` 'background-image': this.raw ? `url(${this.image.url})` : `url(${this.image.url}?thumbnail&size=512)`
}; };
} }

View file

@ -69,8 +69,9 @@ root(isDark)
text-decoration underline text-decoration underline
> .username > .username
text-align left
margin 0 .5em 0 0 margin 0 .5em 0 0
overflow hidden
text-overflow ellipsis
color isDark ? #606984 : #d1d8da color isDark ? #606984 : #d1d8da
> .time > .time

View file

@ -41,7 +41,7 @@
<div class="text"> <div class="text">
<span v-if="p.isHidden" style="opacity: 0.5">(この投稿は非公開です)</span> <span v-if="p.isHidden" style="opacity: 0.5">(この投稿は非公開です)</span>
<a class="reply" v-if="p.reply">%fa:reply%</a> <a class="reply" v-if="p.reply">%fa:reply%</a>
<mk-note-html v-if="p.text" :text="p.text" :i="os.i" :class="$style.text"/> <mk-note-html v-if="p.text && !canHideText(p)" :text="p.text" :i="os.i" :class="$style.text"/>
<a class="rp" v-if="p.renote != null">RP:</a> <a class="rp" v-if="p.renote != null">RP:</a>
</div> </div>
<div class="media" v-if="p.media.length > 0"> <div class="media" v-if="p.media.length > 0">
@ -85,6 +85,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import parse from '../../../../../text/parse'; import parse from '../../../../../text/parse';
import canHideText from '../../../common/scripts/can-hide-text';
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';
@ -112,9 +113,11 @@ export default Vue.extend({
this.note.mediaIds.length == 0 && this.note.mediaIds.length == 0 &&
this.note.poll == null); this.note.poll == null);
}, },
p(): any { p(): any {
return this.isRenote ? this.note.renote : this.note; return this.isRenote ? this.note.renote : this.note;
}, },
reactionsCount(): number { reactionsCount(): number {
return this.p.reactionCounts return this.p.reactionCounts
? Object.keys(this.p.reactionCounts) ? Object.keys(this.p.reactionCounts)
@ -122,6 +125,7 @@ export default Vue.extend({
.reduce((a, b) => a + b) .reduce((a, b) => a + b)
: 0; : 0;
}, },
urls(): string[] { urls(): string[] {
if (this.p.text) { if (this.p.text) {
const ast = parse(this.p.text); const ast = parse(this.p.text);
@ -177,6 +181,8 @@ export default Vue.extend({
}, },
methods: { methods: {
canHideText,
capture(withHandler = false) { capture(withHandler = false) {
if ((this as any).os.isSignedIn) { if ((this as any).os.isSignedIn) {
this.connection.send({ this.connection.send({
@ -186,6 +192,7 @@ export default Vue.extend({
if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated); if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated);
} }
}, },
decapture(withHandler = false) { decapture(withHandler = false) {
if ((this as any).os.isSignedIn) { if ((this as any).os.isSignedIn) {
this.connection.send({ this.connection.send({
@ -195,9 +202,11 @@ export default Vue.extend({
if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated); if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated);
} }
}, },
onStreamConnected() { onStreamConnected() {
this.capture(); this.capture();
}, },
onStreamNoteUpdated(data) { onStreamNoteUpdated(data) {
const note = data.note; const note = data.note;
if (note.id == this.note.id) { if (note.id == this.note.id) {
@ -206,16 +215,19 @@ export default Vue.extend({
this.note.renote = note; this.note.renote = note;
} }
}, },
reply() { reply() {
(this as any).apis.post({ (this as any).apis.post({
reply: this.p reply: this.p
}); });
}, },
renote() { renote() {
(this as any).apis.post({ (this as any).apis.post({
renote: this.p renote: this.p
}); });
}, },
react() { react() {
(this as any).os.new(MkReactionPicker, { (this as any).os.new(MkReactionPicker, {
source: this.$refs.reactButton, source: this.$refs.reactButton,
@ -223,6 +235,7 @@ export default Vue.extend({
compact: true compact: true
}); });
}, },
menu() { menu() {
(this as any).os.new(MkNoteMenu, { (this as any).os.new(MkNoteMenu, {
source: this.$refs.menuButton, source: this.$refs.menuButton,
@ -255,6 +268,7 @@ root(isDark)
align-items center align-items center
padding 8px 16px padding 8px 16px
line-height 28px line-height 28px
white-space pre
color #9dbb00 color #9dbb00
background isDark ? linear-gradient(to bottom, #314027 0%, #282c37 100%) : linear-gradient(to bottom, #edfde2 0%, #fff 100%) background isDark ? linear-gradient(to bottom, #314027 0%, #282c37 100%) : linear-gradient(to bottom, #edfde2 0%, #fff 100%)

View file

@ -1,7 +1,5 @@
<template> <template>
<div class="mk-notes"> <div class="mk-notes">
<div class="newer-indicator" :style="{ top: $store.state.uiHeaderHeight + 'px' }" v-show="queue.length > 0"></div>
<slot name="head"></slot> <slot name="head"></slot>
<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot> <slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
@ -71,6 +69,16 @@ export default Vue.extend({
} }
}, },
watch: {
queue(x) {
if (x.length > 0) {
this.$store.commit('indicate', true);
} else {
this.$store.commit('indicate', false);
}
}
},
mounted() { mounted() {
document.addEventListener('visibilitychange', this.onVisibilitychange, false); document.addEventListener('visibilitychange', this.onVisibilitychange, false);
window.addEventListener('scroll', this.onScroll); window.addEventListener('scroll', this.onScroll);
@ -238,13 +246,6 @@ root(isDark)
[data-fa] [data-fa]
margin-right 8px margin-right 8px
> .newer-indicator
position -webkit-sticky
position sticky
z-index 100
height 3px
background $theme-color
> .init > .init
padding 64px 0 padding 64px 0
text-align center text-align center

View file

@ -5,17 +5,22 @@
<div> <div>
<span class="text-count" :class="{ over: text.length > 1000 }">{{ 1000 - text.length }}</span> <span class="text-count" :class="{ over: text.length > 1000 }">{{ 1000 - text.length }}</span>
<span class="geo" v-if="geo">%fa:map-marker-alt%</span> <span class="geo" v-if="geo">%fa:map-marker-alt%</span>
<button class="submit" :disabled="posting" @click="post">{{ reply ? '返信' : '%i18n:!@submit%' }}</button> <button class="submit" :disabled="posting" @click="post">
<template v-if="reply">%i18n:@reply%</template>
<template v-else-if="renote">%i18n:@renote%</template>
<template v-else>%i18n:@submit%</template>
</button>
</div> </div>
</header> </header>
<div class="form"> <div class="form">
<mk-note-preview v-if="reply" :note="reply"/> <mk-note-preview v-if="reply" :note="reply"/>
<mk-note-preview v-if="renote" :note="renote"/>
<div v-if="visibility == 'specified'" class="visibleUsers"> <div v-if="visibility == 'specified'" class="visibleUsers">
<span v-for="u in visibleUsers">{{ u | userName }}<a @click="removeVisibleUser(u)">[x]</a></span> <span v-for="u in visibleUsers">{{ u | userName }}<a @click="removeVisibleUser(u)">[x]</a></span>
<a @click="addVisibleUser">+ユーザーを追加</a> <a @click="addVisibleUser">+ユーザーを追加</a>
</div> </div>
<input v-show="useCw" v-model="cw" placeholder="内容への注釈 (オプション)"> <input v-show="useCw" v-model="cw" placeholder="内容への注釈 (オプション)">
<textarea v-model="text" ref="text" :disabled="posting" :placeholder="reply ? '%i18n:!@reply-placeholder%' : '%i18n:!@note-placeholder%'"></textarea> <textarea v-model="text" ref="text" :disabled="posting" :placeholder="reply ? '%i18n:!@reply-placeholder%' : renote ? '%i18n:!@renote-placeholder%' : '%i18n:!@note-placeholder%'"></textarea>
<div class="attaches" v-show="files.length != 0"> <div class="attaches" v-show="files.length != 0">
<x-draggable class="files" :list="files" :options="{ animation: 150 }"> <x-draggable class="files" :list="files" :options="{ animation: 150 }">
<div class="file" v-for="file in files" :key="file.id"> <div class="file" v-for="file in files" :key="file.id">
@ -51,7 +56,7 @@ export default Vue.extend({
MkVisibilityChooser MkVisibilityChooser
}, },
props: ['reply'], props: ['reply', 'renote'],
data() { data() {
return { return {
@ -177,6 +182,7 @@ export default Vue.extend({
text: this.text == '' ? undefined : this.text, text: this.text == '' ? undefined : this.text,
mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
replyId: this.reply ? this.reply.id : undefined, replyId: this.reply ? this.reply.id : undefined,
renoteId: this.renote ? this.renote.id : undefined,
poll: this.poll ? (this.$refs.poll as any).get() : undefined, poll: this.poll ? (this.$refs.poll as any).get() : undefined,
cw: this.useCw ? this.cw || '' : undefined, cw: this.useCw ? this.cw || '' : undefined,
geo: this.geo ? { geo: this.geo ? {

View file

@ -13,6 +13,7 @@
<slot name="func"></slot> <slot name="func"></slot>
</div> </div>
</div> </div>
<div class="indicator" v-show="$store.state.indicate"></div>
</div> </div>
</template> </template>
@ -156,6 +157,10 @@ root(isDark)
&, * &, *
user-select none user-select none
> .indicator
height 3px
background $theme-color
> .main > .main
color rgba(#fff, 0.9) color rgba(#fff, 0.9)

View file

@ -21,6 +21,7 @@
<li><router-link to="/othello" :data-active="$route.name == 'othello'">%fa:gamepad%ゲーム<template v-if="hasGameInvitations">%fa:circle%</template>%fa:angle-right%</router-link></li> <li><router-link to="/othello" :data-active="$route.name == 'othello'">%fa:gamepad%ゲーム<template v-if="hasGameInvitations">%fa:circle%</template>%fa:angle-right%</router-link></li>
</ul> </ul>
<ul> <ul>
<li><router-link to="/i/widgets" :data-active="$route.name == 'widgets'">%fa:quidditch%%i18n:@widgets%%fa:angle-right%</router-link></li>
<li><router-link to="/i/drive" :data-active="$route.name == 'drive'">%fa:cloud%%i18n:@drive%%fa:angle-right%</router-link></li> <li><router-link to="/i/drive" :data-active="$route.name == 'drive'">%fa:cloud%%i18n:@drive%%fa:angle-right%</router-link></li>
</ul> </ul>
<ul> <ul>

View file

@ -25,27 +25,27 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.mk-widget-container root(isDark)
background #eee background isDark ? #21242f : #eee
border-radius 8px border-radius 8px
box-shadow 0 0 0 1px rgba(#000, 0.2) box-shadow 0 4px 16px rgba(#000, 0.1)
overflow hidden overflow hidden
&.hideHeader
background #fff
&.naked &.naked
background transparent !important background transparent !important
box-shadow none !important box-shadow none !important
&.hideHeader
background isDark ? #21242f : #fff
> header > header
> .title > .title
margin 0 margin 0
padding 8px 10px padding 8px 10px
font-size 15px font-size 15px
font-weight normal font-weight normal
color #465258 color isDark ? #b8c5cc : #465258
background #fff background isDark ? #282c37 : #fff
border-radius 8px 8px 0 0 border-radius 8px 8px 0 0
> [data-fa] > [data-fa]
@ -65,4 +65,10 @@ export default Vue.extend({
font-size 15px font-size 15px
color #465258 color #465258
.mk-widget-container[data-darkmode]
root(true)
.mk-widget-container:not([data-darkmode])
root(false)
</style> </style>

View file

@ -84,7 +84,7 @@ export default Vue.extend({
style(): any { style(): any {
if (this.user.bannerUrl == null) return {}; if (this.user.bannerUrl == null) return {};
return { return {
backgroundColor: this.user.bannerColor ? `rgb(${ this.user.bannerColor.join(',') })` : null, backgroundColor: this.user.bannerColor && this.user.bannerColor.length == 3 ? `rgb(${ this.user.bannerColor.join(',') })` : null,
backgroundImage: `url(${ this.user.bannerUrl })` backgroundImage: `url(${ this.user.bannerUrl })`
}; };
} }

View file

@ -40,7 +40,7 @@
</x-draggable> </x-draggable>
</template> </template>
<template v-else> <template v-else>
<component class="widget" v-for="widget in widgets" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" :is-mobile="true" @chosen="warp"/> <component class="widget" v-for="widget in widgets" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" :is-mobile="true"/>
</template> </template>
</main> </main>
</mk-ui> </mk-ui>
@ -55,17 +55,24 @@ export default Vue.extend({
components: { components: {
XDraggable XDraggable
}, },
data() { data() {
return { return {
showNav: false, showNav: false,
widgets: [],
customizing: false, customizing: false,
widgetAdderSelected: null widgetAdderSelected: null
}; };
}, },
computed: {
widgets(): any[] {
return this.$store.state.settings.data.mobileHome;
}
},
created() { created() {
if ((this as any).clientSettings.mobileHome == null) { if (this.widgets.length == 0) {
Vue.set((this as any).clientSettings, 'mobileHome', [{ this.widgets = [{
name: 'calendar', name: 'calendar',
id: 'a', data: {} id: 'a', data: {}
}, { }, {
@ -86,18 +93,9 @@ export default Vue.extend({
}, { }, {
name: 'version', name: 'version',
id: 'g', data: {} id: 'g', data: {}
}]); }];
this.widgets = (this as any).clientSettings.mobileHome;
this.saveHome(); this.saveHome();
} else {
this.widgets = (this as any).clientSettings.mobileHome;
} }
this.$watch('clientSettings', i => {
this.widgets = (this as any).clientSettings.mobileHome;
}, {
deep: true
});
}, },
mounted() { mounted() {
@ -105,46 +103,33 @@ export default Vue.extend({
}, },
methods: { methods: {
onHomeUpdated(data) {
if (data.home) {
(this as any).clientSettings.mobileHome = data.home;
this.widgets = data.home;
} else {
const w = (this as any).clientSettings.mobileHome.find(w => w.id == data.id);
if (w != null) {
w.data = data.data;
this.$refs[w.id][0].preventSave = true;
this.$refs[w.id][0].props = w.data;
this.widgets = (this as any).clientSettings.mobileHome;
}
}
},
hint() { hint() {
alert('ウィジェットを追加/削除したり並べ替えたりできます。ウィジェットを移動するには「三」をドラッグします。ウィジェットを削除するには「x」をタップします。いくつかのウィジェットはタップすることで表示を変更できます。'); alert('ウィジェットを追加/削除したり並べ替えたりできます。ウィジェットを移動するには「三」をドラッグします。ウィジェットを削除するには「x」をタップします。いくつかのウィジェットはタップすることで表示を変更できます。');
}, },
widgetFunc(id) { widgetFunc(id) {
const w = this.$refs[id][0]; const w = this.$refs[id][0];
if (w.func) w.func(); if (w.func) w.func();
}, },
onWidgetSort() { onWidgetSort() {
this.saveHome(); this.saveHome();
}, },
addWidget() { addWidget() {
const widget = { this.$store.dispatch('settings/addMobileHomeWidget', {
name: this.widgetAdderSelected, name: this.widgetAdderSelected,
id: uuid(), id: uuid(),
data: {} data: {}
}; });
},
this.widgets.unshift(widget);
this.saveHome();
},
removeWidget(widget) { removeWidget(widget) {
this.widgets = this.widgets.filter(w => w.id != widget.id); this.$store.dispatch('settings/removeMobileHomeWidget', widget);
this.saveHome();
}, },
saveHome() { saveHome() {
(this as any).clientSettings.mobileHome = this.widgets; this.$store.commit('settings/setMobileHome', this.widgets);
(this as any).api('i/update_mobile_home', { (this as any).api('i/update_mobile_home', {
home: this.widgets home: this.widgets
}); });
@ -156,17 +141,25 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
main main
margin 0 auto margin 0 auto
padding 8px
max-width 500px max-width 500px
width 100%
@media (min-width 500px) @media (min-width 500px)
padding 8px padding 16px 8px
@media (min-width 600px)
padding 32px 8px
> header > header
padding 8px padding 8px
background #fff background #fff
.widget .widget
margin 8px margin-bottom 8px
@media (min-width 600px)
margin-bottom 16px
.customize-container .customize-container
margin 8px margin 8px

View file

@ -3,6 +3,7 @@ import MiOS from './mios';
const defaultSettings = { const defaultSettings = {
home: [], home: [],
mobileHome: [],
fetchOnScroll: true, fetchOnScroll: true,
showMaps: true, showMaps: true,
showPostFormOnTopOfTl: false, showPostFormOnTopOfTl: false,
@ -23,10 +24,15 @@ export default (os: MiOS) => new Vuex.Store({
}], }],
state: { state: {
indicate: false,
uiHeaderHeight: 0 uiHeaderHeight: 0
}, },
mutations: { mutations: {
indicate(state, x) {
state.indicate = x;
},
setUiHeaderHeight(state, height) { setUiHeaderHeight(state, height) {
state.uiHeaderHeight = height; state.uiHeaderHeight = height;
} }
@ -58,6 +64,25 @@ export default (os: MiOS) => new Vuex.Store({
addHomeWidget(state, widget) { addHomeWidget(state, widget) {
state.data.home.unshift(widget); state.data.home.unshift(widget);
},
setMobileHome(state, data) {
state.data.mobileHome = data;
},
setMobileHomeWidget(state, x) {
const w = state.data.mobileHome.find(w => w.id == x.id);
if (w) {
w.data = x.data;
}
},
addMobileHomeWidget(state, widget) {
state.data.mobileHome.unshift(widget);
},
removeMobileHomeWidget(state, widget) {
state.data.mobileHome = state.data.mobileHome.filter(w => w.id != widget.id);
} }
}, },
@ -85,6 +110,22 @@ export default (os: MiOS) => new Vuex.Store({
os.api('i/update_home', { os.api('i/update_home', {
home: ctx.state.data.home home: ctx.state.data.home
}); });
},
addMobileHomeWidget(ctx, widget) {
ctx.commit('addMobileHomeWidget', widget);
os.api('i/update_mobile_home', {
home: ctx.state.data.mobileHome
});
},
removeMobileHomeWidget(ctx, widget) {
ctx.commit('removeMobileHomeWidget', widget);
os.api('i/update_mobile_home', {
home: ctx.state.data.mobileHome.filter(w => w.id != widget.id)
});
} }
} }
} }

View file

@ -127,7 +127,7 @@ gulp.task('doc:api:endpoints', async () => {
return; return;
} }
const i18n = new I18nReplacer(lang); const i18n = new I18nReplacer(lang);
html = html.replace(i18n.pattern, i18n.replacement.bind(null, null)); html = html.replace(i18n.pattern, i18n.replacement);
html = fa(html); html = fa(html);
const htmlPath = `./built/client/docs/${lang}/api/endpoints/${ep.endpoint}.html`; const htmlPath = `./built/client/docs/${lang}/api/endpoints/${ep.endpoint}.html`;
mkdirp(path.dirname(htmlPath), (mkdirErr) => { mkdirp(path.dirname(htmlPath), (mkdirErr) => {
@ -171,7 +171,7 @@ gulp.task('doc:api:entities', async () => {
return; return;
} }
const i18n = new I18nReplacer(lang); const i18n = new I18nReplacer(lang);
html = html.replace(i18n.pattern, i18n.replacement.bind(null, null)); html = html.replace(i18n.pattern, i18n.replacement);
html = fa(html); html = fa(html);
const htmlPath = `./built/client/docs/${lang}/api/entities/${kebab(entity.name)}.html`; const htmlPath = `./built/client/docs/${lang}/api/entities/${kebab(entity.name)}.html`;
mkdirp(path.dirname(htmlPath), (mkdirErr) => { mkdirp(path.dirname(htmlPath), (mkdirErr) => {

View file

@ -53,7 +53,7 @@ gulp.task('doc:docs', async () => {
return; return;
} }
const i18n = new I18nReplacer(lang); const i18n = new I18nReplacer(lang);
html = html.replace(i18n.pattern, i18n.replacement.bind(null, null)); html = html.replace(i18n.pattern, i18n.replacement);
html = fa(html); html = fa(html);
const htmlPath = `./built/client/docs/${lang}/${name}.html`; const htmlPath = `./built/client/docs/${lang}/${name}.html`;
mkdirp(path.dirname(htmlPath), (mkdirErr) => { mkdirp(path.dirname(htmlPath), (mkdirErr) => {

View file

@ -154,6 +154,7 @@ export const pack = (
_target = Object.assign(_target, _file.metadata); _target = Object.assign(_target, _file.metadata);
_target.src = _file.metadata.url;
_target.url = `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`; _target.url = `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`;
if (_target.properties == null) _target.properties = {}; if (_target.properties == null) _target.properties = {};

View file

@ -2,14 +2,20 @@ import * as Koa from 'koa';
import summaly from 'summaly'; import summaly from 'summaly';
module.exports = async (ctx: Koa.Context) => { module.exports = async (ctx: Koa.Context) => {
const summary = await summaly(ctx.query.url); try {
summary.icon = wrap(summary.icon); const summary = await summaly(ctx.query.url, {
summary.thumbnail = wrap(summary.thumbnail); followRedirects: false
});
summary.icon = wrap(summary.icon);
summary.thumbnail = wrap(summary.thumbnail);
// Cache 7days // Cache 7days
ctx.set('Cache-Control', 'max-age=604800, immutable'); ctx.set('Cache-Control', 'max-age=604800, immutable');
ctx.body = summary; ctx.body = summary;
} catch (e) {
ctx.status = 500;
}
}; };
function wrap(url: string): string { function wrap(url: string): string {

View file

@ -171,6 +171,9 @@ const addFile = async (
log('calculate average color...'); log('calculate average color...');
const info = await prominence(gm(fs.createReadStream(path), name)).identify();
const isTransparent = info ? info['Channel depth'].Alpha != null : false;
const buffer = await prominence(gm(fs.createReadStream(path), name) const buffer = await prominence(gm(fs.createReadStream(path), name)
.setFormat('ppm') .setFormat('ppm')
.resize(1, 1)) // 1pxのサイズに縮小して平均色を取得するというハック .resize(1, 1)) // 1pxのサイズに縮小して平均色を取得するというハック
@ -182,7 +185,7 @@ const addFile = async (
log(`average color is calculated: ${r}, ${g}, ${b}`); log(`average color is calculated: ${r}, ${g}, ${b}`);
return [r, g, b]; return isTransparent ? [r, g, b, 255] : [r, g, b];
})(), })(),
// folder // folder
(async () => { (async () => {

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,7 @@
/** import * as assert from 'assert';
* Text Tests!
*/
const assert = require('assert'); import analyze from '../src/text/parse';
import syntaxhighlighter from '../src/text/parse/core/syntax-highlighter';
const analyze = require('../built/text/parse').default;
const syntaxhighlighter = require('../built/text/parse/core/syntax-highlighter').default;
describe('Text', () => { describe('Text', () => {
it('can be analyzed', () => { it('can be analyzed', () => {

View file

@ -110,14 +110,14 @@ const plugins = [
//#region i18n //#region i18n
langs.forEach(lang => { langs.forEach(lang => {
Object.keys(entry).forEach(file => { Object.keys(entry).forEach(file => {
let src = fs.readFileSync(`${__dirname}/built/client/assets/${file}.${version}.-.${isProduction ? 'min' : 'raw'}.js`, 'utf8'); let src = fs.readFileSync(`${__dirname}/built/client/assets/${file}.${version}.-.${isProduction ? 'min' : 'raw'}.js`, 'utf-8');
const i18nReplacer = new I18nReplacer(lang); const i18nReplacer = new I18nReplacer(lang);
src = src.replace(i18nReplacer.pattern, i18nReplacer.replacement); src = src.replace(i18nReplacer.pattern, i18nReplacer.replacement);
src = src.replace('%lang%', lang); src = src.replace('%lang%', lang);
fs.writeFileSync(`${__dirname}/built/client/assets/${file}.${version}.${lang}.${isProduction ? 'min' : 'raw'}.js`, src, 'utf8'); fs.writeFileSync(`${__dirname}/built/client/assets/${file}.${version}.${lang}.${isProduction ? 'min' : 'raw'}.js`, src, 'utf-8');
}); });
}); });
//#endregion //#endregion
@ -146,27 +146,20 @@ module.exports = {
}, { }, {
loader: 'replace', loader: 'replace',
query: { query: {
search: /%base64:(.+?)%/g.toString(), qs: [{
replace: 'base64replacement' search: /%base64:(.+?)%/g.toString(),
} replace: 'base64replacement'
}, { }, {
loader: 'replace', search: i18nPattern.toString(),
query: { replace: 'i18nReplacement',
search: i18nPattern.toString(), i18n: true
replace: 'i18nReplacement', }, {
i18n: true search: faPattern.toString(),
} replace: 'faReplacement'
}, { }, {
loader: 'replace', search: /^<template>([\s\S]+?)\r?\n<\/template>/.toString(),
query: { replace: 'collapseSpacesReplacement'
search: faPattern.toString(), }]
replace: 'faReplacement'
}
}, {
loader: 'replace',
query: {
search: /^<template>([\s\S]+?)\r?\n<\/template>/.toString(),
replace: 'collapseSpacesReplacement'
} }
}] }]
}, { }, {

View file

@ -1,22 +1,30 @@
const loaderUtils = require('loader-utils'); import { getOptions } from 'loader-utils';
function trim(text, g) { function trim(text, g) {
return text.substring(1, text.length - (g ? 2 : 0)); return text.substring(1, text.length - (g ? 2 : 0));
} }
module.exports = function(src) { export default function(src) {
const fn = options => {
const search = options.search;
const g = search[search.length - 1] == 'g';
const file = this.resourcePath.replace(/\\/g, '/');
const replace = options.i18n ? global[options.replace].bind(null, {
src: file,
lang: options.lang
}) : global[options.replace];
if (typeof search != 'string' || search.length == 0) console.error('invalid search');
if (typeof replace != 'function') console.error('invalid replacer:', replace, this.request);
src = src.replace(new RegExp(trim(search, g), g ? 'g' : ''), replace);
};
this.cacheable(); this.cacheable();
const options = loaderUtils.getOptions(this); const options = getOptions(this);
const search = options.search; if (options.qs) {
const g = search[search.length - 1] == 'g'; options.qs.forEach(q => fn(q));
const file = this.resourcePath.replace(/\\/g, '/'); } else {
const replace = options.i18n ? global[options.replace].bind(null, { fn(options);
src: file, }
lang: options.lang
}) : global[options.replace];
if (typeof search != 'string' || search.length == 0) console.error('invalid search');
if (typeof replace != 'function') console.error('invalid replacer:', replace, this.request);
src = src.replace(new RegExp(trim(search, g), g ? 'g' : ''), replace);
this.callback(null, src); this.callback(null, src);
return src; return src;
}; }