Compare commits

...

90 Commits

Author SHA1 Message Date
syuilo
f632ec50c1 10.86.2 2019-02-17 03:29:17 +09:00
syuilo
a55d15214b Update ja-JP.yml 2019-02-17 03:28:36 +09:00
syuilo
f1709a2cc2 Update CHANGELOG.md 2019-02-17 03:23:43 +09:00
syuilo
effa542958 Improve user page 2019-02-17 03:23:34 +09:00
MeiMei
e8bf742c87 別タブでルートより下を開いたときにはデッキにしないように (#4291) 2019-02-17 02:26:17 +09:00
syuilo
2e6652edce Resolve #4272 2019-02-17 01:50:17 +09:00
syuilo
230c204b48 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-02-17 01:12:08 +09:00
syuilo
3755c600b1 Update CHANGELOG.md 2019-02-17 01:11:59 +09:00
Acid Chicken (硫酸鶏)
24513fc0a3 Update media-banner.vue (#4287) 2019-02-17 01:09:49 +09:00
syuilo
0a79a6564a Add support for disabled timeline to deck
Close #4286
Resolve #4275
2019-02-17 01:04:21 +09:00
syuilo
562bb5842b [Client] Improve featured notes page 2019-02-17 00:28:41 +09:00
syuilo
ec3ca3032e ミュートワードで正規表現を使えるように 2019-02-16 19:37:05 +09:00
syuilo
890770c275 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-02-16 17:42:01 +09:00
syuilo
9ed58a1b4e 🎨 2019-02-16 17:41:54 +09:00
Acid Chicken (硫酸鶏)
08984be2fe Update config.yml 2019-02-16 17:05:56 +09:00
syuilo
e3ade148ca 10.86.1 2019-02-16 16:59:56 +09:00
syuilo
34c0eff89f 🎨 2019-02-16 16:46:06 +09:00
syuilo
40aba47a47 Fix bug 2019-02-16 16:43:17 +09:00
syuilo
6736f51134 Use home icon 2019-02-16 16:40:17 +09:00
syuilo
9d826d6e52 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-02-16 16:39:42 +09:00
syuilo
902d9bc7a5 Fix bug 2019-02-16 16:39:34 +09:00
MeiMei
b6c86e2845 Change home button to timeline (#4282)
* Home to Timeline

* remove home from locales
2019-02-16 16:27:24 +09:00
Acid Chicken (硫酸鶏)
34dffdfc8f Update config.yml 2019-02-16 11:10:06 +09:00
syuilo
a56f3f1d89 10.86.0 2019-02-16 11:06:45 +09:00
syuilo
88dc4c83cb Improve UI 2019-02-16 10:58:44 +09:00
syuilo
5a28dc0198 Improve user-list component 2019-02-16 09:27:53 +09:00
syuilo
40d2650d49 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-02-16 08:51:38 +09:00
syuilo
545e83efb1 🎨 2019-02-16 08:51:35 +09:00
Acid Chicken (硫酸鶏)
d4b00a5482 Update README.md [AUTOGEN] (#4280) 2019-02-16 06:58:51 +09:00
syuilo
c2b1bbeec5 Exploreページを実装 2019-02-16 06:50:58 +09:00
syuilo
8c8f165a6e Add missing comma 2019-02-16 04:51:57 +09:00
MeiMei
04553de230 Fix #4276 (#4278) 2019-02-15 23:43:49 +09:00
Acid Chicken (硫酸鶏)
2776934728 Update is-objectid.ts (#4277)
* Update is-objectid.ts

* Update is-objectid.ts
2019-02-15 23:42:44 +09:00
syuilo
0064dbb010 🎨 2019-02-15 19:40:11 +09:00
Acid Chicken (硫酸鶏)
d52e671adf Update README.md [AUTOGEN] (#4274) 2019-02-15 17:20:17 +09:00
syuilo
6017dc2dff 10.85.2 2019-02-15 15:38:10 +09:00
syuilo
937f7cbd60 🎨 2019-02-15 15:35:52 +09:00
syuilo
f8b3f66904 Refactor 2019-02-15 15:12:23 +09:00
syuilo
9d5701f35a Update deck.vue 2019-02-15 15:06:15 +09:00
syuilo
dff65810c6 Increase featured limit 2019-02-15 15:01:05 +09:00
syuilo
6752cf1d64 🎨 2019-02-15 15:00:20 +09:00
syuilo
8336910a59 🎨 2019-02-15 14:58:15 +09:00
syuilo
957a1149e0 検索結果を内部コンポーネントに 2019-02-15 14:52:21 +09:00
syuilo
e8719ff6e6 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-02-15 14:29:59 +09:00
syuilo
28b63298e5 Increase featured limit 2019-02-15 14:29:42 +09:00
MeiMei
dd4dee8095 デッキから フォロー/フォロワー ページに行けるように (#4271) 2019-02-15 14:27:16 +09:00
syuilo
c47818fed4 🎨 2019-02-15 08:52:09 +09:00
syuilo
e53c383908 10.85.1 2019-02-15 08:42:48 +09:00
syuilo
55c9c0436b 🎨 2019-02-15 08:42:41 +09:00
syuilo
66b79e5e24 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-02-15 08:40:25 +09:00
syuilo
514b830910 Fix bug 2019-02-15 08:40:16 +09:00
MeiMei
e4f799bf1d Hide localOnly from welcome TL (#4257) 2019-02-15 08:38:59 +09:00
syuilo
b383427d3d 🎨 2019-02-15 08:37:46 +09:00
syuilo
e969518139 Fix bug 2019-02-15 08:34:54 +09:00
syuilo
113fe294bd Fix #4267
Close #4269
2019-02-15 08:32:18 +09:00
syuilo
a4d92f781f 10.85.0 2019-02-15 06:34:45 +09:00
syuilo
414cac49c3 Improve featured api 2019-02-15 06:31:22 +09:00
syuilo
95b157ac3e Add featured page 2019-02-15 06:31:03 +09:00
syuilo
8e3d884081 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-02-15 06:06:24 +09:00
syuilo
9def6fcadd 🎨 2019-02-15 06:06:13 +09:00
Lynx Kotoura
7837bd44fc Add i18n when timelines are empty (#4261) 2019-02-15 05:59:07 +09:00
syuilo
a6c3663155 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-02-15 05:58:18 +09:00
syuilo
0b5afadbb8 Update setup.ja.md 2019-02-15 05:58:09 +09:00
syuilo
43864f0da4 既にフォローされている場合はフォローリクエストを生成しないように (#4266) 2019-02-15 05:56:28 +09:00
syuilo
6a0d9d70ed 非ログイン時にお知らせを表示 2019-02-15 05:55:59 +09:00
syuilo
63c6dce68e 🎨 2019-02-15 05:51:22 +09:00
syuilo
53422ffcb2 Improve desktop UX (#4262)
* wip

* wip

* wip

* wip

* wip

* wip

* Merge

* wip

* wip

* wip

* wip

* wip

* wip
2019-02-15 05:08:59 +09:00
Acid Chicken (硫酸鶏)
38ca514f53 Update README.md [AUTOGEN] (#4253) 2019-02-15 00:58:57 +09:00
MeiMei
caea0f0376 Change minimum allowed maxNoteTextLength to 0 (#4256) 2019-02-14 16:30:20 +09:00
Lynx Kotoura
25a8b26977 Emojify user.friends of desktop view (#4249) 2019-02-14 13:42:14 +09:00
Lynx Kotoura
bcaefe8d62 Fix huge close icon of friends-maker (#4245) 2019-02-14 13:41:48 +09:00
MeiMei
46f1e8c599 Restore web max-age to 5 minutes (#4246) 2019-02-14 13:41:28 +09:00
Lynx Kotoura
16230f320e Unify translations of frequently replied users (#4248) 2019-02-14 13:40:48 +09:00
Acid Chicken (硫酸鶏)
ace6419aef 無効化されているタイムラインのフォールバック (#4242)
* Update timeline.vue

* Update home.vue
2019-02-14 13:39:42 +09:00
Lynx Kotoura
77fb9eb2be Fix title of robot icon in user.header of desktop view (#4243) 2019-02-14 03:01:42 +09:00
syuilo
aa7fc7c893 10.84.2 2019-02-14 01:27:22 +09:00
syuilo
8fc170109f Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-02-14 01:13:28 +09:00
syuilo
ad12d00d7e ハッシュタグの集計期間を短くした
Resolve #4238 ?
2019-02-14 01:13:19 +09:00
Acid Chicken (硫酸鶏)
fa5ea45726 Docker: Remove unnecessary workaround for BusyBox's "free" (#4199) (#4213)
systeminformation gets incorrect memory information due to BusyBox's
"free" issue.(#3409)
A workaround for avoiding it was made.

But it never works because the runner container has no effect.
It should be deleted.
2019-02-13 23:45:58 +09:00
Acid Chicken (硫酸鶏)
4b6c113251 Add prelude function for URL Query (#4135)
* Update string.ts

* Refactor

* Update string.ts

* Update wrap-url.ts

* Update string.ts

* Update get-static-image-url.ts

* Use querystring.stringify

* Update outbox.ts

* Back to the urlQuery

* Update followers.ts

* Update following.ts

* Update outbox.ts

* Update string.ts

* Update get-static-image-url.ts

* Update string.ts

* Update string.ts

* Separate prelude files
2019-02-13 23:45:35 +09:00
Acid Chicken (硫酸鶏)
3548290ff2 Fix tslint.json styles (#4219) 2019-02-13 23:43:55 +09:00
syuilo
b165b90c40 Update dependencies 2019-02-13 21:57:00 +09:00
syuilo
4ffe9c908b Make ignore pointer events 2019-02-13 21:48:20 +09:00
syuilo
a135f75e71 🎨 2019-02-13 21:47:53 +09:00
Acid Chicken (硫酸鶏)
cbc61ba03d Add GIF badge (#4241) 2019-02-13 21:43:58 +09:00
syuilo
5aa58da918 Migrate cafy to 14.0 (#4240) 2019-02-13 16:33:07 +09:00
MeiMei
b083430011 Sort ISSUE_TEMPLATE (#4236) 2019-02-13 15:59:51 +09:00
Acid Chicken (硫酸鶏)
a8946b0404 Update nodeinfo.ts (#4239)
* Update nodeinfo.ts

* Update nodeinfo.ts

* Update nodeinfo.ts

* Update nodeinfo.ts

* Update nodeinfo.ts
2019-02-13 15:56:45 +09:00
dependabot[bot]
0303bccc61 Update vue-i18n requirement from 8.8.0 to 8.8.1 (#4235)
Updates the requirements on [vue-i18n](https://github.com/kazupon/vue-i18n) to permit the latest version.
- [Release notes](https://github.com/kazupon/vue-i18n/releases)
- [Changelog](https://github.com/kazupon/vue-i18n/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/kazupon/vue-i18n/commits/v8.8.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-13 14:46:41 +09:00
MeiMei
f3ce8564ea よく話すユーザーからサスペンドされたユーザーを隠すなど (#4228)
* Resolve #4226

* fix

* Fix: anonymousでアクセスするとサスペンドユーザーが隠れない

* fix
2019-02-12 17:19:15 +09:00
223 changed files with 3523 additions and 3022 deletions

View File

@@ -141,39 +141,28 @@ workflows:
- l10n_develop - l10n_develop
- imgbot - imgbot
- patch-autogen - patch-autogen
- build: - hold:
type: approval
filters: filters:
branches: branches:
ignore: ignore: master
- l10n_develop - build:
- imgbot requires:
- patch-autogen - hold
- test: - test:
executor: with-redis executor: with-redis
requires: requires:
- build - build
filters:
branches:
ignore:
# - master
- l10n_develop
- imgbot
- patch-autogen
- test: - test:
without_redis: true without_redis: true
requires: requires:
- build - build
filters:
# branches:
# only: master
branches:
ignore:
# - master
- l10n_develop
- imgbot
- patch-autogen
docker: docker:
jobs: jobs:
- ok:
filters:
branches:
ignore: master
- hold: - hold:
type: approval type: approval
filters: filters:

View File

@@ -1,6 +1,56 @@
ChangeLog ChangeLog
========= =========
10.86.2
----------
* 別タブでルートより下を開いたときにはデッキにしないように
* 横のナビゲーションバーの改善
* MIDIファイルがオーディオ扱いになる問題を修正
* ミュートワードで正規表現を使えるように
* デッキで無効になったタイムラインに警告を表示するように
* デザインの調整
* その他細かな修正
10.86.1
----------
* ナビゲーションバーの「ホーム」を「タイムライン」に改称
* モバイル版でユーザーページが二重に描画される問題を修正
* ユーザー一覧の「もっと読み込む」の動作がおかしい問題を修正
* デザインの調整
10.86.0
----------
* Exploreページを実装
* UIを改良
* その他細かな修正
10.85.2
----------
* デッキから フォロー/フォロワー ページに行けるように
* ナビゲーションが発生したときに最上部までスクロールように
* 検索結果でページ遷移が発生する問題を修正
* デザインの調整
10.85.1
----------
* ローカルのみ投稿をログイン画面のタイムラインに表示しないように
* ナビゲーションバーを横にしてるとデッキに行けない問題を修正
10.85.0
----------
* デスクトップ版のUIを改良
* 投稿ハイライトページを実装
* 無効化されているタイムラインのフォールバック
* 既にフォローされている場合はフォローリクエストを生成しないように
* その他細かな修正
10.84.2
----------
* GIF画像にGIFバッジを表示
* よく話すユーザーからサスペンドされたユーザーを隠すなど
* nodeinfoが重い問題を修正
* ハッシュタグクラウド取得が重い問題を軽減
10.84.1 10.84.1
---------- ----------
* deckにフォローされていますマークを追加 * deckにフォローされていますマークを追加

View File

@@ -8,7 +8,6 @@ WORKDIR /misskey
FROM base AS builder FROM base AS builder
RUN unlink /usr/bin/free
RUN apk add --no-cache \ RUN apk add --no-cache \
autoconf \ autoconf \
automake \ automake \
@@ -20,7 +19,6 @@ RUN apk add --no-cache \
make \ make \
nasm \ nasm \
pkgconfig \ pkgconfig \
procps \
python \ python \
zlib-dev zlib-dev
RUN npm i -g yarn RUN npm i -g yarn

View File

@@ -94,7 +94,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<!-- PATREON_START --> <!-- PATREON_START -->
<table><tr> <table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/1?token-time=2145916800&token-hash=WeuDzzz24cRXJogyIkU-mxARqkdyms-rcZKbO-GpGjw%3D" alt="weep" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/1?token-time=2145916800&token-hash=WeuDzzz24cRXJogyIkU-mxARqkdyms-rcZKbO-GpGjw%3D" alt="weep" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=prtYqPOiSHBulhM7NU0VzMaWx39-9ntdq25b6kafDNA%3D" alt="negao" width="100"></td> <td><img src="https://c8.patreon.com/2/200/12059069" alt="naga_rus" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/3?token-time=2145916800&token-hash=c8HeVqLtmdgH-gSBJg8i10gmOcwllM87MDHeznl3el0%3D" alt="Melilot" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/3?token-time=2145916800&token-hash=c8HeVqLtmdgH-gSBJg8i10gmOcwllM87MDHeznl3el0%3D" alt="Melilot" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td> <td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/3?token-time=2145916800&token-hash=LtV2lRi3L2jOWMLwccr9qWYfPrFlzIo2jYZHKzHEb6k%3D" alt="Xeltica" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/3?token-time=2145916800&token-hash=LtV2lRi3L2jOWMLwccr9qWYfPrFlzIo2jYZHKzHEb6k%3D" alt="Xeltica" width="100"></td>
@@ -102,7 +102,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=1FlxS9MEgmNGH_RHUVHbO5hIXB5I1z0lvA33CTvYvjA%3D" alt="gutfuckllc" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=1FlxS9MEgmNGH_RHUVHbO5hIXB5I1z0lvA33CTvYvjA%3D" alt="gutfuckllc" width="100"></td>
</tr><tr> </tr><tr>
<td><a href="https://www.patreon.com/weepjp">weep</a></td> <td><a href="https://www.patreon.com/weepjp">weep</a></td>
<td><a href="https://www.patreon.com/negao">negao</a></td> <td><a href="https://www.patreon.com/user?u=12059069">naga_rus</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/user?u=16869916">見当かなみ</a></td> <td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
<td><a href="https://www.patreon.com/Xeltica">Xeltica</a></td> <td><a href="https://www.patreon.com/Xeltica">Xeltica</a></td>
@@ -115,6 +115,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td> <td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=2PsbFNw0tnubZzgSXD01R6hIgncfiElG7H7HX2Y3dyo%3D" alt="nemu" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=2PsbFNw0tnubZzgSXD01R6hIgncfiElG7H7HX2Y3dyo%3D" alt="nemu" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3?token-time=2145916800&token-hash=9JtETp0X8gI280Ne1E8bxn6j4Lw5o2k4mJkICx97V_k%3D" alt="YUKIMOCHI" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3?token-time=2145916800&token-hash=9JtETp0X8gI280Ne1E8bxn6j4Lw5o2k4mJkICx97V_k%3D" alt="YUKIMOCHI" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/17463605" alt="Sampot" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4?token-time=2145916800&token-hash=SbdZeN5SmsuT9stD6v0jN1z0hftg0FmRiCTxysU0Ihw%3D" alt="Damillora" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4?token-time=2145916800&token-hash=SbdZeN5SmsuT9stD6v0jN1z0hftg0FmRiCTxysU0Ihw%3D" alt="Damillora" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8241184/39e18850e87a449e9c9a71acb3310ebd/3?token-time=2145916800&token-hash=gMq30aylxu5v3G8pRhWR5jeRBbYWEoRKjGbNeiCQz5g%3D" alt="Acid Chicken" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8241184/39e18850e87a449e9c9a71acb3310ebd/3?token-time=2145916800&token-hash=gMq30aylxu5v3G8pRhWR5jeRBbYWEoRKjGbNeiCQz5g%3D" alt="Acid Chicken" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/2?token-time=2145916800&token-hash=zcwFxb2zopzWwksKVU1YpfAEjsl4yKT02aQ6yiAFRiQ%3D" alt="natalie" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/2?token-time=2145916800&token-hash=zcwFxb2zopzWwksKVU1YpfAEjsl4yKT02aQ6yiAFRiQ%3D" alt="natalie" width="100"></td>
@@ -125,6 +126,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td> <td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
<td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td> <td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td>
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td> <td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
<td><a href="https://www.patreon.com/user?u=17463605">Sampot</a></td>
<td><a href="https://www.patreon.com/damillora">Damillora</a></td> <td><a href="https://www.patreon.com/damillora">Damillora</a></td>
<td><a href="https://www.patreon.com/acid_chicken">Acid Chicken</a></td> <td><a href="https://www.patreon.com/acid_chicken">Acid Chicken</a></td>
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td> <td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
@@ -142,7 +144,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<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>
</tr></table> </tr></table>
**Last updated:** Wed, 06 Feb 2019 18:18:05 UTC **Last updated:** Fri, 15 Feb 2019 19:12:06 UTC
<!-- PATREON_END --> <!-- PATREON_END -->
:four_leaf_clover: Copyright :four_leaf_clover: Copyright

View File

@@ -122,6 +122,8 @@ CentOSで1024以下のポートを使用してMisskeyを使用する場合は`Ex
4. `npm run build` 4. `npm run build`
5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する 5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する
なにか問題が発生した場合は、`npm run clean`すると直る場合があります。
---------------------------------------------------------------- ----------------------------------------------------------------
なにかお困りのことがありましたらお気軽にご連絡ください。 なにかお困りのことがありましたらお気軽にご連絡ください。

View File

@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
activity: "アクティビティ" activity: "アクティビティ"
keywords: "キーワード" keywords: "キーワード"
domains: "頻出ドメイン" domains: "頻出ドメイン"
frequently-replied-users: "よく話すユーザー" frequently-replied-users: "よく話すユーザー"
followers-you-know: "知り合いのフォロワー" followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン" last-used-at: "最終ログイン"
mobile/views/pages/user/home.followers-you-know.vue: mobile/views/pages/user/home.followers-you-know.vue:
no-users: "知り合いのユーザーはいません" no-users: "知り合いのユーザーはいません"
mobile/views/pages/user/home.friends.vue: mobile/views/pages/user/home.friends.vue:
no-users: "よく話すユーザーはいません" no-users: "よく話すユーザーはいません"
mobile/views/pages/user/home.notes.vue: mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はありません" no-notes: "投稿はありません"
mobile/views/pages/user/home.photos.vue: mobile/views/pages/user/home.photos.vue:

View File

@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
activity: "アクティビティ" activity: "アクティビティ"
keywords: "Schlagwörter" keywords: "Schlagwörter"
domains: "頻出ドメイン" domains: "頻出ドメイン"
frequently-replied-users: "よく話すユーザー" frequently-replied-users: "よく話すユーザー"
followers-you-know: "知り合いのフォロワー" followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン" last-used-at: "最終ログイン"
mobile/views/pages/user/home.followers-you-know.vue: mobile/views/pages/user/home.followers-you-know.vue:
no-users: "知り合いのユーザーはいません" no-users: "知り合いのユーザーはいません"
mobile/views/pages/user/home.friends.vue: mobile/views/pages/user/home.friends.vue:
no-users: "よく話すユーザーはいません" no-users: "よく話すユーザーはいません"
mobile/views/pages/user/home.notes.vue: mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はありません" no-notes: "投稿はありません"
mobile/views/pages/user/home.photos.vue: mobile/views/pages/user/home.photos.vue:

View File

@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
activity: "アクティビティ" activity: "アクティビティ"
keywords: "キーワード" keywords: "キーワード"
domains: "頻出ドメイン" domains: "頻出ドメイン"
frequently-replied-users: "よく話すユーザー" frequently-replied-users: "よく話すユーザー"
followers-you-know: "知り合いのフォロワー" followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン" last-used-at: "最終ログイン"
mobile/views/pages/user/home.followers-you-know.vue: mobile/views/pages/user/home.followers-you-know.vue:
no-users: "知り合いのユーザーはいません" no-users: "知り合いのユーザーはいません"
mobile/views/pages/user/home.friends.vue: mobile/views/pages/user/home.friends.vue:
no-users: "よく話すユーザーはいません" no-users: "よく話すユーザーはいません"
mobile/views/pages/user/home.notes.vue: mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はありません" no-notes: "投稿はありません"
mobile/views/pages/user/home.photos.vue: mobile/views/pages/user/home.photos.vue:

View File

@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
activity: "アクティビティ" activity: "アクティビティ"
keywords: "キーワード" keywords: "キーワード"
domains: "頻出ドメイン" domains: "頻出ドメイン"
frequently-replied-users: "よく話すユーザー" frequently-replied-users: "よく話すユーザー"
followers-you-know: "知り合いのフォロワー" followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン" last-used-at: "最終ログイン"
mobile/views/pages/user/home.followers-you-know.vue: mobile/views/pages/user/home.followers-you-know.vue:
no-users: "知り合いのユーザーはいません" no-users: "知り合いのユーザーはいません"
mobile/views/pages/user/home.friends.vue: mobile/views/pages/user/home.friends.vue:
no-users: "よく話すユーザーはいません" no-users: "よく話すユーザーはいません"
mobile/views/pages/user/home.notes.vue: mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はありません" no-notes: "投稿はありません"
mobile/views/pages/user/home.photos.vue: mobile/views/pages/user/home.photos.vue:

View File

@@ -28,6 +28,8 @@ common:
load-more: "もっと読み込む" load-more: "もっと読み込む"
enter-password: "パスワードを入力してください" enter-password: "パスワードを入力してください"
2fa: "二段階認証" 2fa: "二段階認証"
customize-home: "ホームをカスタマイズ"
featured-notes: "ハイライト"
got-it: "わかった" got-it: "わかった"
customization-tips: customization-tips:
@@ -58,6 +60,11 @@ common:
trash: "ゴミ箱" trash: "ゴミ箱"
drive: "ドライブ" drive: "ドライブ"
messaging: "トーク" messaging: "トーク"
deck: "デッキ"
timeline: "タイムライン"
explore: "みつける"
following: "フォロー中"
followers: "フォロワー"
weekday-short: weekday-short:
sunday: "日" sunday: "日"
@@ -213,6 +220,11 @@ auth/views/index.vue:
error: "セッションが存在しません。" error: "セッションが存在しません。"
sign-in: "サインインしてください" sign-in: "サインインしてください"
common/views/pages/explore.vue:
verified-users: "公式アカウント"
popular-users: "人気のユーザー"
recently-updated-users: "最近投稿したユーザー"
common/views/components/games/reversi/reversi.vue: common/views/components/games/reversi/reversi.vue:
matching: matching:
waiting-for: "{}を待っています" waiting-for: "{}を待っています"
@@ -862,6 +874,9 @@ desktop/views/components/renote-form.vue:
desktop/views/components/renote-form-window.vue: desktop/views/components/renote-form-window.vue:
title: "この投稿をRenoteしますか" title: "この投稿をRenoteしますか"
desktop/views/components/timeline.core.vue:
empty: "投稿がありません"
desktop/views/pages/user-following-or-followers.vue: desktop/views/pages/user-following-or-followers.vue:
following: "{user}のフォロー" following: "{user}のフォロー"
followers: "{user}のフォロワー" followers: "{user}のフォロワー"
@@ -893,14 +908,10 @@ desktop/views/components/settings.vue:
web-search-engine-desc: "例: https://www.google.com/?#q={{query}}" web-search-engine-desc: "例: https://www.google.com/?#q={{query}}"
auto-popout: "ウィンドウの自動ポップアウト" auto-popout: "ウィンドウの自動ポップアウト"
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。" auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
deck-nav: "デッキ内ナビゲーション"
deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
keep-cw: "CW保持" keep-cw: "CW保持"
keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。" keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
deck-default: "デッキをデフォルトのUIにする"
display: "デザインと表示" display: "デザインと表示"
customize: "ホームをカスタマイズ"
wallpaper: "壁紙" wallpaper: "壁紙"
choose-wallpaper: "壁紙を選択" choose-wallpaper: "壁紙を選択"
delete-wallpaper: "壁紙を削除" delete-wallpaper: "壁紙を削除"
@@ -1076,15 +1087,12 @@ desktop/views/components/ui.header.account.vue:
favorites: "お気に入り" favorites: "お気に入り"
lists: "リスト" lists: "リスト"
follow-requests: "フォロー申請" follow-requests: "フォロー申請"
customize: "ホームのカスタマイズ"
admin: "管理" admin: "管理"
settings: "設定" settings: "設定"
signout: "サインアウト" signout: "サインアウト"
dark: "闇に飲まれる" dark: "闇に飲まれる"
desktop/views/components/ui.header.nav.vue: desktop/views/components/ui.header.nav.vue:
home: "ホーム"
deck: "デッキ"
game: "ゲーム" game: "ゲーム"
desktop/views/components/ui.header.notifications.vue: desktop/views/components/ui.header.notifications.vue:
@@ -1447,9 +1455,6 @@ desktop/views/pages/welcome.vue:
desktop/views/pages/drive.vue: desktop/views/pages/drive.vue:
title: "Misskey Drive" title: "Misskey Drive"
desktop/views/pages/home-customize.vue:
title: "ホームのカスタマイズ"
desktop/views/pages/note.vue: desktop/views/pages/note.vue:
prev: "前の投稿" prev: "前の投稿"
next: "次の投稿" next: "次の投稿"
@@ -1490,10 +1495,6 @@ desktop/views/pages/user/user.photos.vue:
loading: "読み込み中" loading: "読み込み中"
no-photos: "写真はありません" no-photos: "写真はありません"
desktop/views/pages/user/user.profile.vue:
follows-you: "フォローされています"
menu: "メニュー"
desktop/views/pages/user/user.header.vue: desktop/views/pages/user/user.header.vue:
posts: "投稿" posts: "投稿"
following: "フォロー" following: "フォロー"
@@ -1503,6 +1504,7 @@ desktop/views/pages/user/user.header.vue:
year: "年" year: "年"
month: "月" month: "月"
day: "日" day: "日"
follows-you: "フォローされています"
desktop/views/pages/user/user.timeline.vue: desktop/views/pages/user/user.timeline.vue:
default: "投稿" default: "投稿"
@@ -1661,10 +1663,6 @@ mobile/views/components/user-timeline.vue:
no-notes: "このユーザーは投稿していないようです。" no-notes: "このユーザーは投稿していないようです。"
no-notes-with-media: "メディア付き投稿はありません。" no-notes-with-media: "メディア付き投稿はありません。"
mobile/views/components/users-list.vue:
all: "すべて"
known: "知り合い"
mobile/views/pages/favorites.vue: mobile/views/pages/favorites.vue:
title: "お気に入り" title: "お気に入り"
@@ -1689,6 +1687,9 @@ mobile/views/pages/home.vue:
mentions: "あなた宛て" mentions: "あなた宛て"
messages: "メッセージ" messages: "メッセージ"
mobile/views/pages/home.timeline.vue:
empty: "投稿がありません"
mobile/views/pages/tag.vue: mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。" no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
@@ -1791,7 +1792,7 @@ mobile/views/pages/user/home.vue:
activity: "アクティビティ" activity: "アクティビティ"
keywords: "キーワード" keywords: "キーワード"
domains: "頻出ドメイン" domains: "頻出ドメイン"
frequently-replied-users: "よく話すユーザー" frequently-replied-users: "よく話すユーザー"
followers-you-know: "知り合いのフォロワー" followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン" last-used-at: "最終ログイン"
@@ -1799,7 +1800,7 @@ mobile/views/pages/user/home.followers-you-know.vue:
no-users: "知り合いのユーザーはいません" no-users: "知り合いのユーザーはいません"
mobile/views/pages/user/home.friends.vue: mobile/views/pages/user/home.friends.vue:
no-users: "よく話すユーザーはいません" no-users: "よく話すユーザーはいません"
mobile/views/pages/user/home.notes.vue: mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はありません" no-notes: "投稿はありません"
@@ -1827,6 +1828,9 @@ deck:
rename: "名前を変更" rename: "名前を変更"
stack-left: "左に重ねる" stack-left: "左に重ねる"
pop-right: "右に出す" pop-right: "右に出す"
disabled-timeline:
title: "無効化されたタイムライン"
description: "サーバーの運営者により、このタイムラインは使用できない状態に設定されています。"
deck/deck.tl-column.vue: deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ" is-media-only: "メディア投稿のみ"

View File

@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
activity: "アクティビティ" activity: "アクティビティ"
keywords: "Nøkkelord" keywords: "Nøkkelord"
domains: "頻出ドメイン" domains: "頻出ドメイン"
frequently-replied-users: "よく話すユーザー" frequently-replied-users: "よく話すユーザー"
followers-you-know: "知り合いのフォロワー" followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン" last-used-at: "最終ログイン"
mobile/views/pages/user/home.followers-you-know.vue: mobile/views/pages/user/home.followers-you-know.vue:
no-users: "知り合いのユーザーはいません" no-users: "知り合いのユーザーはいません"
mobile/views/pages/user/home.friends.vue: mobile/views/pages/user/home.friends.vue:
no-users: "よく話すユーザーはいません" no-users: "よく話すユーザーはいません"
mobile/views/pages/user/home.notes.vue: mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はありません" no-notes: "投稿はありません"
mobile/views/pages/user/home.photos.vue: mobile/views/pages/user/home.photos.vue:

View File

@@ -1597,7 +1597,7 @@ mobile/views/pages/user/home.vue:
mobile/views/pages/user/home.followers-you-know.vue: mobile/views/pages/user/home.followers-you-know.vue:
no-users: "知り合いのユーザーはいません" no-users: "知り合いのユーザーはいません"
mobile/views/pages/user/home.friends.vue: mobile/views/pages/user/home.friends.vue:
no-users: "よく話すユーザーはいません" no-users: "よく話すユーザーはいません"
mobile/views/pages/user/home.notes.vue: mobile/views/pages/user/home.notes.vue:
no-notes: "Nenhuma mensagem" no-notes: "Nenhuma mensagem"
mobile/views/pages/user/home.photos.vue: mobile/views/pages/user/home.photos.vue:

View File

@@ -1591,13 +1591,13 @@ mobile/views/pages/user/home.vue:
activity: "アクティビティ" activity: "アクティビティ"
keywords: "キーワード" keywords: "キーワード"
domains: "頻出ドメイン" domains: "頻出ドメイン"
frequently-replied-users: "よく話すユーザー" frequently-replied-users: "よく話すユーザー"
followers-you-know: "知り合いのフォロワー" followers-you-know: "知り合いのフォロワー"
last-used-at: "最終ログイン" last-used-at: "最終ログイン"
mobile/views/pages/user/home.followers-you-know.vue: mobile/views/pages/user/home.followers-you-know.vue:
no-users: "知り合いのユーザーはいません" no-users: "知り合いのユーザーはいません"
mobile/views/pages/user/home.friends.vue: mobile/views/pages/user/home.friends.vue:
no-users: "よく話すユーザーはいません" no-users: "よく話すユーザーはいません"
mobile/views/pages/user/home.notes.vue: mobile/views/pages/user/home.notes.vue:
no-notes: "投稿はありません" no-notes: "投稿はありません"
mobile/views/pages/user/home.photos.vue: mobile/views/pages/user/home.photos.vue:

View File

@@ -1,8 +1,8 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "10.84.1", "version": "10.86.2",
"clientVersion": "2.0.14252", "clientVersion": "2.0.14342",
"codename": "nighthike", "codename": "nighthike",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -103,7 +103,7 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"bee-queue": "1.2.2", "bee-queue": "1.2.2",
"bootstrap-vue": "2.0.0-rc.11", "bootstrap-vue": "2.0.0-rc.11",
"cafy": "12.1.0", "cafy": "14.0.1",
"chai": "4.2.0", "chai": "4.2.0",
"chai-http": "4.2.1", "chai-http": "4.2.1",
"chalk": "2.4.2", "chalk": "2.4.2",
@@ -229,11 +229,11 @@
"uuid": "3.3.2", "uuid": "3.3.2",
"v-animate-css": "0.0.3", "v-animate-css": "0.0.3",
"video-thumbnail-generator": "1.1.3", "video-thumbnail-generator": "1.1.3",
"vue": "2.6.5", "vue": "2.6.6",
"vue-color": "2.7.0", "vue-color": "2.7.0",
"vue-content-loading": "1.5.3", "vue-content-loading": "1.5.3",
"vue-cropperjs": "3.0.0", "vue-cropperjs": "3.0.0",
"vue-i18n": "8.8.0", "vue-i18n": "8.8.1",
"vue-js-modal": "1.3.28", "vue-js-modal": "1.3.28",
"vue-loader": "15.6.2", "vue-loader": "15.6.2",
"vue-marquee-text-component": "1.1.1", "vue-marquee-text-component": "1.1.1",
@@ -242,7 +242,7 @@
"vue-sequential-entrance": "1.1.3", "vue-sequential-entrance": "1.1.3",
"vue-style-loader": "4.1.2", "vue-style-loader": "4.1.2",
"vue-svg-inline-loader": "1.2.10", "vue-svg-inline-loader": "1.2.10",
"vue-template-compiler": "2.6.5", "vue-template-compiler": "2.6.6",
"vuedraggable": "2.17.0", "vuedraggable": "2.17.0",
"vuewordcloud": "18.7.11", "vuewordcloud": "18.7.11",
"vuex": "3.1.0", "vuex": "3.1.0",

View File

@@ -339,7 +339,7 @@ export default Vue.extend({
}); });
return !confirm.canceled; return !confirm.canceled;
} },
fetchUsers() { fetchUsers() {
this.$root.api('admin/show-users', { this.$root.api('admin/show-users', {

View File

@@ -1,9 +1,11 @@
import { url as instanceUrl } from '../../config'; import { url as instanceUrl } from '../../config';
import * as url from '../../../../prelude/url';
export function getStaticImageUrl(url: string): string { export function getStaticImageUrl(baseUrl: string): string {
const u = new URL(url); const u = new URL(baseUrl);
const dummy = `${u.host}${u.pathname}`; // 拡張子がないとキャッシュしてくれないCDNがあるので const dummy = `${u.host}${u.pathname}`; // 拡張子がないとキャッシュしてくれないCDNがあるので
let result = `${instanceUrl}/proxy/${dummy}?url=${encodeURIComponent(u.href)}`; return `${instanceUrl}/proxy/${dummy}?${url.query({
result += '&static=1'; url: u.href,
return result; static: '1'
})}`;
} }

View File

@@ -4,7 +4,8 @@ export default function(me, settings, note) {
const includesMutedWords = (text: string) => const includesMutedWords = (text: string) =>
text text
? settings.mutedWords.some(q => q.length > 0 && !q.some(word => !text.includes(word))) ? settings.mutedWords.some(q => q.length > 0 && !q.some(word =>
word.startsWith('/') && word.endsWith('/') ? !(new RegExp(word.substr(1, word.length - 2)).test(text)) : !text.includes(word)))
: false; : false;
return ( return (

View File

@@ -0,0 +1,18 @@
export default {
install(Vue) {
Vue.directive('size', {
inserted(el, binding) {
const query = binding.value;
const width = el.clientWidth;
for (const q of query) {
if (q.lt && (width <= q.lt)) {
el.classList.add(q.class);
}
if (q.gt && (width >= q.gt)) {
el.classList.add(q.class);
}
}
}
});
}
};

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="mk-activity"> <div>
<div ref="chart"></div> <div ref="chart"></div>
</div> </div>
</template> </template>
@@ -9,7 +9,17 @@ import Vue from 'vue';
import ApexCharts from 'apexcharts'; import ApexCharts from 'apexcharts';
export default Vue.extend({ export default Vue.extend({
props: ['user'], props: {
user: {
type: Object,
required: true
},
limit: {
type: Number,
required: false,
default: 21
}
},
data() { data() {
return { return {
fetching: true, fetching: true,
@@ -21,7 +31,7 @@ export default Vue.extend({
this.$root.api('charts/user/notes', { this.$root.api('charts/user/notes', {
userId: this.user.id, userId: this.user.id,
span: 'day', span: 'day',
limit: 21 limit: this.limit
}).then(stats => { }).then(stats => {
const normal = []; const normal = [];
const reply = []; const reply = [];
@@ -32,7 +42,7 @@ export default Vue.extend({
const m = now.getMonth(); const m = now.getMonth();
const d = now.getDate(); const d = now.getDate();
for (let i = 0; i < 21; i++) { for (let i = 0; i < this.limit; i++) {
const x = new Date(y, m, d - i); const x = new Date(y, m, d - i);
normal.push([ normal.push([
x, x,
@@ -99,10 +109,3 @@ export default Vue.extend({
} }
}); });
</script> </script>
<style lang="stylus" scoped>
.mk-activity
max-width 600px
margin 0 auto
</style>

View File

@@ -0,0 +1,11 @@
<template>
<div>
<slot></slot>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
});
</script>

View File

@@ -1,8 +1,9 @@
<template> <template>
<button class="wfliddvnhxvyusikowhxozkyxyenqxqr" <button class="wfliddvnhxvyusikowhxozkyxyenqxqr"
:class="{ wait, block, mini, active: isFollowing || hasPendingFollowRequestFromYou }" :class="{ wait, block, inline, mini, active: isFollowing || hasPendingFollowRequestFromYou }"
@click="onClick" @click="onClick"
:disabled="wait" :disabled="wait"
:inline="inline"
> >
<template v-if="!wait"> <template v-if="!wait">
<fa :icon="iconAndText[0]"/> <template v-if="!mini">{{ iconAndText[1] }}</template> <fa :icon="iconAndText[0]"/> <template v-if="!mini">{{ iconAndText[1] }}</template>
@@ -28,6 +29,11 @@ export default Vue.extend({
required: false, required: false,
default: false default: false
}, },
inline: {
type: Boolean,
required: false,
default: false
},
mini: { mini: {
type: Boolean, type: Boolean,
required: false, required: false,
@@ -128,6 +134,9 @@ export default Vue.extend({
border solid 1px var(--primary) border solid 1px var(--primary)
border-radius 36px border-radius 36px
&.inline
display inline-block
&.mini &.mini
padding 0 padding 0
min-width 0 min-width 0

View File

@@ -1,5 +1,6 @@
import Vue from 'vue'; import Vue from 'vue';
import dummy from './dummy.vue';
import userName from './user-name.vue'; import userName from './user-name.vue';
import followButton from './follow-button.vue'; import followButton from './follow-button.vue';
import error from './error.vue'; import error from './error.vue';
@@ -32,6 +33,7 @@ import urlPreview from './url-preview.vue';
import fileTypeIcon from './file-type-icon.vue'; import fileTypeIcon from './file-type-icon.vue';
import emoji from './emoji.vue'; import emoji from './emoji.vue';
import welcomeTimeline from './welcome-timeline.vue'; import welcomeTimeline from './welcome-timeline.vue';
import userList from './user-list.vue';
import uiInput from './ui/input.vue'; import uiInput from './ui/input.vue';
import uiButton from './ui/button.vue'; import uiButton from './ui/button.vue';
import uiHorizonGroup from './ui/horizon-group.vue'; import uiHorizonGroup from './ui/horizon-group.vue';
@@ -46,6 +48,7 @@ import formButton from './ui/form/button.vue';
import formRadio from './ui/form/radio.vue'; import formRadio from './ui/form/radio.vue';
Vue.component('mfm', misskeyFlavoredMarkdown); Vue.component('mfm', misskeyFlavoredMarkdown);
Vue.component('mk-dummy', dummy);
Vue.component('mk-user-name', userName); Vue.component('mk-user-name', userName);
Vue.component('mk-follow-button', followButton); Vue.component('mk-follow-button', followButton);
Vue.component('mk-error', error); Vue.component('mk-error', error);
@@ -77,6 +80,7 @@ Vue.component('mk-url-preview', urlPreview);
Vue.component('mk-file-type-icon', fileTypeIcon); Vue.component('mk-file-type-icon', fileTypeIcon);
Vue.component('mk-emoji', emoji); Vue.component('mk-emoji', emoji);
Vue.component('mk-welcome-timeline', welcomeTimeline); Vue.component('mk-welcome-timeline', welcomeTimeline);
Vue.component('mk-user-list', userList);
Vue.component('ui-input', uiInput); Vue.component('ui-input', uiInput);
Vue.component('ui-button', uiButton); Vue.component('ui-button', uiButton);
Vue.component('ui-horizon-group', uiHorizonGroup); Vue.component('ui-horizon-group', uiHorizonGroup);

View File

@@ -5,7 +5,7 @@
<b>{{ $t('sensitive') }}</b> <b>{{ $t('sensitive') }}</b>
<span>{{ $t('click-to-show') }}</span> <span>{{ $t('click-to-show') }}</span>
</div> </div>
<div class="audio" v-else-if="media.type.startsWith('audio')"> <div class="audio" v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'">
<audio class="audio" <audio class="audio"
:src="media.url" :src="media.url"
:title="media.name" :title="media.name"

View File

@@ -10,7 +10,9 @@
:style="style" :style="style"
:title="image.name" :title="image.name"
@click.prevent="onClick" @click.prevent="onClick"
></a> >
<div v-if="image.type === 'image/gif'">GIF</div>
</a>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -76,6 +78,20 @@ export default Vue.extend({
background-size contain background-size contain
background-repeat no-repeat background-repeat no-repeat
> div
background-color var(--text)
border-radius var(--round)
color var(--secondary)
display inline-block
font-size 14px
font-weight bold
left 12px
opacity .5
padding 0 6px
text-align center
top 12px
pointer-events none
.qjewsnkgzzxlxtzncydssfbgjibiehcy .qjewsnkgzzxlxtzncydssfbgjibiehcy
display flex display flex
justify-content center justify-content center

View File

@@ -0,0 +1,161 @@
<template>
<ui-container :body-togglable="true">
<template slot="header"><slot></slot></template>
<mk-error v-if="!fetching && !inited" @retry="init()"/>
<div class="efvhhmdq" v-size="[{ lt: 500, class: 'narrow' }]">
<div class="user" v-for="user in us">
<mk-avatar class="avatar" :user="user"/>
<div class="body">
<div class="name">
<router-link class="name" :to="user | userPage" v-user-preview="user.id"><mk-user-name :user="user"/></router-link>
<p class="username">@{{ user | acct }}</p>
</div>
<div class="description" v-if="user.description" :title="user.description">
<mfm :text="user.description" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :should-break="false"/>
</div>
</div>
</div>
<button class="more" :class="{ fetching: fetchingMoreUsers }" v-if="cursor != null" @click="fetchMoreUsers()" :disabled="fetchingMoreUsers">
<template v-if="fetchingMoreUsers"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreUsers ? $t('@.loading') : $t('@.load-more') }}
</button>
</div>
</ui-container>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
makePromise: {
required: true
},
iconOnly: {
type: Boolean,
default: false
}
},
data() {
return {
fetching: true,
fetchingMoreUsers: false,
us: [],
inited: false,
cursor: null
};
},
created() {
this.init();
},
methods: {
init() {
this.fetching = true;
this.makePromise().then(x => {
if (Array.isArray(x)) {
this.us = x;
} else {
this.us = x.users;
this.cursor = x.cursor;
}
this.inited = true;
this.fetching = false;
}, e => {
this.fetching = false;
});
},
fetchMoreUsers() {
this.fetchingMoreUsers = true;
this.makePromise(this.cursor).then(x => {
this.us = this.us.concat(x.users);
this.cursor = x.cursor;
this.fetchingMoreUsers = false;
}, e => {
this.fetchingMoreUsers = false;
});
}
}
});
</script>
<style lang="stylus" scoped>
.efvhhmdq
&.narrow
> .user > .body > .name
width 100%
> .user > .body > .description
display none
> .user
display flex
padding 16px
border-bottom solid 1px var(--faceDivider)
&:last-child
border-bottom none
> .avatar
display block
flex-shrink 0
margin 0 12px 0 0
width 42px
height 42px
border-radius 8px
> .body
display flex
width calc(100% - 54px)
> .name
width 45%
> .name
margin 0
font-size 16px
line-height 24px
color var(--text)
> .username
display block
margin 0
font-size 15px
line-height 16px
color var(--text)
opacity 0.7
> .description
width 55%
color var(--text)
line-height 42px
white-space nowrap
overflow hidden
text-overflow ellipsis
opacity 0.7
font-size 14px
> .more
display block
width 100%
padding 16px
color var(--text)
border-top solid var(--lineWidth) rgba(#000, 0.05)
&:hover
background rgba(#000, 0.025)
&:active
background rgba(#000, 0.05)
&.fetching
cursor wait
> [data-icon]
margin-right 4px
</style>

View File

@@ -76,6 +76,7 @@ export default Vue.extend({
if (note.replyId != null) return; if (note.replyId != null) return;
if (note.renoteId != null) return; if (note.renoteId != null) return;
if (note.poll != null) return; if (note.poll != null) return;
if (note.localOnly) return;
this.notes.unshift(note); this.notes.unshift(note);
}, },

View File

@@ -0,0 +1,50 @@
<template>
<div>
<mk-user-list :make-promise="verifiedUsers">
<span><fa :icon="faBookmark"/> {{ $t('verified-users') }}</span>
</mk-user-list>
<mk-user-list :make-promise="popularUsers">
<span><fa :icon="faChartLine"/> {{ $t('popular-users') }}</span>
</mk-user-list>
<mk-user-list :make-promise="recentlyUpdatedUsers">
<span><fa :icon="faCommentAlt"/> {{ $t('recently-updated-users') }}</span>
</mk-user-list>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import { faChartLine } from '@fortawesome/free-solid-svg-icons';
import { faBookmark, faCommentAlt } from '@fortawesome/free-regular-svg-icons';
export default Vue.extend({
i18n: i18n('common/views/pages/explore.vue'),
data() {
return {
verifiedUsers: () => this.$root.api('users', {
state: 'verified',
origin: 'local',
sort: '+follower',
limit: 10
}),
popularUsers: () => this.$root.api('users', {
state: 'alive',
origin: 'local',
sort: '+follower',
limit: 10
}),
recentlyUpdatedUsers: () => this.$root.api('users', {
origin: 'local',
sort: '+updatedAt',
limit: 10
}),
faBookmark, faChartLine, faCommentAlt
};
},
});
</script>
<style lang="stylus" scoped>
</style>

View File

@@ -0,0 +1,30 @@
<template>
<div>
<mk-user-list :make-promise="makePromise">{{ $t('@.followers') }}</mk-user-list>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import parseAcct from '../../../../../misc/acct/parse';
import i18n from '../../../i18n';
export default Vue.extend({
i18n: i18n(''),
data() {
return {
makePromise: cursor => this.$root.api('users/followers', {
...parseAcct(this.$route.params.user),
limit: 30,
cursor: cursor ? cursor : undefined
}).then(x => {
return {
users: x.users,
cursor: x.next
};
}),
};
},
});
</script>

View File

@@ -0,0 +1,27 @@
<template>
<div>
<mk-user-list :make-promise="makePromise">{{ $t('@.following') }}</mk-user-list>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import parseAcct from '../../../../../misc/acct/parse';
export default Vue.extend({
data() {
return {
makePromise: cursor => this.$root.api('users/following', {
...parseAcct(this.$route.params.user),
limit: 30,
cursor: cursor ? cursor : undefined
}).then(x => {
return {
users: x.users,
cursor: x.next
};
}),
};
},
});
</script>

View File

@@ -1,10 +1,10 @@
<template> <template>
<div class="mkw-analog-clock"> <div class="mkw-analog-clock">
<mk-widget-container :naked="props.style % 2 === 0" :show-header="false"> <ui-container :naked="props.style % 2 === 0" :show-header="false">
<div class="mkw-analog-clock--body"> <div class="mkw-analog-clock--body">
<mk-analog-clock :dark="$store.state.device.darkmode" :smooth="props.style < 2"/> <mk-analog-clock :dark="$store.state.device.darkmode" :smooth="props.style < 2"/>
</div> </div>
</mk-widget-container> </ui-container>
</div> </div>
</template> </template>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="anltbovirfeutcigvwgmgxipejaeozxi"> <div class="anltbovirfeutcigvwgmgxipejaeozxi">
<mk-widget-container :show-header="false" :naked="props.design == 1"> <ui-container :show-header="false" :naked="props.design == 1">
<div class="anltbovirfeutcigvwgmgxipejaeozxi-body" <div class="anltbovirfeutcigvwgmgxipejaeozxi-body"
:data-found="announcements && announcements.length != 0" :data-found="announcements && announcements.length != 0"
:data-melt="props.design == 1" :data-melt="props.design == 1"
@@ -23,7 +23,7 @@
</p> </p>
<a v-if="announcements.length > 1" @click="next">{{ $t('next') }} &gt;&gt;</a> <a v-if="announcements.length > 1" @click="next">{{ $t('next') }} &gt;&gt;</a>
</div> </div>
</mk-widget-container> </ui-container>
</div> </div>
</template> </template>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="mkw-calendar" :data-special="special" :data-mobile="platform == 'mobile'"> <div class="mkw-calendar" :data-special="special" :data-mobile="platform == 'mobile'">
<mk-widget-container :naked="props.design == 1" :show-header="false"> <ui-container :naked="props.design == 1" :show-header="false">
<div class="mkw-calendar--body"> <div class="mkw-calendar--body">
<div class="calendar" :data-is-holiday="isHoliday"> <div class="calendar" :data-is-holiday="isHoliday">
<p class="month-and-year"> <p class="month-and-year">
@@ -31,7 +31,7 @@
</div> </div>
</div> </div>
</div> </div>
</mk-widget-container> </ui-container>
</div> </div>
</template> </template>

View File

@@ -1,12 +1,12 @@
<template> <template>
<div class="mkw-hashtags"> <div class="mkw-hashtags">
<mk-widget-container :show-header="!props.compact"> <ui-container :show-header="!props.compact">
<template slot="header"><fa icon="hashtag"/>{{ $t('title') }}</template> <template slot="header"><fa icon="hashtag"/>{{ $t('title') }}</template>
<div class="mkw-hashtags--body" :data-mobile="platform == 'mobile'"> <div class="mkw-hashtags--body" :data-mobile="platform == 'mobile'">
<mk-trends/> <mk-trends/>
</div> </div>
</mk-widget-container> </ui-container>
</div> </div>
</template> </template>

View File

@@ -13,6 +13,7 @@ import wSlideshow from './slideshow.vue';
import wTips from './tips.vue'; import wTips from './tips.vue';
import wNav from './nav.vue'; import wNav from './nav.vue';
import wHashtags from './hashtags.vue'; import wHashtags from './hashtags.vue';
import wInstance from './instance.vue';
Vue.component('mkw-analog-clock', wAnalogClock); Vue.component('mkw-analog-clock', wAnalogClock);
Vue.component('mkw-nav', wNav); Vue.component('mkw-nav', wNav);
@@ -27,3 +28,4 @@ Vue.component('mkw-memo', wMemo);
Vue.component('mkw-rss', wRss); Vue.component('mkw-rss', wRss);
Vue.component('mkw-version', wVersion); Vue.component('mkw-version', wVersion);
Vue.component('mkw-hashtags', wHashtags); Vue.component('mkw-hashtags', wHashtags);
Vue.component('mkw-instance', wInstance);

View File

@@ -0,0 +1,14 @@
<template>
<div class="mkw-instance">
<ui-container>
<mk-instance/>
</ui-container>
</div>
</template>
<script lang="ts">
import define from '../../../common/define-widget';
export default define({
name: 'instance'
});
</script>

View File

@@ -1,13 +1,13 @@
<template> <template>
<div class="mkw-memo"> <div class="mkw-memo">
<mk-widget-container :show-header="!props.compact"> <ui-container :show-header="!props.compact">
<template slot="header"><fa :icon="['far', 'sticky-note']"/>{{ $t('title') }}</template> <template slot="header"><fa :icon="['far', 'sticky-note']"/>{{ $t('title') }}</template>
<div class="mkw-memo--body"> <div class="mkw-memo--body">
<textarea v-model="text" :placeholder="$t('placeholder')" @input="onChange"></textarea> <textarea v-model="text" :placeholder="$t('placeholder')" @input="onChange"></textarea>
<button @click="saveMemo" :disabled="!changed">{{ $t('save') }}</button> <button @click="saveMemo" :disabled="!changed">{{ $t('save') }}</button>
</div> </div>
</mk-widget-container> </ui-container>
</div> </div>
</template> </template>

View File

@@ -1,10 +1,10 @@
<template> <template>
<div class="mkw-nav"> <div class="mkw-nav">
<mk-widget-container> <ui-container>
<div class="mkw-nav--body"> <div class="mkw-nav--body">
<mk-nav/> <mk-nav/>
</div> </div>
</mk-widget-container> </ui-container>
</div> </div>
</template> </template>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="mkw-photo-stream" :class="$style.root" :data-melt="props.design == 2"> <div class="mkw-photo-stream" :class="$style.root" :data-melt="props.design == 2">
<mk-widget-container :show-header="props.design == 0" :naked="props.design == 2"> <ui-container :show-header="props.design == 0" :naked="props.design == 2">
<template slot="header"><fa icon="camera"/>{{ $t('title') }}</template> <template slot="header"><fa icon="camera"/>{{ $t('title') }}</template>
<p :class="$style.fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p :class="$style.fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
@@ -13,7 +13,7 @@
></div> ></div>
</div> </div>
<p :class="$style.empty" v-if="!fetching && images.length == 0">{{ $t('no-photos') }}</p> <p :class="$style.empty" v-if="!fetching && images.length == 0">{{ $t('no-photos') }}</p>
</mk-widget-container> </ui-container>
</div> </div>
</template> </template>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="mkw-posts-monitor"> <div class="mkw-posts-monitor">
<mk-widget-container :show-header="props.design == 0" :naked="props.design == 2"> <ui-container :show-header="props.design == 0" :naked="props.design == 2">
<template slot="header"><fa icon="chart-line"/>{{ $t('title') }}</template> <template slot="header"><fa icon="chart-line"/>{{ $t('title') }}</template>
<button slot="func" @click="toggle" :title="$t('toggle')"><fa icon="sort"/></button> <button slot="func" @click="toggle" :title="$t('toggle')"><fa icon="sort"/></button>
@@ -64,7 +64,7 @@
<text x="1" y="5">Fedi</text> <text x="1" y="5">Fedi</text>
</svg> </svg>
</div> </div>
</mk-widget-container> </ui-container>
</div> </div>
</template> </template>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="mkw-rss"> <div class="mkw-rss">
<mk-widget-container :show-header="!props.compact"> <ui-container :show-header="!props.compact">
<template slot="header"><fa icon="rss-square"/>RSS</template> <template slot="header"><fa icon="rss-square"/>RSS</template>
<button slot="func" title="設定" @click="setting"><fa icon="cog"/></button> <button slot="func" title="設定" @click="setting"><fa icon="cog"/></button>
@@ -10,7 +10,7 @@
<a v-for="item in items" :href="item.link" target="_blank">{{ item.title }}</a> <a v-for="item in items" :href="item.link" target="_blank">{{ item.title }}</a>
</div> </div>
</div> </div>
</mk-widget-container> </ui-container>
</div> </div>
</template> </template>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="mkw-server"> <div class="mkw-server">
<mk-widget-container :show-header="props.design == 0" :naked="props.design == 2"> <ui-container :show-header="props.design == 0" :naked="props.design == 2">
<template slot="header"><fa icon="server"/>{{ $t('title') }}</template> <template slot="header"><fa icon="server"/>{{ $t('title') }}</template>
<button slot="func" @click="toggle" :title="$t('toggle')"><fa icon="sort"/></button> <button slot="func" @click="toggle" :title="$t('toggle')"><fa icon="sort"/></button>
@@ -13,7 +13,7 @@
<x-uptimes v-show="props.view == 4" :connection="connection"/> <x-uptimes v-show="props.view == 4" :connection="connection"/>
<x-info v-show="props.view == 5" :connection="connection" :meta="meta"/> <x-info v-show="props.view == 5" :connection="connection" :meta="meta"/>
</template> </template>
</mk-widget-container> </ui-container>
</div> </div>
</template> </template>

View File

@@ -12,19 +12,12 @@ import init from '../init';
import fuckAdBlock from '../common/scripts/fuck-ad-block'; import fuckAdBlock from '../common/scripts/fuck-ad-block';
import composeNotification from '../common/scripts/compose-notification'; import composeNotification from '../common/scripts/compose-notification';
import MkIndex from './views/pages/index.vue'; import MkHome from './views/home/home.vue';
import MkHome from './views/pages/home.vue'; import MkDeck from './views/deck/deck.vue';
import MkDeck from './views/pages/deck/deck.vue';
import MkUser from './views/pages/user/user.vue';
import MkUserFollowingOrFollowers from './views/pages/user-following-or-followers.vue'; import MkUserFollowingOrFollowers from './views/pages/user-following-or-followers.vue';
import MkFavorites from './views/pages/favorites.vue';
import MkSelectDrive from './views/pages/selectdrive.vue'; import MkSelectDrive from './views/pages/selectdrive.vue';
import MkDrive from './views/pages/drive.vue'; import MkDrive from './views/pages/drive.vue';
import MkHomeCustomize from './views/pages/home-customize.vue';
import MkMessagingRoom from './views/pages/messaging-room.vue'; import MkMessagingRoom from './views/pages/messaging-room.vue';
import MkNote from './views/pages/note.vue';
import MkSearch from './views/pages/search.vue';
import MkTag from './views/pages/tag.vue';
import MkReversi from './views/pages/games/reversi.vue'; import MkReversi from './views/pages/games/reversi.vue';
import MkShare from './views/pages/share.vue'; import MkShare from './views/pages/share.vue';
import MkFollow from '../common/views/pages/follow.vue'; import MkFollow from '../common/views/pages/follow.vue';
@@ -36,6 +29,7 @@ import PostFormWindow from './views/components/post-form-window.vue';
import RenoteFormWindow from './views/components/renote-form-window.vue'; import RenoteFormWindow from './views/components/renote-form-window.vue';
import MkChooseFileFromDriveWindow from './views/components/choose-file-from-drive-window.vue'; import MkChooseFileFromDriveWindow from './views/components/choose-file-from-drive-window.vue';
import MkChooseFolderFromDriveWindow from './views/components/choose-folder-from-drive-window.vue'; import MkChooseFolderFromDriveWindow from './views/components/choose-folder-from-drive-window.vue';
import MkHomeTimeline from './views/home/timeline.vue';
import Notification from './views/components/ui-notification.vue'; import Notification from './views/components/ui-notification.vue';
import { url } from '../config'; import { url } from '../config';
@@ -44,7 +38,7 @@ import MiOS from '../mios';
/** /**
* init * init
*/ */
init(async (launch) => { init(async (launch, os) => {
Vue.mixin({ Vue.mixin({
methods: { methods: {
$contextmenu(e, menu, opts?) { $contextmenu(e, menu, opts?) {
@@ -134,31 +128,52 @@ init(async (launch) => {
const router = new VueRouter({ const router = new VueRouter({
mode: 'history', mode: 'history',
routes: [ routes: [
{ path: '/', name: 'index', component: MkIndex }, os.store.getters.isSignedIn && os.store.state.device.deckMode && document.location.pathname === '/'
{ path: '/home', name: 'home', component: MkHome }, ? { path: '/', name: 'index', component: MkDeck, children: [
{ path: '/deck', name: 'deck', component: MkDeck }, { path: '/@:user', name: 'user', component: () => import('./views/deck/deck.user-column.vue').then(m => m.default), children: [
{ path: '/i/customize-home', component: MkHomeCustomize }, { path: '', name: 'user', component: () => import('./views/deck/deck.user-column.home.vue').then(m => m.default) },
{ path: '/i/favorites', component: MkFavorites }, { path: 'following', component: () => import('../common/views/pages/following.vue').then(m => m.default) },
{ path: 'followers', component: () => import('../common/views/pages/followers.vue').then(m => m.default) },
]},
{ path: '/notes/:note', name: 'note', component: () => import('./views/deck/deck.note-column.vue').then(m => m.default) },
{ path: '/search', component: () => import('./views/deck/deck.search-column.vue').then(m => m.default) },
{ path: '/tags/:tag', name: 'tag', component: () => import('./views/deck/deck.hashtag-column.vue').then(m => m.default) },
{ path: '/featured', component: () => import('./views/deck/deck.featured-column.vue').then(m => m.default) },
{ path: '/explore', component: () => import('./views/deck/deck.explore-column.vue').then(m => m.default) },
{ path: '/i/favorites', component: () => import('./views/deck/deck.favorites-column.vue').then(m => m.default) }
]}
: { path: '/', component: MkHome, children: [
{ path: '', name: 'index', component: MkHomeTimeline },
{ path: '/@:user', component: () => import('./views/home/user/index.vue').then(m => m.default), children: [
{ path: '', name: 'user', component: () => import('./views/home/user/user.home.vue').then(m => m.default) },
{ path: 'following', component: () => import('../common/views/pages/following.vue').then(m => m.default) },
{ path: 'followers', component: () => import('../common/views/pages/followers.vue').then(m => m.default) },
]},
{ path: '/notes/:note', name: 'note', component: () => import('./views/home/note.vue').then(m => m.default) },
{ path: '/search', component: () => import('./views/home/search.vue').then(m => m.default) },
{ path: '/tags/:tag', name: 'tag', component: () => import('./views/home/tag.vue').then(m => m.default) },
{ path: '/featured', name: 'featured', component: () => import('./views/home/featured.vue').then(m => m.default) },
{ path: '/explore', name: 'explore', component: () => import('../common/views/pages/explore.vue').then(m => m.default) },
{ path: '/i/favorites', component: () => import('./views/home/favorites.vue').then(m => m.default) },
]},
{ path: '/i/messaging/:user', component: MkMessagingRoom }, { path: '/i/messaging/:user', component: MkMessagingRoom },
{ path: '/i/drive', component: MkDrive }, { path: '/i/drive', component: MkDrive },
{ path: '/i/drive/folder/:folder', component: MkDrive }, { path: '/i/drive/folder/:folder', component: MkDrive },
{ path: '/i/settings', component: MkSettings }, { path: '/i/settings', component: MkSettings },
{ path: '/selectdrive', component: MkSelectDrive }, { path: '/selectdrive', component: MkSelectDrive },
{ path: '/search', component: MkSearch },
{ path: '/tags/:tag', name: 'tag', component: MkTag },
{ path: '/share', component: MkShare }, { path: '/share', component: MkShare },
{ path: '/games/reversi/:game?', component: MkReversi }, { path: '/games/reversi/:game?', component: MkReversi },
{ path: '/@:user', name: 'user', component: MkUser },
{ path: '/@:user/following', name: 'userFollowing', component: MkUserFollowingOrFollowers },
{ path: '/@:user/followers', name: 'userFollowers', component: MkUserFollowingOrFollowers },
{ path: '/notes/:note', name: 'note', component: MkNote },
{ path: '/authorize-follow', component: MkFollow }, { path: '/authorize-follow', component: MkFollow },
{ path: '/deck', redirect: '/' },
{ path: '*', component: MkNotFound } { path: '*', component: MkNotFound }
] ],
scrollBehavior(to, from, savedPosition) {
return { x: 0, y: 0 };
}
}); });
// Launch the app // Launch the app
const [app, os] = launch(router); const [app, _] = launch(router);
if (os.store.getters.isSignedIn) { if (os.store.getters.isSignedIn) {
/** /**

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="mk-activity"> <div class="mk-activity">
<mk-widget-container :show-header="design == 0" :naked="design == 2"> <ui-container :show-header="design == 0" :naked="design == 2">
<template slot="header"><fa icon="chart-bar"/>{{ $t('title') }}</template> <template slot="header"><fa icon="chart-bar"/>{{ $t('title') }}</template>
<button slot="func" :title="$t('toggle')" @click="toggle"><fa icon="sort"/></button> <button slot="func" :title="$t('toggle')" @click="toggle"><fa icon="sort"/></button>
@@ -9,7 +9,7 @@
<x-calendar v-show="view == 0" :data="[].concat(activity)"/> <x-calendar v-show="view == 0" :data="[].concat(activity)"/>
<x-chart v-show="view == 1" :data="[].concat(activity)"/> <x-chart v-show="view == 1" :data="[].concat(activity)"/>
</template> </template>
</mk-widget-container> </ui-container>
</div> </div>
</template> </template>

View File

@@ -160,6 +160,7 @@ export default Vue.extend({
color #222 color #222
> [data-icon] > [data-icon]
box-sizing initial
padding 14px padding 14px
</style> </style>

View File

@@ -1,396 +0,0 @@
<template>
<div class="mk-home" :data-customize="customize">
<div class="customize" v-if="customize">
<router-link to="/"><fa icon="check"/>{{ $t('done') }}</router-link>
<div>
<div class="adder">
<p>{{ $t('add-widget') }}</p>
<select v-model="widgetAdderSelected">
<option value="profile">{{ $t('@.widgets.profile') }}</option>
<option value="analog-clock">{{ $t('@.widgets.analog-clock') }}</option>
<option value="calendar">{{ $t('@.widgets.calendar') }}</option>
<option value="timemachine">{{ $t('@.widgets.timemachine') }}</option>
<option value="activity">{{ $t('@.widgets.activity') }}</option>
<option value="rss">{{ $t('@.widgets.rss') }}</option>
<option value="trends">{{ $t('@.widgets.trends') }}</option>
<option value="photo-stream">{{ $t('@.widgets.photo-stream') }}</option>
<option value="slideshow">{{ $t('@.widgets.slideshow') }}</option>
<option value="version">{{ $t('@.widgets.version') }}</option>
<option value="broadcast">{{ $t('@.widgets.broadcast') }}</option>
<option value="notifications">{{ $t('@.widgets.notifications') }}</option>
<option value="users">{{ $t('@.widgets.users') }}</option>
<option value="polls">{{ $t('@.widgets.polls') }}</option>
<option value="post-form">{{ $t('@.widgets.post-form') }}</option>
<option value="messaging">{{ $t('@.widgets.messaging') }}</option>
<option value="memo">{{ $t('@.widgets.memo') }}</option>
<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option>
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
<option value="server">{{ $t('@.widgets.server') }}</option>
<option value="nav">{{ $t('@.widgets.nav') }}</option>
<option value="tips">{{ $t('@.widgets.tips') }}</option>
</select>
<button @click="addWidget">{{ $t('add') }}</button>
</div>
<div class="trash">
<x-draggable v-model="trash" :options="{ group: 'x' }" @add="onTrash"></x-draggable>
<p>{{ $t('@.trash') }}</p>
</div>
</div>
</div>
<div class="main" :class="{ side: widgets.left.length == 0 || widgets.right.length == 0 }">
<template v-if="customize">
<x-draggable v-for="place in ['left', 'right']"
:list="widgets[place]"
:class="place"
:data-place="place"
:options="{ group: 'x', animation: 150 }"
@sort="onWidgetSort"
:key="place"
>
<div v-for="widget in widgets[place]" class="customize-container" :key="widget.id" @contextmenu.stop.prevent="onWidgetContextmenu(widget.id)">
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="desktop"/>
</div>
</x-draggable>
<div class="main">
<a @click="hint">{{ $t('@.customization-tips.title') }}</a>
<div>
<mk-post-form v-if="$store.state.settings.showPostFormOnTopOfTl"/>
<mk-timeline ref="tl" @loaded="onTlLoaded"/>
</div>
</div>
</template>
<template v-else>
<div v-for="place in ['left', 'right']" :class="place">
<component v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" @chosen="warp" platform="desktop"/>
</div>
<div class="main">
<mk-post-form class="form" v-if="$store.state.settings.showPostFormOnTopOfTl"/>
<mk-timeline class="tl" ref="tl" @loaded="onTlLoaded" v-if="mode == 'timeline'"/>
</div>
</template>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import * as XDraggable from 'vuedraggable';
import * as uuid from 'uuid';
const defaultDesktopHomeWidgets = {
left: [
'profile',
'calendar',
'activity',
'rss',
'hashtags',
'photo-stream',
'version'
],
right: [
'broadcast',
'notifications',
'users',
'polls',
'server',
'nav',
'tips'
]
};
//#region Construct home data
const _defaultDesktopHomeWidgets = [];
for (const widget of defaultDesktopHomeWidgets.left) {
_defaultDesktopHomeWidgets.push({
name: widget,
id: uuid(),
place: 'left',
data: {}
});
}
for (const widget of defaultDesktopHomeWidgets.right) {
_defaultDesktopHomeWidgets.push({
name: widget,
id: uuid(),
place: 'right',
data: {}
});
}
//#endregion
export default Vue.extend({
i18n: i18n('desktop/views/components/home.vue'),
components: {
XDraggable
},
props: {
customize: {
type: Boolean,
default: false
},
mode: {
type: String,
default: 'timeline'
}
},
data() {
return {
connection: null,
widgetAdderSelected: null,
trash: []
};
},
computed: {
home(): any[] {
return this.$store.state.settings.home || [];
},
left(): any[] {
return this.home.filter(w => w.place == 'left');
},
right(): any[] {
return this.home.filter(w => w.place == 'right');
},
widgets(): any {
return {
left: this.left,
right: this.right
};
}
},
created() {
if (this.$store.state.settings.home == null) {
this.$root.api('i/update_home', {
home: _defaultDesktopHomeWidgets
}).then(() => {
this.$store.commit('settings/setHome', _defaultDesktopHomeWidgets);
});
}
},
mounted() {
this.connection = this.$root.stream.useSharedConnection('main');
},
beforeDestroy() {
this.connection.dispose();
},
methods: {
hint() {
this.$root.dialog({
title: this.$t('@.customization-tips.title'),
text: this.$t('@.customization-tips.paragraph')
});
},
onTlLoaded() {
this.$emit('loaded');
},
onWidgetContextmenu(widgetId) {
const w = (this.$refs[widgetId] as any)[0];
if (w.func) w.func();
},
onWidgetSort() {
this.saveHome();
},
onTrash(evt) {
this.saveHome();
},
addWidget() {
this.$store.dispatch('settings/addHomeWidget', {
name: this.widgetAdderSelected,
id: uuid(),
place: 'left',
data: {}
});
},
saveHome() {
const left = this.widgets.left;
const right = this.widgets.right;
this.$store.commit('settings/setHome', left.concat(right));
for (const w of left) w.place = 'left';
for (const w of right) w.place = 'right';
this.$root.api('i/update_home', {
home: this.home
});
},
warp(date) {
(this.$refs.tl as any).warp(date);
},
focus() {
(this.$refs.tl as any).focus();
}
}
});
</script>
<style lang="stylus" scoped>
.mk-home
display block
&[data-customize]
padding-top 48px
background-image url('/assets/desktop/grid.svg')
> .main > .main
> a
display block
margin-bottom 8px
text-align center
> div
cursor not-allowed !important
> *
pointer-events none
&:not([data-customize])
> .main > *:empty
display none
> .customize
position fixed
z-index 1000
top 0
left 0
width 100%
height 48px
color var(--text)
background var(--desktopHeaderBg)
box-shadow 0 1px 1px rgba(#000, 0.075)
> a
display block
position absolute
z-index 1001
top 0
right 0
padding 0 16px
line-height 48px
text-decoration none
color var(--primaryForeground)
background var(--primary)
transition background 0.1s ease
&:hover
background var(--primaryLighten10)
&:active
background var(--primaryDarken10)
transition background 0s ease
> [data-icon]
margin-right 8px
> div
display flex
margin 0 auto
max-width 1220px - 32px
> div
width 50%
&.adder
> p
display inline
line-height 48px
&.trash
border-left solid 1px var(--faceDivider)
> div
width 100%
height 100%
> p
position absolute
top 0
left 0
width 100%
line-height 48px
margin 0
text-align center
pointer-events none
> .main
display flex
justify-content center
margin 0 auto
max-width 1240px
> *
.customize-container
cursor move
border-radius 6px
&:hover
box-shadow 0 0 8px rgba(64, 120, 200, 0.3)
> *
pointer-events none
> .main
padding 16px
width calc(100% - 280px * 2)
order 2
> .form
margin-bottom 16px
box-shadow var(--shadow)
border-radius var(--round)
&.side
> .main
width calc(100% - 280px)
max-width 680px
> *:not(.main)
width 280px
padding 16px 0 16px 0
> *:not(:last-child)
margin-bottom 16px
> .left
padding-left 16px
order 1
> .right
padding-right 16px
order 3
&.side
@media (max-width 1000px)
> *:not(.main)
display none
> .main
width 100%
max-width 700px
margin 0 auto
&:not(.side)
@media (max-width 1200px)
> *:not(.main)
display none
> .main
width 100%
max-width 700px
margin 0 auto
</style>

View File

@@ -2,8 +2,6 @@ import Vue from 'vue';
import ui from './ui.vue'; import ui from './ui.vue';
import uiNotification from './ui-notification.vue'; import uiNotification from './ui-notification.vue';
import home from './home.vue';
import timeline from './timeline.vue';
import notes from './notes.vue'; import notes from './notes.vue';
import subNoteContent from './sub-note-content.vue'; import subNoteContent from './sub-note-content.vue';
import window from './window.vue'; import window from './window.vue';
@@ -20,12 +18,10 @@ import activity from './activity.vue';
import friendsMaker from './friends-maker.vue'; import friendsMaker from './friends-maker.vue';
import userCard from './user-card.vue'; import userCard from './user-card.vue';
import userListTimeline from './user-list-timeline.vue'; import userListTimeline from './user-list-timeline.vue';
import widgetContainer from './widget-container.vue'; import uiContainer from './ui-container.vue';
Vue.component('mk-ui', ui); Vue.component('mk-ui', ui);
Vue.component('mk-ui-notification', uiNotification); Vue.component('mk-ui-notification', uiNotification);
Vue.component('mk-home', home);
Vue.component('mk-timeline', timeline);
Vue.component('mk-notes', notes); Vue.component('mk-notes', notes);
Vue.component('mk-sub-note-content', subNoteContent); Vue.component('mk-sub-note-content', subNoteContent);
Vue.component('mk-window', window); Vue.component('mk-window', window);
@@ -42,4 +38,4 @@ Vue.component('mk-activity', activity);
Vue.component('mk-friends-maker', friendsMaker); Vue.component('mk-friends-maker', friendsMaker);
Vue.component('mk-user-card', userCard); Vue.component('mk-user-card', userCard);
Vue.component('mk-user-list-timeline', userListTimeline); Vue.component('mk-user-list-timeline', userListTimeline);
Vue.component('mk-widget-container', widgetContainer); Vue.component('ui-container', uiContainer);

View File

@@ -31,9 +31,6 @@
<ui-switch v-model="autoPopout">{{ $t('auto-popout') }} <ui-switch v-model="autoPopout">{{ $t('auto-popout') }}
<span slot="desc">{{ $t('auto-popout-desc') }}</span> <span slot="desc">{{ $t('auto-popout-desc') }}</span>
</ui-switch> </ui-switch>
<ui-switch v-model="deckNav">{{ $t('deck-nav') }}
<span slot="desc">{{ $t('deck-nav-desc') }}</span>
</ui-switch>
<ui-switch v-model="keepCw">{{ $t('keep-cw') }} <ui-switch v-model="keepCw">{{ $t('keep-cw') }}
<span slot="desc">{{ $t('keep-cw-desc') }}</span> <span slot="desc">{{ $t('keep-cw-desc') }}</span>
</ui-switch> </ui-switch>
@@ -89,9 +86,6 @@
<ui-radio v-model="navbar" value="left">{{ $t('navbar-position-left') }}</ui-radio> <ui-radio v-model="navbar" value="left">{{ $t('navbar-position-left') }}</ui-radio>
<ui-radio v-model="navbar" value="right">{{ $t('navbar-position-right') }}</ui-radio> <ui-radio v-model="navbar" value="right">{{ $t('navbar-position-right') }}</ui-radio>
</section> </section>
<section>
<ui-switch v-model="deckDefault">{{ $t('deck-default') }}</ui-switch>
</section>
<section> <section>
<ui-switch v-model="darkmode">{{ $t('dark-mode') }}</ui-switch> <ui-switch v-model="darkmode">{{ $t('dark-mode') }}</ui-switch>
<ui-switch v-model="useShadow">{{ $t('use-shadow') }}</ui-switch> <ui-switch v-model="useShadow">{{ $t('use-shadow') }}</ui-switch>
@@ -337,11 +331,6 @@ export default Vue.extend({
set(value) { this.$store.commit('device/set', { key: 'autoPopout', value }); } set(value) { this.$store.commit('device/set', { key: 'autoPopout', value }); }
}, },
deckNav: {
get() { return this.$store.state.settings.deckNav; },
set(value) { this.$store.commit('settings/set', { key: 'deckNav', value }); }
},
keepCw: { keepCw: {
get() { return this.$store.state.settings.keepCw; }, get() { return this.$store.state.settings.keepCw; },
set(value) { this.$store.commit('settings/set', { key: 'keepCw', value }); } set(value) { this.$store.commit('settings/set', { key: 'keepCw', value }); }
@@ -367,11 +356,6 @@ export default Vue.extend({
set(value) { this.$store.commit('device/set', { key: 'deckColumnWidth', value }); } set(value) { this.$store.commit('device/set', { key: 'deckColumnWidth', value }); }
}, },
deckDefault: {
get() { return this.$store.state.device.deckDefault; },
set(value) { this.$store.commit('device/set', { key: 'deckDefault', value }); }
},
enableSounds: { enableSounds: {
get() { return this.$store.state.device.enableSounds; }, get() { return this.$store.state.device.enableSounds; },
set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); } set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); }
@@ -534,8 +518,7 @@ export default Vue.extend({
}, },
methods: { methods: {
customizeHome() { customizeHome() {
this.$router.push('/i/customize-home'); location.href = '/?customize';
this.$emit('done');
}, },
updateWallpaper() { updateWallpaper() {
this.$chooseDriveFile({ this.$chooseDriveFile({

View File

@@ -1,260 +0,0 @@
<template>
<div class="mk-timeline">
<header>
<span :data-active="src == 'home'" @click="src = 'home'"><fa icon="home"/> {{ $t('home') }}</span>
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline"><fa :icon="['far', 'comments']"/> {{ $t('local') }}</span>
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline"><fa icon="share-alt"/> {{ $t('hybrid') }}</span>
<span :data-active="src == 'global'" @click="src = 'global'" v-if="enableGlobalTimeline"><fa icon="globe"/> {{ $t('global') }}</span>
<span :data-active="src == 'tag'" @click="src = 'tag'" v-if="tagTl"><fa icon="hashtag"/> {{ tagTl.title }}</span>
<span :data-active="src == 'list'" @click="src = 'list'" v-if="list"><fa icon="list"/> {{ list.title }}</span>
<div class="buttons">
<button :data-active="src == 'mentions'" @click="src = 'mentions'" :title="$t('mentions')"><fa icon="at"/><i class="badge" v-if="$store.state.i.hasUnreadMentions"><fa icon="circle"/></i></button>
<button :data-active="src == 'messages'" @click="src = 'messages'" :title="$t('messages')"><fa :icon="['far', 'envelope']"/><i class="badge" v-if="$store.state.i.hasUnreadSpecifiedNotes"><fa icon="circle"/></i></button>
<button @click="chooseTag" :title="$t('hashtag')" ref="tagButton"><fa icon="hashtag"/></button>
<button @click="chooseList" :title="$t('list')" ref="listButton"><fa icon="list"/></button>
</div>
</header>
<x-core v-if="src == 'home'" ref="tl" key="home" src="home"/>
<x-core v-if="src == 'local'" ref="tl" key="local" src="local"/>
<x-core v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
<x-core v-if="src == 'global'" ref="tl" key="global" src="global"/>
<x-core v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
<x-core v-if="src == 'messages'" ref="tl" key="messages" src="messages"/>
<x-core v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import XCore from './timeline.core.vue';
import Menu from '../../../common/views/components/menu.vue';
import MkSettingsWindow from './settings-window.vue';
export default Vue.extend({
i18n: i18n('desktop/views/components/timeline.vue'),
components: {
XCore
},
data() {
return {
src: 'home',
list: null,
tagTl: null,
enableLocalTimeline: false,
enableGlobalTimeline: false,
};
},
watch: {
src() {
this.saveSrc();
},
list(x) {
this.saveSrc();
if (x != null) this.tagTl = null;
},
tagTl(x) {
this.saveSrc();
if (x != null) this.list = null;
}
},
created() {
this.$root.getMeta().then(meta => {
this.enableLocalTimeline = !meta.disableLocalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin;
this.enableGlobalTimeline = !meta.disableGlobalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin;
});
if (this.$store.state.device.tl) {
this.src = this.$store.state.device.tl.src;
if (this.src == 'list') {
this.list = this.$store.state.device.tl.arg;
} else if (this.src == 'tag') {
this.tagTl = this.$store.state.device.tl.arg;
}
} else if (this.$store.state.i.followingCount == 0) {
this.src = 'hybrid';
}
},
mounted() {
(this.$refs.tl as any).$once('loaded', () => {
this.$emit('loaded');
});
},
methods: {
saveSrc() {
this.$store.commit('device/setTl', {
src: this.src,
arg: this.src == 'list' ? this.list : this.tagTl
});
},
focus() {
(this.$refs.tl as any).focus();
},
warp(date) {
(this.$refs.tl as any).warp(date);
},
async chooseList() {
const lists = await this.$root.api('users/lists/list');
let menu = [{
icon: 'plus',
text: this.$t('add-list'),
action: () => {
this.$root.dialog({
title: this.$t('list-name'),
input: true
}).then(async ({ canceled, result: title }) => {
if (canceled) return;
const list = await this.$root.api('users/lists/create', {
title
});
this.list = list;
this.src = 'list';
});
}
}];
if (lists.length > 0) {
menu.push(null);
}
menu = menu.concat(lists.map(list => ({
icon: 'list',
text: list.title,
action: () => {
this.list = list;
this.src = 'list';
}
})));
this.$root.new(Menu, {
source: this.$refs.listButton,
items: menu
});
},
chooseTag() {
let menu = [{
icon: 'plus',
text: this.$t('add-tag-timeline'),
action: () => {
this.$root.new(MkSettingsWindow, {
initialPage: 'hashtags'
});
}
}];
if (this.$store.state.settings.tagTimelines.length > 0) {
menu.push(null);
}
menu = menu.concat(this.$store.state.settings.tagTimelines.map(t => ({
icon: 'hashtag',
text: t.title,
action: () => {
this.tagTl = t;
this.src = 'tag';
}
})));
this.$root.new(Menu, {
source: this.$refs.tagButton,
items: menu
});
}
}
});
</script>
<style lang="stylus" scoped>
.mk-timeline
background var(--face)
box-shadow var(--shadow)
border-radius var(--round)
overflow hidden
> header
padding 0 8px
z-index 10
background var(--faceHeader)
box-shadow 0 var(--lineWidth) var(--desktopTimelineHeaderShadow)
> .buttons
position absolute
z-index 2
top 0
right 0
padding-right 8px
> button
padding 0 8px
font-size 0.9em
line-height 42px
color var(--faceTextButton)
> .badge
position absolute
top -4px
right 4px
font-size 10px
color var(--notificationIndicator)
&:hover
color var(--faceTextButtonHover)
&[data-active]
color var(--primary)
cursor default
&:before
content ""
display block
position absolute
bottom 0
left 0
width 100%
height 2px
background var(--primary)
> span
display inline-block
padding 0 10px
line-height 42px
font-size 12px
user-select none
&[data-active]
color var(--primary)
cursor default
font-weight bold
&:before
content ""
display block
position absolute
bottom 0
left -8px
width calc(100% + 16px)
height 2px
background var(--primary)
&:not([data-active])
color var(--desktopTimelineSrc)
cursor pointer
&:hover
color var(--desktopTimelineSrcHover)
</style>

View File

@@ -0,0 +1,117 @@
<template>
<div class="kedshtep" :class="{ naked, inDeck }">
<header v-if="showHeader">
<div class="title"><slot name="header"></slot></div>
<slot name="func"></slot>
<button v-if="bodyTogglable" @click="() => showBody = !showBody">
<template v-if="showBody"><fa icon="angle-up"/></template>
<template v-else><fa icon="angle-down"/></template>
</button>
</header>
<div v-show="showBody">
<slot></slot>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
showHeader: {
type: Boolean,
default: true
},
naked: {
type: Boolean,
default: false
},
bodyTogglable: {
type: Boolean,
default: false
},
},
inject: {
inDeck: {
default: false
}
},
data() {
return {
showBody: true
};
}
});
</script>
<style lang="stylus" scoped>
.kedshtep
overflow hidden
&:not(.inDeck)
background var(--face)
box-shadow var(--shadow)
border-radius var(--round)
& + .kedshtep
margin-top 16px
&.naked
background transparent !important
box-shadow none !important
> header
background var(--faceHeader)
> .title
z-index 1
margin 0
padding 0 16px
line-height 42px
font-size 0.9em
font-weight bold
color var(--faceHeaderText)
box-shadow 0 var(--lineWidth) rgba(#000, 0.07)
> [data-icon]
margin-right 6px
&:empty
display none
> button
position absolute
z-index 2
top 0
right 0
padding 0
width 42px
font-size 0.9em
line-height 42px
color var(--faceTextButton)
&:hover
color var(--faceTextButtonHover)
&:active
color var(--faceTextButtonActive)
&.inDeck
background var(--face)
> header
margin 0
padding 8px 16px
font-size 12px
color var(--text)
background var(--deckColumnBg)
> button
position absolute
top 0
right 8px
padding 8px 6px
font-size 14px
color var(--text)
</style>

View File

@@ -44,13 +44,6 @@
</li> </li>
</ul> </ul>
<ul> <ul>
<li>
<router-link to="/i/customize-home">
<i><fa icon="wrench"/></i>
<span>{{ $t('customize') }}</span>
<i><fa icon="angle-right"/></i>
</router-link>
</li>
<li> <li>
<router-link to="/i/settings"> <router-link to="/i/settings">
<i><fa icon="cog"/></i> <i><fa icon="cog"/></i>
@@ -67,6 +60,13 @@
</li> </li>
</ul> </ul>
<ul> <ul>
<li @click="toggleDeckMode">
<p>
<span>{{ $t('@.deck') }}</span>
<template v-if="$store.state.device.deckMode"><i><fa :icon="faHome"/></i></template>
<template v-else><i><fa :icon="faColumns"/></i></template>
</p>
</li>
<li @click="dark"> <li @click="dark">
<p> <p>
<span>{{ $t('dark') }}</span> <span>{{ $t('dark') }}</span>
@@ -97,12 +97,14 @@ import MkFollowRequestsWindow from './received-follow-requests-window.vue';
import MkSettingsWindow from './settings-window.vue'; import MkSettingsWindow from './settings-window.vue';
import MkDriveWindow from './drive-window.vue'; import MkDriveWindow from './drive-window.vue';
import contains from '../../../common/scripts/contains'; import contains from '../../../common/scripts/contains';
import { faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/components/ui.header.account.vue'), i18n: i18n('desktop/views/components/ui.header.account.vue'),
data() { data() {
return { return {
isOpen: false isOpen: false,
faHome, faColumns
}; };
}, },
computed: { computed: {
@@ -161,7 +163,11 @@ export default Vue.extend({
key: 'darkmode', key: 'darkmode',
value: !this.$store.state.device.darkmode value: !this.$store.state.device.darkmode
}); });
} },
toggleDeckMode() {
this.$store.commit('device/set', { key: 'deckMode', value: !this.$store.state.device.deckMode });
location.reload();
},
} }
}); });
</script> </script>

View File

@@ -0,0 +1,68 @@
<template>
<div class="toltmoik">
<button @click="open()" :title="$t('@.messaging')">
<i class="bell"><fa :icon="faComments"/></i>
<i class="circle" v-if="hasUnreadMessagingMessage"><fa icon="circle"/></i>
</button>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import MkMessagingWindow from './messaging-window.vue';
import { faComments } from '@fortawesome/free-regular-svg-icons';
export default Vue.extend({
i18n: i18n(),
data() {
return {
faComments
};
},
computed: {
hasUnreadMessagingMessage(): boolean {
return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadMessagingMessage;
}
},
methods: {
open() {
this.$root.new(MkMessagingWindow);
},
}
});
</script>
<style lang="stylus" scoped>
.toltmoik
> button
display block
margin 0
padding 0
width 32px
color var(--desktopHeaderFg)
border none
background transparent
cursor pointer
*
pointer-events none
&:hover
&[data-active='true']
color var(--desktopHeaderHoverFg)
> i.bell
font-size 1.2em
line-height 48px
> i.circle
margin-left -5px
vertical-align super
font-size 10px
color var(--notificationIndicator)
</style>

View File

@@ -1,29 +1,14 @@
<template> <template>
<div class="nav"> <div class="nav">
<ul> <ul>
<template v-if="$store.getters.isSignedIn"> <li v-if="!$store.state.device.deckMode" class="timeline" :class="{ active: $route.name == 'index' }" @click="goToTop">
<template v-if="$store.state.device.deckDefault"> <router-link to="/"><fa icon="home"/><p>{{ $t('@.timeline') }}</p></router-link>
<li class="deck" :class="{ active: $route.name == 'deck' || $route.name == 'index' }" @click="goToTop">
<router-link to="/"><fa icon="columns"/><p>{{ $t('deck') }}</p></router-link>
</li> </li>
<li class="home" :class="{ active: $route.name == 'home' }" @click="goToTop"> <li class="featured" :class="{ active: $route.name == 'featured' }">
<router-link to="/home"><fa icon="home"/><p>{{ $t('home') }}</p></router-link> <router-link to="/featured"><fa :icon="faNewspaper"/><p>{{ $t('@.featured-notes') }}</p></router-link>
</li> </li>
</template> <li class="explore" :class="{ active: $route.name == 'explore' }">
<template v-else> <router-link to="/explore"><fa :icon="faHashtag"/><p>{{ $t('@.explore') }}</p></router-link>
<li class="home" :class="{ active: $route.name == 'home' || $route.name == 'index' }" @click="goToTop">
<router-link to="/"><fa icon="home"/><p>{{ $t('home') }}</p></router-link>
</li>
<li class="deck" :class="{ active: $route.name == 'deck' }" @click="goToTop">
<router-link to="/deck"><fa icon="columns"/><p>{{ $t('deck') }}</p></router-link>
</li>
</template>
<li class="messaging">
<a @click="messaging">
<fa icon="comments"/>
<p>{{ $t('@.messaging') }}</p>
<template v-if="hasUnreadMessagingMessage"><fa icon="circle"/></template>
</a>
</li> </li>
<li class="game"> <li class="game">
<a @click="game"> <a @click="game">
@@ -32,7 +17,6 @@
<template v-if="hasGameInvitations"><fa icon="circle"/></template> <template v-if="hasGameInvitations"><fa icon="circle"/></template>
</a> </a>
</li> </li>
</template>
</ul> </ul>
</div> </div>
</template> </template>
@@ -40,22 +24,18 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import MkMessagingWindow from './messaging-window.vue';
import MkGameWindow from './game-window.vue'; import MkGameWindow from './game-window.vue';
import { faNewspaper, faHashtag } from '@fortawesome/free-solid-svg-icons';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/components/ui.header.nav.vue'), i18n: i18n('desktop/views/components/ui.header.nav.vue'),
data() { data() {
return { return {
hasGameInvitations: false, hasGameInvitations: false,
connection: null connection: null,
faNewspaper, faHashtag
}; };
}, },
computed: {
hasUnreadMessagingMessage(): boolean {
return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadMessagingMessage;
}
},
mounted() { mounted() {
if (this.$store.getters.isSignedIn) { if (this.$store.getters.isSignedIn) {
this.connection = this.$root.stream.useSharedConnection('main'); this.connection = this.$root.stream.useSharedConnection('main');
@@ -78,10 +58,6 @@ export default Vue.extend({
this.hasGameInvitations = false; this.hasGameInvitations = false;
}, },
messaging() {
this.$root.new(MkMessagingWindow);
},
game() { game() {
this.$root.new(MkGameWindow); this.$root.new(MkGameWindow);
}, },
@@ -126,7 +102,7 @@ export default Vue.extend({
display inline-block display inline-block
z-index 1 z-index 1
height 100% height 100%
padding 0 24px padding 0 20px
font-size 13px font-size 13px
font-variant small-caps font-variant small-caps
color var(--desktopHeaderFg) color var(--desktopHeaderFg)

View File

@@ -16,9 +16,10 @@
<div class="right"> <div class="right">
<x-search/> <x-search/>
<x-account v-if="$store.getters.isSignedIn"/> <x-account v-if="$store.getters.isSignedIn"/>
<x-messaging v-if="$store.getters.isSignedIn"/>
<x-notifications v-if="$store.getters.isSignedIn"/> <x-notifications v-if="$store.getters.isSignedIn"/>
<x-post v-if="$store.getters.isSignedIn"/> <x-post v-if="$store.getters.isSignedIn"/>
<x-clock v-if="$store.state.settings.showClockOnHeader"/> <x-clock v-if="$store.state.settings.showClockOnHeader" class="clock"/>
</div> </div>
</div> </div>
</div> </div>
@@ -37,6 +38,7 @@ import XAccount from './ui.header.account.vue';
import XNotifications from './ui.header.notifications.vue'; import XNotifications from './ui.header.notifications.vue';
import XPost from './ui.header.post.vue'; import XPost from './ui.header.post.vue';
import XClock from './ui.header.clock.vue'; import XClock from './ui.header.clock.vue';
import XMessaging from './ui.header.messaging.vue';
export default Vue.extend({ export default Vue.extend({
i18n: i18n(), i18n: i18n(),
@@ -45,6 +47,7 @@ export default Vue.extend({
XSearch, XSearch,
XAccount, XAccount,
XNotifications, XNotifications,
XMessaging,
XPost, XPost,
XClock XClock
}, },
@@ -116,7 +119,7 @@ export default Vue.extend({
> .container > .container
display flex display flex
width 100% width 100%
max-width 1300px max-width 1208px
margin 0 auto margin 0 auto
> * > *
@@ -152,7 +155,7 @@ export default Vue.extend({
vertical-align top vertical-align top
@media (max-width 1100px) @media (max-width 1100px)
> .mk-ui-header-search > .clock
display none display none
</style> </style>

View File

@@ -6,24 +6,16 @@
</div> </div>
<div class="nav" v-if="$store.getters.isSignedIn"> <div class="nav" v-if="$store.getters.isSignedIn">
<template v-if="$store.state.device.deckDefault"> <template v-if="!$store.state.device.deckMode">
<div class="deck" :class="{ active: $route.name == 'deck' || $route.name == 'index' }" @click="goToTop"> <div class="home" :class="{ active: $route.name == 'index' }" @click="goToTop">
<router-link to="/"><fa icon="columns"/></router-link>
</div>
<div class="home" :class="{ active: $route.name == 'home' }" @click="goToTop">
<router-link to="/home"><fa icon="home"/></router-link>
</div>
</template>
<template v-else>
<div class="home" :class="{ active: $route.name == 'home' || $route.name == 'index' }" @click="goToTop">
<router-link to="/"><fa icon="home"/></router-link> <router-link to="/"><fa icon="home"/></router-link>
</div> </div>
<div class="deck" :class="{ active: $route.name == 'deck' }" @click="goToTop">
<router-link to="/deck"><fa icon="columns"/></router-link>
</div>
</template> </template>
<div class="messaging"> <div class="featured" :class="{ active: $route.name == 'featured' }">
<a @click="messaging"><fa icon="comments"/><template v-if="hasUnreadMessagingMessage"><fa icon="circle"/></template></a> <router-link to="/featured"><fa :icon="faNewspaper"/></router-link>
</div>
<div class="explore" :class="{ active: $route.name == 'explore' }">
<router-link to="/explore"><fa :icon="faHashtag"/></router-link>
</div> </div>
<div class="game"> <div class="game">
<a @click="game"><fa icon="gamepad"/><template v-if="hasGameInvitations"><fa icon="circle"/></template></a> <a @click="game"><fa icon="gamepad"/><template v-if="hasGameInvitations"><fa icon="circle"/></template></a>
@@ -37,17 +29,12 @@
<div ref="notificationsButton" :class="{ active: showNotifications }"> <div ref="notificationsButton" :class="{ active: showNotifications }">
<a @click="notifications"><fa :icon="['far', 'bell']"/></a> <a @click="notifications"><fa :icon="['far', 'bell']"/></a>
</div> </div>
<div class="messaging">
<a @click="messaging"><fa icon="comments"/><template v-if="hasUnreadMessagingMessage"><fa icon="circle"/></template></a>
</div>
<div> <div>
<a @click="settings"><fa icon="cog"/></a> <a @click="settings"><fa icon="cog"/></a>
</div> </div>
</div>
<div class="account">
<router-link :to="`/@${ $store.state.i.username }`">
<mk-avatar class="avatar" :user="$store.state.i"/>
</router-link>
<div class="nav menu">
<div class="signout"> <div class="signout">
<a @click="signout"><fa icon="power-off"/></a> <a @click="signout"><fa icon="power-off"/></a>
</div> </div>
@@ -57,10 +44,19 @@
<div v-if="($store.state.i.isLocked || $store.state.i.carefulBot)"> <div v-if="($store.state.i.isLocked || $store.state.i.carefulBot)">
<a @click="followRequests"><fa :icon="['far', 'envelope']"/><i v-if="$store.state.i.pendingReceivedFollowRequestsCount">{{ $store.state.i.pendingReceivedFollowRequestsCount }}</i></a> <a @click="followRequests"><fa :icon="['far', 'envelope']"/><i v-if="$store.state.i.pendingReceivedFollowRequestsCount">{{ $store.state.i.pendingReceivedFollowRequestsCount }}</i></a>
</div> </div>
<div class="account">
<router-link :to="`/@${ $store.state.i.username }`">
<mk-avatar class="avatar" :user="$store.state.i"/>
</router-link>
</div> </div>
<div>
<template v-if="$store.state.device.deckMode">
<a @click="toggleDeckMode(false)"><fa icon="home"/></a>
</template>
<template v-else>
<a @click="toggleDeckMode(true)"><fa icon="columns"/></a>
</template>
</div> </div>
<div class="nav dark">
<div> <div>
<a @click="dark"><template v-if="$store.state.device.darkmode"><fa icon="moon"/></template><template v-else><fa :icon="['far', 'moon']"/></template></a> <a @click="dark"><template v-if="$store.state.device.darkmode"><fa icon="moon"/></template><template v-else><fa :icon="['far', 'moon']"/></template></a>
</div> </div>
@@ -85,6 +81,7 @@ import MkDriveWindow from './drive-window.vue';
import MkMessagingWindow from './messaging-window.vue'; import MkMessagingWindow from './messaging-window.vue';
import MkGameWindow from './game-window.vue'; import MkGameWindow from './game-window.vue';
import contains from '../../../common/scripts/contains'; import contains from '../../../common/scripts/contains';
import { faNewspaper, faHashtag } from '@fortawesome/free-solid-svg-icons';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/components/ui.sidebar.vue'), i18n: i18n('desktop/views/components/ui.sidebar.vue'),
@@ -92,7 +89,8 @@ export default Vue.extend({
return { return {
hasGameInvitations: false, hasGameInvitations: false,
connection: null, connection: null,
showNotifications: false showNotifications: false,
faNewspaper, faHashtag
}; };
}, },
@@ -122,6 +120,11 @@ export default Vue.extend({
}, },
methods: { methods: {
toggleDeckMode(deck) {
this.$store.commit('device/set', { key: 'deckMode', value: deck });
location.reload();
},
onReversiInvited() { onReversiInvited() {
this.hasGameInvitations = true; this.hasGameInvitations = true;
}, },
@@ -273,29 +276,15 @@ export default Vue.extend({
> .nav.bottom > .nav.bottom
position absolute position absolute
bottom 128px bottom 0
left 0 left 0
> .account > .account
position absolute
bottom 64px
left 0
width $width width $width
height $width height $width
padding 14px padding 14px
> .menu > *
display none
position absolute
bottom 64px
left 0
background var(--desktopHeaderBg)
&:hover
> .menu
display block
> *:not(.menu)
display block display block
width 100% width 100%
height 100% height 100%
@@ -305,13 +294,6 @@ export default Vue.extend({
width 100% width 100%
height 100% height 100%
> .dark
position absolute
bottom 0
left 0
width $width
height $width
> .notifications > .notifications
position fixed position fixed
top 0 top 0

View File

@@ -41,7 +41,6 @@ export default Vue.extend({
height 280px height 280px
overflow hidden overflow hidden
font-size 13px font-size 13px
text-align center
background $bg background $bg
box-shadow 0 2px 4px rgba(0, 0, 0, 0.1) box-shadow 0 2px 4px rgba(0, 0, 0, 0.1)
color var(--faceText) color var(--faceText)
@@ -54,7 +53,7 @@ export default Vue.extend({
> .avatar > .avatar
display block display block
margin -40px auto 0 auto margin -40px 0 0 16px
width 80px width 80px
height 80px height 80px
border-radius 100% border-radius 100%
@@ -67,6 +66,7 @@ export default Vue.extend({
> .body > .body
padding 0px 24px padding 0px 24px
margin-top -40px
> .name > .name
font-size 120% font-size 120%

View File

@@ -1,74 +0,0 @@
<template>
<div class="mk-widget-container" :class="{ naked }">
<header v-if="showHeader">
<div class="title"><slot name="header"></slot></div>
<slot name="func"></slot>
</header>
<slot></slot>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
showHeader: {
type: Boolean,
default: true
},
naked: {
type: Boolean,
default: false
}
}
});
</script>
<style lang="stylus" scoped>
.mk-widget-container
background var(--face)
box-shadow var(--shadow)
border-radius var(--round)
overflow hidden
&.naked
background transparent !important
box-shadow none !important
> header
background var(--faceHeader)
> .title
z-index 1
margin 0
padding 0 16px
line-height 42px
font-size 0.9em
font-weight bold
color var(--faceHeaderText)
box-shadow 0 var(--lineWidth) rgba(#000, 0.07)
> [data-icon]
margin-right 6px
&:empty
display none
> button
position absolute
z-index 2
top 0
right 0
padding 0
width 42px
font-size 0.9em
line-height 42px
color var(--faceTextButton)
&:hover
color var(--faceTextButtonHover)
&:active
color var(--faceTextButtonActive)
</style>

View File

@@ -27,9 +27,9 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../../i18n'; import i18n from '../../../i18n';
import Menu from '../../../../common/views/components/menu.vue'; import Menu from '../../../common/views/components/menu.vue';
import { countIf } from '../../../../../../prelude/array'; import { countIf } from '../../../../../prelude/array';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('deck'), i18n: i18n('deck'),
@@ -65,6 +65,16 @@ export default Vue.extend({
} }
}, },
data() {
return {
count: 0,
active: true,
dragging: false,
draghover: false,
dropready: false
};
},
computed: { computed: {
isTemporaryColumn(): boolean { isTemporaryColumn(): boolean {
return this.column == null; return this.column == null;
@@ -84,16 +94,6 @@ export default Vue.extend({
getColumnVm: { from: 'getColumnVm' } getColumnVm: { from: 'getColumnVm' }
}, },
data() {
return {
count: 0,
active: true,
dragging: false,
draghover: false,
dropready: false
};
},
watch: { watch: {
active(v) { active(v) {
if (v && this.isScrollTop()) { if (v && this.isScrollTop()) {
@@ -109,7 +109,8 @@ export default Vue.extend({
return { return {
column: this, column: this,
isScrollTop: this.isScrollTop, isScrollTop: this.isScrollTop,
count: v => this.count = v count: v => this.count = v,
inDeck: !this.naked
}; };
}, },
@@ -245,10 +246,7 @@ export default Vue.extend({
}, },
close() { close() {
this.$store.commit('device/set', { this.$router.push('/');
key: 'deckTemporaryColumn',
value: null
});
}, },
goTop() { goTop() {

View File

@@ -8,7 +8,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../../i18n'; import i18n from '../../../i18n';
import XColumn from './deck.column.vue'; import XColumn from './deck.column.vue';
import XDirect from './deck.direct.vue'; import XDirect from './deck.direct.vue';

View File

@@ -0,0 +1,34 @@
<template>
<x-column>
<span slot="header">
<fa :icon="faHashtag"/>{{ $t('@.explore') }}
</span>
<div>
<x-explore/>
</div>
</x-column>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import XColumn from './deck.column.vue';
import XExplore from '../../../common/views/pages/explore.vue';
import { faHashtag } from '@fortawesome/free-solid-svg-icons';
export default Vue.extend({
i18n: i18n(),
components: {
XColumn,
XExplore,
},
data() {
return {
faHashtag
};
}
});
</script>

View File

@@ -0,0 +1,88 @@
<template>
<x-column>
<span slot="header">
<fa :icon="['fa', 'star']"/>{{ $t('favorites') }}
</span>
<div>
<x-notes ref="timeline" :more="existMore ? more : null"/>
</div>
</x-column>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import XColumn from './deck.column.vue';
import XNotes from './deck.notes.vue';
const fetchLimit = 10;
export default Vue.extend({
i18n: i18n(),
components: {
XColumn,
XNotes,
},
data() {
return {
fetching: true,
moreFetching: false,
existMore: false,
};
},
mounted() {
this.fetch();
},
methods: {
fetch() {
this.fetching = true;
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
this.$root.api('i/favorites', {
limit: fetchLimit + 1,
}).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
this.existMore = true;
}
res(notes.map(x => x.note));
this.fetching = false;
this.$emit('loaded');
}, rej);
}));
},
more() {
this.moreFetching = true;
const promise = this.$root.api('i/favorites', {
limit: fetchLimit + 1,
untilId: (this.$refs.timeline as any).tail().id,
});
promise.then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
} else {
this.existMore = false;
}
for (const n of notes) {
(this.$refs.timeline as any).append(n);
}
this.moreFetching = false;
});
return promise;
},
focus() {
this.$refs.timeline.focus();
}
}
});
</script>

View File

@@ -0,0 +1,60 @@
<template>
<x-column>
<span slot="header">
<fa :icon="faNewspaper"/>{{ $t('@.featured-notes') }}
</span>
<div>
<x-notes ref="timeline" :more="null"/>
</div>
</x-column>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import XColumn from './deck.column.vue';
import XNotes from './deck.notes.vue';
import { faNewspaper } from '@fortawesome/free-solid-svg-icons';
export default Vue.extend({
i18n: i18n(),
components: {
XColumn,
XNotes,
},
data() {
return {
fetching: true,
faNewspaper
};
},
mounted() {
this.fetch();
},
methods: {
fetch() {
this.fetching = true;
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
this.$root.api('notes/featured', {
limit: 20,
}).then(notes => {
notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
res(notes);
this.fetching = false;
this.$emit('loaded');
}, rej);
}));
},
focus() {
this.$refs.timeline.focus();
}
}
});
</script>

View File

@@ -0,0 +1,119 @@
<template>
<x-column>
<span slot="header">
<fa icon="hashtag"/><span>{{ tag }}</span>
</span>
<div class="xroyrflcmhhtmlwmyiwpfqiirqokfueb">
<div ref="chart" class="chart"></div>
<x-hashtag-tl :tag-tl="tagTl" class="tl"/>
</div>
</x-column>
</template>
<script lang="ts">
import Vue from 'vue';
import XColumn from './deck.column.vue';
import XHashtagTl from './deck.hashtag-tl.vue';
import ApexCharts from 'apexcharts';
export default Vue.extend({
components: {
XColumn,
XHashtagTl
},
computed: {
tag(): string {
return this.$route.params.tag;
},
tagTl(): any {
return {
query: [[this.tag]]
};
}
},
watch: {
$route: 'fetch'
},
created() {
this.fetch();
},
methods: {
fetch() {
this.$root.api('charts/hashtag', {
tag: this.tag,
span: 'hour',
limit: 24
}).then(stats => {
const local = [];
const remote = [];
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
const h = now.getHours();
for (let i = 0; i < 24; i++) {
const x = new Date(y, m, d, h - i);
local.push([x, stats.local.count[i]]);
remote.push([x, stats.remote.count[i]]);
}
const chart = new ApexCharts(this.$refs.chart, {
chart: {
type: 'area',
height: 70,
sparkline: {
enabled: true
},
},
grid: {
clipMarkers: false,
padding: {
top: 16,
right: 16,
bottom: 16,
left: 16
}
},
stroke: {
curve: 'straight',
width: 2
},
series: [{
name: 'Local',
data: local
}, {
name: 'Remote',
data: remote
}],
xaxis: {
type: 'datetime',
}
});
chart.render();
});
}
}
});
</script>
<style lang="stylus" scoped>
.xroyrflcmhhtmlwmyiwpfqiirqokfueb
background var(--deckColumnBg)
> .chart
margin-bottom 16px
background var(--face)
> .tl
background var(--face)
</style>

View File

@@ -8,7 +8,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../../i18n'; import i18n from '../../../i18n';
import XColumn from './deck.column.vue'; import XColumn from './deck.column.vue';
import XMentions from './deck.mentions.vue'; import XMentions from './deck.mentions.vue';

View File

@@ -18,10 +18,10 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../../i18n'; import i18n from '../../../i18n';
import XColumn from './deck.column.vue'; import XColumn from './deck.column.vue';
import XNotes from './deck.notes.vue'; import XNotes from './deck.notes.vue';
import XNote from '../../components/note.vue'; import XNote from '../components/note.vue';
export default Vue.extend({ export default Vue.extend({
i18n: i18n(), i18n: i18n(),
@@ -31,13 +31,6 @@ export default Vue.extend({
XNote XNote
}, },
props: {
noteId: {
type: String,
required: true
}
},
data() { data() {
return { return {
note: null, note: null,
@@ -45,12 +38,26 @@ export default Vue.extend({
}; };
}, },
watch: {
$route: 'fetch'
},
created() { created() {
this.$root.api('notes/show', { noteId: this.noteId }).then(note => { this.fetch();
},
methods: {
fetch() {
this.fetching = true;
this.$root.api('notes/show', {
noteId: this.$route.params.note
}).then(note => {
this.note = note; this.note = note;
this.fetching = false; this.fetching = false;
}); });
} }
}
}); });
</script> </script>

View File

@@ -38,10 +38,10 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../../i18n'; import i18n from '../../../i18n';
import shouldMuteNote from '../../../../common/scripts/should-mute-note'; import shouldMuteNote from '../../../common/scripts/should-mute-note';
import XNote from '../../components/note.vue'; import XNote from '../components/note.vue';
const displayLimit = 20; const displayLimit = 20;

View File

@@ -96,8 +96,8 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import getNoteSummary from '../../../../../../misc/get-note-summary'; import getNoteSummary from '../../../../../misc/get-note-summary';
import XNote from '../../components/note.vue'; import XNote from '../components/note.vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {

View File

@@ -8,7 +8,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../../i18n'; import i18n from '../../../i18n';
import XColumn from './deck.column.vue'; import XColumn from './deck.column.vue';
import XNotifications from './deck.notifications.vue'; import XNotifications from './deck.notifications.vue';

View File

@@ -25,7 +25,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../../i18n'; import i18n from '../../../i18n';
import XNotification from './deck.notification.vue'; import XNotification from './deck.notification.vue';
const displayLimit = 20; const displayLimit = 20;

View File

@@ -0,0 +1,99 @@
<template>
<x-column>
<span slot="header">
<fa icon="search"/><span>{{ q }}</span>
</span>
<div>
<x-notes ref="timeline" :more="existMore ? more : null"/>
</div>
</x-column>
</template>
<script lang="ts">
import Vue from 'vue';
import XColumn from './deck.column.vue';
import XNotes from './deck.notes.vue';
const limit = 20;
export default Vue.extend({
components: {
XColumn,
XNotes
},
data() {
return {
fetching: true,
moreFetching: false,
existMore: false,
offset: 0,
empty: false,
notAvailable: false
};
},
computed: {
q(): string {
return this.$route.query.q;
}
},
watch: {
$route: 'fetch'
},
created() {
this.fetch();
},
methods: {
fetch() {
this.fetching = true;
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
this.$root.api('notes/search', {
limit: limit + 1,
offset: this.offset,
query: this.q
}).then(notes => {
if (notes.length == 0) this.empty = true;
if (notes.length == limit + 1) {
notes.pop();
this.existMore = true;
}
res(notes);
this.fetching = false;
}, (e: string) => {
this.fetching = false;
if (e === 'searching not available') this.notAvailable = true;
});
}));
},
more() {
this.offset += limit;
const promise = this.$root.api('notes/search', {
limit: limit + 1,
offset: this.offset,
query: this.q
});
promise.then(notes => {
if (notes.length == limit + 1) {
notes.pop();
} else {
this.existMore = false;
}
for (const n of notes) {
(this.$refs.timeline as any).append(n);
}
this.moreFetching = false;
});
return promise;
}
}
});
</script>

View File

@@ -38,7 +38,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../../i18n'; import i18n from '../../../i18n';
import XColumn from './deck.column.vue'; import XColumn from './deck.column.vue';
import XTl from './deck.tl.vue'; import XTl from './deck.tl.vue';
import XListTl from './deck.list-tl.vue'; import XListTl from './deck.list-tl.vue';

View File

@@ -1,14 +1,25 @@
<template> <template>
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/> <div class="iwaalbte" v-if="disabled">
<p>
<fa :icon="faMinusCircle"/>
{{ $t('disabled-timeline.title') }}
</p>
<p class="desc">{{ $t('disabled-timeline.description') }}</p>
</div>
<x-notes v-else ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import XNotes from './deck.notes.vue'; import XNotes from './deck.notes.vue';
import { faMinusCircle } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../../i18n';
const fetchLimit = 10; const fetchLimit = 10;
export default Vue.extend({ export default Vue.extend({
i18n: i18n('deck'),
components: { components: {
XNotes XNotes
}, },
@@ -36,7 +47,9 @@ export default Vue.extend({
fetching: true, fetching: true,
moreFetching: false, moreFetching: false,
existMore: false, existMore: false,
connection: null connection: null,
disabled: false,
faMinusCircle
}; };
}, },
@@ -75,6 +88,12 @@ export default Vue.extend({
this.connection.on('unfollow', this.onChangeFollowing); this.connection.on('unfollow', this.onChangeFollowing);
} }
this.$root.getMeta().then(meta => {
this.disabled = !this.$store.state.i.isModerator && !this.$store.state.i.isAdmin && (
meta.disableLocalTimeline && ['local', 'hybrid'].includes(this.src) ||
meta.disableGlobalTimeline && ['global'].includes(this.src));
});
this.fetch(); this.fetch();
}, },
@@ -149,3 +168,16 @@ export default Vue.extend({
} }
}); });
</script> </script>
<style lang="stylus" scoped>
.iwaalbte
color var(--text)
text-align center
> p
margin 16px
&.desc
font-size 14px
</style>

View File

@@ -0,0 +1,244 @@
<template>
<div>
<ui-container v-if="user.pinnedNotes && user.pinnedNotes.length > 0" :body-togglable="true">
<span slot="header"><fa icon="thumbtack"/> {{ $t('pinned-notes') }}</span>
<div>
<x-note v-for="n in user.pinnedNotes" :key="n.id" :note="n" :mini="true"/>
</div>
</ui-container>
<ui-container v-if="images.length > 0" :body-togglable="true">
<span slot="header"><fa :icon="['far', 'images']"/> {{ $t('images') }}</span>
<div class="sainvnaq">
<router-link v-for="image in images"
:style="`background-image: url(${image.thumbnailUrl})`"
:key="`${image.id}:${image._note.id}`"
:to="image._note | notePage"
:title="`${image.name}\n${(new Date(image.createdAt)).toLocaleString()}`"
></router-link>
</div>
</ui-container>
<ui-container :body-togglable="true">
<span slot="header"><fa :icon="['far', 'chart-bar']"/> {{ $t('activity') }}</span>
<div>
<div ref="chart"></div>
</div>
</ui-container>
<ui-container>
<span slot="header"><fa :icon="['far', 'comment-alt']"/> {{ $t('timeline') }}</span>
<div>
<x-notes ref="timeline" :more="existMore ? fetchMoreNotes : null"/>
</div>
</ui-container>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import parseAcct from '../../../../../misc/acct/parse';
import XNotes from './deck.notes.vue';
import XNote from '../components/note.vue';
import { concat } from '../../../../../prelude/array';
import ApexCharts from 'apexcharts';
const fetchLimit = 10;
export default Vue.extend({
i18n: i18n('deck/deck.user-column.vue'),
components: {
XNotes,
XNote
},
props: {
user: {
type: Object,
required: true
}
},
data() {
return {
existMore: false,
moreFetching: false,
withFiles: false,
images: [],
};
},
created() {
this.fetch();
},
methods: {
fetch() {
this.$nextTick(() => {
(this.$refs.timeline as any).init(() => this.initTl());
});
const image = [
'image/jpeg',
'image/png',
'image/gif'
];
this.$root.api('users/notes', {
userId: this.user.id,
fileType: image,
excludeNsfw: !this.$store.state.device.alwaysShowNsfw,
limit: 9,
untilDate: new Date().getTime() + 1000 * 86400 * 365
}).then(notes => {
for (const note of notes) {
for (const file of note.files) {
file._note = note;
}
}
const files = concat(notes.map((n: any): any[] => n.files));
this.images = files.filter(f => image.includes(f.type)).slice(0, 9);
});
this.$root.api('charts/user/notes', {
userId: this.user.id,
span: 'day',
limit: 21
}).then(stats => {
const normal = [];
const reply = [];
const renote = [];
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
for (let i = 0; i < 21; i++) {
const x = new Date(y, m, d - i);
normal.push([
x,
stats.diffs.normal[i]
]);
reply.push([
x,
stats.diffs.reply[i]
]);
renote.push([
x,
stats.diffs.renote[i]
]);
}
const chart = new ApexCharts(this.$refs.chart, {
chart: {
type: 'bar',
stacked: true,
height: 100,
sparkline: {
enabled: true
},
},
plotOptions: {
bar: {
columnWidth: '90%'
}
},
grid: {
clipMarkers: false,
padding: {
top: 16,
right: 16,
bottom: 16,
left: 16
}
},
tooltip: {
shared: true,
intersect: false
},
series: [{
name: 'Normal',
data: normal
}, {
name: 'Reply',
data: reply
}, {
name: 'Renote',
data: renote
}],
xaxis: {
type: 'datetime',
crosshairs: {
width: 1,
opacity: 1
}
}
});
chart.render();
});
},
initTl() {
return new Promise((res, rej) => {
this.$root.api('users/notes', {
userId: this.user.id,
limit: fetchLimit + 1,
untilDate: new Date().getTime() + 1000 * 86400 * 365,
withFiles: this.withFiles,
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
}).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
this.existMore = true;
}
res(notes);
}, rej);
});
},
fetchMoreNotes() {
this.moreFetching = true;
const promise = this.$root.api('users/notes', {
userId: this.user.id,
limit: fetchLimit + 1,
untilDate: new Date((this.$refs.timeline as any).tail().createdAt).getTime(),
withFiles: this.withFiles,
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
});
promise.then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
} else {
this.existMore = false;
}
for (const n of notes) (this.$refs.timeline as any).append(n);
this.moreFetching = false;
});
return promise;
}
}
});
</script>
<style lang="stylus" scoped>
.sainvnaq
display grid
grid-template-columns 1fr 1fr 1fr
gap 8px
padding 16px
> *
height 70px
background-position center center
background-size cover
background-clip content-box
border-radius 4px
</style>

View File

@@ -0,0 +1,262 @@
<template>
<x-column>
<span slot="header">
<fa icon="user"/><mk-user-name :user="user" v-if="user"/>
</span>
<div class="zubukjlciycdsyynicqrnlsmdwmymzqu" v-if="user">
<div class="is-remote" v-if="user.host != null">
<details>
<summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}</summary>
<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a>
</details>
</div>
<header :style="bannerStyle">
<div>
<button class="menu" @click="menu" ref="menu"><fa icon="ellipsis-h"/></button>
<mk-follow-button v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" class="follow" mini/>
<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
<span class="name">
<mk-user-name :user="user"/>
</span>
<span class="acct">@{{ user | acct }} <fa v-if="user.isLocked == true" class="locked" icon="lock" fixed-width/></span>
<span class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</span>
</div>
</header>
<div class="info">
<div class="description">
<mfm v-if="user.description" :text="user.description" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
</div>
<div class="fields" v-if="user.fields">
<dl class="field" v-for="(field, i) in user.fields" :key="i">
<dt class="name">
<mfm :text="field.name" :should-break="false" :plain-text="true" :custom-emojis="user.emojis"/>
</dt>
<dd class="value">
<mfm :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
</dd>
</dl>
</div>
<div class="counts">
<div>
<router-link :to="user | userPage()">
<b>{{ user.notesCount | number }}</b>
<span>{{ $t('posts') }}</span>
</router-link>
</div>
<div>
<router-link :to="user | userPage('following')">
<b>{{ user.followingCount | number }}</b>
<span>{{ $t('following') }}</span>
</router-link>
</div>
<div>
<router-link :to="user | userPage('followers')">
<b>{{ user.followersCount | number }}</b>
<span>{{ $t('followers') }}</span>
</router-link>
</div>
</div>
</div>
<router-view :user="user"></router-view>
</div>
</x-column>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import parseAcct from '../../../../../misc/acct/parse';
import XColumn from './deck.column.vue';
import XUserMenu from '../../../common/views/components/user-menu.vue';
export default Vue.extend({
i18n: i18n('deck/deck.user-column.vue'),
components: {
XColumn,
},
data() {
return {
user: null,
fetching: true,
};
},
computed: {
bannerStyle(): any {
if (this.user == null) return {};
if (this.user.bannerUrl == null) return {};
return {
backgroundColor: this.user.bannerColor && this.user.bannerColor.length == 3 ? `rgb(${ this.user.bannerColor.join(',') })` : null,
backgroundImage: `url(${ this.user.bannerUrl })`
};
},
},
watch: {
$route: 'fetch'
},
created() {
this.fetch();
},
methods: {
fetch() {
this.fetching = true;
this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => {
this.user = user;
this.fetching = false;
});
},
menu() {
this.$root.new(XUserMenu, {
source: this.$refs.menu,
user: this.user
});
}
}
});
</script>
<style lang="stylus" scoped>
.zubukjlciycdsyynicqrnlsmdwmymzqu
background var(--deckColumnBg)
> .is-remote
padding 8px 16px
font-size 12px
&.is-remote
color var(--remoteInfoFg)
background var(--remoteInfoBg)
> a
font-weight bold
> header
overflow hidden
background-size cover
background-position center
> div
padding 32px
background rgba(#000, 0.5)
color #fff
text-align center
> .menu
position absolute
top 8px
left 8px
padding 8px
font-size 16px
text-shadow 0 0 8px #000
> .follow
position absolute
top 16px
right 16px
> .avatar
display block
width 64px
height 64px
margin 0 auto
> .name
display block
margin-top 8px
font-weight bold
text-shadow 0 0 8px #000
> .acct
display block
font-size 14px
opacity 0.7
text-shadow 0 0 8px #000
> .locked
opacity 0.8
> .followed
display inline-block
font-size 12px
background rgba(0, 0, 0, 0.5)
opacity 0.7
margin-top: 2px
padding 4px
border-radius 4px
> .info
padding 16px
font-size 12px
color var(--text)
text-align center
background var(--face)
&:before
content ""
display blcok
position absolute
top -32px
left 0
right 0
width 0px
margin 0 auto
border-top solid 16px transparent
border-left solid 16px transparent
border-right solid 16px transparent
border-bottom solid 16px var(--face)
> .fields
margin-top 8px
> .field
display flex
padding 0
margin 0
align-items center
> .name
padding 4px
margin 4px
width 30%
overflow hidden
white-space nowrap
text-overflow ellipsis
font-weight bold
> .value
padding 4px
margin 4px
width 70%
overflow hidden
white-space nowrap
text-overflow ellipsis
> .counts
display grid
grid-template-columns 2fr 2fr 2fr
margin-top 8px
border-top solid var(--lineWidth) var(--faceDivider)
> div
padding 8px 8px 0 8px
text-align center
> a
color var(--text)
> b
display block
font-size 110%
> span
display block
font-size 80%
opacity 0.7
</style>

View File

@@ -9,11 +9,7 @@
</div> </div>
<x-column-core v-else :ref="ids[0]" :key="ids[0]" :column="columns.find(c => c.id == ids[0])" @parentFocus="moveFocus(ids[0], $event)"/> <x-column-core v-else :ref="ids[0]" :key="ids[0]" :column="columns.find(c => c.id == ids[0])" @parentFocus="moveFocus(ids[0], $event)"/>
</template> </template>
<template v-if="temporaryColumn"> <router-view></router-view>
<x-user-column v-if="temporaryColumn.type == 'user'" :acct="temporaryColumn.acct" :key="temporaryColumn.acct"/>
<x-note-column v-else-if="temporaryColumn.type == 'note'" :note-id="temporaryColumn.noteId" :key="temporaryColumn.noteId"/>
<x-hashtag-column v-else-if="temporaryColumn.type == 'tag'" :tag="temporaryColumn.tag" :key="temporaryColumn.tag"/>
</template>
<button ref="add" @click="add" :title="$t('@deck.add-column')"><fa icon="plus"/></button> <button ref="add" @click="add" :title="$t('@deck.add-column')"><fa icon="plus"/></button>
</div> </div>
</mk-ui> </mk-ui>
@@ -21,20 +17,17 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../../i18n'; import i18n from '../../../i18n';
import XColumnCore from './deck.column-core.vue'; import XColumnCore from './deck.column-core.vue';
import Menu from '../../../../common/views/components/menu.vue'; import Menu from '../../../common/views/components/menu.vue';
import MkUserListsWindow from '../../components/user-lists-window.vue'; import MkUserListsWindow from '../components/user-lists-window.vue';
import * as uuid from 'uuid'; import * as uuid from 'uuid';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('deck'), i18n: i18n('deck'),
components: { components: {
XColumnCore, XColumnCore
XUserColumn: () => import('./deck.user-column.vue').then(m => m.default),
XNoteColumn: () => import('./deck.note-column.vue').then(m => m.default),
XHashtagColumn: () => import('./deck.hashtag-column.vue').then(m => m.default)
}, },
computed: { computed: {
@@ -55,10 +48,6 @@ export default Vue.extend({
}; };
}, },
temporaryColumn(): any {
return this.$store.state.device.deckTemporaryColumn;
},
keymap(): any { keymap(): any {
return { return {
't': this.focus 't': this.focus
@@ -67,8 +56,8 @@ export default Vue.extend({
}, },
watch: { watch: {
temporaryColumn() { $route() {
if (this.temporaryColumn != null) { if (this.$route.name == 'index') return;
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.body.scrollTo({ this.$refs.body.scrollTo({
left: this.$refs.body.scrollWidth - this.$refs.body.clientWidth, left: this.$refs.body.scrollWidth - this.$refs.body.clientWidth,
@@ -76,7 +65,6 @@ export default Vue.extend({
}); });
}); });
} }
}
}, },
provide() { provide() {
@@ -86,8 +74,6 @@ export default Vue.extend({
}, },
created() { created() {
this.$store.commit('navHook', this.onNav);
if (this.$store.state.settings.deck == null) { if (this.$store.state.settings.deck == null) {
const deck = { const deck = {
columns: [/*{ columns: [/*{
@@ -133,8 +119,6 @@ export default Vue.extend({
}, },
beforeDestroy() { beforeDestroy() {
this.$store.commit('navHook', null);
document.documentElement.style.overflow = 'auto'; document.documentElement.style.overflow = 'auto';
}, },
@@ -143,39 +127,6 @@ export default Vue.extend({
return this.$refs[id][0]; return this.$refs[id][0];
}, },
onNav(to) {
if (!this.$store.state.settings.deckNav) return false;
if (to.name == 'user') {
this.$store.commit('device/set', {
key: 'deckTemporaryColumn',
value: {
type: 'user',
acct: to.params.user
}
});
return true;
} else if (to.name == 'note') {
this.$store.commit('device/set', {
key: 'deckTemporaryColumn',
value: {
type: 'note',
noteId: to.params.note
}
});
return true;
} else if (to.name == 'tag') {
this.$store.commit('device/set', {
key: 'deckTemporaryColumn',
value: {
type: 'tag',
tag: to.params.tag
}
});
return true;
}
},
add() { add() {
this.$root.new(Menu, { this.$root.new(Menu, {
source: this.$refs.add, source: this.$refs.add,

View File

@@ -50,7 +50,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../../i18n'; import i18n from '../../../i18n';
import XColumn from './deck.column.vue'; import XColumn from './deck.column.vue';
import * as XDraggable from 'vuedraggable'; import * as XDraggable from 'vuedraggable';
import * as uuid from 'uuid'; import * as uuid from 'uuid';

View File

@@ -1,6 +1,5 @@
<template> <template>
<mk-ui> <div class="ecsvsegy" v-if="!fetching">
<main v-if="!fetching">
<sequential-entrance animation="entranceFromTop" delay="25"> <sequential-entrance animation="entranceFromTop" delay="25">
<template v-for="favorite in favorites"> <template v-for="favorite in favorites">
<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/> <mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
@@ -9,8 +8,7 @@
<div class="more" v-if="existMore"> <div class="more" v-if="existMore">
<ui-button inline @click="more">{{ $t('@.load-more') }}</ui-button> <ui-button inline @click="more">{{ $t('@.load-more') }}</ui-button>
</div> </div>
</main> </div>
</mk-ui>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -72,10 +70,8 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
main .ecsvsegy
margin 0 auto margin 0 auto
padding 16px
max-width 700px
> * > .post > * > .post
margin-bottom 16px margin-bottom 16px

View File

@@ -0,0 +1,55 @@
<template>
<div class="glowckho" v-if="!fetching">
<sequential-entrance animation="entranceFromTop" delay="25">
<template v-for="note in notes">
<mk-note-detail class="post" :note="note" :key="note.id"/>
</template>
</sequential-entrance>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Progress from '../../../common/scripts/loading';
export default Vue.extend({
data() {
return {
fetching: true,
notes: [],
};
},
created() {
this.fetch();
},
methods: {
fetch() {
Progress.start();
this.fetching = true;
this.$root.api('notes/featured', {
limit: 20
}).then(notes => {
notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
this.notes = notes;
this.fetching = false;
Progress.done();
});
},
}
});
</script>
<style lang="stylus" scoped>
.glowckho
margin 0 auto
> * > .post
margin-bottom 16px
> .more
margin 32px 16px 16px 16px
text-align center
</style>

View File

@@ -0,0 +1,404 @@
<template>
<component :is="customize ? 'mk-dummy' : 'mk-ui'" v-hotkey.global="keymap" v-if="$store.getters.isSignedIn || $route.name != 'index'">
<div class="wqsofvpm" :data-customize="customize">
<div class="customize" v-if="customize">
<a @click="done()"><fa icon="check"/>{{ $t('done') }}</a>
<div>
<div class="adder">
<p>{{ $t('add-widget') }}</p>
<select v-model="widgetAdderSelected">
<option value="profile">{{ $t('@.widgets.profile') }}</option>
<option value="analog-clock">{{ $t('@.widgets.analog-clock') }}</option>
<option value="calendar">{{ $t('@.widgets.calendar') }}</option>
<option value="timemachine">{{ $t('@.widgets.timemachine') }}</option>
<option value="activity">{{ $t('@.widgets.activity') }}</option>
<option value="rss">{{ $t('@.widgets.rss') }}</option>
<option value="trends">{{ $t('@.widgets.trends') }}</option>
<option value="photo-stream">{{ $t('@.widgets.photo-stream') }}</option>
<option value="slideshow">{{ $t('@.widgets.slideshow') }}</option>
<option value="version">{{ $t('@.widgets.version') }}</option>
<option value="broadcast">{{ $t('@.widgets.broadcast') }}</option>
<option value="notifications">{{ $t('@.widgets.notifications') }}</option>
<option value="users">{{ $t('@.widgets.users') }}</option>
<option value="polls">{{ $t('@.widgets.polls') }}</option>
<option value="post-form">{{ $t('@.widgets.post-form') }}</option>
<option value="messaging">{{ $t('@.widgets.messaging') }}</option>
<option value="memo">{{ $t('@.widgets.memo') }}</option>
<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option>
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
<option value="server">{{ $t('@.widgets.server') }}</option>
<option value="nav">{{ $t('@.widgets.nav') }}</option>
<option value="tips">{{ $t('@.widgets.tips') }}</option>
</select>
<button @click="addWidget">{{ $t('add') }}</button>
</div>
<div class="trash">
<x-draggable v-model="trash" :options="{ group: 'x' }" @add="onTrash"></x-draggable>
<p>{{ $t('@.trash') }}</p>
</div>
</div>
</div>
<div class="main" :class="{ side: widgets.left.length == 0 || widgets.right.length == 0 }">
<template v-if="customize">
<x-draggable v-for="place in ['left', 'right']"
:list="widgets[place]"
:class="place"
:data-place="place"
:options="{ group: 'x', animation: 150 }"
@sort="onWidgetSort"
:key="place"
>
<div v-for="widget in widgets[place]" class="customize-container" :key="widget.id" @contextmenu.stop.prevent="onWidgetContextmenu(widget.id)">
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="desktop"/>
</div>
</x-draggable>
<div class="main">
<a @click="hint">{{ $t('@.customization-tips.title') }}</a>
<div>
<x-timeline/>
</div>
</div>
</template>
<template v-else>
<div v-for="place in ['left', 'right']" :class="place">
<component v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" platform="desktop"/>
</div>
<div class="main">
<router-view ref="content"></router-view>
</div>
</template>
</div>
</div>
</component>
<x-welcome v-else/>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import * as XDraggable from 'vuedraggable';
import * as uuid from 'uuid';
import XWelcome from '../pages/welcome.vue';
export default Vue.extend({
i18n: i18n('desktop/views/components/home.vue'),
components: {
XDraggable,
XWelcome
},
data() {
return {
customize: window.location.search == '?customize',
connection: null,
widgetAdderSelected: null,
trash: [],
view: null
};
},
computed: {
home(): any[] {
if (this.$store.getters.isSignedIn) {
return this.$store.state.settings.home || [];
} else {
return [{
name: 'instance',
place: 'right'
}, {
name: 'broadcast',
place: 'right',
data: {}
}];
}
},
left(): any[] {
return this.home.filter(w => w.place == 'left');
},
right(): any[] {
return this.home.filter(w => w.place == 'right');
},
widgets(): any {
return {
left: this.left,
right: this.right
};
},
keymap(): any {
return {
't': this.focus
};
}
},
created() {
if (this.$store.getters.isSignedIn) {
const defaultDesktopHomeWidgets = {
left: [
'profile',
'calendar',
'activity',
'rss',
'hashtags',
'photo-stream',
'version'
],
right: [
'customize',
'broadcast',
'notifications',
'users',
'polls',
'server',
'nav',
'tips'
]
};
//#region Construct home data
const _defaultDesktopHomeWidgets = [];
for (const widget of defaultDesktopHomeWidgets.left) {
_defaultDesktopHomeWidgets.push({
name: widget,
id: uuid(),
place: 'left',
data: {}
});
}
for (const widget of defaultDesktopHomeWidgets.right) {
_defaultDesktopHomeWidgets.push({
name: widget,
id: uuid(),
place: 'right',
data: {}
});
}
//#endregion
if (this.$store.state.settings.home == null) {
this.$root.api('i/update_home', {
home: _defaultDesktopHomeWidgets
}).then(() => {
this.$store.commit('settings/setHome', _defaultDesktopHomeWidgets);
});
}
}
},
mounted() {
this.connection = this.$root.stream.useSharedConnection('main');
},
beforeDestroy() {
this.connection.dispose();
},
methods: {
hint() {
this.$root.dialog({
title: this.$t('@.customization-tips.title'),
text: this.$t('@.customization-tips.paragraph')
});
},
onTlLoaded() {
this.$emit('loaded');
},
onWidgetContextmenu(widgetId) {
const w = (this.$refs[widgetId] as any)[0];
if (w.func) w.func();
},
onWidgetSort() {
this.saveHome();
},
onTrash(evt) {
this.saveHome();
},
addWidget() {
this.$store.dispatch('settings/addHomeWidget', {
name: this.widgetAdderSelected,
id: uuid(),
place: 'left',
data: {}
});
},
saveHome() {
const left = this.widgets.left;
const right = this.widgets.right;
this.$store.commit('settings/setHome', left.concat(right));
for (const w of left) w.place = 'left';
for (const w of right) w.place = 'right';
this.$root.api('i/update_home', {
home: this.home
});
},
done() {
location.href = '/';
},
focus() {
(this.$refs.content as any).focus();
}
}
});
</script>
<style lang="stylus" scoped>
.wqsofvpm
display block
&[data-customize]
padding-top 48px
background-image url('/assets/desktop/grid.svg')
> .main > .main
> a
display block
margin-bottom 8px
text-align center
> div
cursor not-allowed !important
> *
pointer-events none
&:not([data-customize])
> .main > *:not(.main):empty
display none
> .customize
position fixed
z-index 1000
top 0
left 0
width 100%
height 48px
color var(--text)
background var(--desktopHeaderBg)
box-shadow 0 1px 1px rgba(#000, 0.075)
> a
display block
position absolute
z-index 1001
top 0
right 0
padding 0 16px
line-height 48px
text-decoration none
color var(--primaryForeground)
background var(--primary)
transition background 0.1s ease
&:hover
background var(--primaryLighten10)
&:active
background var(--primaryDarken10)
transition background 0s ease
> [data-icon]
margin-right 8px
> div
display flex
margin 0 auto
max-width 1220px - 32px
> div
width 50%
&.adder
> p
display inline
line-height 48px
&.trash
border-left solid 1px var(--faceDivider)
> div
width 100%
height 100%
> p
position absolute
top 0
left 0
width 100%
line-height 48px
margin 0
text-align center
pointer-events none
> .main
display flex
justify-content center
margin 0 auto
max-width 1240px
> *
.customize-container
cursor move
border-radius 6px
&:hover
box-shadow 0 0 8px rgba(64, 120, 200, 0.3)
> *
pointer-events none
> .main
padding 16px
width calc(100% - 280px * 2)
order 2
&.side
> .main
width calc(100% - 280px)
max-width 680px
> *:not(.main)
width 280px
padding 16px 0 16px 0
> *:not(:last-child)
margin-bottom 16px
> .left
padding-left 16px
order 1
> .right
padding-right 16px
order 3
&.side
@media (max-width 1000px)
> *:not(.main)
display none
> .main
width 100%
max-width 700px
margin 0 auto
&:not(.side)
@media (max-width 1200px)
> *:not(.main)
display none
> .main
width 100%
max-width 700px
margin 0 auto
</style>

View File

@@ -1,13 +1,11 @@
<template> <template>
<mk-ui> <div v-if="!fetching" class="kcthdwmv">
<main v-if="!fetching">
<mk-note-detail :note="note"/> <mk-note-detail :note="note"/>
<footer> <footer>
<router-link v-if="note.next" :to="note.next"><fa icon="angle-left"/> {{ $t('next') }}</router-link> <router-link v-if="note.next" :to="note.next"><fa icon="angle-left"/> {{ $t('next') }}</router-link>
<router-link v-if="note.prev" :to="note.prev">{{ $t('prev') }} <fa icon="angle-right"/></router-link> <router-link v-if="note.prev" :to="note.prev">{{ $t('prev') }} <fa icon="angle-right"/></router-link>
</footer> </footer>
</main> </div>
</mk-ui>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -48,8 +46,7 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
main .kcthdwmv
padding 16px
text-align center text-align center
> footer > footer
@@ -59,8 +56,4 @@ main
display inline-block display inline-block
margin 0 16px margin 0 16px
> .mk-note-detail
margin 0 auto
width 640px
</style> </style>

View File

@@ -1,12 +1,14 @@
<template> <template>
<mk-ui> <div class="oxgbmvii">
<header :class="$style.header"> <div class="notes">
<h1>{{ q }}</h1> <header>
<span><fa icon="search"/> {{ q }}</span>
</header> </header>
<p :class="$style.notAvailable" v-if="!fetching && notAvailable">{{ $t('not-available') }}</p> <p v-if="!fetching && notAvailable">{{ $t('not-available') }}</p>
<p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('not-found', { q }) }}</p> <p v-if="!fetching && empty"><fa icon="search"/> {{ $t('not-found', { q }) }}</p>
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/> <mk-notes ref="timeline" :more="existMore ? more : null"/>
</mk-ui> </div>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -106,45 +108,23 @@ export default Vue.extend({
}); });
</script> </script>
<style lang="stylus" module> <style lang="stylus" scoped>
.header .oxgbmvii
width 100% > .notes
max-width 600px background var(--face)
margin 0 auto box-shadow var(--shadow)
color #555 border-radius var(--round)
.notes
max-width 600px
margin 0 auto
border solid 1px rgba(#000, 0.075)
border-radius 6px
overflow hidden overflow hidden
.empty > header
display block padding 0 8px
margin 0 auto z-index 10
padding 32px background var(--faceHeader)
max-width 400px box-shadow 0 var(--lineWidth) var(--desktopTimelineHeaderShadow)
text-align center
color #999
> [data-icon] > span
display block padding 0 8px
margin-bottom 16px font-size 0.9em
font-size 3em line-height 42px
color #ccc color var(--text)
.notAvailable
display block
margin 0 auto
padding 32px
max-width 400px
text-align center
color #999
> [data-icon]
display block
margin-bottom 16px
font-size 3em
color #ccc
</style> </style>

View File

@@ -1,11 +1,8 @@
<template> <template>
<mk-ui> <div>
<header :class="$style.header">
<h1>#{{ $route.params.tag }}</h1>
</header>
<p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q: $route.params.tag }) }}</p> <p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q: $route.params.tag }) }}</p>
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/> <mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
</mk-ui> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -96,17 +93,10 @@ export default Vue.extend({
</script> </script>
<style lang="stylus" module> <style lang="stylus" module>
.header
width 100%
max-width 600px
margin 0 auto
color #555
.notes .notes
width 600px background var(--face)
margin 0 auto box-shadow var(--shadow)
border solid 1px rgba(#000, 0.075) border-radius var(--round)
border-radius 6px
overflow hidden overflow hidden
.empty .empty

View File

@@ -0,0 +1,273 @@
<template>
<div class="mk-timeline">
<mk-post-form class="form" v-if="$store.state.settings.showPostFormOnTopOfTl"/>
<div class="main">
<header>
<span :data-active="src == 'home'" @click="src = 'home'"><fa icon="home"/> {{ $t('home') }}</span>
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline"><fa :icon="['far', 'comments']"/> {{ $t('local') }}</span>
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline"><fa icon="share-alt"/> {{ $t('hybrid') }}</span>
<span :data-active="src == 'global'" @click="src = 'global'" v-if="enableGlobalTimeline"><fa icon="globe"/> {{ $t('global') }}</span>
<span :data-active="src == 'tag'" @click="src = 'tag'" v-if="tagTl"><fa icon="hashtag"/> {{ tagTl.title }}</span>
<span :data-active="src == 'list'" @click="src = 'list'" v-if="list"><fa icon="list"/> {{ list.title }}</span>
<div class="buttons">
<button :data-active="src == 'mentions'" @click="src = 'mentions'" :title="$t('mentions')"><fa icon="at"/><i class="badge" v-if="$store.state.i.hasUnreadMentions"><fa icon="circle"/></i></button>
<button :data-active="src == 'messages'" @click="src = 'messages'" :title="$t('messages')"><fa :icon="['far', 'envelope']"/><i class="badge" v-if="$store.state.i.hasUnreadSpecifiedNotes"><fa icon="circle"/></i></button>
<button @click="chooseTag" :title="$t('hashtag')" ref="tagButton"><fa icon="hashtag"/></button>
<button @click="chooseList" :title="$t('list')" ref="listButton"><fa icon="list"/></button>
</div>
</header>
<x-core v-if="src == 'home'" ref="tl" key="home" src="home"/>
<x-core v-if="src == 'local'" ref="tl" key="local" src="local"/>
<x-core v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
<x-core v-if="src == 'global'" ref="tl" key="global" src="global"/>
<x-core v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
<x-core v-if="src == 'messages'" ref="tl" key="messages" src="messages"/>
<x-core v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import XCore from './timeline.core.vue';
import Menu from '../../../common/views/components/menu.vue';
import MkSettingsWindow from '../components/settings-window.vue';
export default Vue.extend({
i18n: i18n('desktop/views/components/timeline.vue'),
components: {
XCore
},
data() {
return {
src: 'home',
list: null,
tagTl: null,
enableLocalTimeline: false,
enableGlobalTimeline: false,
};
},
watch: {
src() {
this.saveSrc();
},
list(x) {
this.saveSrc();
if (x != null) this.tagTl = null;
},
tagTl(x) {
this.saveSrc();
if (x != null) this.list = null;
}
},
created() {
this.$root.getMeta().then((meta: Record<string, any>) => {
if (!(
this.enableGlobalTimeline = !meta.disableGlobalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin
) && this.src === 'global') this.src = 'local';
if (!(
this.enableLocalTimeline = !meta.disableLocalTimeline || this.$store.state.i.isModerator || this.$store.state.i.isAdmin
) && ['local', 'hybrid'].includes(this.src)) this.src = 'home';
});
if (this.$store.state.device.tl) {
this.src = this.$store.state.device.tl.src;
if (this.src == 'list') {
this.list = this.$store.state.device.tl.arg;
} else if (this.src == 'tag') {
this.tagTl = this.$store.state.device.tl.arg;
}
} else if (this.$store.state.i.followingCount == 0) {
this.src = 'hybrid';
}
},
mounted() {
(this.$refs.tl as any).$once('loaded', () => {
this.$emit('loaded');
});
},
methods: {
saveSrc() {
this.$store.commit('device/setTl', {
src: this.src,
arg: this.src == 'list' ? this.list : this.tagTl
});
},
focus() {
(this.$refs.tl as any).focus();
},
warp(date) {
(this.$refs.tl as any).warp(date);
},
async chooseList() {
const lists = await this.$root.api('users/lists/list');
let menu = [{
icon: 'plus',
text: this.$t('add-list'),
action: () => {
this.$root.dialog({
title: this.$t('list-name'),
input: true
}).then(async ({ canceled, result: title }) => {
if (canceled) return;
const list = await this.$root.api('users/lists/create', {
title
});
this.list = list;
this.src = 'list';
});
}
}];
if (lists.length > 0) {
menu.push(null);
}
menu = menu.concat(lists.map(list => ({
icon: 'list',
text: list.title,
action: () => {
this.list = list;
this.src = 'list';
}
})));
this.$root.new(Menu, {
source: this.$refs.listButton,
items: menu
});
},
chooseTag() {
let menu = [{
icon: 'plus',
text: this.$t('add-tag-timeline'),
action: () => {
this.$root.new(MkSettingsWindow, {
initialPage: 'hashtags'
});
}
}];
if (this.$store.state.settings.tagTimelines.length > 0) {
menu.push(null);
}
menu = menu.concat(this.$store.state.settings.tagTimelines.map(t => ({
icon: 'hashtag',
text: t.title,
action: () => {
this.tagTl = t;
this.src = 'tag';
}
})));
this.$root.new(Menu, {
source: this.$refs.tagButton,
items: menu
});
}
}
});
</script>
<style lang="stylus" scoped>
.mk-timeline
> .form
margin-bottom 16px
box-shadow var(--shadow)
border-radius var(--round)
> .main
background var(--face)
box-shadow var(--shadow)
border-radius var(--round)
overflow hidden
> header
padding 0 8px
z-index 10
background var(--faceHeader)
box-shadow 0 var(--lineWidth) var(--desktopTimelineHeaderShadow)
> .buttons
position absolute
z-index 2
top 0
right 0
padding-right 8px
> button
padding 0 8px
font-size 0.9em
line-height 42px
color var(--faceTextButton)
> .badge
position absolute
top -4px
right 4px
font-size 10px
color var(--notificationIndicator)
&:hover
color var(--faceTextButtonHover)
&[data-active]
color var(--primary)
cursor default
&:before
content ""
display block
position absolute
bottom 0
left 0
width 100%
height 2px
background var(--primary)
> span
display inline-block
padding 0 10px
line-height 42px
font-size 12px
user-select none
&[data-active]
color var(--primary)
cursor default
font-weight bold
&:before
content ""
display block
position absolute
bottom 0
left -8px
width calc(100% + 16px)
height 2px
background var(--primary)
&:not([data-active])
color var(--desktopTimelineSrc)
cursor pointer
&:hover
color var(--desktopTimelineSrcHover)
</style>

View File

@@ -0,0 +1,82 @@
<template>
<div class="omechnps" v-if="!fetching">
<div class="is-suspended" v-if="user.isSuspended"><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</div>
<div class="is-remote" v-if="user.host != null"><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a></div>
<div class="main">
<x-header class="header" :user="user"/>
<router-view :user="user"></router-view>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../../i18n';
import parseAcct from '../../../../../../misc/acct/parse';
import Progress from '../../../../common/scripts/loading';
import XHeader from './user.header.vue';
export default Vue.extend({
i18n: i18n(),
components: {
XHeader
},
data() {
return {
fetching: true,
user: null
};
},
watch: {
$route: 'fetch'
},
created() {
this.fetch();
},
methods: {
fetch() {
this.fetching = true;
Progress.start();
this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => {
this.user = user;
this.fetching = false;
Progress.done();
});
},
warp(date) {
(this.$refs.tl as any).warp(date);
}
}
});
</script>
<style lang="stylus" scoped>
.omechnps
width 100%
margin 0 auto
> .is-suspended
> .is-remote
margin-bottom 16px
padding 14px 16px
font-size 14px
box-shadow var(--shadow)
border-radius var(--round)
&.is-suspended
color var(--suspendedInfoFg)
background var(--suspendedInfoBg)
&.is-remote
color var(--remoteInfoFg)
background var(--remoteInfoBg)
> a
font-weight bold
> .main
> .header
margin-bottom 16px
</style>

View File

@@ -6,7 +6,7 @@
<div class="user" v-for="friend in users"> <div class="user" v-for="friend in users">
<mk-avatar class="avatar" :user="friend"/> <mk-avatar class="avatar" :user="friend"/>
<div class="body"> <div class="body">
<router-link class="name" :to="friend | userPage" v-user-preview="friend.id">{{ friend.name }}</router-link> <router-link class="name" :to="friend | userPage" v-user-preview="friend.id"><mk-user-name :user="friend"/></router-link>
<p class="username">@{{ friend | acct }}</p> <p class="username">@{{ friend | acct }}</p>
</div> </div>
</div> </div>

View File

@@ -9,12 +9,19 @@
</p> </p>
<div> <div>
<span class="username"><mk-acct :user="user" :detail="true" /></span> <span class="username"><mk-acct :user="user" :detail="true" /></span>
<span v-if="user.isBot" :title="$t('title')"><fa icon="robot"/></span> <span v-if="user.isBot" :title="$t('is-bot')"><fa icon="robot"/></span>
</div> </div>
</div> </div>
</div> </div>
<mk-avatar class="avatar" :user="user" :disable-preview="true"/> <mk-avatar class="avatar" :user="user" :disable-preview="true"/>
<div class="body"> <div class="body">
<div class="actions" v-if="$store.getters.isSignedIn">
<template v-if="$store.state.i.id != user.id">
<span class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</span>
<mk-follow-button :user="user" :inline="true" class="follow"/>
</template>
<ui-button @click="menu" ref="menu" :inline="true"><fa icon="ellipsis-h"/></ui-button>
</div>
<div class="description"> <div class="description">
<mfm v-if="user.description" :text="user.description" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/> <mfm v-if="user.description" :text="user.description" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
</div> </div>
@@ -33,7 +40,7 @@
<span class="birthday" v-if="user.host === null && user.profile.birthday"><fa icon="birthday-cake"/> {{ user.profile.birthday.replace('-', $t('year')).replace('-', $t('month')) + $t('day') }} ({{ $t('years-old', { age }) }})</span> <span class="birthday" v-if="user.host === null && user.profile.birthday"><fa icon="birthday-cake"/> {{ user.profile.birthday.replace('-', $t('year')).replace('-', $t('month')) + $t('day') }} ({{ $t('years-old', { age }) }})</span>
</div> </div>
<div class="status"> <div class="status">
<span class="notes-count"><b>{{ user.notesCount | number }}</b>{{ $t('posts') }}</span> <router-link :to="user | userPage()" class="notes-count"><b>{{ user.notesCount | number }}</b>{{ $t('posts') }}</router-link>
<router-link :to="user | userPage('following')" class="following clickable"><b>{{ user.followingCount | number }}</b>{{ $t('following') }}</router-link> <router-link :to="user | userPage('following')" class="following clickable"><b>{{ user.followingCount | number }}</b>{{ $t('following') }}</router-link>
<router-link :to="user | userPage('followers')" class="followers clickable"><b>{{ user.followersCount | number }}</b>{{ $t('followers') }}</router-link> <router-link :to="user | userPage('followers')" class="followers clickable"><b>{{ user.followersCount | number }}</b>{{ $t('followers') }}</router-link>
</div> </div>
@@ -45,6 +52,7 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../../i18n'; import i18n from '../../../../i18n';
import * as age from 's-age'; import * as age from 's-age';
import XUserMenu from '../../../../common/views/components/user-menu.vue';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/pages/user/user.header.vue'), i18n: i18n('desktop/views/pages/user/user.header.vue'),
@@ -99,6 +107,13 @@ export default Vue.extend({
this.$updateBanner().then(i => { this.$updateBanner().then(i => {
this.user.bannerUrl = i.bannerUrl; this.user.bannerUrl = i.bannerUrl;
}); });
},
menu() {
this.$root.new(XUserMenu, {
source: this.$refs.menu.$el,
user: this.user
});
} }
} }
}); });
@@ -187,6 +202,18 @@ export default Vue.extend({
padding 16px 16px 16px 154px padding 16px 16px 16px 154px
color var(--text) color var(--text)
> .actions
text-align right
padding-bottom 16px
margin-bottom 16px
border-bottom solid 1px var(--faceDivider)
> *
margin-left 8px
> .follow
width 180px
> .fields > .fields
margin-top 16px margin-top 16px

View File

@@ -0,0 +1,57 @@
<template>
<div class="lnctpgve">
<x-integrations :user="user" v-if="user.twitter || user.github || user.discord"/>
<mk-note-detail v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/>
<!--<mk-calendar @chosen="warp" :start="new Date(user.createdAt)"/>-->
<div class="activity">
<ui-container :body-togglable="true">
<template slot="header"><fa icon="chart-bar"/>{{ $t('activity') }}</template>
<x-activity :user="user" :limit="35" style="padding: 16px;"/>
</ui-container>
</div>
<x-photos :user="user"/>
<x-timeline class="timeline" ref="tl" :user="user"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../../i18n';
import parseAcct from '../../../../../../misc/acct/parse';
import Progress from '../../../../common/scripts/loading';
import XTimeline from './user.timeline.vue';
import XPhotos from './user.photos.vue';
import XIntegrations from './user.integrations.vue';
import XActivity from '../../../../common/views/components/activity.vue';
export default Vue.extend({
i18n: i18n(),
components: {
XTimeline,
XPhotos,
XIntegrations,
XActivity
},
props: {
user: {
type: Object,
required: true
}
},
methods: {
warp(date) {
(this.$refs.tl as any).warp(date);
}
}
});
</script>
<style lang="stylus" scoped>
.lnctpgve
> *
margin-bottom 16px
> .timeline
box-shadow var(--shadow)
</style>

Some files were not shown because too many files have changed in this diff Show More