Merge branch 'master' into l10n_master
This commit is contained in:
		
						commit
						0d0c45a4cf
					
				
					 55 changed files with 575 additions and 1526 deletions
				
			
		
							
								
								
									
										11
									
								
								.travis.yml
									
										
									
									
									
								
							
							
						
						
									
										11
									
								
								.travis.yml
									
										
									
									
									
								
							|  | @ -7,7 +7,7 @@ notifications: | |||
| language: node_js | ||||
| 
 | ||||
| node_js: | ||||
|   - 9.8.0 | ||||
|   - 10.1.0 | ||||
| 
 | ||||
| env: | ||||
|   - CXX=g++-4.8 NODE_ENV=production | ||||
|  | @ -22,19 +22,14 @@ addons: | |||
| 
 | ||||
| cache: | ||||
|   directories: | ||||
|     # パッケージをキャッシュすると本来は動かないはずなのに動いてしまう | ||||
|     # 場合があり危険なのでキャッシュはしない: | ||||
|     #- node_modules | ||||
|     - node_modules | ||||
| 
 | ||||
| services: | ||||
|   - mongodb | ||||
|   - redis-server | ||||
| 
 | ||||
| before_script: | ||||
|   # Travisはproduction環境なので(10行目により)、 | ||||
|   # npm install しただけでは devDependencies はインストールされないので、 | ||||
|   # --only=dev オプションを付けてそれらもインストールされるようにする: | ||||
|   - npm install --only=dev | ||||
|   - npm install | ||||
| 
 | ||||
|   # 設定ファイルを配置 | ||||
|   - cp ./.travis/default.yml ./.config | ||||
|  |  | |||
							
								
								
									
										41
									
								
								appveyor.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								appveyor.yml
									
										
									
									
									
										Normal 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 | ||||
|  | @ -20,6 +20,7 @@ import * as replace from 'gulp-replace'; | |||
| import * as htmlmin from 'gulp-htmlmin'; | ||||
| const uglifyes = require('uglify-es'); | ||||
| 
 | ||||
