Merge branch 'master' into l10n_master
|
@ -1,3 +1,9 @@
|
||||||
|
# インスタンス名
|
||||||
|
name:
|
||||||
|
|
||||||
|
# インスタンスの紹介
|
||||||
|
description:
|
||||||
|
|
||||||
# サーバーのメンテナ情報
|
# サーバーのメンテナ情報
|
||||||
maintainer:
|
maintainer:
|
||||||
# メンテナの名前
|
# メンテナの名前
|
||||||
|
@ -55,3 +61,7 @@ twitter:
|
||||||
|
|
||||||
# インテグレーション用アプリのコンシューマーシークレット
|
# インテグレーション用アプリのコンシューマーシークレット
|
||||||
consumer_secret:
|
consumer_secret:
|
||||||
|
|
||||||
|
# true にすると、リモートのファイルをキャッシュしなくなります(直リンクします)。
|
||||||
|
# ストレージ容量を節約することができますが、「リモートメディアを表示しない」設定をオンにしているユーザーは、リモートの画像などは見えなくなります。
|
||||||
|
preventCache: false
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
*.svg -diff -text
|
*.svg -diff -text
|
||||||
*.psd -diff -text
|
*.psd -diff -text
|
||||||
*.ai -diff -text
|
*.ai -diff -text
|
||||||
|
yarn.lock -diff -text
|
||||||
|
|
|
@ -11,4 +11,4 @@ npm-debug.log
|
||||||
run.bat
|
run.bat
|
||||||
api-docs.json
|
api-docs.json
|
||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
*.log
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
ChangeLog
|
||||||
|
=========
|
||||||
|
|
||||||
|
破壊的変更のみ記載。
|
||||||
|
|
||||||
|
This document describes breaking changes only.
|
||||||
|
|
||||||
|
4.0.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
オセロがリバーシに変更されました。
|
||||||
|
|
||||||
|
Othello is now Reversi.
|
||||||
|
|
||||||
|
### Migration
|
||||||
|
|
||||||
|
MongoDBの、`othelloGames`と`othelloMatchings`コレクションをそれぞれ`reversiGames`と`reversiMatchings`にリネームしてください。
|
||||||
|
|
||||||
|
You need to rename `othelloGames` and `othelloMatchings` MongoDB collections to `reversiGames` and `reversiMatchings`.
|
||||||
|
|
||||||
|
3.0.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
### Migration
|
||||||
|
|
||||||
|
起動する前に、`node cli/recount-stats`してください。
|
||||||
|
|
||||||
|
Please run `node cli/recount-stats` before launch.
|
44
README.md
|
@ -12,20 +12,24 @@
|
||||||
> Lead Maintainer: [syuilo][syuilo-link]
|
> Lead Maintainer: [syuilo][syuilo-link]
|
||||||
|
|
||||||
**[Misskey](https://misskey.xyz)** is a completely open source,
|
**[Misskey](https://misskey.xyz)** is a completely open source,
|
||||||
ultimately sophisticated new type of mini-blog based SNS.
|
ultimately sophisticated professional microblogging software.
|
||||||
|
|
||||||
<a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a>
|
<a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a>
|
||||||
|
|
||||||
|
![](https://c10.patreonusercontent.com/3/e30%3D/patreon-posts/RsKWEDEKf8D_wYDQWAbex9CSb-1DnXW1nfqfLvuys5ROj2k0VF6_luuzHMTyf95n.png?token-time=1529539200&token-hash=RmcSP0947mw5o2-B6g1L6aU_OoDXANe198kLU6HMO30%3D)
|
||||||
|
|
||||||
:sparkles: Features
|
:sparkles: Features
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
* Reactions
|
* Reactions
|
||||||
* User lists
|
* User lists
|
||||||
|
* Customizable column view (known as MisskeyDeck)
|
||||||
|
* and widgets!
|
||||||
* Private messages
|
* Private messages
|
||||||
* Mute
|
* Mute
|
||||||
* Real time contents
|
* Streaming
|
||||||
* ActivityPub compatible
|
* ActivityPub compatible
|
||||||
|
|
||||||
and more! You can touch with your own eyes at [misskey.xyz](https://misskey.xyz).
|
and more! You can see it with your own eyes at [misskey.xyz](https://misskey.xyz).
|
||||||
|
|
||||||
:package: Create your instance
|
:package: Create your instance
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
@ -45,18 +49,9 @@ If you want to...
|
||||||
[![Backers][backers-image]][support-url]
|
[![Backers][backers-image]][support-url]
|
||||||
[![Sponsors][sponsors-image]][support-url]
|
[![Sponsors][sponsors-image]][support-url]
|
||||||
|
|
||||||
:mortar_board: Notable contributors
|
| ![][ooo-icon] |
|
||||||
----------------------------------------------------------------
|
|:-:|
|
||||||
| ![syuilo][syuilo-icon] | ![Morisawa Aya][ayamorisawa-icon] | ![otofune][otofune-icon] | ![akihikodaki][akihikodaki-icon] | ![tamaina][tamaina-icon] | ![rinsuki][rinsuki-icon] |
|
| [ooo][ooo-link] |
|
||||||
|:-:|:-:|:-:|:-:|:-:|:-:|
|
|
||||||
| [syuilo][syuilo-link]<br>Owner | [Aya Morisawa][ayamorisawa-link]<br>Collaborator | [otofune][otofune-link]<br>Collaborator | [akihikodaki][akihikodaki-link] | [tamaina][tamaina-link] | [rinsuki][rinsuki-link] |
|
|
||||||
|
|
||||||
[List of all contributors](https://github.com/syuilo/misskey/graphs/contributors)
|
|
||||||
|
|
||||||
### :earth_americas: Translators
|
|
||||||
| ![][mirro-san-icon] | ![][Conan-kun-icon] | ![][m4sk1n-icon] |
|
|
||||||
|:-:|:-:|:-:|
|
|
||||||
| [Mirro][mirro-san-link]<br>English, French | [Asriel][Conan-kun-link]<br>English, French | [Marcin Mikołajczak][m4sk1n-link]<br>Polish |
|
|
||||||
|
|
||||||
:four_leaf_clover: Copyright
|
:four_leaf_clover: Copyright
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
@ -84,23 +79,8 @@ Misskey is an open-source software licensed under [GNU AGPLv3](LICENSE).
|
||||||
[sponsors-image]: https://opencollective.com/misskey/sponsors.svg
|
[sponsors-image]: https://opencollective.com/misskey/sponsors.svg
|
||||||
[support-url]: https://opencollective.com/misskey#support
|
[support-url]: https://opencollective.com/misskey#support
|
||||||
|
|
||||||
<!-- Contributors Info -->
|
|
||||||
[syuilo-link]: https://syuilo.com
|
[syuilo-link]: https://syuilo.com
|
||||||
[syuilo-icon]: https://avatars2.githubusercontent.com/u/4439005?v=3&s=70
|
[syuilo-icon]: https://avatars2.githubusercontent.com/u/4439005?v=3&s=70
|
||||||
[ayamorisawa-link]: https://github.com/ayamorisawa
|
|
||||||
[ayamorisawa-icon]: https://avatars0.githubusercontent.com/u/10798641?v=3&s=70
|
|
||||||
[otofune-link]: https://github.com/otofune
|
|
||||||
[otofune-icon]: https://avatars0.githubusercontent.com/u/15062473?v=3&s=70
|
|
||||||
[akihikodaki-link]: https://github.com/akihikodaki
|
|
||||||
[akihikodaki-icon]: https://avatars2.githubusercontent.com/u/17036990?s=70&v=4
|
|
||||||
[rinsuki-link]: https://github.com/rinsuki
|
|
||||||
[rinsuki-icon]: https://avatars0.githubusercontent.com/u/6533808?s=70&v=4
|
|
||||||
[tamaina-link]: https://github.com/tamaina
|
|
||||||
[tamaina-icon]: https://avatars1.githubusercontent.com/u/7973572?s=70&v=4
|
|
||||||
|
|
||||||
[mirro-san-link]: https://github.com/mirro-san
|
[ooo-link]: https://www.patreon.com/user/creators?u=11601413
|
||||||
[mirro-san-icon]: https://avatars1.githubusercontent.com/u/17948612?s=70&v=4
|
[ooo-icon]: https://c10.patreonusercontent.com/3/eyJ2IjoiMSIsInciOjIwMH0%3D/patreon-media/user/11601413/20cb15f209924302b399b99d3c98b850?token-time=2145916800&token-hash=IO31nK6VZCMWBWU2VAk2c824BX2QZ4DNPKyHHZXS0iw%3D
|
||||||
[Conan-kun-link]: https://github.com/Conan-kun
|
|
||||||
[Conan-kun-icon]: https://avatars3.githubusercontent.com/u/30003708?s=70&v=4
|
|
||||||
[m4sk1n-link]: https://github.com/m4sk1n
|
|
||||||
[m4sk1n-icon]: https://avatars3.githubusercontent.com/u/21127288?s=70&v=4
|
|
||||||
|
|
Before Width: | Height: | Size: 352 KiB After Width: | Height: | Size: 264 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 518 B |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 761 B |
Before Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
BIN
assets/mi.svg
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.3 KiB |
|
@ -0,0 +1,42 @@
|
||||||
|
const { default: Note } = require('../built/models/note');
|
||||||
|
const { default: Meta } = require('../built/models/meta');
|
||||||
|
const { default: User } = require('../built/models/user');
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const meta = await Meta.findOne({});
|
||||||
|
|
||||||
|
const notesCount = await Note.count();
|
||||||
|
|
||||||
|
const usersCount = await User.count();
|
||||||
|
|
||||||
|
const originalNotesCount = await Note.count({
|
||||||
|
'_user.host': null
|
||||||
|
});
|
||||||
|
|
||||||
|
const originalUsersCount = await User.count({
|
||||||
|
host: null
|
||||||
|
});
|
||||||
|
|
||||||
|
const stats = {
|
||||||
|
notesCount,
|
||||||
|
usersCount,
|
||||||
|
originalNotesCount,
|
||||||
|
originalUsersCount
|
||||||
|
};
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
await Meta.update({}, {
|
||||||
|
$set: {
|
||||||
|
stats
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await Meta.insert({
|
||||||
|
stats
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().then(() => {
|
||||||
|
console.log('done');
|
||||||
|
}).catch(console.error);
|
|
@ -3,16 +3,21 @@ const User = require('../built/models/user').default;
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
const userId = new mongo.ObjectID(args[0]);
|
const user = args[0];
|
||||||
|
|
||||||
console.log(`Suspending ${userId}...`);
|
const q = user.startsWith('@') ? {
|
||||||
|
username: user.split('@')[1],
|
||||||
|
host: user.split('@')[2] || null
|
||||||
|
} : { _id: new mongo.ObjectID(user) };
|
||||||
|
|
||||||
User.update({ _id: userId }, {
|
console.log(`Suspending ${user}...`);
|
||||||
|
|
||||||
|
User.update(q, {
|
||||||
$set: {
|
$set: {
|
||||||
isSuspended: true
|
isSuspended: true
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
console.log(`Suspended ${userId}`);
|
console.log(`Suspended ${user}`);
|
||||||
}, e => {
|
}, e => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
const updatePerson = require('../built/remote/activitypub/models/person').updatePerson;
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const user = args[0];
|
||||||
|
|
||||||
|
console.log(`Updating ${user}...`);
|
||||||
|
|
||||||
|
updatePerson(user).then(() => {
|
||||||
|
console.log(`Updated ${user}`);
|
||||||
|
}, e => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
|
@ -47,7 +47,14 @@ You need to generate config file via `npm run config` command.
|
||||||
|
|
||||||
*5.* Build Misskey
|
*5.* Build Misskey
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
We need to use `node-gyp` to build the `crypto` module.
|
|
||||||
|
Build misskey with the following:
|
||||||
|
|
||||||
|
`npm run build`
|
||||||
|
|
||||||
|
If you're on Debian, you will need to install the `build-essential` package.
|
||||||
|
|
||||||
|
If you're still encountering errors about some modules, use node-gyp:
|
||||||
|
|
||||||
1. `npm install -g node-gyp`
|
1. `npm install -g node-gyp`
|
||||||
2. `node-gyp configure`
|
2. `node-gyp configure`
|
||||||
|
|
|
@ -8,12 +8,12 @@ import * as gutil from 'gulp-util';
|
||||||
import * as ts from 'gulp-typescript';
|
import * as ts from 'gulp-typescript';
|
||||||
const sourcemaps = require('gulp-sourcemaps');
|
const sourcemaps = require('gulp-sourcemaps');
|
||||||
import tslint from 'gulp-tslint';
|
import tslint from 'gulp-tslint';
|
||||||
import cssnano = require('gulp-cssnano');
|
const cssnano = require('gulp-cssnano');
|
||||||
import * as uglifyComposer from 'gulp-uglify/composer';
|
import * as uglifyComposer from 'gulp-uglify/composer';
|
||||||
import pug = require('gulp-pug');
|
import pug = require('gulp-pug');
|
||||||
import * as rimraf from 'rimraf';
|
import * as rimraf from 'rimraf';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import imagemin = require('gulp-imagemin');
|
const imagemin = require('gulp-imagemin');
|
||||||
import * as rename from 'gulp-rename';
|
import * as rename from 'gulp-rename';
|
||||||
import * as mocha from 'gulp-mocha';
|
import * as mocha from 'gulp-mocha';
|
||||||
import * as replace from 'gulp-replace';
|
import * as replace from 'gulp-replace';
|
||||||
|
|
|
@ -334,7 +334,7 @@ desktop/views/components/friends-maker.vue:
|
||||||
refresh: "Mehr"
|
refresh: "Mehr"
|
||||||
close: "Schließen"
|
close: "Schließen"
|
||||||
desktop/views/components/game-window.vue:
|
desktop/views/components/game-window.vue:
|
||||||
game: "リバーシ"
|
game: "Reversi"
|
||||||
desktop/views/components/home.vue:
|
desktop/views/components/home.vue:
|
||||||
done: "Verbunden"
|
done: "Verbunden"
|
||||||
add-widget: "Widget hinzufügen:"
|
add-widget: "Widget hinzufügen:"
|
||||||
|
|
|
@ -334,7 +334,7 @@ desktop/views/components/friends-maker.vue:
|
||||||
refresh: "More"
|
refresh: "More"
|
||||||
close: "Close"
|
close: "Close"
|
||||||
desktop/views/components/game-window.vue:
|
desktop/views/components/game-window.vue:
|
||||||
game: "リバーシ"
|
game: "Reversi"
|
||||||
desktop/views/components/home.vue:
|
desktop/views/components/home.vue:
|
||||||
done: "Submit"
|
done: "Submit"
|
||||||
add-widget: "Add widget:"
|
add-widget: "Add widget:"
|
||||||
|
@ -550,7 +550,7 @@ desktop/views/components/ui.header.nav.vue:
|
||||||
home: "Home"
|
home: "Home"
|
||||||
deck: "Deck"
|
deck: "Deck"
|
||||||
messaging: "Messages"
|
messaging: "Messages"
|
||||||
game: "Play Othello"
|
game: "Play Reversi"
|
||||||
desktop/views/components/ui.header.notifications.vue:
|
desktop/views/components/ui.header.notifications.vue:
|
||||||
title: "Notifications"
|
title: "Notifications"
|
||||||
desktop/views/components/ui.header.post.vue:
|
desktop/views/components/ui.header.post.vue:
|
||||||
|
|
|
@ -334,7 +334,7 @@ desktop/views/components/friends-maker.vue:
|
||||||
refresh: "Plus"
|
refresh: "Plus"
|
||||||
close: "Fermer"
|
close: "Fermer"
|
||||||
desktop/views/components/game-window.vue:
|
desktop/views/components/game-window.vue:
|
||||||
game: "リバーシ"
|
game: "Reversi"
|
||||||
desktop/views/components/home.vue:
|
desktop/views/components/home.vue:
|
||||||
done: "Envoyer"
|
done: "Envoyer"
|
||||||
add-widget: "Ajouter un widget"
|
add-widget: "Ajouter un widget"
|
||||||
|
|
|
@ -5,12 +5,15 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as yaml from 'js-yaml';
|
import * as yaml from 'js-yaml';
|
||||||
|
|
||||||
const loadLang = lang => yaml.safeLoad(
|
export type LangKey = 'de' | 'en' | 'fr' | 'ja' | 'pl';
|
||||||
fs.readFileSync(`./locales/${lang}.yml`, 'utf-8'));
|
export type LocaleObject = { [key: string]: any };
|
||||||
|
|
||||||
|
const loadLang = (lang: LangKey) => yaml.safeLoad(
|
||||||
|
fs.readFileSync(`./locales/${lang}.yml`, 'utf-8')) as LocaleObject;
|
||||||
|
|
||||||
const native = loadLang('ja');
|
const native = loadLang('ja');
|
||||||
|
|
||||||
const langs = {
|
const langs: { [key: string]: LocaleObject } = {
|
||||||
'de': loadLang('de'),
|
'de': loadLang('de'),
|
||||||
'en': loadLang('en'),
|
'en': loadLang('en'),
|
||||||
'fr': loadLang('fr'),
|
'fr': loadLang('fr'),
|
||||||
|
@ -23,4 +26,8 @@ Object.entries(langs).map(([, locale]) => {
|
||||||
locale = Object.assign({}, native, locale);
|
locale = Object.assign({}, native, locale);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function isAvailableLanguage(lang: string): lang is LangKey {
|
||||||
|
return lang in langs;
|
||||||
|
}
|
||||||
|
|
||||||
export default langs;
|
export default langs;
|
||||||
|
|
224
locales/ja.yml
|
@ -3,7 +3,9 @@ meta:
|
||||||
divider: ""
|
divider: ""
|
||||||
|
|
||||||
common:
|
common:
|
||||||
misskey: "Misskeyで皆と共有しよう。"
|
misskey: "A ⭐ of fediverse"
|
||||||
|
about-title: "A ⭐ of fediverse."
|
||||||
|
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
|
||||||
|
|
||||||
time:
|
time:
|
||||||
unknown: "なぞのじかん"
|
unknown: "なぞのじかん"
|
||||||
|
@ -37,12 +39,62 @@ common:
|
||||||
confused: "こまこまのこまり"
|
confused: "こまこまのこまり"
|
||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
|
|
||||||
|
note-placeholders:
|
||||||
|
a: "今どうしてる?"
|
||||||
|
b: "何かありましたか?"
|
||||||
|
c: "何をお考えですか?"
|
||||||
|
d: "言いたいことは?"
|
||||||
|
e: "ここに書いてください"
|
||||||
|
f: "あなたが書くのを待っています..."
|
||||||
|
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
loading: "読み込み中"
|
loading: "読み込み中"
|
||||||
ok: "わかった"
|
ok: "わかった"
|
||||||
update-available: "Misskeyの新しいバージョンがあります({newer}。現在{current}を利用中)。ページを再度読み込みすると更新が適用されます。"
|
update-available: "Misskeyの新しいバージョンがあります({newer}。現在{current}を利用中)。ページを再度読み込みすると更新が適用されます。"
|
||||||
my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。"
|
my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。"
|
||||||
|
|
||||||
|
widgets:
|
||||||
|
analog-clock: "アナログ時計"
|
||||||
|
profile: "プロフィール"
|
||||||
|
calendar: "カレンダー"
|
||||||
|
timemachine: "カレンダー(タイムマシン)"
|
||||||
|
activity: "アクティビティ"
|
||||||
|
rss: "RSSリーダー"
|
||||||
|
memo: "付箋"
|
||||||
|
trends: "トレンド"
|
||||||
|
photo-stream: "フォトストリーム"
|
||||||
|
posts-monitor: "投稿チャート"
|
||||||
|
slideshow: "スライドショー"
|
||||||
|
version: "バージョン"
|
||||||
|
broadcast: "ブロードキャスト"
|
||||||
|
notifications: "通知"
|
||||||
|
users: "おすすめユーザー"
|
||||||
|
polls: "アンケート"
|
||||||
|
post-form: "投稿フォーム"
|
||||||
|
messaging: "メッセージ"
|
||||||
|
server: "サーバー情報"
|
||||||
|
donation: "寄付のお願い"
|
||||||
|
nav: "ナビゲーション"
|
||||||
|
tips: "ヒント"
|
||||||
|
hashtags: "ハッシュタグ"
|
||||||
|
|
||||||
|
deck:
|
||||||
|
widgets: "ウィジェット"
|
||||||
|
home: "ホーム"
|
||||||
|
local: "ローカル"
|
||||||
|
global: "グローバル"
|
||||||
|
notifications: "通知"
|
||||||
|
list: "リスト"
|
||||||
|
swap-left: "左に移動"
|
||||||
|
swap-right: "右に移動"
|
||||||
|
swap-up: "上に移動"
|
||||||
|
swap-down: "下に移動"
|
||||||
|
remove: "カラムを削除"
|
||||||
|
add-column: "カラムを追加"
|
||||||
|
rename: "名前を変更"
|
||||||
|
stack-left: "左に重ねる"
|
||||||
|
pop-right: "右に出す"
|
||||||
|
|
||||||
common/views/components/connect-failed.vue:
|
common/views/components/connect-failed.vue:
|
||||||
title: "サーバーに接続できません"
|
title: "サーバーに接続できません"
|
||||||
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
|
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
|
||||||
|
@ -104,6 +156,8 @@ common/views/components/nav.vue:
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
|
delete: "削除"
|
||||||
|
delete-confirm: "この投稿を削除しますか?"
|
||||||
remote: "投稿元で見る"
|
remote: "投稿元で見る"
|
||||||
|
|
||||||
common/views/components/poll.vue:
|
common/views/components/poll.vue:
|
||||||
|
@ -115,11 +169,11 @@ common/views/components/poll.vue:
|
||||||
voted: "投票済み"
|
voted: "投票済み"
|
||||||
|
|
||||||
common/views/components/poll-editor.vue:
|
common/views/components/poll-editor.vue:
|
||||||
no-only-one-choice: "投票には、選択肢が最低2つ必要です"
|
no-only-one-choice: "アンケートには、選択肢が最低2つ必要です"
|
||||||
choice-n: "選択肢{}"
|
choice-n: "選択肢{}"
|
||||||
remove: "この選択肢を削除"
|
remove: "この選択肢を削除"
|
||||||
add: "+選択肢を追加"
|
add: "+選択肢を追加"
|
||||||
destroy: "投票を破棄"
|
destroy: "アンケートを破棄"
|
||||||
|
|
||||||
common/views/components/reaction-picker.vue:
|
common/views/components/reaction-picker.vue:
|
||||||
choose-reaction: "リアクションを選択"
|
choose-reaction: "リアクションを選択"
|
||||||
|
@ -197,10 +251,24 @@ common/views/widgets/photo-stream.vue:
|
||||||
title: "フォトストリーム"
|
title: "フォトストリーム"
|
||||||
no-photos: "写真はありません"
|
no-photos: "写真はありません"
|
||||||
|
|
||||||
|
common/views/widgets/posts-monitor.vue:
|
||||||
|
title: "投稿チャート"
|
||||||
|
toggle: "表示を切り替え"
|
||||||
|
|
||||||
|
common/views/widgets/hashtags.vue:
|
||||||
|
title: "ハッシュタグ"
|
||||||
|
count: "{}人が投稿"
|
||||||
|
empty: "トレンドなし"
|
||||||
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
|
||||||
|
common/views/widgets/memo.vue:
|
||||||
|
title: "付箋"
|
||||||
|
memo: "ここに書いて!"
|
||||||
|
save: "保存"
|
||||||
|
|
||||||
desktop/views/components/activity.chart.vue:
|
desktop/views/components/activity.chart.vue:
|
||||||
total: "Black ... Total"
|
total: "Black ... Total"
|
||||||
notes: "Blue ... Notes"
|
notes: "Blue ... Notes"
|
||||||
|
@ -291,8 +359,10 @@ desktop/views/components/drive.vue:
|
||||||
url-upload: "URLからアップロード"
|
url-upload: "URLからアップロード"
|
||||||
|
|
||||||
desktop/views/components/follow-button.vue:
|
desktop/views/components/follow-button.vue:
|
||||||
unfollow: "フォロー解除"
|
following: "フォロー中"
|
||||||
follow: "フォローする"
|
follow: "フォロー"
|
||||||
|
request-pending: "フォロー許可待ち"
|
||||||
|
follow-request: "フォロー申請"
|
||||||
|
|
||||||
desktop/views/components/followers-window.vue:
|
desktop/views/components/followers-window.vue:
|
||||||
followers: "{} のフォロワー"
|
followers: "{} のフォロワー"
|
||||||
|
@ -314,30 +384,11 @@ desktop/views/components/friends-maker.vue:
|
||||||
close: "閉じる"
|
close: "閉じる"
|
||||||
|
|
||||||
desktop/views/components/game-window.vue:
|
desktop/views/components/game-window.vue:
|
||||||
game: "オセロ"
|
game: "リバーシ"
|
||||||
|
|
||||||
desktop/views/components/home.vue:
|
desktop/views/components/home.vue:
|
||||||
done: "完了"
|
done: "完了"
|
||||||
add-widget: "ウィジェットを追加:"
|
add-widget: "ウィジェットを追加:"
|
||||||
profile: "プロフィール"
|
|
||||||
calendar: "カレンダー"
|
|
||||||
timemachine: "カレンダー(タイムマシン)"
|
|
||||||
activity: "アクティビティ"
|
|
||||||
rss: "RSSリーダー"
|
|
||||||
trends: "トレンド"
|
|
||||||
photostream: "フォトストリーム"
|
|
||||||
slideshow: "スライドショー"
|
|
||||||
version: "バージョン"
|
|
||||||
broadcast: "ブロードキャスト"
|
|
||||||
notifications: "通知"
|
|
||||||
users: "おすすめユーザー"
|
|
||||||
polls: "投票"
|
|
||||||
post-form: "投稿フォーム"
|
|
||||||
messaging: "メッセージ"
|
|
||||||
server: "サーバー情報"
|
|
||||||
donation: "寄付のお願い"
|
|
||||||
nav: "ナビゲーション"
|
|
||||||
tips: "ヒント"
|
|
||||||
add: "追加"
|
add: "追加"
|
||||||
|
|
||||||
desktop/views/input-dialog.vue:
|
desktop/views/input-dialog.vue:
|
||||||
|
@ -352,21 +403,21 @@ desktop/views/components/messaging-window.vue:
|
||||||
|
|
||||||
desktop/views/components/note-detail.vue:
|
desktop/views/components/note-detail.vue:
|
||||||
more: "会話をもっと読み込む"
|
more: "会話をもっと読み込む"
|
||||||
private: "(この投稿は非公開です)"
|
private: "この投稿は非公開です"
|
||||||
|
deleted: "この投稿は削除されました"
|
||||||
reposted-by: "{}がRenote"
|
reposted-by: "{}がRenote"
|
||||||
location: "位置情報"
|
location: "位置情報"
|
||||||
renote: "Renote"
|
renote: "Renote"
|
||||||
add-reaction: "リアクション"
|
add-reaction: "リアクション"
|
||||||
|
|
||||||
desktop/views/components/note-detail.sub.vue:
|
|
||||||
private: "(この投稿は非公開です)"
|
|
||||||
|
|
||||||
desktop/views/components/notes.note.vue:
|
desktop/views/components/notes.note.vue:
|
||||||
reposted-by: "{}がRenote"
|
reposted-by: "{}がRenote"
|
||||||
reply: "返信"
|
reply: "返信"
|
||||||
renote: "Renote"
|
renote: "Renote"
|
||||||
add-reaction: "リアクション"
|
add-reaction: "リアクション"
|
||||||
detail: "詳細"
|
detail: "詳細"
|
||||||
|
private: "この投稿は非公開です"
|
||||||
|
deleted: "この投稿は削除されました"
|
||||||
|
|
||||||
desktop/views/components/notes.vue:
|
desktop/views/components/notes.vue:
|
||||||
error: "読み込みに失敗しました。"
|
error: "読み込みに失敗しました。"
|
||||||
|
@ -377,10 +428,9 @@ desktop/views/components/notifications.vue:
|
||||||
empty: "ありません!"
|
empty: "ありません!"
|
||||||
|
|
||||||
desktop/views/components/post-form.vue:
|
desktop/views/components/post-form.vue:
|
||||||
note-placeholder: "いまどうしてる?"
|
|
||||||
reply-placeholder: "この投稿への返信..."
|
reply-placeholder: "この投稿への返信..."
|
||||||
quote-placeholder: "この投稿を引用..."
|
quote-placeholder: "この投稿を引用..."
|
||||||
note: "投稿"
|
submit: "投稿"
|
||||||
reply: "返信"
|
reply: "返信"
|
||||||
renote: "Renote"
|
renote: "Renote"
|
||||||
posted: "投稿しました!"
|
posted: "投稿しました!"
|
||||||
|
@ -394,7 +444,7 @@ desktop/views/components/post-form.vue:
|
||||||
attach-media-from-drive: "ドライブからメディアを添付"
|
attach-media-from-drive: "ドライブからメディアを添付"
|
||||||
attach-cancel: "添付取り消し"
|
attach-cancel: "添付取り消し"
|
||||||
insert-a-kao: "v(‘ω’)v"
|
insert-a-kao: "v(‘ω’)v"
|
||||||
create-poll: "投票を作成"
|
create-poll: "アンケートを作成"
|
||||||
text-remain: "残り{}文字"
|
text-remain: "残り{}文字"
|
||||||
|
|
||||||
desktop/views/components/post-form-window.vue:
|
desktop/views/components/post-form-window.vue:
|
||||||
|
@ -531,7 +581,7 @@ desktop/views/components/settings.api.vue:
|
||||||
token: "Token:"
|
token: "Token:"
|
||||||
enter-password: "パスワードを入力してください"
|
enter-password: "パスワードを入力してください"
|
||||||
|
|
||||||
desktop/views/components/settings.app.vue:
|
desktop/views/components/settings.apps.vue:
|
||||||
no-apps: "連携しているアプリケーションはありません"
|
no-apps: "連携しているアプリケーションはありません"
|
||||||
|
|
||||||
desktop/views/components/settings.mute.vue:
|
desktop/views/components/settings.mute.vue:
|
||||||
|
@ -557,9 +607,10 @@ desktop/views/components/settings.profile.vue:
|
||||||
is-cat: "このアカウントはCatです"
|
is-cat: "このアカウントはCatです"
|
||||||
|
|
||||||
desktop/views/components/sub-note-content.vue:
|
desktop/views/components/sub-note-content.vue:
|
||||||
hidden: "(この投稿は非公開です)"
|
private: "この投稿は非公開です"
|
||||||
media: "つのメディア"
|
deleted: "この投稿は削除されました"
|
||||||
poll: "投票"
|
media-count: "{}つのメディア"
|
||||||
|
poll: "アンケート"
|
||||||
|
|
||||||
desktop/views/components/taskmanager.vue:
|
desktop/views/components/taskmanager.vue:
|
||||||
title: "タスクマネージャ"
|
title: "タスクマネージャ"
|
||||||
|
@ -575,6 +626,7 @@ desktop/views/components/ui.header.account.vue:
|
||||||
drive: "ドライブ"
|
drive: "ドライブ"
|
||||||
favorites: "お気に入り"
|
favorites: "お気に入り"
|
||||||
lists: "リスト"
|
lists: "リスト"
|
||||||
|
follow-requests: "フォロー申請"
|
||||||
customize: "カスタマイズ"
|
customize: "カスタマイズ"
|
||||||
settings: "設定"
|
settings: "設定"
|
||||||
signout: "サインアウト"
|
signout: "サインアウト"
|
||||||
|
@ -582,6 +634,7 @@ desktop/views/components/ui.header.account.vue:
|
||||||
|
|
||||||
desktop/views/components/ui.header.nav.vue:
|
desktop/views/components/ui.header.nav.vue:
|
||||||
home: "ホーム"
|
home: "ホーム"
|
||||||
|
deck: "デッキ"
|
||||||
messaging: "メッセージ"
|
messaging: "メッセージ"
|
||||||
game: "ゲーム"
|
game: "ゲーム"
|
||||||
|
|
||||||
|
@ -594,7 +647,13 @@ 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/received-follow-requests-window.vue:
|
||||||
|
title: "フォロー申請"
|
||||||
|
accept: "承認"
|
||||||
|
reject: "拒否"
|
||||||
|
|
||||||
desktop/views/components/user-lists-window.vue:
|
desktop/views/components/user-lists-window.vue:
|
||||||
|
title: "リスト"
|
||||||
create-list: "リストを作成"
|
create-list: "リストを作成"
|
||||||
|
|
||||||
desktop/views/components/user-preview.vue:
|
desktop/views/components/user-preview.vue:
|
||||||
|
@ -615,7 +674,18 @@ desktop/views/components/window.vue:
|
||||||
popout: "ポップアウト"
|
popout: "ポップアウト"
|
||||||
close: "閉じる"
|
close: "閉じる"
|
||||||
|
|
||||||
|
desktop/views/pages/deck/deck.tl-column.vue:
|
||||||
|
is-media-only: "メディア投稿のみ"
|
||||||
|
is-media-view: "メディアビュー"
|
||||||
|
|
||||||
|
desktop/views/pages/deck/deck.note.vue:
|
||||||
|
reposted-by: "{}がRenote"
|
||||||
|
private: "この投稿は非公開です"
|
||||||
|
deleted: "この投稿は削除されました"
|
||||||
|
|
||||||
desktop/views/pages/welcome.vue:
|
desktop/views/pages/welcome.vue:
|
||||||
|
about: "詳しく..."
|
||||||
|
gotit: "わかった"
|
||||||
signin: "ログイン"
|
signin: "ログイン"
|
||||||
signup: "新規登録"
|
signup: "新規登録"
|
||||||
signin-button: "やってる"
|
signin-button: "やってる"
|
||||||
|
@ -692,14 +762,13 @@ desktop/views/widgets/notifications.vue:
|
||||||
settings: "通知の設定"
|
settings: "通知の設定"
|
||||||
|
|
||||||
desktop/views/widgets/polls.vue:
|
desktop/views/widgets/polls.vue:
|
||||||
title: "投票"
|
title: "アンケート"
|
||||||
refresh: "他を見る"
|
refresh: "他を見る"
|
||||||
nothing: "ありません!"
|
nothing: "ありません!"
|
||||||
|
|
||||||
desktop/views/widgets/post-form.vue:
|
desktop/views/widgets/post-form.vue:
|
||||||
title: "投稿"
|
title: "投稿"
|
||||||
note: "投稿"
|
note: "投稿"
|
||||||
placeholder: "いまどうしてる?"
|
|
||||||
|
|
||||||
desktop/views/widgets/profile.vue:
|
desktop/views/widgets/profile.vue:
|
||||||
update-banner: "クリックでバナー編集"
|
update-banner: "クリックでバナー編集"
|
||||||
|
@ -724,6 +793,16 @@ mobile/views/components/drive.vue:
|
||||||
load-more: "もっと読み込む"
|
load-more: "もっと読み込む"
|
||||||
nothing-in-drive: "ドライブには何もありません"
|
nothing-in-drive: "ドライブには何もありません"
|
||||||
folder-is-empty: "このフォルダは空です"
|
folder-is-empty: "このフォルダは空です"
|
||||||
|
prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
|
||||||
|
deletion-alert: "ごめんなさい!フォルダの削除は未実装です...。"
|
||||||
|
folder-name: "フォルダー名"
|
||||||
|
root-rename-alert: "現在いる場所はルートで、フォルダではないため名前の変更はできません。名前を変更したいフォルダに移動してからやってください。"
|
||||||
|
root-move-alert: "現在いる場所はルートで、フォルダではないため移動はできません。移動したいフォルダに移動してからやってください。"
|
||||||
|
url-prompt: "アップロードしたいファイルのURL"
|
||||||
|
uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
|
||||||
|
|
||||||
|
mobile/views/components/drive-file-detail.vue:
|
||||||
|
rename: "名前を変更"
|
||||||
|
|
||||||
mobile/views/components/drive-file-chooser.vue:
|
mobile/views/components/drive-file-chooser.vue:
|
||||||
select-file: "ファイルを選択"
|
select-file: "ファイルを選択"
|
||||||
|
@ -739,42 +818,86 @@ mobile/views/components/drive.file-detail.vue:
|
||||||
exif: "EXIF"
|
exif: "EXIF"
|
||||||
|
|
||||||
mobile/views/components/follow-button.vue:
|
mobile/views/components/follow-button.vue:
|
||||||
|
following: "フォロー中"
|
||||||
follow: "フォロー"
|
follow: "フォロー"
|
||||||
unfollow: "フォロー解除"
|
request-pending: "フォロー許可待ち"
|
||||||
|
follow-request: "フォロー申請"
|
||||||
|
|
||||||
|
mobile/views/components/friends-maker.vue:
|
||||||
|
title: "気になるユーザーをフォロー"
|
||||||
|
empty: "おすすめのユーザーは見つかりませんでした。"
|
||||||
|
fetching: "読み込んでいます"
|
||||||
|
refresh: "もっと見る"
|
||||||
|
close: "閉じる"
|
||||||
|
|
||||||
mobile/views/components/note.vue:
|
mobile/views/components/note.vue:
|
||||||
reposted-by: "{}がRenote"
|
reposted-by: "{}がRenote"
|
||||||
|
more: "もっと見る"
|
||||||
|
less: "隠す"
|
||||||
|
private: "この投稿は非公開です"
|
||||||
|
deleted: "この投稿は削除されました"
|
||||||
|
location: "位置情報"
|
||||||
|
|
||||||
mobile/views/components/note-detail.vue:
|
mobile/views/components/note-detail.vue:
|
||||||
reply: "返信"
|
reply: "返信"
|
||||||
reaction: "リアクション"
|
reaction: "リアクション"
|
||||||
|
reposted-by: "{}がRenote"
|
||||||
|
private: "この投稿は非公開です"
|
||||||
|
deleted: "この投稿は削除されました"
|
||||||
|
location: "位置情報"
|
||||||
|
|
||||||
|
mobile/views/components/note-preview.vue:
|
||||||
|
admin: "admin"
|
||||||
|
bot: "bot"
|
||||||
|
cat: "cat"
|
||||||
|
|
||||||
|
mobile/views/components/note-sub.vue:
|
||||||
|
admin: "admin"
|
||||||
|
bot: "bot"
|
||||||
|
cat: "cat"
|
||||||
|
|
||||||
|
mobile/views/components/notes.vue:
|
||||||
|
failed: "読み込みに失敗しました。"
|
||||||
|
retry: "リトライ"
|
||||||
|
|
||||||
mobile/views/components/notifications.vue:
|
mobile/views/components/notifications.vue:
|
||||||
more: "もっと見る"
|
more: "もっと見る"
|
||||||
empty: "ありません!"
|
empty: "ありません!"
|
||||||
|
|
||||||
mobile/views/components/post-form.vue:
|
mobile/views/components/post-form.vue:
|
||||||
|
add-visible-user: "ユーザーを追加"
|
||||||
submit: "投稿"
|
submit: "投稿"
|
||||||
reply: "返信"
|
reply: "返信"
|
||||||
renote: "Renote"
|
renote: "Renote"
|
||||||
renote-placeholder: "この投稿を引用... (オプション)"
|
quote-placeholder: "この投稿を引用... (オプション)"
|
||||||
reply-placeholder: "この投稿への返信..."
|
reply-placeholder: "この投稿への返信..."
|
||||||
note-placeholder: "いまどうしてる?"
|
cw-placeholder: "内容への注釈 (オプション)"
|
||||||
|
location-alert: "お使いの端末は位置情報に対応していません"
|
||||||
|
error: "エラー"
|
||||||
|
username-prompt: "ユーザー名を入力してください"
|
||||||
|
|
||||||
mobile/views/components/sub-note-content.vue:
|
mobile/views/components/sub-note-content.vue:
|
||||||
media-count: "{}個のメディア"
|
private: "この投稿は非公開です"
|
||||||
poll: "投票"
|
deleted: "この投稿は削除されました"
|
||||||
|
media-count: "{}つのメディア"
|
||||||
|
poll: "アンケート"
|
||||||
|
|
||||||
mobile/views/components/timeline.vue:
|
mobile/views/components/timeline.vue:
|
||||||
empty: "投稿がありません"
|
empty: "投稿がありません"
|
||||||
load-more: "もっと"
|
load-more: "もっと"
|
||||||
|
|
||||||
mobile/views/components/ui.nav.vue:
|
mobile/views/components/ui.nav.vue:
|
||||||
home: "ホーム"
|
timeline: "タイムライン"
|
||||||
notifications: "通知"
|
notifications: "通知"
|
||||||
messaging: "メッセージ"
|
messaging: "メッセージ"
|
||||||
|
follow-requests: "フォロー申請"
|
||||||
search: "検索"
|
search: "検索"
|
||||||
drive: "ドライブ"
|
drive: "ドライブ"
|
||||||
|
favorites: "お気に入り"
|
||||||
|
user-lists: "リスト"
|
||||||
|
widgets: "ウィジェット"
|
||||||
|
game: "ゲーム"
|
||||||
|
darkmode: "ダークモード"
|
||||||
settings: "設定"
|
settings: "設定"
|
||||||
about: "Misskeyについて"
|
about: "Misskeyについて"
|
||||||
|
|
||||||
|
@ -788,8 +911,16 @@ mobile/views/components/users-list.vue:
|
||||||
known: "知り合い"
|
known: "知り合い"
|
||||||
load-more: "もっと"
|
load-more: "もっと"
|
||||||
|
|
||||||
|
mobile/views/pages/favorites.vue:
|
||||||
|
title: "お気に入り"
|
||||||
|
|
||||||
|
mobile/views/pages/user-lists.vue:
|
||||||
|
title: "リスト"
|
||||||
|
enter-list-name: "リスト名を入力してください"
|
||||||
|
|
||||||
mobile/views/pages/drive.vue:
|
mobile/views/pages/drive.vue:
|
||||||
drive: "ドライブ"
|
drive: "ドライブ"
|
||||||
|
more: "もっと見る"
|
||||||
|
|
||||||
mobile/views/pages/followers.vue:
|
mobile/views/pages/followers.vue:
|
||||||
followers-of: "{}のフォロワー"
|
followers-of: "{}のフォロワー"
|
||||||
|
@ -808,6 +939,11 @@ mobile/views/pages/messaging.vue:
|
||||||
mobile/views/pages/messaging-room.vue:
|
mobile/views/pages/messaging-room.vue:
|
||||||
messaging: "メッセージ"
|
messaging: "メッセージ"
|
||||||
|
|
||||||
|
mobile/views/pages/received-follow-requests.vue:
|
||||||
|
title: "フォロー申請"
|
||||||
|
accept: "承認"
|
||||||
|
reject: "拒否"
|
||||||
|
|
||||||
mobile/views/pages/note.vue:
|
mobile/views/pages/note.vue:
|
||||||
title: "投稿"
|
title: "投稿"
|
||||||
prev: "前の投稿"
|
prev: "前の投稿"
|
||||||
|
|
|
@ -334,7 +334,7 @@ desktop/views/components/friends-maker.vue:
|
||||||
refresh: "Więcej"
|
refresh: "Więcej"
|
||||||
close: "Zamknij"
|
close: "Zamknij"
|
||||||
desktop/views/components/game-window.vue:
|
desktop/views/components/game-window.vue:
|
||||||
game: "リバーシ"
|
game: "Reversi"
|
||||||
desktop/views/components/home.vue:
|
desktop/views/components/home.vue:
|
||||||
done: "Wyślij"
|
done: "Wyślij"
|
||||||
add-widget: "Dodaj widżet:"
|
add-widget: "Dodaj widżet:"
|
||||||
|
|
49
package.json
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "2.17.0",
|
"version": "4.1.1",
|
||||||
"clientVersion": "1.0.5731",
|
"clientVersion": "1.0.6542",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -23,10 +23,10 @@
|
||||||
"format": "gulp format"
|
"format": "gulp format"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome": "1.0.1",
|
"@fortawesome/fontawesome": "1.1.8",
|
||||||
"@fortawesome/fontawesome-free-brands": "5.0.2",
|
"@fortawesome/fontawesome-free-brands": "5.0.13",
|
||||||
"@fortawesome/fontawesome-free-regular": "5.0.2",
|
"@fortawesome/fontawesome-free-regular": "5.0.13",
|
||||||
"@fortawesome/fontawesome-free-solid": "5.0.2",
|
"@fortawesome/fontawesome-free-solid": "5.0.13",
|
||||||
"@koa/cors": "2.2.1",
|
"@koa/cors": "2.2.1",
|
||||||
"@prezzemolo/rap": "0.1.2",
|
"@prezzemolo/rap": "0.1.2",
|
||||||
"@prezzemolo/zip": "0.0.3",
|
"@prezzemolo/zip": "0.0.3",
|
||||||
|
@ -34,7 +34,6 @@
|
||||||
"@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.23",
|
"@types/elasticsearch": "5.0.23",
|
||||||
"@types/eventemitter3": "2.0.2",
|
|
||||||
"@types/gm": "1.18.0",
|
"@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",
|
||||||
|
@ -63,7 +62,6 @@
|
||||||
"@types/mkdirp": "0.5.2",
|
"@types/mkdirp": "0.5.2",
|
||||||
"@types/mocha": "5.2.0",
|
"@types/mocha": "5.2.0",
|
||||||
"@types/mongodb": "3.0.18",
|
"@types/mongodb": "3.0.18",
|
||||||
"@types/monk": "6.0.0",
|
|
||||||
"@types/ms": "0.7.30",
|
"@types/ms": "0.7.30",
|
||||||
"@types/node": "10.1.2",
|
"@types/node": "10.1.2",
|
||||||
"@types/nopt": "3.0.29",
|
"@types/nopt": "3.0.29",
|
||||||
|
@ -114,7 +112,7 @@
|
||||||
"gulp-cssnano": "2.1.3",
|
"gulp-cssnano": "2.1.3",
|
||||||
"gulp-htmlmin": "4.0.0",
|
"gulp-htmlmin": "4.0.0",
|
||||||
"gulp-imagemin": "4.1.0",
|
"gulp-imagemin": "4.1.0",
|
||||||
"gulp-mocha": "5.0.0",
|
"gulp-mocha": "6.0.0",
|
||||||
"gulp-pug": "4.0.1",
|
"gulp-pug": "4.0.1",
|
||||||
"gulp-rename": "1.2.3",
|
"gulp-rename": "1.2.3",
|
||||||
"gulp-replace": "1.0.0",
|
"gulp-replace": "1.0.0",
|
||||||
|
@ -124,17 +122,17 @@
|
||||||
"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.9",
|
"hard-source-webpack-plugin": "0.6.10",
|
||||||
"highlight.js": "9.12.0",
|
"highlight.js": "9.12.0",
|
||||||
"html-minifier": "3.5.15",
|
"html-minifier": "3.5.16",
|
||||||
"http-signature": "1.2.0",
|
"http-signature": "1.2.0",
|
||||||
"inquirer": "5.2.0",
|
"inquirer": "5.2.0",
|
||||||
"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.10.0",
|
"jsdom": "11.11.0",
|
||||||
"koa": "2.5.1",
|
"koa": "2.5.1",
|
||||||
"koa-bodyparser": "4.2.0",
|
"koa-bodyparser": "4.2.1",
|
||||||
"koa-compress": "3.0.0",
|
"koa-compress": "3.0.0",
|
||||||
"koa-favicon": "2.0.1",
|
"koa-favicon": "2.0.1",
|
||||||
"koa-json-body": "5.3.0",
|
"koa-json-body": "5.3.0",
|
||||||
|
@ -152,7 +150,7 @@
|
||||||
"mkdirp": "0.5.1",
|
"mkdirp": "0.5.1",
|
||||||
"mocha": "5.2.0",
|
"mocha": "5.2.0",
|
||||||
"moji": "0.5.1",
|
"moji": "0.5.1",
|
||||||
"mongodb": "3.0.8",
|
"mongodb": "3.0.10",
|
||||||
"monk": "6.0.6",
|
"monk": "6.0.6",
|
||||||
"ms": "2.1.1",
|
"ms": "2.1.1",
|
||||||
"nan": "2.10.0",
|
"nan": "2.10.0",
|
||||||
|
@ -163,18 +161,18 @@
|
||||||
"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": "5.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.1",
|
||||||
"qrcode": "1.2.0",
|
"qrcode": "1.2.0",
|
||||||
"ratelimiter": "3.0.3",
|
"ratelimiter": "3.0.3",
|
||||||
"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.86.0",
|
"request": "2.87.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",
|
||||||
|
@ -193,7 +191,7 @@
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"tmp": "0.0.33",
|
"tmp": "0.0.33",
|
||||||
"ts-loader": "4.3.0",
|
"ts-loader": "4.3.0",
|
||||||
"ts-node": "6.0.3",
|
"ts-node": "6.0.4",
|
||||||
"tslint": "5.10.0",
|
"tslint": "5.10.0",
|
||||||
"typescript": "2.8.3",
|
"typescript": "2.8.3",
|
||||||
"typescript-eslint-parser": "15.0.0",
|
"typescript-eslint-parser": "15.0.0",
|
||||||
|
@ -205,8 +203,7 @@
|
||||||
"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.1.0",
|
"vue-loader": "15.2.1",
|
||||||
"vue-material": "^1.0.0-beta-10.2",
|
|
||||||
"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",
|
||||||
|
@ -214,10 +211,14 @@
|
||||||
"vuex-persistedstate": "^2.5.4",
|
"vuex-persistedstate": "^2.5.4",
|
||||||
"web-push": "3.3.1",
|
"web-push": "3.3.1",
|
||||||
"webfinger.js": "2.6.6",
|
"webfinger.js": "2.6.6",
|
||||||
"webpack": "4.8.3",
|
"webpack": "4.9.1",
|
||||||
"webpack-cli": "2.1.3",
|
"webpack-cli": "2.1.4",
|
||||||
"websocket": "1.0.26",
|
"websocket": "1.0.26",
|
||||||
"ws": "5.1.1",
|
"ws": "5.2.0",
|
||||||
"xev": "2.0.0"
|
"xev": "2.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/file-type": "5.2.1",
|
||||||
|
"@types/jsdom": "11.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default acct => {
|
export default (acct: string) => {
|
||||||
const splitted = acct.split('@', 2);
|
const splitted = acct.split('@', 2);
|
||||||
return { username: splitted[0], host: splitted[1] || null };
|
return { username: splitted[0], host: splitted[1] || null };
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
export default user => {
|
import { IUser } from '../models/user';
|
||||||
|
|
||||||
|
export default (user: IUser) => {
|
||||||
return user.host === null ? user.username : `${user.username}@${user.host}`;
|
return user.host === null ? user.username : `${user.username}@${user.host}`;
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,18 +3,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fontawesome from '@fortawesome/fontawesome';
|
import * as fontawesome from '@fortawesome/fontawesome';
|
||||||
import * as regular from '@fortawesome/fontawesome-free-regular';
|
import regular from '@fortawesome/fontawesome-free-regular';
|
||||||
import * as solid from '@fortawesome/fontawesome-free-solid';
|
import solid from '@fortawesome/fontawesome-free-solid';
|
||||||
import * as brands from '@fortawesome/fontawesome-free-brands';
|
import brands from '@fortawesome/fontawesome-free-brands';
|
||||||
|
|
||||||
fontawesome.library.add(regular, solid, brands);
|
fontawesome.library.add(regular, solid, brands);
|
||||||
|
|
||||||
export const pattern = /%fa:(.+?)%/g;
|
export const pattern = /%fa:(.+?)%/g;
|
||||||
|
|
||||||
export const replacement = (match, key) => {
|
export const replacement = (match: string, key: string) => {
|
||||||
const args = key.split(' ');
|
const args = key.split(' ');
|
||||||
let prefix = 'fas';
|
let prefix = 'fas';
|
||||||
const classes = [];
|
const classes: string[] = [];
|
||||||
let transform = '';
|
let transform = '';
|
||||||
let name;
|
let name;
|
||||||
|
|
||||||
|
@ -34,12 +34,12 @@ export const replacement = (match, key) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const icon = fontawesome.icon({ prefix, iconName: name }, {
|
const icon = fontawesome.icon({ prefix, iconName: name } as fontawesome.IconLookup, {
|
||||||
classes: classes
|
classes: classes,
|
||||||
|
transform: fontawesome.parse.transform(transform)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (icon) {
|
if (icon) {
|
||||||
icon.transform = fontawesome.parse.transform(transform);
|
|
||||||
return `<i data-fa class="${name}">${icon.html[0]}</i>`;
|
return `<i data-fa class="${name}">${icon.html[0]}</i>`;
|
||||||
} else {
|
} else {
|
||||||
console.warn(`'${name}' not found in fa`);
|
console.warn(`'${name}' not found in fa`);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* Replace i18n texts
|
* Replace i18n texts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import locale from '../../locales';
|
import locale, { isAvailableLanguage, LocaleObject } from '../../locales';
|
||||||
|
|
||||||
export default class Replacer {
|
export default class Replacer {
|
||||||
private lang: string;
|
private lang: string;
|
||||||
|
@ -16,19 +16,19 @@ export default class Replacer {
|
||||||
this.replacement = this.replacement.bind(this);
|
this.replacement = this.replacement.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get(path: string, key: string) {
|
private get(path: string, key: string): string {
|
||||||
const texts = locale[this.lang];
|
if (!isAvailableLanguage(this.lang)) {
|
||||||
|
|
||||||
if (texts == null) {
|
|
||||||
console.warn(`lang '${this.lang}' is not supported`);
|
console.warn(`lang '${this.lang}' is not supported`);
|
||||||
return key; // Fallback
|
return key; // Fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const texts = locale[this.lang];
|
||||||
|
|
||||||
let text = texts;
|
let text = texts;
|
||||||
|
|
||||||
if (path) {
|
if (path) {
|
||||||
if (text.hasOwnProperty(path)) {
|
if (text.hasOwnProperty(path)) {
|
||||||
text = text[path];
|
text = text[path] as LocaleObject;
|
||||||
} else {
|
} else {
|
||||||
console.warn(`path '${path}' not found in '${this.lang}'`);
|
console.warn(`path '${path}' not found in '${this.lang}'`);
|
||||||
return key; // Fallback
|
return key; // Fallback
|
||||||
|
@ -38,7 +38,7 @@ export default class Replacer {
|
||||||
// Check the key existance
|
// Check the key existance
|
||||||
const error = key.split('.').some(k => {
|
const error = key.split('.').some(k => {
|
||||||
if (text.hasOwnProperty(k)) {
|
if (text.hasOwnProperty(k)) {
|
||||||
text = text[k];
|
text = (text as LocaleObject)[k];
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
|
@ -48,12 +48,15 @@ export default class Replacer {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.warn(`key '${key}' not found in '${path}' of '${this.lang}'`);
|
console.warn(`key '${key}' not found in '${path}' of '${this.lang}'`);
|
||||||
return key; // Fallback
|
return key; // Fallback
|
||||||
|
} else if (typeof text !== 'string') {
|
||||||
|
console.warn(`key '${key}' is not string in '${path}' of '${this.lang}'`);
|
||||||
|
return key; // Fallback
|
||||||
} else {
|
} else {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public replacement(match, key) {
|
public replacement(match: string, key: string) {
|
||||||
let path = null;
|
let path = null;
|
||||||
|
|
||||||
if (key.indexOf('|') != -1) {
|
if (key.indexOf('|') != -1) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import * as mongo from 'mongodb';
|
import * as mongo from 'mongodb';
|
||||||
import { Query } from 'cafy';
|
import { Query } from 'cafy';
|
||||||
|
|
||||||
export const isAnId = x => mongo.ObjectID.isValid(x);
|
export const isAnId = (x: any) => mongo.ObjectID.isValid(x);
|
||||||
export const isNotAnId = x => !isAnId(x);
|
export const isNotAnId = (x: any) => !isAnId(x);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID
|
* ID
|
||||||
|
|
|
@ -7,11 +7,6 @@ html
|
||||||
cursor progress !important
|
cursor progress !important
|
||||||
|
|
||||||
body
|
body
|
||||||
// for md
|
|
||||||
font-size 16px !important
|
|
||||||
line-height initial !important
|
|
||||||
letter-spacing initial !important
|
|
||||||
|
|
||||||
overflow-wrap break-word
|
overflow-wrap break-word
|
||||||
|
|
||||||
#error
|
#error
|
||||||
|
|
After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 646 B |
|
@ -20,6 +20,7 @@ init(launch => {
|
||||||
// Init router
|
// Init router
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
mode: 'history',
|
mode: 'history',
|
||||||
|
base: '/auth/',
|
||||||
routes: [
|
routes: [
|
||||||
{ path: '/:token', component: Index },
|
{ path: '/:token', component: Index },
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="index">
|
<div class="index">
|
||||||
<main v-if="os.isSignedIn">
|
<main v-if="$store.getters.isSignedIn">
|
||||||
<p class="fetching" v-if="fetching">読み込み中<mk-ellipsis/></p>
|
<p class="fetching" v-if="fetching">読み込み中<mk-ellipsis/></p>
|
||||||
<x-form
|
<x-form
|
||||||
|
class="form"
|
||||||
ref="form"
|
ref="form"
|
||||||
v-if="state == 'waiting'"
|
v-if="state == 'waiting'"
|
||||||
:session="session"
|
:session="session"
|
||||||
|
@ -22,11 +23,11 @@
|
||||||
<p>セッションが存在しません。</p>
|
<p>セッションが存在しません。</p>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<main class="signin" v-if="!os.isSignedIn">
|
<main class="signin" v-if="!$store.getters.isSignedIn">
|
||||||
<h1>サインインしてください</h1>
|
<h1>サインインしてください</h1>
|
||||||
<mk-signin/>
|
<mk-signin/>
|
||||||
</main>
|
</main>
|
||||||
<footer><img src="/assets/auth/logo.svg" alt="Misskey"/></footer>
|
<footer><img src="/assets/auth/icon.svg" alt="Misskey"/></footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -51,7 +52,7 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (!this.$root.$data.os.isSignedIn) return;
|
if (!this.$store.getters.isSignedIn) return;
|
||||||
|
|
||||||
// Fetch session
|
// Fetch session
|
||||||
(this as any).api('auth/session/show', {
|
(this as any).api('auth/session/show', {
|
||||||
|
@ -62,7 +63,7 @@ export default Vue.extend({
|
||||||
|
|
||||||
// 既に連携していた場合
|
// 既に連携していた場合
|
||||||
if (this.session.app.isAuthorized) {
|
if (this.session.app.isAuthorized) {
|
||||||
this.$root.$data.os.api('auth/accept', {
|
(this as any).api('auth/accept', {
|
||||||
token: this.session.token
|
token: this.session.token
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.accepted();
|
this.accepted();
|
||||||
|
@ -72,6 +73,7 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.state = 'fetch-session-error';
|
this.state = 'fetch-session-error';
|
||||||
|
this.fetching = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -101,7 +103,7 @@ export default Vue.extend({
|
||||||
padding 32px
|
padding 32px
|
||||||
color #555
|
color #555
|
||||||
|
|
||||||
> div
|
> div:not(.form)
|
||||||
padding 64px
|
padding 64px
|
||||||
|
|
||||||
> h1
|
> h1
|
||||||
|
@ -142,8 +144,8 @@ export default Vue.extend({
|
||||||
> footer
|
> footer
|
||||||
> img
|
> img
|
||||||
display block
|
display block
|
||||||
width 64px
|
width 32px
|
||||||
height 64px
|
height 32px
|
||||||
margin 0 auto
|
margin 16px auto
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -19,7 +19,7 @@ html
|
||||||
| Misskey
|
| Misskey
|
||||||
|
|
||||||
block desc
|
block desc
|
||||||
meta(name='description' content='A SNS')
|
meta(name='description' content='A planet of fediverse')
|
||||||
|
|
||||||
block meta
|
block meta
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ html
|
||||||
| JavaScriptを有効にしてください
|
| JavaScriptを有効にしてください
|
||||||
br
|
br
|
||||||
| Please turn on your JavaScript
|
| Please turn on your JavaScript
|
||||||
div#ini: p
|
div#ini.
|
||||||
span .
|
<svg viewBox="0 0 50 50">
|
||||||
span .
|
<path fill=#{themeColor} d="M25.251,6.461c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615V6.461z" />
|
||||||
span .
|
</svg>
|
||||||
|
|
|
@ -32,9 +32,9 @@
|
||||||
//#region Detect app name
|
//#region Detect app name
|
||||||
let app = null;
|
let app = null;
|
||||||
|
|
||||||
if (url.pathname == '/docs') app = 'docs';
|
if (url.pathname == '/docs' || url.pathname.startsWith('/docs/')) app = 'docs';
|
||||||
if (url.pathname == '/dev') app = 'dev';
|
if (url.pathname == '/dev' || url.pathname.startsWith('/dev/')) app = 'dev';
|
||||||
if (url.pathname == '/auth') app = 'auth';
|
if (url.pathname == '/auth' || url.pathname.startsWith('/auth/')) app = 'auth';
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region Detect the user language
|
//#region Detect the user language
|
||||||
|
|
|
@ -9,9 +9,9 @@ export default function<T extends object>(data: {
|
||||||
widget: {
|
widget: {
|
||||||
type: Object
|
type: Object
|
||||||
},
|
},
|
||||||
isMobile: {
|
platform: {
|
||||||
type: Boolean,
|
type: String,
|
||||||
default: false
|
required: true
|
||||||
},
|
},
|
||||||
isCustomizeMode: {
|
isCustomizeMode: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -66,17 +66,10 @@ export default function<T extends object>(data: {
|
||||||
|
|
||||||
this.bakeProps();
|
this.bakeProps();
|
||||||
|
|
||||||
if (this.isMobile) {
|
(this as any).api('i/update_widget', {
|
||||||
(this as any).api('i/update_mobile_home', {
|
id: this.id,
|
||||||
id: this.id,
|
data: this.props
|
||||||
data: this.props
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
(this as any).api('i/update_home', {
|
|
||||||
id: this.id,
|
|
||||||
data: this.props
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -55,7 +55,7 @@ export default function(type, data): Notification {
|
||||||
icon: data.user.avatarUrl + '?thumbnail&size=64'
|
icon: data.user.avatarUrl + '?thumbnail&size=64'
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'othello_invited':
|
case 'reversi_invited':
|
||||||
return {
|
return {
|
||||||
title: '対局への招待があります',
|
title: '対局への招待があります',
|
||||||
body: `${getUserName(data.parent)}さんから`,
|
body: `${getUserName(data.parent)}さんから`,
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import * as merge from 'object-assign-deep';
|
|
||||||
|
|
||||||
import Stream from './stream';
|
import Stream from './stream';
|
||||||
import StreamManager from './stream-manager';
|
import StreamManager from './stream-manager';
|
||||||
import MiOS from '../../../mios';
|
import MiOS from '../../../mios';
|
||||||
|
@ -20,14 +18,36 @@ export class HomeStream extends Stream {
|
||||||
}, 1000 * 60);
|
}, 1000 * 60);
|
||||||
|
|
||||||
// 自分の情報が更新されたとき
|
// 自分の情報が更新されたとき
|
||||||
this.on('i_updated', i => {
|
this.on('meUpdated', i => {
|
||||||
if (os.debug) {
|
if (os.debug) {
|
||||||
console.log('I updated:', i);
|
console.log('I updated:', i);
|
||||||
}
|
}
|
||||||
merge(me, i);
|
|
||||||
|
|
||||||
// キャッシュ更新
|
os.store.dispatch('mergeMe', i);
|
||||||
os.bakeMe();
|
});
|
||||||
|
|
||||||
|
this.on('read_all_notifications', () => {
|
||||||
|
os.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadNotification: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('unread_notification', () => {
|
||||||
|
os.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadNotification: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('read_all_messaging_messages', () => {
|
||||||
|
os.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadMessagingMessage: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('unread_messaging_message', () => {
|
||||||
|
os.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadMessagingMessage: true
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('clientSettingUpdated', x => {
|
this.on('clientSettingUpdated', x => {
|
||||||
|
@ -38,25 +58,18 @@ export class HomeStream extends Stream {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('home_updated', x => {
|
this.on('home_updated', x => {
|
||||||
if (x.home) {
|
os.store.commit('settings/setHome', x);
|
||||||
os.store.commit('settings/setHome', x.home);
|
|
||||||
} else {
|
|
||||||
os.store.commit('settings/setHomeWidget', {
|
|
||||||
id: x.id,
|
|
||||||
data: x.data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('mobile_home_updated', x => {
|
this.on('mobile_home_updated', x => {
|
||||||
if (x.home) {
|
os.store.commit('settings/setMobileHome', x);
|
||||||
os.store.commit('settings/setMobileHome', x.home);
|
});
|
||||||
} else {
|
|
||||||
os.store.commit('settings/setMobileHomeWidget', {
|
this.on('widgetUpdated', x => {
|
||||||
id: x.id,
|
os.store.commit('settings/setWidget', {
|
||||||
data: x.data
|
id: x.id,
|
||||||
});
|
data: x.data
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// トークンが再生成されたとき
|
// トークンが再生成されたとき
|
||||||
|
|
|
@ -3,15 +3,15 @@ import StreamManager from './stream-manager';
|
||||||
import MiOS from '../../../mios';
|
import MiOS from '../../../mios';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server stream connection
|
* Notes stats stream connection
|
||||||
*/
|
*/
|
||||||
export class ServerStream extends Stream {
|
export class NotesStatsStream extends Stream {
|
||||||
constructor(os: MiOS) {
|
constructor(os: MiOS) {
|
||||||
super(os, 'server');
|
super(os, 'notes-stats');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ServerStreamManager extends StreamManager<ServerStream> {
|
export class NotesStatsStreamManager extends StreamManager<NotesStatsStream> {
|
||||||
private os: MiOS;
|
private os: MiOS;
|
||||||
|
|
||||||
constructor(os: MiOS) {
|
constructor(os: MiOS) {
|
||||||
|
@ -22,7 +22,7 @@ export class ServerStreamManager extends StreamManager<ServerStream> {
|
||||||
|
|
||||||
public getConnection() {
|
public getConnection() {
|
||||||
if (this.connection == null) {
|
if (this.connection == null) {
|
||||||
this.connection = new ServerStream(this.os);
|
this.connection = new NotesStatsStream(this.os);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.connection;
|
return this.connection;
|
|
@ -1,9 +1,9 @@
|
||||||
import Stream from './stream';
|
import Stream from './stream';
|
||||||
import MiOS from '../../../mios';
|
import MiOS from '../../../mios';
|
||||||
|
|
||||||
export class OthelloGameStream extends Stream {
|
export class ReversiGameStream extends Stream {
|
||||||
constructor(os: MiOS, me, game) {
|
constructor(os: MiOS, me, game) {
|
||||||
super(os, 'othello-game', {
|
super(os, 'reversi-game', {
|
||||||
i: me ? me.token : null,
|
i: me ? me.token : null,
|
||||||
game: game.id
|
game: game.id
|
||||||
});
|
});
|
|
@ -2,15 +2,15 @@ import StreamManager from './stream-manager';
|
||||||
import Stream from './stream';
|
import Stream from './stream';
|
||||||
import MiOS from '../../../mios';
|
import MiOS from '../../../mios';
|
||||||
|
|
||||||
export class OthelloStream extends Stream {
|
export class ReversiStream extends Stream {
|
||||||
constructor(os: MiOS, me) {
|
constructor(os: MiOS, me) {
|
||||||
super(os, 'othello', {
|
super(os, 'reversi', {
|
||||||
i: me.token
|
i: me.token
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OthelloStreamManager extends StreamManager<OthelloStream> {
|
export class ReversiStreamManager extends StreamManager<ReversiStream> {
|
||||||
private me;
|
private me;
|
||||||
private os: MiOS;
|
private os: MiOS;
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ export class OthelloStreamManager extends StreamManager<OthelloStream> {
|
||||||
|
|
||||||
public getConnection() {
|
public getConnection() {
|
||||||
if (this.connection == null) {
|
if (this.connection == null) {
|
||||||
this.connection = new OthelloStream(this.os, this.me);
|
this.connection = new ReversiStream(this.os, this.me);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.connection;
|
return this.connection;
|
|
@ -0,0 +1,30 @@
|
||||||
|
import Stream from './stream';
|
||||||
|
import StreamManager from './stream-manager';
|
||||||
|
import MiOS from '../../../mios';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server stats stream connection
|
||||||
|
*/
|
||||||
|
export class ServerStatsStream extends Stream {
|
||||||
|
constructor(os: MiOS) {
|
||||||
|
super(os, 'server-stats');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ServerStatsStreamManager extends StreamManager<ServerStatsStream> {
|
||||||
|
private os: MiOS;
|
||||||
|
|
||||||
|
constructor(os: MiOS) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.os = os;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConnection() {
|
||||||
|
if (this.connection == null) {
|
||||||
|
this.connection = new ServerStatsStream(this.os);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.connection;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
<template>
|
||||||
|
<svg class="mk-analog-clock" viewBox="0 0 10 10" preserveAspectRatio="none">
|
||||||
|
<circle v-for="angle, i in graduations"
|
||||||
|
:cx="5 + (Math.sin(angle) * (5 - graduationsPadding))"
|
||||||
|
:cy="5 - (Math.cos(angle) * (5 - graduationsPadding))"
|
||||||
|
:r="i % 5 == 0 ? 0.125 : 0.05"
|
||||||
|
:fill="i % 5 == 0 ? majorGraduationColor : minorGraduationColor"/>
|
||||||
|
|
||||||
|
<line
|
||||||
|
:x1="5 - (Math.sin(sAngle) * (sHandLengthRatio * handsTailLength))"
|
||||||
|
:y1="5 + (Math.cos(sAngle) * (sHandLengthRatio * handsTailLength))"
|
||||||
|
:x2="5 + (Math.sin(sAngle) * ((sHandLengthRatio * 5) - handsPadding))"
|
||||||
|
:y2="5 - (Math.cos(sAngle) * ((sHandLengthRatio * 5) - handsPadding))"
|
||||||
|
:stroke="sHandColor"
|
||||||
|
stroke-width="0.05"/>
|
||||||
|
<line
|
||||||
|
:x1="5 - (Math.sin(mAngle) * (mHandLengthRatio * handsTailLength))"
|
||||||
|
:y1="5 + (Math.cos(mAngle) * (mHandLengthRatio * handsTailLength))"
|
||||||
|
:x2="5 + (Math.sin(mAngle) * ((mHandLengthRatio * 5) - handsPadding))"
|
||||||
|
:y2="5 - (Math.cos(mAngle) * ((mHandLengthRatio * 5) - handsPadding))"
|
||||||
|
:stroke="mHandColor"
|
||||||
|
stroke-width="0.1"/>
|
||||||
|
<line
|
||||||
|
:x1="5 - (Math.sin(hAngle) * (hHandLengthRatio * handsTailLength))"
|
||||||
|
:y1="5 + (Math.cos(hAngle) * (hHandLengthRatio * handsTailLength))"
|
||||||
|
:x2="5 + (Math.sin(hAngle) * ((hHandLengthRatio * 5) - handsPadding))"
|
||||||
|
:y2="5 - (Math.cos(hAngle) * ((hHandLengthRatio * 5) - handsPadding))"
|
||||||
|
:stroke="hHandColor"
|
||||||
|
stroke-width="0.1"/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { themeColor } from '../../../config';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
dark: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
now: new Date(),
|
||||||
|
clock: null,
|
||||||
|
|
||||||
|
graduationsPadding: 0.5,
|
||||||
|
handsPadding: 1,
|
||||||
|
handsTailLength: 0.7,
|
||||||
|
hHandLengthRatio: 0.75,
|
||||||
|
mHandLengthRatio: 1,
|
||||||
|
sHandLengthRatio: 1
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
majorGraduationColor(): string {
|
||||||
|
return this.dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)';
|
||||||
|
},
|
||||||
|
minorGraduationColor(): string {
|
||||||
|
return this.dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||||
|
},
|
||||||
|
|
||||||
|
sHandColor(): string {
|
||||||
|
return this.dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)';
|
||||||
|
},
|
||||||
|
mHandColor(): string {
|
||||||
|
return this.dark ? '#fff' : '#777';
|
||||||
|
},
|
||||||
|
hHandColor(): string {
|
||||||
|
return themeColor;
|
||||||
|
},
|
||||||
|
|
||||||
|
s(): number {
|
||||||
|
return this.now.getSeconds();
|
||||||
|
},
|
||||||
|
m(): number {
|
||||||
|
return this.now.getMinutes();
|
||||||
|
},
|
||||||
|
h(): number {
|
||||||
|
return this.now.getHours();
|
||||||
|
},
|
||||||
|
|
||||||
|
hAngle(): number {
|
||||||
|
return Math.PI * (this.h % 12 + this.m / 60) / 6;
|
||||||
|
},
|
||||||
|
mAngle(): number {
|
||||||
|
return Math.PI * (this.m + this.s / 60) / 30;
|
||||||
|
},
|
||||||
|
sAngle(): number {
|
||||||
|
return Math.PI * this.s / 30;
|
||||||
|
},
|
||||||
|
|
||||||
|
graduations(): any {
|
||||||
|
const angles = [];
|
||||||
|
for (let i = 0; i < 60; i++) {
|
||||||
|
const angle = Math.PI * i / 30;
|
||||||
|
angles.push(angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return angles;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.clock = setInterval(this.tick, 1000);
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.clock);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
tick() {
|
||||||
|
this.now = new Date();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.mk-analog-clock
|
||||||
|
display block
|
||||||
|
</style>
|
|
@ -32,7 +32,7 @@ export default Vue.extend({
|
||||||
? `rgb(${ this.user.avatarColor.join(',') })`
|
? `rgb(${ this.user.avatarColor.join(',') })`
|
||||||
: null,
|
: null,
|
||||||
backgroundImage: this.lightmode ? null : `url(${ this.user.avatarUrl }?thumbnail)`,
|
backgroundImage: this.lightmode ? null : `url(${ this.user.avatarUrl }?thumbnail)`,
|
||||||
borderRadius: (this as any).clientSettings.circleIcons ? '100%' : null
|
borderRadius: this.$store.state.settings.circleIcons ? '100%' : null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,6 @@
|
||||||
|
|
||||||
.a
|
.a
|
||||||
display block
|
display block
|
||||||
position absolute
|
|
||||||
top 0
|
|
||||||
right 0
|
|
||||||
|
|
||||||
> svg
|
> svg
|
||||||
display block
|
display block
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
import analogClock from './analog-clock.vue';
|
||||||
|
import menu from './menu.vue';
|
||||||
|
import noteHeader from './note-header.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';
|
||||||
|
@ -24,9 +27,20 @@ import urlPreview from './url-preview.vue';
|
||||||
import twitterSetting from './twitter-setting.vue';
|
import twitterSetting from './twitter-setting.vue';
|
||||||
import fileTypeIcon from './file-type-icon.vue';
|
import fileTypeIcon from './file-type-icon.vue';
|
||||||
import Switch from './switch.vue';
|
import Switch from './switch.vue';
|
||||||
import Othello from './othello.vue';
|
import Reversi from './reversi.vue';
|
||||||
import welcomeTimeline from './welcome-timeline.vue';
|
import welcomeTimeline from './welcome-timeline.vue';
|
||||||
|
import uiInput from './ui/input.vue';
|
||||||
|
import uiButton from './ui/button.vue';
|
||||||
|
import uiCard from './ui/card.vue';
|
||||||
|
import uiForm from './ui/form.vue';
|
||||||
|
import uiTextarea from './ui/textarea.vue';
|
||||||
|
import uiSwitch from './ui/switch.vue';
|
||||||
|
import uiRadio from './ui/radio.vue';
|
||||||
|
import uiSelect from './ui/select.vue';
|
||||||
|
|
||||||
|
Vue.component('mk-analog-clock', analogClock);
|
||||||
|
Vue.component('mk-menu', menu);
|
||||||
|
Vue.component('mk-note-header', noteHeader);
|
||||||
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);
|
||||||
|
@ -51,5 +65,13 @@ Vue.component('mk-url-preview', urlPreview);
|
||||||
Vue.component('mk-twitter-setting', twitterSetting);
|
Vue.component('mk-twitter-setting', twitterSetting);
|
||||||
Vue.component('mk-file-type-icon', fileTypeIcon);
|
Vue.component('mk-file-type-icon', fileTypeIcon);
|
||||||
Vue.component('mk-switch', Switch);
|
Vue.component('mk-switch', Switch);
|
||||||
Vue.component('mk-othello', Othello);
|
Vue.component('mk-reversi', Reversi);
|
||||||
Vue.component('mk-welcome-timeline', welcomeTimeline);
|
Vue.component('mk-welcome-timeline', welcomeTimeline);
|
||||||
|
Vue.component('ui-input', uiInput);
|
||||||
|
Vue.component('ui-button', uiButton);
|
||||||
|
Vue.component('ui-card', uiCard);
|
||||||
|
Vue.component('ui-form', uiForm);
|
||||||
|
Vue.component('ui-textarea', uiTextarea);
|
||||||
|
Vue.component('ui-switch', uiSwitch);
|
||||||
|
Vue.component('ui-radio', uiRadio);
|
||||||
|
Vue.component('ui-select', uiSelect);
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-media-list" :data-count="mediaList.length">
|
<div class="mk-media-list">
|
||||||
<template v-for="media in mediaList">
|
<div :data-count="mediaList.length" ref="grid">
|
||||||
<mk-media-video :video="media" :key="media.id" v-if="media.type.startsWith('video')" :inline-playable="mediaList.length === 1"/>
|
<template v-for="media in mediaList">
|
||||||
<mk-media-image :image="media" :key="media.id" v-else :raw="raw"/>
|
<mk-media-video :video="media" :key="media.id" v-if="media.type.startsWith('video')" :inline-playable="mediaList.length === 1"/>
|
||||||
</template>
|
<mk-media-image :image="media" :key="media.id" v-else :raw="raw"/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -18,47 +20,60 @@ export default Vue.extend({
|
||||||
raw: {
|
raw: {
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// for Safari bug
|
||||||
|
this.$refs.grid.style.height = this.$refs.grid.clientHeight ? `${this.$refs.grid.clientHeight}px` : '128px';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.mk-media-list
|
.mk-media-list
|
||||||
display grid
|
width 100%
|
||||||
grid-gap 4px
|
|
||||||
height 256px
|
|
||||||
|
|
||||||
@media (max-width 500px)
|
&:before
|
||||||
height 192px
|
content ''
|
||||||
|
display block
|
||||||
|
padding-top 56.25% // 16:9
|
||||||
|
|
||||||
|
> div
|
||||||
|
position absolute
|
||||||
|
top 0
|
||||||
|
right 0
|
||||||
|
bottom 0
|
||||||
|
left 0
|
||||||
|
display grid
|
||||||
|
grid-gap 4px
|
||||||
|
|
||||||
|
&[data-count="1"]
|
||||||
|
grid-template-rows 1fr
|
||||||
|
&[data-count="2"]
|
||||||
|
grid-template-columns 1fr 1fr
|
||||||
|
grid-template-rows 1fr
|
||||||
|
&[data-count="3"]
|
||||||
|
grid-template-columns 1fr 0.5fr
|
||||||
|
grid-template-rows 1fr 1fr
|
||||||
|
:nth-child(1)
|
||||||
|
grid-row 1 / 3
|
||||||
|
:nth-child(3)
|
||||||
|
grid-column 2 / 3
|
||||||
|
grid-row 2 / 3
|
||||||
|
&[data-count="4"]
|
||||||
|
grid-template-columns 1fr 1fr
|
||||||
|
grid-template-rows 1fr 1fr
|
||||||
|
|
||||||
&[data-count="1"]
|
|
||||||
grid-template-rows 1fr
|
|
||||||
&[data-count="2"]
|
|
||||||
grid-template-columns 1fr 1fr
|
|
||||||
grid-template-rows 1fr
|
|
||||||
&[data-count="3"]
|
|
||||||
grid-template-columns 1fr 0.5fr
|
|
||||||
grid-template-rows 1fr 1fr
|
|
||||||
:nth-child(1)
|
:nth-child(1)
|
||||||
grid-row 1 / 3
|
grid-column 1 / 2
|
||||||
:nth-child(3)
|
grid-row 1 / 2
|
||||||
|
:nth-child(2)
|
||||||
grid-column 2 / 3
|
grid-column 2 / 3
|
||||||
grid-row 2/3
|
grid-row 1 / 2
|
||||||
&[data-count="4"]
|
:nth-child(3)
|
||||||
grid-template-columns 1fr 1fr
|
grid-column 1 / 2
|
||||||
grid-template-rows 1fr 1fr
|
grid-row 2 / 3
|
||||||
|
:nth-child(4)
|
||||||
:nth-child(1)
|
grid-column 2 / 3
|
||||||
grid-column 1 / 2
|
grid-row 2 / 3
|
||||||
grid-row 1 / 2
|
|
||||||
:nth-child(2)
|
|
||||||
grid-column 2 / 3
|
|
||||||
grid-row 1 / 2
|
|
||||||
:nth-child(3)
|
|
||||||
grid-column 1 / 2
|
|
||||||
grid-row 2 / 3
|
|
||||||
:nth-child(4)
|
|
||||||
grid-column 2 / 3
|
|
||||||
grid-row 2 / 3
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
<template>
|
||||||
|
<div class="mk-menu">
|
||||||
|
<div class="backdrop" ref="backdrop" @click="close"></div>
|
||||||
|
<div class="popover" :class="{ hukidasi }" ref="popover">
|
||||||
|
<template v-for="item in items">
|
||||||
|
<div v-if="item === null"></div>
|
||||||
|
<button v-if="item" @click="clicked(item.action)" v-html="item.icon ? item.icon + ' ' + item.text : item.text"></button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import * as anime from 'animejs';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
source: {
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
compact: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
hukidasi: !this.compact
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const popover = this.$refs.popover as any;
|
||||||
|
|
||||||
|
const rect = this.source.getBoundingClientRect();
|
||||||
|
const width = popover.offsetWidth;
|
||||||
|
const height = popover.offsetHeight;
|
||||||
|
|
||||||
|
let left;
|
||||||
|
let top;
|
||||||
|
|
||||||
|
if (this.compact) {
|
||||||
|
const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
|
||||||
|
const y = rect.top + window.pageYOffset + (this.source.offsetHeight / 2);
|
||||||
|
left = (x - (width / 2));
|
||||||
|
top = (y - (height / 2));
|
||||||
|
} else {
|
||||||
|
const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
|
||||||
|
const y = rect.top + window.pageYOffset + this.source.offsetHeight;
|
||||||
|
left = (x - (width / 2));
|
||||||
|
top = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left + width - window.pageXOffset > window.innerWidth) {
|
||||||
|
left = window.innerWidth - width + window.pageXOffset;
|
||||||
|
this.hukidasi = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top + height - window.pageYOffset > window.innerHeight) {
|
||||||
|
top = window.innerHeight - height + window.pageYOffset;
|
||||||
|
this.hukidasi = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
popover.style.left = left + 'px';
|
||||||
|
popover.style.top = top + 'px';
|
||||||
|
|
||||||
|
anime({
|
||||||
|
targets: this.$refs.backdrop,
|
||||||
|
opacity: 1,
|
||||||
|
duration: 100,
|
||||||
|
easing: 'linear'
|
||||||
|
});
|
||||||
|
|
||||||
|
anime({
|
||||||
|
targets: this.$refs.popover,
|
||||||
|
opacity: 1,
|
||||||
|
scale: [0.5, 1],
|
||||||
|
duration: 500
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clicked(fn) {
|
||||||
|
fn();
|
||||||
|
this.close();
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
(this.$refs.backdrop as any).style.pointerEvents = 'none';
|
||||||
|
anime({
|
||||||
|
targets: this.$refs.backdrop,
|
||||||
|
opacity: 0,
|
||||||
|
duration: 200,
|
||||||
|
easing: 'linear'
|
||||||
|
});
|
||||||
|
|
||||||
|
(this.$refs.popover as any).style.pointerEvents = 'none';
|
||||||
|
anime({
|
||||||
|
targets: this.$refs.popover,
|
||||||
|
opacity: 0,
|
||||||
|
scale: 0.5,
|
||||||
|
duration: 200,
|
||||||
|
easing: 'easeInBack',
|
||||||
|
complete: () => {
|
||||||
|
this.$emit('closed');
|
||||||
|
this.$destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
$border-color = rgba(27, 31, 35, 0.15)
|
||||||
|
|
||||||
|
.mk-menu
|
||||||
|
position initial
|
||||||
|
|
||||||
|
> .backdrop
|
||||||
|
position fixed
|
||||||
|
top 0
|
||||||
|
left 0
|
||||||
|
z-index 10000
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
background rgba(#000, 0.1)
|
||||||
|
opacity 0
|
||||||
|
|
||||||
|
> .popover
|
||||||
|
position absolute
|
||||||
|
z-index 10001
|
||||||
|
padding 8px 0
|
||||||
|
background #fff
|
||||||
|
border 1px solid $border-color
|
||||||
|
border-radius 4px
|
||||||
|
box-shadow 0 3px 12px rgba(27, 31, 35, 0.15)
|
||||||
|
transform scale(0.5)
|
||||||
|
opacity 0
|
||||||
|
|
||||||
|
$balloon-size = 16px
|
||||||
|
|
||||||
|
&.hukidasi
|
||||||
|
margin-top $balloon-size
|
||||||
|
transform-origin center -($balloon-size)
|
||||||
|
|
||||||
|
&:before
|
||||||
|
&:after
|
||||||
|
content ""
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
&:before
|
||||||
|
top -($balloon-size * 2)
|
||||||
|
left s('calc(50% - %s)', $balloon-size)
|
||||||
|
border-top solid $balloon-size transparent
|
||||||
|
border-left solid $balloon-size transparent
|
||||||
|
border-right solid $balloon-size transparent
|
||||||
|
border-bottom solid $balloon-size $border-color
|
||||||
|
|
||||||
|
&:after
|
||||||
|
top -($balloon-size * 2) + 1.5px
|
||||||
|
left s('calc(50% - %s)', $balloon-size)
|
||||||
|
border-top solid $balloon-size transparent
|
||||||
|
border-left solid $balloon-size transparent
|
||||||
|
border-right solid $balloon-size transparent
|
||||||
|
border-bottom solid $balloon-size #fff
|
||||||
|
|
||||||
|
> button
|
||||||
|
display block
|
||||||
|
padding 8px 16px
|
||||||
|
width 100%
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
color $theme-color-foreground
|
||||||
|
background $theme-color
|
||||||
|
text-decoration none
|
||||||
|
|
||||||
|
&:active
|
||||||
|
color $theme-color-foreground
|
||||||
|
background darken($theme-color, 10%)
|
||||||
|
|
||||||
|
> div
|
||||||
|
margin 8px 0
|
||||||
|
height 1px
|
||||||
|
background #eee
|
||||||
|
|
||||||
|
</style>
|
|
@ -8,7 +8,7 @@
|
||||||
<img src="/assets/desktop/messaging/delete.png" alt="Delete"/>
|
<img src="/assets/desktop/messaging/delete.png" alt="Delete"/>
|
||||||
</button>
|
</button>
|
||||||
<div class="content" v-if="!message.isDeleted">
|
<div class="content" v-if="!message.isDeleted">
|
||||||
<mk-note-html class="text" v-if="message.text" ref="text" :text="message.text" :i="os.i"/>
|
<mk-note-html class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/>
|
||||||
<div class="file" v-if="message.file">
|
<div class="file" v-if="message.file">
|
||||||
<a :href="message.file.url" target="_blank" :title="message.file.name">
|
<a :href="message.file.url" target="_blank" :title="message.file.name">
|
||||||
<img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/>
|
<img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/>
|
||||||
|
@ -42,7 +42,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isMe(): boolean {
|
isMe(): boolean {
|
||||||
return this.message.userId == (this as any).os.i.id;
|
return this.message.userId == this.$store.state.i.id;
|
||||||
},
|
},
|
||||||
urls(): string[] {
|
urls(): string[] {
|
||||||
if (this.message.text) {
|
if (this.message.text) {
|
||||||
|
|
|
@ -72,7 +72,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.connection = new MessagingStream((this as any).os, (this as any).os.i, this.user.id);
|
this.connection = new MessagingStream((this as any).os, this.$store.state.i, this.user.id);
|
||||||
|
|
||||||
this.connection.on('message', this.onMessage);
|
this.connection.on('message', this.onMessage);
|
||||||
this.connection.on('read', this.onRead);
|
this.connection.on('read', this.onRead);
|
||||||
|
@ -164,7 +164,7 @@ export default Vue.extend({
|
||||||
const isBottom = this.isBottom();
|
const isBottom = this.isBottom();
|
||||||
|
|
||||||
this.messages.push(message);
|
this.messages.push(message);
|
||||||
if (message.userId != (this as any).os.i.id && !document.hidden) {
|
if (message.userId != this.$store.state.i.id && !document.hidden) {
|
||||||
this.connection.send({
|
this.connection.send({
|
||||||
type: 'read',
|
type: 'read',
|
||||||
id: message.id
|
id: message.id
|
||||||
|
@ -176,7 +176,7 @@ export default Vue.extend({
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
});
|
});
|
||||||
} else if (message.userId != (this as any).os.i.id) {
|
} else if (message.userId != this.$store.state.i.id) {
|
||||||
// Notify
|
// Notify
|
||||||
this.notifyNewMessage();
|
this.notifyNewMessage();
|
||||||
}
|
}
|
||||||
|
@ -229,7 +229,7 @@ export default Vue.extend({
|
||||||
onVisibilitychange() {
|
onVisibilitychange() {
|
||||||
if (document.hidden) return;
|
if (document.hidden) return;
|
||||||
this.messages.forEach(message => {
|
this.messages.forEach(message => {
|
||||||
if (message.userId !== (this as any).os.i.id && !message.isRead) {
|
if (message.userId !== this.$store.state.i.id && !message.isRead) {
|
||||||
this.connection.send({
|
this.connection.send({
|
||||||
type: 'read',
|
type: 'read',
|
||||||
id: message.id
|
id: message.id
|
||||||
|
|
|
@ -95,7 +95,7 @@ export default Vue.extend({
|
||||||
methods: {
|
methods: {
|
||||||
getAcct,
|
getAcct,
|
||||||
isMe(message) {
|
isMe(message) {
|
||||||
return message.userId == (this as any).os.i.id;
|
return message.userId == this.$store.state.i.id;
|
||||||
},
|
},
|
||||||
onMessage(message) {
|
onMessage(message) {
|
||||||
this.messages = this.messages.filter(m => !(
|
this.messages = this.messages.filter(m => !(
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
<template>
|
||||||
|
<header class="bvonvjxbwzaiskogyhbwgyxvcgserpmu">
|
||||||
|
<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle == 'smart'"/>
|
||||||
|
<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
|
||||||
|
<span class="is-admin" v-if="note.user.isAdmin">admin</span>
|
||||||
|
<span class="is-bot" v-if="note.user.isBot">bot</span>
|
||||||
|
<span class="is-cat" v-if="note.user.isCat">cat</span>
|
||||||
|
<span class="username"><mk-acct :user="note.user"/></span>
|
||||||
|
<div class="info">
|
||||||
|
<span class="app" v-if="note.app && !mini">via <b>{{ note.app.name }}</b></span>
|
||||||
|
<span class="mobile" v-if="note.viaMobile">%fa:mobile-alt%</span>
|
||||||
|
<router-link class="created-at" :to="note | notePage">
|
||||||
|
<mk-time :time="note.createdAt"/>
|
||||||
|
</router-link>
|
||||||
|
<span class="visibility" v-if="note.visibility != 'public'">
|
||||||
|
<template v-if="note.visibility == 'home'">%fa:home%</template>
|
||||||
|
<template v-if="note.visibility == 'followers'">%fa:unlock%</template>
|
||||||
|
<template v-if="note.visibility == 'specified'">%fa:envelope%</template>
|
||||||
|
<template v-if="note.visibility == 'private'">%fa:lock%</template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
note: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
mini: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
root(isDark)
|
||||||
|
display flex
|
||||||
|
align-items baseline
|
||||||
|
white-space nowrap
|
||||||
|
|
||||||
|
> .avatar
|
||||||
|
flex-shrink 0
|
||||||
|
margin-right 8px
|
||||||
|
width 20px
|
||||||
|
height 20px
|
||||||
|
border-radius 100%
|
||||||
|
|
||||||
|
> .name
|
||||||
|
display block
|
||||||
|
margin 0 .5em 0 0
|
||||||
|
padding 0
|
||||||
|
overflow hidden
|
||||||
|
color isDark ? #fff : #627079
|
||||||
|
font-size 1em
|
||||||
|
font-weight bold
|
||||||
|
text-decoration none
|
||||||
|
text-overflow ellipsis
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
text-decoration underline
|
||||||
|
|
||||||
|
> .is-admin
|
||||||
|
> .is-bot
|
||||||
|
> .is-cat
|
||||||
|
align-self center
|
||||||
|
margin 0 .5em 0 0
|
||||||
|
padding 1px 6px
|
||||||
|
font-size 80%
|
||||||
|
color isDark ? #758188 : #aaa
|
||||||
|
border solid 1px isDark ? #57616f : #ddd
|
||||||
|
border-radius 3px
|
||||||
|
|
||||||
|
&.is-admin
|
||||||
|
border-color isDark ? #d42c41 : #f56a7b
|
||||||
|
color isDark ? #d42c41 : #f56a7b
|
||||||
|
|
||||||
|
> .username
|
||||||
|
margin 0 .5em 0 0
|
||||||
|
overflow hidden
|
||||||
|
text-overflow ellipsis
|
||||||
|
color isDark ? #606984 : #ccc
|
||||||
|
|
||||||
|
> .info
|
||||||
|
margin-left auto
|
||||||
|
font-size 0.9em
|
||||||
|
|
||||||
|
> *
|
||||||
|
color isDark ? #606984 : #c0c0c0
|
||||||
|
|
||||||
|
> .mobile
|
||||||
|
margin-right 8px
|
||||||
|
|
||||||
|
> .app
|
||||||
|
margin-right 8px
|
||||||
|
padding-right 8px
|
||||||
|
border-right solid 1px isDark ? #1c2023 : #eaeaea
|
||||||
|
|
||||||
|
> .visibility
|
||||||
|
margin-left 8px
|
||||||
|
|
||||||
|
.bvonvjxbwzaiskogyhbwgyxvcgserpmu[data-darkmode]
|
||||||
|
root(true)
|
||||||
|
|
||||||
|
.bvonvjxbwzaiskogyhbwgyxvcgserpmu:not([data-darkmode])
|
||||||
|
root(false)
|
||||||
|
|
||||||
|
</style>
|
|
@ -40,6 +40,17 @@ export default Vue.component('mk-note-html', {
|
||||||
ast = this.ast;
|
ast = this.ast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ast.filter(x => x.type != 'hashtag').length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (ast[ast.length - 1] && (
|
||||||
|
ast[ast.length - 1].type == 'hashtag' ||
|
||||||
|
(ast[ast.length - 1].type == 'text' && ast[ast.length - 1].content == ' ') ||
|
||||||
|
(ast[ast.length - 1].type == 'text' && ast[ast.length - 1].content == '\n'))) {
|
||||||
|
ast.pop();
|
||||||
|
}
|
||||||
|
|
||||||
// Parse ast to DOM
|
// Parse ast to DOM
|
||||||
const els = flatten(ast.map(token => {
|
const els = flatten(ast.map(token => {
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
|
@ -92,7 +103,7 @@ export default Vue.component('mk-note-html', {
|
||||||
case 'hashtag':
|
case 'hashtag':
|
||||||
return createElement('a', {
|
return createElement('a', {
|
||||||
attrs: {
|
attrs: {
|
||||||
href: `${url}/search?q=${token.content}`,
|
href: `${url}/tags/${token.hashtag}`,
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
}
|
}
|
||||||
}, token.content);
|
}, token.content);
|
||||||
|
|
|
@ -1,54 +1,45 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-note-menu">
|
<div style="position:initial">
|
||||||
<div class="backdrop" ref="backdrop" @click="close"></div>
|
<mk-menu :source="source" :compact="compact" :items="items" @closed="closed"/>
|
||||||
<div class="popover" :class="{ compact }" ref="popover">
|
|
||||||
<button @click="favorite">%i18n:@favorite%</button>
|
|
||||||
<button v-if="note.userId == os.i.id" @click="pin">%i18n:@pin%</button>
|
|
||||||
<a v-if="note.uri" :href="note.uri" target="_blank">%i18n:@remote%</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import * as anime from 'animejs';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['note', 'source', 'compact'],
|
props: ['note', 'source', 'compact'],
|
||||||
mounted() {
|
computed: {
|
||||||
this.$nextTick(() => {
|
items() {
|
||||||
const popover = this.$refs.popover as any;
|
const items = [];
|
||||||
|
items.push({
|
||||||
const rect = this.source.getBoundingClientRect();
|
icon: '%fa:star%',
|
||||||
const width = popover.offsetWidth;
|
text: '%i18n:@favorite%',
|
||||||
const height = popover.offsetHeight;
|
action: this.favorite
|
||||||
|
});
|
||||||
if (this.compact) {
|
if (this.note.userId == this.$store.state.i.id) {
|
||||||
const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
|
items.push({
|
||||||
const y = rect.top + window.pageYOffset + (this.source.offsetHeight / 2);
|
icon: '%fa:thumbtack%',
|
||||||
popover.style.left = (x - (width / 2)) + 'px';
|
text: '%i18n:@pin%',
|
||||||
popover.style.top = (y - (height / 2)) + 'px';
|
action: this.pin
|
||||||
} else {
|
});
|
||||||
const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
|
items.push({
|
||||||
const y = rect.top + window.pageYOffset + this.source.offsetHeight;
|
icon: '%fa:trash-alt R%',
|
||||||
popover.style.left = (x - (width / 2)) + 'px';
|
text: '%i18n:@delete%',
|
||||||
popover.style.top = y + 'px';
|
action: this.del
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
if (this.note.uri) {
|
||||||
anime({
|
items.push({
|
||||||
targets: this.$refs.backdrop,
|
icon: '%fa:external-link-square-alt%',
|
||||||
opacity: 1,
|
text: '%i18n:@remote%',
|
||||||
duration: 100,
|
action: () => {
|
||||||
easing: 'linear'
|
window.open(this.note.uri, '_blank');
|
||||||
});
|
}
|
||||||
|
});
|
||||||
anime({
|
}
|
||||||
targets: this.$refs.popover,
|
return items;
|
||||||
opacity: 1,
|
}
|
||||||
scale: [0.5, 1],
|
|
||||||
duration: 500
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
pin() {
|
pin() {
|
||||||
|
@ -59,6 +50,15 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
del() {
|
||||||
|
if (!window.confirm('%i18n:@delete-confirm%')) return;
|
||||||
|
(this as any).api('notes/delete', {
|
||||||
|
noteId: this.note.id
|
||||||
|
}).then(() => {
|
||||||
|
this.$destroy();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
favorite() {
|
favorite() {
|
||||||
(this as any).api('notes/favorites/create', {
|
(this as any).api('notes/favorites/create', {
|
||||||
noteId: this.note.id
|
noteId: this.note.id
|
||||||
|
@ -67,99 +67,11 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
close() {
|
closed() {
|
||||||
(this.$refs.backdrop as any).style.pointerEvents = 'none';
|
this.$nextTick(() => {
|
||||||
anime({
|
this.$destroy();
|
||||||
targets: this.$refs.backdrop,
|
|
||||||
opacity: 0,
|
|
||||||
duration: 200,
|
|
||||||
easing: 'linear'
|
|
||||||
});
|
|
||||||
|
|
||||||
(this.$refs.popover as any).style.pointerEvents = 'none';
|
|
||||||
anime({
|
|
||||||
targets: this.$refs.popover,
|
|
||||||
opacity: 0,
|
|
||||||
scale: 0.5,
|
|
||||||
duration: 200,
|
|
||||||
easing: 'easeInBack',
|
|
||||||
complete: () => this.$destroy()
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
@import '~const.styl'
|
|
||||||
|
|
||||||
$border-color = rgba(27, 31, 35, 0.15)
|
|
||||||
|
|
||||||
.mk-note-menu
|
|
||||||
position initial
|
|
||||||
|
|
||||||
> .backdrop
|
|
||||||
position fixed
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
z-index 10000
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
background rgba(#000, 0.1)
|
|
||||||
opacity 0
|
|
||||||
|
|
||||||
> .popover
|
|
||||||
position absolute
|
|
||||||
z-index 10001
|
|
||||||
padding 8px 0
|
|
||||||
background #fff
|
|
||||||
border 1px solid $border-color
|
|
||||||
border-radius 4px
|
|
||||||
box-shadow 0 3px 12px rgba(27, 31, 35, 0.15)
|
|
||||||
transform scale(0.5)
|
|
||||||
opacity 0
|
|
||||||
|
|
||||||
$balloon-size = 16px
|
|
||||||
|
|
||||||
&:not(.compact)
|
|
||||||
margin-top $balloon-size
|
|
||||||
transform-origin center -($balloon-size)
|
|
||||||
|
|
||||||
&:before
|
|
||||||
content ""
|
|
||||||
display block
|
|
||||||
position absolute
|
|
||||||
top -($balloon-size * 2)
|
|
||||||
left s('calc(50% - %s)', $balloon-size)
|
|
||||||
border-top solid $balloon-size transparent
|
|
||||||
border-left solid $balloon-size transparent
|
|
||||||
border-right solid $balloon-size transparent
|
|
||||||
border-bottom solid $balloon-size $border-color
|
|
||||||
|
|
||||||
&:after
|
|
||||||
content ""
|
|
||||||
display block
|
|
||||||
position absolute
|
|
||||||
top -($balloon-size * 2) + 1.5px
|
|
||||||
left s('calc(50% - %s)', $balloon-size)
|
|
||||||
border-top solid $balloon-size transparent
|
|
||||||
border-left solid $balloon-size transparent
|
|
||||||
border-right solid $balloon-size transparent
|
|
||||||
border-bottom solid $balloon-size #fff
|
|
||||||
|
|
||||||
> button
|
|
||||||
> a
|
|
||||||
display block
|
|
||||||
padding 8px 16px
|
|
||||||
width 100%
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
color $theme-color-foreground
|
|
||||||
background $theme-color
|
|
||||||
text-decoration none
|
|
||||||
|
|
||||||
&:active
|
|
||||||
color $theme-color-foreground
|
|
||||||
background darken($theme-color, 10%)
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import * as CRC32 from 'crc-32';
|
import * as CRC32 from 'crc-32';
|
||||||
import Othello, { Color } from '../../../../../othello/core';
|
import Reversi, { Color } from '../../../../../reversi/core';
|
||||||
import { url } from '../../../config';
|
import { url } from '../../../config';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
|
@ -52,7 +52,7 @@ export default Vue.extend({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
game: null,
|
game: null,
|
||||||
o: null as Othello,
|
o: null as Reversi,
|
||||||
logs: [],
|
logs: [],
|
||||||
logPos: 0,
|
logPos: 0,
|
||||||
pollingClock: null
|
pollingClock: null
|
||||||
|
@ -61,13 +61,13 @@ export default Vue.extend({
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
iAmPlayer(): boolean {
|
iAmPlayer(): boolean {
|
||||||
if (!(this as any).os.isSignedIn) return false;
|
if (!this.$store.getters.isSignedIn) return false;
|
||||||
return this.game.user1Id == (this as any).os.i.id || this.game.user2Id == (this as any).os.i.id;
|
return this.game.user1Id == this.$store.state.i.id || this.game.user2Id == this.$store.state.i.id;
|
||||||
},
|
},
|
||||||
myColor(): Color {
|
myColor(): Color {
|
||||||
if (!this.iAmPlayer) return null;
|
if (!this.iAmPlayer) return null;
|
||||||
if (this.game.user1Id == (this as any).os.i.id && this.game.black == 1) return true;
|
if (this.game.user1Id == this.$store.state.i.id && this.game.black == 1) return true;
|
||||||
if (this.game.user2Id == (this as any).os.i.id && this.game.black == 2) return true;
|
if (this.game.user2Id == this.$store.state.i.id && this.game.black == 2) return true;
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
opColor(): Color {
|
opColor(): Color {
|
||||||
|
@ -91,14 +91,14 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
isMyTurn(): boolean {
|
isMyTurn(): boolean {
|
||||||
if (this.turnUser == null) return null;
|
if (this.turnUser == null) return null;
|
||||||
return this.turnUser.id == (this as any).os.i.id;
|
return this.turnUser.id == this.$store.state.i.id;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
logPos(v) {
|
logPos(v) {
|
||||||
if (!this.game.isEnded) return;
|
if (!this.game.isEnded) return;
|
||||||
this.o = new Othello(this.game.settings.map, {
|
this.o = new Reversi(this.game.settings.map, {
|
||||||
isLlotheo: this.game.settings.isLlotheo,
|
isLlotheo: this.game.settings.isLlotheo,
|
||||||
canPutEverywhere: this.game.settings.canPutEverywhere,
|
canPutEverywhere: this.game.settings.canPutEverywhere,
|
||||||
loopedBoard: this.game.settings.loopedBoard
|
loopedBoard: this.game.settings.loopedBoard
|
||||||
|
@ -115,7 +115,7 @@ export default Vue.extend({
|
||||||
created() {
|
created() {
|
||||||
this.game = this.initGame;
|
this.game = this.initGame;
|
||||||
|
|
||||||
this.o = new Othello(this.game.settings.map, {
|
this.o = new Reversi(this.game.settings.map, {
|
||||||
isLlotheo: this.game.settings.isLlotheo,
|
isLlotheo: this.game.settings.isLlotheo,
|
||||||
canPutEverywhere: this.game.settings.canPutEverywhere,
|
canPutEverywhere: this.game.settings.canPutEverywhere,
|
||||||
loopedBoard: this.game.settings.loopedBoard
|
loopedBoard: this.game.settings.loopedBoard
|
||||||
|
@ -163,7 +163,7 @@ export default Vue.extend({
|
||||||
|
|
||||||
// サウンドを再生する
|
// サウンドを再生する
|
||||||
if (this.$store.state.device.enableSounds) {
|
if (this.$store.state.device.enableSounds) {
|
||||||
const sound = new Audio(`${url}/assets/othello-put-me.mp3`);
|
const sound = new Audio(`${url}/assets/reversi-put-me.mp3`);
|
||||||
sound.volume = this.$store.state.device.soundVolume;
|
sound.volume = this.$store.state.device.soundVolume;
|
||||||
sound.play();
|
sound.play();
|
||||||
}
|
}
|
||||||
|
@ -187,7 +187,7 @@ export default Vue.extend({
|
||||||
|
|
||||||
// サウンドを再生する
|
// サウンドを再生する
|
||||||
if (this.$store.state.device.enableSounds && x.color != this.myColor) {
|
if (this.$store.state.device.enableSounds && x.color != this.myColor) {
|
||||||
const sound = new Audio(`${url}/assets/othello-put-you.mp3`);
|
const sound = new Audio(`${url}/assets/reversi-put-you.mp3`);
|
||||||
sound.volume = this.$store.state.device.soundVolume;
|
sound.volume = this.$store.state.device.soundVolume;
|
||||||
sound.play();
|
sound.play();
|
||||||
}
|
}
|
||||||
|
@ -213,7 +213,7 @@ export default Vue.extend({
|
||||||
onRescue(game) {
|
onRescue(game) {
|
||||||
this.game = game;
|
this.game = game;
|
||||||
|
|
||||||
this.o = new Othello(this.game.settings.map, {
|
this.o = new Reversi(this.game.settings.map, {
|
||||||
isLlotheo: this.game.settings.isLlotheo,
|
isLlotheo: this.game.settings.isLlotheo,
|
||||||
canPutEverywhere: this.game.settings.canPutEverywhere,
|
canPutEverywhere: this.game.settings.canPutEverywhere,
|
||||||
loopedBoard: this.game.settings.loopedBoard
|
loopedBoard: this.game.settings.loopedBoard
|
|
@ -7,9 +7,9 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import XGame from './othello.game.vue';
|
import XGame from './reversi.game.vue';
|
||||||
import XRoom from './othello.room.vue';
|
import XRoom from './reversi.room.vue';
|
||||||
import { OthelloGameStream } from '../../scripts/streaming/othello-game';
|
import { ReversiGameStream } from '../../scripts/streaming/reversi-game';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
|
@ -25,7 +25,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.g = this.game;
|
this.g = this.game;
|
||||||
this.connection = new OthelloGameStream((this as any).os, (this as any).os.i, this.game);
|
this.connection = new ReversiGameStream((this as any).os, this.$store.state.i, this.game);
|
||||||
this.connection.on('started', this.onStarted);
|
this.connection.on('started', this.onStarted);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
|
@ -94,7 +94,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import * as maps from '../../../../../othello/maps';
|
import * as maps from '../../../../../reversi/maps';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['game', 'connection'],
|
props: ['game', 'connection'],
|
||||||
|
@ -116,13 +116,13 @@ export default Vue.extend({
|
||||||
return categories.filter((item, pos) => categories.indexOf(item) == pos);
|
return categories.filter((item, pos) => categories.indexOf(item) == pos);
|
||||||
},
|
},
|
||||||
isAccepted(): boolean {
|
isAccepted(): boolean {
|
||||||
if (this.game.user1Id == (this as any).os.i.id && this.game.user1Accepted) return true;
|
if (this.game.user1Id == this.$store.state.i.id && this.game.user1Accepted) return true;
|
||||||
if (this.game.user2Id == (this as any).os.i.id && this.game.user2Accepted) return true;
|
if (this.game.user2Id == this.$store.state.i.id && this.game.user2Accepted) return true;
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
isOpAccepted(): boolean {
|
isOpAccepted(): boolean {
|
||||||
if (this.game.user1Id != (this as any).os.i.id && this.game.user1Accepted) return true;
|
if (this.game.user1Id != this.$store.state.i.id && this.game.user1Accepted) return true;
|
||||||
if (this.game.user2Id != (this as any).os.i.id && this.game.user2Accepted) return true;
|
if (this.game.user2Id != this.$store.state.i.id && this.game.user2Accepted) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -133,8 +133,8 @@ export default Vue.extend({
|
||||||
this.connection.on('init-form', this.onInitForm);
|
this.connection.on('init-form', this.onInitForm);
|
||||||
this.connection.on('message', this.onMessage);
|
this.connection.on('message', this.onMessage);
|
||||||
|
|
||||||
if (this.game.user1Id != (this as any).os.i.id && this.game.settings.form1) this.form = this.game.settings.form1;
|
if (this.game.user1Id != this.$store.state.i.id && this.game.settings.form1) this.form = this.game.settings.form1;
|
||||||
if (this.game.user2Id != (this as any).os.i.id && this.game.settings.form2) this.form = this.game.settings.form2;
|
if (this.game.user2Id != this.$store.state.i.id && this.game.settings.form2) this.form = this.game.settings.form2;
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
@ -185,12 +185,12 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
onInitForm(x) {
|
onInitForm(x) {
|
||||||
if (x.userId == (this as any).os.i.id) return;
|
if (x.userId == this.$store.state.i.id) return;
|
||||||
this.form = x.form;
|
this.form = x.form;
|
||||||
},
|
},
|
||||||
|
|
||||||
onMessage(x) {
|
onMessage(x) {
|
||||||
if (x.userId == (this as any).os.i.id) return;
|
if (x.userId == this.$store.state.i.id) return;
|
||||||
this.messages.unshift(x.message);
|
this.messages.unshift(x.message);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-othello">
|
<div class="mk-reversi">
|
||||||
<div v-if="game">
|
<div v-if="game">
|
||||||
<x-gameroom :game="game"/>
|
<x-gameroom :game="game"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,14 +11,14 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="index" v-else>
|
<div class="index" v-else>
|
||||||
<h1>Misskey %fa:circle%thell%fa:circle R%</h1>
|
<h1>Misskey %fa:circle%thell%fa:circle R%</h1>
|
||||||
<p>他のMisskeyユーザーとオセロで対戦しよう</p>
|
<p>他のMisskeyユーザーとリバーシで対戦しよう</p>
|
||||||
<div class="play">
|
<div class="play">
|
||||||
<el-button round>フリーマッチ(準備中)</el-button>
|
<el-button round>フリーマッチ(準備中)</el-button>
|
||||||
<el-button type="primary" round @click="match">指名</el-button>
|
<el-button type="primary" round @click="match">指名</el-button>
|
||||||
<details>
|
<details>
|
||||||
<summary>遊び方</summary>
|
<summary>遊び方</summary>
|
||||||
<div>
|
<div>
|
||||||
<p>オセロは、相手と交互に石をボードに置いてゆき、相手の石を挟んでひっくり返しながら、最終的に残った石が多い方が勝ちというボードゲームです。</p>
|
<p>リバーシは、相手と交互に石をボードに置いてゆき、相手の石を挟んでひっくり返しながら、最終的に残った石が多い方が勝ちというボードゲームです。</p>
|
||||||
<dl>
|
<dl>
|
||||||
<dt><b>フリーマッチ</b></dt>
|
<dt><b>フリーマッチ</b></dt>
|
||||||
<dd>ランダムなユーザーと対戦するモードです。</dd>
|
<dd>ランダムなユーザーと対戦するモードです。</dd>
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
</section>
|
</section>
|
||||||
<section v-if="myGames.length > 0">
|
<section v-if="myGames.length > 0">
|
||||||
<h2>自分の対局</h2>
|
<h2>自分の対局</h2>
|
||||||
<a class="game" v-for="g in myGames" tabindex="-1" @click.prevent="go(g)" :href="`/othello/${g.id}`">
|
<a class="game" v-for="g in myGames" tabindex="-1" @click.prevent="go(g)" :href="`/reversi/${g.id}`">
|
||||||
<mk-avatar class="avatar" :user="g.user1"/>
|
<mk-avatar class="avatar" :user="g.user1"/>
|
||||||
<mk-avatar class="avatar" :user="g.user2"/>
|
<mk-avatar class="avatar" :user="g.user2"/>
|
||||||
<span><b>{{ g.user1.name }}</b> vs <b>{{ g.user2.name }}</b></span>
|
<span><b>{{ g.user1.name }}</b> vs <b>{{ g.user2.name }}</b></span>
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
</section>
|
</section>
|
||||||
<section v-if="games.length > 0">
|
<section v-if="games.length > 0">
|
||||||
<h2>みんなの対局</h2>
|
<h2>みんなの対局</h2>
|
||||||
<a class="game" v-for="g in games" tabindex="-1" @click.prevent="go(g)" :href="`/othello/${g.id}`">
|
<a class="game" v-for="g in games" tabindex="-1" @click.prevent="go(g)" :href="`/reversi/${g.id}`">
|
||||||
<mk-avatar class="avatar" :user="g.user1"/>
|
<mk-avatar class="avatar" :user="g.user1"/>
|
||||||
<mk-avatar class="avatar" :user="g.user2"/>
|
<mk-avatar class="avatar" :user="g.user2"/>
|
||||||
<span><b>{{ g.user1.name }}</b> vs <b>{{ g.user2.name }}</b></span>
|
<span><b>{{ g.user1.name }}</b> vs <b>{{ g.user2.name }}</b></span>
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import XGameroom from './othello.gameroom.vue';
|
import XGameroom from './reversi.gameroom.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
|
@ -93,24 +93,24 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.connection = (this as any).os.streams.othelloStream.getConnection();
|
this.connection = (this as any).os.streams.reversiStream.getConnection();
|
||||||
this.connectionId = (this as any).os.streams.othelloStream.use();
|
this.connectionId = (this as any).os.streams.reversiStream.use();
|
||||||
|
|
||||||
this.connection.on('matched', this.onMatched);
|
this.connection.on('matched', this.onMatched);
|
||||||
this.connection.on('invited', this.onInvited);
|
this.connection.on('invited', this.onInvited);
|
||||||
|
|
||||||
(this as any).api('othello/games', {
|
(this as any).api('reversi/games', {
|
||||||
my: true
|
my: true
|
||||||
}).then(games => {
|
}).then(games => {
|
||||||
this.myGames = games;
|
this.myGames = games;
|
||||||
});
|
});
|
||||||
|
|
||||||
(this as any).api('othello/games').then(games => {
|
(this as any).api('reversi/games').then(games => {
|
||||||
this.games = games;
|
this.games = games;
|
||||||
this.gamesFetching = false;
|
this.gamesFetching = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
(this as any).api('othello/invitations').then(invitations => {
|
(this as any).api('reversi/invitations').then(invitations => {
|
||||||
this.invitations = this.invitations.concat(invitations);
|
this.invitations = this.invitations.concat(invitations);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -126,13 +126,13 @@ export default Vue.extend({
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.connection.off('matched', this.onMatched);
|
this.connection.off('matched', this.onMatched);
|
||||||
this.connection.off('invited', this.onInvited);
|
this.connection.off('invited', this.onInvited);
|
||||||
(this as any).os.streams.othelloStream.dispose(this.connectionId);
|
(this as any).os.streams.reversiStream.dispose(this.connectionId);
|
||||||
|
|
||||||
clearInterval(this.pingClock);
|
clearInterval(this.pingClock);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
go(game) {
|
go(game) {
|
||||||
(this as any).api('othello/games/show', {
|
(this as any).api('reversi/games/show', {
|
||||||
gameId: game.id
|
gameId: game.id
|
||||||
}).then(game => {
|
}).then(game => {
|
||||||
this.matching = null;
|
this.matching = null;
|
||||||
|
@ -146,7 +146,7 @@ export default Vue.extend({
|
||||||
(this as any).api('users/show', {
|
(this as any).api('users/show', {
|
||||||
username
|
username
|
||||||
}).then(user => {
|
}).then(user => {
|
||||||
(this as any).api('othello/match', {
|
(this as any).api('reversi/match', {
|
||||||
userId: user.id
|
userId: user.id
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
if (res == null) {
|
if (res == null) {
|
||||||
|
@ -160,10 +160,10 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
cancel() {
|
cancel() {
|
||||||
this.matching = null;
|
this.matching = null;
|
||||||
(this as any).api('othello/match/cancel');
|
(this as any).api('reversi/match/cancel');
|
||||||
},
|
},
|
||||||
accept(invitation) {
|
accept(invitation) {
|
||||||
(this as any).api('othello/match', {
|
(this as any).api('reversi/match', {
|
||||||
userId: invitation.parent.id
|
userId: invitation.parent.id
|
||||||
}).then(game => {
|
}).then(game => {
|
||||||
if (game) {
|
if (game) {
|
||||||
|
@ -186,7 +186,7 @@ export default Vue.extend({
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
@import '~const.styl'
|
||||||
|
|
||||||
.mk-othello
|
.mk-reversi
|
||||||
color #677f84
|
color #677f84
|
||||||
background #fff
|
background #fff
|
||||||
|
|
|
@ -1,24 +1,33 @@
|
||||||
<template>
|
<template>
|
||||||
<form class="mk-signin" :class="{ signing }" @submit.prevent="onSubmit">
|
<form class="mk-signin" :class="{ signing }" @submit.prevent="onSubmit">
|
||||||
<label class="user-name">
|
<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div>
|
||||||
<input v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" placeholder="%i18n:@username%" autofocus required @change="onUsernameChange"/>%fa:at%
|
<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @input="onUsernameChange">
|
||||||
</label>
|
<span>%i18n:@username%</span>
|
||||||
<label class="password">
|
<span slot="prefix">@</span>
|
||||||
<input v-model="password" type="password" placeholder="%i18n:@password%" required/>%fa:lock%
|
<span slot="suffix">@{{ host }}</span>
|
||||||
</label>
|
</ui-input>
|
||||||
<label class="token" v-if="user && user.twoFactorEnabled">
|
<ui-input v-model="password" type="password" required>
|
||||||
<input v-model="token" type="number" placeholder="%i18n:@token%" required/>%fa:lock%
|
<span>%i18n:@password%</span>
|
||||||
</label>
|
<span slot="prefix">%fa:lock%</span>
|
||||||
<button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</button>
|
</ui-input>
|
||||||
もしくは <a :href="`${apiUrl}/signin/twitter`">Twitterでログイン</a>
|
<ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required/>
|
||||||
|
<ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button>
|
||||||
|
<p style="margin: 8px 0;">または<a :href="`${apiUrl}/signin/twitter`">Twitterでログイン</a></p>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { apiUrl } from '../../../config';
|
import { apiUrl, host } from '../../../config';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
withAvatar: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
signing: false,
|
signing: false,
|
||||||
|
@ -27,6 +36,7 @@ export default Vue.extend({
|
||||||
password: '',
|
password: '',
|
||||||
token: '',
|
token: '',
|
||||||
apiUrl,
|
apiUrl,
|
||||||
|
host
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -35,6 +45,8 @@ export default Vue.extend({
|
||||||
username: this.username
|
username: this.username
|
||||||
}).then(user => {
|
}).then(user => {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
}, () => {
|
||||||
|
this.user = null;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
|
@ -59,84 +71,19 @@ export default Vue.extend({
|
||||||
@import '~const.styl'
|
@import '~const.styl'
|
||||||
|
|
||||||
.mk-signin
|
.mk-signin
|
||||||
|
color #555
|
||||||
|
|
||||||
&.signing
|
&.signing
|
||||||
&, *
|
&, *
|
||||||
cursor wait !important
|
cursor wait !important
|
||||||
|
|
||||||
label
|
> .avatar
|
||||||
display block
|
margin 16px auto 0 auto
|
||||||
margin 12px 0
|
width 64px
|
||||||
|
height 64px
|
||||||
[data-fa]
|
background #ddd
|
||||||
display block
|
background-position center
|
||||||
pointer-events none
|
background-size cover
|
||||||
position absolute
|
border-radius 100%
|
||||||
bottom 0
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
z-index 1
|
|
||||||
margin auto
|
|
||||||
padding 0 16px
|
|
||||||
height 1em
|
|
||||||
color #898786
|
|
||||||
|
|
||||||
input[type=text]
|
|
||||||
input[type=password]
|
|
||||||
input[type=number]
|
|
||||||
user-select text
|
|
||||||
display inline-block
|
|
||||||
cursor auto
|
|
||||||
padding 0 0 0 38px
|
|
||||||
margin 0
|
|
||||||
width 100%
|
|
||||||
line-height 44px
|
|
||||||
font-size 1em
|
|
||||||
color rgba(#000, 0.7)
|
|
||||||
background #fff
|
|
||||||
outline none
|
|
||||||
border solid 1px #eee
|
|
||||||
border-radius 4px
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
background rgba(255, 255, 255, 0.7)
|
|
||||||
border-color #ddd
|
|
||||||
|
|
||||||
& + i
|
|
||||||
color #797776
|
|
||||||
|
|
||||||
&:focus
|
|
||||||
background #fff
|
|
||||||
border-color #ccc
|
|
||||||
|
|
||||||
& + i
|
|
||||||
color #797776
|
|
||||||
|
|
||||||
[type=submit]
|
|
||||||
cursor pointer
|
|
||||||
padding 16px
|
|
||||||
margin -6px 0 0 0
|
|
||||||
width 100%
|
|
||||||
font-size 1.2em
|
|
||||||
color rgba(#000, 0.5)
|
|
||||||
outline none
|
|
||||||
border none
|
|
||||||
border-radius 0
|
|
||||||
background transparent
|
|
||||||
transition all .5s ease
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
color $theme-color
|
|
||||||
transition all .2s ease
|
|
||||||
|
|
||||||
&:focus
|
|
||||||
color $theme-color
|
|
||||||
transition all .2s ease
|
|
||||||
|
|
||||||
&:active
|
|
||||||
color darken($theme-color, 30%)
|
|
||||||
transition all .2s ease
|
|
||||||
|
|
||||||
&:disabled
|
|
||||||
opacity 0.7
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,60 +1,58 @@
|
||||||
<template>
|
<template>
|
||||||
<form class="mk-signup" @submit.prevent="onSubmit" autocomplete="off">
|
<form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()">
|
||||||
<label class="username">
|
<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername">
|
||||||
<p class="caption">%fa:at%%i18n:@username%</p>
|
<span>%i18n:@username%</span>
|
||||||
<input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" placeholder="a~z、A~Z、0~9、-" autocomplete="off" required @input="onChangeUsername"/>
|
<span slot="prefix">@</span>
|
||||||
<p class="profile-page-url-preview" v-if="shouldShowProfileUrl">{{ `${url}/@${username}` }}</p>
|
<span slot="suffix">@{{ host }}</span>
|
||||||
<p class="info" v-if="usernameState == 'wait'" style="color:#999">%fa:spinner .pulse .fw%%i18n:@checking%</p>
|
<p slot="text" v-if="usernameState == 'wait'" style="color:#999">%fa:spinner .pulse .fw% %i18n:@checking%</p>
|
||||||
<p class="info" v-if="usernameState == 'ok'" style="color:#3CB7B5">%fa:check .fw%%i18n:@available%</p>
|
<p slot="text" v-if="usernameState == 'ok'" style="color:#3CB7B5">%fa:check .fw% %i18n:@available%</p>
|
||||||
<p class="info" v-if="usernameState == 'unavailable'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@unavailable%</p>
|
<p slot="text" v-if="usernameState == 'unavailable'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@unavailable%</p>
|
||||||
<p class="info" v-if="usernameState == 'error'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@error%</p>
|
<p slot="text" v-if="usernameState == 'error'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@error%</p>
|
||||||
<p class="info" v-if="usernameState == 'invalid-format'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@invalid-format%</p>
|
<p slot="text" v-if="usernameState == 'invalid-format'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@invalid-format%</p>
|
||||||
<p class="info" v-if="usernameState == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@too-short%</p>
|
<p slot="text" v-if="usernameState == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-short%</p>
|
||||||
<p class="info" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@too-long%</p>
|
<p slot="text" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-long%</p>
|
||||||
</label>
|
</ui-input>
|
||||||
<label class="password">
|
<ui-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword" :with-password-meter="true">
|
||||||
<p class="caption">%fa:lock%%i18n:@password%</p>
|
<span>%i18n:@password%</span>
|
||||||
<input v-model="password" type="password" placeholder="%i18n:@password-placeholder%" autocomplete="off" required @input="onChangePassword"/>
|
<span slot="prefix">%fa:lock%</span>
|
||||||
<div class="meter" v-show="passwordStrength != ''" :data-strength="passwordStrength">
|
<div slot="text">
|
||||||
<div class="value" ref="passwordMetar"></div>
|
<p slot="text" v-if="passwordStrength == 'low'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@weak-password%</p>
|
||||||
|
<p slot="text" v-if="passwordStrength == 'medium'" style="color:#3CB7B5">%fa:check .fw% %i18n:@normal-password%</p>
|
||||||
|
<p slot="text" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw% %i18n:@strong-password%</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="info" v-if="passwordStrength == 'low'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@weak-password%</p>
|
</ui-input>
|
||||||
<p class="info" v-if="passwordStrength == 'medium'" style="color:#3CB7B5">%fa:check .fw%%i18n:@normal-password%</p>
|
<ui-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype">
|
||||||
<p class="info" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw%%i18n:@strong-password%</p>
|
<span>%i18n:@password% (%i18n:@retype%)</span>
|
||||||
</label>
|
<span slot="prefix">%fa:lock%</span>
|
||||||
<label class="retype-password">
|
<div slot="text">
|
||||||
<p class="caption">%fa:lock%%i18n:@password%(%i18n:@retype%)</p>
|
<p slot="text" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5">%fa:check .fw% %i18n:@password-matched%</p>
|
||||||
<input v-model="retypedPassword" type="password" placeholder="%i18n:@retype-placeholder%" autocomplete="off" required @input="onChangePasswordRetype"/>
|
<p slot="text" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@password-not-matched%</p>
|
||||||
<p class="info" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5">%fa:check .fw%%i18n:@password-matched%</p>
|
</div>
|
||||||
<p class="info" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@password-not-matched%</p>
|
</ui-input>
|
||||||
</label>
|
<div class="g-recaptcha" :data-sitekey="recaptchaSitekey" style="margin: 16px 0;"></div>
|
||||||
<label class="recaptcha">
|
<label class="agree-tou" style="display: block; margin: 16px 0;">
|
||||||
<p class="caption"><template v-if="recaptchaed">%fa:toggle-on%</template><template v-if="!recaptchaed">%fa:toggle-off%</template>%i18n:@recaptcha%</p>
|
<input name="agree-tou" type="checkbox" required/>
|
||||||
<div class="g-recaptcha" data-callback="onRecaptchaed" data-expired-callback="onRecaptchaExpired" :data-sitekey="recaptchaSitekey"></div>
|
|
||||||
</label>
|
|
||||||
<label class="agree-tou">
|
|
||||||
<input name="agree-tou" type="checkbox" autocomplete="off" required/>
|
|
||||||
<p><a :href="touUrl" target="_blank">利用規約</a>に同意する</p>
|
<p><a :href="touUrl" target="_blank">利用規約</a>に同意する</p>
|
||||||
</label>
|
</label>
|
||||||
<button type="submit">%i18n:@create%</button>
|
<ui-button type="submit">%i18n:@create%</ui-button>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
const getPasswordStrength = require('syuilo-password-strength');
|
const getPasswordStrength = require('syuilo-password-strength');
|
||||||
import { url, docsUrl, lang, recaptchaSitekey } from '../../../config';
|
import { host, url, docsUrl, lang, recaptchaSitekey } from '../../../config';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
host,
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
retypedPassword: '',
|
retypedPassword: '',
|
||||||
url,
|
url,
|
||||||
touUrl: `${docsUrl}/${lang}/tou`,
|
touUrl: `${docsUrl}/${lang}/tou`,
|
||||||
recaptchaSitekey,
|
recaptchaSitekey,
|
||||||
recaptchaed: false,
|
|
||||||
usernameState: null,
|
usernameState: null,
|
||||||
passwordStrength: '',
|
passwordStrength: '',
|
||||||
passwordRetypeState: null
|
passwordRetypeState: null
|
||||||
|
@ -104,7 +102,6 @@ export default Vue.extend({
|
||||||
|
|
||||||
const strength = getPasswordStrength(this.password);
|
const strength = getPasswordStrength(this.password);
|
||||||
this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
|
this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
|
||||||
(this.$refs.passwordMetar as any).style.width = `${strength * 100}%`;
|
|
||||||
},
|
},
|
||||||
onChangePasswordRetype() {
|
onChangePasswordRetype() {
|
||||||
if (this.retypedPassword == '') {
|
if (this.retypedPassword == '') {
|
||||||
|
@ -130,19 +127,9 @@ export default Vue.extend({
|
||||||
alert('%i18n:@some-error%');
|
alert('%i18n:@some-error%');
|
||||||
|
|
||||||
(window as any).grecaptcha.reset();
|
(window as any).grecaptcha.reset();
|
||||||
this.recaptchaed = false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
|
||||||
(window as any).onRecaptchaed = () => {
|
|
||||||
this.recaptchaed = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
(window as any).onRecaptchaExpired = () => {
|
|
||||||
this.recaptchaed = false;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
const head = document.getElementsByTagName('head')[0];
|
const head = document.getElementsByTagName('head')[0];
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
|
@ -158,100 +145,6 @@ export default Vue.extend({
|
||||||
.mk-signup
|
.mk-signup
|
||||||
min-width 302px
|
min-width 302px
|
||||||
|
|
||||||
label
|
|
||||||
display block
|
|
||||||
margin 0 0 16px 0
|
|
||||||
|
|
||||||
> .caption
|
|
||||||
margin 0 0 4px 0
|
|
||||||
color #828888
|
|
||||||
font-size 0.95em
|
|
||||||
|
|
||||||
> [data-fa]
|
|
||||||
margin-right 0.25em
|
|
||||||
color #96adac
|
|
||||||
|
|
||||||
> .info
|
|
||||||
display block
|
|
||||||
margin 4px 0
|
|
||||||
font-size 0.8em
|
|
||||||
|
|
||||||
> [data-fa]
|
|
||||||
margin-right 0.3em
|
|
||||||
|
|
||||||
&.username
|
|
||||||
.profile-page-url-preview
|
|
||||||
display block
|
|
||||||
margin 4px 8px 0 4px
|
|
||||||
font-size 0.8em
|
|
||||||
color #888
|
|
||||||
|
|
||||||
&:empty
|
|
||||||
display none
|
|
||||||
|
|
||||||
&:not(:empty) + .info
|
|
||||||
margin-top 0
|
|
||||||
|
|
||||||
&.password
|
|
||||||
.meter
|
|
||||||
display block
|
|
||||||
margin-top 8px
|
|
||||||
width 100%
|
|
||||||
height 8px
|
|
||||||
|
|
||||||
&[data-strength='']
|
|
||||||
display none
|
|
||||||
|
|
||||||
&[data-strength='low']
|
|
||||||
> .value
|
|
||||||
background #d73612
|
|
||||||
|
|
||||||
&[data-strength='medium']
|
|
||||||
> .value
|
|
||||||
background #d7ca12
|
|
||||||
|
|
||||||
&[data-strength='high']
|
|
||||||
> .value
|
|
||||||
background #61bb22
|
|
||||||
|
|
||||||
> .value
|
|
||||||
display block
|
|
||||||
width 0%
|
|
||||||
height 100%
|
|
||||||
background transparent
|
|
||||||
border-radius 4px
|
|
||||||
transition all 0.1s ease
|
|
||||||
|
|
||||||
[type=text], [type=password]
|
|
||||||
user-select text
|
|
||||||
display inline-block
|
|
||||||
cursor auto
|
|
||||||
padding 0 12px
|
|
||||||
margin 0
|
|
||||||
width 100%
|
|
||||||
line-height 44px
|
|
||||||
font-size 1em
|
|
||||||
color #333 !important
|
|
||||||
background #fff !important
|
|
||||||
outline none
|
|
||||||
border solid 1px rgba(#000, 0.1)
|
|
||||||
border-radius 4px
|
|
||||||
box-shadow 0 0 0 114514px #fff inset
|
|
||||||
transition all .3s ease
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
border-color rgba(#000, 0.2)
|
|
||||||
transition all .1s ease
|
|
||||||
|
|
||||||
&:focus
|
|
||||||
color $theme-color !important
|
|
||||||
border-color $theme-color
|
|
||||||
box-shadow 0 0 0 1024px #fff inset, 0 0 0 4px rgba($theme-color, 10%)
|
|
||||||
transition all 0s ease
|
|
||||||
|
|
||||||
&:disabled
|
|
||||||
opacity 0.5
|
|
||||||
|
|
||||||
.agree-tou
|
.agree-tou
|
||||||
padding 4px
|
padding 4px
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
|
@ -269,19 +162,4 @@ export default Vue.extend({
|
||||||
display inline
|
display inline
|
||||||
color #555
|
color #555
|
||||||
|
|
||||||
button
|
|
||||||
margin 0
|
|
||||||
padding 16px
|
|
||||||
width 100%
|
|
||||||
font-size 1em
|
|
||||||
color #fff
|
|
||||||
background $theme-color
|
|
||||||
border-radius 3px
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
background lighten($theme-color, 5%)
|
|
||||||
|
|
||||||
&:active
|
|
||||||
background darken($theme-color, 5%)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -58,18 +58,21 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if (this.mode == 'relative' || this.mode == 'detail') {
|
if (this.mode == 'relative' || this.mode == 'detail') {
|
||||||
this.tick();
|
this.tickId = window.requestAnimationFrame(this.tick);
|
||||||
this.tickId = setInterval(this.tick, 1000);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
if (this.mode === 'relative' || this.mode === 'detail') {
|
if (this.mode === 'relative' || this.mode === 'detail') {
|
||||||
clearInterval(this.tickId);
|
window.clearTimeout(this.tickId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
tick() {
|
tick() {
|
||||||
this.now = new Date();
|
this.now = new Date();
|
||||||
|
|
||||||
|
this.tickId = setTimeout(() => {
|
||||||
|
window.requestAnimationFrame(this.tick);
|
||||||
|
}, 10000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-twitter-setting">
|
<div class="mk-twitter-setting">
|
||||||
<p>%i18n:@description%<a :href="`${docsUrl}/link-to-twitter`" target="_blank">%i18n:@detail%</a></p>
|
<p>%i18n:@description%<a :href="`${docsUrl}/link-to-twitter`" target="_blank">%i18n:@detail%</a></p>
|
||||||
<p class="account" v-if="os.i.twitter" :title="`Twitter ID: ${os.i.twitter.userId}`">%i18n:@connected-to%: <a :href="`https://twitter.com/${os.i.twitter.screenName}`" target="_blank">@{{ os.i.twitter.screenName }}</a></p>
|
<p class="account" v-if="$store.state.i.twitter" :title="`Twitter ID: ${$store.state.i.twitter.userId}`">%i18n:@connected-to%: <a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p>
|
||||||
<p>
|
<p>
|
||||||
<a :href="`${apiUrl}/connect/twitter`" target="_blank" @click.prevent="connect">{{ os.i.twitter ? '%i18n:@reconnect%' : '%i18n:@connect%' }}</a>
|
<a :href="`${apiUrl}/connect/twitter`" target="_blank" @click.prevent="connect">{{ $store.state.i.twitter ? '%i18n:@reconnect%' : '%i18n:@connect%' }}</a>
|
||||||
<span v-if="os.i.twitter"> or </span>
|
<span v-if="$store.state.i.twitter"> or </span>
|
||||||
<a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="os.i.twitter" @click.prevent="disconnect">%i18n:@disconnect%</a>
|
<a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="$store.state.i.twitter" @click.prevent="disconnect">%i18n:@disconnect%</a>
|
||||||
</p>
|
</p>
|
||||||
<p class="id" v-if="os.i.twitter">Twitter ID: {{ os.i.twitter.userId }}</p>
|
<p class="id" v-if="$store.state.i.twitter">Twitter ID: {{ $store.state.i.twitter.userId }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -24,8 +24,8 @@ export default Vue.extend({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$watch('os.i', () => {
|
this.$watch('$store.state.i', () => {
|
||||||
if ((this as any).os.i.twitter) {
|
if (this.$store.state.i.twitter) {
|
||||||
if (this.form) this.form.close();
|
if (this.form) this.form.close();
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
<template>
|
||||||
|
<div class="ui-button" :class="[styl]">
|
||||||
|
<button :type="type" @click="$emit('click')">
|
||||||
|
<slot></slot>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
styl: 'fill'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: {
|
||||||
|
isCardChild: { default: false }
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.isCardChild) {
|
||||||
|
this.styl = 'line';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
root(isDark, fill)
|
||||||
|
> button
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
margin 0
|
||||||
|
padding 0
|
||||||
|
font-weight bold
|
||||||
|
font-size 16px
|
||||||
|
line-height 44px
|
||||||
|
border none
|
||||||
|
border-radius 6px
|
||||||
|
outline none
|
||||||
|
box-shadow none
|
||||||
|
|
||||||
|
if fill
|
||||||
|
color $theme-color-foreground
|
||||||
|
background $theme-color
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background lighten($theme-color, 5%)
|
||||||
|
|
||||||
|
&:active
|
||||||
|
background darken($theme-color, 5%)
|
||||||
|
else
|
||||||
|
color $theme-color
|
||||||
|
background none
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
color darken($theme-color, 5%)
|
||||||
|
|
||||||
|
&:active
|
||||||
|
background rgba($theme-color, 0.3)
|
||||||
|
|
||||||
|
.ui-button[data-darkmode]
|
||||||
|
&.fill
|
||||||
|
root(true, true)
|
||||||
|
&:not(.fill)
|
||||||
|
root(true, false)
|
||||||
|
|
||||||
|
.ui-button:not([data-darkmode])
|
||||||
|
&.fill
|
||||||
|
root(false, true)
|
||||||
|
&:not(.fill)
|
||||||
|
root(false, false)
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<div class="ui-card">
|
||||||
|
<header>
|
||||||
|
<slot name="title"></slot>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
export default Vue.extend({
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
isCardChild: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
root(isDark)
|
||||||
|
margin 16px
|
||||||
|
padding 16px
|
||||||
|
color isDark ? #fff : #000
|
||||||
|
background isDark ? #282C37 : #fff
|
||||||
|
box-shadow 0 3px 1px -2px rgba(#000, 0.2), 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12)
|
||||||
|
|
||||||
|
@media (min-width 500px)
|
||||||
|
padding 32px
|
||||||
|
|
||||||
|
> header
|
||||||
|
font-weight normal
|
||||||
|
font-size 24px
|
||||||
|
color isDark ? #fff : #444
|
||||||
|
|
||||||
|
.ui-card[data-darkmode]
|
||||||
|
root(true)
|
||||||
|
|
||||||
|
.ui-card:not([data-darkmode])
|
||||||
|
root(false)
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,30 @@
|
||||||
|
<template>
|
||||||
|
<div class="ui-form">
|
||||||
|
<fieldset :disabled="disabled">
|
||||||
|
<slot></slot>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
.ui-form
|
||||||
|
> fieldset
|
||||||
|
margin 0
|
||||||
|
padding 0
|
||||||
|
border none
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,350 @@
|
||||||
|
<template>
|
||||||
|
<div class="ui-input" :class="[{ focused, filled }, styl]">
|
||||||
|
<div class="icon" ref="icon"><slot name="icon"></slot></div>
|
||||||
|
<div class="input">
|
||||||
|
<div class="password-meter" v-if="withPasswordMeter" v-show="passwordStrength != ''" :data-strength="passwordStrength">
|
||||||
|
<div class="value" ref="passwordMetar"></div>
|
||||||
|
</div>
|
||||||
|
<span class="label" ref="label"><slot></slot></span>
|
||||||
|
<div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
|
||||||
|
<template v-if="type != 'file'">
|
||||||
|
<input ref="input"
|
||||||
|
:type="type"
|
||||||
|
v-model="v"
|
||||||
|
:required="required"
|
||||||
|
:readonly="readonly"
|
||||||
|
:pattern="pattern"
|
||||||
|
:autocomplete="autocomplete"
|
||||||
|
:spellcheck="spellcheck"
|
||||||
|
@focus="focused = true"
|
||||||
|
@blur="focused = false">
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<input ref="input"
|
||||||
|
type="text"
|
||||||
|
:value="placeholder"
|
||||||
|
readonly
|
||||||
|
@click="chooseFile">
|
||||||
|
<input ref="file"
|
||||||
|
type="file"
|
||||||
|
:value="value"
|
||||||
|
@change="onChangeFile">
|
||||||
|
</template>
|
||||||
|
<div class="suffix" ref="suffix"><slot name="suffix"></slot></div>
|
||||||
|
</div>
|
||||||
|
<div class="text"><slot name="text"></slot></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
const getPasswordStrength = require('syuilo-password-strength');
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
readonly: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
autocomplete: {
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
spellcheck: {
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
withPasswordMeter: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
v: this.value,
|
||||||
|
focused: false,
|
||||||
|
passwordStrength: '',
|
||||||
|
styl: 'fill'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filled(): boolean {
|
||||||
|
return this.v != '' && this.v != null;
|
||||||
|
},
|
||||||
|
placeholder(): string {
|
||||||
|
if (this.type != 'file') return null;
|
||||||
|
if (this.v == null) return null;
|
||||||
|
|
||||||
|
if (typeof this.v == 'string') return this.v;
|
||||||
|
|
||||||
|
if (Array.isArray(this.v)) {
|
||||||
|
return this.v.map(file => file.name).join(', ');
|
||||||
|
} else {
|
||||||
|
return this.v.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(v) {
|
||||||
|
this.v = v;
|
||||||
|
},
|
||||||
|
v(v) {
|
||||||
|
this.$emit('input', v);
|
||||||
|
|
||||||
|
if (this.withPasswordMeter) {
|
||||||
|
if (v == '') {
|
||||||
|
this.passwordStrength = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const strength = getPasswordStrength(v);
|
||||||
|
this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
|
||||||
|
(this.$refs.passwordMetar as any).style.width = `${strength * 100}%`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: {
|
||||||
|
isCardChild: { default: false }
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.isCardChild) {
|
||||||
|
this.styl = 'line';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.$refs.prefix) {
|
||||||
|
this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
|
||||||
|
if (this.$refs.prefix.offsetWidth) {
|
||||||
|
this.$refs.input.style.paddingLeft = this.$refs.prefix.offsetWidth + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.$refs.suffix) {
|
||||||
|
if (this.$refs.suffix.offsetWidth) {
|
||||||
|
this.$refs.input.style.paddingRight = this.$refs.suffix.offsetWidth + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
focus() {
|
||||||
|
this.$refs.input.focus();
|
||||||
|
},
|
||||||
|
chooseFile() {
|
||||||
|
this.$refs.file.click();
|
||||||
|
},
|
||||||
|
onChangeFile() {
|
||||||
|
this.v = Array.from((this.$refs.file as any).files);
|
||||||
|
this.$emit('input', this.v);
|
||||||
|
this.$emit('change', this.v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
root(isDark, fill)
|
||||||
|
margin 32px 0
|
||||||
|
|
||||||
|
> .icon
|
||||||
|
position absolute
|
||||||
|
top 0
|
||||||
|
left 0
|
||||||
|
width 24px
|
||||||
|
text-align center
|
||||||
|
line-height 32px
|
||||||
|
color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
|
||||||
|
|
||||||
|
&:not(:empty) + .input
|
||||||
|
margin-left 28px
|
||||||
|
|
||||||
|
> .input
|
||||||
|
|
||||||
|
if !fill
|
||||||
|
&:before
|
||||||
|
content ''
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
bottom 0
|
||||||
|
left 0
|
||||||
|
right 0
|
||||||
|
height 1px
|
||||||
|
background isDark ? rgba(#fff, 0.7) : rgba(#000, 0.42)
|
||||||
|
|
||||||
|
&:after
|
||||||
|
content ''
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
bottom 0
|
||||||
|
left 0
|
||||||
|
right 0
|
||||||
|
height 2px
|
||||||
|
background $theme-color
|
||||||
|
opacity 0
|
||||||
|
transform scaleX(0.12)
|
||||||
|
transition border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)
|
||||||
|
will-change border opacity transform
|
||||||
|
|
||||||
|
> .password-meter
|
||||||
|
position absolute
|
||||||
|
top 0
|
||||||
|
left 0
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
border-radius 6px
|
||||||
|
overflow hidden
|
||||||
|
opacity 0.3
|
||||||
|
|
||||||
|
&[data-strength='']
|
||||||
|
display none
|
||||||
|
|
||||||
|
&[data-strength='low']
|
||||||
|
> .value
|
||||||
|
background #d73612
|
||||||
|
|
||||||
|
&[data-strength='medium']
|
||||||
|
> .value
|
||||||
|
background #d7ca12
|
||||||
|
|
||||||
|
&[data-strength='high']
|
||||||
|
> .value
|
||||||
|
background #61bb22
|
||||||
|
|
||||||
|
> .value
|
||||||
|
display block
|
||||||
|
width 0%
|
||||||
|
height 100%
|
||||||
|
background transparent
|
||||||
|
border-radius 6px
|
||||||
|
transition all 0.1s ease
|
||||||
|
|
||||||
|
> .label
|
||||||
|
position absolute
|
||||||
|
z-index 1
|
||||||
|
top fill ? 6px : 0
|
||||||
|
left 0
|
||||||
|
pointer-events none
|
||||||
|
transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
|
||||||
|
transition-duration 0.3s
|
||||||
|
font-size 16px
|
||||||
|
line-height 32px
|
||||||
|
color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
|
||||||
|
pointer-events none
|
||||||
|
//will-change transform
|
||||||
|
transform-origin top left
|
||||||
|
transform scale(1)
|
||||||
|
|
||||||
|
> input
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
margin 0
|
||||||
|
padding 0
|
||||||
|
font inherit
|
||||||
|
font-weight fill ? bold : normal
|
||||||
|
font-size 16px
|
||||||
|
line-height 32px
|
||||||
|
color isDark ? #fff : #000
|
||||||
|
background transparent
|
||||||
|
border none
|
||||||
|
border-radius 0
|
||||||
|
outline none
|
||||||
|
box-shadow none
|
||||||
|
|
||||||
|
if fill
|
||||||
|
padding 6px 12px
|
||||||
|
background rgba(#000, 0.035)
|
||||||
|
border-radius 6px
|
||||||
|
|
||||||
|
&[type='file']
|
||||||
|
display none
|
||||||
|
|
||||||
|
> .prefix
|
||||||
|
> .suffix
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
z-index 1
|
||||||
|
top 0
|
||||||
|
font-size 16px
|
||||||
|
line-height fill ? 44px : 32px
|
||||||
|
color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
&:empty
|
||||||
|
display none
|
||||||
|
|
||||||
|
> *
|
||||||
|
display block
|
||||||
|
min-width 16px
|
||||||
|
max-width 150px
|
||||||
|
overflow hidden
|
||||||
|
white-space nowrap
|
||||||
|
text-overflow ellipsis
|
||||||
|
|
||||||
|
> .prefix
|
||||||
|
left 0
|
||||||
|
padding-right 4px
|
||||||
|
|
||||||
|
if fill
|
||||||
|
padding-left 12px
|
||||||
|
|
||||||
|
> .suffix
|
||||||
|
right 0
|
||||||
|
padding-left 4px
|
||||||
|
|
||||||
|
if fill
|
||||||
|
padding-right 12px
|
||||||
|
|
||||||
|
> .text
|
||||||
|
margin 6px 0
|
||||||
|
font-size 13px
|
||||||
|
|
||||||
|
*
|
||||||
|
margin 0
|
||||||
|
|
||||||
|
&.focused
|
||||||
|
> .input
|
||||||
|
if fill
|
||||||
|
background rgba(#000, 0.05)
|
||||||
|
else
|
||||||
|
&:after
|
||||||
|
opacity 1
|
||||||
|
transform scaleX(1)
|
||||||
|
|
||||||
|
> .label
|
||||||
|
color $theme-color
|
||||||
|
|
||||||
|
&.focused
|
||||||
|
&.filled
|
||||||
|
> .input
|
||||||
|
> .label
|
||||||
|
top fill ? -24px : -17px
|
||||||
|
left 0 !important
|
||||||
|
transform scale(0.75)
|
||||||
|
|
||||||
|
.ui-input[data-darkmode]
|
||||||
|
&.fill
|
||||||
|
root(true, true)
|
||||||
|
&:not(.fill)
|
||||||
|
root(true, false)
|
||||||
|
|
||||||
|
.ui-input:not([data-darkmode])
|
||||||
|
&.fill
|
||||||
|
root(false, true)
|
||||||
|
&:not(.fill)
|
||||||
|
root(false, false)
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,120 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="ui-radio"
|
||||||
|
:class="{ disabled, checked }"
|
||||||
|
:aria-checked="checked"
|
||||||
|
:aria-disabled="disabled"
|
||||||
|
@click="toggle"
|
||||||
|
>
|
||||||
|
<input type="radio"
|
||||||
|
:disabled="disabled"
|
||||||
|
>
|
||||||
|
<span class="button">
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
<span class="label"><slot></slot></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
export default Vue.extend({
|
||||||
|
model: {
|
||||||
|
prop: 'model',
|
||||||
|
event: 'change'
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
model: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
checked(): boolean {
|
||||||
|
return this.model === this.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggle() {
|
||||||
|
this.$emit('change', this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
root(isDark)
|
||||||
|
display inline-block
|
||||||
|
margin 32px 32px 32px 0
|
||||||
|
cursor pointer
|
||||||
|
transition all 0.3s
|
||||||
|
|
||||||
|
> *
|
||||||
|
user-select none
|
||||||
|
|
||||||
|
&.disabled
|
||||||
|
opacity 0.6
|
||||||
|
cursor not-allowed
|
||||||
|
|
||||||
|
&.checked
|
||||||
|
> .button
|
||||||
|
border-color $theme-color
|
||||||
|
|
||||||
|
&:after
|
||||||
|
background-color $theme-color
|
||||||
|
transform scale(1)
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
> input
|
||||||
|
position absolute
|
||||||
|
width 0
|
||||||
|
height 0
|
||||||
|
opacity 0
|
||||||
|
margin 0
|
||||||
|
|
||||||
|
> .button
|
||||||
|
position absolute
|
||||||
|
width 20px
|
||||||
|
height 20px
|
||||||
|
background none
|
||||||
|
border solid 2px isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
|
||||||
|
border-radius 100%
|
||||||
|
transition inherit
|
||||||
|
|
||||||
|
&:after
|
||||||
|
content ''
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
top 3px
|
||||||
|
right 3px
|
||||||
|
bottom 3px
|
||||||
|
left 3px
|
||||||
|
border-radius 100%
|
||||||
|
opacity 0
|
||||||
|
transform scale(0)
|
||||||
|
transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
|
||||||
|
|
||||||
|
> .label
|
||||||
|
margin-left 28px
|
||||||
|
display block
|
||||||
|
font-size 16px
|
||||||
|
line-height 20px
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
|
.ui-radio[data-darkmode]
|
||||||
|
root(true)
|
||||||
|
|
||||||
|
.ui-radio:not([data-darkmode])
|
||||||
|
root(false)
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,215 @@
|
||||||
|
<template>
|
||||||
|
<div class="ui-select" :class="[{ focused, filled }, styl]">
|
||||||
|
<div class="icon" ref="icon"><slot name="icon"></slot></div>
|
||||||
|
<div class="input" @click="focus">
|
||||||
|
<span class="label" ref="label"><slot name="label"></slot></span>
|
||||||
|
<div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
|
||||||
|
<select ref="input"
|
||||||
|
:value="v"
|
||||||
|
:required="required"
|
||||||
|
@input="$emit('input', $event.target.value)"
|
||||||
|
@focus="focused = true"
|
||||||
|
@blur="focused = false">
|
||||||
|
<slot></slot>
|
||||||
|
</select>
|
||||||
|
<div class="suffix"><slot name="suffix"></slot></div>
|
||||||
|
</div>
|
||||||
|
<div class="text"><slot name="text"></slot></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
v: this.value,
|
||||||
|
focused: false,
|
||||||
|
styl: 'fill'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filled(): boolean {
|
||||||
|
return this.v != '' && this.v != null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(v) {
|
||||||
|
this.v = v;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: {
|
||||||
|
isCardChild: { default: false }
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.isCardChild) {
|
||||||
|
this.styl = 'line';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.$refs.prefix) {
|
||||||
|
this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
focus() {
|
||||||
|
this.$refs.input.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
root(isDark, fill)
|
||||||
|
margin 32px 0
|
||||||
|
|
||||||
|
> .icon
|
||||||
|
position absolute
|
||||||
|
top 0
|
||||||
|
left 0
|
||||||
|
width 24px
|
||||||
|
text-align center
|
||||||
|
line-height 32px
|
||||||
|
color rgba(#000, 0.54)
|
||||||
|
|
||||||
|
&:not(:empty) + .input
|
||||||
|
margin-left 28px
|
||||||
|
|
||||||
|
> .input
|
||||||
|
display flex
|
||||||
|
|
||||||
|
if fill
|
||||||
|
padding 6px 12px
|
||||||
|
background rgba(#000, 0.035)
|
||||||
|
border-radius 6px
|
||||||
|
else
|
||||||
|
&:before
|
||||||
|
content ''
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
bottom 0
|
||||||
|
left 0
|
||||||
|
right 0
|
||||||
|
height 1px
|
||||||
|
background isDark ? rgba(#fff, 0.7) : rgba(#000, 0.42)
|
||||||
|
|
||||||
|
&:after
|
||||||
|
content ''
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
bottom 0
|
||||||
|
left 0
|
||||||
|
right 0
|
||||||
|
height 2px
|
||||||
|
background $theme-color
|
||||||
|
opacity 0
|
||||||
|
transform scaleX(0.12)
|
||||||
|
transition border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)
|
||||||
|
will-change border opacity transform
|
||||||
|
|
||||||
|
> .label
|
||||||
|
position absolute
|
||||||
|
top fill ? 6px : 0
|
||||||
|
left 0
|
||||||
|
pointer-events none
|
||||||
|
transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
|
||||||
|
transition-duration 0.3s
|
||||||
|
font-size 16px
|
||||||
|
line-height 32px
|
||||||
|
color rgba(#000, 0.54)
|
||||||
|
pointer-events none
|
||||||
|
//will-change transform
|
||||||
|
transform-origin top left
|
||||||
|
transform scale(1)
|
||||||
|
|
||||||
|
> select
|
||||||
|
display block
|
||||||
|
flex 1
|
||||||
|
width 100%
|
||||||
|
padding 0
|
||||||
|
font inherit
|
||||||
|
font-weight fill ? bold : normal
|
||||||
|
font-size 16px
|
||||||
|
height 32px
|
||||||
|
color isDark ? #fff : #000
|
||||||
|
background transparent
|
||||||
|
border none
|
||||||
|
border-radius 0
|
||||||
|
outline none
|
||||||
|
box-shadow none
|
||||||
|
|
||||||
|
*
|
||||||
|
color #000
|
||||||
|
|
||||||
|
> .prefix
|
||||||
|
> .suffix
|
||||||
|
display block
|
||||||
|
align-self center
|
||||||
|
justify-self center
|
||||||
|
font-size 16px
|
||||||
|
line-height 32px
|
||||||
|
color rgba(#000, 0.54)
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
> *
|
||||||
|
display block
|
||||||
|
min-width 16px
|
||||||
|
|
||||||
|
> .prefix
|
||||||
|
padding-right 4px
|
||||||
|
|
||||||
|
> .suffix
|
||||||
|
padding-left 4px
|
||||||
|
|
||||||
|
> .text
|
||||||
|
margin 6px 0
|
||||||
|
font-size 13px
|
||||||
|
|
||||||
|
*
|
||||||
|
margin 0
|
||||||
|
|
||||||
|
&.focused
|
||||||
|
> .input
|
||||||
|
if fill
|
||||||
|
background rgba(#000, 0.05)
|
||||||
|
else
|
||||||
|
&:after
|
||||||
|
opacity 1
|
||||||
|
transform scaleX(1)
|
||||||
|
|
||||||
|
> .label
|
||||||
|
color $theme-color
|
||||||
|
|
||||||
|
&.focused
|
||||||
|
&.filled
|
||||||
|
> .input
|
||||||
|
> .label
|
||||||
|
top fill ? -24px : -17px
|
||||||
|
left 0 !important
|
||||||
|
transform scale(0.75)
|
||||||
|
|
||||||
|
.ui-select[data-darkmode]
|
||||||
|
&.fill
|
||||||
|
root(true, true)
|
||||||
|
&:not(.fill)
|
||||||
|
root(true, false)
|
||||||
|
|
||||||
|
.ui-select:not([data-darkmode])
|
||||||
|
&.fill
|
||||||
|
root(false, true)
|
||||||
|
&:not(.fill)
|
||||||
|
root(false, false)
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,135 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="ui-switch"
|
||||||
|
:class="{ disabled, checked }"
|
||||||
|
role="switch"
|
||||||
|
:aria-checked="checked"
|
||||||
|
:aria-disabled="disabled"
|
||||||
|
@click="toggle"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
ref="input"
|
||||||
|
:disabled="disabled"
|
||||||
|
@keydown.enter="toggle"
|
||||||
|
>
|
||||||
|
<span class="button">
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
<span class="label">
|
||||||
|
<span :aria-hidden="!checked"><slot></slot></span>
|
||||||
|
<p :aria-hidden="!checked">
|
||||||
|
<slot name="text"></slot>
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
export default Vue.extend({
|
||||||
|
model: {
|
||||||
|
prop: 'value',
|
||||||
|
event: 'change'
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
checked(): boolean {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggle() {
|
||||||
|
this.$emit('change', !this.checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
root(isDark)
|
||||||
|
display flex
|
||||||
|
margin 32px 0
|
||||||
|
cursor pointer
|
||||||
|
transition all 0.3s
|
||||||
|
|
||||||
|
> *
|
||||||
|
user-select none
|
||||||
|
|
||||||
|
&.disabled
|
||||||
|
opacity 0.6
|
||||||
|
cursor not-allowed
|
||||||
|
|
||||||
|
&.checked
|
||||||
|
> .button
|
||||||
|
background-color rgba($theme-color, 0.4)
|
||||||
|
border-color rgba($theme-color, 0.4)
|
||||||
|
|
||||||
|
> *
|
||||||
|
background-color $theme-color
|
||||||
|
transform translateX(14px)
|
||||||
|
|
||||||
|
> input
|
||||||
|
position absolute
|
||||||
|
width 0
|
||||||
|
height 0
|
||||||
|
opacity 0
|
||||||
|
margin 0
|
||||||
|
|
||||||
|
> .button
|
||||||
|
display inline-block
|
||||||
|
margin 3px 0 0 0
|
||||||
|
width 34px
|
||||||
|
height 14px
|
||||||
|
background isDark ? rgba(#fff, 0.15) : rgba(#000, 0.25)
|
||||||
|
outline none
|
||||||
|
border-radius 14px
|
||||||
|
transition inherit
|
||||||
|
|
||||||
|
> *
|
||||||
|
position absolute
|
||||||
|
top -3px
|
||||||
|
left 0
|
||||||
|
border-radius 100%
|
||||||
|
transition background-color 0.3s, transform 0.3s
|
||||||
|
width 20px
|
||||||
|
height 20px
|
||||||
|
background-color #fff
|
||||||
|
box-shadow 0 2px 1px -1px rgba(#000, 0.2), 0 1px 1px 0 rgba(#000, 0.14), 0 1px 3px 0 rgba(#000, 0.12)
|
||||||
|
|
||||||
|
> .label
|
||||||
|
margin-left 8px
|
||||||
|
display block
|
||||||
|
font-size 16px
|
||||||
|
cursor pointer
|
||||||
|
transition inherit
|
||||||
|
|
||||||
|
> span
|
||||||
|
display block
|
||||||
|
line-height 20px
|
||||||
|
color isDark ? #c4ccd2 : rgba(#000, 0.75)
|
||||||
|
transition inherit
|
||||||
|
|
||||||
|
> p
|
||||||
|
margin 0
|
||||||
|
//font-size 90%
|
||||||
|
color isDark ? #78858e : #9daab3
|
||||||
|
|
||||||
|
.ui-switch[data-darkmode]
|
||||||
|
root(true)
|
||||||
|
|
||||||
|
.ui-switch:not([data-darkmode])
|
||||||
|
root(false)
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,174 @@
|
||||||
|
<template>
|
||||||
|
<div class="ui-textarea" :class="{ focused, filled }">
|
||||||
|
<div class="input">
|
||||||
|
<span class="label" ref="label"><slot></slot></span>
|
||||||
|
<textarea ref="input"
|
||||||
|
:value="value"
|
||||||
|
:required="required"
|
||||||
|
:readonly="readonly"
|
||||||
|
:pattern="pattern"
|
||||||
|
:autocomplete="autocomplete"
|
||||||
|
@input="$emit('input', $event.target.value)"
|
||||||
|
@focus="focused = true"
|
||||||
|
@blur="focused = false">
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="text"><slot name="text"></slot></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
const getPasswordStrength = require('syuilo-password-strength');
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
readonly: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
autocomplete: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
focused: false,
|
||||||
|
passwordStrength: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filled(): boolean {
|
||||||
|
return this.value != '' && this.value != null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
focus() {
|
||||||
|
this.$refs.input.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
root(isDark, fill)
|
||||||
|
margin 42px 0 32px 0
|
||||||
|
|
||||||
|
> .input
|
||||||
|
padding 12px
|
||||||
|
|
||||||
|
if fill
|
||||||
|
background rgba(#000, 0.035)
|
||||||
|
border-radius 6px
|
||||||
|
else
|
||||||
|
&:before
|
||||||
|
content ''
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
top 0
|
||||||
|
bottom 0
|
||||||
|
left 0
|
||||||
|
right 0
|
||||||
|
background none
|
||||||
|
border solid 1px isDark ? rgba(#fff, 0.7) : rgba(#000, 0.42)
|
||||||
|
border-radius 3px
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
&:after
|
||||||
|
content ''
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
top 0
|
||||||
|
bottom 0
|
||||||
|
left 0
|
||||||
|
right 0
|
||||||
|
background none
|
||||||
|
border solid 2px $theme-color
|
||||||
|
border-radius 3px
|
||||||
|
opacity 0
|
||||||
|
transition opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1)
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
> .label
|
||||||
|
position absolute
|
||||||
|
top 6px
|
||||||
|
left 12px
|
||||||
|
pointer-events none
|
||||||
|
transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
|
||||||
|
transition-duration 0.3s
|
||||||
|
font-size 16px
|
||||||
|
line-height 32px
|
||||||
|
color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
|
||||||
|
pointer-events none
|
||||||
|
//will-change transform
|
||||||
|
transform-origin top left
|
||||||
|
transform scale(1)
|
||||||
|
|
||||||
|
> textarea
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
min-height 100px
|
||||||
|
padding 0
|
||||||
|
font inherit
|
||||||
|
font-weight fill ? bold : normal
|
||||||
|
font-size 16px
|
||||||
|
color isDark ? #fff : #000
|
||||||
|
background transparent
|
||||||
|
border none
|
||||||
|
border-radius 0
|
||||||
|
outline none
|
||||||
|
box-shadow none
|
||||||
|
|
||||||
|
> .text
|
||||||
|
margin 6px 0
|
||||||
|
font-size 13px
|
||||||
|
|
||||||
|
*
|
||||||
|
margin 0
|
||||||
|
|
||||||
|
&.focused
|
||||||
|
> .input
|
||||||
|
if fill
|
||||||
|
background rgba(#000, 0.05)
|
||||||
|
else
|
||||||
|
&:after
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
> .label
|
||||||
|
color $theme-color
|
||||||
|
|
||||||
|
&.focused
|
||||||
|
&.filled
|
||||||
|
> .input
|
||||||
|
> .label
|
||||||
|
top -24px
|
||||||
|
left 0 !important
|
||||||
|
transform scale(0.75)
|
||||||
|
|
||||||
|
.ui-textarea[data-darkmode]
|
||||||
|
&.fill
|
||||||
|
root(true, true)
|
||||||
|
&:not(.fill)
|
||||||
|
root(true, false)
|
||||||
|
|
||||||
|
.ui-textarea:not([data-darkmode])
|
||||||
|
&.fill
|
||||||
|
root(false, true)
|
||||||
|
&:not(.fill)
|
||||||
|
root(false, false)
|
||||||
|
|
||||||
|
</style>
|
|
@ -50,7 +50,7 @@ export default Vue.extend({
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
|
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
data.append('i', (this as any).os.i.token);
|
data.append('i', this.$store.state.i.token);
|
||||||
data.append('file', file);
|
data.append('file', file);
|
||||||
|
|
||||||
if (folder) data.append('folderId', folder);
|
if (folder) data.append('folderId', folder);
|
||||||
|
|
|
@ -68,7 +68,7 @@ iframe
|
||||||
root(isDark)
|
root(isDark)
|
||||||
> a
|
> a
|
||||||
display block
|
display block
|
||||||
font-size 16px
|
font-size 14px
|
||||||
border solid 1px isDark ? #191b1f : #eee
|
border solid 1px isDark ? #191b1f : #eee
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
@ -136,8 +136,17 @@ root(isDark)
|
||||||
left 0
|
left 0
|
||||||
width 100%
|
width 100%
|
||||||
|
|
||||||
|
@media (max-width 550px)
|
||||||
|
font-size 12px
|
||||||
|
|
||||||
|
> .thumbnail
|
||||||
|
height 80px
|
||||||
|
|
||||||
|
> article
|
||||||
|
padding 12px
|
||||||
|
|
||||||
@media (max-width 500px)
|
@media (max-width 500px)
|
||||||
font-size 8px
|
font-size 10px
|
||||||
|
|
||||||
> .thumbnail
|
> .thumbnail
|
||||||
height 70px
|
height 70px
|
||||||
|
@ -145,6 +154,16 @@ root(isDark)
|
||||||
> article
|
> article
|
||||||
padding 8px
|
padding 8px
|
||||||
|
|
||||||
|
> header
|
||||||
|
margin-bottom 4px
|
||||||
|
|
||||||
|
> footer
|
||||||
|
margin-top 4px
|
||||||
|
|
||||||
|
> img
|
||||||
|
width 12px
|
||||||
|
height 12px
|
||||||
|
|
||||||
.mk-url-preview[data-darkmode]
|
.mk-url-preview[data-darkmode]
|
||||||
root(true)
|
root(true)
|
||||||
|
|
||||||
|
|
|
@ -203,6 +203,7 @@ root(isDark)
|
||||||
justify-content center
|
justify-content center
|
||||||
align-items center
|
align-items center
|
||||||
margin-right 10px
|
margin-right 10px
|
||||||
|
width 16px
|
||||||
|
|
||||||
> *:last-child
|
> *:last-child
|
||||||
flex 1 1 auto
|
flex 1 1 auto
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<mk-note-html :text="note.text"/>
|
<mk-note-html v-if="note.text" :text="note.text"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -109,6 +109,9 @@ root(isDark)
|
||||||
> .created-at
|
> .created-at
|
||||||
color isDark ? #606984 : #c0c0c0
|
color isDark ? #606984 : #c0c0c0
|
||||||
|
|
||||||
|
> .text
|
||||||
|
text-align left
|
||||||
|
|
||||||
.mk-welcome-timeline[data-darkmode]
|
.mk-welcome-timeline[data-darkmode]
|
||||||
root(true)
|
root(true)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
<template>
|
||||||
|
<div class="mkw-analog-clock">
|
||||||
|
<mk-widget-container :naked="props.naked" :show-header="false">
|
||||||
|
<div class="mkw-analog-clock--body">
|
||||||
|
<mk-analog-clock :dark="$store.state.device.darkmode"/>
|
||||||
|
</div>
|
||||||
|
</mk-widget-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import define from '../../../common/define-widget';
|
||||||
|
export default define({
|
||||||
|
name: 'analog-clock',
|
||||||
|
props: () => ({
|
||||||
|
naked: false
|
||||||
|
})
|
||||||
|
}).extend({
|
||||||
|
methods: {
|
||||||
|
func() {
|
||||||
|
this.props.naked = !this.props.naked;
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
root(isDark)
|
||||||
|
.mkw-analog-clock--body
|
||||||
|
padding 8px
|
||||||
|
|
||||||
|
.mkw-analog-clock[data-darkmode]
|
||||||
|
root(true)
|
||||||
|
|
||||||
|
.mkw-analog-clock:not([data-darkmode])
|
||||||
|
root(false)
|
||||||
|
|
||||||
|
</style>
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="mkw-broadcast"
|
<div class="mkw-broadcast"
|
||||||
:data-found="broadcasts.length != 0"
|
:data-found="broadcasts.length != 0"
|
||||||
:data-melt="props.design == 1"
|
:data-melt="props.design == 1"
|
||||||
:data-mobile="isMobile"
|
:data-mobile="platform == 'mobile'"
|
||||||
>
|
>
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<svg height="32" version="1.1" viewBox="0 0 32 32" width="32">
|
<svg height="32" version="1.1" viewBox="0 0 32 32" width="32">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mkw-calendar" :data-special="special" :data-mobile="isMobile">
|
<div class="mkw-calendar" :data-special="special" :data-mobile="platform == 'mobile'">
|
||||||
<mk-widget-container :naked="props.design == 1" :show-header="false">
|
<mk-widget-container :naked="props.design == 1" :show-header="false">
|
||||||
<div class="mkw-calendar--body">
|
<div class="mkw-calendar--body">
|
||||||
<div class="calendar" :data-is-holiday="isHoliday">
|
<div class="calendar" :data-is-holiday="isHoliday">
|
||||||
|
@ -67,7 +67,7 @@ export default define({
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
func() {
|
func() {
|
||||||
if (this.isMobile) return;
|
if (this.platform == 'mobile') return;
|
||||||
if (this.props.design == 2) {
|
if (this.props.design == 2) {
|
||||||
this.props.design = 0;
|
this.props.design = 0;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mkw-donation" :data-mobile="isMobile">
|
<div class="mkw-donation" :data-mobile="platform == 'mobile'">
|
||||||
<article>
|
<article>
|
||||||
<h1>%fa:heart%%i18n:@title%</h1>
|
<h1>%fa:heart%%i18n:@title%</h1>
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
<template>
|
||||||
|
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" style="overflow:visible">
|
||||||
|
<defs>
|
||||||
|
<linearGradient :id="gradientId" x1="0" x2="0" y1="1" y2="0">
|
||||||
|
<stop offset="0%" stop-color="hsl(200, 80%, 70%)"></stop>
|
||||||
|
<stop offset="100%" stop-color="hsl(90, 80%, 70%)"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
<mask :id="maskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY">
|
||||||
|
<polygon
|
||||||
|
:points="polygonPoints"
|
||||||
|
fill="#fff"
|
||||||
|
fill-opacity="0.5"/>
|
||||||
|
<polyline
|
||||||
|
:points="polylinePoints"
|
||||||
|
fill="none"
|
||||||
|
stroke="#fff"
|
||||||
|
stroke-width="2"/>
|
||||||
|
<circle
|
||||||
|
:cx="headX"
|
||||||
|
:cy="headY"
|
||||||
|
r="3"
|
||||||
|
fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
</defs>
|
||||||
|
<rect
|
||||||
|
x="-10" y="-10"
|
||||||
|
:width="viewBoxX + 20" :height="viewBoxY + 20"
|
||||||
|
:style="`stroke: none; fill: url(#${ gradientId }); mask: url(#${ maskId })`"/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import * as uuid from 'uuid';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
src: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
viewBoxX: 50,
|
||||||
|
viewBoxY: 30,
|
||||||
|
gradientId: uuid(),
|
||||||
|
maskId: uuid(),
|
||||||
|
polylinePoints: '',
|
||||||
|
polygonPoints: '',
|
||||||
|
headX: null,
|
||||||
|
headY: null,
|
||||||
|
clock: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
src() {
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.draw();
|
||||||
|
|
||||||
|
// Vueが何故かWatchを発動させない場合があるので
|
||||||
|
this.clock = setInterval(this.draw, 1000);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.clock);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
draw() {
|
||||||
|
const stats = this.src.slice().reverse();
|
||||||
|
const peak = Math.max.apply(null, stats) || 1;
|
||||||
|
|
||||||
|
const polylinePoints = stats.map((n, i) => [
|
||||||
|
i * (this.viewBoxX / (stats.length - 1)),
|
||||||
|
(1 - (n / peak)) * this.viewBoxY
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.polylinePoints = polylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
|
||||||
|
|
||||||
|
this.polygonPoints = `0,${ this.viewBoxY } ${ this.polylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
||||||
|
|
||||||
|
this.headX = polylinePoints[polylinePoints.length - 1][0];
|
||||||
|
this.headY = polylinePoints[polylinePoints.length - 1][1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,118 @@
|
||||||
|
<template>
|
||||||
|
<div class="mkw-hashtags">
|
||||||
|
<mk-widget-container :show-header="!props.compact">
|
||||||
|
<template slot="header">%fa:hashtag%%i18n:@title%</template>
|
||||||
|
|
||||||
|
<div class="mkw-hashtags--body" :data-mobile="platform == 'mobile'">
|
||||||
|
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
||||||
|
<p class="empty" v-else-if="stats.length == 0">%fa:exclamation-circle%%i18n:@empty%</p>
|
||||||
|
<transition-group v-else tag="div" name="chart">
|
||||||
|
<div v-for="stat in stats" :key="stat.tag">
|
||||||
|
<div class="tag">
|
||||||
|
<router-link :to="`/tags/${ stat.tag }`" :title="stat.tag">#{{ stat.tag }}</router-link>
|
||||||
|
<p>{{ '%i18n:@count%'.replace('{}', stat.usersCount) }}</p>
|
||||||
|
</div>
|
||||||
|
<x-chart class="chart" :src="stat.chart"/>
|
||||||
|
</div>
|
||||||
|
</transition-group>
|
||||||
|
</div>
|
||||||
|
</mk-widget-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import define from '../../../common/define-widget';
|
||||||
|
import XChart from './hashtags.chart.vue';
|
||||||
|
|
||||||
|
export default define({
|
||||||
|
name: 'hashtags',
|
||||||
|
props: () => ({
|
||||||
|
compact: false
|
||||||
|
})
|
||||||
|
}).extend({
|
||||||
|
components: {
|
||||||
|
XChart
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
stats: [],
|
||||||
|
fetching: true,
|
||||||
|
clock: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetch();
|
||||||
|
this.clock = setInterval(this.fetch, 1000 * 60);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.clock);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
func() {
|
||||||
|
this.props.compact = !this.props.compact;
|
||||||
|
this.save();
|
||||||
|
},
|
||||||
|
fetch() {
|
||||||
|
(this as any).api('hashtags/trend').then(stats => {
|
||||||
|
this.stats = stats;
|
||||||
|
this.fetching = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
root(isDark)
|
||||||
|
.mkw-hashtags--body
|
||||||
|
> .fetching
|
||||||
|
> .empty
|
||||||
|
margin 0
|
||||||
|
padding 16px
|
||||||
|
text-align center
|
||||||
|
color #aaa
|
||||||
|
|
||||||
|
> [data-fa]
|
||||||
|
margin-right 4px
|
||||||
|
|
||||||
|
> div
|
||||||
|
.chart-move
|
||||||
|
transition transform 1s ease
|
||||||
|
|
||||||
|
> div
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
padding 14px 16px
|
||||||
|
|
||||||
|
&:not(:last-child)
|
||||||
|
border-bottom solid 1px isDark ? #393f4f : #eee
|
||||||
|
|
||||||
|
> .tag
|
||||||
|
flex 1
|
||||||
|
overflow hidden
|
||||||
|
font-size 14px
|
||||||
|
color isDark ? #9baec8 : #65727b
|
||||||
|
|
||||||
|
> a
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
white-space nowrap
|
||||||
|
overflow hidden
|
||||||
|
text-overflow ellipsis
|
||||||
|
color inherit
|
||||||
|
|
||||||
|
> p
|
||||||
|
margin 0
|
||||||
|
font-size 75%
|
||||||
|
opacity 0.7
|
||||||
|
|
||||||
|
> .chart
|
||||||
|
height 30px
|
||||||
|
|
||||||
|
.mkw-hashtags[data-darkmode]
|
||||||
|
root(true)
|
||||||
|
|
||||||
|
.mkw-hashtags:not([data-darkmode])
|
||||||
|
root(false)
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,8 +1,11 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
import wAnalogClock from './analog-clock.vue';
|
||||||
import wVersion from './version.vue';
|
import wVersion from './version.vue';
|
||||||
import wRss from './rss.vue';
|
import wRss from './rss.vue';
|
||||||
import wServer from './server.vue';
|
import wServer from './server.vue';
|
||||||
|
import wPostsMonitor from './posts-monitor.vue';
|
||||||
|
import wMemo from './memo.vue';
|
||||||
import wBroadcast from './broadcast.vue';
|
import wBroadcast from './broadcast.vue';
|
||||||
import wCalendar from './calendar.vue';
|
import wCalendar from './calendar.vue';
|
||||||
import wPhotoStream from './photo-stream.vue';
|
import wPhotoStream from './photo-stream.vue';
|
||||||
|
@ -10,7 +13,9 @@ import wSlideshow from './slideshow.vue';
|
||||||
import wTips from './tips.vue';
|
import wTips from './tips.vue';
|
||||||
import wDonation from './donation.vue';
|
import wDonation from './donation.vue';
|
||||||
import wNav from './nav.vue';
|
import wNav from './nav.vue';
|
||||||
|
import wHashtags from './hashtags.vue';
|
||||||
|
|
||||||
|
Vue.component('mkw-analog-clock', wAnalogClock);
|
||||||
Vue.component('mkw-nav', wNav);
|
Vue.component('mkw-nav', wNav);
|
||||||
Vue.component('mkw-calendar', wCalendar);
|
Vue.component('mkw-calendar', wCalendar);
|
||||||
Vue.component('mkw-photo-stream', wPhotoStream);
|
Vue.component('mkw-photo-stream', wPhotoStream);
|
||||||
|
@ -19,5 +24,8 @@ Vue.component('mkw-tips', wTips);
|
||||||
Vue.component('mkw-donation', wDonation);
|
Vue.component('mkw-donation', wDonation);
|
||||||
Vue.component('mkw-broadcast', wBroadcast);
|
Vue.component('mkw-broadcast', wBroadcast);
|
||||||
Vue.component('mkw-server', wServer);
|
Vue.component('mkw-server', wServer);
|
||||||
|
Vue.component('mkw-posts-monitor', wPostsMonitor);
|
||||||
|
Vue.component('mkw-memo', wMemo);
|
||||||
Vue.component('mkw-rss', wRss);
|
Vue.component('mkw-rss', wRss);
|
||||||
Vue.component('mkw-version', wVersion);
|
Vue.component('mkw-version', wVersion);
|
||||||
|
Vue.component('mkw-hashtags', wHashtags);
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
<template>
|
||||||
|
<div class="mkw-memo">
|
||||||
|
<mk-widget-container :show-header="!props.compact">
|
||||||
|
<template slot="header">%fa:R sticky-note%%i18n:@title%</template>
|
||||||
|
|
||||||
|
<div class="mkw-memo--body">
|
||||||
|
<textarea v-model="text" placeholder="%i18n:@memo%" @input="onChange"></textarea>
|
||||||
|
<button @click="saveMemo" :disabled="!changed">%i18n:@save%</button>
|
||||||
|
</div>
|
||||||
|
</mk-widget-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import define from '../../define-widget';
|
||||||
|
|
||||||
|
export default define({
|
||||||
|
name: 'memo',
|
||||||
|
props: () => ({
|
||||||
|
compact: false
|
||||||
|
})
|
||||||
|
}).extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
text: null,
|
||||||
|
changed: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.text = this.$store.state.settings.memo;
|
||||||
|
|
||||||
|
this.$watch('$store.state.settings.memo', text => {
|
||||||
|
this.text = text;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
func() {
|
||||||
|
this.props.compact = !this.props.compact;
|
||||||
|
this.save();
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange() {
|
||||||
|
this.changed = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
saveMemo() {
|
||||||
|
this.$store.dispatch('settings/set', {
|
||||||
|
key: 'memo',
|
||||||
|
value: this.text
|
||||||
|
});
|
||||||
|
this.changed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import '~const.styl'
|
||||||
|
|
||||||
|
root(isDark)
|
||||||
|
.mkw-memo--body
|
||||||
|
padding-bottom 28px + 16px
|
||||||
|
|
||||||
|
> textarea
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
max-width 100%
|
||||||
|
min-width 100%
|
||||||
|
padding 16px
|
||||||
|
color isDark ? #fff : #222
|
||||||
|
background isDark ? #282c37 : #fff
|
||||||
|
border none
|
||||||
|
border-bottom solid 1px isDark ? #1c2023 : #eee
|
||||||
|
border-radius 0
|
||||||
|
|
||||||
|
> button
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
bottom 8px
|
||||||
|
right 8px
|
||||||
|
margin 0
|
||||||
|
padding 0 10px
|
||||||
|
height 28px
|
||||||
|
color $theme-color-foreground
|
||||||
|
background $theme-color !important
|
||||||
|
outline none
|
||||||
|
border none
|
||||||
|
border-radius 4px
|
||||||
|
transition background 0.1s ease
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background lighten($theme-color, 10%) !important
|
||||||
|
|
||||||
|
&:active
|
||||||
|
background darken($theme-color, 10%) !important
|
||||||
|
transition background 0s ease
|
||||||
|
|
||||||
|
&:disabled
|
||||||
|
opacity 0.7
|
||||||
|
cursor default
|
||||||
|
|
||||||
|
.mkw-memo[data-darkmode]
|
||||||
|
root(true)
|
||||||
|
|
||||||
|
.mkw-memo:not([data-darkmode])
|
||||||
|
root(false)
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,211 @@
|
||||||
|
<template>
|
||||||
|
<div class="mkw-posts-monitor">
|
||||||
|
<mk-widget-container :show-header="props.design == 0" :naked="props.design == 2">
|
||||||
|
<template slot="header">%fa:chart-line%%i18n:@title%</template>
|
||||||
|
<button slot="func" @click="toggle" title="%i18n:@toggle%">%fa:sort%</button>
|
||||||
|
|
||||||
|
<div class="qpdmibaztplkylerhdbllwcokyrfxeyj" :class="{ dual: props.view == 0 }" :data-darkmode="$store.state.device.darkmode">
|
||||||
|
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" v-show="props.view != 2">
|
||||||
|
<defs>
|
||||||
|
<linearGradient :id="localGradientId" x1="0" x2="0" y1="1" y2="0">
|
||||||
|
<stop offset="0%" stop-color="hsl(200, 80%, 70%)"></stop>
|
||||||
|
<stop offset="100%" stop-color="hsl(90, 80%, 70%)"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
<mask :id="localMaskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY">
|
||||||
|
<polygon
|
||||||
|
:points="localPolygonPoints"
|
||||||
|
fill="#fff"
|
||||||
|
fill-opacity="0.5"/>
|
||||||
|
<polyline
|
||||||
|
:points="localPolylinePoints"
|
||||||
|
fill="none"
|
||||||
|
stroke="#fff"
|
||||||
|
stroke-width="1"/>
|
||||||
|
<circle
|
||||||
|
:cx="localHeadX"
|
||||||
|
:cy="localHeadY"
|
||||||
|
r="1.5"
|
||||||
|
fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
</defs>
|
||||||
|
<rect
|
||||||
|
x="-2" y="-2"
|
||||||
|
:width="viewBoxX + 4" :height="viewBoxY + 4"
|
||||||
|
:style="`stroke: none; fill: url(#${ localGradientId }); mask: url(#${ localMaskId })`"/>
|
||||||
|
<text x="1" y="5">Local</text>
|
||||||
|
</svg>
|
||||||
|
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" v-show="props.view != 1">
|
||||||
|
<defs>
|
||||||
|
<linearGradient :id="fediGradientId" x1="0" x2="0" y1="1" y2="0">
|
||||||
|
<stop offset="0%" stop-color="hsl(200, 80%, 70%)"></stop>
|
||||||
|
<stop offset="100%" stop-color="hsl(90, 80%, 70%)"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
<mask :id="fediMaskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY">
|
||||||
|
<polygon
|
||||||
|
:points="fediPolygonPoints"
|
||||||
|
fill="#fff"
|
||||||
|
fill-opacity="0.5"/>
|
||||||
|
<polyline
|
||||||
|
:points="fediPolylinePoints"
|
||||||
|
fill="none"
|
||||||
|
stroke="#fff"
|
||||||
|
stroke-width="1"/>
|
||||||
|
<circle
|
||||||
|
:cx="fediHeadX"
|
||||||
|
:cy="fediHeadY"
|
||||||
|
r="1.5"
|
||||||
|
fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
</defs>
|
||||||
|
<rect
|
||||||
|
x="-2" y="-2"
|
||||||
|
:width="viewBoxX + 4" :height="viewBoxY + 4"
|
||||||
|
:style="`stroke: none; fill: url(#${ fediGradientId }); mask: url(#${ fediMaskId })`"/>
|
||||||
|
<text x="1" y="5">Fedi</text>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</mk-widget-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import define from '../../../common/define-widget';
|
||||||
|
import * as uuid from 'uuid';
|
||||||
|
|
||||||
|
export default define({
|
||||||
|
name: 'server',
|
||||||
|
props: () => ({
|
||||||
|
design: 0,
|
||||||
|
view: 0
|
||||||
|
})
|
||||||
|
}).extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
connection: null,
|
||||||
|
connectionId: null,
|
||||||
|
viewBoxY: 30,
|
||||||
|
stats: [],
|
||||||
|
fediGradientId: uuid(),
|
||||||
|
fediMaskId: uuid(),
|
||||||
|
localGradientId: uuid(),
|
||||||
|
localMaskId: uuid(),
|
||||||
|
fediPolylinePoints: '',
|
||||||
|
localPolylinePoints: '',
|
||||||
|
fediPolygonPoints: '',
|
||||||
|
localPolygonPoints: '',
|
||||||
|
fediHeadX: null,
|
||||||
|
fediHeadY: null,
|
||||||
|
localHeadX: null,
|
||||||
|
localHeadY: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
viewBoxX(): number {
|
||||||
|
return this.props.view == 0 ? 50 : 100;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
viewBoxX() {
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.connection = (this as any).os.streams.notesStatsStream.getConnection();
|
||||||
|
this.connectionId = (this as any).os.streams.notesStatsStream.use();
|
||||||
|
|
||||||
|
this.connection.on('stats', this.onStats);
|
||||||
|
this.connection.on('statsLog', this.onStatsLog);
|
||||||
|
this.connection.send({
|
||||||
|
type: 'requestLog',
|
||||||
|
id: Math.random().toString()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.connection.off('stats', this.onStats);
|
||||||
|
this.connection.off('statsLog', this.onStatsLog);
|
||||||
|
(this as any).os.streams.notesStatsStream.dispose(this.connectionId);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggle() {
|
||||||
|
if (this.props.view == 2) {
|
||||||
|
this.props.view = 0;
|
||||||
|
} else {
|
||||||
|
this.props.view++;
|
||||||
|
}
|
||||||
|
this.save();
|
||||||
|
},
|
||||||
|
func() {
|
||||||
|
if (this.props.design == 2) {
|
||||||
|
this.props.design = 0;
|
||||||
|
} else {
|
||||||
|
this.props.design++;
|
||||||
|
}
|
||||||
|
this.save();
|
||||||
|
},
|
||||||
|
draw() {
|
||||||
|
const stats = this.props.view == 0 ? this.stats.slice(-50) : this.stats;
|
||||||
|
const fediPeak = Math.max.apply(null, stats.map(x => x.all)) || 1;
|
||||||
|
const localPeak = Math.max.apply(null, stats.map(x => x.local)) || 1;
|
||||||
|
|
||||||
|
const fediPolylinePoints = stats.map((s, i) => [this.viewBoxX - ((stats.length - 1) - i), (1 - (s.all / fediPeak)) * this.viewBoxY]);
|
||||||
|
const localPolylinePoints = stats.map((s, i) => [this.viewBoxX - ((stats.length - 1) - i), (1 - (s.local / localPeak)) * this.viewBoxY]);
|
||||||
|
this.fediPolylinePoints = fediPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
|
||||||
|
this.localPolylinePoints = localPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
|
||||||
|
|
||||||
|
this.fediPolygonPoints = `${this.viewBoxX - (stats.length - 1)},${ this.viewBoxY } ${ this.fediPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
||||||
|
this.localPolygonPoints = `${this.viewBoxX - (stats.length - 1)},${ this.viewBoxY } ${ this.localPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
||||||
|
|
||||||
|
this.fediHeadX = fediPolylinePoints[fediPolylinePoints.length - 1][0];
|
||||||
|
this.fediHeadY = fediPolylinePoints[fediPolylinePoints.length - 1][1];
|
||||||
|
this.localHeadX = localPolylinePoints[localPolylinePoints.length - 1][0];
|
||||||
|
this.localHeadY = localPolylinePoints[localPolylinePoints.length - 1][1];
|
||||||
|
},
|
||||||
|
onStats(stats) {
|
||||||
|
this.stats.push(stats);
|
||||||
|
if (this.stats.length > 100) this.stats.shift();
|
||||||
|
this.draw();
|
||||||
|
},
|
||||||
|
onStatsLog(statsLog) {
|
||||||
|
statsLog.forEach(stats => this.onStats(stats));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
root(isDark)
|
||||||
|
&.dual
|
||||||
|
> svg
|
||||||
|
width 50%
|
||||||
|
float left
|
||||||
|
|
||||||
|
&:first-child
|
||||||
|
padding-right 5px
|
||||||
|
|
||||||
|
&:last-child
|
||||||
|
padding-left 5px
|
||||||
|
|
||||||
|
> svg
|
||||||
|
display block
|
||||||
|
padding 10px
|
||||||
|
width 100%
|
||||||
|
|
||||||
|
> text
|
||||||
|
font-size 5px
|
||||||
|
fill isDark ? rgba(#fff, 0.55) : rgba(#000, 0.55)
|
||||||
|
|
||||||
|
> tspan
|
||||||
|
opacity 0.5
|
||||||
|
|
||||||
|
&:after
|
||||||
|
content ""
|
||||||
|
display block
|
||||||
|
clear both
|
||||||
|
|
||||||
|
.qpdmibaztplkylerhdbllwcokyrfxeyj[data-darkmode]
|
||||||
|
root(true)
|
||||||
|
|
||||||
|
.qpdmibaztplkylerhdbllwcokyrfxeyj:not([data-darkmode])
|
||||||
|
root(false)
|
||||||
|
|
||||||
|
</style>
|
|
@ -4,7 +4,7 @@
|
||||||
<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" :data-mobile="isMobile">
|
<div class="mkw-rss--body" :data-mobile="platform == 'mobile'">
|
||||||
<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>
|
||||||
|
@ -19,12 +19,12 @@ import define from '../../../common/define-widget';
|
||||||
export default define({
|
export default define({
|
||||||
name: 'rss',
|
name: 'rss',
|
||||||
props: () => ({
|
props: () => ({
|
||||||
compact: false
|
compact: false,
|
||||||
|
url: 'http://news.yahoo.co.jp/pickup/rss.xml'
|
||||||
})
|
})
|
||||||
}).extend({
|
}).extend({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
url: 'http://news.yahoo.co.jp/pickup/rss.xml',
|
|
||||||
items: [],
|
items: [],
|
||||||
fetching: true,
|
fetching: true,
|
||||||
clock: null
|
clock: null
|
||||||
|
@ -43,7 +43,7 @@ export default define({
|
||||||
this.save();
|
this.save();
|
||||||
},
|
},
|
||||||
fetch() {
|
fetch() {
|
||||||
fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.url}`, {
|
fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.props.url}`, {
|
||||||
cache: 'no-cache'
|
cache: 'no-cache'
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
res.json().then(feed => {
|
res.json().then(feed => {
|
||||||
|
@ -53,7 +53,12 @@ export default define({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setting() {
|
setting() {
|
||||||
alert('not implemented yet');
|
const url = window.prompt('URL', this.props.url);
|
||||||
|
if (url && url != '') {
|
||||||
|
this.props.url = url;
|
||||||
|
this.save();
|
||||||
|
this.fetch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="cpu-memory">
|
<div class="cpu-memory">
|
||||||
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" preserveAspectRatio="none">
|
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient :id="cpuGradientId" x1="0" x2="0" y1="1" y2="0">
|
<linearGradient :id="cpuGradientId" x1="0" x2="0" y1="1" y2="0">
|
||||||
<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
|
<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
|
||||||
|
@ -16,15 +16,20 @@
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="#fff"
|
stroke="#fff"
|
||||||
stroke-width="1"/>
|
stroke-width="1"/>
|
||||||
|
<circle
|
||||||
|
:cx="cpuHeadX"
|
||||||
|
:cy="cpuHeadY"
|
||||||
|
r="1.5"
|
||||||
|
fill="#fff"/>
|
||||||
</mask>
|
</mask>
|
||||||
</defs>
|
</defs>
|
||||||
<rect
|
<rect
|
||||||
x="-1" y="-1"
|
x="-2" y="-2"
|
||||||
:width="viewBoxX + 2" :height="viewBoxY + 2"
|
:width="viewBoxX + 4" :height="viewBoxY + 4"
|
||||||
:style="`stroke: none; fill: url(#${ cpuGradientId }); mask: url(#${ cpuMaskId })`"/>
|
:style="`stroke: none; fill: url(#${ cpuGradientId }); mask: url(#${ cpuMaskId })`"/>
|
||||||
<text x="1" y="5">CPU <tspan>{{ cpuP }}%</tspan></text>
|
<text x="1" y="5">CPU <tspan>{{ cpuP }}%</tspan></text>
|
||||||
</svg>
|
</svg>
|
||||||
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" preserveAspectRatio="none">
|
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient :id="memGradientId" x1="0" x2="0" y1="1" y2="0">
|
<linearGradient :id="memGradientId" x1="0" x2="0" y1="1" y2="0">
|
||||||
<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
|
<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
|
||||||
|
@ -40,11 +45,16 @@
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="#fff"
|
stroke="#fff"
|
||||||
stroke-width="1"/>
|
stroke-width="1"/>
|
||||||
|
<circle
|
||||||
|
:cx="memHeadX"
|
||||||
|
:cy="memHeadY"
|
||||||
|
r="1.5"
|
||||||
|
fill="#fff"/>
|
||||||
</mask>
|
</mask>
|
||||||
</defs>
|
</defs>
|
||||||
<rect
|
<rect
|
||||||
x="-1" y="-1"
|
x="-2" y="-2"
|
||||||
:width="viewBoxX + 2" :height="viewBoxY + 2"
|
:width="viewBoxX + 4" :height="viewBoxY + 4"
|
||||||
:style="`stroke: none; fill: url(#${ memGradientId }); mask: url(#${ memMaskId })`"/>
|
:style="`stroke: none; fill: url(#${ memGradientId }); mask: url(#${ memMaskId })`"/>
|
||||||
<text x="1" y="5">MEM <tspan>{{ memP }}%</tspan></text>
|
<text x="1" y="5">MEM <tspan>{{ memP }}%</tspan></text>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -70,15 +80,25 @@ export default Vue.extend({
|
||||||
memPolylinePoints: '',
|
memPolylinePoints: '',
|
||||||
cpuPolygonPoints: '',
|
cpuPolygonPoints: '',
|
||||||
memPolygonPoints: '',
|
memPolygonPoints: '',
|
||||||
|
cpuHeadX: null,
|
||||||
|
cpuHeadY: null,
|
||||||
|
memHeadX: null,
|
||||||
|
memHeadY: null,
|
||||||
cpuP: '',
|
cpuP: '',
|
||||||
memP: ''
|
memP: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.connection.on('stats', this.onStats);
|
this.connection.on('stats', this.onStats);
|
||||||
|
this.connection.on('statsLog', this.onStatsLog);
|
||||||
|
this.connection.send({
|
||||||
|
type: 'requestLog',
|
||||||
|
id: Math.random().toString()
|
||||||
|
});
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.connection.off('stats', this.onStats);
|
this.connection.off('stats', this.onStats);
|
||||||
|
this.connection.off('statsLog', this.onStatsLog);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onStats(stats) {
|
onStats(stats) {
|
||||||
|
@ -86,14 +106,24 @@ export default Vue.extend({
|
||||||
this.stats.push(stats);
|
this.stats.push(stats);
|
||||||
if (this.stats.length > 50) this.stats.shift();
|
if (this.stats.length > 50) this.stats.shift();
|
||||||
|
|
||||||
this.cpuPolylinePoints = this.stats.map((s, i) => `${this.viewBoxX - ((this.stats.length - 1) - i)},${(1 - s.cpu_usage) * this.viewBoxY}`).join(' ');
|
const cpuPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - s.cpu_usage) * this.viewBoxY]);
|
||||||
this.memPolylinePoints = this.stats.map((s, i) => `${this.viewBoxX - ((this.stats.length - 1) - i)},${(1 - (s.mem.used / s.mem.total)) * this.viewBoxY}`).join(' ');
|
const memPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.mem.used / s.mem.total)) * this.viewBoxY]);
|
||||||
|
this.cpuPolylinePoints = cpuPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
|
||||||
|
this.memPolylinePoints = memPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
|
||||||
|
|
||||||
this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ this.cpuPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ this.cpuPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
||||||
this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ this.memPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ this.memPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
||||||
|
|
||||||
|
this.cpuHeadX = cpuPolylinePoints[cpuPolylinePoints.length - 1][0];
|
||||||
|
this.cpuHeadY = cpuPolylinePoints[cpuPolylinePoints.length - 1][1];
|
||||||
|
this.memHeadX = memPolylinePoints[memPolylinePoints.length - 1][0];
|
||||||
|
this.memHeadY = memPolylinePoints[memPolylinePoints.length - 1][1];
|
||||||
|
|
||||||
this.cpuP = (stats.cpu_usage * 100).toFixed(0);
|
this.cpuP = (stats.cpu_usage * 100).toFixed(0);
|
||||||
this.memP = (stats.mem.used / stats.mem.total * 100).toFixed(0);
|
this.memP = (stats.mem.used / stats.mem.total * 100).toFixed(0);
|
||||||
|
},
|
||||||
|
onStatsLog(statsLog) {
|
||||||
|
statsLog.forEach(stats => this.onStats(stats));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -55,11 +55,11 @@ export default define({
|
||||||
this.fetching = false;
|
this.fetching = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.connection = (this as any).os.streams.serverStream.getConnection();
|
this.connection = (this as any).os.streams.serverStatsStream.getConnection();
|
||||||
this.connectionId = (this as any).os.streams.serverStream.use();
|
this.connectionId = (this as any).os.streams.serverStatsStream.use();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
(this as any).os.streams.serverStream.dispose(this.connectionId);
|
(this as any).os.streams.serverStatsStream.dispose(this.connectionId);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggle() {
|
toggle() {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mkw-slideshow" :data-mobile="isMobile">
|
<div class="mkw-slideshow" :data-mobile="platform == 'mobile'">
|
||||||
<div @click="choose">
|
<div @click="choose">
|
||||||
<p v-if="props.folder === undefined">
|
<p v-if="props.folder === undefined">
|
||||||
<template v-if="isCustomizeMode">フォルダを指定するには、カスタマイズモードを終了してください</template>
|
<template v-if="isCustomizeMode">フォルダを指定するには、カスタマイズモードを終了してください</template>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
declare const _HOST_: string;
|
declare const _HOST_: string;
|
||||||
declare const _HOSTNAME_: string;
|
declare const _HOSTNAME_: string;
|
||||||
declare const _URL_: string;
|
declare const _URL_: string;
|
||||||
|
declare const _NAME_: string;
|
||||||
|
declare const _DESCRIPTION_: string;
|
||||||
declare const _API_URL_: string;
|
declare const _API_URL_: string;
|
||||||
declare const _WS_URL_: string;
|
declare const _WS_URL_: string;
|
||||||
declare const _DOCS_URL_: string;
|
declare const _DOCS_URL_: string;
|
||||||
|
@ -17,10 +19,13 @@ declare const _VERSION_: string;
|
||||||
declare const _CODENAME_: string;
|
declare const _CODENAME_: string;
|
||||||
declare const _LICENSE_: string;
|
declare const _LICENSE_: string;
|
||||||
declare const _GOOGLE_MAPS_API_KEY_: string;
|
declare const _GOOGLE_MAPS_API_KEY_: string;
|
||||||
|
declare const _WELCOME_BG_URL_: string;
|
||||||
|
|
||||||
export const host = _HOST_;
|
export const host = _HOST_;
|
||||||
export const hostname = _HOSTNAME_;
|
export const hostname = _HOSTNAME_;
|
||||||
export const url = _URL_;
|
export const url = _URL_;
|
||||||
|
export const name = _NAME_;
|
||||||
|
export const description = _DESCRIPTION_;
|
||||||
export const apiUrl = _API_URL_;
|
export const apiUrl = _API_URL_;
|
||||||
export const wsUrl = _WS_URL_;
|
export const wsUrl = _WS_URL_;
|
||||||
export const docsUrl = _DOCS_URL_;
|
export const docsUrl = _DOCS_URL_;
|
||||||
|
@ -37,3 +42,4 @@ export const version = _VERSION_;
|
||||||
export const codename = _CODENAME_;
|
export const codename = _CODENAME_;
|
||||||
export const license = _LICENSE_;
|
export const license = _LICENSE_;
|
||||||
export const googleMapsApiKey = _GOOGLE_MAPS_API_KEY_;
|
export const googleMapsApiKey = _GOOGLE_MAPS_API_KEY_;
|
||||||
|
export const welcomeBgUrl = _WELCOME_BG_URL_;
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
|
import OS from '../../mios';
|
||||||
import { url } from '../../config';
|
import { url } from '../../config';
|
||||||
import MkChooseFileFromDriveWindow from '../views/components/choose-file-from-drive-window.vue';
|
import MkChooseFileFromDriveWindow from '../views/components/choose-file-from-drive-window.vue';
|
||||||
|
|
||||||
export default function(opts) {
|
export default (os: OS) => opts => {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
const o = opts || {};
|
const o = opts || {};
|
||||||
|
|
||||||
if (document.body.clientWidth > 800) {
|
if (document.body.clientWidth > 800) {
|
||||||
const w = new MkChooseFileFromDriveWindow({
|
const w = os.new(MkChooseFileFromDriveWindow, {
|
||||||
propsData: {
|
title: o.title,
|
||||||
title: o.title,
|
multiple: o.multiple,
|
||||||
multiple: o.multiple,
|
initFolder: o.currentFolder
|
||||||
initFolder: o.currentFolder
|
});
|
||||||
}
|
|
||||||
}).$mount();
|
|
||||||
w.$once('selected', file => {
|
w.$once('selected', file => {
|
||||||
res(file);
|
res(file);
|
||||||
});
|
});
|
||||||
|
@ -22,9 +21,9 @@ export default function(opts) {
|
||||||
res(file);
|
res(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.open(url + '/selectdrive',
|
window.open(url + `/selectdrive?multiple=${o.multiple}`,
|
||||||
'choose_drive_window',
|
'choose_drive_window',
|
||||||
'height=500, width=800');
|
'height=500, width=800');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
|
import OS from '../../mios';
|
||||||
import MkChooseFolderFromDriveWindow from '../views/components/choose-folder-from-drive-window.vue';
|
import MkChooseFolderFromDriveWindow from '../views/components/choose-folder-from-drive-window.vue';
|
||||||
|
|
||||||
export default function(opts) {
|
export default (os: OS) => opts => {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
const o = opts || {};
|
const o = opts || {};
|
||||||
const w = new MkChooseFolderFromDriveWindow({
|
const w = os.new(MkChooseFolderFromDriveWindow, {
|
||||||
propsData: {
|
title: o.title,
|
||||||
title: o.title,
|
initFolder: o.currentFolder
|
||||||
initFolder: o.currentFolder
|
});
|
||||||
}
|
|
||||||
}).$mount();
|
|
||||||
w.$once('selected', folder => {
|
w.$once('selected', folder => {
|
||||||
res(folder);
|
res(folder);
|
||||||
});
|
});
|
||||||
document.body.appendChild(w.$el);
|
document.body.appendChild(w.$el);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
|
import OS from '../../mios';
|
||||||
import Ctx from '../views/components/context-menu.vue';
|
import Ctx from '../views/components/context-menu.vue';
|
||||||
|
|
||||||
export default function(e, menu, opts?) {
|
export default (os: OS) => (e, menu, opts?) => {
|
||||||
const o = opts || {};
|
const o = opts || {};
|
||||||
const vm = new Ctx({
|
const vm = os.new(Ctx, {
|
||||||
propsData: {
|
menu,
|
||||||
menu,
|
x: e.pageX - window.pageXOffset,
|
||||||
x: e.pageX - window.pageXOffset,
|
y: e.pageY - window.pageYOffset,
|
||||||
y: e.pageY - window.pageYOffset,
|
});
|
||||||
}
|
|
||||||
}).$mount();
|
|
||||||
vm.$once('closed', () => {
|
vm.$once('closed', () => {
|
||||||
if (o.closed) o.closed();
|
if (o.closed) o.closed();
|
||||||
});
|
});
|
||||||
document.body.appendChild(vm.$el);
|
document.body.appendChild(vm.$el);
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
|
import OS from '../../mios';
|
||||||
import Dialog from '../views/components/dialog.vue';
|
import Dialog from '../views/components/dialog.vue';
|
||||||
|
|
||||||
export default function(opts) {
|
export default (os: OS) => opts => {
|
||||||
return new Promise<string>((res, rej) => {
|
return new Promise<string>((res, rej) => {
|
||||||
const o = opts || {};
|
const o = opts || {};
|
||||||
const d = new Dialog({
|
const d = os.new(Dialog, {
|
||||||
propsData: {
|
title: o.title,
|
||||||
title: o.title,
|
text: o.text,
|
||||||
text: o.text,
|
modal: o.modal,
|
||||||
modal: o.modal,
|
buttons: o.actions
|
||||||
buttons: o.actions
|
});
|
||||||
}
|
|
||||||
}).$mount();
|
|
||||||
d.$once('clicked', id => {
|
d.$once('clicked', id => {
|
||||||
res(id);
|
res(id);
|
||||||
});
|
});
|
||||||
document.body.appendChild(d.$el);
|
document.body.appendChild(d.$el);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
|
import OS from '../../mios';
|
||||||
import InputDialog from '../views/components/input-dialog.vue';
|
import InputDialog from '../views/components/input-dialog.vue';
|
||||||
|
|
||||||
export default function(opts) {
|
export default (os: OS) => opts => {
|
||||||
return new Promise<string>((res, rej) => {
|
return new Promise<string>((res, rej) => {
|
||||||
const o = opts || {};
|
const o = opts || {};
|
||||||
const d = new InputDialog({
|
const d = os.new(InputDialog, {
|
||||||
propsData: {
|
title: o.title,
|
||||||
title: o.title,
|
placeholder: o.placeholder,
|
||||||
placeholder: o.placeholder,
|
default: o.default,
|
||||||
default: o.default,
|
type: o.type || 'text',
|
||||||
type: o.type || 'text',
|
allowEmpty: o.allowEmpty
|
||||||
allowEmpty: o.allowEmpty
|
});
|
||||||
}
|
|
||||||
}).$mount();
|
|
||||||
d.$once('done', text => {
|
d.$once('done', text => {
|
||||||
res(text);
|
res(text);
|
||||||
});
|
});
|
||||||
document.body.appendChild(d.$el);
|
document.body.appendChild(d.$el);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
|
import OS from '../../mios';
|
||||||
import Notification from '../views/components/ui-notification.vue';
|
import Notification from '../views/components/ui-notification.vue';
|
||||||
|
|
||||||
export default function(message) {
|
export default (os: OS) => message => {
|
||||||
const vm = new Notification({
|
const vm = os.new(Notification, {
|
||||||
propsData: {
|
message
|
||||||
message
|
});
|
||||||
}
|
|
||||||
}).$mount();
|
|
||||||
document.body.appendChild(vm.$el);
|
document.body.appendChild(vm.$el);
|
||||||
}
|
};
|
||||||
|
|