Compare commits
	
		
			90 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | f632ec50c1 | ||
|   | a55d15214b | ||
|   | f1709a2cc2 | ||
|   | effa542958 | ||
|   | e8bf742c87 | ||
|   | 2e6652edce | ||
|   | 230c204b48 | ||
|   | 3755c600b1 | ||
|   | 24513fc0a3 | ||
|   | 0a79a6564a | ||
|   | 562bb5842b | ||
|   | ec3ca3032e | ||
|   | 890770c275 | ||
|   | 9ed58a1b4e | ||
|   | 08984be2fe | ||
|   | e3ade148ca | ||
|   | 34c0eff89f | ||
|   | 40aba47a47 | ||
|   | 6736f51134 | ||
|   | 9d826d6e52 | ||
|   | 902d9bc7a5 | ||
|   | b6c86e2845 | ||
|   | 34dffdfc8f | ||
|   | a56f3f1d89 | ||
|   | 88dc4c83cb | ||
|   | 5a28dc0198 | ||
|   | 40d2650d49 | ||
|   | 545e83efb1 | ||
|   | d4b00a5482 | ||
|   | c2b1bbeec5 | ||
|   | 8c8f165a6e | ||
|   | 04553de230 | ||
|   | 2776934728 | ||
|   | 0064dbb010 | ||
|   | d52e671adf | ||
|   | 6017dc2dff | ||
|   | 937f7cbd60 | ||
|   | f8b3f66904 | ||
|   | 9d5701f35a | ||
|   | dff65810c6 | ||
|   | 6752cf1d64 | ||
|   | 8336910a59 | ||
|   | 957a1149e0 | ||
|   | e8719ff6e6 | ||
|   | 28b63298e5 | ||
|   | dd4dee8095 | ||
|   | c47818fed4 | ||
|   | e53c383908 | ||
|   | 55c9c0436b | ||
|   | 66b79e5e24 | ||
|   | 514b830910 | ||
|   | e4f799bf1d | ||
|   | b383427d3d | ||
|   | e969518139 | ||
|   | 113fe294bd | ||
|   | a4d92f781f | ||
|   | 414cac49c3 | ||
|   | 95b157ac3e | ||
|   | 8e3d884081 | ||
|   | 9def6fcadd | ||
|   | 7837bd44fc | ||
|   | a6c3663155 | ||
|   | 0b5afadbb8 | ||
|   | 43864f0da4 | ||
|   | 6a0d9d70ed | ||
|   | 63c6dce68e | ||
|   | 53422ffcb2 | ||
|   | 38ca514f53 | ||
|   | caea0f0376 | ||
|   | 25a8b26977 | ||
|   | bcaefe8d62 | ||
|   | 46f1e8c599 | ||
|   | 16230f320e | ||
|   | ace6419aef | ||
|   | 77fb9eb2be | ||
|   | aa7fc7c893 | ||
|   | 8fc170109f | ||
|   | ad12d00d7e | ||
|   | fa5ea45726 | ||
|   | 4b6c113251 | ||
|   | 3548290ff2 | ||
|   | b165b90c40 | ||
|   | 4ffe9c908b | ||
|   | a135f75e71 | ||
|   | cbc61ba03d | ||
|   | 5aa58da918 | ||
|   | b083430011 | ||
|   | a8946b0404 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 0303bccc61 | ||
|   | f3ce8564ea | 
| @@ -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: | ||||||
|   | |||||||
							
								
								
									
										50
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -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にフォローされていますマークを追加 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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`すると直る場合があります。 | ||||||
|  |  | ||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
|  |  | ||||||
| なにかお困りのことがありましたらお気軽にご連絡ください。 | なにかお困りのことがありましたらお気軽にご連絡ください。 | ||||||
|   | |||||||
| @@ -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: | ||||||
|   | |||||||
| @@ -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: | ||||||
|   | |||||||
| @@ -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: | ||||||
|   | |||||||
| @@ -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: | ||||||
|   | |||||||
| @@ -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: "メディア投稿のみ" | ||||||
|   | |||||||
| @@ -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: | ||||||
|   | |||||||
| @@ -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: | ||||||
|   | |||||||
| @@ -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: | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								package.json
									
									
									
									
									
								
							| @@ -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", | ||||||
|   | |||||||
| @@ -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', { | ||||||
|   | |||||||
| @@ -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' | ||||||
|  | 	})}`; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 ( | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								src/client/app/common/size.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/client/app/common/size.ts
									
									
									
									
									
										Normal 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); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
| @@ -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> |  | ||||||
							
								
								
									
										11
									
								
								src/client/app/common/views/components/dummy.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/client/app/common/views/components/dummy.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | <template> | ||||||
|  | <div> | ||||||
|  | 	<slot></slot> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | export default Vue.extend({ | ||||||
|  | }); | ||||||
|  | </script> | ||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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" | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
							
								
								
									
										161
									
								
								src/client/app/common/views/components/user-list.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								src/client/app/common/views/components/user-list.vue
									
									
									
									
									
										Normal 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> | ||||||
| @@ -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); | ||||||
| 		}, | 		}, | ||||||
|   | |||||||
							
								
								
									
										50
									
								
								src/client/app/common/views/pages/explore.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/client/app/common/views/pages/explore.vue
									
									
									
									
									
										Normal 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> | ||||||
							
								
								
									
										30
									
								
								src/client/app/common/views/pages/followers.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/client/app/common/views/pages/followers.vue
									
									
									
									
									
										Normal 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> | ||||||
							
								
								
									
										27
									
								
								src/client/app/common/views/pages/following.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/client/app/common/views/pages/following.vue
									
									
									
									
									
										Normal 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> | ||||||
| @@ -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> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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') }} >></a> | 			<a v-if="announcements.length > 1" @click="next">{{ $t('next') }} >></a> | ||||||
| 		</div> | 		</div> | ||||||
| 	</mk-widget-container> | 	</ui-container> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								src/client/app/common/views/widgets/instance.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/client/app/common/views/widgets/instance.vue
									
									
									
									
									
										Normal 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> | ||||||
| @@ -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> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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) { | ||||||
| 		/** | 		/** | ||||||
|   | |||||||
| @@ -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> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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> |  | ||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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({ | ||||||
|   | |||||||
| @@ -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> |  | ||||||
							
								
								
									
										117
									
								
								src/client/app/desktop/views/components/ui-container.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/client/app/desktop/views/components/ui-container.vue
									
									
									
									
									
										Normal 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> | ||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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> | ||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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% | ||||||
|   | |||||||
| @@ -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> |  | ||||||
| @@ -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() { | ||||||
| @@ -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'; | ||||||
| 
 | 
 | ||||||
							
								
								
									
										34
									
								
								src/client/app/desktop/views/deck/deck.explore-column.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/client/app/desktop/views/deck/deck.explore-column.vue
									
									
									
									
									
										Normal 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> | ||||||
							
								
								
									
										88
									
								
								src/client/app/desktop/views/deck/deck.favorites-column.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/client/app/desktop/views/deck/deck.favorites-column.vue
									
									
									
									
									
										Normal 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> | ||||||
							
								
								
									
										60
									
								
								src/client/app/desktop/views/deck/deck.featured-column.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/client/app/desktop/views/deck/deck.featured-column.vue
									
									
									
									
									
										Normal 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> | ||||||
							
								
								
									
										119
									
								
								src/client/app/desktop/views/deck/deck.hashtag-column.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								src/client/app/desktop/views/deck/deck.hashtag-column.vue
									
									
									
									
									
										Normal 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> | ||||||
| @@ -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'; | ||||||
| 
 | 
 | ||||||
| @@ -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> | ||||||
| 
 | 
 | ||||||
| @@ -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; | ||||||
| 
 | 
 | ||||||
| @@ -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: { | ||||||
| @@ -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'; | ||||||
| 
 | 
 | ||||||
| @@ -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; | ||||||
							
								
								
									
										99
									
								
								src/client/app/desktop/views/deck/deck.search-column.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/client/app/desktop/views/deck/deck.search-column.vue
									
									
									
									
									
										Normal 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> | ||||||
| @@ -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'; | ||||||
| @@ -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> | ||||||
							
								
								
									
										244
									
								
								src/client/app/desktop/views/deck/deck.user-column.home.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								src/client/app/desktop/views/deck/deck.user-column.home.vue
									
									
									
									
									
										Normal 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> | ||||||
							
								
								
									
										262
									
								
								src/client/app/desktop/views/deck/deck.user-column.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								src/client/app/desktop/views/deck/deck.user-column.vue
									
									
									
									
									
										Normal 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> | ||||||
| @@ -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, | ||||||
| @@ -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'; | ||||||
| @@ -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 | ||||||
							
								
								
									
										55
									
								
								src/client/app/desktop/views/home/featured.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/client/app/desktop/views/home/featured.vue
									
									
									
									
									
										Normal 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> | ||||||
							
								
								
									
										404
									
								
								src/client/app/desktop/views/home/home.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								src/client/app/desktop/views/home/home.vue
									
									
									
									
									
										Normal 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> | ||||||
| @@ -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> | ||||||
| @@ -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> | ||||||
| @@ -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 | ||||||
							
								
								
									
										273
									
								
								src/client/app/desktop/views/home/timeline.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										273
									
								
								src/client/app/desktop/views/home/timeline.vue
									
									
									
									
									
										Normal 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> | ||||||
							
								
								
									
										82
									
								
								src/client/app/desktop/views/home/user/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/client/app/desktop/views/home/user/index.vue
									
									
									
									
									
										Normal 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> | ||||||
| @@ -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> | ||||||
| @@ -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 | ||||||
| 
 | 
 | ||||||
							
								
								
									
										57
									
								
								src/client/app/desktop/views/home/user/user.home.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/client/app/desktop/views/home/user/user.home.vue
									
									
									
									
									
										Normal 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
		Reference in New Issue
	
	Block a user