| import locales from './locales'; | ||||
| import { fa } from './src/build/fa'; | ||||
| const client = require('./built/client/meta.json'); | ||||
| import config from './src/config'; | ||||
|  | @ -122,6 +123,7 @@ gulp.task('build:client:script', () => | |||
| 		.pipe(replace('VERSION', JSON.stringify(client.version))) | ||||
| 		.pipe(replace('API', JSON.stringify(config.api_url))) | ||||
| 		.pipe(replace('ENV', JSON.stringify(env))) | ||||
| 		.pipe(replace('LANGS', JSON.stringify(Object.keys(locales)))) | ||||
| 		.pipe(isProduction ? uglify({ | ||||
| 			toplevel: true | ||||
| 		} as any) : gutil.noop()) | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| --- | ||||
| meta: | ||||
|   lang: "日本語" | ||||
|   divider: "" | ||||
|   lang: "English" | ||||
|   divider: " " | ||||
| common: | ||||
|   misskey: "Share everything with others using Misskey." | ||||
|   time: | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ const loadLang = lang => yaml.safeLoad( | |||
| const native = loadLang('ja'); | ||||
| 
 | ||||
| const langs = { | ||||
| 	'de': loadLang('de'), | ||||
| 	'en': loadLang('en'), | ||||
| 	'fr': loadLang('fr'), | ||||
| 	'ja': native, | ||||
|  |  | |||
|  | @ -1,3 +1,7 @@ | |||
| meta: | ||||
|   lang: "日本語" | ||||
|   divider: "" | ||||
| 
 | ||||
| common: | ||||
|   misskey: "Misskeyで皆と共有しよう。" | ||||
| 
 | ||||
|  | @ -253,6 +257,32 @@ desktop/views/components/drive.vue: | |||
|     upload: "ファイルをアップロード" | ||||
|     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: | ||||
|   title: "メッセージ" | ||||
| 
 | ||||
|  | @ -312,6 +342,7 @@ desktop/views/components/settings.vue: | |||
|   mute: "ミュート" | ||||
|   drive: "ドライブ" | ||||
|   security: "セキュリティ" | ||||
|   signin: "サインイン履歴" | ||||
|   password: "パスワード" | ||||
|   2fa: "二段階認証" | ||||
|   other: "その他" | ||||
|  | @ -341,6 +372,7 @@ desktop/views/components/settings.api.vue: | |||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "パスワードを入力してください" | ||||
| 
 | ||||
| desktop/views/components/settings.app.vue: | ||||
|  | @ -396,6 +428,20 @@ desktop/views/components/ui.header.post.vue: | |||
| desktop/views/components/ui.header.search.vue: | ||||
|   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: | ||||
|   prev: "前の投稿" | ||||
|   next: "次の投稿" | ||||
|  | @ -510,6 +556,9 @@ mobile/views/components/notifications.vue: | |||
| 
 | ||||
| mobile/views/components/post-form.vue: | ||||
|   submit: "投稿" | ||||
|   reply: "返信" | ||||
|   renote: "Renote" | ||||
|   renote-placeholder: "この投稿を引用... (オプション)" | ||||
|   reply-placeholder: "この投稿への返信..." | ||||
|   note-placeholder: "いまどうしてる?" | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										75
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										75
									
								
								package.json
									
										
									
									
									
								
							|  | @ -1,8 +1,8 @@ | |||
| { | ||||
| 	"name": "misskey", | ||||
| 	"author": "syuilo <i@syuilo.com>", | ||||
| 	"version": "2.6.2", | ||||
| 	"clientVersion": "1.0.5260", | ||||
| 	"version": "2.10.0", | ||||
| 	"clientVersion": "1.0.5406", | ||||
| 	"codename": "nighthike", | ||||
| 	"main": "./built/index.js", | ||||
| 	"private": true, | ||||
|  | @ -31,13 +31,11 @@ | |||
| 		"@prezzemolo/rap": "0.1.2", | ||||
| 		"@prezzemolo/zip": "0.0.3", | ||||
| 		"@types/bcryptjs": "2.4.1", | ||||
| 		"@types/chai": "4.1.3", | ||||
| 		"@types/chai-http": "3.0.4", | ||||
| 		"@types/debug": "0.0.30", | ||||
| 		"@types/deep-equal": "1.0.1", | ||||
| 		"@types/elasticsearch": "5.0.22", | ||||
| 		"@types/elasticsearch": "5.0.23", | ||||
| 		"@types/eventemitter3": "2.0.2", | ||||
| 		"@types/gm": "1.17.33", | ||||
| 		"@types/gm": "1.18.0", | ||||
| 		"@types/gulp": "3.8.36", | ||||
| 		"@types/gulp-htmlmin": "1.3.32", | ||||
| 		"@types/gulp-mocha": "0.0.32", | ||||
|  | @ -58,18 +56,18 @@ | |||
| 		"@types/koa-multer": "1.0.0", | ||||
| 		"@types/koa-router": "7.0.28", | ||||
| 		"@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/kue": "0.11.8", | ||||
| 		"@types/license-checker": "15.0.0", | ||||
| 		"@types/mkdirp": "0.5.2", | ||||
| 		"@types/mocha": "5.2.0", | ||||
| 		"@types/mongodb": "3.0.15", | ||||
| 		"@types/mongodb": "3.0.18", | ||||
| 		"@types/monk": "6.0.0", | ||||
| 		"@types/ms": "0.7.30", | ||||
| 		"@types/node": "9.6.6", | ||||
| 		"@types/node": "10.1.0", | ||||
| 		"@types/nopt": "3.0.29", | ||||
| 		"@types/parse5": "^3.0.0", | ||||
| 		"@types/parse5": "3.0.0", | ||||
| 		"@types/pug": "2.0.4", | ||||
| 		"@types/qrcode": "0.8.1", | ||||
| 		"@types/ratelimiter": "2.1.28", | ||||
|  | @ -78,22 +76,20 @@ | |||
| 		"@types/request-promise-native": "1.0.14", | ||||
| 		"@types/rimraf": "2.0.2", | ||||
| 		"@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/tmp": "0.0.33", | ||||
| 		"@types/uuid": "3.4.3", | ||||
| 		"@types/webpack": "4.1.4", | ||||
| 		"@types/webpack": "4.1.7", | ||||
| 		"@types/webpack-stream": "3.2.10", | ||||
| 		"@types/websocket": "0.0.38", | ||||
| 		"@types/ws": "4.0.2", | ||||
| 		"@types/websocket": "0.0.39", | ||||
| 		"@types/ws": "5.1.1", | ||||
| 		"animejs": "2.2.0", | ||||
| 		"autosize": "4.0.1", | ||||
| 		"autosize": "4.0.2", | ||||
| 		"autwh": "0.1.0", | ||||
| 		"bcryptjs": "2.4.3", | ||||
| 		"bootstrap-vue": "2.0.0-rc.6", | ||||
| 		"cafy": "8.0.0", | ||||
| 		"chai": "4.1.2", | ||||
| 		"chai-http": "4.0.0", | ||||
| 		"chalk": "2.4.1", | ||||
| 		"crc-32": "1.2.0", | ||||
| 		"css-loader": "0.28.11", | ||||
|  | @ -101,9 +97,9 @@ | |||
| 		"deep-equal": "1.0.1", | ||||
| 		"deepcopy": "0.6.3", | ||||
| 		"diskusage": "0.2.4", | ||||
| 		"dompurify": "1.0.3", | ||||
| 		"dompurify": "1.0.4", | ||||
| 		"elasticsearch": "14.2.2", | ||||
| 		"element-ui": "2.3.6", | ||||
| 		"element-ui": "2.3.8", | ||||
| 		"emojilib": "2.2.12", | ||||
| 		"escape-regexp": "0.0.1", | ||||
| 		"eslint": "4.19.1", | ||||
|  | @ -111,7 +107,7 @@ | |||
| 		"eventemitter3": "3.1.0", | ||||
| 		"exif-js": "2.3.0", | ||||
| 		"file-loader": "1.1.11", | ||||
| 		"file-type": "7.6.0", | ||||
| 		"file-type": "8.0.0", | ||||
| 		"fuckadblock": "3.2.1", | ||||
| 		"gm": "1.23.1", | ||||
| 		"gulp": "3.9.1", | ||||
|  | @ -120,15 +116,15 @@ | |||
| 		"gulp-imagemin": "4.1.0", | ||||
| 		"gulp-mocha": "5.0.0", | ||||
| 		"gulp-pug": "4.0.1", | ||||
| 		"gulp-rename": "1.2.2", | ||||
| 		"gulp-replace": "0.6.1", | ||||
| 		"gulp-rename": "1.2.3", | ||||
| 		"gulp-replace": "1.0.0", | ||||
| 		"gulp-sourcemaps": "2.6.4", | ||||
| 		"gulp-stylus": "2.7.0", | ||||
| 		"gulp-tslint": "8.1.3", | ||||
| 		"gulp-typescript": "4.0.2", | ||||
| 		"gulp-uglify": "3.0.0", | ||||
| 		"gulp-util": "3.0.8", | ||||
| 		"hard-source-webpack-plugin": "0.6.4", | ||||
| 		"hard-source-webpack-plugin": "0.6.7", | ||||
| 		"highlight.js": "9.12.0", | ||||
| 		"html-minifier": "3.5.15", | ||||
| 		"http-signature": "1.2.0", | ||||
|  | @ -136,7 +132,7 @@ | |||
| 		"is-root": "2.0.0", | ||||
| 		"is-url": "1.2.4", | ||||
| 		"js-yaml": "3.11.0", | ||||
| 		"jsdom": "11.9.0", | ||||
| 		"jsdom": "11.10.0", | ||||
| 		"koa": "2.5.1", | ||||
| 		"koa-bodyparser": "4.2.0", | ||||
| 		"koa-compress": "3.0.0", | ||||
|  | @ -148,16 +144,16 @@ | |||
| 		"koa-router": "7.4.0", | ||||
| 		"koa-send": "4.1.3", | ||||
| 		"koa-slow": "2.1.0", | ||||
| 		"koa-views": "^6.1.4", | ||||
| 		"koa-views": "6.1.4", | ||||
| 		"kue": "0.11.6", | ||||
| 		"license-checker": "18.0.0", | ||||
| 		"license-checker": "19.0.0", | ||||
| 		"loader-utils": "1.1.0", | ||||
| 		"mecab-async": "0.1.2", | ||||
| 		"mkdirp": "0.5.1", | ||||
| 		"mocha": "5.1.1", | ||||
| 		"moji": "0.5.1", | ||||
| 		"mongodb": "3.0.7", | ||||
| 		"monk": "6.0.5", | ||||
| 		"mongodb": "3.0.8", | ||||
| 		"monk": "6.0.6", | ||||
| 		"ms": "2.1.1", | ||||
| 		"nan": "2.10.0", | ||||
| 		"node-sass": "4.9.0", | ||||
|  | @ -167,10 +163,10 @@ | |||
| 		"object-assign-deep": "0.4.0", | ||||
| 		"on-build-webpack": "0.1.0", | ||||
| 		"os-utils": "0.0.14", | ||||
| 		"parse5": "^4.0.0", | ||||
| 		"parse5": "4.0.0", | ||||
| 		"progress-bar-webpack-plugin": "1.11.0", | ||||
| 		"prominence": "0.2.0", | ||||
| 		"promise-sequential": "^1.1.1", | ||||
| 		"promise-sequential": "1.1.1", | ||||
| 		"pug": "2.0.3", | ||||
| 		"punycode": "2.1.0", | ||||
| 		"qrcode": "1.2.0", | ||||
|  | @ -178,14 +174,14 @@ | |||
| 		"recaptcha-promise": "0.1.3", | ||||
| 		"reconnecting-websocket": "3.2.2", | ||||
| 		"redis": "2.8.0", | ||||
| 		"request": "2.85.0", | ||||
| 		"request": "2.86.0", | ||||
| 		"request-promise-native": "1.0.5", | ||||
| 		"rimraf": "2.6.2", | ||||
| 		"rndstr": "1.0.0", | ||||
| 		"s-age": "1.1.2", | ||||
| 		"sass-loader": "7.0.1", | ||||
| 		"seedrandom": "2.4.3", | ||||
| 		"single-line-log": "^1.1.2", | ||||
| 		"single-line-log": "1.1.2", | ||||
| 		"speakeasy": "2.0.0", | ||||
| 		"style-loader": "0.21.0", | ||||
| 		"stylus": "0.54.5", | ||||
|  | @ -196,9 +192,9 @@ | |||
| 		"tcp-port-used": "0.1.2", | ||||
| 		"textarea-caret": "3.1.0", | ||||
| 		"tmp": "0.0.33", | ||||
| 		"ts-loader": "4.2.0", | ||||
| 		"ts-node": "6.0.1", | ||||
| 		"tslint": "5.9.1", | ||||
| 		"ts-loader": "4.3.0", | ||||
| 		"ts-node": "6.0.3", | ||||
| 		"tslint": "5.10.0", | ||||
| 		"typescript": "2.8.3", | ||||
| 		"typescript-eslint-parser": "15.0.0", | ||||
| 		"uglify-es": "3.3.9", | ||||
|  | @ -209,16 +205,15 @@ | |||
| 		"vue-cropperjs": "2.2.0", | ||||
| 		"vue-js-modal": "1.3.13", | ||||
| 		"vue-json-tree-view": "2.1.4", | ||||
| 		"vue-loader": "15.0.3", | ||||
| 		"vue-loader": "15.0.11", | ||||
| 		"vue-router": "3.0.1", | ||||
| 		"vue-template-compiler": "2.5.16", | ||||
| 		"vuedraggable": "2.16.0", | ||||
| 		"vuex": "3.0.1", | ||||
| 		"web-push": "3.3.0", | ||||
| 		"web-push": "3.3.1", | ||||
| 		"webfinger.js": "2.6.6", | ||||
| 		"webpack": "4.6.0", | ||||
| 		"webpack-cli": "2.0.15", | ||||
| 		"webpack-replace-loader": "1.3.0", | ||||
| 		"webpack": "4.8.3", | ||||
| 		"webpack-cli": "2.1.3", | ||||
| 		"websocket": "1.0.26", | ||||
| 		"ws": "5.1.1", | ||||
| 		"xev": "2.0.0" | ||||
|  |  | |||
|  | @ -7,10 +7,7 @@ import * as regular from '@fortawesome/fontawesome-free-regular'; | |||
| import * as solid from '@fortawesome/fontawesome-free-solid'; | ||||
| import * as brands from '@fortawesome/fontawesome-free-brands'; | ||||
| 
 | ||||
| // Add icons
 | ||||
| fontawesome.library.add(regular); | ||||
| fontawesome.library.add(solid); | ||||
| fontawesome.library.add(brands); | ||||
| fontawesome.library.add(regular, solid, brands); | ||||
| 
 | ||||
| export const pattern = /%fa:(.+?)%/g; | ||||
| 
 | ||||
|  |  | |||
|  | @ -32,7 +32,7 @@ | |||
| 	// Detect the user language
 | ||||
| 	// Note: The default language is Japanese
 | ||||
| 	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'); | ||||
| 
 | ||||
| 	// Detect the user agent
 | ||||
|  |  | |||
							
								
								
									
										16
									
								
								src/client/app/common/scripts/can-hide-text.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/client/app/common/scripts/can-hide-text.ts
									
										
									
									
									
										Normal 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; | ||||
| } | ||||
|  | @ -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が利用できないので強制的にサインアウトさせる
 | ||||
| 		this.on('my_token_regenerated', () => { | ||||
|  |  | |||
							
								
								
									
										19
									
								
								src/client/app/common/views/components/acct.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/client/app/common/views/components/acct.vue
									
										
									
									
									
										Normal 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> | ||||
|  | @ -23,7 +23,7 @@ export default Vue.extend({ | |||
| 	computed: { | ||||
| 		style(): any { | ||||
| 			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)`, | ||||
| 				borderRadius: (this as any).clientSettings.circleIcons ? '100%' : null | ||||
| 			}; | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import Vue from 'vue'; | |||
| import signin from './signin.vue'; | ||||
| import signup from './signup.vue'; | ||||
| import forkit from './forkit.vue'; | ||||
| import acct from './acct.vue'; | ||||
| import avatar from './avatar.vue'; | ||||
| import nav from './nav.vue'; | ||||
| import noteHtml from './note-html'; | ||||
|  | @ -29,6 +30,7 @@ import welcomeTimeline from './welcome-timeline.vue'; | |||
| Vue.component('mk-signin', signin); | ||||
| Vue.component('mk-signup', signup); | ||||
| Vue.component('mk-forkit', forkit); | ||||
| Vue.component('mk-acct', acct); | ||||
| Vue.component('mk-avatar', avatar); | ||||
| Vue.component('mk-nav', nav); | ||||
| Vue.component('mk-note-html', noteHtml); | ||||
|  |  | |||
|  | @ -126,16 +126,21 @@ root(isDark) | |||
| 					line-height 16px | ||||
| 					vertical-align top | ||||
| 
 | ||||
| 		@media (max-width 500px) | ||||
| 			font-size 8px | ||||
| 			border none | ||||
| 
 | ||||
| 		@media (max-width 700px) | ||||
| 			> .thumbnail | ||||
| 				width 70px | ||||
| 				position relative | ||||
| 				width 100% | ||||
| 				height 100px | ||||
| 
 | ||||
| 				& + article | ||||
| 					left 70px | ||||
| 					width calc(100% - 70px) | ||||
| 					left 0 | ||||
| 					width 100% | ||||
| 
 | ||||
| 		@media (max-width 500px) | ||||
| 			font-size 8px | ||||
| 
 | ||||
| 			> .thumbnail | ||||
| 				height 70px | ||||
| 
 | ||||
| 			> article | ||||
| 				padding 8px | ||||
|  |  | |||
|  | @ -1,37 +1,37 @@ | |||
| <template> | ||||
| <div class="mkw-calendar" | ||||
| 	:data-melt="props.design == 1" | ||||
| 	:data-special="special" | ||||
| 	:data-mobile="isMobile" | ||||
| > | ||||
| 	<div class="calendar" :data-is-holiday="isHoliday"> | ||||
| 		<p class="month-and-year"> | ||||
| 			<span class="year">{{ year }}年</span> | ||||
| 			<span class="month">{{ month }}月</span> | ||||
| 		</p> | ||||
| 		<p class="day">{{ day }}日</p> | ||||
| 		<p class="week-day">{{ weekDay }}曜日</p> | ||||
| 	</div> | ||||
| 	<div class="info"> | ||||
| 		<div> | ||||
| 			<p>今日:<b>{{ dayP.toFixed(1) }}%</b></p> | ||||
| 			<div class="meter"> | ||||
| 				<div class="val" :style="{ width: `${dayP}%` }"></div> | ||||
| <div class="mkw-calendar" :data-special="special" :data-mobile="isMobile"> | ||||
| 	<mk-widget-container :naked="props.design == 1" :show-header="false"> | ||||
| 		<div class="mkw-calendar--body"> | ||||
| 			<div class="calendar" :data-is-holiday="isHoliday"> | ||||
| 				<p class="month-and-year"> | ||||
| 					<span class="year">{{ year }}年</span> | ||||
| 					<span class="month">{{ month }}月</span> | ||||
| 				</p> | ||||
| 				<p class="day">{{ day }}日</p> | ||||
| 				<p class="week-day">{{ weekDay }}曜日</p> | ||||
| 			</div> | ||||
| 			<div class="info"> | ||||
| 				<div> | ||||
| 					<p>今日:<b>{{ dayP.toFixed(1) }}%</b></p> | ||||
| 					<div class="meter"> | ||||
| 						<div class="val" :style="{ width: `${dayP}%` }"></div> | ||||
| 					</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> | ||||
| 			<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> | ||||
| 	</mk-widget-container> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -111,93 +111,82 @@ export default define({ | |||
| @import '~const.styl' | ||||
| 
 | ||||
| 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'] | ||||
| 		border-color #ef95a0 | ||||
| 
 | ||||
| 	&[data-melt] | ||||
| 		background transparent | ||||
| 		border none | ||||
| 	.mkw-calendar--body | ||||
| 		padding 16px 0 | ||||
| 		color isDark ? #c5ced6 : #777 | ||||
| 
 | ||||
| 	&[data-mobile] | ||||
| 		border none | ||||
| 		border-radius 8px | ||||
| 		box-shadow 0 0 0 1px rgba(#000, 0.2) | ||||
| 		&:after | ||||
| 			content "" | ||||
| 			display block | ||||
| 			clear both | ||||
| 
 | ||||
| 	&:after | ||||
| 		content "" | ||||
| 		display block | ||||
| 		clear both | ||||
| 		> .calendar | ||||
| 			float left | ||||
| 			width 60% | ||||
| 			text-align center | ||||
| 
 | ||||
| 	> .calendar | ||||
| 		float left | ||||
| 		width 60% | ||||
| 		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 | ||||
| 			&[data-is-holiday] | ||||
| 				> .day | ||||
| 					color #ef95a0 | ||||
| 
 | ||||
| 			> p | ||||
| 				margin 0 0 2px 0 | ||||
| 				font-size 12px | ||||
| 				margin 0 | ||||
| 				line-height 18px | ||||
| 				color isDark ? #7a8692 : #888 | ||||
| 				font-size 14px | ||||
| 
 | ||||
| 				> b | ||||
| 					margin-left 2px | ||||
| 				> span | ||||
| 					margin 0 4px | ||||
| 
 | ||||
| 			> .meter | ||||
| 				width 100% | ||||
| 				overflow hidden | ||||
| 				background isDark ? #1c1f25 : #eee | ||||
| 				border-radius 8px | ||||
| 			> .day | ||||
| 				margin 10px 0 | ||||
| 				line-height 32px | ||||
| 				font-size 28px | ||||
| 
 | ||||
| 				> .val | ||||
| 					height 4px | ||||
| 					background $theme-color | ||||
| 		> .info | ||||
| 			display block | ||||
| 			float left | ||||
| 			width 40% | ||||
| 			padding 0 16px 0 0 | ||||
| 
 | ||||
| 			&:nth-child(1) | ||||
| 				> .meter > .val | ||||
| 					background #f7796c | ||||
| 			> div | ||||
| 				margin-bottom 8px | ||||
| 
 | ||||
| 			&:nth-child(2) | ||||
| 				> .meter > .val | ||||
| 					background #a1de41 | ||||
| 				&:last-child | ||||
| 					margin-bottom 4px | ||||
| 
 | ||||
| 			&:nth-child(3) | ||||
| 				> .meter > .val | ||||
| 					background #41ddde | ||||
| 				> p | ||||
| 					margin 0 0 2px 0 | ||||
| 					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] | ||||
| 	root(true) | ||||
|  |  | |||
|  | @ -1,10 +1,10 @@ | |||
| <template> | ||||
| <div class="mkw-rss" :data-mobile="isMobile"> | ||||
| <div class="mkw-rss"> | ||||
| 	<mk-widget-container :show-header="!props.compact"> | ||||
| 		<template slot="header">%fa:rss-square%RSS</template> | ||||
| 		<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> | ||||
| 			<div class="feed" v-else> | ||||
| 				<a v-for="item in items" :href="item.link" target="_blank">{{ item.title }}</a> | ||||
|  | @ -85,15 +85,17 @@ root(isDark) | |||
| 				margin-right 4px | ||||
| 
 | ||||
| 		&[data-mobile] | ||||
| 			background isDark ? #21242f : #f3f3f3 | ||||
| 
 | ||||
| 			.feed | ||||
| 				padding 0 | ||||
| 				font-size 1em | ||||
| 
 | ||||
| 				> a | ||||
| 					padding 8px 16px | ||||
| 					border-bottom none | ||||
| 
 | ||||
| 					&:nth-child(even) | ||||
| 						background rgba(#000, 0.05) | ||||
| 						background isDark ? rgba(#000, 0.05) : rgba(#fff, 0.7) | ||||
| 
 | ||||
| .mkw-rss[data-darkmode] | ||||
| 	root(true) | ||||
|  |  | |||
|  | @ -50,7 +50,7 @@ export default Vue.extend({ | |||
| 			return `${this.file.name}\n${this.file.type} ${Vue.filter('bytes')(this.file.datasize)}`; | ||||
| 		}, | ||||
| 		background(): string { | ||||
| 			return this.file.properties.avgColor | ||||
| 			return this.file.properties.avgColor && this.file.properties.avgColor.length == 3 | ||||
| 				? `rgb(${this.file.properties.avgColor.join(',')})` | ||||
| 				: 'transparent'; | ||||
| 		} | ||||
|  | @ -129,7 +129,7 @@ export default Vue.extend({ | |||
| 		}, | ||||
| 
 | ||||
| 		onThumbnailLoaded() { | ||||
| 			if (this.file.properties.avgColor) { | ||||
| 			if (this.file.properties.avgColor && this.file.properties.avgColor.length == 3) { | ||||
| 				anime({ | ||||
| 					targets: this.$refs.thumbnail, | ||||
| 					backgroundColor: `rgba(${this.file.properties.avgColor.join(',')}, 0)`, | ||||
|  |  | |||
|  | @ -1,34 +1,34 @@ | |||
| <template> | ||||
| <div class="mk-home" :data-customize="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 class="adder"> | ||||
| 				<p>ウィジェットを追加:</p> | ||||
| 				<p>%i18n:@add-widget%</p> | ||||
| 				<select v-model="widgetAdderSelected"> | ||||
| 					<option value="profile">プロフィール</option> | ||||
| 					<option value="calendar">カレンダー</option> | ||||
| 					<option value="timemachine">カレンダー(タイムマシン)</option> | ||||
| 					<option value="activity">アクティビティ</option> | ||||
| 					<option value="rss">RSSリーダー</option> | ||||
| 					<option value="trends">トレンド</option> | ||||
| 					<option value="photo-stream">フォトストリーム</option> | ||||
| 					<option value="slideshow">スライドショー</option> | ||||
| 					<option value="version">バージョン</option> | ||||
| 					<option value="broadcast">ブロードキャスト</option> | ||||
| 					<option value="notifications">通知</option> | ||||
| 					<option value="users">おすすめユーザー</option> | ||||
| 					<option value="polls">投票</option> | ||||
| 					<option value="post-form">投稿フォーム</option> | ||||
| 					<option value="messaging">メッセージ</option> | ||||
| 					<option value="channel">チャンネル</option> | ||||
| 					<option value="access-log">アクセスログ</option> | ||||
| 					<option value="server">サーバー情報</option> | ||||
| 					<option value="donation">寄付のお願い</option> | ||||
| 					<option value="nav">ナビゲーション</option> | ||||
| 					<option value="tips">ヒント</option> | ||||
| 					<option value="profile">%i18n:@profile%</option> | ||||
| 					<option value="calendar">%i18n:@calendar%</option> | ||||
| 					<option value="timemachine">%i18n:@timemachine%</option> | ||||
| 					<option value="activity">%i18n:@activity%</option> | ||||
| 					<option value="rss">%i18n:@rss%</option> | ||||
| 					<option value="trends">%i18n:@trends%</option> | ||||
| 					<option value="photo-stream">%i18n:@photo-stream%</option> | ||||
| 					<option value="slideshow">%i18n:@slideshow%</option> | ||||
| 					<option value="version">%i18n:@version%</option> | ||||
| 					<option value="broadcast">%i18n:@broadcast%</option> | ||||
| 					<option value="notifications">%i18n:@notifications%</option> | ||||
| 					<option value="users">%i18n:@users%</option> | ||||
| 					<option value="polls">%i18n:@polls%</option> | ||||
| 					<option value="post-form">%i18n:@post-form%</option> | ||||
| 					<option value="messaging">%i18n:@messaging%</option> | ||||
| 					<option value="channel">%i18n:@channel%</option> | ||||
| 					<option value="access-log">%i18n:@access-log%</option> | ||||
| 					<option value="server">%i18n:@server%</option> | ||||
| 					<option value="donation">%i18n:@donation%</option> | ||||
| 					<option value="nav">%i18n:@nav%</option> | ||||
| 					<option value="tips">%i18n:@tips%</option> | ||||
| 				</select> | ||||
| 				<button @click="addWidget">追加</button> | ||||
| 				<button @click="addWidget">%i18n:@add%</button> | ||||
| 			</div> | ||||
| 			<div class="trash"> | ||||
| 				<x-draggable v-model="trash" :options="{ group: 'x' }" @add="onTrash"></x-draggable> | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ export default Vue.extend({ | |||
| 	computed: { | ||||
| 		style(): any { | ||||
| 			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)` | ||||
| 			}; | ||||
| 		} | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ | |||
| 		<header> | ||||
| 			<div class="left"> | ||||
| 				<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 class="right"> | ||||
| 				<router-link class="time" :to="note | notePage"> | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ | |||
| 		<mk-avatar class="avatar" :user="p.user"/> | ||||
| 		<header> | ||||
| 			<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"> | ||||
| 				<mk-time :time="p.createdAt"/> | ||||
| 			</router-link> | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<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"> | ||||
| 				<mk-time :time="note.createdAt"/> | ||||
| 			</router-link> | ||||
|  | @ -59,17 +59,20 @@ root(isDark) | |||
| 			> .name | ||||
| 				margin 0 .5em 0 0 | ||||
| 				padding 0 | ||||
| 				overflow hidden | ||||
| 				color isDark ? #fff : #607073 | ||||
| 				font-size 1em | ||||
| 				font-weight bold | ||||
| 				text-decoration none | ||||
| 				white-space normal | ||||
| 				text-overflow ellipsis | ||||
| 
 | ||||
| 				&:hover | ||||
| 					text-decoration underline | ||||
| 
 | ||||
| 			> .username | ||||
| 				margin 0 .5em 0 0 | ||||
| 				overflow hidden | ||||
| 				text-overflow ellipsis | ||||
| 				color isDark ? #606984 : #d1d8da | ||||
| 
 | ||||
| 			> .time | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
| 	<div class="main"> | ||||
| 		<header> | ||||
| 			<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"> | ||||
| 				<span class="mobile" v-if="note.viaMobile">%fa:mobile-alt%</span> | ||||
| 				<router-link class="created-at" :to="note | notePage"> | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ | |||
| 			<header> | ||||
| 				<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="username">@{{ p.user | acct }}</span> | ||||
| 				<span class="username"><mk-acct :user="p.user"/></span> | ||||
| 				<div class="info"> | ||||
| 					<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> | ||||
|  | @ -44,7 +44,7 @@ | |||
| 					<div class="text"> | ||||
| 						<span v-if="p.isHidden" style="opacity: 0.5">(この投稿は非公開です)</span> | ||||
| 						<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> | ||||
| 					</div> | ||||
| 					<div class="media" v-if="p.media.length > 0"> | ||||
|  | @ -94,6 +94,7 @@ | |||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import dateStringify from '../../../common/scripts/date-stringify'; | ||||
| import canHideText from '../../../common/scripts/can-hide-text'; | ||||
| import parse from '../../../../../text/parse'; | ||||
| 
 | ||||
| import MkPostFormWindow from './post-form-window.vue'; | ||||
|  | @ -130,16 +131,17 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 
 | ||||
| 		isRenote(): boolean { | ||||
| 			return (this.note.renote && | ||||
| 				this.note.text == null && | ||||
| 				this.note.mediaIds.length == 0 && | ||||
| 				this.note.poll == null); | ||||
| 		}, | ||||
| 
 | ||||
| 		p(): any { | ||||
| 			return this.isRenote ? this.note.renote : this.note; | ||||
| 		}, | ||||
| 
 | ||||
| 		reactionsCount(): number { | ||||
| 			return this.p.reactionCounts | ||||
| 				? Object.keys(this.p.reactionCounts) | ||||
|  | @ -147,9 +149,11 @@ export default Vue.extend({ | |||
| 					.reduce((a, b) => a + b) | ||||
| 				: 0; | ||||
| 		}, | ||||
| 
 | ||||
| 		title(): string { | ||||
| 			return dateStringify(this.p.createdAt); | ||||
| 		}, | ||||
| 
 | ||||
| 		urls(): string[] { | ||||
| 			if (this.p.text) { | ||||
| 				const ast = parse(this.p.text); | ||||
|  | @ -205,6 +209,8 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		canHideText, | ||||
| 
 | ||||
| 		capture(withHandler = false) { | ||||
| 			if ((this as any).os.isSignedIn) { | ||||
| 				this.connection.send({ | ||||
|  | @ -214,6 +220,7 @@ export default Vue.extend({ | |||
| 				if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated); | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		decapture(withHandler = false) { | ||||
| 			if ((this as any).os.isSignedIn) { | ||||
| 				this.connection.send({ | ||||
|  | @ -223,9 +230,11 @@ export default Vue.extend({ | |||
| 				if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated); | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		onStreamConnected() { | ||||
| 			this.capture(); | ||||
| 		}, | ||||
| 
 | ||||
| 		onStreamNoteUpdated(data) { | ||||
| 			const note = data.note; | ||||
| 			if (note.id == this.note.id) { | ||||
|  | @ -234,28 +243,33 @@ export default Vue.extend({ | |||
| 				this.note.renote = note; | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		reply() { | ||||
| 			(this as any).os.new(MkPostFormWindow, { | ||||
| 				reply: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		renote() { | ||||
| 			(this as any).os.new(MkRenoteFormWindow, { | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		react() { | ||||
| 			(this as any).os.new(MkReactionPicker, { | ||||
| 				source: this.$refs.reactButton, | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		menu() { | ||||
| 			(this as any).os.new(MkNoteMenu, { | ||||
| 				source: this.$refs.menuButton, | ||||
| 				note: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		onKeydown(e) { | ||||
| 			let shouldBeCancel = true; | ||||
| 
 | ||||
|  | @ -336,6 +350,7 @@ root(isDark) | |||
| 		align-items center | ||||
| 		padding 16px 32px | ||||
| 		line-height 28px | ||||
| 		white-space pre | ||||
| 		color #9dbb00 | ||||
| 		background isDark ? linear-gradient(to bottom, #314027 0%, #282c37 100%) : linear-gradient(to bottom, #edfde2 0%, #fff 100%) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <template> | ||||
| <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> | ||||
| 	<div class="ui info warn"><p>%fa:exclamation-triangle%%i18n:@caution%</p></div> | ||||
| 	<p>%i18n:@regeneration-of-token%</p> | ||||
|  |  | |||
|  | @ -80,10 +80,11 @@ | |||
| 					<el-option label="自動" value=""/> | ||||
| 				</el-option-group> | ||||
| 				<el-option-group label="言語を指定"> | ||||
| 					<el-option label="ja-JP" value="ja"/> | ||||
| 					<el-option label="en-US" value="en"/> | ||||
| 					<el-option label="ja" value="ja"/> | ||||
| 					<el-option label="en" value="en"/> | ||||
| 					<el-option label="fr" value="fr"/> | ||||
| 					<el-option label="pl" value="pl"/> | ||||
| 					<el-option label="de" value="de"/> | ||||
| 				</el-option-group> | ||||
| 			</el-select> | ||||
| 			<div class="none ui info"> | ||||
|  | @ -100,7 +101,7 @@ | |||
| 		</section> | ||||
| 
 | ||||
| 		<section class="notification" v-show="page == 'notification'"> | ||||
| 			<h1>通知</h1> | ||||
| 			<h1>%i18n:@notification%</h1> | ||||
| 			<mk-switch v-model="os.i.settings.autoWatch" @change="onChangeAutoWatch" text="投稿の自動ウォッチ"> | ||||
| 				<span>リアクションしたり返信したりした投稿に関する通知を自動的に受け取るようにします。</span> | ||||
| 			</mk-switch> | ||||
|  | @ -117,7 +118,7 @@ | |||
| 		</section> | ||||
| 
 | ||||
| 		<section class="apps" v-show="page == 'apps'"> | ||||
| 			<h1>アプリケーション</h1> | ||||
| 			<h1>%i18n:@apps%</h1> | ||||
| 			<x-apps/> | ||||
| 		</section> | ||||
| 
 | ||||
|  | @ -137,7 +138,7 @@ | |||
| 		</section> | ||||
| 
 | ||||
| 		<section class="signin" v-show="page == 'security'"> | ||||
| 			<h1>サインイン履歴</h1> | ||||
| 			<h1>%i18n:@signin%</h1> | ||||
| 			<x-signins/> | ||||
| 		</section> | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
| 	<span slot="header">%fa:list% リスト</span> | ||||
| 
 | ||||
| 	<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> | ||||
| 	</div> | ||||
| </mk-window> | ||||
|  |  | |||
|  | @ -10,13 +10,13 @@ | |||
| 		<div class="description">{{ u.description }}</div> | ||||
| 		<div class="status"> | ||||
| 			<div> | ||||
| 				<p>投稿</p><a>{{ u.notesCount }}</a> | ||||
| 				<p>%i18n:@notes%</p><a>{{ u.notesCount }}</a> | ||||
| 			</div> | ||||
| 			<div> | ||||
| 				<p>フォロー</p><a>{{ u.followingCount }}</a> | ||||
| 				<p>%i18n:@following%</p><a>{{ u.followingCount }}</a> | ||||
| 			</div> | ||||
| 			<div> | ||||
| 				<p>フォロワー</p><a>{{ u.followersCount }}</a> | ||||
| 				<p>%i18n:@followers%</p><a>{{ u.followersCount }}</a> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<mk-follow-button v-if="os.isSignedIn && user.id != os.i.id" :user="u"/> | ||||
|  |  | |||
|  | @ -2,8 +2,8 @@ | |||
| <div class="mk-users-list"> | ||||
| 	<nav> | ||||
| 		<div> | ||||
| 			<span :data-active="mode == 'all'" @click="mode = 'all'">すべて<span>{{ count }}</span></span> | ||||
| 			<span v-if="os.isSignedIn && youKnowCount" :data-active="mode == 'iknow'" @click="mode = 'iknow'">知り合い<span>{{ youKnowCount }}</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'">%i18n:@iknow%<span>{{ youKnowCount }}</span></span> | ||||
| 		</div> | ||||
| 	</nav> | ||||
| 	<div class="users" v-if="!fetching && users.length != 0"> | ||||
|  | @ -12,13 +12,13 @@ | |||
| 		</div> | ||||
| 	</div> | ||||
| 	<button class="more" v-if="!fetching && next != null" @click="more" :disabled="moreFetching"> | ||||
| 		<span v-if="!moreFetching">もっと</span> | ||||
| 		<span v-if="moreFetching">読み込み中<mk-ellipsis/></span> | ||||
| 		<span v-if="!moreFetching">%i18n:@load-more%</span> | ||||
| 		<span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span> | ||||
| 	</button> | ||||
| 	<p class="no" v-if="!fetching && users.length == 0"> | ||||
| 		<slot></slot> | ||||
| 	</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> | ||||
| </template> | ||||
| 
 | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ export default Vue.extend({ | |||
| 		style(): any { | ||||
| 			if (this.user.bannerUrl == null) 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 })` | ||||
| 			}; | ||||
| 		} | ||||
|  |  | |||
|  | @ -1,43 +1,24 @@ | |||
| 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) => { | ||||
| 	const o = opts || {}; | ||||
| 
 | ||||
| 	if (o.renote) { | ||||
| 		/*const vm = new RenoteForm({ | ||||
| 			propsData: { | ||||
| 				renote: o.renote | ||||
| 			} | ||||
| 		}).$mount(); | ||||
| 		vm.$once('cancel', recover); | ||||
| 		vm.$once('note', recover); | ||||
| 		document.body.appendChild(vm.$el);*/ | ||||
| 	const app = document.getElementById('app'); | ||||
| 	app.style.display = 'none'; | ||||
| 
 | ||||
| 		const text = window.prompt(`「${getNoteSummary(o.renote)}」をRenote`); | ||||
| 		if (text == null) return; | ||||
| 		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(); | ||||
| 	function recover() { | ||||
| 		app.style.display = 'block'; | ||||
| 	} | ||||
| 
 | ||||
| 	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(); | ||||
| }; | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ import MkUser from './views/pages/user.vue'; | |||
| import MkSelectDrive from './views/pages/selectdrive.vue'; | ||||
| import MkDrive from './views/pages/drive.vue'; | ||||
| import MkNotifications from './views/pages/notifications.vue'; | ||||
| import MkWidgets from './views/pages/widgets.vue'; | ||||
| import MkMessaging from './views/pages/messaging.vue'; | ||||
| import MkMessagingRoom from './views/pages/messaging-room.vue'; | ||||
| import MkNote from './views/pages/note.vue'; | ||||
|  | @ -56,6 +57,7 @@ init((launch) => { | |||
| 			{ path: '/i/settings', component: MkSettings }, | ||||
| 			{ path: '/i/settings/profile', component: MkProfileSetting }, | ||||
| 			{ path: '/i/notifications', name: 'notifications', component: MkNotifications }, | ||||
| 			{ path: '/i/widgets', name: 'widgets', component: MkWidgets }, | ||||
| 			{ path: '/i/messaging', name: 'messaging', component: MkMessaging }, | ||||
| 			{ path: '/i/messaging/:user', component: MkMessagingRoom }, | ||||
| 			{ path: '/i/drive', name: 'drive', component: MkDrive }, | ||||
|  |  | |||
|  | @ -86,7 +86,7 @@ export default Vue.extend({ | |||
| 			return this.file.type.split('/')[0]; | ||||
| 		}, | ||||
| 		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(',') })` | ||||
| 			} : {}; | ||||
| 		} | ||||
|  |  | |||
|  | @ -42,7 +42,7 @@ export default Vue.extend({ | |||
| 		}, | ||||
| 		thumbnail(): any { | ||||
| 			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)` | ||||
| 			}; | ||||
| 		} | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ export default Vue.extend({ | |||
| 	computed: { | ||||
| 		style(): any { | ||||
| 			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)` | ||||
| 			}; | ||||
| 		} | ||||
|  |  | |||
|  | @ -69,8 +69,9 @@ root(isDark) | |||
| 					text-decoration underline | ||||
| 
 | ||||
| 			> .username | ||||
| 				text-align left | ||||
| 				margin 0 .5em 0 0 | ||||
| 				overflow hidden | ||||
| 				text-overflow ellipsis | ||||
| 				color isDark ? #606984 : #d1d8da | ||||
| 
 | ||||
| 			> .time | ||||
|  |  | |||
|  | @ -41,7 +41,7 @@ | |||
| 					<div class="text"> | ||||
| 						<span v-if="p.isHidden" style="opacity: 0.5">(この投稿は非公開です)</span> | ||||
| 						<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> | ||||
| 					</div> | ||||
| 					<div class="media" v-if="p.media.length > 0"> | ||||
|  | @ -85,6 +85,7 @@ | |||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import parse from '../../../../../text/parse'; | ||||
| import canHideText from '../../../common/scripts/can-hide-text'; | ||||
| 
 | ||||
| import MkNoteMenu from '../../../common/views/components/note-menu.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.poll == null); | ||||
| 		}, | ||||
| 
 | ||||
| 		p(): any { | ||||
| 			return this.isRenote ? this.note.renote : this.note; | ||||
| 		}, | ||||
| 
 | ||||
| 		reactionsCount(): number { | ||||
| 			return this.p.reactionCounts | ||||
| 				? Object.keys(this.p.reactionCounts) | ||||
|  | @ -122,6 +125,7 @@ export default Vue.extend({ | |||
| 					.reduce((a, b) => a + b) | ||||
| 				: 0; | ||||
| 		}, | ||||
| 
 | ||||
| 		urls(): string[] { | ||||
| 			if (this.p.text) { | ||||
| 				const ast = parse(this.p.text); | ||||
|  | @ -177,6 +181,8 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		canHideText, | ||||
| 
 | ||||
| 		capture(withHandler = false) { | ||||
| 			if ((this as any).os.isSignedIn) { | ||||
| 				this.connection.send({ | ||||
|  | @ -186,6 +192,7 @@ export default Vue.extend({ | |||
| 				if (withHandler) this.connection.on('note-updated', this.onStreamNoteUpdated); | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		decapture(withHandler = false) { | ||||
| 			if ((this as any).os.isSignedIn) { | ||||
| 				this.connection.send({ | ||||
|  | @ -195,9 +202,11 @@ export default Vue.extend({ | |||
| 				if (withHandler) this.connection.off('note-updated', this.onStreamNoteUpdated); | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		onStreamConnected() { | ||||
| 			this.capture(); | ||||
| 		}, | ||||
| 
 | ||||
| 		onStreamNoteUpdated(data) { | ||||
| 			const note = data.note; | ||||
| 			if (note.id == this.note.id) { | ||||
|  | @ -206,16 +215,19 @@ export default Vue.extend({ | |||
| 				this.note.renote = note; | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		reply() { | ||||
| 			(this as any).apis.post({ | ||||
| 				reply: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		renote() { | ||||
| 			(this as any).apis.post({ | ||||
| 				renote: this.p | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		react() { | ||||
| 			(this as any).os.new(MkReactionPicker, { | ||||
| 				source: this.$refs.reactButton, | ||||
|  | @ -223,6 +235,7 @@ export default Vue.extend({ | |||
| 				compact: true | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		menu() { | ||||
| 			(this as any).os.new(MkNoteMenu, { | ||||
| 				source: this.$refs.menuButton, | ||||
|  | @ -255,6 +268,7 @@ root(isDark) | |||
| 		align-items center | ||||
| 		padding 8px 16px | ||||
| 		line-height 28px | ||||
| 		white-space pre | ||||
| 		color #9dbb00 | ||||
| 		background isDark ? linear-gradient(to bottom, #314027 0%, #282c37 100%) : linear-gradient(to bottom, #edfde2 0%, #fff 100%) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,5 @@ | |||
| <template> | ||||
| <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="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() { | ||||
| 		document.addEventListener('visibilitychange', this.onVisibilitychange, false); | ||||
| 		window.addEventListener('scroll', this.onScroll); | ||||
|  | @ -238,13 +246,6 @@ root(isDark) | |||
| 			[data-fa] | ||||
| 				margin-right 8px | ||||
| 
 | ||||
| 	> .newer-indicator | ||||
| 		position -webkit-sticky | ||||
| 		position sticky | ||||
| 		z-index 100 | ||||
| 		height 3px | ||||
| 		background $theme-color | ||||
| 
 | ||||
| 	> .init | ||||
| 		padding 64px 0 | ||||
| 		text-align center | ||||
|  |  | |||
|  | @ -5,17 +5,22 @@ | |||
| 		<div> | ||||
| 			<span class="text-count" :class="{ over: text.length > 1000 }">{{ 1000 - text.length }}</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> | ||||
| 	</header> | ||||
| 	<div class="form"> | ||||
| 		<mk-note-preview v-if="reply" :note="reply"/> | ||||
| 		<mk-note-preview v-if="renote" :note="renote"/> | ||||
| 		<div v-if="visibility == 'specified'" class="visibleUsers"> | ||||
| 			<span v-for="u in visibleUsers">{{ u | userName }}<a @click="removeVisibleUser(u)">[x]</a></span> | ||||
| 			<a @click="addVisibleUser">+ユーザーを追加</a> | ||||
| 		</div> | ||||
| 		<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"> | ||||
| 			<x-draggable class="files" :list="files" :options="{ animation: 150 }"> | ||||
| 				<div class="file" v-for="file in files" :key="file.id"> | ||||
|  | @ -51,7 +56,7 @@ export default Vue.extend({ | |||
| 		MkVisibilityChooser | ||||
| 	}, | ||||
| 
 | ||||
| 	props: ['reply'], | ||||
| 	props: ['reply', 'renote'], | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
|  | @ -177,6 +182,7 @@ export default Vue.extend({ | |||
| 				text: this.text == '' ? undefined : this.text, | ||||
| 				mediaIds: this.files.length > 0 ? this.files.map(f => f.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, | ||||
| 				cw: this.useCw ? this.cw || '' : undefined, | ||||
| 				geo: this.geo ? { | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ | |||
| 			<slot name="func"></slot> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<div class="indicator" v-show="$store.state.indicate"></div> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -156,6 +157,10 @@ root(isDark) | |||
| 	&, * | ||||
| 		user-select none | ||||
| 
 | ||||
| 	> .indicator | ||||
| 		height 3px | ||||
| 		background $theme-color | ||||
| 
 | ||||
| 	> .main | ||||
| 		color rgba(#fff, 0.9) | ||||
| 
 | ||||
|  |  | |||
|  | @ -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> | ||||
| 				</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> | ||||
| 				</ul> | ||||
| 				<ul> | ||||
|  |  | |||
|  | @ -25,27 +25,27 @@ export default Vue.extend({ | |||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mk-widget-container | ||||
| 	background #eee | ||||
| root(isDark) | ||||
| 	background isDark ? #21242f : #eee | ||||
| 	border-radius 8px | ||||
| 	box-shadow 0 0 0 1px rgba(#000, 0.2) | ||||
| 	box-shadow 0 4px 16px rgba(#000, 0.1) | ||||
| 	overflow hidden | ||||
| 
 | ||||
| 	&.hideHeader | ||||
| 		background #fff | ||||
| 
 | ||||
| 	&.naked | ||||
| 		background transparent !important | ||||
| 		box-shadow none !important | ||||
| 
 | ||||
| 	&.hideHeader | ||||
| 		background isDark ? #21242f : #fff | ||||
| 
 | ||||
| 	> header | ||||
| 		> .title | ||||
| 			margin 0 | ||||
| 			padding 8px 10px | ||||
| 			font-size 15px | ||||
| 			font-weight normal | ||||
| 			color #465258 | ||||
| 			background #fff | ||||
| 			color isDark ? #b8c5cc : #465258 | ||||
| 			background isDark ? #282c37 : #fff | ||||
| 			border-radius 8px 8px 0 0 | ||||
| 
 | ||||
| 			> [data-fa] | ||||
|  | @ -65,4 +65,10 @@ export default Vue.extend({ | |||
| 			font-size 15px | ||||
| 			color #465258 | ||||
| 
 | ||||
| .mk-widget-container[data-darkmode] | ||||
| 	root(true) | ||||
| 
 | ||||
| .mk-widget-container:not([data-darkmode]) | ||||
| 	root(false) | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -84,7 +84,7 @@ export default Vue.extend({ | |||
| 		style(): any { | ||||
| 			if (this.user.bannerUrl == null) 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 })` | ||||
| 			}; | ||||
| 		} | ||||
|  |  | |||
|  | @ -40,7 +40,7 @@ | |||
| 			</x-draggable> | ||||
| 		</template> | ||||
| 		<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> | ||||
| 	</main> | ||||
| </mk-ui> | ||||
|  | @ -55,17 +55,24 @@ export default Vue.extend({ | |||
| 	components: { | ||||
| 		XDraggable | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			showNav: false, | ||||
| 			widgets: [], | ||||
| 			customizing: false, | ||||
| 			widgetAdderSelected: null | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		widgets(): any[] { | ||||
| 			return this.$store.state.settings.data.mobileHome; | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	created() { | ||||
| 		if ((this as any).clientSettings.mobileHome == null) { | ||||
| 			Vue.set((this as any).clientSettings, 'mobileHome', [{ | ||||
| 		if (this.widgets.length == 0) { | ||||
| 			this.widgets = [{ | ||||
| 				name: 'calendar', | ||||
| 				id: 'a', data: {} | ||||
| 			}, { | ||||
|  | @ -86,18 +93,9 @@ export default Vue.extend({ | |||
| 			}, { | ||||
| 				name: 'version', | ||||
| 				id: 'g', data: {} | ||||
| 			}]); | ||||
| 			this.widgets = (this as any).clientSettings.mobileHome; | ||||
| 			}]; | ||||
| 			this.saveHome(); | ||||
| 		} else { | ||||
| 			this.widgets = (this as any).clientSettings.mobileHome; | ||||
| 		} | ||||
| 
 | ||||
| 		this.$watch('clientSettings', i => { | ||||
| 			this.widgets = (this as any).clientSettings.mobileHome; | ||||
| 		}, { | ||||
| 			deep: true | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
|  | @ -105,46 +103,33 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 
 | ||||
| 	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() { | ||||
| 			alert('ウィジェットを追加/削除したり並べ替えたりできます。ウィジェットを移動するには「三」をドラッグします。ウィジェットを削除するには「x」をタップします。いくつかのウィジェットはタップすることで表示を変更できます。'); | ||||
| 		}, | ||||
| 
 | ||||
| 		widgetFunc(id) { | ||||
| 			const w = this.$refs[id][0]; | ||||
| 			if (w.func) w.func(); | ||||
| 		}, | ||||
| 
 | ||||
| 		onWidgetSort() { | ||||
| 			this.saveHome(); | ||||
| 		}, | ||||
| 
 | ||||
| 		addWidget() { | ||||
| 			const widget = { | ||||
| 			this.$store.dispatch('settings/addMobileHomeWidget', { | ||||
| 				name: this.widgetAdderSelected, | ||||
| 				id: uuid(), | ||||
| 				data: {} | ||||
| 			}; | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 			this.widgets.unshift(widget); | ||||
| 			this.saveHome(); | ||||
| 		}, | ||||
| 		removeWidget(widget) { | ||||
| 			this.widgets = this.widgets.filter(w => w.id != widget.id); | ||||
| 			this.saveHome(); | ||||
| 			this.$store.dispatch('settings/removeMobileHomeWidget', widget); | ||||
| 		}, | ||||
| 
 | ||||
| 		saveHome() { | ||||
| 			(this as any).clientSettings.mobileHome = this.widgets; | ||||
| 			this.$store.commit('settings/setMobileHome', this.widgets); | ||||
| 			(this as any).api('i/update_mobile_home', { | ||||
| 				home: this.widgets | ||||
| 			}); | ||||
|  | @ -156,17 +141,25 @@ export default Vue.extend({ | |||
| <style lang="stylus" scoped> | ||||
| main | ||||
| 	margin 0 auto | ||||
| 	padding 8px | ||||
| 	max-width 500px | ||||
| 	width 100% | ||||
| 
 | ||||
| 	@media (min-width 500px) | ||||
| 		padding 8px | ||||
| 		padding 16px 8px | ||||
| 
 | ||||
| 	@media (min-width 600px) | ||||
| 		padding 32px 8px | ||||
| 
 | ||||
| 	> header | ||||
| 		padding 8px | ||||
| 		background #fff | ||||
| 
 | ||||
| 	.widget | ||||
| 		margin 8px | ||||
| 		margin-bottom 8px | ||||
| 
 | ||||
| 		@media (min-width 600px) | ||||
| 			margin-bottom 16px | ||||
| 
 | ||||
| 	.customize-container | ||||
| 		margin 8px | ||||
|  | @ -3,6 +3,7 @@ import MiOS from './mios'; | |||
| 
 | ||||
| const defaultSettings = { | ||||
| 	home: [], | ||||
| 	mobileHome: [], | ||||
| 	fetchOnScroll: true, | ||||
| 	showMaps: true, | ||||
| 	showPostFormOnTopOfTl: false, | ||||
|  | @ -23,10 +24,15 @@ export default (os: MiOS) => new Vuex.Store({ | |||
| 	}], | ||||
| 
 | ||||
| 	state: { | ||||
| 		indicate: false, | ||||
| 		uiHeaderHeight: 0 | ||||
| 	}, | ||||
| 
 | ||||
| 	mutations: { | ||||
| 		indicate(state, x) { | ||||
| 			state.indicate = x; | ||||
| 		}, | ||||
| 
 | ||||
| 		setUiHeaderHeight(state, height) { | ||||
| 			state.uiHeaderHeight = height; | ||||
| 		} | ||||
|  | @ -58,6 +64,25 @@ export default (os: MiOS) => new Vuex.Store({ | |||
| 
 | ||||
| 				addHomeWidget(state, 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', { | ||||
| 						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) | ||||
| 					}); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  |  | |||
|  | @ -127,7 +127,7 @@ gulp.task('doc:api:endpoints', async () => { | |||
| 						return; | ||||
| 					} | ||||
| 					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); | ||||
| 					const htmlPath = `./built/client/docs/${lang}/api/endpoints/${ep.endpoint}.html`; | ||||
| 					mkdirp(path.dirname(htmlPath), (mkdirErr) => { | ||||
|  | @ -171,7 +171,7 @@ gulp.task('doc:api:entities', async () => { | |||
| 						return; | ||||
| 					} | ||||
| 					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); | ||||
| 					const htmlPath = `./built/client/docs/${lang}/api/entities/${kebab(entity.name)}.html`; | ||||
| 					mkdirp(path.dirname(htmlPath), (mkdirErr) => { | ||||
|  |  | |||
|  | @ -53,7 +53,7 @@ gulp.task('doc:docs', async () => { | |||
| 						return; | ||||
| 					} | ||||
| 					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); | ||||
| 					const htmlPath = `./built/client/docs/${lang}/${name}.html`; | ||||
| 					mkdirp(path.dirname(htmlPath), (mkdirErr) => { | ||||
|  |  | |||
|  | @ -154,6 +154,7 @@ export const pack = ( | |||
| 
 | ||||
| 	_target = Object.assign(_target, _file.metadata); | ||||
| 
 | ||||
| 	_target.src = _file.metadata.url; | ||||
| 	_target.url = `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`; | ||||
| 
 | ||||
| 	if (_target.properties == null) _target.properties = {}; | ||||
|  |  | |||
|  | @ -2,14 +2,20 @@ import * as Koa from 'koa'; | |||
| import summaly from 'summaly'; | ||||
| 
 | ||||
| module.exports = async (ctx: Koa.Context) => { | ||||
| 	const summary = await summaly(ctx.query.url); | ||||
| 	summary.icon = wrap(summary.icon); | ||||
| 	summary.thumbnail = wrap(summary.thumbnail); | ||||
| 	try { | ||||
| 		const summary = await summaly(ctx.query.url, { | ||||
| 			followRedirects: false | ||||
| 		}); | ||||
| 		summary.icon = wrap(summary.icon); | ||||
| 		summary.thumbnail = wrap(summary.thumbnail); | ||||
| 
 | ||||
| 	// Cache 7days
 | ||||
| 	ctx.set('Cache-Control', 'max-age=604800, immutable'); | ||||
| 		// Cache 7days
 | ||||
| 		ctx.set('Cache-Control', 'max-age=604800, immutable'); | ||||
| 
 | ||||
| 	ctx.body = summary; | ||||
| 		ctx.body = summary; | ||||
| 	} catch (e) { | ||||
| 		ctx.status = 500; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| function wrap(url: string): string { | ||||
|  |  | |||
|  | @ -171,6 +171,9 @@ const addFile = async ( | |||
| 
 | ||||
| 			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) | ||||
| 				.setFormat('ppm') | ||||
| 				.resize(1, 1)) // 1pxのサイズに縮小して平均色を取得するというハック
 | ||||
|  | @ -182,7 +185,7 @@ const addFile = async ( | |||
| 
 | ||||
| 			log(`average color is calculated: ${r}, ${g}, ${b}`); | ||||
| 
 | ||||
| 			return [r, g, b]; | ||||
| 			return isTransparent ? [r, g, b, 255] : [r, g, b]; | ||||
| 		})(), | ||||
| 		// folder
 | ||||
| 		(async () => { | ||||
|  |  | |||
							
								
								
									
										1152
									
								
								test/api.ts
									
										
									
									
									
								
							
							
						
						
									
										1152
									
								
								test/api.ts
									
										
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										10
									
								
								test/text.ts
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								test/text.ts
									
										
									
									
									
								
							|  | @ -1,11 +1,7 @@ | |||
| /** | ||||
|  * Text Tests! | ||||
|  */ | ||||
| import * as assert from 'assert'; | ||||
| 
 | ||||
| const assert = require('assert'); | ||||
| 
 | ||||
| const analyze = require('../built/text/parse').default; | ||||
| const syntaxhighlighter = require('../built/text/parse/core/syntax-highlighter').default; | ||||
| import analyze from '../src/text/parse'; | ||||
| import syntaxhighlighter from '../src/text/parse/core/syntax-highlighter'; | ||||
| 
 | ||||
| describe('Text', () => { | ||||
| 	it('can be analyzed', () => { | ||||
|  |  | |||
|  | @ -110,14 +110,14 @@ const plugins = [ | |||
| 		//#region i18n
 | ||||
| 		langs.forEach(lang => { | ||||
| 			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); | ||||
| 
 | ||||
| 				src = src.replace(i18nReplacer.pattern, i18nReplacer.replacement); | ||||
| 				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
 | ||||
|  | @ -146,27 +146,20 @@ module.exports = { | |||
| 			}, { | ||||
| 				loader: 'replace', | ||||
| 				query: { | ||||
| 					search: /%base64:(.+?)%/g.toString(), | ||||
| 					replace: 'base64replacement' | ||||
| 				} | ||||
| 			}, { | ||||
| 				loader: 'replace', | ||||
| 				query: { | ||||
| 					search: i18nPattern.toString(), | ||||
| 					replace: 'i18nReplacement', | ||||
| 					i18n: true | ||||
| 				} | ||||
| 			}, { | ||||
| 				loader: 'replace', | ||||
| 				query: { | ||||
| 					search: faPattern.toString(), | ||||
| 					replace: 'faReplacement' | ||||
| 				} | ||||
| 			}, { | ||||
| 				loader: 'replace', | ||||
| 				query: { | ||||
| 					search: /^<template>([\s\S]+?)\r?\n<\/template>/.toString(), | ||||
| 					replace: 'collapseSpacesReplacement' | ||||
| 					qs: [{ | ||||
| 						search: /%base64:(.+?)%/g.toString(), | ||||
| 						replace: 'base64replacement' | ||||
| 					}, { | ||||
| 						search: i18nPattern.toString(), | ||||
| 						replace: 'i18nReplacement', | ||||
| 						i18n: true | ||||
| 					}, { | ||||
| 						search: faPattern.toString(), | ||||
| 						replace: 'faReplacement' | ||||
| 					}, { | ||||
| 						search: /^<template>([\s\S]+?)\r?\n<\/template>/.toString(), | ||||
| 						replace: 'collapseSpacesReplacement' | ||||
| 					}] | ||||
| 				} | ||||
| 			}] | ||||
| 		}, { | ||||
|  |  | |||
|  | @ -1,22 +1,30 @@ | |||
| const loaderUtils = require('loader-utils'); | ||||
| import { getOptions } from 'loader-utils'; | ||||
| 
 | ||||
| function trim(text, g) { | ||||
| 	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(); | ||||
| 	const options = loaderUtils.getOptions(this); | ||||
| 	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); | ||||
| 	const options = getOptions(this); | ||||
| 	if (options.qs) { | ||||
| 		options.qs.forEach(q => fn(q)); | ||||
| 	} else { | ||||
| 		fn(options); | ||||
| 	} | ||||
| 	this.callback(null, src); | ||||
| 	return src; | ||||
| }; | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue