forked from FoundKeyGang/FoundKey
Merge branch 'develop' into l10n_develop
This commit is contained in:
commit
9c170c426b
504 changed files with 11762 additions and 8715 deletions
|
@ -1,18 +1,19 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# BEARER_TOKEN=
|
# __MISSKEY_BEARER_TOKEN=
|
||||||
# CAMPAIGN_ID=
|
# __MISSKEY_CAMPAIGN_ID=
|
||||||
# GITHUB_TOKEN=
|
# __MISSKEY_GITHUB_TOKEN=
|
||||||
# HEAD='acid-chicken:patch-autogen'
|
# __MISSKEY_HEAD=acid-chicken:patch-autogen
|
||||||
# REPO='syuilo/misskey'
|
# __MISSKEY_REPO=syuilo/misskey
|
||||||
test "$(curl -LSs -w '\n' -- "https://api.github.com/repos/$REPO/pulls?access_token=$GITHUB_TOKEN" | jq -r '.[].head.label' | grep $HEAD)" && exit 1
|
# __MISSKEY_BRANCH=develop
|
||||||
|
test "$(curl -LSs -w '\n' -- "https://api.github.com/repos/$REPO/pulls?access_token=$__MISSKEY_GITHUB_TOKEN" | jq -r '.[].head.label' | grep $__MISSKEY_HEAD)" && exit 1
|
||||||
cd "$(dirname $0)/.." && \
|
cd "$(dirname $0)/.." && \
|
||||||
touch null.cache && \
|
touch null.cache && \
|
||||||
rm *.cache && \
|
rm *.cache && \
|
||||||
git checkout master && \
|
git checkout $__MISSKEY_BRANCH && \
|
||||||
git pull origin master && \
|
git pull origin $__MISSKEY_BRANCH && \
|
||||||
git pull upstream master && \
|
git pull upstream $__MISSKEY_BRANCH && \
|
||||||
git stash && \
|
git stash && \
|
||||||
git rebase -f upstream/master && \
|
git rebase -f upstream/$__MISSKEY_BRANCH && \
|
||||||
git branch patch-autogen && \
|
git branch patch-autogen && \
|
||||||
git checkout patch-autogen && \
|
git checkout patch-autogen && \
|
||||||
git reset --hard HEAD || \
|
git reset --hard HEAD || \
|
||||||
|
@ -20,12 +21,12 @@ exit 1
|
||||||
touch patreon.md.cache && \
|
touch patreon.md.cache && \
|
||||||
rm patreon.md.cache && \
|
rm patreon.md.cache && \
|
||||||
echo '<!-- PATREON_START -->' > patreon.md.cache && \
|
echo '<!-- PATREON_START -->' > patreon.md.cache && \
|
||||||
URL="https://www.patreon.com/api/oauth2/v2/campaigns/$CAMPAIGN_ID/members?include=currently_entitled_tiers,user&fields%5Btier%5D=title&fields%5Buser%5D=full_name,thumb_url,url,hide_pledges"
|
url="https://www.patreon.com/api/oauth2/v2/campaigns/$__MISSKEY_CAMPAIGN_ID/members?include=currently_entitled_tiers,user&fields%5Btier%5D=title&fields%5Buser%5D=full_name,thumb_url,url,hide_pledges"
|
||||||
while :
|
while :
|
||||||
do
|
do
|
||||||
touch patreon.raw.cache && \
|
touch patreon.raw.cache && \
|
||||||
rm patreon.raw.cache && \
|
rm patreon.raw.cache && \
|
||||||
curl -LSs -w '\n' -H "Authorization: Bearer $BEARER_TOKEN" -- $URL > patreon.raw.cache && \
|
curl -LSs -w '\n' -H "Authorization: Bearer $__MISSKEY_BEARER_TOKEN" -- $url > patreon.raw.cache && \
|
||||||
touch patreon.cache && \
|
touch patreon.cache && \
|
||||||
rm patreon.cache && \
|
rm patreon.cache && \
|
||||||
cat patreon.raw.cache | \
|
cat patreon.raw.cache | \
|
||||||
|
@ -42,31 +43,31 @@ while :
|
||||||
xargs -I% echo '<td><a href="%</a></td>' >> patreon.md.cache && \
|
xargs -I% echo '<td><a href="%</a></td>' >> patreon.md.cache && \
|
||||||
echo '</tr></table>' >> patreon.md.cache || \
|
echo '</tr></table>' >> patreon.md.cache || \
|
||||||
exit 1
|
exit 1
|
||||||
NEW_URL="$(cat patreon.raw.cache | jq -r '.links.next')"
|
new_url="$(cat patreon.raw.cache | jq -r '.links.next')"
|
||||||
test "$NEW_URL" = 'null' && \
|
test "$new_url" = 'null' && \
|
||||||
break || \
|
break || \
|
||||||
URL="$NEW_URL"
|
URL="$url"
|
||||||
done
|
done
|
||||||
IGNORE= && \
|
ignore= && \
|
||||||
echo -e "\n**Last updated:** $(date -uR | sed 's/\+0000/UTC/')\n<!-- PATREON_END -->" >> patreon.md.cache && \
|
echo -e "\n**Last updated:** $(date -uR | sed 's/\+0000/UTC/')\n<!-- PATREON_END -->" >> patreon.md.cache && \
|
||||||
touch README.md && \
|
touch README.md && \
|
||||||
touch .autogen/README.md && \
|
touch .autogen/README.md && \
|
||||||
rm .autogen/README.md && \
|
rm .autogen/README.md && \
|
||||||
mv README.md .autogen/README.md && \
|
mv README.md .autogen/README.md && \
|
||||||
cat .autogen/README.md | while IFS= read LINE;
|
cat .autogen/README.md | while IFS= read line;
|
||||||
do
|
do
|
||||||
if [[ -z "$IGNORE" ]]
|
if [[ -z "$ignore" ]]
|
||||||
then
|
then
|
||||||
if [[ "$LINE" = '<!-- PATREON_START -->' ]]
|
if [[ "$line" = '<!-- PATREON_START -->' ]]
|
||||||
then
|
then
|
||||||
IGNORE='PATREON_INSIDE'
|
ignore='PATREON_INSIDE'
|
||||||
else
|
else
|
||||||
echo "$LINE" >> README.md
|
echo "$line" >> README.md
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ "$LINE" = '<!-- PATREON_END -->' ]]
|
if [[ "$LINE" = '<!-- PATREON_END -->' ]]
|
||||||
then
|
then
|
||||||
IGNORE=
|
ignore=
|
||||||
cat patreon.md.cache >> README.md
|
cat patreon.md.cache >> README.md
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
@ -80,7 +81,7 @@ test 4 -lt $(cat diff.cache | wc -l) && \
|
||||||
git add README.md && \
|
git add README.md && \
|
||||||
git commit -m 'Update README.md [AUTOGEN]' && \
|
git commit -m 'Update README.md [AUTOGEN]' && \
|
||||||
git push -f origin patch-autogen && \
|
git push -f origin patch-autogen && \
|
||||||
curl -LSs -w '\n' -X POST -d '{"title":"[AUTOMATED] Update README.md","body":"*This pull request was created by a tool.*","head":"'$HEAD'","base":"master"}' -- "https://api.github.com/repos/$REPO/pulls?access_token=$GITHUB_TOKEN"
|
curl -LSs -w '\n' -X POST -d '{"title":"[AUTOMATED] Update README.md","body":"*This pull request was created by a tool.*","head":"'$__MISSKEY_HEAD'","base":"'$__MISSKEY_BRANCH'"}' -- "https://api.github.com/repos/$__MISSKEY_REPO/pulls?access_token=$__MISSKEY_GITHUB_TOKEN"
|
||||||
git stash
|
git stash
|
||||||
git checkout master
|
git checkout $__MISSKEY_BRANCH
|
||||||
git branch -D patch-autogen
|
git branch -D patch-autogen
|
||||||
|
|
|
@ -7,27 +7,51 @@ maintainer:
|
||||||
repository_url: https://github.com/syuilo/misskey # Repository URL
|
repository_url: https://github.com/syuilo/misskey # Repository URL
|
||||||
feedback_url: https://github.com/syuilo/misskey/issues # Feedback URL (e.g. github issue)
|
feedback_url: https://github.com/syuilo/misskey/issues # Feedback URL (e.g. github issue)
|
||||||
|
|
||||||
# URL and Port settings overview
|
|
||||||
# e.g., If you want to realize following structure:
|
|
||||||
#
|
|
||||||
# +--- https://example.com:123 ----------+
|
|
||||||
# +------+ |+-------------+ +---------------+|
|
|
||||||
# | User | ---> || Proxy (123) | ---> | Misskey (456) ||
|
|
||||||
# +------+ |+-------------+ +---------------+|
|
|
||||||
# +--------------------------------------+
|
|
||||||
#
|
|
||||||
# You need to set 'https://example.com:123' to 'url' prop and
|
|
||||||
# You need to set 456 to 'port' prop.
|
|
||||||
#
|
|
||||||
# In other words, the 'url' prop should be the final accessible URL seen by a user.
|
|
||||||
# 'port' prop is a port that the Misskey server should actually listen
|
|
||||||
# on and it is not necessarily the port that a user accesses.
|
|
||||||
|
|
||||||
url: http://localhost/
|
# Final accessible URL seen by a user.
|
||||||
|
url: https://example.tld/
|
||||||
|
|
||||||
|
|
||||||
|
### Port and TLS settings ######################################
|
||||||
|
#
|
||||||
|
# Misskey supports two deployment options for public.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Option 1: With Reverse Proxy
|
||||||
|
#
|
||||||
|
# +----- https://example.tld/ ------------+
|
||||||
|
# +------+ |+-------------+ +----------------+|
|
||||||
|
# | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
|
||||||
|
# +------+ |+-------------+ +----------------+|
|
||||||
|
# +---------------------------------------+
|
||||||
|
#
|
||||||
|
# You need to setup reverse proxy. (eg. Nginx)
|
||||||
|
# You do not define 'https' section.
|
||||||
|
|
||||||
|
# Option 2: Standalone
|
||||||
|
#
|
||||||
|
# +- https://example.tld/ -+
|
||||||
|
# +------+ | +---------------+ |
|
||||||
|
# | User | ---> | | Misskey (443) | |
|
||||||
|
# +------+ | +---------------+ |
|
||||||
|
# +------------------------+
|
||||||
|
#
|
||||||
|
# You need to run Misskey as root.
|
||||||
|
# You need to set Certificate in 'https' section.
|
||||||
|
|
||||||
|
# To use option 1, uncomment below line.
|
||||||
|
# port: 3000 # A port that your Misskey server should listen.
|
||||||
|
|
||||||
|
# To use option 2, uncomment below lines.
|
||||||
|
# port: 443
|
||||||
|
#
|
||||||
|
# https:
|
||||||
|
# # path for certification
|
||||||
|
# key: /etc/letsencrypt/live/example.tld/privkey.pem
|
||||||
|
# cert: /etc/letsencrypt/live/example.tld/fullchain.pem
|
||||||
|
|
||||||
|
################################################################
|
||||||
|
|
||||||
# A port that your Misskey server should listen.
|
|
||||||
# This value is not a port to use when accessing with a browser.
|
|
||||||
port: 80
|
|
||||||
|
|
||||||
mongodb:
|
mongodb:
|
||||||
host: localhost
|
host: localhost
|
||||||
|
@ -98,12 +122,6 @@ drive:
|
||||||
# Below settings are optional
|
# Below settings are optional
|
||||||
#
|
#
|
||||||
|
|
||||||
# TLS
|
|
||||||
# https:
|
|
||||||
# # path for certification
|
|
||||||
# key: example-tls-key
|
|
||||||
# cert: example-tls-cert
|
|
||||||
|
|
||||||
# Elasticsearch
|
# Elasticsearch
|
||||||
# elasticsearch:
|
# elasticsearch:
|
||||||
# host: localhost
|
# host: localhost
|
||||||
|
|
92
CHANGELOG.md
92
CHANGELOG.md
|
@ -5,6 +5,94 @@ ChangeLog
|
||||||
|
|
||||||
This document describes breaking changes only.
|
This document describes breaking changes only.
|
||||||
|
|
||||||
|
10.0.0
|
||||||
|
------
|
||||||
|
|
||||||
|
ストリーミングAPIに破壊的変更があります。運営者がすべきことはありません。
|
||||||
|
|
||||||
|
変更は以下の通りです
|
||||||
|
|
||||||
|
* ストリーミングでやり取りする際の snake_case が全て camelCase に
|
||||||
|
* リバーシのストリームエンドポイント名が reversi → gamesReversi、reversiGame → gamesReversiGame に
|
||||||
|
* ストリーミングの個々のエンドポイントが廃止され、一旦元となるストリームに接続してから、個々のチャンネル(今までのエンドポイント)に接続します。詳細は後述します。
|
||||||
|
* ストリームから流れてくる、キャプチャした投稿の更新イベントに投稿自体のデータは含まれず、代わりにアクションが設定されるようになります。詳細は後述します。
|
||||||
|
* ストリームに接続する際に追加で指定していたパラメータ(トークン除く)が、URLにクエリとして含むのではなくチャンネル接続時にパラメータ指定するように
|
||||||
|
|
||||||
|
### 個々のエンドポイントが廃止されることによる新しいストリーミングAPIの利用方法
|
||||||
|
具体的には、まず https://example.misskey/streaming にwebsocket接続します。
|
||||||
|
次に、例えば「messaging」ストリーム(チャンネルと呼びます)に接続したいときは、ストリームに次のようなデータを送信します:
|
||||||
|
``` javascript
|
||||||
|
{
|
||||||
|
type: 'connect',
|
||||||
|
body: {
|
||||||
|
channel: 'messaging',
|
||||||
|
id: 'foobar',
|
||||||
|
params: {
|
||||||
|
otherparty: 'xxxxxxxxxxxx'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
ここで、`id`にはそのチャンネルとやり取りするための任意のIDを設定します。
|
||||||
|
IDはチャンネルごとではなく「チャンネルの接続ごと」です。なぜなら、同じチャンネルに異なるパラメータで複数接続するケースもあるからです。
|
||||||
|
`params`はチャンネルに接続する際のパラメータです。チャンネルによって接続時に必要とされるパラメータは異なります。パラメータ不要のチャンネルに接続する際は、このプロパティは省略可能です。
|
||||||
|
|
||||||
|
チャンネルにメッセージを送信するには、次のようなデータを送信します:
|
||||||
|
``` javascript
|
||||||
|
{
|
||||||
|
type: 'channel',
|
||||||
|
body: {
|
||||||
|
id: 'foobar',
|
||||||
|
type: 'something',
|
||||||
|
body: {
|
||||||
|
some: 'thing'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
ここで、`id`にはチャンネルに接続するときに指定したIDを設定します。
|
||||||
|
|
||||||
|
逆に、チャンネルからメッセージが流れてくると、次のようなデータが受信されます:
|
||||||
|
``` javascript
|
||||||
|
{
|
||||||
|
type: 'channel',
|
||||||
|
body: {
|
||||||
|
id: 'foobar',
|
||||||
|
type: 'something',
|
||||||
|
body: {
|
||||||
|
some: 'thing'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
ここで、`id`にはチャンネルに接続するときに指定したIDが設定されています。
|
||||||
|
|
||||||
|
### 投稿のキャプチャに関する変更
|
||||||
|
投稿の更新イベントに投稿情報は含まれなくなりました。代わりに、その投稿が「リアクションされた」「アンケートに投票された」「削除された」といったアクション情報が設定されます。
|
||||||
|
|
||||||
|
具体的には次のようなデータが受信されます:
|
||||||
|
``` javascript
|
||||||
|
{
|
||||||
|
type: 'noteUpdated',
|
||||||
|
body: {
|
||||||
|
id: 'xxxxxxxxxxx',
|
||||||
|
type: 'reacted',
|
||||||
|
body: {
|
||||||
|
reaction: 'hmm'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* reacted ... 投稿にリアクションされた。`reaction`プロパティにリアクションコードが含まれます。
|
||||||
|
* pollVoted ... アンケートに投票された。`choice`プロパティに選択肢ID、`userId`に投票者IDが含まれます。
|
||||||
|
|
||||||
|
9.0.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
Misskey v8.64.0 を使っている方は、9.0.0に際しては特にすべきことはありません。
|
||||||
|
Misskey v8.64.0 に満たないバージョンをお使いの方は、一旦8.64.0にアップデートして(そして起動して)から9.0.0に再度アップデートしてください。
|
||||||
|
|
||||||
8.0.0
|
8.0.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -47,13 +135,13 @@ Please run `node cli/migration/5.0.0` before launch.
|
||||||
|
|
||||||
オセロがリバーシに変更されました。
|
オセロがリバーシに変更されました。
|
||||||
|
|
||||||
Othello is now Reversi.
|
Othello is rename to Reversi.
|
||||||
|
|
||||||
### Migration
|
### Migration
|
||||||
|
|
||||||
MongoDBの、`othelloGames`と`othelloMatchings`コレクションをそれぞれ`reversiGames`と`reversiMatchings`にリネームしてください。
|
MongoDBの、`othelloGames`と`othelloMatchings`コレクションをそれぞれ`reversiGames`と`reversiMatchings`にリネームしてください。
|
||||||
|
|
||||||
You need to rename `othelloGames` and `othelloMatchings` MongoDB collections to `reversiGames` and `reversiMatchings`.
|
Please rename `othelloGames` and `othelloMatchings` MongoDB collections to `reversiGames` and `reversiMatchings` respectively.
|
||||||
|
|
||||||
3.0.0
|
3.0.0
|
||||||
-----
|
-----
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
# Contribution guide
|
# Contribution guide
|
||||||
:v: Misskeyへの貢献ありがとうございます。 :v:
|
:v: Thanks for your contributions :v:
|
||||||
|
|
||||||
## Issueの報告
|
## Issues
|
||||||
新機能の提案や不具合の報告は https://github.com/syuilo/misskey/issues で管理しています。
|
Feature suggestions and bug reports are filed in https://github.com/syuilo/misskey/issues .
|
||||||
Issueを作成する前に、既に同じIssueが作成されていないかご確認ください。
|
Before creating a new issue, please search existing issues to avoid duplication.
|
||||||
もし既にIssueが作成されている場合は、既存のIssueにコメントをしたりリアクションをするようお願いします。
|
If you find the existing issue, please add your reaction or comment to the issue.
|
||||||
|
|
||||||
## Issueの解決
|
## Internationalization (i18n)
|
||||||
[pr-welcomeのラベルがついているIssue](https://github.com/syuilo/misskey/labels/pr-welcome)
|
Please see [Translation guide](./docs/translate.en.md).
|
||||||
の解決を目的としたPull Requestを作成してくださると非常にありがたいです。
|
|
||||||
|
|
||||||
## 翻訳の改善
|
## Localization (l10n)
|
||||||
ソースコード中の `%i18n:id%` という形の文字列は、言語ファイルの対応するテキストに置換されます。
|
Please use [Crowdin](https://crowdin.com/project/misskey) for localization.
|
||||||
言語ファイルは /locales ディレクトリに存在します。
|
|
||||||
|
|
||||||
## ドキュメントの編集
|
![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)
|
||||||
現在Misskeyはドキュメントが大きく不足しています。
|
|
||||||
ドキュメントは /docs ディレクトリに存在します。
|
|
||||||
|
|
||||||
## テストの追加
|
## Documentation
|
||||||
現在Misskeyはテストが大きく不足しています。
|
* Documents for contributors are located in `/docs`.
|
||||||
テストコードは /test ディレクトリに存在します。
|
* Documents for instance admins are located in `/docs`.
|
||||||
|
* Documents for end users are located in `src/docs`.
|
||||||
|
|
||||||
## 自動テスト及び自動リリース
|
## Test
|
||||||
Travis CIで行っています。
|
* Test codes are located in `/test`.
|
||||||
設定ファイルは /.travis に存在します。
|
|
||||||
|
## Continuous integration
|
||||||
|
Misskey uses Travis for automated test.
|
||||||
|
Configuration files are located in `/.travis`.
|
||||||
|
|
84
README.md
84
README.md
|
@ -1,4 +1,4 @@
|
||||||
<img src="https://github.com/syuilo/misskey/blob/b3f42e62af698a67c2250533c437569559f1fdf9/src/himasaku/resources/himasaku.png?raw=true" align="right" width="320px"/>
|
<img src="https://github.com/syuilo/misskey/blob/develop/assets/ai-orig.png?raw=true" align="right" height="320px"/>
|
||||||
|
|
||||||
[![Misskey](/assets/title.png)](https://misskey.xyz/)
|
[![Misskey](/assets/title.png)](https://misskey.xyz/)
|
||||||
================================================================
|
================================================================
|
||||||
|
@ -7,12 +7,12 @@
|
||||||
[![][dependencies-badge]][dependencies-link]
|
[![][dependencies-badge]][dependencies-link]
|
||||||
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Greenkeeper badge](https://badges.greenkeeper.io/syuilo/misskey.svg)](https://greenkeeper.io/)
|
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Greenkeeper badge](https://badges.greenkeeper.io/syuilo/misskey.svg)](https://greenkeeper.io/)
|
||||||
|
|
||||||
Sophisticated microblogging platform, evolving forever.
|
**Sophisticated microblogging platform, evolving forever.**
|
||||||
|
|
||||||
[Misskey](https://misskey.xyz) is a decentralized microblogging platform born on Earth.
|
[Misskey](https://misskey.xyz) is a decentralized microblogging platform born on Earth.
|
||||||
Since it exists within the Fediverse (a universe where various social media platforms are organized),
|
Since it exists within the Fediverse (a universe where various social media platforms are organized),
|
||||||
it is mutually linked with other social media platforms.
|
it is mutually linked with other social media platforms.
|
||||||
Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet?
|
Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet? [Find instance!](https://joinmisskey.github.io/)
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
|
@ -20,52 +20,70 @@ Why don't you take a short break from the hustle and bustle of the city, and div
|
||||||
|
|
||||||
:sparkles: Features
|
:sparkles: Features
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
* Rich text contents
|
|
||||||
* Reactions
|
|
||||||
* User lists
|
|
||||||
* Customizable column view (called MisskeyDeck)
|
|
||||||
* and widgets!
|
|
||||||
* Private messages
|
|
||||||
* ActivityPub support
|
|
||||||
|
|
||||||
and more! You can see it with your own eyes at [misskey.xyz](https://misskey.xyz).
|
<img src="/assets/about/post.png" align="left" height="200px"/>
|
||||||
|
|
||||||
|
<h3 align="left">Posting</h3>
|
||||||
|
<p align="left">
|
||||||
|
Just post your idea, hot topics and anything you want to share. You may want to decorate your words, attach your favorite pictures, send files including movies and create a poll - those are the things you can do on Misskey!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<img src="/assets/about/reaction.png" align="right" height="200px"/>
|
||||||
|
|
||||||
|
<h3 align="right">Reactions</h3>
|
||||||
|
<p align="right">
|
||||||
|
Easiest way to tell your emotions. Misskey allows you to add various type of reactions to other’s post. The emotional experience on Misskey will never be on other SNSs which only able to push “likes”.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<img src="/assets/about/ui.png" align="left" height="200px"/>
|
||||||
|
|
||||||
|
<h3 align="left">Interface</h3>
|
||||||
|
<p align="left">
|
||||||
|
No UI fits for everyone. Therefore, Misskey has a highly customizable UI for your taste. You can edit layouts of your timeline, place selectable widgets you can easily move and create your unique home as this place will be your home.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<img src="/assets/about/drive.png" align="right" width="300px"/>
|
||||||
|
|
||||||
|
<h3 align="right">Misskey Drive</h3>
|
||||||
|
<p align="right">
|
||||||
|
Wanna post a picture you have already uploaded? Wish to organize, name and create a folder for your uploaded files? Misskey Drive is the best solution for you. Very easy to share your files online.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
and more! You can see it with your own eyes at [misskey.xyz](https://misskey.xyz) or [other instances](https://joinmisskey.github.io/).
|
||||||
|
|
||||||
:package: Create your own instance
|
:package: Create your own instance
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
If you want to run your own instance of Misskey,
|
Please see [Setup and installation guide](./docs/setup.en.md).
|
||||||
please see [Setup and installation guide](./docs/setup.en.md).
|
|
||||||
|
|
||||||
:wrench: Contribute
|
:wrench: Contribution
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
**[PR](https://github.com/syuilo/misskey/pulls)s welcome!**
|
Please see [Contribution guide](./CONTRIBUTING.md).
|
||||||
|
|
||||||
### i18n
|
|
||||||
|
|
||||||
Please see [Translation guide](./docs/translate.en.md).
|
|
||||||
|
|
||||||
### l10n
|
|
||||||
|
|
||||||
Misskey is using Crowdin for l10n.
|
|
||||||
|
|
||||||
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)](https://crowdin.com/project/misskey)
|
|
||||||
|
|
||||||
:heart: Backers & Sponsors
|
:heart: Backers & Sponsors
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
<!-- PATREON_START -->
|
<!-- PATREON_START -->
|
||||||
<table><tr>
|
<table><tr>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12378075/0156f769e20f412594fa6b87d85fe228/1?token-time=2145916800&token-hash=IsIJRUXszzoD6-7pDnRY8I05T9nSznc4GTaxj7C9SwU%3D" alt="39ff"></td>
|
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D" alt="negao"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D" alt="negao"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1?token-time=2145916800&token-hash=d6P5MWHHsCMxUuBAEPAoVc5wLUR19mIhqAq7Ma9h9rI%3D" alt="ne_moni"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1?token-time=2145916800&token-hash=d6P5MWHHsCMxUuBAEPAoVc5wLUR19mIhqAq7Ma9h9rI%3D" alt="ne_moni"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/1?token-time=2145916800&token-hash=f03BFb4S2FUx9YEt87TnEmifb4h33OywGBW2akQVtQY%3D" alt="Melilot"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/2?token-time=2145916800&token-hash=mgPdX9TqZxEg4TTPuc477dxhIgYk9246qafjWZEqZ7g%3D" alt="Melilot"></td>
|
||||||
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/2?token-time=2145916800&token-hash=rwZ8qvbm_kpA4ib3kc07tVKupXeySpY5ATQFGxfL9v0%3D" alt="Xeltica"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td>
|
||||||
<td><img src="https://c8.patreon.com/2/100/12718187" alt="Peter G."></td>
|
<td><img src="https://c8.patreon.com/2/100/12718187" alt="Peter G."></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=zwSu01tOtn5xTUucDZHuPsCxF2HBEMVs9ROJKTlEV_o%3D" alt="nemu"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=zwSu01tOtn5xTUucDZHuPsCxF2HBEMVs9ROJKTlEV_o%3D" alt="nemu"></td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><a href="https://www.patreon.com/user?u=12378075">39ff</a></td>
|
<td><a href="https://www.patreon.com/negao">negao</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=12731202">negao</a></td>
|
|
||||||
<td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td>
|
<td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
|
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
|
||||||
|
<td><a href="https://www.patreon.com/AxellaMC">Xeltica</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=3384329">べすれい</a></td>
|
<td><a href="https://www.patreon.com/user?u=3384329">べすれい</a></td>
|
||||||
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
|
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td>
|
<td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td>
|
||||||
|
@ -73,23 +91,17 @@ Misskey is using Crowdin for l10n.
|
||||||
</tr></table>
|
</tr></table>
|
||||||
<table><tr>
|
<table><tr>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/2?token-time=2145916800&token-hash=zElv7ZcPL3viGsXbNG_KWiKrbV0vvw1gk0panx8DJoo%3D" alt="Naoki Kosaka"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/2?token-time=2145916800&token-hash=zElv7ZcPL3viGsXbNG_KWiKrbV0vvw1gk0panx8DJoo%3D" alt="Naoki Kosaka"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12931605/ead494101f364dffa90efe49e36fb494/1?token-time=2145916800&token-hash=NzSFPjIlodXyv41rwK61aZWVZWfI4surJaNj8vWKvqM%3D" alt="Reiju"></td>
|
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=UERBN4OyP7Nh5XwwdDg0N0IE5cD6_qUQMO81Z5Wizso%3D" alt="Hiratake"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=UERBN4OyP7Nh5XwwdDg0N0IE5cD6_qUQMO81Z5Wizso%3D" alt="Hiratake"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D" alt="dansup"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D" alt="dansup"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4950409/28e7d016209243759d9316be2e21381d/2?token-time=2145916800&token-hash=LuEaDkchH3GQWUcTOhBQ8xfKQYF0s5FjlZRd7Yduia8%3D" alt="mikan54951"></td>
|
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D" alt="Takashi Shibuya"></td>
|
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D" alt="Takashi Shibuya"></td>
|
||||||
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12959468/c249e15aebec4424b5c0f427173671b6/1?token-time=2145916800&token-hash=lubpCEdxAkxPlpR2O6bvZ7BIh8Q4nGf-U_mE1qpjVAQ%3D" alt="fujishan"></td>
|
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</a></td>
|
<td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=12931605">Reiju</a></td>
|
|
||||||
<td><a href="https://www.patreon.com/hiratake">Hiratake</a></td>
|
<td><a href="https://www.patreon.com/hiratake">Hiratake</a></td>
|
||||||
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
|
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
|
||||||
<td><a href="https://www.patreon.com/user?u=4950409">mikan54951</a></td>
|
|
||||||
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
|
||||||
<td><a href="https://www.patreon.com/fujishan">fujishan</a></td>
|
|
||||||
</tr></table>
|
</tr></table>
|
||||||
|
|
||||||
**Last updated:** Wed, 22 Aug 2018 05:25:06 UTC
|
**Last updated:** Tue, 02 Oct 2018 09:25:07 UTC
|
||||||
<!-- PATREON_END -->
|
<!-- PATREON_END -->
|
||||||
|
|
||||||
:four_leaf_clover: Copyright
|
:four_leaf_clover: Copyright
|
||||||
|
|
BIN
assets/about/drive.png
Normal file
BIN
assets/about/drive.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 110 KiB |
BIN
assets/about/post.png
Normal file
BIN
assets/about/post.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 344 KiB |
BIN
assets/about/reaction.png
Normal file
BIN
assets/about/reaction.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
assets/about/ui.png
Normal file
BIN
assets/about/ui.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 125 KiB |
BIN
assets/ai-orig.png
Normal file
BIN
assets/ai-orig.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 256 KiB |
BIN
assets/ai.png
Normal file
BIN
assets/ai.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 243 KiB |
|
@ -54,7 +54,7 @@ Please visit https://www.google.com/recaptcha/intro/ and generate keys.
|
||||||
|
|
||||||
*(optional)* Generating VAPID keys
|
*(optional)* Generating VAPID keys
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
If you want to enable ServiceWroker, you need to generate VAPID keys:
|
If you want to enable ServiceWorker, you need to generate VAPID keys:
|
||||||
Unless you have set your global node_modules location elsewhere, you need to run this in root.
|
Unless you have set your global node_modules location elsewhere, you need to run this in root.
|
||||||
|
|
||||||
``` shell
|
``` shell
|
||||||
|
@ -131,6 +131,7 @@ You can check if the service is running with `systemctl status misskey`.
|
||||||
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
|
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
|
||||||
3. `npm install`
|
3. `npm install`
|
||||||
4. `npm run build`
|
4. `npm run build`
|
||||||
|
5. Check [ChangeLog](../CHANGELOG.md) for migration information
|
||||||
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ Misskeyサーバーの構築にご関心をお寄せいただきありがとう
|
||||||
|
|
||||||
*1.* Misskeyユーザーの作成
|
*1.* Misskeyユーザーの作成
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
Misskeyのrootで実行しない方がよいため、代わりにユーザーを作成します。
|
Misskeyはrootユーザーで実行しない方がよいため、代わりにユーザーを作成します。
|
||||||
Debianの例:
|
Debianの例:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -109,6 +109,7 @@ Restart=always
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
```
|
```
|
||||||
|
CentOSで1024以下のポートを使用してMisskeyを使用する場合は`ExecStart=/usr/bin/sudo /usr/bin/npm start`に変更する必要があります。
|
||||||
|
|
||||||
3. `systemctl daemon-reload ; systemctl enable misskey` systemdを再読み込みしmisskeyサービスを有効化
|
3. `systemctl daemon-reload ; systemctl enable misskey` systemdを再読み込みしmisskeyサービスを有効化
|
||||||
4. `systemctl start misskey` misskeyサービスの起動
|
4. `systemctl start misskey` misskeyサービスの起動
|
||||||
|
@ -120,6 +121,7 @@ WantedBy=multi-user.target
|
||||||
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
|
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
|
||||||
3. `npm install`
|
3. `npm install`
|
||||||
4. `npm run build`
|
4. `npm run build`
|
||||||
|
5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する
|
||||||
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
* Gulp tasks
|
* Gulp tasks
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as gulp from 'gulp';
|
import * as gulp from 'gulp';
|
||||||
import * as gutil from 'gulp-util';
|
import * as gutil from 'gulp-util';
|
||||||
import * as ts from 'gulp-typescript';
|
import * as ts from 'gulp-typescript';
|
||||||
|
@ -78,7 +77,7 @@ gulp.task('build:copy', ['build:copy:views', 'build:copy:lang'], () =>
|
||||||
]).pipe(gulp.dest('./built/'))
|
]).pipe(gulp.dest('./built/'))
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task('test', ['lint', 'mocha']);
|
gulp.task('test', ['mocha']);
|
||||||
|
|
||||||
gulp.task('lint', () =>
|
gulp.task('lint', () =>
|
||||||
gulp.src('./src/**/*.ts')
|
gulp.src('./src/**/*.ts')
|
||||||
|
@ -166,9 +165,7 @@ gulp.task('build:client:pug', [
|
||||||
.pipe(pug({
|
.pipe(pug({
|
||||||
locals: {
|
locals: {
|
||||||
themeColor: constants.themeColor,
|
themeColor: constants.themeColor,
|
||||||
facss: fa.dom.css(),
|
facss: fa.dom.css()
|
||||||
//hljscss: fs.readFileSync('./node_modules/highlight.js/styles/default.css', 'utf8')
|
|
||||||
hljscss: fs.readFileSync('./src/client/assets/code-highlight.css', 'utf8')
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.pipe(htmlmin({
|
.pipe(htmlmin({
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
# **Please DO NOT edit these files** except `ja-JP.yml`.
|
# **DO NOT edit locale files** except `ja-JP.yml`.
|
||||||
|
|
||||||
If you want to...
|
Please see [Contribution guide](../CONTRIBUTING.md) for more information.
|
||||||
* i18n ... please see [Translation guide](../docs/translate.en.md).
|
|
||||||
* l10n ... please visit https://crowdin.com/project/misskey
|
|
||||||
|
|
|
@ -5,24 +5,9 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
|
|
||||||
const loadLang = lang => yaml.safeLoad(
|
const langs = ['de-DE', 'en-US', 'fr-FR', 'ja-JP', 'ja-KS', 'pl-PL', 'es-ES', 'nl-NL'];
|
||||||
fs.readFileSync(`${__dirname}/${lang}.yml`, 'utf-8'));
|
|
||||||
|
|
||||||
const native = loadLang('ja-JP');
|
const loadLocale = lang => yaml.safeLoad(fs.readFileSync(`${__dirname}/${lang}.yml`, 'utf-8'));
|
||||||
|
const locales = langs.map(lang => ({ [lang]: loadLocale(lang) }));
|
||||||
|
|
||||||
const langs = {
|
module.exports = locales.reduce((a, b) => ({ ...a, ...b }));
|
||||||
'de-DE': loadLang('de-DE'),
|
|
||||||
'en-US': loadLang('en-US'),
|
|
||||||
'fr-FR': loadLang('fr-FR'),
|
|
||||||
'ja-JP': native,
|
|
||||||
'ja-KS': loadLang('ja-KS'),
|
|
||||||
'pl-PL': loadLang('pl-PL'),
|
|
||||||
'es-ES': loadLang('es-ES')
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.values(langs).forEach(locale => {
|
|
||||||
// Extend native language (Japanese)
|
|
||||||
locale = Object.assign({}, native, locale);
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = langs;
|
|
||||||
|
|
|
@ -6,6 +6,19 @@ common:
|
||||||
misskey: "A ⭐ of fediverse"
|
misskey: "A ⭐ of fediverse"
|
||||||
about-title: "A ⭐ of fediverse."
|
about-title: "A ⭐ of fediverse."
|
||||||
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
|
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
|
||||||
|
intro:
|
||||||
|
title: "Misskeyって?"
|
||||||
|
about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
|
||||||
|
features: "特徴"
|
||||||
|
rich-contents: "投稿"
|
||||||
|
rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
|
||||||
|
reaction: "リアクション"
|
||||||
|
reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
|
||||||
|
ui: "インターフェース"
|
||||||
|
ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
|
||||||
|
drive: "ドライブ"
|
||||||
|
drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
|
||||||
|
outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
|
||||||
adblock:
|
adblock:
|
||||||
detected: "広告ブロッカーを無効にしてください"
|
detected: "広告ブロッカーを無効にしてください"
|
||||||
warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
|
warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
|
||||||
|
@ -73,6 +86,16 @@ common:
|
||||||
rip: "RIP"
|
rip: "RIP"
|
||||||
pudding: "Pudding"
|
pudding: "Pudding"
|
||||||
|
|
||||||
|
note-visibility:
|
||||||
|
public: "公開"
|
||||||
|
home: "ホーム"
|
||||||
|
home-desc: "ホームタイムラインにのみ公開"
|
||||||
|
followers: "フォロワー"
|
||||||
|
followers-desc: "自分のフォロワーにのみ公開"
|
||||||
|
specified: "ダイレクト"
|
||||||
|
specified-desc: "指定したユーザーにのみ公開"
|
||||||
|
private: "非公開"
|
||||||
|
|
||||||
note-placeholders:
|
note-placeholders:
|
||||||
a: "今どうしてる?"
|
a: "今どうしてる?"
|
||||||
b: "何かありましたか?"
|
b: "何かありましたか?"
|
||||||
|
@ -93,6 +116,13 @@ common:
|
||||||
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
|
||||||
verified-user: "公式アカウント"
|
verified-user: "公式アカウント"
|
||||||
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
|
||||||
|
always-show-nsfw: "常に閲覧注意のメディアを表示する"
|
||||||
|
always-mark-nsfw: "常にメディアを閲覧注意として投稿"
|
||||||
|
show-full-acct: "ユーザー名のホストを省略しない"
|
||||||
|
reduce-motion: "UIの動きを減らす"
|
||||||
|
this-setting-is-this-device-only: "このデバイスのみ"
|
||||||
|
|
||||||
|
do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
|
||||||
|
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "引き分け"
|
drawn: "引き分け"
|
||||||
|
@ -136,7 +166,10 @@ common:
|
||||||
home: "ホーム"
|
home: "ホーム"
|
||||||
local: "ローカル"
|
local: "ローカル"
|
||||||
hybrid: "ソーシャル"
|
hybrid: "ソーシャル"
|
||||||
|
hashtag: "ハッシュタグ"
|
||||||
global: "グローバル"
|
global: "グローバル"
|
||||||
|
mentions: "あなた宛て"
|
||||||
|
direct: "ダイレクト投稿"
|
||||||
notifications: "通知"
|
notifications: "通知"
|
||||||
list: "リスト"
|
list: "リスト"
|
||||||
swap-left: "左に移動"
|
swap-left: "左に移動"
|
||||||
|
@ -248,6 +281,47 @@ common/views/components/connect-failed.troubleshooter.vue:
|
||||||
flush: "キャッシュの削除"
|
flush: "キャッシュの削除"
|
||||||
set-version: "バージョン指定"
|
set-version: "バージョン指定"
|
||||||
|
|
||||||
|
common/views/components/media-banner.vue:
|
||||||
|
sensitive: "閲覧注意"
|
||||||
|
click-to-show: "クリックして表示"
|
||||||
|
|
||||||
|
common/views/components/theme.vue:
|
||||||
|
light-theme: "非ダークモード時に使用するテーマ"
|
||||||
|
dark-theme: "ダークモード時に使用するテーマ"
|
||||||
|
light-themes: "明るいテーマ"
|
||||||
|
dark-themes: "暗いテーマ"
|
||||||
|
install-a-theme: "テーマのインストール"
|
||||||
|
theme-code: "テーマコード"
|
||||||
|
install: "インストール"
|
||||||
|
installed: "「{}」をインストールしました"
|
||||||
|
create-a-theme: "テーマの作成"
|
||||||
|
save-created-theme: "テーマを保存"
|
||||||
|
primary-color: "プライマリ カラー"
|
||||||
|
secondary-color: "セカンダリ カラー"
|
||||||
|
text-color: "文字色"
|
||||||
|
base-theme: "ベーステーマ"
|
||||||
|
base-theme-light: "Light"
|
||||||
|
base-theme-dark: "Dark"
|
||||||
|
theme-name: "テーマ名"
|
||||||
|
preview-created-theme: "プレビュー"
|
||||||
|
invalid-theme: "テーマが正しくありません。"
|
||||||
|
already-installed: "既にそのテーマはインストールされています。"
|
||||||
|
saved: "保存しました"
|
||||||
|
installed-themes: "インストールされたテーマ"
|
||||||
|
select-theme: "テーマを選択してください"
|
||||||
|
uninstall: "アンインストール"
|
||||||
|
uninstalled: "「{}」をアンインストールしました"
|
||||||
|
author: "作者"
|
||||||
|
desc: "説明"
|
||||||
|
export: "エクスポート"
|
||||||
|
import: "インポート"
|
||||||
|
import-by-code: "またはコードをペースト"
|
||||||
|
theme-name-required: "テーマ名は必須です。"
|
||||||
|
|
||||||
|
common/views/components/cw-button.vue:
|
||||||
|
hide: "隠す"
|
||||||
|
show: "もっと見る"
|
||||||
|
|
||||||
common/views/components/messaging.vue:
|
common/views/components/messaging.vue:
|
||||||
search-user: "ユーザーを探す"
|
search-user: "ユーザーを探す"
|
||||||
you: "あなた"
|
you: "あなた"
|
||||||
|
@ -283,8 +357,11 @@ common/views/components/nav.vue:
|
||||||
feedback: "フィードバック"
|
feedback: "フィードバック"
|
||||||
|
|
||||||
common/views/components/note-menu.vue:
|
common/views/components/note-menu.vue:
|
||||||
|
detail: "詳細"
|
||||||
|
copy-link: "リンクをコピー"
|
||||||
favorite: "お気に入り"
|
favorite: "お気に入り"
|
||||||
pin: "ピン留め"
|
pin: "ピン留め"
|
||||||
|
unpin: "ピン留め解除"
|
||||||
delete: "削除"
|
delete: "削除"
|
||||||
delete-confirm: "この投稿を削除しますか?"
|
delete-confirm: "この投稿を削除しますか?"
|
||||||
remote: "投稿元で見る"
|
remote: "投稿元で見る"
|
||||||
|
@ -371,6 +448,10 @@ common/views/components/visibility-chooser.vue:
|
||||||
specified-desc: "指定したユーザーにのみ公開"
|
specified-desc: "指定したユーザーにのみ公開"
|
||||||
private: "非公開"
|
private: "非公開"
|
||||||
|
|
||||||
|
common/views/components/trends.vue:
|
||||||
|
count: "{}人が投稿"
|
||||||
|
empty: "トレンドなし"
|
||||||
|
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "確認中"
|
fetching: "確認中"
|
||||||
no-broadcasts: "お知らせはありません"
|
no-broadcasts: "お知らせはありません"
|
||||||
|
@ -399,8 +480,6 @@ common/views/widgets/posts-monitor.vue:
|
||||||
|
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
count: "{}人が投稿"
|
|
||||||
empty: "トレンドなし"
|
|
||||||
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
|
@ -443,6 +522,7 @@ common/views/pages/follow.vue:
|
||||||
following: "フォロー中"
|
following: "フォロー中"
|
||||||
follow: "フォロー"
|
follow: "フォロー"
|
||||||
request-pending: "フォロー許可待ち"
|
request-pending: "フォロー許可待ち"
|
||||||
|
follow-processing: "フォロー処理中"
|
||||||
follow-request: "フォロー申請"
|
follow-request: "フォロー申請"
|
||||||
|
|
||||||
desktop:
|
desktop:
|
||||||
|
@ -481,17 +561,21 @@ desktop/views/components/charts.vue:
|
||||||
notes: "投稿"
|
notes: "投稿"
|
||||||
users: "ユーザー"
|
users: "ユーザー"
|
||||||
drive: "ドライブ"
|
drive: "ドライブ"
|
||||||
|
network: "ネットワーク"
|
||||||
charts:
|
charts:
|
||||||
notes: "投稿の増減 (統合)"
|
notes: "投稿の増減 (統合)"
|
||||||
local-notes: "投稿の増減 (ローカル)"
|
local-notes: "投稿の増減 (ローカル)"
|
||||||
remote-notes: "投稿の増減 (リモート)"
|
remote-notes: "投稿の増減 (リモート)"
|
||||||
notes-total: "投稿の累計"
|
notes-total: "投稿の積算"
|
||||||
users: "ユーザーの増減"
|
users: "ユーザーの増減"
|
||||||
users-total: "ユーザーの累計"
|
users-total: "ユーザーの積算"
|
||||||
drive: "ドライブ使用量の増減"
|
drive: "ドライブ使用量の増減"
|
||||||
drive-total: "ドライブ使用量の累計"
|
drive-total: "ドライブ使用量の積算"
|
||||||
drive-files: "ドライブのファイル数の増減"
|
drive-files: "ドライブのファイル数の増減"
|
||||||
drive-files-total: "ドライブのファイル数の累計"
|
drive-files-total: "ドライブのファイル数の積算"
|
||||||
|
network-requests: "リクエスト"
|
||||||
|
network-time: "応答時間"
|
||||||
|
network-usage: "通信量"
|
||||||
|
|
||||||
desktop/views/components/choose-file-from-drive-window.vue:
|
desktop/views/components/choose-file-from-drive-window.vue:
|
||||||
choose-file: "ファイル選択中"
|
choose-file: "ファイル選択中"
|
||||||
|
@ -581,6 +665,7 @@ desktop/views/components/follow-button.vue:
|
||||||
following: "フォロー中"
|
following: "フォロー中"
|
||||||
follow: "フォロー"
|
follow: "フォロー"
|
||||||
request-pending: "フォロー許可待ち"
|
request-pending: "フォロー許可待ち"
|
||||||
|
follow-processing: "フォロー処理中"
|
||||||
follow-request: "フォロー申請"
|
follow-request: "フォロー申請"
|
||||||
|
|
||||||
desktop/views/components/followers-window.vue:
|
desktop/views/components/followers-window.vue:
|
||||||
|
@ -637,8 +722,6 @@ desktop/views/components/notes.note.vue:
|
||||||
detail: "詳細"
|
detail: "詳細"
|
||||||
private: "この投稿は非公開です"
|
private: "この投稿は非公開です"
|
||||||
deleted: "この投稿は削除されました"
|
deleted: "この投稿は削除されました"
|
||||||
hide: "隠す"
|
|
||||||
see-more: "もっと見る"
|
|
||||||
|
|
||||||
desktop/views/components/notes.vue:
|
desktop/views/components/notes.vue:
|
||||||
error: "読み込みに失敗しました。"
|
error: "読み込みに失敗しました。"
|
||||||
|
@ -714,10 +797,14 @@ desktop/views/components/settings.vue:
|
||||||
2fa: "二段階認証"
|
2fa: "二段階認証"
|
||||||
other: "その他"
|
other: "その他"
|
||||||
license: "ライセンス"
|
license: "ライセンス"
|
||||||
|
theme: "テーマ"
|
||||||
|
|
||||||
behaviour: "動作"
|
behaviour: "動作"
|
||||||
fetch-on-scroll: "スクロールで自動読み込み"
|
fetch-on-scroll: "スクロールで自動読み込み"
|
||||||
fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
|
fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
|
||||||
|
note-visibility: "投稿の公開範囲"
|
||||||
|
default-note-visibility: "デフォルトの公開範囲"
|
||||||
|
remember-note-visibility: "投稿の公開範囲を記憶する"
|
||||||
auto-popout: "ウィンドウの自動ポップアウト"
|
auto-popout: "ウィンドウの自動ポップアウト"
|
||||||
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
|
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
|
||||||
advanced: "詳細設定"
|
advanced: "詳細設定"
|
||||||
|
@ -729,8 +816,10 @@ desktop/views/components/settings.vue:
|
||||||
choose-wallpaper: "壁紙を選択"
|
choose-wallpaper: "壁紙を選択"
|
||||||
delete-wallpaper: "壁紙を削除"
|
delete-wallpaper: "壁紙を削除"
|
||||||
dark-mode: "ダークモード"
|
dark-mode: "ダークモード"
|
||||||
|
use-shadow: "UIに影を使用"
|
||||||
|
rounded-corners: "UIの角を丸める"
|
||||||
circle-icons: "円形のアイコンを使用"
|
circle-icons: "円形のアイコンを使用"
|
||||||
gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
|
contrasted-acct: "ユーザー名にコントラストを付ける"
|
||||||
post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
|
post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
|
||||||
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
|
||||||
show-clock-on-header: "右上に時計を表示する"
|
show-clock-on-header: "右上に時計を表示する"
|
||||||
|
@ -739,7 +828,6 @@ desktop/views/components/settings.vue:
|
||||||
show-renoted-my-notes: "自分の投稿のRenoteをタイムラインに表示する"
|
show-renoted-my-notes: "自分の投稿のRenoteをタイムラインに表示する"
|
||||||
show-local-renotes: "ローカルの投稿のRenoteをタイムラインに表示する"
|
show-local-renotes: "ローカルの投稿のRenoteをタイムラインに表示する"
|
||||||
show-maps: "マップの自動展開"
|
show-maps: "マップの自動展開"
|
||||||
show-maps-desc: "位置情報が添付された投稿のマップを自動的に展開します。"
|
|
||||||
|
|
||||||
sound: "サウンド"
|
sound: "サウンド"
|
||||||
enable-sounds: "サウンドを有効にする"
|
enable-sounds: "サウンドを有効にする"
|
||||||
|
@ -845,7 +933,7 @@ desktop/views/components/settings.profile.vue:
|
||||||
birthday: "誕生日"
|
birthday: "誕生日"
|
||||||
save: "保存"
|
save: "保存"
|
||||||
locked-account: "アカウントの保護"
|
locked-account: "アカウントの保護"
|
||||||
is-locked: "投稿を非公開にする"
|
is-locked: "フォローを承認制にする"
|
||||||
other: "その他"
|
other: "その他"
|
||||||
is-bot: "このアカウントはBotです"
|
is-bot: "このアカウントはBotです"
|
||||||
is-cat: "このアカウントはCatです"
|
is-cat: "このアカウントはCatです"
|
||||||
|
@ -865,7 +953,13 @@ desktop/views/components/timeline.vue:
|
||||||
local: "ローカル"
|
local: "ローカル"
|
||||||
hybrid: "ソーシャル"
|
hybrid: "ソーシャル"
|
||||||
global: "グローバル"
|
global: "グローバル"
|
||||||
|
mentions: "あなた宛て"
|
||||||
|
messages: "メッセージ"
|
||||||
list: "リスト"
|
list: "リスト"
|
||||||
|
hashtag: "ハッシュタグ"
|
||||||
|
add-tag-timeline: "ハッシュタグを追加"
|
||||||
|
add-list: "リストを追加"
|
||||||
|
list-name: "リスト名"
|
||||||
|
|
||||||
desktop/views/components/ui.header.vue:
|
desktop/views/components/ui.header.vue:
|
||||||
welcome-back: "おかえりなさい、"
|
welcome-back: "おかえりなさい、"
|
||||||
|
@ -984,7 +1078,10 @@ desktop/views/pages/welcome.vue:
|
||||||
signin-button: "やってる"
|
signin-button: "やってる"
|
||||||
signup-button: "やる"
|
signup-button: "やる"
|
||||||
timeline: "タイムライン"
|
timeline: "タイムライン"
|
||||||
|
announcements: "お知らせ"
|
||||||
|
photos: "最近の画像"
|
||||||
powered-by-misskey: "Powered by <b>Misskey</b>."
|
powered-by-misskey: "Powered by <b>Misskey</b>."
|
||||||
|
info: "情報"
|
||||||
|
|
||||||
desktop/views/pages/drive.vue:
|
desktop/views/pages/drive.vue:
|
||||||
title: "Misskey Drive"
|
title: "Misskey Drive"
|
||||||
|
@ -1145,6 +1242,7 @@ mobile/views/components/follow-button.vue:
|
||||||
following: "フォロー中"
|
following: "フォロー中"
|
||||||
follow: "フォロー"
|
follow: "フォロー"
|
||||||
request-pending: "フォロー許可待ち"
|
request-pending: "フォロー許可待ち"
|
||||||
|
follow-processing: "フォロー処理中"
|
||||||
follow-request: "フォロー申請"
|
follow-request: "フォロー申請"
|
||||||
|
|
||||||
mobile/views/components/friends-maker.vue:
|
mobile/views/components/friends-maker.vue:
|
||||||
|
@ -1156,8 +1254,6 @@ mobile/views/components/friends-maker.vue:
|
||||||
|
|
||||||
mobile/views/components/note.vue:
|
mobile/views/components/note.vue:
|
||||||
reposted-by: "{}がRenote"
|
reposted-by: "{}がRenote"
|
||||||
more: "もっと見る"
|
|
||||||
less: "隠す"
|
|
||||||
private: "この投稿は非公開です"
|
private: "この投稿は非公開です"
|
||||||
deleted: "この投稿は削除されました"
|
deleted: "この投稿は削除されました"
|
||||||
location: "位置情報"
|
location: "位置情報"
|
||||||
|
@ -1265,6 +1361,8 @@ mobile/views/pages/home.vue:
|
||||||
local: "ローカル"
|
local: "ローカル"
|
||||||
hybrid: "ソーシャル"
|
hybrid: "ソーシャル"
|
||||||
global: "グローバル"
|
global: "グローバル"
|
||||||
|
mentions: "あなた宛て"
|
||||||
|
messages: "メッセージ"
|
||||||
|
|
||||||
mobile/views/pages/tag.vue:
|
mobile/views/pages/tag.vue:
|
||||||
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
|
||||||
|
@ -1317,6 +1415,9 @@ mobile/views/pages/settings/settings.profile.vue:
|
||||||
avatar: "アイコン"
|
avatar: "アイコン"
|
||||||
banner: "バナー"
|
banner: "バナー"
|
||||||
is-cat: "このアカウントはCatです"
|
is-cat: "このアカウントはCatです"
|
||||||
|
is-locked: "フォローを承認制にする"
|
||||||
|
advanced: "その他"
|
||||||
|
privacy: "プライバシー"
|
||||||
save: "保存"
|
save: "保存"
|
||||||
saved: "プロフィールを保存しました"
|
saved: "プロフィールを保存しました"
|
||||||
uploading: "アップロード中"
|
uploading: "アップロード中"
|
||||||
|
@ -1341,6 +1442,7 @@ mobile/views/pages/settings.vue:
|
||||||
dark-mode: "ダークモード"
|
dark-mode: "ダークモード"
|
||||||
i-am-under-limited-internet: "私は通信を制限されている"
|
i-am-under-limited-internet: "私は通信を制限されている"
|
||||||
circle-icons: "円形のアイコンを使用"
|
circle-icons: "円形のアイコンを使用"
|
||||||
|
contrasted-acct: "ユーザー名にコントラストを付ける"
|
||||||
timeline: "タイムライン"
|
timeline: "タイムライン"
|
||||||
show-reply-target: "リプライ先を表示する"
|
show-reply-target: "リプライ先を表示する"
|
||||||
show-my-renotes: "自分の行ったRenoteを表示する"
|
show-my-renotes: "自分の行ったRenoteを表示する"
|
||||||
|
@ -1349,8 +1451,15 @@ mobile/views/pages/settings.vue:
|
||||||
post-style: "投稿の表示スタイル"
|
post-style: "投稿の表示スタイル"
|
||||||
post-style-standard: "標準"
|
post-style-standard: "標準"
|
||||||
post-style-smart: "スマート"
|
post-style-smart: "スマート"
|
||||||
|
notification-position: "通知の表示"
|
||||||
|
notification-position-bottom: "下"
|
||||||
|
notification-position-top: "上"
|
||||||
|
theme: "テーマ"
|
||||||
behavior: "動作"
|
behavior: "動作"
|
||||||
fetch-on-scroll: "スクロールで自動読み込み"
|
fetch-on-scroll: "スクロールで自動読み込み"
|
||||||
|
note-visibility: "投稿の公開範囲"
|
||||||
|
default-note-visibility: "デフォルトの公開範囲"
|
||||||
|
remember-note-visibility: "投稿の公開範囲を記憶する"
|
||||||
disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
|
disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
|
||||||
load-raw-images: "添付された画像を高画質で表示する"
|
load-raw-images: "添付された画像を高画質で表示する"
|
||||||
load-remote-media: "リモートサーバーのメディアを表示する"
|
load-remote-media: "リモートサーバーのメディアを表示する"
|
||||||
|
@ -1370,7 +1479,7 @@ mobile/views/pages/settings.vue:
|
||||||
settings: "設定"
|
settings: "設定"
|
||||||
signout: "サインアウト"
|
signout: "サインアウト"
|
||||||
sound: "サウンド"
|
sound: "サウンド"
|
||||||
enableSounds: "サウンドを有効にする"
|
enable-sounds: "サウンドを有効にする"
|
||||||
|
|
||||||
mobile/views/pages/user.vue:
|
mobile/views/pages/user.vue:
|
||||||
follows-you: "フォローされています"
|
follows-you: "フォローされています"
|
||||||
|
|
91
package.json
91
package.json
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "8.15.0",
|
"version": "9.7.1",
|
||||||
"clientVersion": "1.0.9031",
|
"clientVersion": "1.0.10090",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -20,16 +20,16 @@
|
||||||
"format": "gulp format"
|
"format": "gulp format"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome": "1.1.8",
|
"@fortawesome/fontawesome-svg-core": "1.2.4",
|
||||||
"@fortawesome/fontawesome-free-brands": "5.0.13",
|
"@fortawesome/free-brands-svg-icons": "5.3.1",
|
||||||
"@fortawesome/fontawesome-free-regular": "5.0.13",
|
"@fortawesome/free-regular-svg-icons": "5.3.1",
|
||||||
"@fortawesome/fontawesome-free-solid": "5.0.13",
|
"@fortawesome/free-solid-svg-icons": "5.3.1",
|
||||||
"@koa/cors": "2.2.2",
|
"@koa/cors": "2.2.2",
|
||||||
"@prezzemolo/rap": "0.1.2",
|
"@prezzemolo/rap": "0.1.2",
|
||||||
"@prezzemolo/zip": "0.0.3",
|
"@prezzemolo/zip": "0.0.3",
|
||||||
"@types/bcryptjs": "2.4.1",
|
"@types/bcryptjs": "2.4.2",
|
||||||
"@types/dateformat": "1.0.1",
|
"@types/dateformat": "1.0.1",
|
||||||
"@types/debug": "0.0.30",
|
"@types/debug": "0.0.31",
|
||||||
"@types/deep-equal": "1.0.1",
|
"@types/deep-equal": "1.0.1",
|
||||||
"@types/double-ended-queue": "2.1.0",
|
"@types/double-ended-queue": "2.1.0",
|
||||||
"@types/elasticsearch": "5.0.26",
|
"@types/elasticsearch": "5.0.26",
|
||||||
|
@ -51,19 +51,19 @@
|
||||||
"@types/koa-logger": "3.1.0",
|
"@types/koa-logger": "3.1.0",
|
||||||
"@types/koa-mount": "3.0.1",
|
"@types/koa-mount": "3.0.1",
|
||||||
"@types/koa-multer": "1.0.0",
|
"@types/koa-multer": "1.0.0",
|
||||||
"@types/koa-router": "7.0.31",
|
"@types/koa-router": "7.0.32",
|
||||||
"@types/koa-send": "4.1.1",
|
"@types/koa-send": "4.1.1",
|
||||||
"@types/koa-views": "2.0.3",
|
"@types/koa-views": "2.0.3",
|
||||||
"@types/koa__cors": "2.2.3",
|
"@types/koa__cors": "2.2.3",
|
||||||
"@types/minio": "6.0.2",
|
"@types/minio": "7.0.0",
|
||||||
"@types/mkdirp": "0.5.2",
|
"@types/mkdirp": "0.5.2",
|
||||||
"@types/mocha": "5.2.3",
|
"@types/mocha": "5.2.3",
|
||||||
"@types/mongodb": "3.1.4",
|
"@types/mongodb": "3.1.10",
|
||||||
"@types/ms": "0.7.30",
|
"@types/ms": "0.7.30",
|
||||||
"@types/node": "10.9.3",
|
"@types/node": "10.11.4",
|
||||||
"@types/portscanner": "2.1.0",
|
"@types/portscanner": "2.1.0",
|
||||||
"@types/pug": "2.0.4",
|
"@types/pug": "2.0.4",
|
||||||
"@types/qrcode": "1.2.0",
|
"@types/qrcode": "1.3.0",
|
||||||
"@types/ratelimiter": "2.1.28",
|
"@types/ratelimiter": "2.1.28",
|
||||||
"@types/redis": "2.8.6",
|
"@types/redis": "2.8.6",
|
||||||
"@types/request": "2.47.1",
|
"@types/request": "2.47.1",
|
||||||
|
@ -75,13 +75,15 @@
|
||||||
"@types/single-line-log": "1.1.0",
|
"@types/single-line-log": "1.1.0",
|
||||||
"@types/speakeasy": "2.0.2",
|
"@types/speakeasy": "2.0.2",
|
||||||
"@types/systeminformation": "3.23.0",
|
"@types/systeminformation": "3.23.0",
|
||||||
|
"@types/tinycolor2": "1.4.1",
|
||||||
"@types/tmp": "0.0.33",
|
"@types/tmp": "0.0.33",
|
||||||
"@types/uuid": "3.4.3",
|
"@types/uuid": "3.4.4",
|
||||||
"@types/webpack": "4.4.11",
|
"@types/webpack": "4.4.14",
|
||||||
"@types/webpack-stream": "3.2.10",
|
"@types/webpack-stream": "3.2.10",
|
||||||
"@types/websocket": "0.0.39",
|
"@types/websocket": "0.0.40",
|
||||||
"@types/ws": "6.0.0",
|
"@types/ws": "6.0.1",
|
||||||
"animejs": "2.2.0",
|
"animejs": "2.2.0",
|
||||||
|
"autobind-decorator": "2.1.0",
|
||||||
"autosize": "4.0.2",
|
"autosize": "4.0.2",
|
||||||
"autwh": "0.1.0",
|
"autwh": "0.1.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
|
@ -94,26 +96,25 @@
|
||||||
"crc-32": "1.2.0",
|
"crc-32": "1.2.0",
|
||||||
"css-loader": "1.0.0",
|
"css-loader": "1.0.0",
|
||||||
"dateformat": "3.0.3",
|
"dateformat": "3.0.3",
|
||||||
"debug": "3.1.0",
|
"debug": "4.0.1",
|
||||||
"deep-equal": "1.0.1",
|
"deep-equal": "1.0.1",
|
||||||
"deepcopy": "0.6.3",
|
"deepcopy": "0.6.3",
|
||||||
"diskusage": "0.2.4",
|
"diskusage": "0.2.5",
|
||||||
"dompurify": "1.0.5",
|
"dompurify": "1.0.5",
|
||||||
"double-ended-queue": "2.1.0-0",
|
"double-ended-queue": "2.1.0-0",
|
||||||
"elasticsearch": "15.1.1",
|
"elasticsearch": "15.1.1",
|
||||||
"element-ui": "2.4.6",
|
|
||||||
"emojilib": "2.3.0",
|
"emojilib": "2.3.0",
|
||||||
"escape-regexp": "0.0.1",
|
"escape-regexp": "0.0.1",
|
||||||
"eslint": "5.0.1",
|
"eslint": "5.0.1",
|
||||||
"eslint-plugin-vue": "4.7.1",
|
"eslint-plugin-vue": "4.7.1",
|
||||||
"eventemitter3": "3.1.0",
|
"eventemitter3": "3.1.0",
|
||||||
"exif-js": "2.3.0",
|
"exif-js": "2.3.0",
|
||||||
"file-loader": "1.1.11",
|
"file-loader": "2.0.0",
|
||||||
"file-type": "9.0.0",
|
"file-type": "10.0.0",
|
||||||
"fuckadblock": "3.2.1",
|
"fuckadblock": "3.2.1",
|
||||||
"gulp": "3.9.1",
|
"gulp": "3.9.1",
|
||||||
"gulp-cssnano": "2.1.3",
|
"gulp-cssnano": "2.1.3",
|
||||||
"gulp-htmlmin": "4.0.0",
|
"gulp-htmlmin": "5.0.1",
|
||||||
"gulp-imagemin": "4.1.0",
|
"gulp-imagemin": "4.1.0",
|
||||||
"gulp-mocha": "6.0.0",
|
"gulp-mocha": "6.0.0",
|
||||||
"gulp-pug": "4.0.1",
|
"gulp-pug": "4.0.1",
|
||||||
|
@ -132,16 +133,17 @@
|
||||||
"insert-text-at-cursor": "0.1.1",
|
"insert-text-at-cursor": "0.1.1",
|
||||||
"is-root": "2.0.0",
|
"is-root": "2.0.0",
|
||||||
"is-url": "1.2.4",
|
"is-url": "1.2.4",
|
||||||
"jquery": "3.3.1",
|
|
||||||
"js-yaml": "3.12.0",
|
"js-yaml": "3.12.0",
|
||||||
"jsdom": "11.12.0",
|
"jsdom": "12.2.0",
|
||||||
|
"json5": "2.1.0",
|
||||||
|
"json5-loader": "1.0.1",
|
||||||
"koa": "2.5.1",
|
"koa": "2.5.1",
|
||||||
"koa-bodyparser": "4.2.1",
|
"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",
|
||||||
"koa-logger": "3.2.0",
|
"koa-logger": "3.2.0",
|
||||||
"koa-mount": "3.0.0",
|
"koa-mount": "4.0.0",
|
||||||
"koa-multer": "1.0.2",
|
"koa-multer": "1.0.2",
|
||||||
"koa-router": "7.4.0",
|
"koa-router": "7.4.0",
|
||||||
"koa-send": "5.0.0",
|
"koa-send": "5.0.0",
|
||||||
|
@ -151,17 +153,15 @@
|
||||||
"lodash.assign": "4.2.0",
|
"lodash.assign": "4.2.0",
|
||||||
"mecab-async": "0.1.2",
|
"mecab-async": "0.1.2",
|
||||||
"merge-options": "1.0.1",
|
"merge-options": "1.0.1",
|
||||||
"minio": "7.0.0",
|
"minio": "7.0.1",
|
||||||
"mkdirp": "0.5.1",
|
"mkdirp": "0.5.1",
|
||||||
"mocha": "5.2.0",
|
"mocha": "5.2.0",
|
||||||
"moji": "0.5.1",
|
"moji": "0.5.1",
|
||||||
"mongodb": "3.1.1",
|
"mongodb": "3.1.1",
|
||||||
"monk": "6.0.6",
|
"monk": "6.0.6",
|
||||||
"ms": "2.1.1",
|
"ms": "2.1.1",
|
||||||
"nan": "2.11.0",
|
"nan": "2.11.1",
|
||||||
"nested-property": "0.0.7",
|
"nested-property": "0.0.7",
|
||||||
"node-sass": "4.9.3",
|
|
||||||
"node-sass-json-importer": "3.3.1",
|
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"object-assign-deep": "0.4.0",
|
"object-assign-deep": "0.4.0",
|
||||||
"on-build-webpack": "0.1.0",
|
"on-build-webpack": "0.1.0",
|
||||||
|
@ -172,13 +172,14 @@
|
||||||
"promise-sequential": "1.1.1",
|
"promise-sequential": "1.1.1",
|
||||||
"pug": "2.0.3",
|
"pug": "2.0.3",
|
||||||
"punycode": "2.1.1",
|
"punycode": "2.1.1",
|
||||||
"qrcode": "1.2.2",
|
"qrcode": "1.3.0",
|
||||||
"ratelimiter": "3.2.0",
|
"ratelimiter": "3.2.0",
|
||||||
"recaptcha-promise": "0.1.3",
|
"recaptcha-promise": "0.1.3",
|
||||||
"reconnecting-websocket": "3.2.2",
|
"reconnecting-websocket": "4.1.5",
|
||||||
"redis": "2.8.0",
|
"redis": "2.8.0",
|
||||||
"request": "2.88.0",
|
"request": "2.88.0",
|
||||||
"request-promise-native": "1.0.5",
|
"request-promise-native": "1.0.5",
|
||||||
|
"request-stats": "3.0.0",
|
||||||
"rimraf": "2.6.2",
|
"rimraf": "2.6.2",
|
||||||
"rndstr": "1.0.0",
|
"rndstr": "1.0.0",
|
||||||
"s-age": "1.1.2",
|
"s-age": "1.1.2",
|
||||||
|
@ -193,38 +194,42 @@
|
||||||
"style-loader": "0.23.0",
|
"style-loader": "0.23.0",
|
||||||
"stylus": "0.54.5",
|
"stylus": "0.54.5",
|
||||||
"stylus-loader": "3.0.2",
|
"stylus-loader": "3.0.2",
|
||||||
"summaly": "2.1.4",
|
"summaly": "2.2.0",
|
||||||
"systeminformation": "3.44.2",
|
"systeminformation": "3.45.7",
|
||||||
"syuilo-password-strength": "0.0.1",
|
"syuilo-password-strength": "0.0.1",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
|
"tinycolor2": "1.4.1",
|
||||||
"tmp": "0.0.33",
|
"tmp": "0.0.33",
|
||||||
"ts-loader": "4.4.1",
|
"ts-loader": "4.4.1",
|
||||||
"ts-node": "7.0.1",
|
"ts-node": "7.0.1",
|
||||||
"tslint": "5.10.0",
|
"tslint": "5.10.0",
|
||||||
"typescript": "2.9.2",
|
"typescript": "2.9.2",
|
||||||
"typescript-eslint-parser": "18.0.0",
|
"typescript-eslint-parser": "19.0.2",
|
||||||
"uglify-es": "3.3.9",
|
"uglify-es": "3.3.9",
|
||||||
"url-loader": "1.1.1",
|
"url-loader": "1.1.1",
|
||||||
"uuid": "3.3.2",
|
"uuid": "3.3.2",
|
||||||
"v-animate-css": "0.0.2",
|
"v-animate-css": "0.0.2",
|
||||||
"vue": "2.5.17",
|
"vue": "2.5.17",
|
||||||
"vue-chartjs": "3.4.0",
|
"vue-chartjs": "3.4.0",
|
||||||
"vue-cropperjs": "2.2.1",
|
"vue-color": "2.6.0",
|
||||||
"vue-js-modal": "1.3.23",
|
"vue-cropperjs": "2.2.2",
|
||||||
|
"vue-js-modal": "1.3.26",
|
||||||
"vue-json-tree-view": "2.1.4",
|
"vue-json-tree-view": "2.1.4",
|
||||||
"vue-loader": "15.4.1",
|
"vue-loader": "15.4.2",
|
||||||
"vue-router": "3.0.1",
|
"vue-router": "3.0.1",
|
||||||
"vue-style-loader": "4.1.2",
|
"vue-style-loader": "4.1.2",
|
||||||
|
"vue-svg-inline-loader": "1.2.0",
|
||||||
"vue-template-compiler": "2.5.17",
|
"vue-template-compiler": "2.5.17",
|
||||||
"vuedraggable": "2.16.0",
|
"vuedraggable": "2.16.0",
|
||||||
|
"vuewordcloud": "18.7.11",
|
||||||
"vuex": "3.0.1",
|
"vuex": "3.0.1",
|
||||||
"vuex-persistedstate": "2.5.4",
|
"vuex-persistedstate": "2.5.4",
|
||||||
"web-push": "3.3.2",
|
"web-push": "3.3.3",
|
||||||
"webfinger.js": "2.6.6",
|
"webfinger.js": "2.6.6",
|
||||||
"webpack": "4.17.1",
|
"webpack": "4.20.2",
|
||||||
"webpack-cli": "3.1.0",
|
"webpack-cli": "3.1.2",
|
||||||
"websocket": "1.0.26",
|
"websocket": "1.0.28",
|
||||||
"ws": "6.0.0",
|
"ws": "6.1.0",
|
||||||
"xev": "2.0.1"
|
"xev": "2.0.1"
|
||||||
},
|
},
|
||||||
"greenkeeper": {
|
"greenkeeper": {
|
||||||
|
|
|
@ -6,6 +6,10 @@ html
|
||||||
&, *
|
&, *
|
||||||
cursor progress !important
|
cursor progress !important
|
||||||
|
|
||||||
|
html
|
||||||
|
// iOSのため
|
||||||
|
overflow auto
|
||||||
|
|
||||||
body
|
body
|
||||||
overflow-wrap break-word
|
overflow-wrap break-word
|
||||||
|
|
||||||
|
@ -23,7 +27,7 @@ body
|
||||||
z-index 65536
|
z-index 65536
|
||||||
|
|
||||||
.bar
|
.bar
|
||||||
background $theme-color
|
background var(--primary)
|
||||||
|
|
||||||
position fixed
|
position fixed
|
||||||
z-index 65537
|
z-index 65537
|
||||||
|
@ -40,7 +44,7 @@ body
|
||||||
right 0px
|
right 0px
|
||||||
width 100px
|
width 100px
|
||||||
height 100%
|
height 100%
|
||||||
box-shadow 0 0 10px $theme-color, 0 0 5px $theme-color
|
box-shadow 0 0 10px var(--primary), 0 0 5px var(--primary)
|
||||||
opacity 1
|
opacity 1
|
||||||
|
|
||||||
transform rotate(3deg) translate(0px, -4px)
|
transform rotate(3deg) translate(0px, -4px)
|
||||||
|
@ -60,8 +64,8 @@ body
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
|
|
||||||
border solid 2px transparent
|
border solid 2px transparent
|
||||||
border-top-color $theme-color
|
border-top-color var(--primary)
|
||||||
border-left-color $theme-color
|
border-left-color var(--primary)
|
||||||
border-radius 50%
|
border-radius 50%
|
||||||
|
|
||||||
animation progress-spinner 400ms linear infinite
|
animation progress-spinner 400ms linear infinite
|
||||||
|
|
|
@ -1,3 +1,32 @@
|
||||||
<template>
|
<template>
|
||||||
<router-view id="app"></router-view>
|
<router-view id="app" v-hotkey.global="keymap"></router-view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { url, lang } from './config';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
computed: {
|
||||||
|
keymap(): any {
|
||||||
|
return {
|
||||||
|
'h|slash': this.help,
|
||||||
|
'd': this.dark
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
help() {
|
||||||
|
window.open(`${url}/docs/${lang}/keyboard-shortcut`, '_blank');
|
||||||
|
},
|
||||||
|
|
||||||
|
dark() {
|
||||||
|
this.$store.commit('device/set', {
|
||||||
|
key: 'darkmode',
|
||||||
|
value: !this.$store.state.device.darkmode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
|
@ -80,7 +80,7 @@ export default Vue.extend({
|
||||||
accepted() {
|
accepted() {
|
||||||
this.state = 'accepted';
|
this.state = 'accepted';
|
||||||
if (this.session.app.callbackUrl) {
|
if (this.session.app.callbackUrl) {
|
||||||
location.href = this.session.app.callbackUrl + '?token=' + this.session.token;
|
location.href = `${this.session.app.callbackUrl}?token=${this.session.token}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,9 +34,6 @@ html
|
||||||
//- FontAwesome style
|
//- FontAwesome style
|
||||||
style #{facss}
|
style #{facss}
|
||||||
|
|
||||||
//- highlight.js style
|
|
||||||
style #{hljscss}
|
|
||||||
|
|
||||||
body
|
body
|
||||||
noscript: p
|
noscript: p
|
||||||
| JavaScriptを有効にしてください
|
| JavaScriptを有効にしてください
|
||||||
|
|
|
@ -18,6 +18,17 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const langs = LANGS;
|
||||||
|
|
||||||
|
//#region Apply theme
|
||||||
|
const theme = localStorage.getItem('theme');
|
||||||
|
if (theme) {
|
||||||
|
Object.entries(JSON.parse(theme)).forEach(([k, v]) => {
|
||||||
|
document.documentElement.style.setProperty(`--${k}`, v.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
//#region Load settings
|
//#region Load settings
|
||||||
let settings = null;
|
let settings = null;
|
||||||
const vuex = localStorage.getItem('vuex');
|
const vuex = localStorage.getItem('vuex');
|
||||||
|
@ -40,10 +51,10 @@
|
||||||
//#region Detect the user language
|
//#region Detect the user language
|
||||||
let lang = null;
|
let lang = null;
|
||||||
|
|
||||||
if (LANGS.includes(navigator.language)) {
|
if (langs.includes(navigator.language)) {
|
||||||
lang = navigator.language;
|
lang = navigator.language;
|
||||||
} else {
|
} else {
|
||||||
lang = LANGS.find(x => x.split('-')[0] == navigator.language);
|
lang = langs.find(x => x.split('-')[0] == navigator.language);
|
||||||
|
|
||||||
if (lang == null) {
|
if (lang == null) {
|
||||||
// Fallback
|
// Fallback
|
||||||
|
@ -52,7 +63,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings && settings.device.lang &&
|
if (settings && settings.device.lang &&
|
||||||
LANGS.includes(settings.device.lang)) {
|
langs.includes(settings.device.lang)) {
|
||||||
lang = settings.device.lang;
|
lang = settings.device.lang;
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -82,19 +93,12 @@
|
||||||
app = isMobile ? 'mobile' : 'desktop';
|
app = isMobile ? 'mobile' : 'desktop';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dark/Light
|
|
||||||
if (settings) {
|
|
||||||
if (settings.device.darkmode) {
|
|
||||||
document.documentElement.setAttribute('data-darkmode', 'true');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Script version
|
// Script version
|
||||||
const ver = localStorage.getItem('v') || VERSION;
|
const ver = localStorage.getItem('v') || VERSION;
|
||||||
|
|
||||||
// Get salt query
|
// Get salt query
|
||||||
const salt = localStorage.getItem('salt')
|
const salt = localStorage.getItem('salt')
|
||||||
? '?salt=' + localStorage.getItem('salt')
|
? `?salt=${localStorage.getItem('salt')}`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
// Load an app script
|
// Load an app script
|
||||||
|
@ -140,7 +144,7 @@
|
||||||
// Random
|
// Random
|
||||||
localStorage.setItem('salt', Math.random().toString());
|
localStorage.setItem('salt', Math.random().toString());
|
||||||
|
|
||||||
// Clear cache (serive worker)
|
// Clear cache (service worker)
|
||||||
try {
|
try {
|
||||||
navigator.serviceWorker.controller.postMessage('clear');
|
navigator.serviceWorker.controller.postMessage('clear');
|
||||||
|
|
||||||
|
|
110
src/client/app/common/hotkey.ts
Normal file
110
src/client/app/common/hotkey.ts
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import keyCode from './keycode';
|
||||||
|
import { concat } from '../../../prelude/array';
|
||||||
|
|
||||||
|
type pattern = {
|
||||||
|
which: string[];
|
||||||
|
ctrl?: boolean;
|
||||||
|
shift?: boolean;
|
||||||
|
alt?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type action = {
|
||||||
|
patterns: pattern[];
|
||||||
|
|
||||||
|
callback: Function;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getKeyMap = keymap => Object.entries(keymap).map(([patterns, callback]): action => {
|
||||||
|
const result = {
|
||||||
|
patterns: [],
|
||||||
|
callback: callback
|
||||||
|
} as action;
|
||||||
|
|
||||||
|
result.patterns = patterns.split('|').map(part => {
|
||||||
|
const pattern = {
|
||||||
|
which: [],
|
||||||
|
ctrl: false,
|
||||||
|
alt: false,
|
||||||
|
shift: false
|
||||||
|
} as pattern;
|
||||||
|
|
||||||
|
part.trim().split('+').forEach(key => {
|
||||||
|
key = key.trim().toLowerCase();
|
||||||
|
switch (key) {
|
||||||
|
case 'ctrl': pattern.ctrl = true; break;
|
||||||
|
case 'alt': pattern.alt = true; break;
|
||||||
|
case 'shift': pattern.shift = true; break;
|
||||||
|
default: pattern.which = keyCode(key).map(k => k.toLowerCase());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return pattern;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
const ignoreElemens = ['input', 'textarea'];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
install(Vue) {
|
||||||
|
Vue.directive('hotkey', {
|
||||||
|
bind(el, binding) {
|
||||||
|
el._hotkey_global = binding.modifiers.global === true;
|
||||||
|
|
||||||
|
const actions = getKeyMap(binding.value);
|
||||||
|
|
||||||
|
// flatten
|
||||||
|
const reservedKeys = concat(concat(actions.map(a => a.patterns.map(p => p.which))));
|
||||||
|
|
||||||
|
el.dataset.reservedKeys = reservedKeys.map(key => `'${key}'`).join(' ');
|
||||||
|
|
||||||
|
el._keyHandler = (e: KeyboardEvent) => {
|
||||||
|
const key = e.code.toLowerCase();
|
||||||
|
|
||||||
|
const targetReservedKeys = document.activeElement ? ((document.activeElement as any).dataset || {}).reservedKeys || '' : '';
|
||||||
|
if (document.activeElement && ignoreElemens.some(el => document.activeElement.matches(el))) return;
|
||||||
|
|
||||||
|
for (const action of actions) {
|
||||||
|
if (el._hotkey_global && targetReservedKeys.includes(`'${key}'`)) break;
|
||||||
|
|
||||||
|
const matched = action.patterns.some(pattern => {
|
||||||
|
const matched = pattern.which.includes(key) &&
|
||||||
|
pattern.ctrl == e.ctrlKey &&
|
||||||
|
pattern.shift == e.shiftKey &&
|
||||||
|
pattern.alt == e.altKey &&
|
||||||
|
e.metaKey == false;
|
||||||
|
|
||||||
|
if (matched) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
action.callback(e);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matched) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (el._hotkey_global) {
|
||||||
|
document.addEventListener('keydown', el._keyHandler);
|
||||||
|
} else {
|
||||||
|
el.addEventListener('keydown', el._keyHandler);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
unbind(el) {
|
||||||
|
if (el._hotkey_global) {
|
||||||
|
document.removeEventListener('keydown', el._keyHandler);
|
||||||
|
} else {
|
||||||
|
el.removeEventListener('keydown', el._keyHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
33
src/client/app/common/keycode.ts
Normal file
33
src/client/app/common/keycode.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
export default (input: string): string[] => {
|
||||||
|
if (Object.keys(aliases).some(a => a.toLowerCase() == input.toLowerCase())) {
|
||||||
|
const codes = aliases[input];
|
||||||
|
return Array.isArray(codes) ? codes : [codes];
|
||||||
|
} else {
|
||||||
|
return [input];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const aliases = {
|
||||||
|
'esc': 'Escape',
|
||||||
|
'enter': ['Enter', 'NumpadEnter'],
|
||||||
|
'up': 'ArrowUp',
|
||||||
|
'down': 'ArrowDown',
|
||||||
|
'left': 'ArrowLeft',
|
||||||
|
'right': 'ArrowRight',
|
||||||
|
'plus': ['NumpadAdd', 'Semicolon'],
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Programatically add the following
|
||||||
|
*/
|
||||||
|
|
||||||
|
// lower case chars
|
||||||
|
for (let i = 97; i < 123; i++) {
|
||||||
|
const char = String.fromCharCode(i);
|
||||||
|
aliases[char] = `Key${char.toUpperCase()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// numbers
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
aliases[i] = [`Numpad${i}`, `Digit${i}`];
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ export default async function(mios: MiOS, force = false, silent = false) {
|
||||||
localStorage.setItem('should-refresh', 'true');
|
localStorage.setItem('should-refresh', 'true');
|
||||||
localStorage.setItem('v', newer);
|
localStorage.setItem('v', newer);
|
||||||
|
|
||||||
// Clear cache (serive worker)
|
// Clear cache (service worker)
|
||||||
try {
|
try {
|
||||||
if (navigator.serviceWorker.controller) {
|
if (navigator.serviceWorker.controller) {
|
||||||
navigator.serviceWorker.controller.postMessage('clear');
|
navigator.serviceWorker.controller.postMessage('clear');
|
||||||
|
|
|
@ -13,21 +13,21 @@ type Notification = {
|
||||||
|
|
||||||
export default function(type, data): Notification {
|
export default function(type, data): Notification {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'drive_file_created':
|
case 'driveFileCreated':
|
||||||
return {
|
return {
|
||||||
title: '%i18n:common.notification.file-uploaded%',
|
title: '%i18n:common.notification.file-uploaded%',
|
||||||
body: data.name,
|
body: data.name,
|
||||||
icon: data.url
|
icon: data.url
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'unread_messaging_message':
|
case 'unreadMessagingMessage':
|
||||||
return {
|
return {
|
||||||
title: '%i18n:common.notification.message-from%'.split("{}")[0] + `${getUserName(data.user)}` + '%i18n:common.notification.message-from%'.split("{}")[1] ,
|
title: '%i18n:common.notification.message-from%'.split("{}")[0] + `${getUserName(data.user)}` + '%i18n:common.notification.message-from%'.split("{}")[1] ,
|
||||||
body: data.text, // TODO: getMessagingMessageSummary(data),
|
body: data.text, // TODO: getMessagingMessageSummary(data),
|
||||||
icon: data.user.avatarUrl
|
icon: data.user.avatarUrl
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'reversi_invited':
|
case 'reversiInvited':
|
||||||
return {
|
return {
|
||||||
title: '%i18n:common.notification.reversi-invited%',
|
title: '%i18n:common.notification.reversi-invited%',
|
||||||
body: '%i18n:common.notification.reversi-invited-by%'.split("{}")[0] + `${getUserName(data.parent)}` + '%i18n:common.notification.reversi-invited-by%'.split("{}")[1],
|
body: '%i18n:common.notification.reversi-invited-by%'.split("{}")[0] + `${getUserName(data.parent)}` + '%i18n:common.notification.reversi-invited-by%'.split("{}")[1],
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
require('fuckadblock');
|
|
||||||
|
|
||||||
declare const fuckAdBlock: any;
|
declare const fuckAdBlock: any;
|
||||||
|
|
||||||
export default (os) => {
|
export default (os) => {
|
||||||
|
require('fuckadblock');
|
||||||
|
|
||||||
function adBlockDetected() {
|
function adBlockDetected() {
|
||||||
os.apis.dialog({
|
os.apis.dialog({
|
||||||
title: '%fa:exclamation-triangle%%i18n:common.adblock.detected%',
|
title: '%fa:exclamation-triangle%%i18n:common.adblock.detected%',
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
const gcd = (a, b) => !b ? a : gcd(b, a % b);
|
|
||||||
export default gcd;
|
|
8
src/client/app/common/scripts/get-md5.ts
Normal file
8
src/client/app/common/scripts/get-md5.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
export default (data: ArrayBuffer) => {
|
||||||
|
const buf = new Buffer(data);
|
||||||
|
const hash = crypto.createHash("md5");
|
||||||
|
hash.update(buf);
|
||||||
|
return hash.digest("hex");
|
||||||
|
};
|
116
src/client/app/common/scripts/note-subscriber.ts
Normal file
116
src/client/app/common/scripts/note-subscriber.ts
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default prop => ({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
connection: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
$_ns_note_(): any {
|
||||||
|
return this[prop];
|
||||||
|
},
|
||||||
|
|
||||||
|
$_ns_isRenote(): boolean {
|
||||||
|
return (this.$_ns_note_.renote &&
|
||||||
|
this.$_ns_note_.text == null &&
|
||||||
|
this.$_ns_note_.fileIds.length == 0 &&
|
||||||
|
this.$_ns_note_.poll == null);
|
||||||
|
},
|
||||||
|
|
||||||
|
$_ns_target(): any {
|
||||||
|
return this._ns_isRenote ? this.$_ns_note_.renote : this.$_ns_note_;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
if (this.$store.getters.isSignedIn) {
|
||||||
|
this.connection = (this as any).os.stream;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.capture(true);
|
||||||
|
|
||||||
|
if (this.$store.getters.isSignedIn) {
|
||||||
|
this.connection.on('_connected_', this.onStreamConnected);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.decapture(true);
|
||||||
|
|
||||||
|
if (this.$store.getters.isSignedIn) {
|
||||||
|
this.connection.off('_connected_', this.onStreamConnected);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
capture(withHandler = false) {
|
||||||
|
if (this.$store.getters.isSignedIn) {
|
||||||
|
const data = {
|
||||||
|
id: this.$_ns_target.id
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(this.$_ns_target.visibleUserIds || []).includes(this.$store.state.i.id) ||
|
||||||
|
(this.$_ns_target.mentions || []).includes(this.$store.state.i.id)
|
||||||
|
) {
|
||||||
|
data.read = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connection.send('sn', data);
|
||||||
|
if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
decapture(withHandler = false) {
|
||||||
|
if (this.$store.getters.isSignedIn) {
|
||||||
|
this.connection.send('un', {
|
||||||
|
id: this.$_ns_target.id
|
||||||
|
});
|
||||||
|
if (withHandler) this.connection.off('noteUpdated', this.onStreamNoteUpdated);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onStreamConnected() {
|
||||||
|
this.capture();
|
||||||
|
},
|
||||||
|
|
||||||
|
onStreamNoteUpdated(data) {
|
||||||
|
const { type, id, body } = data;
|
||||||
|
|
||||||
|
if (id !== this.$_ns_target.id) return;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'reacted': {
|
||||||
|
const reaction = body.reaction;
|
||||||
|
if (this.$_ns_target.reactionCounts == null) Vue.set(this.$_ns_target, 'reactionCounts', {});
|
||||||
|
this.$_ns_target.reactionCounts[reaction] = (this.$_ns_target.reactionCounts[reaction] || 0) + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'pollVoted': {
|
||||||
|
if (body.userId == this.$store.state.i.id) return;
|
||||||
|
const choice = body.choice;
|
||||||
|
this.$_ns_target.poll.choices.find(c => c.id === choice).votes++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'deleted': {
|
||||||
|
Vue.set(this.$_ns_target, 'deletedAt', body.deletedAt);
|
||||||
|
this.$_ns_target.text = null;
|
||||||
|
this.$_ns_target.tags = [];
|
||||||
|
this.$_ns_target.fileIds = [];
|
||||||
|
this.$_ns_target.poll = null;
|
||||||
|
this.$_ns_target.geo = null;
|
||||||
|
this.$_ns_target.cw = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit(`update:${prop}`, this.$_ns_note_);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,53 +0,0 @@
|
||||||
export default function(qs: string) {
|
|
||||||
const q = {
|
|
||||||
text: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
qs.split(' ').forEach(x => {
|
|
||||||
if (/^([a-z_]+?):(.+?)$/.test(x)) {
|
|
||||||
const [key, value] = x.split(':');
|
|
||||||
switch (key) {
|
|
||||||
case 'user':
|
|
||||||
q['includeUserUsernames'] = value.split(',');
|
|
||||||
break;
|
|
||||||
case 'exclude_user':
|
|
||||||
q['excludeUserUsernames'] = value.split(',');
|
|
||||||
break;
|
|
||||||
case 'follow':
|
|
||||||
q['following'] = value == 'null' ? null : value == 'true';
|
|
||||||
break;
|
|
||||||
case 'reply':
|
|
||||||
q['reply'] = value == 'null' ? null : value == 'true';
|
|
||||||
break;
|
|
||||||
case 'renote':
|
|
||||||
q['renote'] = value == 'null' ? null : value == 'true';
|
|
||||||
break;
|
|
||||||
case 'media':
|
|
||||||
q['media'] = value == 'null' ? null : value == 'true';
|
|
||||||
break;
|
|
||||||
case 'poll':
|
|
||||||
q['poll'] = value == 'null' ? null : value == 'true';
|
|
||||||
break;
|
|
||||||
case 'until':
|
|
||||||
case 'since':
|
|
||||||
// YYYY-MM-DD
|
|
||||||
if (/^[0-9]+\-[0-9]+\-[0-9]+$/) {
|
|
||||||
const [yyyy, mm, dd] = value.split('-');
|
|
||||||
q[`${key}_date`] = (new Date(parseInt(yyyy, 10), parseInt(mm, 10) - 1, parseInt(dd, 10))).getTime();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
q[key] = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
q.text += x + ' ';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (q.text) {
|
|
||||||
q.text = q.text.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
return q;
|
|
||||||
}
|
|
318
src/client/app/common/scripts/stream.ts
Normal file
318
src/client/app/common/scripts/stream.ts
Normal file
|
@ -0,0 +1,318 @@
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import { EventEmitter } from 'eventemitter3';
|
||||||
|
import ReconnectingWebsocket from 'reconnecting-websocket';
|
||||||
|
import { wsUrl } from '../../config';
|
||||||
|
import MiOS from '../../mios';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Misskey stream connection
|
||||||
|
*/
|
||||||
|
export default class Stream extends EventEmitter {
|
||||||
|
private stream: ReconnectingWebsocket;
|
||||||
|
private state: string;
|
||||||
|
private buffer: any[];
|
||||||
|
private sharedConnections: SharedConnection[] = [];
|
||||||
|
private nonSharedConnections: NonSharedConnection[] = [];
|
||||||
|
|
||||||
|
constructor(os: MiOS) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.state = 'initializing';
|
||||||
|
this.buffer = [];
|
||||||
|
|
||||||
|
const user = os.store.state.i;
|
||||||
|
|
||||||
|
this.stream = new ReconnectingWebsocket(wsUrl + (user ? `?i=${user.token}` : ''));
|
||||||
|
this.stream.addEventListener('open', this.onOpen);
|
||||||
|
this.stream.addEventListener('close', this.onClose);
|
||||||
|
this.stream.addEventListener('message', this.onMessage);
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
const main = this.useSharedConnection('main');
|
||||||
|
|
||||||
|
// 自分の情報が更新されたとき
|
||||||
|
main.on('meUpdated', i => {
|
||||||
|
os.store.dispatch('mergeMe', i);
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('readAllNotifications', () => {
|
||||||
|
os.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadNotification: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('unreadNotification', () => {
|
||||||
|
os.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadNotification: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('readAllMessagingMessages', () => {
|
||||||
|
os.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadMessagingMessage: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('unreadMessagingMessage', () => {
|
||||||
|
os.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadMessagingMessage: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('unreadMention', () => {
|
||||||
|
os.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadMentions: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('readAllUnreadMentions', () => {
|
||||||
|
os.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadMentions: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('unreadSpecifiedNote', () => {
|
||||||
|
os.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadSpecifiedNotes: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('readAllUnreadSpecifiedNotes', () => {
|
||||||
|
os.store.dispatch('mergeMe', {
|
||||||
|
hasUnreadSpecifiedNotes: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('clientSettingUpdated', x => {
|
||||||
|
os.store.commit('settings/set', {
|
||||||
|
key: x.key,
|
||||||
|
value: x.value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('homeUpdated', x => {
|
||||||
|
os.store.commit('settings/setHome', x);
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('mobileHomeUpdated', x => {
|
||||||
|
os.store.commit('settings/setMobileHome', x);
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on('widgetUpdated', x => {
|
||||||
|
os.store.commit('settings/setWidget', {
|
||||||
|
id: x.id,
|
||||||
|
data: x.data
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// トークンが再生成されたとき
|
||||||
|
// このままではMisskeyが利用できないので強制的にサインアウトさせる
|
||||||
|
main.on('myTokenRegenerated', () => {
|
||||||
|
alert('%i18n:common.my-token-regenerated%');
|
||||||
|
os.signout();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public useSharedConnection = (channel: string): SharedConnection => {
|
||||||
|
const existConnection = this.sharedConnections.find(c => c.channel === channel);
|
||||||
|
|
||||||
|
if (existConnection) {
|
||||||
|
existConnection.use();
|
||||||
|
return existConnection;
|
||||||
|
} else {
|
||||||
|
const connection = new SharedConnection(this, channel);
|
||||||
|
connection.use();
|
||||||
|
this.sharedConnections.push(connection);
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public removeSharedConnection(connection: SharedConnection) {
|
||||||
|
this.sharedConnections = this.sharedConnections.filter(c => c.id !== connection.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public connectToChannel = (channel: string, params?: any): NonSharedConnection => {
|
||||||
|
const connection = new NonSharedConnection(this, channel, params);
|
||||||
|
this.nonSharedConnections.push(connection);
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public disconnectToChannel(connection: NonSharedConnection) {
|
||||||
|
this.nonSharedConnections = this.nonSharedConnections.filter(c => c.id !== connection.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback of when open connection
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
private onOpen() {
|
||||||
|
const isReconnect = this.state == 'reconnecting';
|
||||||
|
|
||||||
|
this.state = 'connected';
|
||||||
|
this.emit('_connected_');
|
||||||
|
|
||||||
|
// バッファーを処理
|
||||||
|
const _buffer = [].concat(this.buffer); // Shallow copy
|
||||||
|
this.buffer = []; // Clear buffer
|
||||||
|
_buffer.forEach(data => {
|
||||||
|
this.send(data); // Resend each buffered messages
|
||||||
|
});
|
||||||
|
|
||||||
|
// チャンネル再接続
|
||||||
|
if (isReconnect) {
|
||||||
|
this.sharedConnections.forEach(c => {
|
||||||
|
c.connect();
|
||||||
|
});
|
||||||
|
this.nonSharedConnections.forEach(c => {
|
||||||
|
c.connect();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback of when close connection
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
private onClose() {
|
||||||
|
this.state = 'reconnecting';
|
||||||
|
this.emit('_disconnected_');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback of when received a message from connection
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
private onMessage(message) {
|
||||||
|
const { type, body } = JSON.parse(message.data);
|
||||||
|
|
||||||
|
if (type == 'channel') {
|
||||||
|
const id = body.id;
|
||||||
|
const connection = this.sharedConnections.find(c => c.id === id) || this.nonSharedConnections.find(c => c.id === id);
|
||||||
|
connection.emit(body.type, body.body);
|
||||||
|
} else {
|
||||||
|
this.emit(type, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to connection
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
public send(typeOrPayload, payload?) {
|
||||||
|
const data = payload === undefined ? typeOrPayload : {
|
||||||
|
type: typeOrPayload,
|
||||||
|
body: payload
|
||||||
|
};
|
||||||
|
|
||||||
|
// まだ接続が確立されていなかったらバッファリングして次に接続した時に送信する
|
||||||
|
if (this.state != 'connected') {
|
||||||
|
this.buffer.push(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stream.send(JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close this connection
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
public close() {
|
||||||
|
this.stream.removeEventListener('open', this.onOpen);
|
||||||
|
this.stream.removeEventListener('message', this.onMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Connection extends EventEmitter {
|
||||||
|
public channel: string;
|
||||||
|
public id: string;
|
||||||
|
protected params: any;
|
||||||
|
protected stream: Stream;
|
||||||
|
|
||||||
|
constructor(stream: Stream, channel: string, params?: any) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.stream = stream;
|
||||||
|
this.channel = channel;
|
||||||
|
this.params = params;
|
||||||
|
this.id = Math.random().toString();
|
||||||
|
this.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public connect() {
|
||||||
|
this.stream.send('connect', {
|
||||||
|
channel: this.channel,
|
||||||
|
id: this.id,
|
||||||
|
params: this.params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public send(typeOrPayload, payload?) {
|
||||||
|
const data = payload === undefined ? typeOrPayload : {
|
||||||
|
type: typeOrPayload,
|
||||||
|
body: payload
|
||||||
|
};
|
||||||
|
|
||||||
|
this.stream.send('channel', {
|
||||||
|
id: this.id,
|
||||||
|
body: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract dispose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SharedConnection extends Connection {
|
||||||
|
private users = 0;
|
||||||
|
private disposeTimerId: any;
|
||||||
|
|
||||||
|
constructor(stream: Stream, channel: string) {
|
||||||
|
super(stream, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public use() {
|
||||||
|
this.users++;
|
||||||
|
|
||||||
|
// タイマー解除
|
||||||
|
if (this.disposeTimerId) {
|
||||||
|
clearTimeout(this.disposeTimerId);
|
||||||
|
this.disposeTimerId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public dispose() {
|
||||||
|
this.users--;
|
||||||
|
|
||||||
|
// そのコネクションの利用者が誰もいなくなったら
|
||||||
|
if (this.users === 0) {
|
||||||
|
// また直ぐに再利用される可能性があるので、一定時間待ち、
|
||||||
|
// 新たな利用者が現れなければコネクションを切断する
|
||||||
|
this.disposeTimerId = setTimeout(() => {
|
||||||
|
this.disposeTimerId = null;
|
||||||
|
this.removeAllListeners();
|
||||||
|
this.stream.send('disconnect', { id: this.id });
|
||||||
|
this.stream.removeSharedConnection(this);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonSharedConnection extends Connection {
|
||||||
|
constructor(stream: Stream, channel: string, params?: any) {
|
||||||
|
super(stream, channel, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public dispose() {
|
||||||
|
this.removeAllListeners();
|
||||||
|
this.stream.send('disconnect', { id: this.id });
|
||||||
|
this.stream.disconnectToChannel(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,34 +0,0 @@
|
||||||
import Stream from './stream';
|
|
||||||
import StreamManager from './stream-manager';
|
|
||||||
import MiOS from '../../../mios';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drive stream connection
|
|
||||||
*/
|
|
||||||
export class DriveStream extends Stream {
|
|
||||||
constructor(os: MiOS, me) {
|
|
||||||
super(os, 'drive', {
|
|
||||||
i: me.token
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DriveStreamManager extends StreamManager<DriveStream> {
|
|
||||||
private me;
|
|
||||||
private os: MiOS;
|
|
||||||
|
|
||||||
constructor(os: MiOS, me) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.me = me;
|
|
||||||
this.os = os;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getConnection() {
|
|
||||||
if (this.connection == null) {
|
|
||||||
this.connection = new DriveStream(this.os, this.me);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.connection;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import Stream from '../../stream';
|
|
||||||
import MiOS from '../../../../../mios';
|
|
||||||
|
|
||||||
export class ReversiGameStream extends Stream {
|
|
||||||
constructor(os: MiOS, me, game) {
|
|
||||||
super(os, 'games/reversi-game', {
|
|
||||||
i: me ? me.token : null,
|
|
||||||
game: game.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
import StreamManager from '../../stream-manager';
|
|
||||||
import Stream from '../../stream';
|
|
||||||
import MiOS from '../../../../../mios';
|
|
||||||
|
|
||||||
export class ReversiStream extends Stream {
|
|
||||||
constructor(os: MiOS, me) {
|
|
||||||
super(os, 'games/reversi', {
|
|
||||||
i: me.token
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ReversiStreamManager extends StreamManager<ReversiStream> {
|
|
||||||
private me;
|
|
||||||
private os: MiOS;
|
|
||||||
|
|
||||||
constructor(os: MiOS, me) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.me = me;
|
|
||||||
this.os = os;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getConnection() {
|
|
||||||
if (this.connection == null) {
|
|
||||||
this.connection = new ReversiStream(this.os, this.me);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.connection;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import Stream from './stream';
|
|
||||||
import StreamManager from './stream-manager';
|
|
||||||
import MiOS from '../../../mios';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Global timeline stream connection
|
|
||||||
*/
|
|
||||||
export class GlobalTimelineStream extends Stream {
|
|
||||||
constructor(os: MiOS, me) {
|
|
||||||
super(os, 'global-timeline', {
|
|
||||||
i: me.token
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GlobalTimelineStreamManager extends StreamManager<GlobalTimelineStream> {
|
|
||||||
private me;
|
|
||||||
private os: MiOS;
|
|
||||||
|
|
||||||
constructor(os: MiOS, me) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.me = me;
|
|
||||||
this.os = os;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getConnection() {
|
|
||||||
if (this.connection == null) {
|
|
||||||
this.connection = new GlobalTimelineStream(this.os, this.me);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.connection;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
import Stream from './stream';
|
|
||||||
import StreamManager from './stream-manager';
|
|
||||||
import MiOS from '../../../mios';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Home stream connection
|
|
||||||
*/
|
|
||||||
export class HomeStream extends Stream {
|
|
||||||
constructor(os: MiOS, me) {
|
|
||||||
super(os, '', {
|
|
||||||
i: me.token
|
|
||||||
});
|
|
||||||
|
|
||||||
// 最終利用日時を更新するため定期的にaliveメッセージを送信
|
|
||||||
setInterval(() => {
|
|
||||||
this.send({ type: 'alive' });
|
|
||||||
me.lastUsedAt = new Date();
|
|
||||||
}, 1000 * 60);
|
|
||||||
|
|
||||||
// 自分の情報が更新されたとき
|
|
||||||
this.on('meUpdated', i => {
|
|
||||||
if (os.debug) {
|
|
||||||
console.log('I updated:', i);
|
|
||||||
}
|
|
||||||
|
|
||||||
os.store.dispatch('mergeMe', i);
|
|
||||||
});
|
|
||||||
|
|
||||||
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 => {
|
|
||||||
os.store.commit('settings/set', {
|
|
||||||
key: x.key,
|
|
||||||
value: x.value
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on('home_updated', x => {
|
|
||||||
os.store.commit('settings/setHome', x);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on('mobile_home_updated', x => {
|
|
||||||
os.store.commit('settings/setMobileHome', x);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on('widgetUpdated', x => {
|
|
||||||
os.store.commit('settings/setWidget', {
|
|
||||||
id: x.id,
|
|
||||||
data: x.data
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// トークンが再生成されたとき
|
|
||||||
// このままではMisskeyが利用できないので強制的にサインアウトさせる
|
|
||||||
this.on('my_token_regenerated', () => {
|
|
||||||
alert('%i18n:common.my-token-regenerated%');
|
|
||||||
os.signout();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HomeStreamManager extends StreamManager<HomeStream> {
|
|
||||||
private me;
|
|
||||||
private os: MiOS;
|
|
||||||
|
|
||||||
constructor(os: MiOS, me) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.me = me;
|
|
||||||
this.os = os;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getConnection() {
|
|
||||||
if (this.connection == null) {
|
|
||||||
this.connection = new HomeStream(this.os, this.me);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.connection;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import Stream from './stream';
|
|
||||||
import StreamManager from './stream-manager';
|
|
||||||
import MiOS from '../../../mios';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hybrid timeline stream connection
|
|
||||||
*/
|
|
||||||
export class HybridTimelineStream extends Stream {
|
|
||||||
constructor(os: MiOS, me) {
|
|
||||||
super(os, 'hybrid-timeline', {
|
|
||||||
i: me.token
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HybridTimelineStreamManager extends StreamManager<HybridTimelineStream> {
|
|
||||||
private me;
|
|
||||||
private os: MiOS;
|
|
||||||
|
|
||||||
constructor(os: MiOS, me) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.me = me;
|
|
||||||
this.os = os;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getConnection() {
|
|
||||||
if (this.connection == null) {
|
|
||||||
this.connection = new HybridTimelineStream(this.os, this.me);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.connection;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import Stream from './stream';
|
|
||||||
import StreamManager from './stream-manager';
|
|
||||||
import MiOS from '../../../mios';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Local timeline stream connection
|
|
||||||
*/
|
|
||||||
export class LocalTimelineStream extends Stream {
|
|
||||||
constructor(os: MiOS, me) {
|
|
||||||
super(os, 'local-timeline', {
|
|
||||||
i: me.token
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LocalTimelineStreamManager extends StreamManager<LocalTimelineStream> {
|
|
||||||
private me;
|
|
||||||
private os: MiOS;
|
|
||||||
|
|
||||||
constructor(os: MiOS, me) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.me = me;
|
|
||||||
this.os = os;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getConnection() {
|
|
||||||
if (this.connection == null) {
|
|
||||||
this.connection = new LocalTimelineStream(this.os, this.me);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.connection;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import Stream from './stream';
|
|
||||||
import StreamManager from './stream-manager';
|
|
||||||
import MiOS from '../../../mios';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Messaging index stream connection
|
|
||||||
*/
|
|
||||||
export class MessagingIndexStream extends Stream {
|
|
||||||
constructor(os: MiOS, me) {
|
|
||||||
super(os, 'messaging-index', {
|
|
||||||
i: me.token
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MessagingIndexStreamManager extends StreamManager<MessagingIndexStream> {
|
|
||||||
private me;
|
|
||||||
private os: MiOS;
|
|
||||||
|
|
||||||
constructor(os: MiOS, me) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.me = me;
|
|
||||||
this.os = os;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getConnection() {
|
|
||||||
if (this.connection == null) {
|
|
||||||
this.connection = new MessagingIndexStream(this.os, this.me);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.connection;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
import Stream from './stream';
|
|
||||||
import MiOS from '../../../mios';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Messaging stream connection
|
|
||||||
*/
|
|
||||||
export class MessagingStream extends Stream {
|
|
||||||
constructor(os: MiOS, me, otherparty) {
|
|
||||||
super(os, 'messaging', {
|
|
||||||
i: me.token,
|
|
||||||
otherparty
|
|
||||||
});
|
|
||||||
|
|
||||||
(this as any).on('_connected_', () => {
|
|
||||||
this.send({
|
|
||||||
i: me.token
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
import Stream from './stream';
|
|
||||||
import StreamManager from './stream-manager';
|
|
||||||
import MiOS from '../../../mios';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notes stats stream connection
|
|
||||||
*/
|
|
||||||
export class NotesStatsStream extends Stream {
|
|
||||||
constructor(os: MiOS) {
|
|
||||||
super(os, 'notes-stats');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NotesStatsStreamManager extends StreamManager<NotesStatsStream> {
|
|
||||||
private os: MiOS;
|
|
||||||
|
|
||||||
constructor(os: MiOS) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.os = os;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getConnection() {
|
|
||||||
if (this.connection == null) {
|
|
||||||
this.connection = new NotesStatsStream(this.os);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.connection;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
import { EventEmitter } from 'eventemitter3';
|
|
||||||
import * as uuid from 'uuid';
|
|
||||||
import Connection from './stream';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ストリーム接続を管理するクラス
|
|
||||||
* 複数の場所から同じストリームを利用する際、接続をまとめたりする
|
|
||||||
*/
|
|
||||||
export default abstract class StreamManager<T extends Connection> extends EventEmitter {
|
|
||||||
private _connection: T = null;
|
|
||||||
|
|
||||||
private disposeTimerId: any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* コネクションを必要としているユーザー
|
|
||||||
*/
|
|
||||||
private users = [];
|
|
||||||
|
|
||||||
protected set connection(connection: T) {
|
|
||||||
this._connection = connection;
|
|
||||||
|
|
||||||
if (this._connection == null) {
|
|
||||||
this.emit('disconnected');
|
|
||||||
} else {
|
|
||||||
this.emit('connected', this._connection);
|
|
||||||
|
|
||||||
this._connection.on('_connected_', () => {
|
|
||||||
this.emit('_connected_');
|
|
||||||
});
|
|
||||||
|
|
||||||
this._connection.on('_disconnected_', () => {
|
|
||||||
this.emit('_disconnected_');
|
|
||||||
});
|
|
||||||
|
|
||||||
this._connection.user = 'Managed';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get connection() {
|
|
||||||
return this._connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* コネクションを持っているか否か
|
|
||||||
*/
|
|
||||||
public get hasConnection() {
|
|
||||||
return this._connection != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get state(): string {
|
|
||||||
if (!this.hasConnection) return 'no-connection';
|
|
||||||
return this._connection.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* コネクションを要求します
|
|
||||||
*/
|
|
||||||
public abstract getConnection(): T;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 現在接続しているコネクションを取得します
|
|
||||||
*/
|
|
||||||
public borrow() {
|
|
||||||
return this._connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* コネクションを要求するためのユーザーIDを発行します
|
|
||||||
*/
|
|
||||||
public use() {
|
|
||||||
// タイマー解除
|
|
||||||
if (this.disposeTimerId) {
|
|
||||||
clearTimeout(this.disposeTimerId);
|
|
||||||
this.disposeTimerId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ユーザーID生成
|
|
||||||
const userId = uuid();
|
|
||||||
|
|
||||||
this.users.push(userId);
|
|
||||||
|
|
||||||
this._connection.user = `Managed (${ this.users.length })`;
|
|
||||||
|
|
||||||
return userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* コネクションを利用し終わってもう必要ないことを通知します
|
|
||||||
* @param userId use で発行したユーザーID
|
|
||||||
*/
|
|
||||||
public dispose(userId) {
|
|
||||||
this.users = this.users.filter(id => id != userId);
|
|
||||||
|
|
||||||
this._connection.user = `Managed (${ this.users.length })`;
|
|
||||||
|
|
||||||
// 誰もコネクションの利用者がいなくなったら
|
|
||||||
if (this.users.length == 0) {
|
|
||||||
// また直ぐに再利用される可能性があるので、一定時間待ち、
|
|
||||||
// 新たな利用者が現れなければコネクションを切断する
|
|
||||||
this.disposeTimerId = setTimeout(() => {
|
|
||||||
this.disposeTimerId = null;
|
|
||||||
|
|
||||||
this.connection.close();
|
|
||||||
this.connection = null;
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
import { EventEmitter } from 'eventemitter3';
|
|
||||||
import * as uuid from 'uuid';
|
|
||||||
import * as ReconnectingWebsocket from 'reconnecting-websocket';
|
|
||||||
import { wsUrl } from '../../../config';
|
|
||||||
import MiOS from '../../../mios';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Misskey stream connection
|
|
||||||
*/
|
|
||||||
export default class Connection extends EventEmitter {
|
|
||||||
public state: string;
|
|
||||||
private buffer: any[];
|
|
||||||
public socket: ReconnectingWebsocket;
|
|
||||||
public name: string;
|
|
||||||
public connectedAt: Date;
|
|
||||||
public user: string = null;
|
|
||||||
public in: number = 0;
|
|
||||||
public out: number = 0;
|
|
||||||
public inout: Array<{
|
|
||||||
type: 'in' | 'out',
|
|
||||||
at: Date,
|
|
||||||
data: string
|
|
||||||
}> = [];
|
|
||||||
public id: string;
|
|
||||||
public isSuspended = false;
|
|
||||||
private os: MiOS;
|
|
||||||
|
|
||||||
constructor(os: MiOS, endpoint, params?) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
//#region BIND
|
|
||||||
this.onOpen = this.onOpen.bind(this);
|
|
||||||
this.onClose = this.onClose.bind(this);
|
|
||||||
this.onMessage = this.onMessage.bind(this);
|
|
||||||
this.send = this.send.bind(this);
|
|
||||||
this.close = this.close.bind(this);
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
this.id = uuid();
|
|
||||||
this.os = os;
|
|
||||||
this.name = endpoint;
|
|
||||||
this.state = 'initializing';
|
|
||||||
this.buffer = [];
|
|
||||||
|
|
||||||
const query = params
|
|
||||||
? Object.keys(params)
|
|
||||||
.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
|
|
||||||
.join('&')
|
|
||||||
: null;
|
|
||||||
|
|
||||||
this.socket = new ReconnectingWebsocket(`${wsUrl}/${endpoint}${query ? '?' + query : ''}`);
|
|
||||||
this.socket.addEventListener('open', this.onOpen);
|
|
||||||
this.socket.addEventListener('close', this.onClose);
|
|
||||||
this.socket.addEventListener('message', this.onMessage);
|
|
||||||
|
|
||||||
// Register this connection for debugging
|
|
||||||
this.os.registerStreamConnection(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback of when open connection
|
|
||||||
*/
|
|
||||||
private onOpen() {
|
|
||||||
this.state = 'connected';
|
|
||||||
this.emit('_connected_');
|
|
||||||
|
|
||||||
this.connectedAt = new Date();
|
|
||||||
|
|
||||||
// バッファーを処理
|
|
||||||
const _buffer = [].concat(this.buffer); // Shallow copy
|
|
||||||
this.buffer = []; // Clear buffer
|
|
||||||
_buffer.forEach(data => {
|
|
||||||
this.send(data); // Resend each buffered messages
|
|
||||||
|
|
||||||
if (this.os.debug) {
|
|
||||||
this.out++;
|
|
||||||
this.inout.push({ type: 'out', at: new Date(), data });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback of when close connection
|
|
||||||
*/
|
|
||||||
private onClose() {
|
|
||||||
this.state = 'reconnecting';
|
|
||||||
this.emit('_disconnected_');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback of when received a message from connection
|
|
||||||
*/
|
|
||||||
private onMessage(message) {
|
|
||||||
if (this.isSuspended) return;
|
|
||||||
|
|
||||||
if (this.os.debug) {
|
|
||||||
this.in++;
|
|
||||||
this.inout.push({ type: 'in', at: new Date(), data: message.data });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const msg = JSON.parse(message.data);
|
|
||||||
if (msg.type) this.emit(msg.type, msg.body);
|
|
||||||
} catch (e) {
|
|
||||||
// noop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a message to connection
|
|
||||||
*/
|
|
||||||
public send(data) {
|
|
||||||
if (this.isSuspended) return;
|
|
||||||
|
|
||||||
// まだ接続が確立されていなかったらバッファリングして次に接続した時に送信する
|
|
||||||
if (this.state != 'connected') {
|
|
||||||
this.buffer.push(data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.os.debug) {
|
|
||||||
this.out++;
|
|
||||||
this.inout.push({ type: 'out', at: new Date(), data });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.socket.send(JSON.stringify(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close this connection
|
|
||||||
*/
|
|
||||||
public close() {
|
|
||||||
this.os.unregisterStreamConnection(this);
|
|
||||||
this.socket.removeEventListener('open', this.onOpen);
|
|
||||||
this.socket.removeEventListener('message', this.onMessage);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import Stream from './stream';
|
|
||||||
import MiOS from '../../mios';
|
|
||||||
|
|
||||||
export class UserListStream extends Stream {
|
|
||||||
constructor(os: MiOS, me, listId) {
|
|
||||||
super(os, 'user-list', {
|
|
||||||
i: me.token,
|
|
||||||
listId
|
|
||||||
});
|
|
||||||
|
|
||||||
(this as any).on('_connected_', () => {
|
|
||||||
this.send({
|
|
||||||
i: me.token
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<span class="mk-acct">
|
<span class="mk-acct">
|
||||||
<span class="name">@{{ user.username }}</span>
|
<span class="name">@{{ user.username }}</span>
|
||||||
<span class="host" v-if="user.host">@{{ user.host }}</span>
|
<span class="host" :class="{ fade: $store.state.settings.contrastedAcct }" v-if="user.host || detail || $store.state.settings.showFullAcct">@{{ user.host || host }}</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import { host } from '../../../config';
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['user']
|
props: ['user', 'detail'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
host
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.mk-acct
|
.mk-acct
|
||||||
> .host
|
> .host.fade
|
||||||
opacity 0.5
|
opacity 0.5
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -125,7 +125,7 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.type == 'user') {
|
if (this.type == 'user') {
|
||||||
const cacheKey = 'autocomplete:user:' + this.q;
|
const cacheKey = `autocomplete:user:${this.q}`;
|
||||||
const cache = sessionStorage.getItem(cacheKey);
|
const cache = sessionStorage.getItem(cacheKey);
|
||||||
if (cache) {
|
if (cache) {
|
||||||
const users = JSON.parse(cache);
|
const users = JSON.parse(cache);
|
||||||
|
@ -148,7 +148,7 @@ export default Vue.extend({
|
||||||
this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]');
|
this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]');
|
||||||
this.fetching = false;
|
this.fetching = false;
|
||||||
} else {
|
} else {
|
||||||
const cacheKey = 'autocomplete:hashtag:' + this.q;
|
const cacheKey = `autocomplete:hashtag:${this.q}`;
|
||||||
const cache = sessionStorage.getItem(cacheKey);
|
const cache = sessionStorage.getItem(cacheKey);
|
||||||
if (cache) {
|
if (cache) {
|
||||||
const hashtags = JSON.parse(cache);
|
const hashtags = JSON.parse(cache);
|
||||||
|
@ -259,15 +259,13 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.mk-autocomplete
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
position fixed
|
position fixed
|
||||||
z-index 65535
|
z-index 65535
|
||||||
max-width 100%
|
max-width 100%
|
||||||
margin-top calc(1em + 8px)
|
margin-top calc(1em + 8px)
|
||||||
overflow hidden
|
overflow hidden
|
||||||
background isDark ? #313543 : #fff
|
background var(--faceHeader)
|
||||||
border solid 1px rgba(#000, 0.1)
|
border solid 1px rgba(#000, 0.1)
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
transition top 0.1s ease, left 0.1s ease
|
transition top 0.1s ease, left 0.1s ease
|
||||||
|
@ -299,16 +297,16 @@ root(isDark)
|
||||||
text-overflow ellipsis
|
text-overflow ellipsis
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
background isDark ? rgba(#fff, 0.1) : rgba(#000, 0.1)
|
background var(--autocompleteItemHoverBg)
|
||||||
|
|
||||||
&[data-selected='true']
|
&[data-selected='true']
|
||||||
background $theme-color
|
background var(--primary)
|
||||||
|
|
||||||
&, *
|
&, *
|
||||||
color #fff !important
|
color #fff !important
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
background darken($theme-color, 10%)
|
background var(--primaryDarken10)
|
||||||
|
|
||||||
&, *
|
&, *
|
||||||
color #fff !important
|
color #fff !important
|
||||||
|
@ -325,15 +323,15 @@ root(isDark)
|
||||||
|
|
||||||
.name
|
.name
|
||||||
margin 0 8px 0 0
|
margin 0 8px 0 0
|
||||||
color isDark ? rgba(#fff, 0.8) : rgba(#000, 0.8)
|
color var(--autocompleteItemText)
|
||||||
|
|
||||||
.username
|
.username
|
||||||
color isDark ? rgba(#fff, 0.3) : rgba(#000, 0.3)
|
color var(--autocompleteItemTextSub)
|
||||||
|
|
||||||
> .hashtags > li
|
> .hashtags > li
|
||||||
|
|
||||||
.name
|
.name
|
||||||
color isDark ? rgba(#fff, 0.8) : rgba(#000, 0.8)
|
color var(--autocompleteItemText)
|
||||||
|
|
||||||
> .emojis > li
|
> .emojis > li
|
||||||
|
|
||||||
|
@ -343,15 +341,9 @@ root(isDark)
|
||||||
width 24px
|
width 24px
|
||||||
|
|
||||||
.name
|
.name
|
||||||
color isDark ? rgba(#fff, 0.8) : rgba(#000, 0.8)
|
color var(--autocompleteItemText)
|
||||||
|
|
||||||
.alias
|
.alias
|
||||||
margin 0 0 0 8px
|
margin 0 0 0 8px
|
||||||
color isDark ? rgba(#fff, 0.3) : rgba(#000, 0.3)
|
color var(--autocompleteItemTextSub)
|
||||||
|
|
||||||
.mk-autocomplete[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.mk-autocomplete:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<span class="mk-avatar" :class="{ cat }" :title="user | acct" v-if="disableLink && !disablePreview" v-user-preview="user.id" @click="onClick">
|
<span class="mk-avatar" :style="style" :class="{ cat }" :title="user | acct" v-if="disableLink && !disablePreview" v-user-preview="user.id" @click="onClick">
|
||||||
<span class="inner" :style="style"></span>
|
<span class="inner" :style="icon"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="mk-avatar" :class="{ cat }" :title="user | acct" v-else-if="disableLink && disablePreview" @click="onClick">
|
<span class="mk-avatar" :style="style" :class="{ cat }" :title="user | acct" v-else-if="disableLink && disablePreview" @click="onClick">
|
||||||
<span class="inner" :style="style"></span>
|
<span class="inner" :style="icon"></span>
|
||||||
</span>
|
</span>
|
||||||
<router-link class="mk-avatar" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && !disablePreview" v-user-preview="user.id">
|
<router-link class="mk-avatar" :style="style" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && !disablePreview" v-user-preview="user.id">
|
||||||
<span class="inner" :style="style"></span>
|
<span class="inner" :style="icon"></span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link class="mk-avatar" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && disablePreview">
|
<router-link class="mk-avatar" :style="style" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && disablePreview">
|
||||||
<span class="inner" :style="style"></span>
|
<span class="inner" :style="icon"></span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -42,6 +42,11 @@ export default Vue.extend({
|
||||||
return this.user.isCat && this.$store.state.settings.circleIcons;
|
return this.user.isCat && this.$store.state.settings.circleIcons;
|
||||||
},
|
},
|
||||||
style(): any {
|
style(): any {
|
||||||
|
return {
|
||||||
|
borderRadius: this.$store.state.settings.circleIcons ? '100%' : null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
icon(): any {
|
||||||
return {
|
return {
|
||||||
backgroundColor: this.lightmode
|
backgroundColor: this.lightmode
|
||||||
? `rgb(${this.user.avatarColor.slice(0, 3).join(',')})`
|
? `rgb(${this.user.avatarColor.slice(0, 3).join(',')})`
|
||||||
|
@ -53,6 +58,11 @@ export default Vue.extend({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.user.avatarColor) {
|
||||||
|
this.$el.style.color = `rgb(${this.user.avatarColor.slice(0, 3).join(',')})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onClick(e) {
|
onClick(e) {
|
||||||
this.$emit('click', e);
|
this.$emit('click', e);
|
||||||
|
@ -62,8 +72,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
|
.mk-avatar
|
||||||
root(isDark)
|
|
||||||
display inline-block
|
display inline-block
|
||||||
vertical-align bottom
|
vertical-align bottom
|
||||||
|
|
||||||
|
@ -74,7 +83,7 @@ root(isDark)
|
||||||
&.cat::before,
|
&.cat::before,
|
||||||
&.cat::after
|
&.cat::after
|
||||||
background #df548f
|
background #df548f
|
||||||
border solid 4px isDark ? #e0eefd : #202224
|
border solid 4px currentColor
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
content ''
|
content ''
|
||||||
display inline-block
|
display inline-block
|
||||||
|
@ -100,9 +109,4 @@ root(isDark)
|
||||||
transition border-radius 1s ease
|
transition border-radius 1s ease
|
||||||
z-index 1
|
z-index 1
|
||||||
|
|
||||||
.mk-avatar[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.mk-avatar:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check internet connection
|
// Check internet connection
|
||||||
fetch('https://google.com?rand=' + Math.random(), {
|
fetch(`https://google.com?rand=${Math.random()}`, {
|
||||||
mode: 'no-cors'
|
mode: 'no-cors'
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.internet = true;
|
this.internet = true;
|
||||||
|
|
|
@ -39,7 +39,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
|
||||||
|
|
||||||
.mk-connect-failed
|
.mk-connect-failed
|
||||||
width 100%
|
width 100%
|
||||||
|
@ -70,17 +70,17 @@ export default Vue.extend({
|
||||||
display block
|
display block
|
||||||
margin 1em auto 0 auto
|
margin 1em auto 0 auto
|
||||||
padding 8px 10px
|
padding 8px 10px
|
||||||
color $theme-color-foreground
|
color var(--primaryForeground)
|
||||||
background $theme-color
|
background var(--primary)
|
||||||
|
|
||||||
&:focus
|
&:focus
|
||||||
outline solid 3px rgba($theme-color, 0.3)
|
outline solid 3px var(--primaryAlpha03)
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
background lighten($theme-color, 10%)
|
background var(--primaryLighten10)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
background darken($theme-color, 10%)
|
background var(--primaryDarken10)
|
||||||
|
|
||||||
> .thanks
|
> .thanks
|
||||||
display block
|
display block
|
||||||
|
|
38
src/client/app/common/views/components/cw-button.vue
Normal file
38
src/client/app/common/views/components/cw-button.vue
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<template>
|
||||||
|
<button class="nrvgflfuaxwgkxoynpnumyookecqrrvh" @click="toggle">{{ value ? '%i18n:@hide%' : '%i18n:@show%' }}</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggle() {
|
||||||
|
this.$emit('input', !this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.nrvgflfuaxwgkxoynpnumyookecqrrvh
|
||||||
|
display inline-block
|
||||||
|
padding 4px 8px
|
||||||
|
font-size 0.7em
|
||||||
|
color var(--cwButtonFg)
|
||||||
|
background var(--cwButtonBg)
|
||||||
|
border-radius 2px
|
||||||
|
cursor pointer
|
||||||
|
user-select none
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background var(--cwButtonHoverBg)
|
||||||
|
|
||||||
|
</style>
|
|
@ -9,7 +9,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
|
||||||
|
|
||||||
.a
|
.a
|
||||||
display block
|
display block
|
||||||
|
@ -18,8 +18,8 @@
|
||||||
display block
|
display block
|
||||||
//fill #151513
|
//fill #151513
|
||||||
//color #fff
|
//color #fff
|
||||||
fill $theme-color
|
fill var(--primary)
|
||||||
color $theme-color-foreground
|
color var(--primaryForeground)
|
||||||
|
|
||||||
.octo-arm
|
.octo-arm
|
||||||
transform-origin 130px 106px
|
transform-origin 130px 106px
|
||||||
|
|
|
@ -50,15 +50,15 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="player" v-if="game.isEnded">
|
<div class="player" v-if="game.isEnded">
|
||||||
<el-button-group>
|
<div>
|
||||||
<el-button type="primary" @click="logPos = 0" :disabled="logPos == 0">%fa:angle-double-left%</el-button>
|
<button @click="logPos = 0" :disabled="logPos == 0">%fa:angle-double-left%</button>
|
||||||
<el-button type="primary" @click="logPos--" :disabled="logPos == 0">%fa:angle-left%</el-button>
|
<button @click="logPos--" :disabled="logPos == 0">%fa:angle-left%</button>
|
||||||
</el-button-group>
|
</div>
|
||||||
<span>{{ logPos }} / {{ logs.length }}</span>
|
<span>{{ logPos }} / {{ logs.length }}</span>
|
||||||
<el-button-group>
|
<div>
|
||||||
<el-button type="primary" @click="logPos++" :disabled="logPos == logs.length">%fa:angle-right%</el-button>
|
<button @click="logPos++" :disabled="logPos == logs.length">%fa:angle-right%</button>
|
||||||
<el-button type="primary" @click="logPos = logs.length" :disabled="logPos == logs.length">%fa:angle-double-right%</el-button>
|
<button @click="logPos = logs.length" :disabled="logPos == logs.length">%fa:angle-double-right%</button>
|
||||||
</el-button-group>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info">
|
<div class="info">
|
||||||
|
@ -159,11 +159,9 @@ export default Vue.extend({
|
||||||
canPutEverywhere: this.game.settings.canPutEverywhere,
|
canPutEverywhere: this.game.settings.canPutEverywhere,
|
||||||
loopedBoard: this.game.settings.loopedBoard
|
loopedBoard: this.game.settings.loopedBoard
|
||||||
});
|
});
|
||||||
this.logs.forEach((log, i) => {
|
for (const log of this.logs.slice(0, v)) {
|
||||||
if (i < v) {
|
|
||||||
this.o.put(log.color, log.pos);
|
this.o.put(log.color, log.pos);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
this.$forceUpdate();
|
this.$forceUpdate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -306,9 +304,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.xqnhankfuuilcwvhgsopeqncafzsquya
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
text-align center
|
text-align center
|
||||||
|
|
||||||
> .go-index
|
> .go-index
|
||||||
|
@ -321,7 +317,7 @@ root(isDark)
|
||||||
|
|
||||||
> header
|
> header
|
||||||
padding 8px
|
padding 8px
|
||||||
border-bottom dashed 1px isDark ? #4c5761 : #c4cdd4
|
border-bottom dashed 1px var(--reversiGameHeaderLine)
|
||||||
|
|
||||||
a
|
a
|
||||||
color inherit
|
color inherit
|
||||||
|
@ -388,30 +384,30 @@ root(isDark)
|
||||||
user-select none
|
user-select none
|
||||||
|
|
||||||
&.empty
|
&.empty
|
||||||
border solid 2px isDark ? #51595f : #eee
|
border solid 2px var(--reversiGameEmptyCell)
|
||||||
|
|
||||||
&.empty.can
|
&.empty.can
|
||||||
background isDark ? #51595f : #eee
|
background var(--reversiGameEmptyCell)
|
||||||
|
|
||||||
&.empty.myTurn
|
&.empty.myTurn
|
||||||
border-color isDark ? #6a767f : #ddd
|
border-color var(--reversiGameEmptyCellMyTurn)
|
||||||
|
|
||||||
&.can
|
&.can
|
||||||
background isDark ? #51595f : #eee
|
background var(--reversiGameEmptyCellCanPut)
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
border-color darken($theme-color, 10%)
|
border-color var(--primaryDarken10)
|
||||||
background $theme-color
|
background var(--primary)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
background darken($theme-color, 10%)
|
background var(--primaryDarken10)
|
||||||
|
|
||||||
&.prev
|
&.prev
|
||||||
box-shadow 0 0 0 4px rgba($theme-color, 0.7)
|
box-shadow 0 0 0 4px var(--primaryAlpha07)
|
||||||
|
|
||||||
&.isEnded
|
&.isEnded
|
||||||
border-color isDark ? #6a767f : #ddd
|
border-color var(--reversiGameEmptyCellMyTurn)
|
||||||
|
|
||||||
&.none
|
&.none
|
||||||
border-color transparent !important
|
border-color transparent !important
|
||||||
|
@ -460,10 +456,4 @@ root(isDark)
|
||||||
margin 0 8px
|
margin 0 8px
|
||||||
min-width 70px
|
min-width 70px
|
||||||
|
|
||||||
.xqnhankfuuilcwvhgsopeqncafzsquya[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.xqnhankfuuilcwvhgsopeqncafzsquya:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import XGame from './reversi.game.vue';
|
import XGame from './reversi.game.vue';
|
||||||
import XRoom from './reversi.room.vue';
|
import XRoom from './reversi.room.vue';
|
||||||
import { ReversiGameStream } from '../../../../scripts/streaming/games/reversi/reversi-game';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
|
@ -34,12 +33,13 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.g = this.game;
|
this.g = this.game;
|
||||||
this.connection = new ReversiGameStream((this as any).os, this.$store.state.i, this.game);
|
this.connection = (this as any).os.stream.connectToChannel('gamesReversiGame', {
|
||||||
|
gameId: this.game.id
|
||||||
|
});
|
||||||
this.connection.on('started', this.onStarted);
|
this.connection.on('started', this.onStarted);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.connection.off('started', this.onStarted);
|
this.connection.dispose();
|
||||||
this.connection.close();
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onStarted(game) {
|
onStarted(game) {
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
<h1>%i18n:@title%</h1>
|
<h1>%i18n:@title%</h1>
|
||||||
<p>%i18n:@sub-title%</p>
|
<p>%i18n:@sub-title%</p>
|
||||||
<div class="play">
|
<div class="play">
|
||||||
<!--<el-button round>フリーマッチ(準備中)</el-button>-->
|
|
||||||
<form-button primary round @click="match">%i18n:@invite%</form-button>
|
<form-button primary round @click="match">%i18n:@invite%</form-button>
|
||||||
<details>
|
<details>
|
||||||
<summary>%i18n:@rule%</summary>
|
<summary>%i18n:@rule%</summary>
|
||||||
|
@ -60,15 +59,13 @@ export default Vue.extend({
|
||||||
myGames: [],
|
myGames: [],
|
||||||
matching: null,
|
matching: null,
|
||||||
invitations: [],
|
invitations: [],
|
||||||
connection: null,
|
connection: null
|
||||||
connectionId: null
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.$store.getters.isSignedIn) {
|
if (this.$store.getters.isSignedIn) {
|
||||||
this.connection = (this as any).os.streams.reversiStream.getConnection();
|
this.connection = (this as any).os.stream.useSharedConnection('gamesReversi');
|
||||||
this.connectionId = (this as any).os.streams.reversiStream.use();
|
|
||||||
|
|
||||||
this.connection.on('invited', this.onInvited);
|
this.connection.on('invited', this.onInvited);
|
||||||
|
|
||||||
|
@ -91,8 +88,7 @@ export default Vue.extend({
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (this.connection) {
|
if (this.connection) {
|
||||||
this.connection.off('invited', this.onInvited);
|
this.connection.dispose();
|
||||||
(this as any).os.streams.reversiStream.dispose(this.connectionId);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -139,9 +135,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.phgnkghfpyvkrvwiajkiuoxyrdaqpzcx
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
> h1
|
> h1
|
||||||
margin 0
|
margin 0
|
||||||
padding 24px
|
padding 24px
|
||||||
|
@ -149,7 +143,7 @@ root(isDark)
|
||||||
text-align center
|
text-align center
|
||||||
font-weight normal
|
font-weight normal
|
||||||
color #fff
|
color #fff
|
||||||
background linear-gradient(to bottom, isDark ? #45730e : #8bca3e, isDark ? #464300 : #d6cf31)
|
background linear-gradient(to bottom, var(--reversiBannerGradientStart), var(--reversiBannerGradientEnd))
|
||||||
|
|
||||||
& + p
|
& + p
|
||||||
margin 0
|
margin 0
|
||||||
|
@ -157,7 +151,7 @@ root(isDark)
|
||||||
margin-bottom 12px
|
margin-bottom 12px
|
||||||
text-align center
|
text-align center
|
||||||
font-size 14px
|
font-size 14px
|
||||||
border-bottom solid 1px isDark ? #535f65 : #d3d9dc
|
border-bottom solid 1px var(--faceDivider)
|
||||||
|
|
||||||
> .play
|
> .play
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
|
@ -172,14 +166,14 @@ root(isDark)
|
||||||
padding 16px
|
padding 16px
|
||||||
font-size 14px
|
font-size 14px
|
||||||
text-align left
|
text-align left
|
||||||
background isDark ? #282c37 : #f5f5f5
|
background var(--reversiDescBg)
|
||||||
border-radius 8px
|
border-radius 8px
|
||||||
|
|
||||||
> section
|
> section
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
padding 0 16px 16px 16px
|
padding 0 16px 16px 16px
|
||||||
max-width 500px
|
max-width 500px
|
||||||
border-top solid 1px isDark ? #535f65 : #d3d9dc
|
border-top solid 1px var(--faceDivider)
|
||||||
|
|
||||||
> h2
|
> h2
|
||||||
margin 0
|
margin 0
|
||||||
|
@ -190,9 +184,9 @@ root(isDark)
|
||||||
.invitation
|
.invitation
|
||||||
margin 8px 0
|
margin 8px 0
|
||||||
padding 8px
|
padding 8px
|
||||||
color isDark ? #fff : #677f84
|
color var(--text)
|
||||||
background isDark ? #282c37 : #fff
|
background var(--face)
|
||||||
box-shadow 0 2px 16px rgba(#000, isDark ? 0.7 : 0.15)
|
box-shadow 0 2px 16px var(--reversiListItemShadow)
|
||||||
border-radius 6px
|
border-radius 6px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
|
||||||
|
@ -201,13 +195,13 @@ root(isDark)
|
||||||
user-select none
|
user-select none
|
||||||
|
|
||||||
&:focus
|
&:focus
|
||||||
border-color $theme-color
|
border-color var(--primary)
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
background isDark ? #313543 : #f5f5f5
|
box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.05)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
background isDark ? #1e222b : #eee
|
box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.1)
|
||||||
|
|
||||||
> .avatar
|
> .avatar
|
||||||
width 32px
|
width 32px
|
||||||
|
@ -222,9 +216,9 @@ root(isDark)
|
||||||
display block
|
display block
|
||||||
margin 8px 0
|
margin 8px 0
|
||||||
padding 8px
|
padding 8px
|
||||||
color isDark ? #fff : #677f84
|
color var(--text)
|
||||||
background isDark ? #282c37 : #fff
|
background var(--face)
|
||||||
box-shadow 0 2px 16px rgba(#000, isDark ? 0.7 : 0.15)
|
box-shadow 0 2px 16px var(--reversiListItemShadow)
|
||||||
border-radius 6px
|
border-radius 6px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
|
||||||
|
@ -233,10 +227,10 @@ root(isDark)
|
||||||
user-select none
|
user-select none
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
background isDark ? #313543 : #f5f5f5
|
box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.05)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
background isDark ? #1e222b : #eee
|
box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.1)
|
||||||
|
|
||||||
> .avatar
|
> .avatar
|
||||||
width 32px
|
width 32px
|
||||||
|
@ -247,10 +241,4 @@ root(isDark)
|
||||||
margin 0 8px
|
margin 0 8px
|
||||||
line-height 32px
|
line-height 32px
|
||||||
|
|
||||||
.phgnkghfpyvkrvwiajkiuoxyrdaqpzcx[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.phgnkghfpyvkrvwiajkiuoxyrdaqpzcx:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -47,9 +47,9 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<mk-switch v-model="game.settings.isLlotheo" @change="updateSettings" text="%i18n:@is-llotheo%"/>
|
<ui-switch v-model="game.settings.isLlotheo" @change="updateSettings">%i18n:@is-llotheo%</ui-switch>
|
||||||
<mk-switch v-model="game.settings.loopedBoard" @change="updateSettings" text="%i18n:@looped-map%"/>
|
<ui-switch v-model="game.settings.loopedBoard" @change="updateSettings">%i18n:@looped-map%</ui-switch>
|
||||||
<mk-switch v-model="game.settings.canPutEverywhere" @change="updateSettings" text="%i18n:@can-put-everywhere%"/>
|
<ui-switch v-model="game.settings.canPutEverywhere" @change="updateSettings">%i18n:@can-put-everywhere%</ui-switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -59,13 +59,8 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<el-alert v-for="message in messages"
|
|
||||||
:title="message.text"
|
|
||||||
:type="message.type"
|
|
||||||
:key="message.id"/>
|
|
||||||
|
|
||||||
<template v-for="item in form">
|
<template v-for="item in form">
|
||||||
<mk-switch v-if="item.type == 'switch'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm(item)">{{ item.desc || '' }}</mk-switch>
|
<ui-switch v-if="item.type == 'switch'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm(item)">{{ item.desc || '' }}</ui-switch>
|
||||||
|
|
||||||
<div class="card" v-if="item.type == 'radio'" :key="item.id">
|
<div class="card" v-if="item.type == 'radio'" :key="item.id">
|
||||||
<header>
|
<header>
|
||||||
|
@ -93,7 +88,7 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<el-input v-model="item.value" @change="onChangeForm(item)"/>
|
<input v-model="item.value" @change="onChangeForm(item)"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -257,11 +252,9 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.urbixznjwwuukfsckrwzwsqzsxornqij
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
text-align center
|
text-align center
|
||||||
background isDark ? #191b22 : #f9f9f9
|
background var(--bg)
|
||||||
|
|
||||||
> header
|
> header
|
||||||
padding 8px
|
padding 8px
|
||||||
|
@ -278,10 +271,10 @@ root(isDark)
|
||||||
> select
|
> select
|
||||||
width 100%
|
width 100%
|
||||||
padding 12px 14px
|
padding 12px 14px
|
||||||
background isDark ? #282C37 : #fff
|
background var(--face)
|
||||||
border 1px solid isDark ? #6a707d : #dcdfe6
|
border 1px solid var(--reversiMapSelectBorder)
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
color isDark ? #fff : #606266
|
color var(--text)
|
||||||
cursor pointer
|
cursor pointer
|
||||||
transition border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1)
|
transition border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1)
|
||||||
-webkit-appearance none
|
-webkit-appearance none
|
||||||
|
@ -289,17 +282,18 @@ root(isDark)
|
||||||
appearance none
|
appearance none
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
border-color isDark ? #a7aebd : #c0c4cc
|
border-color var(--reversiMapSelectHoverBorder)
|
||||||
|
|
||||||
&:focus
|
&:focus
|
||||||
&:active
|
&:active
|
||||||
border-color $theme-color
|
border-color var(--primary)
|
||||||
|
|
||||||
> div
|
> div
|
||||||
> .random
|
> .random
|
||||||
padding 32px 0
|
padding 32px 0
|
||||||
font-size 64px
|
font-size 64px
|
||||||
color isDark ? #4e5961 : #d8d8d8
|
color var(--text)
|
||||||
|
opacity 0.7
|
||||||
|
|
||||||
> .board
|
> .board
|
||||||
display grid
|
display grid
|
||||||
|
@ -307,11 +301,11 @@ root(isDark)
|
||||||
width 300px
|
width 300px
|
||||||
height 300px
|
height 300px
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
color isDark ? #fff : #444
|
color var(--text)
|
||||||
|
|
||||||
> div
|
> div
|
||||||
background transparent
|
background transparent
|
||||||
border solid 2px isDark ? #6a767f : #ddd
|
border solid 2px var(--faceDivider)
|
||||||
border-radius 6px
|
border-radius 6px
|
||||||
overflow hidden
|
overflow hidden
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
@ -336,32 +330,26 @@ root(isDark)
|
||||||
.card
|
.card
|
||||||
max-width 400px
|
max-width 400px
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
background isDark ? #282C37 : #fff
|
background var(--face)
|
||||||
color isDark ? #fff : #303133
|
color var(--text)
|
||||||
box-shadow 0 2px 12px 0 rgba(#000, isDark ? 0.7 : 0.1)
|
box-shadow 0 2px 12px 0 var(--reversiRoomFormShadow)
|
||||||
|
|
||||||
> header
|
> header
|
||||||
padding 18px 20px
|
padding 18px 20px
|
||||||
border-bottom 1px solid isDark ? #1c2023 : #ebeef5
|
border-bottom 1px solid var(--faceDivider)
|
||||||
|
|
||||||
> div
|
> div
|
||||||
padding 20px
|
padding 20px
|
||||||
color isDark ? #fff : #606266
|
color var(--text)
|
||||||
|
|
||||||
> footer
|
> footer
|
||||||
position sticky
|
position sticky
|
||||||
bottom 0
|
bottom 0
|
||||||
padding 16px
|
padding 16px
|
||||||
background rgba(isDark ? #191b22 : #fff, 0.9)
|
background var(--reversiRoomFooterBg)
|
||||||
border-top solid 1px isDark ? #606266 : #c4cdd4
|
border-top solid 1px var(--faceDivider)
|
||||||
|
|
||||||
> .status
|
> .status
|
||||||
margin 0 0 16px 0
|
margin 0 0 16px 0
|
||||||
|
|
||||||
.urbixznjwwuukfsckrwzwsqzsxornqij[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.urbixznjwwuukfsckrwzwsqzsxornqij:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -47,7 +47,6 @@ export default Vue.extend({
|
||||||
game: null,
|
game: null,
|
||||||
matching: null,
|
matching: null,
|
||||||
connection: null,
|
connection: null,
|
||||||
connectionId: null,
|
|
||||||
pingClock: null
|
pingClock: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -66,8 +65,7 @@ export default Vue.extend({
|
||||||
this.fetch();
|
this.fetch();
|
||||||
|
|
||||||
if (this.$store.getters.isSignedIn) {
|
if (this.$store.getters.isSignedIn) {
|
||||||
this.connection = (this as any).os.streams.reversiStream.getConnection();
|
this.connection = (this as any).os.stream.useSharedConnection('gamesReversi');
|
||||||
this.connectionId = (this as any).os.streams.reversiStream.use();
|
|
||||||
|
|
||||||
this.connection.on('matched', this.onMatched);
|
this.connection.on('matched', this.onMatched);
|
||||||
|
|
||||||
|
@ -84,9 +82,7 @@ export default Vue.extend({
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (this.connection) {
|
if (this.connection) {
|
||||||
this.connection.off('matched', this.onMatched);
|
this.connection.dispose();
|
||||||
(this as any).os.streams.reversiStream.dispose(this.connectionId);
|
|
||||||
|
|
||||||
clearInterval(this.pingClock);
|
clearInterval(this.pingClock);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -156,11 +152,9 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.vchtoekanapleubgzioubdtmlkribzfd
|
||||||
|
color var(--text)
|
||||||
root(isDark)
|
background var(--bg)
|
||||||
color isDark ? #fff : #677f84
|
|
||||||
background isDark ? #191b22 : #fff
|
|
||||||
|
|
||||||
> .matching
|
> .matching
|
||||||
> h1
|
> h1
|
||||||
|
@ -177,10 +171,4 @@ root(isDark)
|
||||||
text-align center
|
text-align center
|
||||||
border-top dashed 1px #c4cdd4
|
border-top dashed 1px #c4cdd4
|
||||||
|
|
||||||
.vchtoekanapleubgzioubdtmlkribzfd[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.vchtoekanapleubgzioubdtmlkribzfd:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
root(isDark)
|
.mk-google
|
||||||
display flex
|
display flex
|
||||||
margin 8px 0
|
margin 8px 0
|
||||||
|
|
||||||
|
@ -37,31 +37,25 @@ root(isDark)
|
||||||
height 40px
|
height 40px
|
||||||
font-family sans-serif
|
font-family sans-serif
|
||||||
font-size 16px
|
font-size 16px
|
||||||
color isDark ? #dee4e8 : #55595c
|
color var(--googleSearchFg)
|
||||||
background isDark ? #191b22 : #fff
|
background var(--googleSearchBg)
|
||||||
border solid 1px isDark ? #495156 : #dadada
|
border solid 1px var(--googleSearchBorder)
|
||||||
border-radius 4px 0 0 4px
|
border-radius 4px 0 0 4px
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
border-color isDark ? #777c86 : #b0b0b0
|
border-color var(--googleSearchHoverBorder)
|
||||||
|
|
||||||
> button
|
> button
|
||||||
flex-shrink 0
|
flex-shrink 0
|
||||||
padding 0 16px
|
padding 0 16px
|
||||||
border solid 1px isDark ? #495156 : #dadada
|
border solid 1px var(--googleSearchBorder)
|
||||||
border-left none
|
border-left none
|
||||||
border-radius 0 4px 4px 0
|
border-radius 0 4px 4px 0
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
background-color isDark ? #2e3440 : #eee
|
background-color var(--googleSearchHoverButton)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
box-shadow 0 2px 4px rgba(#000, 0.15) inset
|
box-shadow 0 2px 4px rgba(#000, 0.15) inset
|
||||||
|
|
||||||
.mk-google[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.mk-google:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
import theme from './theme.vue';
|
||||||
|
import instance from './instance.vue';
|
||||||
|
import cwButton from './cw-button.vue';
|
||||||
|
import tagCloud from './tag-cloud.vue';
|
||||||
|
import trends from './trends.vue';
|
||||||
import analogClock from './analog-clock.vue';
|
import analogClock from './analog-clock.vue';
|
||||||
import menu from './menu.vue';
|
import menu from './menu.vue';
|
||||||
import noteHeader from './note-header.vue';
|
import noteHeader from './note-header.vue';
|
||||||
|
@ -26,7 +31,6 @@ import messagingRoom from './messaging-room.vue';
|
||||||
import urlPreview from './url-preview.vue';
|
import urlPreview from './url-preview.vue';
|
||||||
import twitterSetting from './twitter-setting.vue';
|
import twitterSetting from './twitter-setting.vue';
|
||||||
import fileTypeIcon from './file-type-icon.vue';
|
import fileTypeIcon from './file-type-icon.vue';
|
||||||
import Switch from './switch.vue';
|
|
||||||
import Reversi from './games/reversi/reversi.vue';
|
import Reversi from './games/reversi/reversi.vue';
|
||||||
import welcomeTimeline from './welcome-timeline.vue';
|
import welcomeTimeline from './welcome-timeline.vue';
|
||||||
import uiInput from './ui/input.vue';
|
import uiInput from './ui/input.vue';
|
||||||
|
@ -40,6 +44,11 @@ import uiSelect from './ui/select.vue';
|
||||||
import formButton from './ui/form/button.vue';
|
import formButton from './ui/form/button.vue';
|
||||||
import formRadio from './ui/form/radio.vue';
|
import formRadio from './ui/form/radio.vue';
|
||||||
|
|
||||||
|
Vue.component('mk-theme', theme);
|
||||||
|
Vue.component('mk-instance', instance);
|
||||||
|
Vue.component('mk-cw-button', cwButton);
|
||||||
|
Vue.component('mk-tag-cloud', tagCloud);
|
||||||
|
Vue.component('mk-trends', trends);
|
||||||
Vue.component('mk-analog-clock', analogClock);
|
Vue.component('mk-analog-clock', analogClock);
|
||||||
Vue.component('mk-menu', menu);
|
Vue.component('mk-menu', menu);
|
||||||
Vue.component('mk-note-header', noteHeader);
|
Vue.component('mk-note-header', noteHeader);
|
||||||
|
@ -66,7 +75,6 @@ Vue.component('mk-messaging-room', messagingRoom);
|
||||||
Vue.component('mk-url-preview', urlPreview);
|
Vue.component('mk-url-preview', urlPreview);
|
||||||
Vue.component('mk-twitter-setting', twitterSetting);
|
Vue.component('mk-twitter-setting', twitterSetting);
|
||||||
Vue.component('mk-file-type-icon', fileTypeIcon);
|
Vue.component('mk-file-type-icon', fileTypeIcon);
|
||||||
Vue.component('mk-switch', Switch);
|
|
||||||
Vue.component('mk-reversi', Reversi);
|
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-input', uiInput);
|
||||||
|
|
51
src/client/app/common/views/components/instance.vue
Normal file
51
src/client/app/common/views/components/instance.vue
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<template>
|
||||||
|
<div class="nhasjydimbopojusarffqjyktglcuxjy" v-if="meta">
|
||||||
|
<div class="banner" :style="{ backgroundImage: meta.bannerUrl ? `url(${meta.bannerUrl})` : null }"></div>
|
||||||
|
|
||||||
|
<h1>{{ meta.name }}</h1>
|
||||||
|
<p v-html="meta.description || '%i18n:common.about%'"></p>
|
||||||
|
<router-link to="/">%i18n:@start%</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
meta: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
(this as any).os.getMeta().then(meta => {
|
||||||
|
this.meta = meta;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.nhasjydimbopojusarffqjyktglcuxjy
|
||||||
|
color var(--text)
|
||||||
|
background var(--face)
|
||||||
|
text-align center
|
||||||
|
|
||||||
|
> .banner
|
||||||
|
height 100px
|
||||||
|
background-position center
|
||||||
|
background-size cover
|
||||||
|
|
||||||
|
> h1
|
||||||
|
margin 16px
|
||||||
|
font-size 16px
|
||||||
|
|
||||||
|
> p
|
||||||
|
margin 16px
|
||||||
|
font-size 14px
|
||||||
|
|
||||||
|
> a
|
||||||
|
display block
|
||||||
|
padding-bottom 16px
|
||||||
|
|
||||||
|
</style>
|
85
src/client/app/common/views/components/media-banner.vue
Normal file
85
src/client/app/common/views/components/media-banner.vue
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<template>
|
||||||
|
<div class="mk-media-banner">
|
||||||
|
<div class="sensitive" v-if="media.isSensitive && hide" @click="hide = false">
|
||||||
|
<span class="icon">%fa:exclamation-triangle%</span>
|
||||||
|
<b>%i18n:@sensitive%</b>
|
||||||
|
<span>%i18n:@click-to-show%</span>
|
||||||
|
</div>
|
||||||
|
<div class="audio" v-else-if="media.type.startsWith('audio')">
|
||||||
|
<audio class="audio"
|
||||||
|
:src="media.url"
|
||||||
|
:title="media.name"
|
||||||
|
controls
|
||||||
|
ref="audio"
|
||||||
|
preload="metadata" />
|
||||||
|
</div>
|
||||||
|
<a class="download" v-else
|
||||||
|
:href="media.url"
|
||||||
|
:title="media.name"
|
||||||
|
:download="media.name"
|
||||||
|
>
|
||||||
|
<span class="icon">%fa:download%</span>
|
||||||
|
<b>{{ media.name }}</b>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
media: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
hide: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.mk-media-banner
|
||||||
|
width 100%
|
||||||
|
border-radius 4px
|
||||||
|
margin-top 4px
|
||||||
|
overflow hidden
|
||||||
|
|
||||||
|
> .download,
|
||||||
|
> .sensitive
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
font-size 12px
|
||||||
|
padding 8px 12px
|
||||||
|
white-space nowrap
|
||||||
|
|
||||||
|
> *
|
||||||
|
display block
|
||||||
|
|
||||||
|
> b
|
||||||
|
overflow hidden
|
||||||
|
text-overflow ellipsis
|
||||||
|
|
||||||
|
> *:not(:last-child)
|
||||||
|
margin-right .2em
|
||||||
|
|
||||||
|
> .icon
|
||||||
|
font-size 1.6em
|
||||||
|
|
||||||
|
> .download
|
||||||
|
background var(--noteAttachedFile)
|
||||||
|
|
||||||
|
> .sensitive
|
||||||
|
background #111
|
||||||
|
color #fff
|
||||||
|
|
||||||
|
> .audio
|
||||||
|
.audio
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,18 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-media-list">
|
<div class="mk-media-list">
|
||||||
<div :data-count="mediaList.length" ref="grid">
|
<template v-for="media in mediaList.filter(media => !previewable(media))">
|
||||||
<template v-for="media in mediaList">
|
<x-banner :media="media" :key="media.id"/>
|
||||||
<mk-media-video :video="media" :key="media.id" v-if="media.type.startsWith('video')" :inline-playable="mediaList.length === 1"/>
|
|
||||||
<mk-media-image :image="media" :key="media.id" v-else :raw="raw"/>
|
|
||||||
</template>
|
</template>
|
||||||
|
<div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container">
|
||||||
|
<div :data-count="mediaList.filter(media => previewable(media)).length" ref="grid">
|
||||||
|
<template v-for="media in mediaList">
|
||||||
|
<mk-media-video :video="media" :key="media.id" v-if="media.type.startsWith('video')"/>
|
||||||
|
<mk-media-image :image="media" :key="media.id" v-else-if="media.type.startsWith('image')" :raw="raw"/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import XBanner from './media-banner.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
|
components: {
|
||||||
|
XBanner
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
mediaList: {
|
mediaList: {
|
||||||
required: true
|
required: true
|
||||||
|
@ -22,15 +31,25 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// for Safari bug
|
//#region for Safari bug
|
||||||
|
if (this.$refs.grid) {
|
||||||
this.$refs.grid.style.height = this.$refs.grid.clientHeight ? `${this.$refs.grid.clientHeight}px` : '128px';
|
this.$refs.grid.style.height = this.$refs.grid.clientHeight ? `${this.$refs.grid.clientHeight}px` : '128px';
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
previewable(file) {
|
||||||
|
return file.type.startsWith('video') || file.type.startsWith('image');
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.mk-media-list
|
.mk-media-list
|
||||||
|
> .gird-container
|
||||||
width 100%
|
width 100%
|
||||||
|
margin-top 4px
|
||||||
|
|
||||||
&:before
|
&:before
|
||||||
content ''
|
content ''
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-menu">
|
<div class="onchrpzrvnoruiaenfcqvccjfuupzzwv">
|
||||||
<div class="backdrop" ref="backdrop" @click="close"></div>
|
<div class="backdrop" ref="backdrop" @click="close"></div>
|
||||||
<div class="popover" :class="{ hukidasi }" ref="popover">
|
<div class="popover" :class="{ hukidasi }" ref="popover">
|
||||||
<template v-for="item in items">
|
<template v-for="item, i in items">
|
||||||
<div v-if="item === null"></div>
|
<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>
|
<button v-if="item" @click="clicked(item.action)" v-html="item.icon ? item.icon + ' ' + item.text : item.text" :tabindex="i"></button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -108,7 +108,7 @@ export default Vue.extend({
|
||||||
easing: 'easeInBack',
|
easing: 'easeInBack',
|
||||||
complete: () => {
|
complete: () => {
|
||||||
this.$emit('closed');
|
this.$emit('closed');
|
||||||
this.$destroy();
|
this.destroyDom();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -117,11 +117,10 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.onchrpzrvnoruiaenfcqvccjfuupzzwv
|
||||||
|
$bg-color = var(--popupBg)
|
||||||
$border-color = rgba(27, 31, 35, 0.15)
|
$border-color = rgba(27, 31, 35, 0.15)
|
||||||
|
|
||||||
.mk-menu
|
|
||||||
position initial
|
position initial
|
||||||
|
|
||||||
> .backdrop
|
> .backdrop
|
||||||
|
@ -131,14 +130,14 @@ $border-color = rgba(27, 31, 35, 0.15)
|
||||||
z-index 10000
|
z-index 10000
|
||||||
width 100%
|
width 100%
|
||||||
height 100%
|
height 100%
|
||||||
background rgba(#000, 0.1)
|
background var(--modalBackdrop)
|
||||||
opacity 0
|
opacity 0
|
||||||
|
|
||||||
> .popover
|
> .popover
|
||||||
position absolute
|
position absolute
|
||||||
z-index 10001
|
z-index 10001
|
||||||
padding 8px 0
|
padding 8px 0
|
||||||
background #fff
|
background $bg-color
|
||||||
border 1px solid $border-color
|
border 1px solid $border-color
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
box-shadow 0 3px 12px rgba(27, 31, 35, 0.15)
|
box-shadow 0 3px 12px rgba(27, 31, 35, 0.15)
|
||||||
|
@ -172,25 +171,26 @@ $border-color = rgba(27, 31, 35, 0.15)
|
||||||
border-top solid $balloon-size transparent
|
border-top solid $balloon-size transparent
|
||||||
border-left solid $balloon-size transparent
|
border-left solid $balloon-size transparent
|
||||||
border-right solid $balloon-size transparent
|
border-right solid $balloon-size transparent
|
||||||
border-bottom solid $balloon-size #fff
|
border-bottom solid $balloon-size $bg-color
|
||||||
|
|
||||||
> button
|
> button
|
||||||
display block
|
display block
|
||||||
padding 8px 16px
|
padding 8px 16px
|
||||||
width 100%
|
width 100%
|
||||||
|
color var(--popupFg)
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
color $theme-color-foreground
|
color var(--primaryForeground)
|
||||||
background $theme-color
|
background var(--primary)
|
||||||
text-decoration none
|
text-decoration none
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
color $theme-color-foreground
|
color var(--primaryForeground)
|
||||||
background darken($theme-color, 10%)
|
background var(--primaryDarken10)
|
||||||
|
|
||||||
> div
|
> div
|
||||||
margin 8px 0
|
margin 8px 0
|
||||||
height 1px
|
height 1px
|
||||||
background #eee
|
background var(--faceDivider)
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -195,9 +195,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.mk-messaging-form
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
> textarea
|
> textarea
|
||||||
cursor auto
|
cursor auto
|
||||||
display block
|
display block
|
||||||
|
@ -209,10 +207,10 @@ root(isDark)
|
||||||
padding 8px
|
padding 8px
|
||||||
resize none
|
resize none
|
||||||
font-size 1em
|
font-size 1em
|
||||||
color isDark ? #fff : #000
|
color var(--inputText)
|
||||||
outline none
|
outline none
|
||||||
border none
|
border none
|
||||||
border-top solid 1px isDark ? #4b5056 : #eee
|
border-top solid 1px var(--faceDivider)
|
||||||
border-radius 0
|
border-radius 0
|
||||||
box-shadow none
|
box-shadow none
|
||||||
background transparent
|
background transparent
|
||||||
|
@ -234,10 +232,10 @@ root(isDark)
|
||||||
transition color 0.1s ease
|
transition color 0.1s ease
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
color $theme-color
|
color var(--primary)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
color darken($theme-color, 10%)
|
color var(--primaryDarken10)
|
||||||
transition color 0s ease
|
transition color 0s ease
|
||||||
|
|
||||||
.files
|
.files
|
||||||
|
@ -293,19 +291,13 @@ root(isDark)
|
||||||
transition color 0.1s ease
|
transition color 0.1s ease
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
color $theme-color
|
color var(--primary)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
color darken($theme-color, 10%)
|
color var(--primaryDarken10)
|
||||||
transition color 0s ease
|
transition color 0s ease
|
||||||
|
|
||||||
input[type=file]
|
input[type=file]
|
||||||
display none
|
display none
|
||||||
|
|
||||||
.mk-messaging-form[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.mk-messaging-form:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -59,10 +59,8 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.message
|
||||||
|
$me-balloon-color = var(--primary)
|
||||||
root(isDark)
|
|
||||||
$me-balloon-color = $theme-color
|
|
||||||
|
|
||||||
padding 10px 12px 10px 12px
|
padding 10px 12px 10px 12px
|
||||||
background-color transparent
|
background-color transparent
|
||||||
|
@ -179,7 +177,7 @@ root(isDark)
|
||||||
display block
|
display block
|
||||||
margin 2px 0 0 0
|
margin 2px 0 0 0
|
||||||
font-size 10px
|
font-size 10px
|
||||||
color isDark ? rgba(#fff, 0.4) : rgba(#000, 0.4)
|
color var(--messagingRoomMessageInfo)
|
||||||
|
|
||||||
> [data-fa]
|
> [data-fa]
|
||||||
margin-left 4px
|
margin-left 4px
|
||||||
|
@ -192,7 +190,7 @@ root(isDark)
|
||||||
padding-left 66px
|
padding-left 66px
|
||||||
|
|
||||||
> .balloon
|
> .balloon
|
||||||
$color = isDark ? #2d3338 : #eee
|
$color = var(--messagingRoomMessageBg)
|
||||||
float left
|
float left
|
||||||
background $color
|
background $color
|
||||||
|
|
||||||
|
@ -208,8 +206,7 @@ root(isDark)
|
||||||
|
|
||||||
> .content
|
> .content
|
||||||
> .text
|
> .text
|
||||||
if isDark
|
color var(--messagingRoomMessageFg)
|
||||||
color #fff
|
|
||||||
|
|
||||||
> footer
|
> footer
|
||||||
text-align left
|
text-align left
|
||||||
|
@ -250,18 +247,9 @@ root(isDark)
|
||||||
|
|
||||||
> .read
|
> .read
|
||||||
user-select none
|
user-select none
|
||||||
margin 0 4px 0 0
|
|
||||||
color isDark ? rgba(#fff, 0.5) : rgba(#000, 0.5)
|
|
||||||
font-size 11px
|
|
||||||
|
|
||||||
&[data-is-deleted]
|
&[data-is-deleted]
|
||||||
> .balloon
|
> .balloon
|
||||||
opacity 0.5
|
opacity 0.5
|
||||||
|
|
||||||
.message[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.message:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
@dragover.prevent.stop="onDragover"
|
@dragover.prevent.stop="onDragover"
|
||||||
@drop.prevent.stop="onDrop"
|
@drop.prevent.stop="onDrop"
|
||||||
>
|
>
|
||||||
<div class="stream">
|
<div class="body">
|
||||||
<p class="init" v-if="init">%fa:spinner .spin%%i18n:common.loading%</p>
|
<p class="init" v-if="init">%fa:spinner .spin%%i18n:common.loading%</p>
|
||||||
<p class="empty" v-if="!init && messages.length == 0">%fa:info-circle%%i18n:@empty%</p>
|
<p class="empty" v-if="!init && messages.length == 0">%fa:info-circle%%i18n:@empty%</p>
|
||||||
<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages">%fa:flag%%i18n:@no-history%</p>
|
<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages">%fa:flag%%i18n:@no-history%</p>
|
||||||
|
@ -30,7 +30,6 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { MessagingStream } from '../../scripts/streaming/messaging';
|
|
||||||
import XMessage from './messaging-room.message.vue';
|
import XMessage from './messaging-room.message.vue';
|
||||||
import XForm from './messaging-room.form.vue';
|
import XForm from './messaging-room.form.vue';
|
||||||
import { url } from '../../../config';
|
import { url } from '../../../config';
|
||||||
|
@ -72,11 +71,17 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.connection = new MessagingStream((this as any).os, this.$store.state.i, this.user.id);
|
this.connection =((this as any).os.stream.connectToChannel('messaging', { otherparty: 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);
|
||||||
|
|
||||||
|
if (this.isNaked) {
|
||||||
|
window.addEventListener('scroll', this.onScroll, { passive: true });
|
||||||
|
} else {
|
||||||
|
this.$el.addEventListener('scroll', this.onScroll, { passive: true });
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', this.onVisibilitychange);
|
document.addEventListener('visibilitychange', this.onVisibilitychange);
|
||||||
|
|
||||||
this.fetchMessages().then(() => {
|
this.fetchMessages().then(() => {
|
||||||
|
@ -86,9 +91,13 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.connection.off('message', this.onMessage);
|
this.connection.dispose();
|
||||||
this.connection.off('read', this.onRead);
|
|
||||||
this.connection.close();
|
if (this.isNaked) {
|
||||||
|
window.removeEventListener('scroll', this.onScroll);
|
||||||
|
} else {
|
||||||
|
this.$el.removeEventListener('scroll', this.onScroll);
|
||||||
|
}
|
||||||
|
|
||||||
document.removeEventListener('visibilitychange', this.onVisibilitychange);
|
document.removeEventListener('visibilitychange', this.onVisibilitychange);
|
||||||
},
|
},
|
||||||
|
@ -226,6 +235,14 @@ export default Vue.extend({
|
||||||
}, 4000);
|
}, 4000);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onScroll() {
|
||||||
|
const el = this.isNaked ? window.document.documentElement : this.$el;
|
||||||
|
const current = el.scrollTop + el.clientHeight;
|
||||||
|
if (current > el.scrollHeight - 1) {
|
||||||
|
this.showIndicator = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onVisibilitychange() {
|
onVisibilitychange() {
|
||||||
if (document.hidden) return;
|
if (document.hidden) return;
|
||||||
this.messages.forEach(message => {
|
this.messages.forEach(message => {
|
||||||
|
@ -242,39 +259,28 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.mk-messaging-room
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
display flex
|
display flex
|
||||||
flex 1
|
flex 1
|
||||||
flex-direction column
|
flex-direction column
|
||||||
height 100%
|
height 100%
|
||||||
background isDark ? #191b22 : #fff
|
background var(--messagingRoomBg)
|
||||||
|
|
||||||
> .stream
|
> .body
|
||||||
width 100%
|
width 100%
|
||||||
max-width 600px
|
max-width 600px
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
flex 1
|
flex 1
|
||||||
|
|
||||||
> .init
|
> .init,
|
||||||
width 100%
|
|
||||||
margin 0
|
|
||||||
padding 16px 8px 8px 8px
|
|
||||||
text-align center
|
|
||||||
font-size 0.8em
|
|
||||||
color rgba(isDark ? #fff : #000, 0.4)
|
|
||||||
|
|
||||||
[data-fa]
|
|
||||||
margin-right 4px
|
|
||||||
|
|
||||||
> .empty
|
> .empty
|
||||||
width 100%
|
width 100%
|
||||||
margin 0
|
margin 0
|
||||||
padding 16px 8px 8px 8px
|
padding 16px 8px 8px 8px
|
||||||
text-align center
|
text-align center
|
||||||
font-size 0.8em
|
font-size 0.8em
|
||||||
color rgba(isDark ? #fff : #000, 0.4)
|
color var(--messagingRoomInfo)
|
||||||
|
opacity 0.5
|
||||||
|
|
||||||
[data-fa]
|
[data-fa]
|
||||||
margin-right 4px
|
margin-right 4px
|
||||||
|
@ -285,7 +291,8 @@ root(isDark)
|
||||||
padding 16px
|
padding 16px
|
||||||
text-align center
|
text-align center
|
||||||
font-size 0.8em
|
font-size 0.8em
|
||||||
color rgba(isDark ? #fff : #000, 0.4)
|
color var(--messagingRoomInfo)
|
||||||
|
opacity 0.5
|
||||||
|
|
||||||
[data-fa]
|
[data-fa]
|
||||||
margin-right 4px
|
margin-right 4px
|
||||||
|
@ -329,7 +336,7 @@ root(isDark)
|
||||||
left 0
|
left 0
|
||||||
right 0
|
right 0
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
background rgba(isDark ? #fff : #000, 0.1)
|
background var(--messagingRoomDateDividerLine)
|
||||||
|
|
||||||
> span
|
> span
|
||||||
display inline-block
|
display inline-block
|
||||||
|
@ -337,8 +344,8 @@ root(isDark)
|
||||||
padding 0 16px
|
padding 0 16px
|
||||||
//font-weight bold
|
//font-weight bold
|
||||||
line-height 32px
|
line-height 32px
|
||||||
color rgba(isDark ? #fff : #000, 0.3)
|
color var(--messagingRoomDateDividerText)
|
||||||
background isDark ? #191b22 : #fff
|
background var(--messagingRoomBg)
|
||||||
|
|
||||||
> footer
|
> footer
|
||||||
position -webkit-sticky
|
position -webkit-sticky
|
||||||
|
@ -349,7 +356,7 @@ root(isDark)
|
||||||
max-width 600px
|
max-width 600px
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
padding 0
|
padding 0
|
||||||
background rgba(isDark ? #282c37 : #fff, 0.95)
|
//background rgba(var(--face), 0.95)
|
||||||
background-clip content-box
|
background-clip content-box
|
||||||
|
|
||||||
> .new-message
|
> .new-message
|
||||||
|
@ -366,15 +373,15 @@ root(isDark)
|
||||||
cursor pointer
|
cursor pointer
|
||||||
line-height 32px
|
line-height 32px
|
||||||
font-size 12px
|
font-size 12px
|
||||||
color $theme-color-foreground
|
color var(--primaryForeground)
|
||||||
background $theme-color
|
background var(--primary)
|
||||||
border-radius 16px
|
border-radius 16px
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
background lighten($theme-color, 10%)
|
background var(--primaryLighten10)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
background darken($theme-color, 10%)
|
background var(--primaryDarken10)
|
||||||
|
|
||||||
> [data-fa]
|
> [data-fa]
|
||||||
position absolute
|
position absolute
|
||||||
|
@ -390,10 +397,4 @@ root(isDark)
|
||||||
transition opacity 0.5s
|
transition opacity 0.5s
|
||||||
opacity 0
|
opacity 0
|
||||||
|
|
||||||
.mk-messaging-room[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.mk-messaging-room:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -71,13 +71,11 @@ export default Vue.extend({
|
||||||
messages: [],
|
messages: [],
|
||||||
q: null,
|
q: null,
|
||||||
result: [],
|
result: [],
|
||||||
connection: null,
|
connection: null
|
||||||
connectionId: null
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.connection = (this as any).os.streams.messagingIndexStream.getConnection();
|
this.connection = (this as any).os.stream.useSharedConnection('messagingIndex');
|
||||||
this.connectionId = (this as any).os.streams.messagingIndexStream.use();
|
|
||||||
|
|
||||||
this.connection.on('message', this.onMessage);
|
this.connection.on('message', this.onMessage);
|
||||||
this.connection.on('read', this.onRead);
|
this.connection.on('read', this.onRead);
|
||||||
|
@ -88,9 +86,7 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.connection.off('message', this.onMessage);
|
this.connection.dispose();
|
||||||
this.connection.off('read', this.onRead);
|
|
||||||
(this as any).os.streams.messagingIndexStream.dispose(this.connectionId);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getAcct,
|
getAcct,
|
||||||
|
@ -167,9 +163,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.mk-messaging
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
|
|
||||||
&[data-compact]
|
&[data-compact]
|
||||||
font-size 0.8em
|
font-size 0.8em
|
||||||
|
@ -204,12 +198,10 @@ root(isDark)
|
||||||
left 0
|
left 0
|
||||||
z-index 1
|
z-index 1
|
||||||
width 100%
|
width 100%
|
||||||
background #fff
|
|
||||||
box-shadow 0 0px 2px rgba(#000, 0.2)
|
box-shadow 0 0px 2px rgba(#000, 0.2)
|
||||||
|
|
||||||
> .form
|
> .form
|
||||||
padding 8px
|
background rgba(0, 0, 0, 0.02)
|
||||||
background isDark ? #282c37 : #f7f7f7
|
|
||||||
|
|
||||||
> label
|
> label
|
||||||
display block
|
display block
|
||||||
|
@ -229,32 +221,22 @@ root(isDark)
|
||||||
bottom 0
|
bottom 0
|
||||||
left 0
|
left 0
|
||||||
width 1em
|
width 1em
|
||||||
line-height 56px
|
line-height 48px
|
||||||
margin auto
|
margin auto
|
||||||
color #555
|
color #555
|
||||||
|
|
||||||
> input
|
> input
|
||||||
margin 0
|
margin 0
|
||||||
padding 0 0 0 32px
|
padding 0 0 0 42px
|
||||||
width 100%
|
width 100%
|
||||||
font-size 1em
|
font-size 1em
|
||||||
line-height 38px
|
line-height 48px
|
||||||
color #000
|
color var(--faceText)
|
||||||
outline none
|
outline none
|
||||||
background isDark ? #191b22 : #fff
|
background transparent
|
||||||
border solid 1px isDark ? #495156 : #eee
|
border none
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
box-shadow none
|
box-shadow none
|
||||||
transition color 0.5s ease, border 0.5s ease
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
border solid 1px isDark ? #b0b0b0 : #ddd
|
|
||||||
transition border 0.2s ease
|
|
||||||
|
|
||||||
&:focus
|
|
||||||
color darken($theme-color, 20%)
|
|
||||||
border solid 1px $theme-color
|
|
||||||
transition color 0, border 0
|
|
||||||
|
|
||||||
> .result
|
> .result
|
||||||
display block
|
display block
|
||||||
|
@ -287,7 +269,7 @@ root(isDark)
|
||||||
&:hover
|
&:hover
|
||||||
&:focus
|
&:focus
|
||||||
color #fff
|
color #fff
|
||||||
background $theme-color
|
background var(--primary)
|
||||||
|
|
||||||
.name
|
.name
|
||||||
color #fff
|
color #fff
|
||||||
|
@ -297,7 +279,7 @@ root(isDark)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
color #fff
|
color #fff
|
||||||
background darken($theme-color, 10%)
|
background var(--primaryDarken10)
|
||||||
|
|
||||||
.name
|
.name
|
||||||
color #fff
|
color #fff
|
||||||
|
@ -329,21 +311,21 @@ root(isDark)
|
||||||
> a
|
> a
|
||||||
display block
|
display block
|
||||||
text-decoration none
|
text-decoration none
|
||||||
background isDark ? #282c37 : #fff
|
background var(--face)
|
||||||
border-bottom solid 1px isDark ? #1c2023 : #eee
|
border-bottom solid 1px var(--faceDivider)
|
||||||
|
|
||||||
*
|
*
|
||||||
pointer-events none
|
pointer-events none
|
||||||
user-select none
|
user-select none
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
background isDark ? #1e2129 : #fafafa
|
box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.05)
|
||||||
|
|
||||||
> .avatar
|
.avatar
|
||||||
filter saturate(200%)
|
filter saturate(200%)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
background isDark ? #14161b : #eee
|
box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.1)
|
||||||
|
|
||||||
&[data-is-read]
|
&[data-is-read]
|
||||||
&[data-is-me]
|
&[data-is-me]
|
||||||
|
@ -383,17 +365,17 @@ root(isDark)
|
||||||
overflow hidden
|
overflow hidden
|
||||||
text-overflow ellipsis
|
text-overflow ellipsis
|
||||||
font-size 1em
|
font-size 1em
|
||||||
color isDark ? #fff : rgba(#000, 0.9)
|
color var(--noteHeaderName)
|
||||||
font-weight bold
|
font-weight bold
|
||||||
transition all 0.1s ease
|
transition all 0.1s ease
|
||||||
|
|
||||||
> .username
|
> .username
|
||||||
margin 0 8px
|
margin 0 8px
|
||||||
color isDark ? #606984 : rgba(#000, 0.5)
|
color var(--noteHeaderAcct)
|
||||||
|
|
||||||
> .mk-time
|
> .mk-time
|
||||||
margin 0 0 0 auto
|
margin 0 0 0 auto
|
||||||
color isDark ? #606984 : rgba(#000, 0.5)
|
color var(--noteHeaderInfo)
|
||||||
font-size 80%
|
font-size 80%
|
||||||
|
|
||||||
> .avatar
|
> .avatar
|
||||||
|
@ -413,10 +395,10 @@ root(isDark)
|
||||||
overflow hidden
|
overflow hidden
|
||||||
overflow-wrap break-word
|
overflow-wrap break-word
|
||||||
font-size 1.1em
|
font-size 1.1em
|
||||||
color isDark ? #fff : rgba(#000, 0.8)
|
color var(--faceText)
|
||||||
|
|
||||||
.me
|
.me
|
||||||
color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.4)
|
opacity 0.7
|
||||||
|
|
||||||
> .image
|
> .image
|
||||||
display block
|
display block
|
||||||
|
@ -461,10 +443,4 @@ root(isDark)
|
||||||
> .avatar
|
> .avatar
|
||||||
margin 0 12px 0 0
|
margin 0 12px 0 0
|
||||||
|
|
||||||
.mk-messaging[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.mk-messaging:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import Vue from 'vue';
|
import Vue, { VNode } from 'vue';
|
||||||
import * as emojilib from 'emojilib';
|
import * as emojilib from 'emojilib';
|
||||||
import { length } from 'stringz';
|
import { length } from 'stringz';
|
||||||
import parse from '../../../../../mfm/parse';
|
import parse from '../../../../../mfm/parse';
|
||||||
|
@ -6,10 +6,7 @@ import getAcct from '../../../../../misc/acct/render';
|
||||||
import { url } from '../../../config';
|
import { url } from '../../../config';
|
||||||
import MkUrl from './url.vue';
|
import MkUrl from './url.vue';
|
||||||
import MkGoogle from './google.vue';
|
import MkGoogle from './google.vue';
|
||||||
|
import { concat } from '../../../../../prelude/array';
|
||||||
const flatten = list => list.reduce(
|
|
||||||
(a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []
|
|
||||||
);
|
|
||||||
|
|
||||||
export default Vue.component('misskey-flavored-markdown', {
|
export default Vue.component('misskey-flavored-markdown', {
|
||||||
props: {
|
props: {
|
||||||
|
@ -32,20 +29,20 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||||
},
|
},
|
||||||
|
|
||||||
render(createElement) {
|
render(createElement) {
|
||||||
let ast;
|
let ast: any[];
|
||||||
|
|
||||||
if (this.ast == null) {
|
if (this.ast == null) {
|
||||||
// Parse text to ast
|
// Parse text to ast
|
||||||
ast = parse(this.text);
|
ast = parse(this.text);
|
||||||
} else {
|
} else {
|
||||||
ast = this.ast;
|
ast = this.ast as any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
let bigCount = 0;
|
let bigCount = 0;
|
||||||
let motionCount = 0;
|
let motionCount = 0;
|
||||||
|
|
||||||
// Parse ast to DOM
|
// Parse ast to DOM
|
||||||
const els = flatten(ast.map(token => {
|
const els = concat(ast.map((token): VNode[] => {
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case 'text': {
|
case 'text': {
|
||||||
const text = token.content.replace(/(\r\n|\n|\r)/g, '\n');
|
const text = token.content.replace(/(\r\n|\n|\r)/g, '\n');
|
||||||
|
@ -56,12 +53,12 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||||
x[x.length - 1].pop();
|
x[x.length - 1].pop();
|
||||||
return x;
|
return x;
|
||||||
} else {
|
} else {
|
||||||
return createElement('span', text.replace(/\n/g, ' '));
|
return [createElement('span', text.replace(/\n/g, ' '))];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'bold': {
|
case 'bold': {
|
||||||
return createElement('b', token.bold);
|
return [createElement('b', token.bold)];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'big': {
|
case 'big': {
|
||||||
|
@ -95,23 +92,23 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'url': {
|
case 'url': {
|
||||||
return createElement(MkUrl, {
|
return [createElement(MkUrl, {
|
||||||
props: {
|
props: {
|
||||||
url: token.content,
|
url: token.content,
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
}
|
}
|
||||||
});
|
})];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'link': {
|
case 'link': {
|
||||||
return createElement('a', {
|
return [createElement('a', {
|
||||||
attrs: {
|
attrs: {
|
||||||
class: 'link',
|
class: 'link',
|
||||||
href: token.url,
|
href: token.url,
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
title: token.url
|
title: token.url
|
||||||
}
|
}
|
||||||
}, token.title);
|
}, token.title)];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'mention': {
|
case 'mention': {
|
||||||
|
@ -129,16 +126,16 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'hashtag': {
|
case 'hashtag': {
|
||||||
return createElement('a', {
|
return [createElement('a', {
|
||||||
attrs: {
|
attrs: {
|
||||||
href: `${url}/tags/${encodeURIComponent(token.hashtag)}`,
|
href: `${url}/tags/${encodeURIComponent(token.hashtag)}`,
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
}
|
}
|
||||||
}, token.content);
|
}, token.content)];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'code': {
|
case 'code': {
|
||||||
return createElement('pre', {
|
return [createElement('pre', {
|
||||||
class: 'code'
|
class: 'code'
|
||||||
}, [
|
}, [
|
||||||
createElement('code', {
|
createElement('code', {
|
||||||
|
@ -146,15 +143,15 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||||
innerHTML: token.html
|
innerHTML: token.html
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
]);
|
])];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'inline-code': {
|
case 'inline-code': {
|
||||||
return createElement('code', {
|
return [createElement('code', {
|
||||||
domProps: {
|
domProps: {
|
||||||
innerHTML: token.html
|
innerHTML: token.html
|
||||||
}
|
}
|
||||||
});
|
})];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'quote': {
|
case 'quote': {
|
||||||
|
@ -164,58 +161,51 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||||
const x = text2.split('\n')
|
const x = text2.split('\n')
|
||||||
.map(t => [createElement('span', t), createElement('br')]);
|
.map(t => [createElement('span', t), createElement('br')]);
|
||||||
x[x.length - 1].pop();
|
x[x.length - 1].pop();
|
||||||
return createElement('div', {
|
return [createElement('div', {
|
||||||
attrs: {
|
attrs: {
|
||||||
class: 'quote'
|
class: 'quote'
|
||||||
}
|
}
|
||||||
}, x);
|
}, x)];
|
||||||
} else {
|
} else {
|
||||||
return createElement('span', {
|
return [createElement('span', {
|
||||||
attrs: {
|
attrs: {
|
||||||
class: 'quote'
|
class: 'quote'
|
||||||
}
|
}
|
||||||
}, text2.replace(/\n/g, ' '));
|
}, text2.replace(/\n/g, ' '))];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'title': {
|
case 'title': {
|
||||||
return createElement('div', {
|
return [createElement('div', {
|
||||||
attrs: {
|
attrs: {
|
||||||
class: 'title'
|
class: 'title'
|
||||||
}
|
}
|
||||||
}, token.title);
|
}, token.title)];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'emoji': {
|
case 'emoji': {
|
||||||
const emoji = emojilib.lib[token.emoji];
|
const emoji = emojilib.lib[token.emoji];
|
||||||
return createElement('span', emoji ? emoji.char : token.content);
|
return [createElement('span', emoji ? emoji.char : token.content)];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'search': {
|
case 'search': {
|
||||||
return createElement(MkGoogle, {
|
return [createElement(MkGoogle, {
|
||||||
props: {
|
props: {
|
||||||
q: token.query
|
q: token.query
|
||||||
}
|
}
|
||||||
});
|
})];
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
console.log('unknown ast type:', token.type);
|
console.log('unknown ast type:', token.type);
|
||||||
|
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const _els = [];
|
// el.tag === 'br' のとき i !== 0 が保証されるため、短絡評価により els[i - 1] は配列外参照しない
|
||||||
els.forEach((el, i) => {
|
const _els = els.filter((el, i) => !(el.tag === 'br' && ['div', 'pre'].includes(els[i - 1].tag)));
|
||||||
if (el.tag == 'br') {
|
|
||||||
if (!['div', 'pre'].includes(els[i - 1].tag)) {
|
|
||||||
_els.push(el);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_els.push(el);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return createElement('span', _els);
|
return createElement('span', _els);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
<span class="mk-nav">
|
<span class="mk-nav">
|
||||||
<a :href="aboutUrl">%i18n:@about%</a>
|
<a :href="aboutUrl">%i18n:@about%</a>
|
||||||
<i>・</i>
|
<i>・</i>
|
||||||
|
<a href="/stats">%i18n:@stats%</a>
|
||||||
|
<i>・</i>
|
||||||
<a :href="repositoryUrl">%i18n:@repository%</a>
|
<a :href="repositoryUrl">%i18n:@repository%</a>
|
||||||
<i>・</i>
|
<i>・</i>
|
||||||
<a :href="feedbackUrl" target="_blank">%i18n:@feedback%</a>
|
<a :href="feedbackUrl" target="_blank">%i18n:@feedback%</a>
|
||||||
|
|
|
@ -42,9 +42,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.bvonvjxbwzaiskogyhbwgyxvcgserpmu
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
display flex
|
display flex
|
||||||
align-items baseline
|
align-items baseline
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
|
@ -61,7 +59,7 @@ root(isDark)
|
||||||
margin 0 .5em 0 0
|
margin 0 .5em 0 0
|
||||||
padding 0
|
padding 0
|
||||||
overflow hidden
|
overflow hidden
|
||||||
color isDark ? #fff : #627079
|
color var(--noteHeaderName)
|
||||||
font-size 1em
|
font-size 1em
|
||||||
font-weight bold
|
font-weight bold
|
||||||
text-decoration none
|
text-decoration none
|
||||||
|
@ -82,19 +80,19 @@ root(isDark)
|
||||||
margin 0 .5em 0 0
|
margin 0 .5em 0 0
|
||||||
padding 1px 6px
|
padding 1px 6px
|
||||||
font-size 80%
|
font-size 80%
|
||||||
color isDark ? #758188 : #aaa
|
color var(--noteHeaderBadgeFg)
|
||||||
border solid 1px isDark ? #57616f : #ddd
|
background var(--noteHeaderBadgeBg)
|
||||||
border-radius 3px
|
border-radius 3px
|
||||||
|
|
||||||
&.is-admin
|
&.is-admin
|
||||||
border-color isDark ? #d42c41 : #f56a7b
|
background var(--noteHeaderAdminBg)
|
||||||
color isDark ? #d42c41 : #f56a7b
|
color var(--noteHeaderAdminFg)
|
||||||
|
|
||||||
> .username
|
> .username
|
||||||
margin 0 .5em 0 0
|
margin 0 .5em 0 0
|
||||||
overflow hidden
|
overflow hidden
|
||||||
text-overflow ellipsis
|
text-overflow ellipsis
|
||||||
color isDark ? #606984 : #ccc
|
color var(--noteHeaderAcct)
|
||||||
flex-shrink 2147483647
|
flex-shrink 2147483647
|
||||||
|
|
||||||
> .info
|
> .info
|
||||||
|
@ -102,7 +100,7 @@ root(isDark)
|
||||||
font-size 0.9em
|
font-size 0.9em
|
||||||
|
|
||||||
> *
|
> *
|
||||||
color isDark ? #606984 : #c0c0c0
|
color var(--noteHeaderInfo)
|
||||||
|
|
||||||
> .mobile
|
> .mobile
|
||||||
margin-right 8px
|
margin-right 8px
|
||||||
|
@ -110,15 +108,9 @@ root(isDark)
|
||||||
> .app
|
> .app
|
||||||
margin-right 8px
|
margin-right 8px
|
||||||
padding-right 8px
|
padding-right 8px
|
||||||
border-right solid 1px isDark ? #1c2023 : #eaeaea
|
border-right solid 1px var(--faceDivider)
|
||||||
|
|
||||||
> .visibility
|
> .visibility
|
||||||
margin-left 8px
|
margin-left 8px
|
||||||
|
|
||||||
.bvonvjxbwzaiskogyhbwgyxvcgserpmu[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.bvonvjxbwzaiskogyhbwgyxvcgserpmu:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -6,29 +6,51 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import { url } from '../../../config';
|
||||||
|
import copyToClipboard from '../../../common/scripts/copy-to-clipboard';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['note', 'source', 'compact'],
|
props: ['note', 'source', 'compact'],
|
||||||
computed: {
|
computed: {
|
||||||
items() {
|
items() {
|
||||||
const items = [];
|
const items = [{
|
||||||
items.push({
|
icon: '%fa:info-circle%',
|
||||||
|
text: '%i18n:@detail%',
|
||||||
|
action: this.detail
|
||||||
|
}, {
|
||||||
|
icon: '%fa:link%',
|
||||||
|
text: '%i18n:@copy-link%',
|
||||||
|
action: this.copyLink
|
||||||
|
}, null, {
|
||||||
icon: '%fa:star%',
|
icon: '%fa:star%',
|
||||||
text: '%i18n:@favorite%',
|
text: '%i18n:@favorite%',
|
||||||
action: this.favorite
|
action: this.favorite
|
||||||
});
|
}];
|
||||||
|
|
||||||
if (this.note.userId == this.$store.state.i.id) {
|
if (this.note.userId == this.$store.state.i.id) {
|
||||||
|
if ((this.$store.state.i.pinnedNoteIds || []).includes(this.note.id)) {
|
||||||
|
items.push({
|
||||||
|
icon: '%fa:thumbtack%',
|
||||||
|
text: '%i18n:@unpin%',
|
||||||
|
action: this.unpin
|
||||||
|
});
|
||||||
|
} else {
|
||||||
items.push({
|
items.push({
|
||||||
icon: '%fa:thumbtack%',
|
icon: '%fa:thumbtack%',
|
||||||
text: '%i18n:@pin%',
|
text: '%i18n:@pin%',
|
||||||
action: this.pin
|
action: this.pin
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.note.userId == this.$store.state.i.id || this.$store.state.i.isAdmin) {
|
||||||
items.push({
|
items.push({
|
||||||
icon: '%fa:trash-alt R%',
|
icon: '%fa:trash-alt R%',
|
||||||
text: '%i18n:@delete%',
|
text: '%i18n:@delete%',
|
||||||
action: this.del
|
action: this.del
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.note.uri) {
|
if (this.note.uri) {
|
||||||
items.push({
|
items.push({
|
||||||
icon: '%fa:external-link-square-alt%',
|
icon: '%fa:external-link-square-alt%',
|
||||||
|
@ -38,15 +60,33 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
detail() {
|
||||||
|
this.$router.push(`/notes/${ this.note.id }`);
|
||||||
|
},
|
||||||
|
|
||||||
|
copyLink() {
|
||||||
|
copyToClipboard(`${url}/notes/${ this.note.id }`);
|
||||||
|
},
|
||||||
|
|
||||||
pin() {
|
pin() {
|
||||||
(this as any).api('i/pin', {
|
(this as any).api('i/pin', {
|
||||||
noteId: this.note.id
|
noteId: this.note.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.$destroy();
|
this.destroyDom();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
unpin() {
|
||||||
|
(this as any).api('i/unpin', {
|
||||||
|
noteId: this.note.id
|
||||||
|
}).then(() => {
|
||||||
|
this.destroyDom();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -55,7 +95,7 @@ export default Vue.extend({
|
||||||
(this as any).api('notes/delete', {
|
(this as any).api('notes/delete', {
|
||||||
noteId: this.note.id
|
noteId: this.note.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.$destroy();
|
this.destroyDom();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -63,13 +103,13 @@ export default Vue.extend({
|
||||||
(this as any).api('notes/favorites/create', {
|
(this as any).api('notes/favorites/create', {
|
||||||
noteId: this.note.id
|
noteId: this.note.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.$destroy();
|
this.destroyDom();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
closed() {
|
closed() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$destroy();
|
this.destroyDom();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import { erase } from '../../../../../prelude/array';
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -53,7 +54,7 @@ export default Vue.extend({
|
||||||
|
|
||||||
get() {
|
get() {
|
||||||
return {
|
return {
|
||||||
choices: this.choices.filter(choice => choice != '')
|
choices: erase('', this.choices)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -67,9 +68,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.mk-poll-editor
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
padding 8px
|
padding 8px
|
||||||
|
|
||||||
> .caution
|
> .caution
|
||||||
|
@ -102,49 +101,43 @@ root(isDark)
|
||||||
padding 6px 8px
|
padding 6px 8px
|
||||||
width 300px
|
width 300px
|
||||||
font-size 14px
|
font-size 14px
|
||||||
color isDark ? #fff : #000
|
color var(--inputText)
|
||||||
background isDark ? #191b22 : #fff
|
background var(--pollEditorInputBg)
|
||||||
border solid 1px rgba($theme-color, 0.1)
|
border solid 1px var(--primaryAlpha01)
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
border-color rgba($theme-color, 0.2)
|
border-color var(--primaryAlpha02)
|
||||||
|
|
||||||
&:focus
|
&:focus
|
||||||
border-color rgba($theme-color, 0.5)
|
border-color var(--primaryAlpha05)
|
||||||
|
|
||||||
> button
|
> button
|
||||||
padding 4px 8px
|
padding 4px 8px
|
||||||
color rgba($theme-color, 0.4)
|
color var(--primaryAlpha04)
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
color rgba($theme-color, 0.6)
|
color var(--primaryAlpha06)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
color darken($theme-color, 30%)
|
color var(--primaryDarken30)
|
||||||
|
|
||||||
> .add
|
> .add
|
||||||
margin 8px 0 0 0
|
margin 8px 0 0 0
|
||||||
vertical-align top
|
vertical-align top
|
||||||
color $theme-color
|
color var(--primary)
|
||||||
|
|
||||||
> .destroy
|
> .destroy
|
||||||
position absolute
|
position absolute
|
||||||
top 0
|
top 0
|
||||||
right 0
|
right 0
|
||||||
padding 4px 8px
|
padding 4px 8px
|
||||||
color rgba($theme-color, 0.4)
|
color var(--primaryAlpha04)
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
color rgba($theme-color, 0.6)
|
color var(--primaryAlpha06)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
color darken($theme-color, 30%)
|
color var(--primaryDarken30)
|
||||||
|
|
||||||
.mk-poll-editor[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.mk-poll-editor:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import { sum } from '../../../../../prelude/array';
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['note'],
|
props: ['note'],
|
||||||
data() {
|
data() {
|
||||||
|
@ -33,7 +34,7 @@ export default Vue.extend({
|
||||||
return this.note.poll;
|
return this.note.poll;
|
||||||
},
|
},
|
||||||
total(): number {
|
total(): number {
|
||||||
return this.poll.choices.reduce((a, b) => a + b.votes, 0);
|
return sum(this.poll.choices.map(x => x.votes));
|
||||||
},
|
},
|
||||||
isVoted(): boolean {
|
isVoted(): boolean {
|
||||||
return this.poll.choices.some(c => c.isVoted);
|
return this.poll.choices.some(c => c.isVoted);
|
||||||
|
@ -66,10 +67,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.mk-poll
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
|
|
||||||
> ul
|
> ul
|
||||||
display block
|
display block
|
||||||
margin 0
|
margin 0
|
||||||
|
@ -81,8 +79,8 @@ root(isDark)
|
||||||
margin 4px 0
|
margin 4px 0
|
||||||
padding 4px 8px
|
padding 4px 8px
|
||||||
width 100%
|
width 100%
|
||||||
color isDark ? #fff : #000
|
color var(--pollChoiceText)
|
||||||
border solid 1px isDark ? #5e636f : #eee
|
border solid 1px var(--pollChoiceBorder)
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
overflow hidden
|
overflow hidden
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
@ -98,7 +96,7 @@ root(isDark)
|
||||||
top 0
|
top 0
|
||||||
left 0
|
left 0
|
||||||
height 100%
|
height 100%
|
||||||
background $theme-color
|
background var(--primary)
|
||||||
transition width 1s ease
|
transition width 1s ease
|
||||||
|
|
||||||
> span
|
> span
|
||||||
|
@ -109,7 +107,7 @@ root(isDark)
|
||||||
margin-left 4px
|
margin-left 4px
|
||||||
|
|
||||||
> p
|
> p
|
||||||
color isDark ? #a3aebf : #000
|
color var(--text)
|
||||||
|
|
||||||
a
|
a
|
||||||
color inherit
|
color inherit
|
||||||
|
@ -124,10 +122,4 @@ root(isDark)
|
||||||
&:active
|
&:active
|
||||||
background transparent
|
background transparent
|
||||||
|
|
||||||
.mk-poll[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.mk-poll:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<span class="mk-reaction-icon">
|
<span class="mk-reaction-icon">
|
||||||
<img v-if="reaction == 'like'" src="/assets/reactions/like.png" alt="%i18n:common.reactions.like%">
|
<img v-if="reaction == 'like'" src="https://twemoji.maxcdn.com/2/svg/1f44d.svg" alt="%i18n:common.reactions.like%">
|
||||||
<img v-if="reaction == 'love'" src="/assets/reactions/love.png" alt="%i18n:common.reactions.love%">
|
<img v-if="reaction == 'love'" src="https://twemoji.maxcdn.com/2/svg/2764.svg" alt="%i18n:common.reactions.love%">
|
||||||
<img v-if="reaction == 'laugh'" src="/assets/reactions/laugh.png" alt="%i18n:common.reactions.laugh%">
|
<img v-if="reaction == 'laugh'" src="https://twemoji.maxcdn.com/2/svg/1f606.svg" alt="%i18n:common.reactions.laugh%">
|
||||||
<img v-if="reaction == 'hmm'" src="/assets/reactions/hmm.png" alt="%i18n:common.reactions.hmm%">
|
<img v-if="reaction == 'hmm'" src="https://twemoji.maxcdn.com/2/svg/1f914.svg" alt="%i18n:common.reactions.hmm%">
|
||||||
<img v-if="reaction == 'surprise'" src="/assets/reactions/surprise.png" alt="%i18n:common.reactions.surprise%">
|
<img v-if="reaction == 'surprise'" src="https://twemoji.maxcdn.com/2/svg/1f62e.svg" alt="%i18n:common.reactions.surprise%">
|
||||||
<img v-if="reaction == 'congrats'" src="/assets/reactions/congrats.png" alt="%i18n:common.reactions.congrats%">
|
<img v-if="reaction == 'congrats'" src="https://twemoji.maxcdn.com/2/svg/1f389.svg" alt="%i18n:common.reactions.congrats%">
|
||||||
<img v-if="reaction == 'angry'" src="/assets/reactions/angry.png" alt="%i18n:common.reactions.angry%">
|
<img v-if="reaction == 'angry'" src="https://twemoji.maxcdn.com/2/svg/1f4a2.svg" alt="%i18n:common.reactions.angry%">
|
||||||
<img v-if="reaction == 'confused'" src="/assets/reactions/confused.png" alt="%i18n:common.reactions.confused%">
|
<img v-if="reaction == 'confused'" src="https://twemoji.maxcdn.com/2/svg/1f625.svg" alt="%i18n:common.reactions.confused%">
|
||||||
<img v-if="reaction == 'rip'" src="/assets/reactions/rip.png" alt="%i18n:common.reactions.rip%">
|
<img v-if="reaction == 'rip'" src="https://twemoji.maxcdn.com/2/svg/1f607.svg" alt="%i18n:common.reactions.rip%">
|
||||||
<template v-if="reaction == 'pudding'">
|
<template v-if="reaction == 'pudding'">
|
||||||
<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="/assets/reactions/sushi.png" alt="%i18n:common.reactions.pudding%">
|
<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="https://twemoji.maxcdn.com/2/svg/1f363.svg" alt="%i18n:common.reactions.pudding%">
|
||||||
<img v-else src="/assets/reactions/pudding.png" alt="%i18n:common.reactions.pudding%">
|
<img v-else src="https://twemoji.maxcdn.com/2/svg/1f36e.svg" alt="%i18n:common.reactions.pudding%">
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-reaction-picker">
|
<div class="mk-reaction-picker" v-hotkey.global="keymap">
|
||||||
<div class="backdrop" ref="backdrop" @click="close"></div>
|
<div class="backdrop" ref="backdrop" @click="close"></div>
|
||||||
<div class="popover" :class="{ compact, big }" ref="popover">
|
<div class="popover" :class="{ compact, big }" ref="popover">
|
||||||
<p v-if="!compact">{{ title }}</p>
|
<p v-if="!compact">{{ title }}</p>
|
||||||
<div>
|
<div ref="buttons" :class="{ showFocus }">
|
||||||
<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" title="%i18n:common.reactions.like%"><mk-reaction-icon reaction='like'/></button>
|
<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" title="%i18n:common.reactions.like%"><mk-reaction-icon reaction='like'/></button>
|
||||||
<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" title="%i18n:common.reactions.love%"><mk-reaction-icon reaction='love'/></button>
|
<button @click="react('love')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="2" title="%i18n:common.reactions.love%"><mk-reaction-icon reaction='love'/></button>
|
||||||
<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" title="%i18n:common.reactions.laugh%"><mk-reaction-icon reaction='laugh'/></button>
|
<button @click="react('laugh')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="3" title="%i18n:common.reactions.laugh%"><mk-reaction-icon reaction='laugh'/></button>
|
||||||
|
@ -31,30 +31,84 @@ export default Vue.extend({
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
|
||||||
source: {
|
source: {
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
|
||||||
compact: {
|
compact: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
|
||||||
cb: {
|
cb: {
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
|
|
||||||
big: {
|
big: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
|
showFocus: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
|
animation: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
title: placeholder
|
title: placeholder,
|
||||||
|
focus: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
keymap(): any {
|
||||||
|
return {
|
||||||
|
'esc': this.close,
|
||||||
|
'enter|space|plus': this.choose,
|
||||||
|
'up|k': this.focusUp,
|
||||||
|
'left|h|shift+tab': this.focusLeft,
|
||||||
|
'right|l|tab': this.focusRight,
|
||||||
|
'down|j': this.focusDown,
|
||||||
|
'1': () => this.react('like'),
|
||||||
|
'2': () => this.react('love'),
|
||||||
|
'3': () => this.react('laugh'),
|
||||||
|
'4': () => this.react('hmm'),
|
||||||
|
'5': () => this.react('surprise'),
|
||||||
|
'6': () => this.react('congrats'),
|
||||||
|
'7': () => this.react('angry'),
|
||||||
|
'8': () => this.react('confused'),
|
||||||
|
'9': () => this.react('rip'),
|
||||||
|
'0': () => this.react('pudding'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
focus(i) {
|
||||||
|
this.$refs.buttons.children[i].focus();
|
||||||
|
|
||||||
|
if (this.showFocus) {
|
||||||
|
this.title = this.$refs.buttons.children[i].title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
this.focus = 0;
|
||||||
|
|
||||||
const popover = this.$refs.popover as any;
|
const popover = this.$refs.popover as any;
|
||||||
|
|
||||||
const rect = this.source.getBoundingClientRect();
|
const rect = this.source.getBoundingClientRect();
|
||||||
|
@ -76,7 +130,7 @@ export default Vue.extend({
|
||||||
anime({
|
anime({
|
||||||
targets: this.$refs.backdrop,
|
targets: this.$refs.backdrop,
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
duration: 100,
|
duration: this.animation ? 100 : 0,
|
||||||
easing: 'linear'
|
easing: 'linear'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -84,10 +138,11 @@ export default Vue.extend({
|
||||||
targets: this.$refs.popover,
|
targets: this.$refs.popover,
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
scale: [0.5, 1],
|
scale: [0.5, 1],
|
||||||
duration: 500
|
duration: this.animation ? 500 : 0
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
react(reaction) {
|
react(reaction) {
|
||||||
(this as any).api('notes/reactions/create', {
|
(this as any).api('notes/reactions/create', {
|
||||||
|
@ -95,21 +150,25 @@ export default Vue.extend({
|
||||||
reaction: reaction
|
reaction: reaction
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (this.cb) this.cb();
|
if (this.cb) this.cb();
|
||||||
this.$destroy();
|
this.$emit('closed');
|
||||||
|
this.destroyDom();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onMouseover(e) {
|
onMouseover(e) {
|
||||||
this.title = e.target.title;
|
this.title = e.target.title;
|
||||||
},
|
},
|
||||||
|
|
||||||
onMouseout(e) {
|
onMouseout(e) {
|
||||||
this.title = placeholder;
|
this.title = placeholder;
|
||||||
},
|
},
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
(this.$refs.backdrop as any).style.pointerEvents = 'none';
|
(this.$refs.backdrop as any).style.pointerEvents = 'none';
|
||||||
anime({
|
anime({
|
||||||
targets: this.$refs.backdrop,
|
targets: this.$refs.backdrop,
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
duration: 200,
|
duration: this.animation ? 200 : 0,
|
||||||
easing: 'linear'
|
easing: 'linear'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -118,21 +177,42 @@ export default Vue.extend({
|
||||||
targets: this.$refs.popover,
|
targets: this.$refs.popover,
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
scale: 0.5,
|
scale: 0.5,
|
||||||
duration: 200,
|
duration: this.animation ? 200 : 0,
|
||||||
easing: 'easeInBack',
|
easing: 'easeInBack',
|
||||||
complete: () => this.$destroy()
|
complete: () => {
|
||||||
|
this.$emit('closed');
|
||||||
|
this.destroyDom();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
focusUp() {
|
||||||
|
this.focus = this.focus == 0 ? 9 : this.focus < 5 ? (this.focus + 4) : (this.focus - 5);
|
||||||
|
},
|
||||||
|
|
||||||
|
focusDown() {
|
||||||
|
this.focus = this.focus == 9 ? 0 : this.focus >= 5 ? (this.focus - 4) : (this.focus + 5);
|
||||||
|
},
|
||||||
|
|
||||||
|
focusRight() {
|
||||||
|
this.focus = this.focus == 9 ? 0 : (this.focus + 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
focusLeft() {
|
||||||
|
this.focus = this.focus == 0 ? 9 : (this.focus - 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
choose() {
|
||||||
|
this.$refs.buttons.childNodes[this.focus].click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
|
||||||
|
|
||||||
$border-color = rgba(27, 31, 35, 0.15)
|
$border-color = rgba(27, 31, 35, 0.15)
|
||||||
|
|
||||||
root(isDark)
|
.mk-reaction-picker
|
||||||
position initial
|
position initial
|
||||||
|
|
||||||
> .backdrop
|
> .backdrop
|
||||||
|
@ -142,11 +222,11 @@ root(isDark)
|
||||||
z-index 10000
|
z-index 10000
|
||||||
width 100%
|
width 100%
|
||||||
height 100%
|
height 100%
|
||||||
background isDark ? rgba(#000, 0.4) : rgba(#000, 0.1)
|
background var(--modalBackdrop)
|
||||||
opacity 0
|
opacity 0
|
||||||
|
|
||||||
> .popover
|
> .popover
|
||||||
$bgcolor = isDark ? #2c303c : #fff
|
$bgcolor = var(--popupBg)
|
||||||
position absolute
|
position absolute
|
||||||
z-index 10001
|
z-index 10001
|
||||||
background $bgcolor
|
background $bgcolor
|
||||||
|
@ -199,14 +279,29 @@ root(isDark)
|
||||||
margin 0
|
margin 0
|
||||||
padding 8px 10px
|
padding 8px 10px
|
||||||
font-size 14px
|
font-size 14px
|
||||||
color isDark ? #d6dce2 : #586069
|
color var(--popupFg)
|
||||||
border-bottom solid 1px isDark ? #1c2023 : #e1e4e8
|
border-bottom solid 1px var(--faceDivider)
|
||||||
|
|
||||||
> div
|
> div
|
||||||
padding 4px
|
padding 4px
|
||||||
width 240px
|
width 240px
|
||||||
text-align center
|
text-align center
|
||||||
|
|
||||||
|
&.showFocus
|
||||||
|
> button:focus
|
||||||
|
z-index 1
|
||||||
|
|
||||||
|
&:after
|
||||||
|
content ""
|
||||||
|
pointer-events none
|
||||||
|
position absolute
|
||||||
|
top 0
|
||||||
|
right 0
|
||||||
|
bottom 0
|
||||||
|
left 0
|
||||||
|
border 2px solid var(--primaryAlpha03)
|
||||||
|
border-radius 4px
|
||||||
|
|
||||||
> button
|
> button
|
||||||
padding 0
|
padding 0
|
||||||
width 40px
|
width 40px
|
||||||
|
@ -215,16 +310,10 @@ root(isDark)
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
background isDark ? #252731 : #eee
|
background var(--reactionPickerButtonHoverBg)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
background $theme-color
|
background var(--primary)
|
||||||
box-shadow inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15)
|
box-shadow inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15)
|
||||||
|
|
||||||
.mk-reaction-picker[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.mk-reaction-picker:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -39,10 +39,9 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
root(isDark)
|
.mk-reactions-viewer
|
||||||
$borderColor = isDark ? #5e6673 : #eee
|
border-top dashed 1px var(--reactionViewerBorder)
|
||||||
border-top dashed 1px $borderColor
|
border-bottom dashed 1px var(--reactionViewerBorder)
|
||||||
border-bottom dashed 1px $borderColor
|
|
||||||
margin 4px 0
|
margin 4px 0
|
||||||
|
|
||||||
&:empty
|
&:empty
|
||||||
|
@ -60,12 +59,6 @@ root(isDark)
|
||||||
> span
|
> span
|
||||||
margin-left 4px
|
margin-left 4px
|
||||||
font-size 1.2em
|
font-size 1.2em
|
||||||
color isDark ? #d1d5dc : #444
|
color var(--text)
|
||||||
|
|
||||||
.mk-reactions-viewer[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.mk-reactions-viewer:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<form class="mk-signin" :class="{ signing }" @submit.prevent="onSubmit">
|
<form class="mk-signin" :class="{ signing }" @submit.prevent="onSubmit">
|
||||||
<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div>
|
<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div>
|
||||||
<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @input="onUsernameChange">
|
<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @input="onUsernameChange" styl="fill">
|
||||||
<span>%i18n:@username%</span>
|
<span>%i18n:@username%</span>
|
||||||
<span slot="prefix">@</span>
|
<span slot="prefix">@</span>
|
||||||
<span slot="suffix">@{{ host }}</span>
|
<span slot="suffix">@{{ host }}</span>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-input v-model="password" type="password" required>
|
<ui-input v-model="password" type="password" required styl="fill">
|
||||||
<span>%i18n:@password%</span>
|
<span>%i18n:@password%</span>
|
||||||
<span slot="prefix">%fa:lock%</span>
|
<span slot="prefix">%fa:lock%</span>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required/>
|
<ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required styl="fill"/>
|
||||||
<ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button>
|
<ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button>
|
||||||
<p style="margin: 8px 0;">%i18n:@or% <a :href="`${apiUrl}/signin/twitter`">%i18n:@signin-with-twitter%</a></p>
|
<p style="margin: 8px 0;">%i18n:@or% <a :href="`${apiUrl}/signin/twitter`">%i18n:@signin-with-twitter%</a></p>
|
||||||
</form>
|
</form>
|
||||||
|
@ -56,7 +56,7 @@ export default Vue.extend({
|
||||||
username: this.username,
|
username: this.username,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
token: this.user && this.user.twoFactorEnabled ? this.token : undefined
|
token: this.user && this.user.twoFactorEnabled ? this.token : undefined
|
||||||
}).then(() => {
|
}, true).then(() => {
|
||||||
location.reload();
|
location.reload();
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
alert('%i18n:@login-failed%');
|
alert('%i18n:@login-failed%');
|
||||||
|
@ -68,7 +68,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
|
||||||
|
|
||||||
.mk-signin
|
.mk-signin
|
||||||
color #555
|
color #555
|
||||||
|
@ -78,7 +78,7 @@ export default Vue.extend({
|
||||||
cursor wait !important
|
cursor wait !important
|
||||||
|
|
||||||
> .avatar
|
> .avatar
|
||||||
margin 16px auto 0 auto
|
margin 0 auto 0 auto
|
||||||
width 64px
|
width 64px
|
||||||
height 64px
|
height 64px
|
||||||
background #ddd
|
background #ddd
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()">
|
<form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()">
|
||||||
<template v-if="meta">
|
<template v-if="meta">
|
||||||
<ui-input v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required>
|
<ui-input v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required styl="fill">
|
||||||
<span>%i18n:@invitation-code%</span>
|
<span>%i18n:@invitation-code%</span>
|
||||||
<span slot="prefix">%fa:id-card-alt%</span>
|
<span slot="prefix">%fa:id-card-alt%</span>
|
||||||
<p slot="text" v-html="'%i18n:@invitation-info%'.replace('{}', meta.maintainer.url)"></p>
|
<p slot="text" v-html="'%i18n:@invitation-info%'.replace('{}', meta.maintainer.url)"></p>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername">
|
<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername" styl="fill">
|
||||||
<span>%i18n:@username%</span>
|
<span>%i18n:@username%</span>
|
||||||
<span slot="prefix">@</span>
|
<span slot="prefix">@</span>
|
||||||
<span slot="suffix">@{{ host }}</span>
|
<span slot="suffix">@{{ host }}</span>
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
<p slot="text" 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 slot="text" 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>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword" :with-password-meter="true">
|
<ui-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword" :with-password-meter="true" styl="fill">
|
||||||
<span>%i18n:@password%</span>
|
<span>%i18n:@password%</span>
|
||||||
<span slot="prefix">%fa:lock%</span>
|
<span slot="prefix">%fa:lock%</span>
|
||||||
<div slot="text">
|
<div slot="text">
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
<p slot="text" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw% %i18n:@strong-password%</p>
|
<p slot="text" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw% %i18n:@strong-password%</p>
|
||||||
</div>
|
</div>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype">
|
<ui-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype" styl="fill">
|
||||||
<span>%i18n:@password% (%i18n:@retype%)</span>
|
<span>%i18n:@password% (%i18n:@retype%)</span>
|
||||||
<span slot="prefix">%fa:lock%</span>
|
<span slot="prefix">%fa:lock%</span>
|
||||||
<div slot="text">
|
<div slot="text">
|
||||||
|
@ -131,11 +131,11 @@ export default Vue.extend({
|
||||||
password: this.password,
|
password: this.password,
|
||||||
invitationCode: this.invitationCode,
|
invitationCode: this.invitationCode,
|
||||||
'g-recaptcha-response': this.meta.recaptchaSitekey != null ? (window as any).grecaptcha.getResponse() : null
|
'g-recaptcha-response': this.meta.recaptchaSitekey != null ? (window as any).grecaptcha.getResponse() : null
|
||||||
}).then(() => {
|
}, true).then(() => {
|
||||||
(this as any).api('signin', {
|
(this as any).api('signin', {
|
||||||
username: this.username,
|
username: this.username,
|
||||||
password: this.password
|
password: this.password
|
||||||
}).then(() => {
|
}, true).then(() => {
|
||||||
location.href = '/';
|
location.href = '/';
|
||||||
});
|
});
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
|
@ -151,7 +151,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
|
||||||
|
|
||||||
.mk-signup
|
.mk-signup
|
||||||
min-width 302px
|
min-width 302px
|
||||||
|
|
|
@ -1,199 +0,0 @@
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="mk-switch"
|
|
||||||
:class="{ disabled, checked }"
|
|
||||||
role="switch"
|
|
||||||
:aria-checked="checked"
|
|
||||||
:aria-disabled="disabled"
|
|
||||||
@click="switchValue"
|
|
||||||
@mouseover="mouseenter"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
@change="handleChange"
|
|
||||||
ref="input"
|
|
||||||
:disabled="disabled"
|
|
||||||
@keydown.enter="switchValue"
|
|
||||||
>
|
|
||||||
<span class="button">
|
|
||||||
<span :style="{ transform }"></span>
|
|
||||||
</span>
|
|
||||||
<span class="label">
|
|
||||||
<span :aria-hidden="!checked">{{ text }}</span>
|
|
||||||
<p :aria-hidden="!checked">
|
|
||||||
<slot></slot>
|
|
||||||
</p>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Vue from 'vue';
|
|
||||||
export default Vue.extend({
|
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
text: String
|
|
||||||
},/*
|
|
||||||
created() {
|
|
||||||
if (!~[true, false].indexOf(this.value)) {
|
|
||||||
this.$emit('input', false);
|
|
||||||
}
|
|
||||||
},*/
|
|
||||||
computed: {
|
|
||||||
checked(): boolean {
|
|
||||||
return this.value;
|
|
||||||
},
|
|
||||||
transform(): string {
|
|
||||||
return this.checked ? 'translate3d(20px, 0, 0)' : '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
value() {
|
|
||||||
(this.$el).style.transition = 'all 0.3s';
|
|
||||||
(this.$refs.input as any).checked = this.checked;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
(this.$refs.input as any).checked = this.checked;
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
mouseenter() {
|
|
||||||
(this.$el).style.transition = 'all 0s';
|
|
||||||
},
|
|
||||||
handleChange() {
|
|
||||||
(this.$el).style.transition = 'all 0.3s';
|
|
||||||
this.$emit('input', !this.checked);
|
|
||||||
this.$emit('change', !this.checked);
|
|
||||||
this.$nextTick(() => {
|
|
||||||
// set input's checked property
|
|
||||||
// in case parent refuses to change component's value
|
|
||||||
(this.$refs.input as any).checked = this.checked;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
switchValue() {
|
|
||||||
!this.disabled && this.handleChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
@import '~const.styl'
|
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
display flex
|
|
||||||
margin 12px 0
|
|
||||||
cursor pointer
|
|
||||||
transition all 0.3s
|
|
||||||
|
|
||||||
> *
|
|
||||||
user-select none
|
|
||||||
|
|
||||||
&.disabled
|
|
||||||
opacity 0.6
|
|
||||||
cursor not-allowed
|
|
||||||
|
|
||||||
&.checked
|
|
||||||
> .button
|
|
||||||
background-color $theme-color
|
|
||||||
border-color $theme-color
|
|
||||||
|
|
||||||
> .label
|
|
||||||
> span
|
|
||||||
color $theme-color
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
> .label
|
|
||||||
> span
|
|
||||||
color darken($theme-color, 10%)
|
|
||||||
|
|
||||||
> .button
|
|
||||||
background darken($theme-color, 10%)
|
|
||||||
border-color darken($theme-color, 10%)
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
> .label
|
|
||||||
> span
|
|
||||||
color isDark ? #fff : #2e3338
|
|
||||||
|
|
||||||
> .button
|
|
||||||
$color = isDark ? #15181d : #ced2da
|
|
||||||
background $color
|
|
||||||
border-color $color
|
|
||||||
|
|
||||||
> input
|
|
||||||
position absolute
|
|
||||||
width 0
|
|
||||||
height 0
|
|
||||||
opacity 0
|
|
||||||
margin 0
|
|
||||||
|
|
||||||
&:focus + .button
|
|
||||||
&:after
|
|
||||||
content ""
|
|
||||||
pointer-events none
|
|
||||||
position absolute
|
|
||||||
top -5px
|
|
||||||
right -5px
|
|
||||||
bottom -5px
|
|
||||||
left -5px
|
|
||||||
border 2px solid rgba($theme-color, 0.3)
|
|
||||||
border-radius 14px
|
|
||||||
|
|
||||||
> .button
|
|
||||||
$color = isDark ? #1c1f25 : #dcdfe6
|
|
||||||
|
|
||||||
display inline-block
|
|
||||||
margin 0
|
|
||||||
width 40px
|
|
||||||
min-width 40px
|
|
||||||
height 20px
|
|
||||||
min-height 20px
|
|
||||||
background $color
|
|
||||||
border 1px solid $color
|
|
||||||
outline none
|
|
||||||
border-radius 10px
|
|
||||||
transition inherit
|
|
||||||
|
|
||||||
> *
|
|
||||||
position absolute
|
|
||||||
top 1px
|
|
||||||
left 1px
|
|
||||||
border-radius 100%
|
|
||||||
transition transform 0.3s
|
|
||||||
width 16px
|
|
||||||
height 16px
|
|
||||||
background-color #fff
|
|
||||||
|
|
||||||
> .label
|
|
||||||
margin-left 8px
|
|
||||||
display block
|
|
||||||
font-size 15px
|
|
||||||
cursor pointer
|
|
||||||
transition inherit
|
|
||||||
|
|
||||||
> span
|
|
||||||
display block
|
|
||||||
line-height 20px
|
|
||||||
color isDark ? #c4ccd2 : #4a535a
|
|
||||||
transition inherit
|
|
||||||
|
|
||||||
> p
|
|
||||||
margin 0
|
|
||||||
//font-size 90%
|
|
||||||
color isDark ? #78858e : #9daab3
|
|
||||||
|
|
||||||
.mk-switch[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.mk-switch:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
|
84
src/client/app/common/views/components/tag-cloud.vue
Normal file
84
src/client/app/common/views/components/tag-cloud.vue
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
<template>
|
||||||
|
<div class="jtivnzhfwquxpsfidertopbmwmchmnmo">
|
||||||
|
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
||||||
|
<p class="empty" v-else-if="tags.length == 0">%fa:exclamation-circle%%i18n:@empty%</p>
|
||||||
|
<div v-else>
|
||||||
|
<vue-word-cloud
|
||||||
|
:words="tags.slice(0, 20).map(x => [x.name, x.count])"
|
||||||
|
:color="color"
|
||||||
|
:spacing="1">
|
||||||
|
<template slot-scope="{word, text, weight}">
|
||||||
|
<div style="cursor: pointer;" :title="weight">
|
||||||
|
{{ text }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</vue-word-cloud>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import * as VueWordCloud from 'vuewordcloud';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
components: {
|
||||||
|
[VueWordCloud.name]: VueWordCloud
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tags: [],
|
||||||
|
fetching: true,
|
||||||
|
clock: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetch();
|
||||||
|
this.clock = setInterval(this.fetch, 1000 * 60);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.clock);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
(this as any).api('aggregation/hashtags').then(tags => {
|
||||||
|
this.tags = tags;
|
||||||
|
this.fetching = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
color([, weight]) {
|
||||||
|
const peak = Math.max.apply(null, this.tags.map(x => x.count));
|
||||||
|
const w = weight / peak;
|
||||||
|
|
||||||
|
if (w > 0.9) {
|
||||||
|
return this.$store.state.device.darkmode ? '#ff4e69' : '#ff4e69';
|
||||||
|
} else if (w > 0.5) {
|
||||||
|
return this.$store.state.device.darkmode ? '#3bc4c7' : '#3bc4c7';
|
||||||
|
} else {
|
||||||
|
return this.$store.state.device.darkmode ? '#fff' : '#555';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.jtivnzhfwquxpsfidertopbmwmchmnmo
|
||||||
|
height 100%
|
||||||
|
width 100%
|
||||||
|
|
||||||
|
> .fetching
|
||||||
|
> .empty
|
||||||
|
margin 0
|
||||||
|
padding 16px
|
||||||
|
text-align center
|
||||||
|
color #aaa
|
||||||
|
|
||||||
|
> [data-fa]
|
||||||
|
margin-right 4px
|
||||||
|
|
||||||
|
> div
|
||||||
|
height 100%
|
||||||
|
width 100%
|
||||||
|
|
||||||
|
</style>
|
308
src/client/app/common/views/components/theme.vue
Normal file
308
src/client/app/common/views/components/theme.vue
Normal file
|
@ -0,0 +1,308 @@
|
||||||
|
<template>
|
||||||
|
<div class="nicnklzforebnpfgasiypmpdaaglujqm">
|
||||||
|
<label>
|
||||||
|
<span>%i18n:@light-theme%</span>
|
||||||
|
<ui-select v-model="light" placeholder="%i18n:@light-theme%">
|
||||||
|
<optgroup label="%i18n:@light-themes%">
|
||||||
|
<option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="%i18n:@dark-themes%">
|
||||||
|
<option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||||
|
</optgroup>
|
||||||
|
</ui-select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<span>%i18n:@dark-theme%</span>
|
||||||
|
<ui-select v-model="dark" placeholder="%i18n:@dark-theme%">
|
||||||
|
<optgroup label="%i18n:@dark-themes%">
|
||||||
|
<option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="%i18n:@light-themes%">
|
||||||
|
<option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||||
|
</optgroup>
|
||||||
|
</ui-select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<details class="creator">
|
||||||
|
<summary>%fa:palette% %i18n:@create-a-theme%</summary>
|
||||||
|
<div>
|
||||||
|
<span>%i18n:@base-theme%:</span>
|
||||||
|
<ui-radio v-model="myThemeBase" value="light">%i18n:@base-theme-light%</ui-radio>
|
||||||
|
<ui-radio v-model="myThemeBase" value="dark">%i18n:@base-theme-dark%</ui-radio>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ui-input v-model="myThemeName">
|
||||||
|
<span>%i18n:@theme-name%</span>
|
||||||
|
</ui-input>
|
||||||
|
<ui-textarea v-model="myThemeDesc">
|
||||||
|
<span>%i18n:@desc%</span>
|
||||||
|
</ui-textarea>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style="padding-bottom:8px;">%i18n:@primary-color%:</div>
|
||||||
|
<color-picker v-model="myThemePrimary"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style="padding-bottom:8px;">%i18n:@secondary-color%:</div>
|
||||||
|
<color-picker v-model="myThemeSecondary"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style="padding-bottom:8px;">%i18n:@text-color%:</div>
|
||||||
|
<color-picker v-model="myThemeText"/>
|
||||||
|
</div>
|
||||||
|
<ui-button @click="preview()">%fa:eye% %i18n:@preview-created-theme%</ui-button>
|
||||||
|
<ui-button primary @click="gen()">%fa:save R% %i18n:@save-created-theme%</ui-button>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>%fa:download% %i18n:@install-a-theme%</summary>
|
||||||
|
<ui-button @click="import_()">%fa:file-import% %i18n:@import%</ui-button>
|
||||||
|
<input ref="file" type="file" accept=".misskeytheme" style="display:none;" @change="onUpdateImportFile"/>
|
||||||
|
<p>%i18n:@import-by-code%:</p>
|
||||||
|
<ui-textarea v-model="installThemeCode">
|
||||||
|
<span>%i18n:@theme-code%</span>
|
||||||
|
</ui-textarea>
|
||||||
|
<ui-button @click="() => install(this.installThemeCode)">%fa:check% %i18n:@install%</ui-button>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>%fa:folder-open% %i18n:@installed-themes%</summary>
|
||||||
|
<ui-select v-model="selectedInstalledThemeId" placeholder="%i18n:@select-theme%">
|
||||||
|
<option v-for="x in installedThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||||
|
</ui-select>
|
||||||
|
<template v-if="selectedInstalledTheme">
|
||||||
|
<ui-input readonly :value="selectedInstalledTheme.author">
|
||||||
|
<span>%i18n:@author%</span>
|
||||||
|
</ui-input>
|
||||||
|
<ui-textarea v-if="selectedInstalledTheme.desc" readonly :value="selectedInstalledTheme.desc">
|
||||||
|
<span>%i18n:@desc%</span>
|
||||||
|
</ui-textarea>
|
||||||
|
<ui-textarea readonly :value="selectedInstalledThemeCode">
|
||||||
|
<span>%i18n:@theme-code%</span>
|
||||||
|
</ui-textarea>
|
||||||
|
<ui-button @click="export_()" link :download="`${selectedInstalledTheme.name}.misskeytheme`" ref="export">%fa:box% %i18n:@export%</ui-button>
|
||||||
|
<ui-button @click="uninstall()">%fa:trash-alt R% %i18n:@uninstall%</ui-button>
|
||||||
|
</template>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { lightTheme, darkTheme, builtinThemes, applyTheme, Theme } from '../../../theme';
|
||||||
|
import { Chrome } from 'vue-color';
|
||||||
|
import * as uuid from 'uuid';
|
||||||
|
import * as tinycolor from 'tinycolor2';
|
||||||
|
import * as JSON5 from 'json5';
|
||||||
|
|
||||||
|
// 後方互換性のため
|
||||||
|
function convertOldThemedefinition(t) {
|
||||||
|
const t2 = {
|
||||||
|
id: t.meta.id,
|
||||||
|
name: t.meta.name,
|
||||||
|
author: t.meta.author,
|
||||||
|
base: t.meta.base,
|
||||||
|
vars: t.meta.vars,
|
||||||
|
props: t
|
||||||
|
};
|
||||||
|
delete t2.props.meta;
|
||||||
|
return t2;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
components: {
|
||||||
|
ColorPicker: Chrome
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
installThemeCode: null,
|
||||||
|
selectedInstalledThemeId: null,
|
||||||
|
myThemeBase: 'light',
|
||||||
|
myThemeName: '',
|
||||||
|
myThemeDesc: '',
|
||||||
|
myThemePrimary: lightTheme.vars.primary,
|
||||||
|
myThemeSecondary: lightTheme.vars.secondary,
|
||||||
|
myThemeText: lightTheme.vars.text
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
themes(): Theme[] {
|
||||||
|
return builtinThemes.concat(this.$store.state.device.themes);
|
||||||
|
},
|
||||||
|
|
||||||
|
darkThemes(): Theme[] {
|
||||||
|
return this.themes.filter(t => t.base == 'dark' || t.kind == 'dark');
|
||||||
|
},
|
||||||
|
|
||||||
|
lightThemes(): Theme[] {
|
||||||
|
return this.themes.filter(t => t.base == 'light' || t.kind == 'light');
|
||||||
|
},
|
||||||
|
|
||||||
|
installedThemes(): Theme[] {
|
||||||
|
return this.$store.state.device.themes;
|
||||||
|
},
|
||||||
|
|
||||||
|
light: {
|
||||||
|
get() { return this.$store.state.device.lightTheme; },
|
||||||
|
set(value) { this.$store.commit('device/set', { key: 'lightTheme', value }); }
|
||||||
|
},
|
||||||
|
|
||||||
|
dark: {
|
||||||
|
get() { return this.$store.state.device.darkTheme; },
|
||||||
|
set(value) { this.$store.commit('device/set', { key: 'darkTheme', value }); }
|
||||||
|
},
|
||||||
|
|
||||||
|
selectedInstalledTheme() {
|
||||||
|
if (this.selectedInstalledThemeId == null) return null;
|
||||||
|
return this.installedThemes.find(x => x.id == this.selectedInstalledThemeId);
|
||||||
|
},
|
||||||
|
|
||||||
|
selectedInstalledThemeCode() {
|
||||||
|
if (this.selectedInstalledTheme == null) return null;
|
||||||
|
return JSON5.stringify(this.selectedInstalledTheme, null, '\t');
|
||||||
|
},
|
||||||
|
|
||||||
|
myTheme(): any {
|
||||||
|
return {
|
||||||
|
name: this.myThemeName,
|
||||||
|
author: this.$store.state.i.username,
|
||||||
|
desc: this.myThemeDesc,
|
||||||
|
base: this.myThemeBase,
|
||||||
|
vars: {
|
||||||
|
primary: tinycolor(typeof this.myThemePrimary == 'string' ? this.myThemePrimary : this.myThemePrimary.rgba).toRgbString(),
|
||||||
|
secondary: tinycolor(typeof this.myThemeSecondary == 'string' ? this.myThemeSecondary : this.myThemeSecondary.rgba).toRgbString(),
|
||||||
|
text: tinycolor(typeof this.myThemeText == 'string' ? this.myThemeText : this.myThemeText.rgba).toRgbString()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
myThemeBase(v) {
|
||||||
|
const theme = v == 'light' ? lightTheme : darkTheme;
|
||||||
|
this.myThemePrimary = theme.vars.primary;
|
||||||
|
this.myThemeSecondary = theme.vars.secondary;
|
||||||
|
this.myThemeText = theme.vars.text;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeCreate() {
|
||||||
|
// migrate old theme definitions
|
||||||
|
// 後方互換性のため
|
||||||
|
this.$store.commit('device/set', {
|
||||||
|
key: 'themes', value: this.$store.state.device.themes.map(t => {
|
||||||
|
if (t.id == null) {
|
||||||
|
return convertOldThemedefinition(t);
|
||||||
|
} else {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
install(code) {
|
||||||
|
let theme;
|
||||||
|
|
||||||
|
try {
|
||||||
|
theme = JSON5.parse(code);
|
||||||
|
} catch (e) {
|
||||||
|
alert('%i18n:@invalid-theme%');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 後方互換性のため
|
||||||
|
if (theme.id == null && theme.meta != null) {
|
||||||
|
theme = convertOldThemedefinition(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theme.id == null) {
|
||||||
|
alert('%i18n:@invalid-theme%');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$store.state.device.themes.some(t => t.id == theme.id)) {
|
||||||
|
alert('%i18n:@already-installed%');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const themes = this.$store.state.device.themes.concat(theme);
|
||||||
|
this.$store.commit('device/set', {
|
||||||
|
key: 'themes', value: themes
|
||||||
|
});
|
||||||
|
|
||||||
|
alert('%i18n:@installed%'.replace('{}', theme.name));
|
||||||
|
},
|
||||||
|
|
||||||
|
uninstall() {
|
||||||
|
const theme = this.selectedInstalledTheme;
|
||||||
|
const themes = this.$store.state.device.themes.filter(t => t.id != theme.id);
|
||||||
|
this.$store.commit('device/set', {
|
||||||
|
key: 'themes', value: themes
|
||||||
|
});
|
||||||
|
alert('%i18n:@uninstalled%'.replace('{}', theme.name));
|
||||||
|
},
|
||||||
|
|
||||||
|
import_() {
|
||||||
|
(this.$refs.file as any).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export_() {
|
||||||
|
const blob = new Blob([this.selectedInstalledThemeCode], {
|
||||||
|
type: 'application/json5'
|
||||||
|
});
|
||||||
|
this.$refs.export.$el.href = window.URL.createObjectURL(blob);
|
||||||
|
},
|
||||||
|
|
||||||
|
onUpdateImportFile() {
|
||||||
|
const f = (this.$refs.file as any).files[0];
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = e => {
|
||||||
|
this.install(e.target.result);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(f);
|
||||||
|
},
|
||||||
|
|
||||||
|
preview() {
|
||||||
|
applyTheme(this.myTheme, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
gen() {
|
||||||
|
const theme = this.myTheme;
|
||||||
|
if (theme.name == null || theme.name.trim() == '') {
|
||||||
|
alert('%i18n:@theme-name-required%');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
theme.id = uuid();
|
||||||
|
const themes = this.$store.state.device.themes.concat(theme);
|
||||||
|
this.$store.commit('device/set', {
|
||||||
|
key: 'themes', value: themes
|
||||||
|
});
|
||||||
|
alert('%i18n:@saved%');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.nicnklzforebnpfgasiypmpdaaglujqm
|
||||||
|
> details
|
||||||
|
border-top solid 1px var(--faceDivider)
|
||||||
|
|
||||||
|
> summary
|
||||||
|
padding 16px 0
|
||||||
|
|
||||||
|
> *:last-child
|
||||||
|
margin-bottom 16px
|
||||||
|
|
||||||
|
> .creator
|
||||||
|
> div
|
||||||
|
padding 16px 0
|
||||||
|
border-bottom solid 1px var(--faceDivider)
|
||||||
|
</style>
|
98
src/client/app/common/views/components/trends.vue
Normal file
98
src/client/app/common/views/components/trends.vue
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
<template>
|
||||||
|
<div class="csqvmxybqbycalfhkxvyfrgbrdalkaoc">
|
||||||
|
<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/${ encodeURIComponent(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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import XChart from './trends.chart.vue';
|
||||||
|
|
||||||
|
export default Vue.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: {
|
||||||
|
fetch() {
|
||||||
|
(this as any).api('hashtags/trend').then(stats => {
|
||||||
|
this.stats = stats;
|
||||||
|
this.fetching = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.csqvmxybqbycalfhkxvyfrgbrdalkaoc
|
||||||
|
> .fetching
|
||||||
|
> .empty
|
||||||
|
margin 0
|
||||||
|
padding 16px
|
||||||
|
text-align center
|
||||||
|
color var(--text)
|
||||||
|
opacity 0.7
|
||||||
|
|
||||||
|
> [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 var(--faceDivider)
|
||||||
|
|
||||||
|
> .tag
|
||||||
|
flex 1
|
||||||
|
overflow hidden
|
||||||
|
font-size 14px
|
||||||
|
color var(--text)
|
||||||
|
|
||||||
|
> 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
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,9 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="ui-button" :class="[styl]">
|
<component class="dmtdnykelhudezerjlfpbhgovrgnqqgr" :is="link ? 'a' : 'button'" :class="[styl, { inline, primary }]" :type="type" @click="$emit('click')">
|
||||||
<button :type="type" @click="$emit('click')">
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</button>
|
</component>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -13,70 +11,100 @@ export default Vue.extend({
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false
|
required: false
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
inline: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
styl: 'fill'
|
styl: 'fill'
|
||||||
};
|
};
|
||||||
},
|
|
||||||
inject: {
|
|
||||||
isCardChild: { default: false }
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (this.isCardChild) {
|
|
||||||
this.styl = 'line';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.dmtdnykelhudezerjlfpbhgovrgnqqgr
|
||||||
|
|
||||||
root(isDark, fill)
|
|
||||||
> button
|
|
||||||
display block
|
display block
|
||||||
width 100%
|
width 100%
|
||||||
margin 0
|
margin 0
|
||||||
padding 0
|
padding 8px
|
||||||
font-weight bold
|
text-align center
|
||||||
|
font-weight normal
|
||||||
font-size 16px
|
font-size 16px
|
||||||
line-height 44px
|
|
||||||
border none
|
border none
|
||||||
border-radius 6px
|
border-radius 6px
|
||||||
outline none
|
outline none
|
||||||
box-shadow none
|
box-shadow none
|
||||||
|
text-decoration none
|
||||||
|
user-select none
|
||||||
|
|
||||||
if fill
|
*
|
||||||
color $theme-color-foreground
|
pointer-events none
|
||||||
background $theme-color
|
|
||||||
|
&:focus
|
||||||
|
&:after
|
||||||
|
content ""
|
||||||
|
pointer-events none
|
||||||
|
position absolute
|
||||||
|
top -5px
|
||||||
|
right -5px
|
||||||
|
bottom -5px
|
||||||
|
left -5px
|
||||||
|
border 2px solid var(--primaryAlpha03)
|
||||||
|
border-radius 10px
|
||||||
|
|
||||||
|
&:not(.inline) + .dmtdnykelhudezerjlfpbhgovrgnqqgr
|
||||||
|
margin-top 16px
|
||||||
|
|
||||||
|
&.inline
|
||||||
|
display inline-block
|
||||||
|
width auto
|
||||||
|
|
||||||
|
&.primary
|
||||||
|
font-weight bold
|
||||||
|
|
||||||
|
&.fill
|
||||||
|
color var(--text)
|
||||||
|
background var(--buttonBg)
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
background lighten($theme-color, 5%)
|
background var(--buttonHoverBg)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
background darken($theme-color, 5%)
|
background var(--buttonActiveBg)
|
||||||
else
|
|
||||||
color $theme-color
|
&.primary
|
||||||
|
color var(--primaryForeground)
|
||||||
|
background var(--primary)
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background var(--primaryLighten5)
|
||||||
|
|
||||||
|
&:active
|
||||||
|
background var(--primaryDarken5)
|
||||||
|
|
||||||
|
&:not(.fill)
|
||||||
|
color var(--primary)
|
||||||
background none
|
background none
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
color darken($theme-color, 5%)
|
color var(--primaryDarken5)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
background rgba($theme-color, 0.3)
|
background var(--primaryAlpha03)
|
||||||
|
|
||||||
.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>
|
</style>
|
||||||
|
|
|
@ -20,27 +20,33 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.ui-card
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
margin 16px
|
margin 16px
|
||||||
padding 16px
|
color var(--faceText)
|
||||||
color isDark ? #fff : #000
|
background var(--face)
|
||||||
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)
|
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)
|
||||||
|
|
||||||
|
> header
|
||||||
|
padding 16px
|
||||||
|
font-weight bold
|
||||||
|
font-size 20px
|
||||||
|
color var(--faceText)
|
||||||
|
|
||||||
|
@media (min-width 500px)
|
||||||
|
padding 24px 32px
|
||||||
|
|
||||||
|
> section
|
||||||
|
padding 20px 16px
|
||||||
|
border-top solid 1px var(--faceDivider)
|
||||||
|
|
||||||
@media (min-width 500px)
|
@media (min-width 500px)
|
||||||
padding 32px
|
padding 32px
|
||||||
|
|
||||||
|
&.fit-top
|
||||||
|
padding-top 0
|
||||||
|
|
||||||
> header
|
> header
|
||||||
font-weight normal
|
margin-bottom 16px
|
||||||
font-size 24px
|
font-weight bold
|
||||||
color isDark ? #fff : #444
|
color var(--faceText)
|
||||||
|
|
||||||
.ui-card[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.ui-card:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
|
||||||
|
|
||||||
.ui-form
|
.ui-form
|
||||||
> fieldset
|
> fieldset
|
||||||
|
|
|
@ -25,9 +25,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.nvemkhtwcnnpkdrwfcbzuwhfulejhmzg
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
display inline-block
|
display inline-block
|
||||||
|
|
||||||
& + .nvemkhtwcnnpkdrwfcbzuwhfulejhmzg
|
& + .nvemkhtwcnnpkdrwfcbzuwhfulejhmzg
|
||||||
|
@ -38,11 +36,11 @@ root(isDark)
|
||||||
margin 0
|
margin 0
|
||||||
padding 12px 20px
|
padding 12px 20px
|
||||||
font-size 14px
|
font-size 14px
|
||||||
border 1px solid isDark ? #6d727d : #dcdfe6
|
border 1px solid var(--formButtonBorder)
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
outline none
|
outline none
|
||||||
box-shadow none
|
box-shadow none
|
||||||
color isDark ? #fff : #606266
|
color var(--text)
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
|
|
||||||
*
|
*
|
||||||
|
@ -50,40 +48,34 @@ root(isDark)
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
&:focus
|
&:focus
|
||||||
color $theme-color
|
color var(--primary)
|
||||||
background rgba($theme-color, isDark ? 0.2 : 0.12)
|
background var(--formButtonHoverBg)
|
||||||
border-color rgba($theme-color, isDark ? 0.5 : 0.3)
|
border-color var(--formButtonHoverBorder)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
color darken($theme-color, 20%)
|
color var(--primaryDarken20)
|
||||||
background rgba($theme-color, 0.12)
|
background var(--formButtonActiveBg)
|
||||||
border-color $theme-color
|
border-color var(--primary)
|
||||||
transition all 0s
|
transition all 0s
|
||||||
|
|
||||||
&.primary
|
&.primary
|
||||||
> button
|
> button
|
||||||
border 1px solid $theme-color
|
border 1px solid var(--primary)
|
||||||
background $theme-color
|
background var(--primary)
|
||||||
color $theme-color-foreground
|
color var(--primaryForeground)
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
&:focus
|
&:focus
|
||||||
background lighten($theme-color, 20%)
|
background var(--primaryLighten20)
|
||||||
border-color lighten($theme-color, 20%)
|
border-color var(--primaryLighten20)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
background darken($theme-color, 20%)
|
background var(--primaryDarken20)
|
||||||
border-color darken($theme-color, 20%)
|
border-color var(--primaryDarken20)
|
||||||
transition all 0s
|
transition all 0s
|
||||||
|
|
||||||
&.round
|
&.round
|
||||||
> button
|
> button
|
||||||
border-radius 64px
|
border-radius 64px
|
||||||
|
|
||||||
.nvemkhtwcnnpkdrwfcbzuwhfulejhmzg[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.nvemkhtwcnnpkdrwfcbzuwhfulejhmzg:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -49,9 +49,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.uywduthvrdnlpsvsjkqigicixgyfctto
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
display inline-flex
|
display inline-flex
|
||||||
margin 0 16px 0 0
|
margin 0 16px 0 0
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
@ -62,7 +60,7 @@ root(isDark)
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
> .button
|
> .button
|
||||||
border solid 2px isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
|
border solid 2px var(--inputLabel)
|
||||||
|
|
||||||
&.disabled
|
&.disabled
|
||||||
opacity 0.6
|
opacity 0.6
|
||||||
|
@ -70,15 +68,15 @@ root(isDark)
|
||||||
|
|
||||||
&.checked
|
&.checked
|
||||||
> .button
|
> .button
|
||||||
border-color $theme-color
|
border-color var(--primary)
|
||||||
|
|
||||||
&:after
|
&:after
|
||||||
background-color $theme-color
|
background-color var(--primary)
|
||||||
transform scale(1)
|
transform scale(1)
|
||||||
opacity 1
|
opacity 1
|
||||||
|
|
||||||
> .label
|
> .label
|
||||||
color $theme-color
|
color var(--primary)
|
||||||
|
|
||||||
> input
|
> input
|
||||||
position absolute
|
position absolute
|
||||||
|
@ -93,7 +91,7 @@ root(isDark)
|
||||||
width 20px
|
width 20px
|
||||||
height 20px
|
height 20px
|
||||||
background none
|
background none
|
||||||
border solid 2px isDark ? rgba(#fff, 0.6) : rgba(#000, 0.4)
|
border solid 2px var(--radioBorder)
|
||||||
border-radius 100%
|
border-radius 100%
|
||||||
transition inherit
|
transition inherit
|
||||||
|
|
||||||
|
@ -117,10 +115,4 @@ root(isDark)
|
||||||
line-height 20px
|
line-height 20px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
|
||||||
.uywduthvrdnlpsvsjkqigicixgyfctto[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.uywduthvrdnlpsvsjkqigicixgyfctto:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -71,14 +71,18 @@ export default Vue.extend({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
styl: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'line'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
v: this.value,
|
v: this.value,
|
||||||
focused: false,
|
focused: false,
|
||||||
passwordStrength: '',
|
passwordStrength: ''
|
||||||
styl: 'fill'
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -117,14 +121,6 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inject: {
|
|
||||||
isCardChild: { default: false }
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (this.isCardChild) {
|
|
||||||
this.styl = 'line';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.$refs.prefix) {
|
if (this.$refs.prefix) {
|
||||||
this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
|
this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
|
||||||
|
@ -155,9 +151,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
root(fill)
|
||||||
|
|
||||||
root(isDark, fill)
|
|
||||||
margin 32px 0
|
margin 32px 0
|
||||||
|
|
||||||
> .icon
|
> .icon
|
||||||
|
@ -167,7 +161,7 @@ root(isDark, fill)
|
||||||
width 24px
|
width 24px
|
||||||
text-align center
|
text-align center
|
||||||
line-height 32px
|
line-height 32px
|
||||||
color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
|
color var(--inputLabel)
|
||||||
|
|
||||||
&:not(:empty) + .input
|
&:not(:empty) + .input
|
||||||
margin-left 28px
|
margin-left 28px
|
||||||
|
@ -183,7 +177,7 @@ root(isDark, fill)
|
||||||
left 0
|
left 0
|
||||||
right 0
|
right 0
|
||||||
height 1px
|
height 1px
|
||||||
background isDark ? rgba(#fff, 0.7) : rgba(#000, 0.42)
|
background var(--inputBorder)
|
||||||
|
|
||||||
&:after
|
&:after
|
||||||
content ''
|
content ''
|
||||||
|
@ -193,7 +187,7 @@ root(isDark, fill)
|
||||||
left 0
|
left 0
|
||||||
right 0
|
right 0
|
||||||
height 2px
|
height 2px
|
||||||
background $theme-color
|
background var(--primary)
|
||||||
opacity 0
|
opacity 0
|
||||||
transform scaleX(0.12)
|
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)
|
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)
|
||||||
|
@ -242,7 +236,7 @@ root(isDark, fill)
|
||||||
transition-duration 0.3s
|
transition-duration 0.3s
|
||||||
font-size 16px
|
font-size 16px
|
||||||
line-height 32px
|
line-height 32px
|
||||||
color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
|
color var(--inputLabel)
|
||||||
pointer-events none
|
pointer-events none
|
||||||
//will-change transform
|
//will-change transform
|
||||||
transform-origin top left
|
transform-origin top left
|
||||||
|
@ -257,7 +251,7 @@ root(isDark, fill)
|
||||||
font-weight fill ? bold : normal
|
font-weight fill ? bold : normal
|
||||||
font-size 16px
|
font-size 16px
|
||||||
line-height 32px
|
line-height 32px
|
||||||
color isDark ? #fff : #000
|
color var(--inputText)
|
||||||
background transparent
|
background transparent
|
||||||
border none
|
border none
|
||||||
border-radius 0
|
border-radius 0
|
||||||
|
@ -280,7 +274,7 @@ root(isDark, fill)
|
||||||
top 0
|
top 0
|
||||||
font-size 16px
|
font-size 16px
|
||||||
line-height fill ? 44px : 32px
|
line-height fill ? 44px : 32px
|
||||||
color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
|
color var(--inputLabel)
|
||||||
pointer-events none
|
pointer-events none
|
||||||
|
|
||||||
&:empty
|
&:empty
|
||||||
|
@ -325,7 +319,7 @@ root(isDark, fill)
|
||||||
transform scaleX(1)
|
transform scaleX(1)
|
||||||
|
|
||||||
> .label
|
> .label
|
||||||
color $theme-color
|
color var(--primary)
|
||||||
|
|
||||||
&.focused
|
&.focused
|
||||||
&.filled
|
&.filled
|
||||||
|
@ -335,16 +329,10 @@ root(isDark, fill)
|
||||||
left 0 !important
|
left 0 !important
|
||||||
transform scale(0.75)
|
transform scale(0.75)
|
||||||
|
|
||||||
.ui-input[data-darkmode]
|
.ui-input
|
||||||
&.fill
|
&.fill
|
||||||
root(true, true)
|
root(true)
|
||||||
&:not(.fill)
|
&:not(.fill)
|
||||||
root(true, false)
|
root(false)
|
||||||
|
|
||||||
.ui-input:not([data-darkmode])
|
|
||||||
&.fill
|
|
||||||
root(false, true)
|
|
||||||
&:not(.fill)
|
|
||||||
root(false, false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -51,11 +51,9 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.ui-radio
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
display inline-block
|
display inline-block
|
||||||
margin 32px 32px 32px 0
|
margin 0 32px 0 0
|
||||||
cursor pointer
|
cursor pointer
|
||||||
transition all 0.3s
|
transition all 0.3s
|
||||||
|
|
||||||
|
@ -68,10 +66,10 @@ root(isDark)
|
||||||
|
|
||||||
&.checked
|
&.checked
|
||||||
> .button
|
> .button
|
||||||
border-color $theme-color
|
border-color var(--primary)
|
||||||
|
|
||||||
&:after
|
&:after
|
||||||
background-color $theme-color
|
background-color var(--primary)
|
||||||
transform scale(1)
|
transform scale(1)
|
||||||
opacity 1
|
opacity 1
|
||||||
|
|
||||||
|
@ -87,7 +85,7 @@ root(isDark)
|
||||||
width 20px
|
width 20px
|
||||||
height 20px
|
height 20px
|
||||||
background none
|
background none
|
||||||
border solid 2px isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
|
border solid 2px var(--inputLabel)
|
||||||
border-radius 100%
|
border-radius 100%
|
||||||
transition inherit
|
transition inherit
|
||||||
|
|
||||||
|
@ -111,10 +109,4 @@ root(isDark)
|
||||||
line-height 20px
|
line-height 20px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
|
||||||
.ui-radio[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.ui-radio:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -29,13 +29,17 @@ export default Vue.extend({
|
||||||
required: {
|
required: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false
|
required: false
|
||||||
|
},
|
||||||
|
styl: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'line'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
v: this.value,
|
v: this.value,
|
||||||
focused: false,
|
focused: false
|
||||||
styl: 'fill'
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -48,14 +52,6 @@ export default Vue.extend({
|
||||||
this.v = v;
|
this.v = v;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inject: {
|
|
||||||
isCardChild: { default: false }
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (this.isCardChild) {
|
|
||||||
this.styl = 'line';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.$refs.prefix) {
|
if (this.$refs.prefix) {
|
||||||
this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
|
this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
|
||||||
|
@ -70,9 +66,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
root(fill)
|
||||||
|
|
||||||
root(isDark, fill)
|
|
||||||
margin 32px 0
|
margin 32px 0
|
||||||
|
|
||||||
> .icon
|
> .icon
|
||||||
|
@ -103,7 +97,7 @@ root(isDark, fill)
|
||||||
left 0
|
left 0
|
||||||
right 0
|
right 0
|
||||||
height 1px
|
height 1px
|
||||||
background isDark ? rgba(#fff, 0.7) : rgba(#000, 0.42)
|
background var(--inputBorder)
|
||||||
|
|
||||||
&:after
|
&:after
|
||||||
content ''
|
content ''
|
||||||
|
@ -113,7 +107,7 @@ root(isDark, fill)
|
||||||
left 0
|
left 0
|
||||||
right 0
|
right 0
|
||||||
height 2px
|
height 2px
|
||||||
background $theme-color
|
background var(--primary)
|
||||||
opacity 0
|
opacity 0
|
||||||
transform scaleX(0.12)
|
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)
|
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)
|
||||||
|
@ -143,7 +137,7 @@ root(isDark, fill)
|
||||||
font-weight fill ? bold : normal
|
font-weight fill ? bold : normal
|
||||||
font-size 16px
|
font-size 16px
|
||||||
height 32px
|
height 32px
|
||||||
color isDark ? #fff : #000
|
color var(--inputText)
|
||||||
background transparent
|
background transparent
|
||||||
border none
|
border none
|
||||||
border-radius 0
|
border-radius 0
|
||||||
|
@ -190,7 +184,7 @@ root(isDark, fill)
|
||||||
transform scaleX(1)
|
transform scaleX(1)
|
||||||
|
|
||||||
> .label
|
> .label
|
||||||
color $theme-color
|
color var(--primary)
|
||||||
|
|
||||||
&.focused
|
&.focused
|
||||||
&.filled
|
&.filled
|
||||||
|
@ -200,16 +194,10 @@ root(isDark, fill)
|
||||||
left 0 !important
|
left 0 !important
|
||||||
transform scale(0.75)
|
transform scale(0.75)
|
||||||
|
|
||||||
.ui-select[data-darkmode]
|
.ui-select
|
||||||
&.fill
|
&.fill
|
||||||
root(true, true)
|
root(true)
|
||||||
&:not(.fill)
|
&:not(.fill)
|
||||||
root(true, false)
|
root(false)
|
||||||
|
|
||||||
.ui-select:not([data-darkmode])
|
|
||||||
&.fill
|
|
||||||
root(false, true)
|
|
||||||
&:not(.fill)
|
|
||||||
root(false, false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<span class="label">
|
<span class="label">
|
||||||
<span :aria-hidden="!checked"><slot></slot></span>
|
<span :aria-hidden="!checked"><slot></slot></span>
|
||||||
<p :aria-hidden="!checked">
|
<p :aria-hidden="!checked">
|
||||||
<slot name="text"></slot>
|
<slot name="desc"></slot>
|
||||||
</p>
|
</p>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,14 +56,18 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
.ui-switch
|
||||||
|
|
||||||
root(isDark)
|
|
||||||
display flex
|
display flex
|
||||||
margin 32px 0
|
margin 32px 0
|
||||||
cursor pointer
|
cursor pointer
|
||||||
transition all 0.3s
|
transition all 0.3s
|
||||||
|
|
||||||
|
&:first-child
|
||||||
|
margin-top 0
|
||||||
|
|
||||||
|
&:last-child
|
||||||
|
margin-bottom 0
|
||||||
|
|
||||||
> *
|
> *
|
||||||
user-select none
|
user-select none
|
||||||
|
|
||||||
|
@ -73,11 +77,11 @@ root(isDark)
|
||||||
|
|
||||||
&.checked
|
&.checked
|
||||||
> .button
|
> .button
|
||||||
background-color rgba($theme-color, 0.4)
|
background-color var(--primaryAlpha04)
|
||||||
border-color rgba($theme-color, 0.4)
|
border-color var(--primaryAlpha04)
|
||||||
|
|
||||||
> *
|
> *
|
||||||
background-color $theme-color
|
background-color var(--primary)
|
||||||
transform translateX(14px)
|
transform translateX(14px)
|
||||||
|
|
||||||
> input
|
> input
|
||||||
|
@ -89,10 +93,11 @@ root(isDark)
|
||||||
|
|
||||||
> .button
|
> .button
|
||||||
display inline-block
|
display inline-block
|
||||||
|
flex-shrink 0
|
||||||
margin 3px 0 0 0
|
margin 3px 0 0 0
|
||||||
width 34px
|
width 34px
|
||||||
height 14px
|
height 14px
|
||||||
background isDark ? rgba(#fff, 0.15) : rgba(#000, 0.25)
|
background var(--switchTrack)
|
||||||
outline none
|
outline none
|
||||||
border-radius 14px
|
border-radius 14px
|
||||||
transition inherit
|
transition inherit
|
||||||
|
@ -118,18 +123,11 @@ root(isDark)
|
||||||
> span
|
> span
|
||||||
display block
|
display block
|
||||||
line-height 20px
|
line-height 20px
|
||||||
color isDark ? #c4ccd2 : rgba(#000, 0.75)
|
color currentColor
|
||||||
transition inherit
|
transition inherit
|
||||||
|
|
||||||
> p
|
> p
|
||||||
margin 0
|
margin 0
|
||||||
//font-size 90%
|
opacity 0.7
|
||||||
color isDark ? #78858e : #9daab3
|
|
||||||
|
|
||||||
.ui-switch[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.ui-switch:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -63,9 +63,7 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
root(fill)
|
||||||
|
|
||||||
root(isDark, fill)
|
|
||||||
margin 42px 0 32px 0
|
margin 42px 0 32px 0
|
||||||
|
|
||||||
> .input
|
> .input
|
||||||
|
@ -84,7 +82,7 @@ root(isDark, fill)
|
||||||
left 0
|
left 0
|
||||||
right 0
|
right 0
|
||||||
background none
|
background none
|
||||||
border solid 1px isDark ? rgba(#fff, 0.7) : rgba(#000, 0.42)
|
border solid 1px var(--inputBorder)
|
||||||
border-radius 3px
|
border-radius 3px
|
||||||
pointer-events none
|
pointer-events none
|
||||||
|
|
||||||
|
@ -97,7 +95,7 @@ root(isDark, fill)
|
||||||
left 0
|
left 0
|
||||||
right 0
|
right 0
|
||||||
background none
|
background none
|
||||||
border solid 2px $theme-color
|
border solid 2px var(--primary)
|
||||||
border-radius 3px
|
border-radius 3px
|
||||||
opacity 0
|
opacity 0
|
||||||
transition opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1)
|
transition opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1)
|
||||||
|
@ -112,7 +110,7 @@ root(isDark, fill)
|
||||||
transition-duration 0.3s
|
transition-duration 0.3s
|
||||||
font-size 16px
|
font-size 16px
|
||||||
line-height 32px
|
line-height 32px
|
||||||
color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
|
color var(--inputLabel)
|
||||||
pointer-events none
|
pointer-events none
|
||||||
//will-change transform
|
//will-change transform
|
||||||
transform-origin top left
|
transform-origin top left
|
||||||
|
@ -126,7 +124,7 @@ root(isDark, fill)
|
||||||
font inherit
|
font inherit
|
||||||
font-weight fill ? bold : normal
|
font-weight fill ? bold : normal
|
||||||
font-size 16px
|
font-size 16px
|
||||||
color isDark ? #fff : #000
|
color var(--inputText)
|
||||||
background transparent
|
background transparent
|
||||||
border none
|
border none
|
||||||
border-radius 0
|
border-radius 0
|
||||||
|
@ -149,7 +147,7 @@ root(isDark, fill)
|
||||||
opacity 1
|
opacity 1
|
||||||
|
|
||||||
> .label
|
> .label
|
||||||
color $theme-color
|
color var(--primary)
|
||||||
|
|
||||||
&.focused
|
&.focused
|
||||||
&.filled
|
&.filled
|
||||||
|
@ -159,16 +157,10 @@ root(isDark, fill)
|
||||||
left 0 !important
|
left 0 !important
|
||||||
transform scale(0.75)
|
transform scale(0.75)
|
||||||
|
|
||||||
.ui-textarea[data-darkmode]
|
.ui-textarea.fill
|
||||||
&.fill
|
root(true)
|
||||||
root(true, true)
|
|
||||||
&:not(.fill)
|
|
||||||
root(true, false)
|
|
||||||
|
|
||||||
.ui-textarea:not([data-darkmode])
|
.ui-textarea:not(.fill)
|
||||||
&.fill
|
root(false)
|
||||||
root(false, true)
|
|
||||||
&:not(.fill)
|
|
||||||
root(false, false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { apiUrl } from '../../../config';
|
import { apiUrl } from '../../../config';
|
||||||
|
import getMD5 from '../../scripts/get-md5';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
data() {
|
data() {
|
||||||
|
@ -28,27 +29,48 @@ export default Vue.extend({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
upload(file, folder) {
|
checkExistence(fileData: ArrayBuffer): Promise<any> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const data = new FormData();
|
||||||
|
data.append('md5', getMD5(fileData));
|
||||||
|
|
||||||
|
(this as any).api('drive/files/check_existence', {
|
||||||
|
md5: getMD5(fileData)
|
||||||
|
}).then(resp => {
|
||||||
|
resolve(resp.file);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
upload(file: File, folder: any) {
|
||||||
if (folder && typeof folder == 'object') folder = folder.id;
|
if (folder && typeof folder == 'object') folder = folder.id;
|
||||||
|
|
||||||
const id = Math.random();
|
const id = Math.random();
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e: any) => {
|
||||||
|
this.checkExistence(e.target.result).then(result => {
|
||||||
|
if (result !== null) {
|
||||||
|
this.$emit('uploaded', result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload if the file didn't exist yet
|
||||||
|
const buf = new Uint8Array(e.target.result);
|
||||||
|
let bin = '';
|
||||||
|
// We use for-of loop instead of apply() to avoid RangeError
|
||||||
|
// SEE: https://stackoverflow.com/questions/9267899/arraybuffer-to-base64-encoded-string
|
||||||
|
for (const byte of buf) bin += String.fromCharCode(byte);
|
||||||
const ctx = {
|
const ctx = {
|
||||||
id: id,
|
id: id,
|
||||||
name: file.name || 'untitled',
|
name: file.name || 'untitled',
|
||||||
progress: undefined,
|
progress: undefined,
|
||||||
img: undefined
|
img: 'data:*/*;base64,' + btoa(bin)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.uploads.push(ctx);
|
this.uploads.push(ctx);
|
||||||
this.$emit('change', this.uploads);
|
this.$emit('change', this.uploads);
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e: any) => {
|
|
||||||
ctx.img = e.target.result;
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
|
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
data.append('i', this.$store.state.i.token);
|
data.append('i', this.$store.state.i.token);
|
||||||
data.append('file', file);
|
data.append('file', file);
|
||||||
|
@ -75,14 +97,15 @@ export default Vue.extend({
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.send(data);
|
xhr.send(data);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
|
||||||
|
|
||||||
.mk-uploader
|
.mk-uploader
|
||||||
overflow auto
|
overflow auto
|
||||||
|
|
||||||
|
@ -100,7 +123,7 @@ export default Vue.extend({
|
||||||
margin 8px 0 0 0
|
margin 8px 0 0 0
|
||||||
padding 0
|
padding 0
|
||||||
height 36px
|
height 36px
|
||||||
box-shadow 0 -1px 0 rgba($theme-color, 0.1)
|
box-shadow 0 -1px 0 var(--primaryAlpha01)
|
||||||
border-top solid 8px transparent
|
border-top solid 8px transparent
|
||||||
|
|
||||||
&:first-child
|
&:first-child
|
||||||
|
@ -127,7 +150,7 @@ export default Vue.extend({
|
||||||
padding 0
|
padding 0
|
||||||
max-width 256px
|
max-width 256px
|
||||||
font-size 0.8em
|
font-size 0.8em
|
||||||
color rgba($theme-color, 0.7)
|
color var(--primaryAlpha07)
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
text-overflow ellipsis
|
text-overflow ellipsis
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
@ -145,17 +168,17 @@ export default Vue.extend({
|
||||||
font-size 0.8em
|
font-size 0.8em
|
||||||
|
|
||||||
> .initing
|
> .initing
|
||||||
color rgba($theme-color, 0.5)
|
color var(--primaryAlpha05)
|
||||||
|
|
||||||
> .kb
|
> .kb
|
||||||
color rgba($theme-color, 0.5)
|
color var(--primaryAlpha05)
|
||||||
|
|
||||||
> .percentage
|
> .percentage
|
||||||
display inline-block
|
display inline-block
|
||||||
width 48px
|
width 48px
|
||||||
text-align right
|
text-align right
|
||||||
|
|
||||||
color rgba($theme-color, 0.7)
|
color var(--primaryAlpha07)
|
||||||
|
|
||||||
&:after
|
&:after
|
||||||
content '%'
|
content '%'
|
||||||
|
@ -174,10 +197,10 @@ export default Vue.extend({
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
|
||||||
&::-webkit-progress-value
|
&::-webkit-progress-value
|
||||||
background $theme-color
|
background var(--primary)
|
||||||
|
|
||||||
&::-webkit-progress-bar
|
&::-webkit-progress-bar
|
||||||
background rgba($theme-color, 0.1)
|
background var(--primaryAlpha01)
|
||||||
|
|
||||||
> .progress
|
> .progress
|
||||||
display block
|
display block
|
||||||
|
@ -191,13 +214,13 @@ export default Vue.extend({
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
background linear-gradient(
|
background linear-gradient(
|
||||||
45deg,
|
45deg,
|
||||||
lighten($theme-color, 30%) 25%,
|
var(--primaryLighten30) 25%,
|
||||||
$theme-color 25%,
|
var(--primary) 25%,
|
||||||
$theme-color 50%,
|
var(--primary) 50%,
|
||||||
lighten($theme-color, 30%) 50%,
|
var(--primaryLighten30) 50%,
|
||||||
lighten($theme-color, 30%) 75%,
|
var(--primaryLighten30) 75%,
|
||||||
$theme-color 75%,
|
var(--primary) 75%,
|
||||||
$theme-color
|
var(--primary)
|
||||||
)
|
)
|
||||||
background-size 32px 32px
|
background-size 32px 32px
|
||||||
animation bg 1.5s linear infinite
|
animation bg 1.5s linear infinite
|
||||||
|
|
|
@ -8,13 +8,13 @@
|
||||||
</blockquote>
|
</blockquote>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="mk-url-preview">
|
<div v-else class="mk-url-preview">
|
||||||
<a :href="url" target="_blank" :title="url" v-if="!fetching">
|
<a :class="{ mini }" :href="url" target="_blank" :title="url" v-if="!fetching">
|
||||||
<div class="thumbnail" v-if="thumbnail" :style="`background-image: url(${thumbnail})`"></div>
|
<div class="thumbnail" v-if="thumbnail" :style="`background-image: url(${thumbnail})`"></div>
|
||||||
<article>
|
<article>
|
||||||
<header>
|
<header>
|
||||||
<h1>{{ title }}</h1>
|
<h1>{{ title }}</h1>
|
||||||
</header>
|
</header>
|
||||||
<p>{{ description }}</p>
|
<p>{{ description.length > 85 ? description.slice(0, 85) + '…' : description }}</p>
|
||||||
<footer>
|
<footer>
|
||||||
<img class="icon" v-if="icon" :src="icon"/>
|
<img class="icon" v-if="icon" :src="icon"/>
|
||||||
<p>{{ sitename }}</p>
|
<p>{{ sitename }}</p>
|
||||||
|
@ -118,6 +118,12 @@ export default Vue.extend({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
|
mini: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -164,7 +170,7 @@ export default Vue.extend({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch('/url?url=' + encodeURIComponent(this.url)).then(res => {
|
fetch(`/url?url=${encodeURIComponent(this.url)}`).then(res => {
|
||||||
res.json().then(info => {
|
res.json().then(info => {
|
||||||
if (info.url == null) return;
|
if (info.url == null) return;
|
||||||
this.title = info.title;
|
this.title = info.title;
|
||||||
|
@ -194,17 +200,17 @@ export default Vue.extend({
|
||||||
top 0
|
top 0
|
||||||
width 100%
|
width 100%
|
||||||
|
|
||||||
root(isDark)
|
.mk-url-preview
|
||||||
> a
|
> a
|
||||||
display block
|
display block
|
||||||
font-size 14px
|
font-size 14px
|
||||||
border solid 1px isDark ? #191b1f : #eee
|
border solid 1px var(--urlPreviewBorder)
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
text-decoration none
|
text-decoration none
|
||||||
border-color isDark ? #4f5561 : #ddd
|
border-color var(--urlPreviewBorderHover)
|
||||||
|
|
||||||
> article > header > h1
|
> article > header > h1
|
||||||
text-decoration underline
|
text-decoration underline
|
||||||
|
@ -229,11 +235,11 @@ root(isDark)
|
||||||
> h1
|
> h1
|
||||||
margin 0
|
margin 0
|
||||||
font-size 1em
|
font-size 1em
|
||||||
color isDark ? #d6dae0 : #555
|
color var(--urlPreviewTitle)
|
||||||
|
|
||||||
> p
|
> p
|
||||||
margin 0
|
margin 0
|
||||||
color isDark ? #a4aab3 : #777
|
color var(--urlPreviewText)
|
||||||
font-size 0.8em
|
font-size 0.8em
|
||||||
|
|
||||||
> footer
|
> footer
|
||||||
|
@ -250,7 +256,7 @@ root(isDark)
|
||||||
> p
|
> p
|
||||||
display inline-block
|
display inline-block
|
||||||
margin 0
|
margin 0
|
||||||
color isDark ? #b0b4bf : #666
|
color var(--urlPreviewInfo)
|
||||||
font-size 0.8em
|
font-size 0.8em
|
||||||
line-height 16px
|
line-height 16px
|
||||||
vertical-align top
|
vertical-align top
|
||||||
|
@ -293,10 +299,27 @@ root(isDark)
|
||||||
width 12px
|
width 12px
|
||||||
height 12px
|
height 12px
|
||||||
|
|
||||||
.mk-url-preview[data-darkmode]
|
&.mini
|
||||||
root(true)
|
font-size 10px
|
||||||
|
|
||||||
.mk-url-preview:not([data-darkmode])
|
> .thumbnail
|
||||||
root(false)
|
position relative
|
||||||
|
width 100%
|
||||||
|
height 60px
|
||||||
|
|
||||||
|
> article
|
||||||
|
left 0
|
||||||
|
width 100%
|
||||||
|
padding 8px
|
||||||
|
|
||||||
|
> header
|
||||||
|
margin-bottom 4px
|
||||||
|
|
||||||
|
> footer
|
||||||
|
margin-top 4px
|
||||||
|
|
||||||
|
> img
|
||||||
|
width 12px
|
||||||
|
height 12px
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import { toUnicode as decodePunycode } from 'punycode';
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['url', 'target'],
|
props: ['url', 'target'],
|
||||||
data() {
|
data() {
|
||||||
|
@ -27,11 +28,11 @@ export default Vue.extend({
|
||||||
created() {
|
created() {
|
||||||
const url = new URL(this.url);
|
const url = new URL(this.url);
|
||||||
this.schema = url.protocol;
|
this.schema = url.protocol;
|
||||||
this.hostname = url.hostname;
|
this.hostname = decodePunycode(url.hostname);
|
||||||
this.port = url.port;
|
this.port = url.port;
|
||||||
this.pathname = url.pathname;
|
this.pathname = decodeURIComponent(url.pathname);
|
||||||
this.query = url.search;
|
this.query = decodeURIComponent(url.search);
|
||||||
this.hash = url.hash;
|
this.hash = decodeURIComponent(url.hash);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default Vue.extend({
|
||||||
props: ['source', 'compact'],
|
props: ['source', 'compact'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
v: this.$store.state.device.visibility || 'public'
|
v: this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -97,9 +97,11 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
choose(visibility) {
|
choose(visibility) {
|
||||||
|
if (this.$store.state.settings.rememberNoteVisibility) {
|
||||||
this.$store.commit('device/setVisibility', visibility);
|
this.$store.commit('device/setVisibility', visibility);
|
||||||
|
}
|
||||||
this.$emit('chosen', visibility);
|
this.$emit('chosen', visibility);
|
||||||
this.$destroy();
|
this.destroyDom();
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
(this.$refs.backdrop as any).style.pointerEvents = 'none';
|
(this.$refs.backdrop as any).style.pointerEvents = 'none';
|
||||||
|
@ -117,7 +119,7 @@ export default Vue.extend({
|
||||||
scale: 0.5,
|
scale: 0.5,
|
||||||
duration: 200,
|
duration: 200,
|
||||||
easing: 'easeInBack',
|
easing: 'easeInBack',
|
||||||
complete: () => this.$destroy()
|
complete: () => this.destroyDom()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,11 +127,9 @@ export default Vue.extend({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@import '~const.styl'
|
|
||||||
|
|
||||||
$border-color = rgba(27, 31, 35, 0.15)
|
$border-color = rgba(27, 31, 35, 0.15)
|
||||||
|
|
||||||
root(isDark)
|
.mk-visibility-chooser
|
||||||
position initial
|
position initial
|
||||||
|
|
||||||
> .backdrop
|
> .backdrop
|
||||||
|
@ -139,11 +139,11 @@ root(isDark)
|
||||||
z-index 10000
|
z-index 10000
|
||||||
width 100%
|
width 100%
|
||||||
height 100%
|
height 100%
|
||||||
background isDark ? rgba(#000, 0.4) : rgba(#000, 0.1)
|
background var(--modalBackdrop)
|
||||||
opacity 0
|
opacity 0
|
||||||
|
|
||||||
> .popover
|
> .popover
|
||||||
$bgcolor = isDark ? #2c303c : #fff
|
$bgcolor = var(--popupBg)
|
||||||
position absolute
|
position absolute
|
||||||
z-index 10001
|
z-index 10001
|
||||||
width 240px
|
width 240px
|
||||||
|
@ -187,18 +187,18 @@ root(isDark)
|
||||||
display flex
|
display flex
|
||||||
padding 8px 14px
|
padding 8px 14px
|
||||||
font-size 12px
|
font-size 12px
|
||||||
color isDark ? #fff : #666
|
color var(--popupFg)
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
background isDark ? #252731 : #eee
|
background var(--faceClearButtonHover)
|
||||||
|
|
||||||
&:active
|
&:active
|
||||||
background isDark ? #21242b : #ddd
|
background var(--faceClearButtonActive)
|
||||||
|
|
||||||
&.active
|
&.active
|
||||||
color $theme-color-foreground
|
color var(--primaryForeground)
|
||||||
background $theme-color
|
background var(--primary)
|
||||||
|
|
||||||
> *
|
> *
|
||||||
user-select none
|
user-select none
|
||||||
|
@ -220,11 +220,4 @@ root(isDark)
|
||||||
|
|
||||||
> span:last-child:not(:first-child)
|
> span:last-child:not(:first-child)
|
||||||
opacity 0.6
|
opacity 0.6
|
||||||
|
|
||||||
.mk-visibility-chooser[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.mk-visibility-chooser:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-welcome-timeline">
|
<div class="mk-welcome-timeline">
|
||||||
<div v-for="note in notes">
|
<transition-group name="ldzpakcixzickvggyixyrhqwjaefknon" tag="div">
|
||||||
|
<div v-for="note in notes" :key="note.id">
|
||||||
<mk-avatar class="avatar" :user="note.user" target="_blank"/>
|
<mk-avatar class="avatar" :user="note.user" target="_blank"/>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<header>
|
<header>
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -31,15 +33,27 @@ export default Vue.extend({
|
||||||
default: undefined
|
default: undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
fetching: true,
|
fetching: true,
|
||||||
notes: []
|
notes: [],
|
||||||
|
connection: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.fetch();
|
this.fetch();
|
||||||
|
|
||||||
|
this.connection = (this as any).os.stream.useSharedConnection('localTimeline');
|
||||||
|
|
||||||
|
this.connection.on('note', this.onNote);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.connection.dispose();
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
fetch(cb?) {
|
fetch(cb?) {
|
||||||
this.fetching = true;
|
this.fetching = true;
|
||||||
|
@ -48,28 +62,44 @@ export default Vue.extend({
|
||||||
local: true,
|
local: true,
|
||||||
reply: false,
|
reply: false,
|
||||||
renote: false,
|
renote: false,
|
||||||
media: false,
|
file: false,
|
||||||
poll: false,
|
poll: false
|
||||||
bot: false
|
|
||||||
}).then(notes => {
|
}).then(notes => {
|
||||||
this.notes = notes;
|
this.notes = notes;
|
||||||
this.fetching = false;
|
this.fetching = false;
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
|
onNote(note) {
|
||||||
|
if (note.replyId != null) return;
|
||||||
|
if (note.renoteId != null) return;
|
||||||
|
if (note.poll != null) return;
|
||||||
|
|
||||||
|
this.notes.unshift(note);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
root(isDark)
|
.ldzpakcixzickvggyixyrhqwjaefknon-enter
|
||||||
background isDark ? #282C37 : #fff
|
.ldzpakcixzickvggyixyrhqwjaefknon-leave-to
|
||||||
|
opacity 0
|
||||||
|
transform translateY(-30px)
|
||||||
|
|
||||||
|
.mk-welcome-timeline
|
||||||
|
background var(--face)
|
||||||
|
|
||||||
|
> div
|
||||||
|
> *
|
||||||
|
transition transform .3s ease, opacity .3s ease
|
||||||
|
|
||||||
> div
|
> div
|
||||||
padding 16px
|
padding 16px
|
||||||
overflow-wrap break-word
|
overflow-wrap break-word
|
||||||
font-size .9em
|
font-size .9em
|
||||||
color isDark ? #fff : #4C4C4C
|
color var(--noteText)
|
||||||
border-bottom 1px solid isDark ? rgba(#000, 0.1) : rgba(#000, 0.05)
|
border-bottom 1px solid var(--faceDivider)
|
||||||
|
|
||||||
&:after
|
&:after
|
||||||
content ""
|
content ""
|
||||||
|
@ -104,26 +134,20 @@ root(isDark)
|
||||||
overflow hidden
|
overflow hidden
|
||||||
font-weight bold
|
font-weight bold
|
||||||
text-overflow ellipsis
|
text-overflow ellipsis
|
||||||
color isDark ? #fff : #627079
|
color var(--noteHeaderName)
|
||||||
|
|
||||||
> .username
|
> .username
|
||||||
margin 0 .5em 0 0
|
margin 0 .5em 0 0
|
||||||
color isDark ? #606984 : #ccc
|
color var(--noteHeaderAcct)
|
||||||
|
|
||||||
> .info
|
> .info
|
||||||
margin-left auto
|
margin-left auto
|
||||||
font-size 0.9em
|
font-size 0.9em
|
||||||
|
|
||||||
> .created-at
|
> .created-at
|
||||||
color isDark ? #606984 : #c0c0c0
|
color var(--noteHeaderInfo)
|
||||||
|
|
||||||
> .text
|
> .text
|
||||||
text-align left
|
text-align left
|
||||||
|
|
||||||
.mk-welcome-timeline[data-darkmode]
|
|
||||||
root(true)
|
|
||||||
|
|
||||||
.mk-welcome-timeline:not([data-darkmode])
|
|
||||||
root(false)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue