Compare commits
	
		
			103 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					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 | ||
| 
						 | 
					0303bccc61 | ||
| 
						 | 
					f3ce8564ea | ||
| 
						 | 
					52c3f9e98c | ||
| 
						 | 
					6c8b4184fe | ||
| 
						 | 
					a0979f8435 | ||
| 
						 | 
					faba21d003 | ||
| 
						 | 
					d82c5dff71 | ||
| 
						 | 
					59fbc5b054 | ||
| 
						 | 
					2c1a7f4392 | ||
| 
						 | 
					769e6182d8 | ||
| 
						 | 
					88176a17a3 | ||
| 
						 | 
					fc660e869f | ||
| 
						 | 
					dc04869650 | ||
| 
						 | 
					93c3f34813 | ||
| 
						 | 
					1282eed192 | ||
| 
						 | 
					962b3ca78e | ||
| 
						 | 
					62d17c9266 | ||
| 
						 | 
					f5b928a537 | ||
| 
						 | 
					c8811894b5 | ||
| 
						 | 
					e579b49228 | ||
| 
						 | 
					9561908ad3 | ||
| 
						 | 
					fac7ebf4f6 | ||
| 
						 | 
					a0769d65e3 | ||
| 
						 | 
					d17aa4b24e | ||
| 
						 | 
					310371658b | ||
| 
						 | 
					7ca073aafd | ||
| 
						 | 
					7216d0fb1f | ||
| 
						 | 
					22a9e950c7 | ||
| 
						 | 
					6683d50bae | ||
| 
						 | 
					8f26176273 | 
@@ -2,6 +2,11 @@ version: 2.1
 | 
			
		||||
 | 
			
		||||
executors:
 | 
			
		||||
  default:
 | 
			
		||||
    working_directory: /tmp/workspace
 | 
			
		||||
    docker:
 | 
			
		||||
      - image: misskey/ci:latest
 | 
			
		||||
      - image: circleci/mongo:latest
 | 
			
		||||
  with-redis:
 | 
			
		||||
    working_directory: /tmp/workspace
 | 
			
		||||
    docker:
 | 
			
		||||
      - image: misskey/ci:latest
 | 
			
		||||
@@ -24,7 +29,6 @@ jobs:
 | 
			
		||||
          name: OK
 | 
			
		||||
          command: |
 | 
			
		||||
            echo -e '\033[0;32mOK\033[0;39m'
 | 
			
		||||
 | 
			
		||||
  build:
 | 
			
		||||
    executor: default
 | 
			
		||||
    steps:
 | 
			
		||||
@@ -68,10 +72,13 @@ jobs:
 | 
			
		||||
            - .
 | 
			
		||||
  test:
 | 
			
		||||
    parameters:
 | 
			
		||||
      without_redis:
 | 
			
		||||
      executor:
 | 
			
		||||
        type: string
 | 
			
		||||
        default: ""
 | 
			
		||||
    executor: default
 | 
			
		||||
        default: "default"
 | 
			
		||||
      without_redis:
 | 
			
		||||
        type: boolean
 | 
			
		||||
        default: false
 | 
			
		||||
    executor: <<parameters.executor>>
 | 
			
		||||
    steps:
 | 
			
		||||
      - attach_workspace:
 | 
			
		||||
          at: /tmp/workspace
 | 
			
		||||
@@ -94,12 +101,11 @@ jobs:
 | 
			
		||||
          key: yarn-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-package-{{ checksum "package.json" }}-lock-{{ checksum "yarn.lock" }}
 | 
			
		||||
          paths:
 | 
			
		||||
            - node_modules
 | 
			
		||||
 | 
			
		||||
  docker:
 | 
			
		||||
    parameters:
 | 
			
		||||
      with_deploy:
 | 
			
		||||
        type: string
 | 
			
		||||
        default: ""
 | 
			
		||||
        type: boolean
 | 
			
		||||
        default: false
 | 
			
		||||
    executor: docker
 | 
			
		||||
    steps:
 | 
			
		||||
      - checkout
 | 
			
		||||
@@ -126,7 +132,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
workflows:
 | 
			
		||||
  version: 2
 | 
			
		||||
  build-and-test:
 | 
			
		||||
  nodejs:
 | 
			
		||||
    jobs:
 | 
			
		||||
      - ok:
 | 
			
		||||
          filters:
 | 
			
		||||
@@ -135,7 +141,14 @@ workflows:
 | 
			
		||||
                - l10n_develop
 | 
			
		||||
                - imgbot
 | 
			
		||||
                - patch-autogen
 | 
			
		||||
      - hold:
 | 
			
		||||
          type: approval
 | 
			
		||||
          filters:
 | 
			
		||||
            branches:
 | 
			
		||||
              ignore: master
 | 
			
		||||
      - build:
 | 
			
		||||
          requires:
 | 
			
		||||
            - hold
 | 
			
		||||
          filters:
 | 
			
		||||
            branches:
 | 
			
		||||
              ignore:
 | 
			
		||||
@@ -143,6 +156,7 @@ workflows:
 | 
			
		||||
                - imgbot
 | 
			
		||||
                - patch-autogen
 | 
			
		||||
      - test:
 | 
			
		||||
          executor: with-redis
 | 
			
		||||
          requires:
 | 
			
		||||
            - build
 | 
			
		||||
          filters:
 | 
			
		||||
@@ -153,7 +167,7 @@ workflows:
 | 
			
		||||
                - imgbot
 | 
			
		||||
                - patch-autogen
 | 
			
		||||
      - test:
 | 
			
		||||
          without_redis: "true"
 | 
			
		||||
          without_redis: true
 | 
			
		||||
          requires:
 | 
			
		||||
            - build
 | 
			
		||||
          filters:
 | 
			
		||||
@@ -165,12 +179,21 @@ workflows:
 | 
			
		||||
                - l10n_develop
 | 
			
		||||
                - imgbot
 | 
			
		||||
                - patch-autogen
 | 
			
		||||
#      - docker:
 | 
			
		||||
#          filters:
 | 
			
		||||
#            branches:
 | 
			
		||||
#              ignore: master
 | 
			
		||||
  docker:
 | 
			
		||||
    jobs:
 | 
			
		||||
      - hold:
 | 
			
		||||
          type: approval
 | 
			
		||||
          filters:
 | 
			
		||||
            branches:
 | 
			
		||||
              ignore: master
 | 
			
		||||
      - docker:
 | 
			
		||||
          with_deploy: "true"
 | 
			
		||||
          requires:
 | 
			
		||||
            - hold
 | 
			
		||||
          filters:
 | 
			
		||||
            branches:
 | 
			
		||||
              ignore: master
 | 
			
		||||
      - docker:
 | 
			
		||||
          with_deploy: true
 | 
			
		||||
          filters:
 | 
			
		||||
            branches:
 | 
			
		||||
              only: master
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										49
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,6 +1,55 @@
 | 
			
		||||
ChangeLog
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
----------
 | 
			
		||||
* deckにフォローされていますマークを追加
 | 
			
		||||
* URLプレビューのサムネイルの調整
 | 
			
		||||
* 管理画面でサイレンスされているユーザーを一覧できるように
 | 
			
		||||
* ドキュメントにアクセスできない問題を修正
 | 
			
		||||
* ジョブキューを無効化
 | 
			
		||||
* 軽微なバグ修正
 | 
			
		||||
 | 
			
		||||
10.84.0
 | 
			
		||||
----------
 | 
			
		||||
* インスタンス管理の強化
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								Dockerfile
									
									
									
									
									
								
							@@ -8,7 +8,6 @@ WORKDIR /misskey
 | 
			
		||||
 | 
			
		||||
FROM base AS builder
 | 
			
		||||
 | 
			
		||||
RUN unlink /usr/bin/free
 | 
			
		||||
RUN apk add --no-cache \
 | 
			
		||||
    autoconf \
 | 
			
		||||
    automake \
 | 
			
		||||
@@ -20,18 +19,13 @@ RUN apk add --no-cache \
 | 
			
		||||
    make \
 | 
			
		||||
    nasm \
 | 
			
		||||
    pkgconfig \
 | 
			
		||||
    procps \
 | 
			
		||||
    python \
 | 
			
		||||
    zlib-dev
 | 
			
		||||
RUN npm i -g node-gyp
 | 
			
		||||
 | 
			
		||||
COPY ./package.json ./
 | 
			
		||||
RUN npm i
 | 
			
		||||
RUN npm i -g yarn
 | 
			
		||||
 | 
			
		||||
COPY . ./
 | 
			
		||||
RUN node-gyp configure \
 | 
			
		||||
 && node-gyp build \
 | 
			
		||||
 && npm run build
 | 
			
		||||
RUN yarn install
 | 
			
		||||
RUN yarn build
 | 
			
		||||
 | 
			
		||||
FROM base AS runner
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							@@ -3,9 +3,9 @@
 | 
			
		||||
[](https://misskey.xyz/)
 | 
			
		||||
================================================================
 | 
			
		||||
 | 
			
		||||
[](https://circleci.com/gh/syuilo/misskey)
 | 
			
		||||
[](https://david-dm.org/syuilo/misskey)
 | 
			
		||||
[](http://makeapullrequest.com)
 | 
			
		||||
[](https://circleci.com/gh/syuilo/misskey)
 | 
			
		||||
[](https://david-dm.org/syuilo/misskey)
 | 
			
		||||
[](http://makeapullrequest.com)
 | 
			
		||||
 | 
			
		||||
**A forever evolving, sophisticated microblogging platform.**
 | 
			
		||||
 | 
			
		||||
@@ -94,7 +94,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
 | 
			
		||||
<!-- PATREON_START -->
 | 
			
		||||
<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/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://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>
 | 
			
		||||
@@ -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>
 | 
			
		||||
</tr><tr>
 | 
			
		||||
<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=16869916">見当かなみ</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://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://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/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>
 | 
			
		||||
@@ -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=13039004">nemu</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/acid_chicken">Acid Chicken</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>
 | 
			
		||||
</tr></table>
 | 
			
		||||
 | 
			
		||||
**Last updated:** Wed, 06 Feb 2019 18:18:05 UTC
 | 
			
		||||
**Last updated:** Fri, 15 Feb 2019 19:12:06 UTC
 | 
			
		||||
<!-- PATREON_END -->
 | 
			
		||||
 | 
			
		||||
:four_leaf_clover: Copyright
 | 
			
		||||
 
 | 
			
		||||
@@ -122,6 +122,8 @@ CentOSで1024以下のポートを使用してMisskeyを使用する場合は`Ex
 | 
			
		||||
4. `npm run build`
 | 
			
		||||
5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する
 | 
			
		||||
 | 
			
		||||
なにか問題が発生した場合は、`npm run clean`すると直る場合があります。
 | 
			
		||||
 | 
			
		||||
----------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
なにかお困りのことがありましたらお気軽にご連絡ください。
 | 
			
		||||
 
 | 
			
		||||
@@ -1186,6 +1186,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "モデレーター"
 | 
			
		||||
      adminOrModerator: "管理者+モデレーター"
 | 
			
		||||
      verified: "公式アカウント"
 | 
			
		||||
      silenced: "サイレンス済み"
 | 
			
		||||
      suspended: "凍結済み"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "オリジン"
 | 
			
		||||
@@ -1590,13 +1591,13 @@ mobile/views/pages/user/home.vue:
 | 
			
		||||
  activity: "アクティビティ"
 | 
			
		||||
  keywords: "キーワード"
 | 
			
		||||
  domains: "頻出ドメイン"
 | 
			
		||||
  frequently-replied-users: "よく会話するユーザー"
 | 
			
		||||
  frequently-replied-users: "よく話すユーザー"
 | 
			
		||||
  followers-you-know: "知り合いのフォロワー"
 | 
			
		||||
  last-used-at: "最終ログイン"
 | 
			
		||||
mobile/views/pages/user/home.followers-you-know.vue:
 | 
			
		||||
  no-users: "知り合いのユーザーはいません"
 | 
			
		||||
mobile/views/pages/user/home.friends.vue:
 | 
			
		||||
  no-users: "よく会話するユーザーはいません"
 | 
			
		||||
  no-users: "よく話すユーザーはいません"
 | 
			
		||||
mobile/views/pages/user/home.notes.vue:
 | 
			
		||||
  no-notes: "投稿はありません"
 | 
			
		||||
mobile/views/pages/user/home.photos.vue:
 | 
			
		||||
@@ -1626,6 +1627,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-view: "メディアビュー"
 | 
			
		||||
  edit: "オプション"
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  posts: "投稿"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
 
 | 
			
		||||
@@ -1186,6 +1186,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "モデレーター"
 | 
			
		||||
      adminOrModerator: "管理者+モデレーター"
 | 
			
		||||
      verified: "公式アカウント"
 | 
			
		||||
      silenced: "サイレンス済み"
 | 
			
		||||
      suspended: "凍結済み"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "オリジン"
 | 
			
		||||
@@ -1590,13 +1591,13 @@ mobile/views/pages/user/home.vue:
 | 
			
		||||
  activity: "アクティビティ"
 | 
			
		||||
  keywords: "Schlagwörter"
 | 
			
		||||
  domains: "頻出ドメイン"
 | 
			
		||||
  frequently-replied-users: "よく会話するユーザー"
 | 
			
		||||
  frequently-replied-users: "よく話すユーザー"
 | 
			
		||||
  followers-you-know: "知り合いのフォロワー"
 | 
			
		||||
  last-used-at: "最終ログイン"
 | 
			
		||||
mobile/views/pages/user/home.followers-you-know.vue:
 | 
			
		||||
  no-users: "知り合いのユーザーはいません"
 | 
			
		||||
mobile/views/pages/user/home.friends.vue:
 | 
			
		||||
  no-users: "よく会話するユーザーはいません"
 | 
			
		||||
  no-users: "よく話すユーザーはいません"
 | 
			
		||||
mobile/views/pages/user/home.notes.vue:
 | 
			
		||||
  no-notes: "投稿はありません"
 | 
			
		||||
mobile/views/pages/user/home.photos.vue:
 | 
			
		||||
@@ -1626,6 +1627,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-view: "メディアビュー"
 | 
			
		||||
  edit: "オプション"
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  posts: "投稿"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
 
 | 
			
		||||
@@ -1118,7 +1118,7 @@ admin/views/charts.vue:
 | 
			
		||||
    users: "The number of users: increase/decrease"
 | 
			
		||||
    users-total: "Total users"
 | 
			
		||||
    active-users: "Active users"
 | 
			
		||||
    drive: "Capacity used as the storage: increase/decrease"
 | 
			
		||||
    drive: "Increase and decrease in storage capacity use"
 | 
			
		||||
    drive-total: "Total usage of Drive"
 | 
			
		||||
    drive-files: "The number of files on the storage: increase/decrease"
 | 
			
		||||
    drive-files-total: "Total number of files on Drive"
 | 
			
		||||
@@ -1186,6 +1186,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "Moderator"
 | 
			
		||||
      adminOrModerator: "Admin/Moderator"
 | 
			
		||||
      verified: "Verified account"
 | 
			
		||||
      silenced: "Already silenced"
 | 
			
		||||
      suspended: "Suspended"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "Origin"
 | 
			
		||||
@@ -1244,7 +1245,7 @@ admin/views/federation.vue:
 | 
			
		||||
  latest-request-sent-at: "Time of last request sent"
 | 
			
		||||
  latest-request-received-at: "Last request received at"
 | 
			
		||||
  remove-all-following: "Withold all followers"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  remove-all-following-info: "Unfollow all accounts from {host}. Please run this if the instance no longer exists."
 | 
			
		||||
  block: "Block"
 | 
			
		||||
  marked-as-closed: "Marked as closed"
 | 
			
		||||
  lookup: "Look up"
 | 
			
		||||
@@ -1254,8 +1255,8 @@ admin/views/federation.vue:
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "Date of discovery (Ascending)"
 | 
			
		||||
    caughtAtDesc: "Date of discovery (Descending)"
 | 
			
		||||
    lastCommunicatedAtAsc: "最後にやり取りした日時が古い順"
 | 
			
		||||
    lastCommunicatedAtDesc: "最後にやり取りした日時が新しい順"
 | 
			
		||||
    lastCommunicatedAtAsc: "The date and time of the older interactions"
 | 
			
		||||
    lastCommunicatedAtDesc: "The date and time of the newer interactions"
 | 
			
		||||
    notesAsc: "Order by least Notes posted"
 | 
			
		||||
    notesDesc: "Order by most Notes posted"
 | 
			
		||||
    usersAsc: "Less followers"
 | 
			
		||||
@@ -1264,10 +1265,10 @@ admin/views/federation.vue:
 | 
			
		||||
    followingDesc: "Has more followers"
 | 
			
		||||
    followersAsc: "Sort by having less followers"
 | 
			
		||||
    followersDesc: "Sort by the larger number of followers"
 | 
			
		||||
    driveUsageAsc: "ドライブ使用量が少ない順"
 | 
			
		||||
    driveUsageDesc: "ドライブ使用量が多い順"
 | 
			
		||||
    driveFilesAsc: "ドライブのファイル数が少ない順"
 | 
			
		||||
    driveFilesDesc: "ドライブのファイル数が多い順"
 | 
			
		||||
    driveUsageAsc: "Least storage used"
 | 
			
		||||
    driveUsageDesc: "Most storage used"
 | 
			
		||||
    driveFilesAsc: "By the smallest number of files stored on Drive"
 | 
			
		||||
    driveFilesDesc: "By the largest number of files stored on Drive"
 | 
			
		||||
  state: "Status"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "All"
 | 
			
		||||
@@ -1282,12 +1283,12 @@ admin/views/federation.vue:
 | 
			
		||||
    users-total: "Total number of users"
 | 
			
		||||
    notes: "Increase, or decrease in the number of notes"
 | 
			
		||||
    notes-total: "Total number of notes"
 | 
			
		||||
    ff: "フォロー/フォロワーの増減"
 | 
			
		||||
    ff-total: "フォロー/フォロワーの積算"
 | 
			
		||||
    drive-usage: "ドライブ使用量の増減"
 | 
			
		||||
    drive-usage-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブファイル数の積算"
 | 
			
		||||
    ff: "Increase of followers"
 | 
			
		||||
    ff-total: "Total number of follows accumulated"
 | 
			
		||||
    drive-usage: "Increase and decrease in storage use"
 | 
			
		||||
    drive-usage-total: "Total usage of the Drive"
 | 
			
		||||
    drive-files: "Increase, or decrease in the number of files stored on Drive"
 | 
			
		||||
    drive-files-total: "The number of files accumulated on Drive"
 | 
			
		||||
  chart-spans:
 | 
			
		||||
    hour: "Hourly"
 | 
			
		||||
    day: "Daily"
 | 
			
		||||
@@ -1626,6 +1627,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-view: "Media view"
 | 
			
		||||
  edit: "Options"
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "Follows you"
 | 
			
		||||
  posts: "Posts"
 | 
			
		||||
  following: "Following"
 | 
			
		||||
  followers: "Followers"
 | 
			
		||||
 
 | 
			
		||||
@@ -1186,6 +1186,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "モデレーター"
 | 
			
		||||
      adminOrModerator: "管理者+モデレーター"
 | 
			
		||||
      verified: "公式アカウント"
 | 
			
		||||
      silenced: "サイレンス済み"
 | 
			
		||||
      suspended: "凍結済み"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "オリジン"
 | 
			
		||||
@@ -1590,13 +1591,13 @@ mobile/views/pages/user/home.vue:
 | 
			
		||||
  activity: "アクティビティ"
 | 
			
		||||
  keywords: "キーワード"
 | 
			
		||||
  domains: "頻出ドメイン"
 | 
			
		||||
  frequently-replied-users: "よく会話するユーザー"
 | 
			
		||||
  frequently-replied-users: "よく話すユーザー"
 | 
			
		||||
  followers-you-know: "知り合いのフォロワー"
 | 
			
		||||
  last-used-at: "最終ログイン"
 | 
			
		||||
mobile/views/pages/user/home.followers-you-know.vue:
 | 
			
		||||
  no-users: "知り合いのユーザーはいません"
 | 
			
		||||
mobile/views/pages/user/home.friends.vue:
 | 
			
		||||
  no-users: "よく会話するユーザーはいません"
 | 
			
		||||
  no-users: "よく話すユーザーはいません"
 | 
			
		||||
mobile/views/pages/user/home.notes.vue:
 | 
			
		||||
  no-notes: "投稿はありません"
 | 
			
		||||
mobile/views/pages/user/home.photos.vue:
 | 
			
		||||
@@ -1626,6 +1627,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-view: "メディアビュー"
 | 
			
		||||
  edit: "オプション"
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  posts: "投稿"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
 
 | 
			
		||||
@@ -1186,6 +1186,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "Modérateur"
 | 
			
		||||
      adminOrModerator: "Administrateur/Modérateur"
 | 
			
		||||
      verified: "Compte vérifié"
 | 
			
		||||
      silenced: "サイレンス済み"
 | 
			
		||||
      suspended: "Suspendu"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "Origine"
 | 
			
		||||
@@ -1626,6 +1627,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-view: "Vue média"
 | 
			
		||||
  edit: "Option"
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  posts: "Notes"
 | 
			
		||||
  following: "Suit"
 | 
			
		||||
  followers: "Abonnés"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								locales/index.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								locales/index.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,3 @@
 | 
			
		||||
type Locale = { [key: string]: string };
 | 
			
		||||
declare const locales: { [lang: string]: any };
 | 
			
		||||
 | 
			
		||||
declare const locales: { [lang: string]: Locale };
 | 
			
		||||
 | 
			
		||||
export default locales;
 | 
			
		||||
export = locales;
 | 
			
		||||
 
 | 
			
		||||
@@ -1186,6 +1186,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "モデレーター"
 | 
			
		||||
      adminOrModerator: "管理者+モデレーター"
 | 
			
		||||
      verified: "公式アカウント"
 | 
			
		||||
      silenced: "サイレンス済み"
 | 
			
		||||
      suspended: "凍結済み"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "オリジン"
 | 
			
		||||
@@ -1590,13 +1591,13 @@ mobile/views/pages/user/home.vue:
 | 
			
		||||
  activity: "アクティビティ"
 | 
			
		||||
  keywords: "キーワード"
 | 
			
		||||
  domains: "頻出ドメイン"
 | 
			
		||||
  frequently-replied-users: "よく会話するユーザー"
 | 
			
		||||
  frequently-replied-users: "よく話すユーザー"
 | 
			
		||||
  followers-you-know: "知り合いのフォロワー"
 | 
			
		||||
  last-used-at: "最終ログイン"
 | 
			
		||||
mobile/views/pages/user/home.followers-you-know.vue:
 | 
			
		||||
  no-users: "知り合いのユーザーはいません"
 | 
			
		||||
mobile/views/pages/user/home.friends.vue:
 | 
			
		||||
  no-users: "よく会話するユーザーはいません"
 | 
			
		||||
  no-users: "よく話すユーザーはいません"
 | 
			
		||||
mobile/views/pages/user/home.notes.vue:
 | 
			
		||||
  no-notes: "投稿はありません"
 | 
			
		||||
mobile/views/pages/user/home.photos.vue:
 | 
			
		||||
@@ -1626,6 +1627,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-view: "メディアビュー"
 | 
			
		||||
  edit: "オプション"
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  posts: "投稿"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,8 @@ common:
 | 
			
		||||
  load-more: "もっと読み込む"
 | 
			
		||||
  enter-password: "パスワードを入力してください"
 | 
			
		||||
  2fa: "二段階認証"
 | 
			
		||||
  customize-home: "ホームをカスタマイズ"
 | 
			
		||||
  featured-notes: "ハイライト"
 | 
			
		||||
 | 
			
		||||
  got-it: "わかった"
 | 
			
		||||
  customization-tips:
 | 
			
		||||
@@ -58,6 +60,11 @@ common:
 | 
			
		||||
  trash: "ゴミ箱"
 | 
			
		||||
  drive: "ドライブ"
 | 
			
		||||
  messaging: "トーク"
 | 
			
		||||
  deck: "デッキ"
 | 
			
		||||
  timeline: "タイムライン"
 | 
			
		||||
  explore: "みつける"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
 | 
			
		||||
  weekday-short:
 | 
			
		||||
    sunday: "日"
 | 
			
		||||
@@ -862,6 +869,9 @@ desktop/views/components/renote-form.vue:
 | 
			
		||||
desktop/views/components/renote-form-window.vue:
 | 
			
		||||
  title: "この投稿をRenoteしますか?"
 | 
			
		||||
 | 
			
		||||
desktop/views/components/timeline.core.vue:
 | 
			
		||||
  empty: "投稿がありません"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/user-following-or-followers.vue:
 | 
			
		||||
  following: "{user}のフォロー"
 | 
			
		||||
  followers: "{user}のフォロワー"
 | 
			
		||||
@@ -893,14 +903,10 @@ desktop/views/components/settings.vue:
 | 
			
		||||
  web-search-engine-desc: "例: https://www.google.com/?#q={{query}}"
 | 
			
		||||
  auto-popout: "ウィンドウの自動ポップアウト"
 | 
			
		||||
  auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
 | 
			
		||||
  deck-nav: "デッキ内ナビゲーション"
 | 
			
		||||
  deck-nav-desc: "デッキを使用しているとき、ナビゲーションが発生する際にページ遷移を行わずに一時的なカラムで受けるようにします。"
 | 
			
		||||
  keep-cw: "CW保持"
 | 
			
		||||
  keep-cw-desc: "投稿にリプライする際、リプライ元の投稿にCWが設定されていたとき、デフォルトで同じCWを設定するようにします。"
 | 
			
		||||
  deck-default: "デッキをデフォルトのUIにする"
 | 
			
		||||
 | 
			
		||||
  display: "デザインと表示"
 | 
			
		||||
  customize: "ホームをカスタマイズ"
 | 
			
		||||
  wallpaper: "壁紙"
 | 
			
		||||
  choose-wallpaper: "壁紙を選択"
 | 
			
		||||
  delete-wallpaper: "壁紙を削除"
 | 
			
		||||
@@ -1076,15 +1082,12 @@ desktop/views/components/ui.header.account.vue:
 | 
			
		||||
  favorites: "お気に入り"
 | 
			
		||||
  lists: "リスト"
 | 
			
		||||
  follow-requests: "フォロー申請"
 | 
			
		||||
  customize: "ホームのカスタマイズ"
 | 
			
		||||
  admin: "管理"
 | 
			
		||||
  settings: "設定"
 | 
			
		||||
  signout: "サインアウト"
 | 
			
		||||
  dark: "闇に飲まれる"
 | 
			
		||||
 | 
			
		||||
desktop/views/components/ui.header.nav.vue:
 | 
			
		||||
  home: "ホーム"
 | 
			
		||||
  deck: "デッキ"
 | 
			
		||||
  game: "ゲーム"
 | 
			
		||||
 | 
			
		||||
desktop/views/components/ui.header.notifications.vue:
 | 
			
		||||
@@ -1319,6 +1322,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "モデレーター"
 | 
			
		||||
      adminOrModerator: "管理者+モデレーター"
 | 
			
		||||
      verified: "公式アカウント"
 | 
			
		||||
      silenced: "サイレンス済み"
 | 
			
		||||
      suspended: "凍結済み"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "オリジン"
 | 
			
		||||
@@ -1446,9 +1450,6 @@ desktop/views/pages/welcome.vue:
 | 
			
		||||
desktop/views/pages/drive.vue:
 | 
			
		||||
  title: "Misskey Drive"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/home-customize.vue:
 | 
			
		||||
  title: "ホームのカスタマイズ"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/note.vue:
 | 
			
		||||
  prev: "前の投稿"
 | 
			
		||||
  next: "次の投稿"
 | 
			
		||||
@@ -1489,10 +1490,6 @@ desktop/views/pages/user/user.photos.vue:
 | 
			
		||||
  loading: "読み込み中"
 | 
			
		||||
  no-photos: "写真はありません"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/user/user.profile.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  menu: "メニュー"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/user/user.header.vue:
 | 
			
		||||
  posts: "投稿"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
@@ -1502,6 +1499,7 @@ desktop/views/pages/user/user.header.vue:
 | 
			
		||||
  year: "年"
 | 
			
		||||
  month: "月"
 | 
			
		||||
  day: "日"
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
 | 
			
		||||
desktop/views/pages/user/user.timeline.vue:
 | 
			
		||||
  default: "投稿"
 | 
			
		||||
@@ -1660,10 +1658,6 @@ mobile/views/components/user-timeline.vue:
 | 
			
		||||
  no-notes: "このユーザーは投稿していないようです。"
 | 
			
		||||
  no-notes-with-media: "メディア付き投稿はありません。"
 | 
			
		||||
 | 
			
		||||
mobile/views/components/users-list.vue:
 | 
			
		||||
  all: "すべて"
 | 
			
		||||
  known: "知り合い"
 | 
			
		||||
 | 
			
		||||
mobile/views/pages/favorites.vue:
 | 
			
		||||
  title: "お気に入り"
 | 
			
		||||
 | 
			
		||||
@@ -1688,6 +1682,9 @@ mobile/views/pages/home.vue:
 | 
			
		||||
  mentions: "あなた宛て"
 | 
			
		||||
  messages: "メッセージ"
 | 
			
		||||
 | 
			
		||||
mobile/views/pages/home.timeline.vue:
 | 
			
		||||
  empty: "投稿がありません"
 | 
			
		||||
 | 
			
		||||
mobile/views/pages/tag.vue:
 | 
			
		||||
  no-posts-found: "ハッシュタグ「{q}」が付けられた投稿は見つかりませんでした。"
 | 
			
		||||
 | 
			
		||||
@@ -1790,7 +1787,7 @@ mobile/views/pages/user/home.vue:
 | 
			
		||||
  activity: "アクティビティ"
 | 
			
		||||
  keywords: "キーワード"
 | 
			
		||||
  domains: "頻出ドメイン"
 | 
			
		||||
  frequently-replied-users: "よく会話するユーザー"
 | 
			
		||||
  frequently-replied-users: "よく話すユーザー"
 | 
			
		||||
  followers-you-know: "知り合いのフォロワー"
 | 
			
		||||
  last-used-at: "最終ログイン"
 | 
			
		||||
 | 
			
		||||
@@ -1798,7 +1795,7 @@ mobile/views/pages/user/home.followers-you-know.vue:
 | 
			
		||||
  no-users: "知り合いのユーザーはいません"
 | 
			
		||||
 | 
			
		||||
mobile/views/pages/user/home.friends.vue:
 | 
			
		||||
  no-users: "よく会話するユーザーはいません"
 | 
			
		||||
  no-users: "よく話すユーザーはいません"
 | 
			
		||||
 | 
			
		||||
mobile/views/pages/user/home.notes.vue:
 | 
			
		||||
  no-notes: "投稿はありません"
 | 
			
		||||
@@ -1833,6 +1830,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  edit: "オプション"
 | 
			
		||||
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  posts: "投稿"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
 
 | 
			
		||||
@@ -1186,6 +1186,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "モデレーター"
 | 
			
		||||
      adminOrModerator: "管理者+モデレーター"
 | 
			
		||||
      verified: "公式アカウント"
 | 
			
		||||
      silenced: "サイレンス済み"
 | 
			
		||||
      suspended: "凍結済み"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "オリジン"
 | 
			
		||||
@@ -1626,6 +1627,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-view: "メディアビュー"
 | 
			
		||||
  edit: "オプション"
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  posts: "投稿"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
 
 | 
			
		||||
@@ -113,7 +113,7 @@ common:
 | 
			
		||||
  use-avatar-reversi-stones: "리버시의 돌로 아바타를 사용"
 | 
			
		||||
  verified-user: "공식 계정"
 | 
			
		||||
  disable-animated-mfm: "글의 문자 애니메이션을 비활성화"
 | 
			
		||||
  disable-showing-animated-images: "アニメーション画像を再生しない"
 | 
			
		||||
  disable-showing-animated-images: "움직이는 이미지를 자동으로 재생하지 않음"
 | 
			
		||||
  suggest-recent-hashtags: "최근 해시태그를 글 작성란에 표시"
 | 
			
		||||
  always-show-nsfw: "항상 열람주의 미디어를 표시"
 | 
			
		||||
  always-mark-nsfw: "항상 미디어를 열람주의로 설정하여 게시"
 | 
			
		||||
@@ -345,8 +345,8 @@ common/views/components/note-menu.vue:
 | 
			
		||||
  copy-link: "링크 복사"
 | 
			
		||||
  favorite: "이 노트 즐겨찾기"
 | 
			
		||||
  unfavorite: "즐겨찾기에서 제거"
 | 
			
		||||
  watch: "ウォッチ"
 | 
			
		||||
  unwatch: "ウォッチ解除"
 | 
			
		||||
  watch: "지켜보기"
 | 
			
		||||
  unwatch: "지켜보기 해제"
 | 
			
		||||
  pin: "프로필에 고정"
 | 
			
		||||
  unpin: "프로필에서 고정 해제"
 | 
			
		||||
  delete: "삭제"
 | 
			
		||||
@@ -509,13 +509,13 @@ common/views/components/profile-editor.vue:
 | 
			
		||||
  email-address: "메일 주소"
 | 
			
		||||
  email-verified: "매일 주소가 확인되었습니다"
 | 
			
		||||
  email-not-verified: "메일 주소가 확인되지 않았습니다. 받은 편지함을 확인하여 주시기 바랍니다."
 | 
			
		||||
  export: "エクスポート"
 | 
			
		||||
  export: "내보내기"
 | 
			
		||||
  export-targets:
 | 
			
		||||
    all-notes: "すべての投稿データ"
 | 
			
		||||
    following-list: "フォロー"
 | 
			
		||||
    mute-list: "ミュート"
 | 
			
		||||
    blocking-list: "ブロック"
 | 
			
		||||
  export-requested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、ドライブにファイルが追加されます。"
 | 
			
		||||
    all-notes: "모든 글 데이터"
 | 
			
		||||
    following-list: "팔로잉"
 | 
			
		||||
    mute-list: "뮤트"
 | 
			
		||||
    blocking-list: "차단"
 | 
			
		||||
  export-requested: "내보내기를 요청하였습니다. 이 작업은 시간이 걸릴 수 있습니다. 내보내기가 완료되면 드라이브에 파일이 추가됩니다."
 | 
			
		||||
common/views/components/user-list-editor.vue:
 | 
			
		||||
  users: "사용자"
 | 
			
		||||
  rename: "리스트 이름 바꾸기"
 | 
			
		||||
@@ -1007,7 +1007,7 @@ admin/views/index.vue:
 | 
			
		||||
  announcements: "공지사항"
 | 
			
		||||
  hashtags: "해시태그"
 | 
			
		||||
  abuse: "스팸 신고"
 | 
			
		||||
  queue: "ジョブキュー"
 | 
			
		||||
  queue: "작업 대기열"
 | 
			
		||||
  back-to-misskey: "Misskey로 돌아가기"
 | 
			
		||||
admin/views/dashboard.vue:
 | 
			
		||||
  dashboard: "대시보드"
 | 
			
		||||
@@ -1018,8 +1018,8 @@ admin/views/dashboard.vue:
 | 
			
		||||
  this-instance: "이 인스턴스"
 | 
			
		||||
  federated: "연합"
 | 
			
		||||
admin/views/queue.vue:
 | 
			
		||||
  operation: "操作"
 | 
			
		||||
  remove-all-jobs: "すべてのジョブをクリア"
 | 
			
		||||
  operation: "동작"
 | 
			
		||||
  remove-all-jobs: "모든 작업 제거"
 | 
			
		||||
admin/views/abuse.vue:
 | 
			
		||||
  title: "스팸 신고"
 | 
			
		||||
  target: "대상"
 | 
			
		||||
@@ -1114,7 +1114,7 @@ admin/views/charts.vue:
 | 
			
		||||
    notes: "글 증감 (통합)"
 | 
			
		||||
    local-notes: "글 증감 (로컬)"
 | 
			
		||||
    remote-notes: "글 증감 (원격)"
 | 
			
		||||
    notes-total: "글 누적 수"
 | 
			
		||||
    notes-total: "글 누적"
 | 
			
		||||
    users: "사용자 증감"
 | 
			
		||||
    users-total: "사용자 누적"
 | 
			
		||||
    active-users: "활성 사용자 수"
 | 
			
		||||
@@ -1186,6 +1186,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "모더레이터"
 | 
			
		||||
      adminOrModerator: "관리자+모더레이터"
 | 
			
		||||
      verified: "공식 계정"
 | 
			
		||||
      silenced: "침묵됨"
 | 
			
		||||
      suspended: "정지됨"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "위치 (오리진)"
 | 
			
		||||
@@ -1234,63 +1235,63 @@ admin/views/announcements.vue:
 | 
			
		||||
admin/views/hashtags.vue:
 | 
			
		||||
  hided-tags: "Hidden Tags"
 | 
			
		||||
admin/views/federation.vue:
 | 
			
		||||
  federation: "連合"
 | 
			
		||||
  host: "ホスト"
 | 
			
		||||
  notes: "投稿"
 | 
			
		||||
  users: "ユーザー"
 | 
			
		||||
  following: "フォロー中"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
  status: "ステータス"
 | 
			
		||||
  latest-request-sent-at: "直近のリクエスト送信"
 | 
			
		||||
  latest-request-received-at: "直近のリクエスト受信"
 | 
			
		||||
  remove-all-following: "フォローを全解除"
 | 
			
		||||
  remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
 | 
			
		||||
  block: "ブロック"
 | 
			
		||||
  marked-as-closed: "閉鎖されているとマーク"
 | 
			
		||||
  lookup: "照会"
 | 
			
		||||
  instances: "インスタンス"
 | 
			
		||||
  instance-not-registered: "そのインスタンスは登録されていません"
 | 
			
		||||
  sort: "ソート"
 | 
			
		||||
  federation: "연합"
 | 
			
		||||
  host: "호스트"
 | 
			
		||||
  notes: "글"
 | 
			
		||||
  users: "사용자"
 | 
			
		||||
  following: "팔로우 중"
 | 
			
		||||
  followers: "팔로워"
 | 
			
		||||
  status: "상태"
 | 
			
		||||
  latest-request-sent-at: "마지막으로 요청을 전송한 시간"
 | 
			
		||||
  latest-request-received-at: "마지막으로 요청을 받은 시간"
 | 
			
		||||
  remove-all-following: "모든 팔로잉 해제"
 | 
			
		||||
  remove-all-following-info: "{host}(으)로부터 모든 팔로잉을 해제합니다. 해당 인스턴스가 더 이상 존재하지 않게 된 경우 등에 실행하십시오."
 | 
			
		||||
  block: "차단"
 | 
			
		||||
  marked-as-closed: "폐쇄된 것으로 표시"
 | 
			
		||||
  lookup: "조회"
 | 
			
		||||
  instances: "인스턴스"
 | 
			
		||||
  instance-not-registered: "해당 인스턴스가 등록되어 있지 않습니다"
 | 
			
		||||
  sort: "정렬"
 | 
			
		||||
  sorts:
 | 
			
		||||
    caughtAtAsc: "登録日時が古い順"
 | 
			
		||||
    caughtAtDesc: "登録日時が新しい順"
 | 
			
		||||
    lastCommunicatedAtAsc: "最後にやり取りした日時が古い順"
 | 
			
		||||
    lastCommunicatedAtDesc: "最後にやり取りした日時が新しい順"
 | 
			
		||||
    notesAsc: "投稿が少ない順"
 | 
			
		||||
    notesDesc: "投稿が多い順"
 | 
			
		||||
    usersAsc: "ユーザーが少ない順"
 | 
			
		||||
    usersDesc: "ユーザーが多い順"
 | 
			
		||||
    followingAsc: "フォローが少ない順"
 | 
			
		||||
    followingDesc: "フォローが多い順"
 | 
			
		||||
    followersAsc: "フォロワーが少ない順"
 | 
			
		||||
    followersDesc: "フォロワーが多い順"
 | 
			
		||||
    driveUsageAsc: "ドライブ使用量が少ない順"
 | 
			
		||||
    driveUsageDesc: "ドライブ使用量が多い順"
 | 
			
		||||
    driveFilesAsc: "ドライブのファイル数が少ない順"
 | 
			
		||||
    driveFilesDesc: "ドライブのファイル数が多い順"
 | 
			
		||||
  state: "状態"
 | 
			
		||||
    caughtAtAsc: "등록일이 오래된 순"
 | 
			
		||||
    caughtAtDesc: "등록일이 최신인 순"
 | 
			
		||||
    lastCommunicatedAtAsc: "마지막으로 요청을 주고받은 일시가 오래된 순"
 | 
			
		||||
    lastCommunicatedAtDesc: "마지막으로 요청을 주고받은 일시가 빠른 순"
 | 
			
		||||
    notesAsc: "글이 적은 순"
 | 
			
		||||
    notesDesc: "글이 많은 순"
 | 
			
		||||
    usersAsc: "사용자가 적은 순"
 | 
			
		||||
    usersDesc: "사용자가 많은 순"
 | 
			
		||||
    followingAsc: "팔로잉이 적은 순"
 | 
			
		||||
    followingDesc: "팔로잉이 많은 순"
 | 
			
		||||
    followersAsc: "팔로워가 적은 순"
 | 
			
		||||
    followersDesc: "팔로워가 많은 순"
 | 
			
		||||
    driveUsageAsc: "드라이브 사용량이 적은 순"
 | 
			
		||||
    driveUsageDesc: "드라이브 사용량이 많은 순"
 | 
			
		||||
    driveFilesAsc: "드라이브 파일 수가 적은 순"
 | 
			
		||||
    driveFilesDesc: "드라이브 파일 수가 많은 순"
 | 
			
		||||
  state: "상태"
 | 
			
		||||
  states:
 | 
			
		||||
    all: "すべて"
 | 
			
		||||
    blocked: "ブロック"
 | 
			
		||||
    not-responding: "応答なし"
 | 
			
		||||
    marked-as-closed: "閉鎖とマーク済み"
 | 
			
		||||
  result-is-truncated: "上位{n}件を表示しています。"
 | 
			
		||||
  charts: "チャート"
 | 
			
		||||
    all: "모두"
 | 
			
		||||
    blocked: "차단됨"
 | 
			
		||||
    not-responding: "응답 없음"
 | 
			
		||||
    marked-as-closed: "폐쇄된 것으로 표시됨"
 | 
			
		||||
  result-is-truncated: "상위 {n}개를 표시하고 있습니다."
 | 
			
		||||
  charts: "차트"
 | 
			
		||||
  chart-srcs:
 | 
			
		||||
    requests: "リクエスト"
 | 
			
		||||
    users: "ユーザーの増減"
 | 
			
		||||
    users-total: "ユーザーの積算"
 | 
			
		||||
    notes: "投稿の増減"
 | 
			
		||||
    notes-total: "投稿の積算"
 | 
			
		||||
    ff: "フォロー/フォロワーの増減"
 | 
			
		||||
    ff-total: "フォロー/フォロワーの積算"
 | 
			
		||||
    drive-usage: "ドライブ使用量の増減"
 | 
			
		||||
    drive-usage-total: "ドライブ使用量の積算"
 | 
			
		||||
    drive-files: "ドライブファイル数の増減"
 | 
			
		||||
    drive-files-total: "ドライブファイル数の積算"
 | 
			
		||||
    requests: "요청"
 | 
			
		||||
    users: "사용자 증감"
 | 
			
		||||
    users-total: "사용자 누적"
 | 
			
		||||
    notes: "글 증감"
 | 
			
		||||
    notes-total: "글 누적"
 | 
			
		||||
    ff: "팔로잉/팔로워 증감"
 | 
			
		||||
    ff-total: "팔로잉/팔로워 누적"
 | 
			
		||||
    drive-usage: "드라이브 사용량 증감"
 | 
			
		||||
    drive-usage-total: "드라이브 사용량 누적"
 | 
			
		||||
    drive-files: "드라이브 파일 수 증감"
 | 
			
		||||
    drive-files-total: "드라이브 파일 수 누적"
 | 
			
		||||
  chart-spans:
 | 
			
		||||
    hour: "1時間ごと"
 | 
			
		||||
    day: "1日ごと"
 | 
			
		||||
    hour: "1시간마다"
 | 
			
		||||
    day: "1일마다"
 | 
			
		||||
desktop/views/pages/welcome.vue:
 | 
			
		||||
  about: "자세히..."
 | 
			
		||||
  gotit: "알겠습니다"
 | 
			
		||||
@@ -1626,6 +1627,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-view: "미디어 보기"
 | 
			
		||||
  edit: "옵션"
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "당신을 팔로우합니다"
 | 
			
		||||
  posts: "글"
 | 
			
		||||
  following: "팔로잉"
 | 
			
		||||
  followers: "팔로워"
 | 
			
		||||
 
 | 
			
		||||
@@ -1186,6 +1186,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "モデレーター"
 | 
			
		||||
      adminOrModerator: "管理者+モデレーター"
 | 
			
		||||
      verified: "公式アカウント"
 | 
			
		||||
      silenced: "サイレンス済み"
 | 
			
		||||
      suspended: "凍結済み"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "オリジン"
 | 
			
		||||
@@ -1626,6 +1627,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-view: "メディアビュー"
 | 
			
		||||
  edit: "オプション"
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  posts: "投稿"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
 
 | 
			
		||||
@@ -1186,6 +1186,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "モデレーター"
 | 
			
		||||
      adminOrModerator: "管理者+モデレーター"
 | 
			
		||||
      verified: "公式アカウント"
 | 
			
		||||
      silenced: "サイレンス済み"
 | 
			
		||||
      suspended: "凍結済み"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "オリジン"
 | 
			
		||||
@@ -1590,13 +1591,13 @@ mobile/views/pages/user/home.vue:
 | 
			
		||||
  activity: "アクティビティ"
 | 
			
		||||
  keywords: "Nøkkelord"
 | 
			
		||||
  domains: "頻出ドメイン"
 | 
			
		||||
  frequently-replied-users: "よく会話するユーザー"
 | 
			
		||||
  frequently-replied-users: "よく話すユーザー"
 | 
			
		||||
  followers-you-know: "知り合いのフォロワー"
 | 
			
		||||
  last-used-at: "最終ログイン"
 | 
			
		||||
mobile/views/pages/user/home.followers-you-know.vue:
 | 
			
		||||
  no-users: "知り合いのユーザーはいません"
 | 
			
		||||
mobile/views/pages/user/home.friends.vue:
 | 
			
		||||
  no-users: "よく会話するユーザーはいません"
 | 
			
		||||
  no-users: "よく話すユーザーはいません"
 | 
			
		||||
mobile/views/pages/user/home.notes.vue:
 | 
			
		||||
  no-notes: "投稿はありません"
 | 
			
		||||
mobile/views/pages/user/home.photos.vue:
 | 
			
		||||
@@ -1626,6 +1627,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-view: "メディアビュー"
 | 
			
		||||
  edit: "オプション"
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  posts: "投稿"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
 
 | 
			
		||||
@@ -1186,6 +1186,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "モデレーター"
 | 
			
		||||
      adminOrModerator: "管理者+モデレーター"
 | 
			
		||||
      verified: "公式アカウント"
 | 
			
		||||
      silenced: "サイレンス済み"
 | 
			
		||||
      suspended: "凍結済み"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "Źródło"
 | 
			
		||||
@@ -1626,6 +1627,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-view: "Widok multimediów"
 | 
			
		||||
  edit: "Opcje"
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  posts: "Wpisy"
 | 
			
		||||
  following: "Śledzeni"
 | 
			
		||||
  followers: "Śledzący"
 | 
			
		||||
 
 | 
			
		||||
@@ -1186,6 +1186,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "モデレーター"
 | 
			
		||||
      adminOrModerator: "管理者+モデレーター"
 | 
			
		||||
      verified: "公式アカウント"
 | 
			
		||||
      silenced: "サイレンス済み"
 | 
			
		||||
      suspended: "凍結済み"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "オリジン"
 | 
			
		||||
@@ -1596,7 +1597,7 @@ mobile/views/pages/user/home.vue:
 | 
			
		||||
mobile/views/pages/user/home.followers-you-know.vue:
 | 
			
		||||
  no-users: "知り合いのユーザーはいません"
 | 
			
		||||
mobile/views/pages/user/home.friends.vue:
 | 
			
		||||
  no-users: "よく会話するユーザーはいません"
 | 
			
		||||
  no-users: "よく話すユーザーはいません"
 | 
			
		||||
mobile/views/pages/user/home.notes.vue:
 | 
			
		||||
  no-notes: "Nenhuma mensagem"
 | 
			
		||||
mobile/views/pages/user/home.photos.vue:
 | 
			
		||||
@@ -1626,6 +1627,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-view: "メディアビュー"
 | 
			
		||||
  edit: "オプション"
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  posts: "投稿"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
 
 | 
			
		||||
@@ -1186,6 +1186,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "モデレーター"
 | 
			
		||||
      adminOrModerator: "管理者+モデレーター"
 | 
			
		||||
      verified: "公式アカウント"
 | 
			
		||||
      silenced: "サイレンス済み"
 | 
			
		||||
      suspended: "凍結済み"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "オリジン"
 | 
			
		||||
@@ -1590,13 +1591,13 @@ mobile/views/pages/user/home.vue:
 | 
			
		||||
  activity: "アクティビティ"
 | 
			
		||||
  keywords: "キーワード"
 | 
			
		||||
  domains: "頻出ドメイン"
 | 
			
		||||
  frequently-replied-users: "よく会話するユーザー"
 | 
			
		||||
  frequently-replied-users: "よく話すユーザー"
 | 
			
		||||
  followers-you-know: "知り合いのフォロワー"
 | 
			
		||||
  last-used-at: "最終ログイン"
 | 
			
		||||
mobile/views/pages/user/home.followers-you-know.vue:
 | 
			
		||||
  no-users: "知り合いのユーザーはいません"
 | 
			
		||||
mobile/views/pages/user/home.friends.vue:
 | 
			
		||||
  no-users: "よく会話するユーザーはいません"
 | 
			
		||||
  no-users: "よく話すユーザーはいません"
 | 
			
		||||
mobile/views/pages/user/home.notes.vue:
 | 
			
		||||
  no-notes: "投稿はありません"
 | 
			
		||||
mobile/views/pages/user/home.photos.vue:
 | 
			
		||||
@@ -1626,6 +1627,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-view: "メディアビュー"
 | 
			
		||||
  edit: "オプション"
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "フォローされています"
 | 
			
		||||
  posts: "投稿"
 | 
			
		||||
  following: "フォロー"
 | 
			
		||||
  followers: "フォロワー"
 | 
			
		||||
 
 | 
			
		||||
@@ -1186,6 +1186,7 @@ admin/views/users.vue:
 | 
			
		||||
      moderator: "版主"
 | 
			
		||||
      adminOrModerator: "管理员+版主"
 | 
			
		||||
      verified: "官方认证账户"
 | 
			
		||||
      silenced: "已禁言"
 | 
			
		||||
      suspended: "已冻结"
 | 
			
		||||
    origin:
 | 
			
		||||
      title: "源自"
 | 
			
		||||
@@ -1626,6 +1627,7 @@ deck/deck.tl-column.vue:
 | 
			
		||||
  is-media-view: "媒体视图"
 | 
			
		||||
  edit: "选项"
 | 
			
		||||
deck/deck.user-column.vue:
 | 
			
		||||
  follows-you: "关注您"
 | 
			
		||||
  posts: "帖子"
 | 
			
		||||
  following: "关注中"
 | 
			
		||||
  followers: "关注者"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								package.json
									
									
									
									
									
								
							@@ -1,8 +1,8 @@
 | 
			
		||||
{
 | 
			
		||||
	"name": "misskey",
 | 
			
		||||
	"author": "syuilo <i@syuilo.com>",
 | 
			
		||||
	"version": "10.84.0",
 | 
			
		||||
	"clientVersion": "2.0.14224",
 | 
			
		||||
	"version": "10.86.1",
 | 
			
		||||
	"clientVersion": "2.0.14327",
 | 
			
		||||
	"codename": "nighthike",
 | 
			
		||||
	"repository": {
 | 
			
		||||
		"type": "git",
 | 
			
		||||
@@ -23,9 +23,6 @@
 | 
			
		||||
		"test": "gulp test",
 | 
			
		||||
		"format": "gulp format"
 | 
			
		||||
	},
 | 
			
		||||
	"resolutions": {
 | 
			
		||||
		"terser": "3.14.1"
 | 
			
		||||
	},
 | 
			
		||||
	"dependencies": {
 | 
			
		||||
		"@fortawesome/fontawesome-svg-core": "1.2.14",
 | 
			
		||||
		"@fortawesome/free-brands-svg-icons": "5.7.1",
 | 
			
		||||
@@ -99,14 +96,14 @@
 | 
			
		||||
		"@types/websocket": "0.0.40",
 | 
			
		||||
		"@types/ws": "6.0.1",
 | 
			
		||||
		"animejs": "3.0.1",
 | 
			
		||||
		"apexcharts": "3.2.2",
 | 
			
		||||
		"apexcharts": "3.3.0",
 | 
			
		||||
		"autobind-decorator": "2.4.0",
 | 
			
		||||
		"autosize": "4.0.2",
 | 
			
		||||
		"autwh": "0.1.0",
 | 
			
		||||
		"bcryptjs": "2.4.3",
 | 
			
		||||
		"bee-queue": "1.2.2",
 | 
			
		||||
		"bootstrap-vue": "2.0.0-rc.11",
 | 
			
		||||
		"cafy": "12.1.0",
 | 
			
		||||
		"cafy": "14.0.1",
 | 
			
		||||
		"chai": "4.2.0",
 | 
			
		||||
		"chai-http": "4.2.1",
 | 
			
		||||
		"chalk": "2.4.2",
 | 
			
		||||
@@ -144,7 +141,7 @@
 | 
			
		||||
		"hard-source-webpack-plugin": "0.13.1",
 | 
			
		||||
		"html-minifier": "3.5.21",
 | 
			
		||||
		"http-signature": "1.2.0",
 | 
			
		||||
		"insert-text-at-cursor": "0.1.1",
 | 
			
		||||
		"insert-text-at-cursor": "0.1.2",
 | 
			
		||||
		"is-root": "2.0.0",
 | 
			
		||||
		"is-svg": "3.0.0",
 | 
			
		||||
		"js-yaml": "3.12.1",
 | 
			
		||||
@@ -232,11 +229,11 @@
 | 
			
		||||
		"uuid": "3.3.2",
 | 
			
		||||
		"v-animate-css": "0.0.3",
 | 
			
		||||
		"video-thumbnail-generator": "1.1.3",
 | 
			
		||||
		"vue": "2.6.4",
 | 
			
		||||
		"vue": "2.6.6",
 | 
			
		||||
		"vue-color": "2.7.0",
 | 
			
		||||
		"vue-content-loading": "1.5.3",
 | 
			
		||||
		"vue-cropperjs": "3.0.0",
 | 
			
		||||
		"vue-i18n": "8.8.0",
 | 
			
		||||
		"vue-i18n": "8.8.1",
 | 
			
		||||
		"vue-js-modal": "1.3.28",
 | 
			
		||||
		"vue-loader": "15.6.2",
 | 
			
		||||
		"vue-marquee-text-component": "1.1.1",
 | 
			
		||||
@@ -245,7 +242,7 @@
 | 
			
		||||
		"vue-sequential-entrance": "1.1.3",
 | 
			
		||||
		"vue-style-loader": "4.1.2",
 | 
			
		||||
		"vue-svg-inline-loader": "1.2.10",
 | 
			
		||||
		"vue-template-compiler": "2.6.4",
 | 
			
		||||
		"vue-template-compiler": "2.6.6",
 | 
			
		||||
		"vuedraggable": "2.17.0",
 | 
			
		||||
		"vuewordcloud": "18.7.11",
 | 
			
		||||
		"vuex": "3.1.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,9 @@ program
 | 
			
		||||
	.version(pkg.version)
 | 
			
		||||
	.option('--no-daemons', 'Disable daemon processes (for debbuging)')
 | 
			
		||||
	.option('--disable-clustering', 'Disable clustering')
 | 
			
		||||
	.option('--disable-ap-queue', 'Disable creating job queue related to ap')
 | 
			
		||||
	.option('--disable-queue', 'Disable job queue processing')
 | 
			
		||||
	.option('--only-queue', 'Pocessing job queue only')
 | 
			
		||||
	.option('--only-server', 'Run server only (without job queue)')
 | 
			
		||||
	.option('--only-queue', 'Pocessing job queue only (without server)')
 | 
			
		||||
	.option('--quiet', 'Suppress all logs')
 | 
			
		||||
	.option('--verbose', 'Enable all logs')
 | 
			
		||||
	.option('--with-log-time', 'Include timestamp for each logs')
 | 
			
		||||
@@ -15,8 +15,7 @@ program
 | 
			
		||||
	.option('--color', 'This option is a dummy for some external program\'s (e.g. forever) issue.')
 | 
			
		||||
	.parse(process.argv);
 | 
			
		||||
 | 
			
		||||
/*if (process.env.MK_DISABLE_AP_QUEUE)*/ program.disableApQueue = true;
 | 
			
		||||
if (process.env.MK_DISABLE_QUEUE) program.disableQueue = true;
 | 
			
		||||
/*if (process.env.MK_DISABLE_QUEUE)*/ program.disableQueue = true;
 | 
			
		||||
if (process.env.MK_ONLY_QUEUE) program.onlyQueue = true;
 | 
			
		||||
 | 
			
		||||
export { program };
 | 
			
		||||
 
 | 
			
		||||
@@ -148,7 +148,7 @@ export default Vue.extend({
 | 
			
		||||
		return {
 | 
			
		||||
			instance: null,
 | 
			
		||||
			target: null,
 | 
			
		||||
			sort: '+caughtAt',
 | 
			
		||||
			sort: '+lastCommunicatedAt',
 | 
			
		||||
			state: 'all',
 | 
			
		||||
			limit: 50,
 | 
			
		||||
			instances: [],
 | 
			
		||||
 
 | 
			
		||||
@@ -48,6 +48,7 @@
 | 
			
		||||
					<option value="admin">{{ $t('users.state.admin') }}</option>
 | 
			
		||||
					<option value="moderator">{{ $t('users.state.moderator') }}</option>
 | 
			
		||||
					<option value="verified">{{ $t('users.state.verified') }}</option>
 | 
			
		||||
					<option value="silenced">{{ $t('users.state.silenced') }}</option>
 | 
			
		||||
					<option value="suspended">{{ $t('users.state.suspended') }}</option>
 | 
			
		||||
				</ui-select>
 | 
			
		||||
				<ui-select v-model="origin">
 | 
			
		||||
@@ -89,7 +90,7 @@ export default Vue.extend({
 | 
			
		||||
			unsuspending: false,
 | 
			
		||||
			sort: '+createdAt',
 | 
			
		||||
			state: 'all',
 | 
			
		||||
			origin: 'combined',
 | 
			
		||||
			origin: 'local',
 | 
			
		||||
			limit: 10,
 | 
			
		||||
			offset: 0,
 | 
			
		||||
			users: [],
 | 
			
		||||
@@ -129,16 +130,25 @@ export default Vue.extend({
 | 
			
		||||
				const usernamePromise = this.$root.api('users/show', parseAcct(this.target));
 | 
			
		||||
				const idPromise = this.$root.api('users/show', { userId: this.target });
 | 
			
		||||
 | 
			
		||||
				usernamePromise.then(res);
 | 
			
		||||
				idPromise.then(res);
 | 
			
		||||
 | 
			
		||||
				idPromise.catch(e => {
 | 
			
		||||
					if (e == 'user not found') {
 | 
			
		||||
				let _notFound = false;
 | 
			
		||||
				const notFound = () => {
 | 
			
		||||
					if (_notFound) {
 | 
			
		||||
						this.$root.dialog({
 | 
			
		||||
							type: 'error',
 | 
			
		||||
							text: this.$t('user-not-found')
 | 
			
		||||
						});
 | 
			
		||||
					} else {
 | 
			
		||||
						_notFound = true;
 | 
			
		||||
					}
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				usernamePromise.then(res).catch(e => {
 | 
			
		||||
					if (e == 'user not found') {
 | 
			
		||||
						notFound();
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
				idPromise.then(res).catch(e => {
 | 
			
		||||
					notFound();
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
@@ -329,7 +339,7 @@ export default Vue.extend({
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			return !confirm.canceled;
 | 
			
		||||
		}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		fetchUsers() {
 | 
			
		||||
			this.$root.api('admin/show-users', {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
import { url as instanceUrl } from '../../config';
 | 
			
		||||
import * as url from '../../../../prelude/url';
 | 
			
		||||
 | 
			
		||||
export function getStaticImageUrl(url: string): string {
 | 
			
		||||
	const u = new URL(url);
 | 
			
		||||
export function getStaticImageUrl(baseUrl: string): string {
 | 
			
		||||
	const u = new URL(baseUrl);
 | 
			
		||||
	const dummy = `${u.host}${u.pathname}`;	// 拡張子がないとキャッシュしてくれないCDNがあるので
 | 
			
		||||
	let result = `${instanceUrl}/proxy/${dummy}?url=${encodeURIComponent(u.href)}`;
 | 
			
		||||
	result += '&static=1';
 | 
			
		||||
	return result;
 | 
			
		||||
	return `${instanceUrl}/proxy/${dummy}?${url.query({
 | 
			
		||||
		url: u.href,
 | 
			
		||||
		static: '1'
 | 
			
		||||
	})}`;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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>
 | 
			
		||||
<div class="mk-activity">
 | 
			
		||||
<div>
 | 
			
		||||
	<div ref="chart"></div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -9,7 +9,17 @@ import Vue from 'vue';
 | 
			
		||||
import ApexCharts from 'apexcharts';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['user'],
 | 
			
		||||
	props: {
 | 
			
		||||
		user: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: true
 | 
			
		||||
		},
 | 
			
		||||
		limit: {
 | 
			
		||||
			type: Number,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: 21
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			fetching: true,
 | 
			
		||||
@@ -21,7 +31,7 @@ export default Vue.extend({
 | 
			
		||||
		this.$root.api('charts/user/notes', {
 | 
			
		||||
			userId: this.user.id,
 | 
			
		||||
			span: 'day',
 | 
			
		||||
			limit: 21
 | 
			
		||||
			limit: this.limit
 | 
			
		||||
		}).then(stats => {
 | 
			
		||||
			const normal = [];
 | 
			
		||||
			const reply = [];
 | 
			
		||||
@@ -32,7 +42,7 @@ export default Vue.extend({
 | 
			
		||||
			const m = now.getMonth();
 | 
			
		||||
			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);
 | 
			
		||||
				normal.push([
 | 
			
		||||
					x,
 | 
			
		||||
@@ -99,10 +109,3 @@ export default Vue.extend({
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</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>
 | 
			
		||||
<button class="wfliddvnhxvyusikowhxozkyxyenqxqr"
 | 
			
		||||
	:class="{ wait, block, mini, active: isFollowing || hasPendingFollowRequestFromYou }"
 | 
			
		||||
	:class="{ wait, block, inline, mini, active: isFollowing || hasPendingFollowRequestFromYou }"
 | 
			
		||||
	@click="onClick"
 | 
			
		||||
	:disabled="wait"
 | 
			
		||||
	:inline="inline"
 | 
			
		||||
>
 | 
			
		||||
	<template v-if="!wait">
 | 
			
		||||
		<fa :icon="iconAndText[0]"/> <template v-if="!mini">{{ iconAndText[1] }}</template>
 | 
			
		||||
@@ -28,6 +29,11 @@ export default Vue.extend({
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
		inline: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: false
 | 
			
		||||
		},
 | 
			
		||||
		mini: {
 | 
			
		||||
			type: Boolean,
 | 
			
		||||
			required: false,
 | 
			
		||||
@@ -128,6 +134,9 @@ export default Vue.extend({
 | 
			
		||||
	border solid 1px var(--primary)
 | 
			
		||||
	border-radius 36px
 | 
			
		||||
 | 
			
		||||
	&.inline
 | 
			
		||||
		display inline-block
 | 
			
		||||
 | 
			
		||||
	&.mini
 | 
			
		||||
		padding 0
 | 
			
		||||
		min-width 0
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
import dummy from './dummy.vue';
 | 
			
		||||
import userName from './user-name.vue';
 | 
			
		||||
import followButton from './follow-button.vue';
 | 
			
		||||
import error from './error.vue';
 | 
			
		||||
@@ -32,6 +33,7 @@ import urlPreview from './url-preview.vue';
 | 
			
		||||
import fileTypeIcon from './file-type-icon.vue';
 | 
			
		||||
import emoji from './emoji.vue';
 | 
			
		||||
import welcomeTimeline from './welcome-timeline.vue';
 | 
			
		||||
import userList from './user-list.vue';
 | 
			
		||||
import uiInput from './ui/input.vue';
 | 
			
		||||
import uiButton from './ui/button.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';
 | 
			
		||||
 | 
			
		||||
Vue.component('mfm', misskeyFlavoredMarkdown);
 | 
			
		||||
Vue.component('mk-dummy', dummy);
 | 
			
		||||
Vue.component('mk-user-name', userName);
 | 
			
		||||
Vue.component('mk-follow-button', followButton);
 | 
			
		||||
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-emoji', emoji);
 | 
			
		||||
Vue.component('mk-welcome-timeline', welcomeTimeline);
 | 
			
		||||
Vue.component('mk-user-list', userList);
 | 
			
		||||
Vue.component('ui-input', uiInput);
 | 
			
		||||
Vue.component('ui-button', uiButton);
 | 
			
		||||
Vue.component('ui-horizon-group', uiHorizonGroup);
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,9 @@
 | 
			
		||||
	:style="style"
 | 
			
		||||
	:title="image.name"
 | 
			
		||||
	@click.prevent="onClick"
 | 
			
		||||
></a>
 | 
			
		||||
>
 | 
			
		||||
	<div v-if="image.type === 'image/gif'">GIF</div>
 | 
			
		||||
</a>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
@@ -76,6 +78,20 @@ export default Vue.extend({
 | 
			
		||||
	background-size contain
 | 
			
		||||
	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
 | 
			
		||||
	display flex
 | 
			
		||||
	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.renoteId != null) return;
 | 
			
		||||
			if (note.poll != null) return;
 | 
			
		||||
			if (note.localOnly) return;
 | 
			
		||||
 | 
			
		||||
			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>
 | 
			
		||||
<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">
 | 
			
		||||
			<mk-analog-clock :dark="$store.state.device.darkmode" :smooth="props.style < 2"/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
	</ui-container>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<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"
 | 
			
		||||
			:data-found="announcements && announcements.length != 0"
 | 
			
		||||
			:data-melt="props.design == 1"
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
			</p>
 | 
			
		||||
			<a v-if="announcements.length > 1" @click="next">{{ $t('next') }} >></a>
 | 
			
		||||
		</div>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
	</ui-container>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<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="calendar" :data-is-holiday="isHoliday">
 | 
			
		||||
				<p class="month-and-year">
 | 
			
		||||
@@ -31,7 +31,7 @@
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
	</ui-container>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
<template>
 | 
			
		||||
<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>
 | 
			
		||||
 | 
			
		||||
		<div class="mkw-hashtags--body" :data-mobile="platform == 'mobile'">
 | 
			
		||||
			<mk-trends/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
	</ui-container>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ import wSlideshow from './slideshow.vue';
 | 
			
		||||
import wTips from './tips.vue';
 | 
			
		||||
import wNav from './nav.vue';
 | 
			
		||||
import wHashtags from './hashtags.vue';
 | 
			
		||||
import wInstance from './instance.vue';
 | 
			
		||||
 | 
			
		||||
Vue.component('mkw-analog-clock', wAnalogClock);
 | 
			
		||||
Vue.component('mkw-nav', wNav);
 | 
			
		||||
@@ -27,3 +28,4 @@ Vue.component('mkw-memo', wMemo);
 | 
			
		||||
Vue.component('mkw-rss', wRss);
 | 
			
		||||
Vue.component('mkw-version', wVersion);
 | 
			
		||||
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>
 | 
			
		||||
<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>
 | 
			
		||||
 | 
			
		||||
		<div class="mkw-memo--body">
 | 
			
		||||
			<textarea v-model="text" :placeholder="$t('placeholder')" @input="onChange"></textarea>
 | 
			
		||||
			<button @click="saveMemo" :disabled="!changed">{{ $t('save') }}</button>
 | 
			
		||||
		</div>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
	</ui-container>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mkw-nav">
 | 
			
		||||
	<mk-widget-container>
 | 
			
		||||
	<ui-container>
 | 
			
		||||
		<div class="mkw-nav--body">
 | 
			
		||||
			<mk-nav/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
	</ui-container>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<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>
 | 
			
		||||
 | 
			
		||||
		<p :class="$style.fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
			></div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<p :class="$style.empty" v-if="!fetching && images.length == 0">{{ $t('no-photos') }}</p>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
	</ui-container>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<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>
 | 
			
		||||
		<button slot="func" @click="toggle" :title="$t('toggle')"><fa icon="sort"/></button>
 | 
			
		||||
 | 
			
		||||
@@ -64,7 +64,7 @@
 | 
			
		||||
				<text x="1" y="5">Fedi</text>
 | 
			
		||||
			</svg>
 | 
			
		||||
		</div>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
	</ui-container>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<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>
 | 
			
		||||
		<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>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
	</ui-container>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<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>
 | 
			
		||||
		<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-info v-show="props.view == 5" :connection="connection" :meta="meta"/>
 | 
			
		||||
		</template>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
	</ui-container>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,19 +12,12 @@ import init from '../init';
 | 
			
		||||
import fuckAdBlock from '../common/scripts/fuck-ad-block';
 | 
			
		||||
import composeNotification from '../common/scripts/compose-notification';
 | 
			
		||||
 | 
			
		||||
import MkIndex from './views/pages/index.vue';
 | 
			
		||||
import MkHome from './views/pages/home.vue';
 | 
			
		||||
import MkDeck from './views/pages/deck/deck.vue';
 | 
			
		||||
import MkUser from './views/pages/user/user.vue';
 | 
			
		||||
import MkHome from './views/home/home.vue';
 | 
			
		||||
import MkDeck from './views/deck/deck.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 MkDrive from './views/pages/drive.vue';
 | 
			
		||||
import MkHomeCustomize from './views/pages/home-customize.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 MkShare from './views/pages/share.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 MkChooseFileFromDriveWindow from './views/components/choose-file-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 { url } from '../config';
 | 
			
		||||
@@ -44,7 +38,7 @@ import MiOS from '../mios';
 | 
			
		||||
/**
 | 
			
		||||
 * init
 | 
			
		||||
 */
 | 
			
		||||
init(async (launch) => {
 | 
			
		||||
init(async (launch, os) => {
 | 
			
		||||
	Vue.mixin({
 | 
			
		||||
		methods: {
 | 
			
		||||
			$contextmenu(e, menu, opts?) {
 | 
			
		||||
@@ -134,31 +128,52 @@ init(async (launch) => {
 | 
			
		||||
	const router = new VueRouter({
 | 
			
		||||
		mode: 'history',
 | 
			
		||||
		routes: [
 | 
			
		||||
			{ path: '/', name: 'index', component: MkIndex },
 | 
			
		||||
			{ path: '/home', name: 'home', component: MkHome },
 | 
			
		||||
			{ path: '/deck', name: 'deck', component: MkDeck },
 | 
			
		||||
			{ path: '/i/customize-home', component: MkHomeCustomize },
 | 
			
		||||
			{ path: '/i/favorites', component: MkFavorites },
 | 
			
		||||
			os.store.getters.isSignedIn && os.store.state.device.deckMode
 | 
			
		||||
				? { path: '/', name: 'index', component: MkDeck, children: [
 | 
			
		||||
					{ path: '/@:user', name: 'user', component: () => import('./views/deck/deck.user-column.vue').then(m => m.default), children: [
 | 
			
		||||
						{ path: '', name: 'user', component: () => import('./views/deck/deck.user-column.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/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', component: () => import('./views/home/featured.vue').then(m => m.default) },
 | 
			
		||||
					{ path: '/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/drive', component: MkDrive },
 | 
			
		||||
			{ path: '/i/drive/folder/:folder', component: MkDrive },
 | 
			
		||||
			{ path: '/i/settings', component: MkSettings },
 | 
			
		||||
			{ path: '/selectdrive', component: MkSelectDrive },
 | 
			
		||||
			{ path: '/search', component: MkSearch },
 | 
			
		||||
			{ path: '/tags/:tag', name: 'tag', component: MkTag },
 | 
			
		||||
			{ path: '/share', component: MkShare },
 | 
			
		||||
			{ 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: '/deck', redirect: '/' },
 | 
			
		||||
			{ path: '*', component: MkNotFound }
 | 
			
		||||
		]
 | 
			
		||||
		],
 | 
			
		||||
		scrollBehavior(to, from, savedPosition) {
 | 
			
		||||
			return { x: 0, y: 0 };
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// Launch the app
 | 
			
		||||
	const [app, os] = launch(router);
 | 
			
		||||
	const [app, _] = launch(router);
 | 
			
		||||
 | 
			
		||||
	if (os.store.getters.isSignedIn) {
 | 
			
		||||
		/**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<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>
 | 
			
		||||
		<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-chart v-show="view == 1" :data="[].concat(activity)"/>
 | 
			
		||||
		</template>
 | 
			
		||||
	</mk-widget-container>
 | 
			
		||||
	</ui-container>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -160,6 +160,7 @@ export default Vue.extend({
 | 
			
		||||
			color #222
 | 
			
		||||
 | 
			
		||||
		> [data-icon]
 | 
			
		||||
			box-sizing initial
 | 
			
		||||
			padding 14px
 | 
			
		||||
 | 
			
		||||
</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 uiNotification from './ui-notification.vue';
 | 
			
		||||
import home from './home.vue';
 | 
			
		||||
import timeline from './timeline.vue';
 | 
			
		||||
import notes from './notes.vue';
 | 
			
		||||
import subNoteContent from './sub-note-content.vue';
 | 
			
		||||
import window from './window.vue';
 | 
			
		||||
@@ -20,12 +18,10 @@ import activity from './activity.vue';
 | 
			
		||||
import friendsMaker from './friends-maker.vue';
 | 
			
		||||
import userCard from './user-card.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-notification', uiNotification);
 | 
			
		||||
Vue.component('mk-home', home);
 | 
			
		||||
Vue.component('mk-timeline', timeline);
 | 
			
		||||
Vue.component('mk-notes', notes);
 | 
			
		||||
Vue.component('mk-sub-note-content', subNoteContent);
 | 
			
		||||
Vue.component('mk-window', window);
 | 
			
		||||
@@ -42,4 +38,4 @@ Vue.component('mk-activity', activity);
 | 
			
		||||
Vue.component('mk-friends-maker', friendsMaker);
 | 
			
		||||
Vue.component('mk-user-card', userCard);
 | 
			
		||||
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') }}
 | 
			
		||||
					<span slot="desc">{{ $t('auto-popout-desc') }}</span>
 | 
			
		||||
				</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') }}
 | 
			
		||||
					<span slot="desc">{{ $t('keep-cw-desc') }}</span>
 | 
			
		||||
				</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="right">{{ $t('navbar-position-right') }}</ui-radio>
 | 
			
		||||
			</section>
 | 
			
		||||
			<section>
 | 
			
		||||
				<ui-switch v-model="deckDefault">{{ $t('deck-default') }}</ui-switch>
 | 
			
		||||
			</section>
 | 
			
		||||
			<section>
 | 
			
		||||
				<ui-switch v-model="darkmode">{{ $t('dark-mode') }}</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 }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		deckNav: {
 | 
			
		||||
			get() { return this.$store.state.settings.deckNav; },
 | 
			
		||||
			set(value) { this.$store.commit('settings/set', { key: 'deckNav', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		keepCw: {
 | 
			
		||||
			get() { return this.$store.state.settings.keepCw; },
 | 
			
		||||
			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 }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		deckDefault: {
 | 
			
		||||
			get() { return this.$store.state.device.deckDefault; },
 | 
			
		||||
			set(value) { this.$store.commit('device/set', { key: 'deckDefault', value }); }
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		enableSounds: {
 | 
			
		||||
			get() { return this.$store.state.device.enableSounds; },
 | 
			
		||||
			set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); }
 | 
			
		||||
@@ -534,8 +518,7 @@ export default Vue.extend({
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		customizeHome() {
 | 
			
		||||
			this.$router.push('/i/customize-home');
 | 
			
		||||
			this.$emit('done');
 | 
			
		||||
			location.href = '/?customize';
 | 
			
		||||
		},
 | 
			
		||||
		updateWallpaper() {
 | 
			
		||||
			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>
 | 
			
		||||
			</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>
 | 
			
		||||
					<router-link to="/i/settings">
 | 
			
		||||
						<i><fa icon="cog"/></i>
 | 
			
		||||
@@ -67,6 +60,13 @@
 | 
			
		||||
				</li>
 | 
			
		||||
			</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">
 | 
			
		||||
					<p>
 | 
			
		||||
						<span>{{ $t('dark') }}</span>
 | 
			
		||||
@@ -97,12 +97,14 @@ import MkFollowRequestsWindow from './received-follow-requests-window.vue';
 | 
			
		||||
import MkSettingsWindow from './settings-window.vue';
 | 
			
		||||
import MkDriveWindow from './drive-window.vue';
 | 
			
		||||
import contains from '../../../common/scripts/contains';
 | 
			
		||||
import { faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('desktop/views/components/ui.header.account.vue'),
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			isOpen: false
 | 
			
		||||
			isOpen: false,
 | 
			
		||||
			faHome, faColumns
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
@@ -161,7 +163,11 @@ export default Vue.extend({
 | 
			
		||||
				key: 'darkmode',
 | 
			
		||||
				value: !this.$store.state.device.darkmode
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
		},
 | 
			
		||||
		toggleDeckMode() {
 | 
			
		||||
			this.$store.commit('device/set', { key: 'deckMode', value: !this.$store.state.device.deckMode });
 | 
			
		||||
			location.reload();
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</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,38 +1,22 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="nav">
 | 
			
		||||
	<ul>
 | 
			
		||||
		<template v-if="$store.getters.isSignedIn">
 | 
			
		||||
			<template v-if="$store.state.device.deckDefault">
 | 
			
		||||
				<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 class="home" :class="{ active: $route.name == 'home' }" @click="goToTop">
 | 
			
		||||
					<router-link to="/home"><fa icon="home"/><p>{{ $t('home') }}</p></router-link>
 | 
			
		||||
				</li>
 | 
			
		||||
			</template>
 | 
			
		||||
			<template v-else>
 | 
			
		||||
				<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 class="game">
 | 
			
		||||
				<a @click="game">
 | 
			
		||||
					<fa icon="gamepad"/>
 | 
			
		||||
					<p>{{ $t('game') }}</p>
 | 
			
		||||
					<template v-if="hasGameInvitations"><fa icon="circle"/></template>
 | 
			
		||||
				</a>
 | 
			
		||||
			</li>
 | 
			
		||||
		</template>
 | 
			
		||||
		<li v-if="!$store.state.device.deckMode" class="timeline" @click="goToTop">
 | 
			
		||||
			<router-link to="/"><fa icon="home"/><p>{{ $t('@.timeline') }}</p></router-link>
 | 
			
		||||
		</li>
 | 
			
		||||
		<li class="featured">
 | 
			
		||||
			<router-link to="/featured"><fa :icon="faNewspaper"/><p>{{ $t('@.featured-notes') }}</p></router-link>
 | 
			
		||||
		</li>
 | 
			
		||||
		<li class="explore">
 | 
			
		||||
			<router-link to="/explore"><fa :icon="faHashtag"/><p>{{ $t('@.explore') }}</p></router-link>
 | 
			
		||||
		</li>
 | 
			
		||||
		<li class="game">
 | 
			
		||||
			<a @click="game">
 | 
			
		||||
				<fa icon="gamepad"/>
 | 
			
		||||
				<p>{{ $t('game') }}</p>
 | 
			
		||||
				<template v-if="hasGameInvitations"><fa icon="circle"/></template>
 | 
			
		||||
			</a>
 | 
			
		||||
		</li>
 | 
			
		||||
	</ul>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -40,22 +24,18 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import MkMessagingWindow from './messaging-window.vue';
 | 
			
		||||
import MkGameWindow from './game-window.vue';
 | 
			
		||||
import { faNewspaper, faHashtag } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('desktop/views/components/ui.header.nav.vue'),
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			hasGameInvitations: false,
 | 
			
		||||
			connection: null
 | 
			
		||||
			connection: null,
 | 
			
		||||
			faNewspaper, faHashtag
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	computed: {
 | 
			
		||||
		hasUnreadMessagingMessage(): boolean {
 | 
			
		||||
			return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadMessagingMessage;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		if (this.$store.getters.isSignedIn) {
 | 
			
		||||
			this.connection = this.$root.stream.useSharedConnection('main');
 | 
			
		||||
@@ -78,10 +58,6 @@ export default Vue.extend({
 | 
			
		||||
			this.hasGameInvitations = false;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		messaging() {
 | 
			
		||||
			this.$root.new(MkMessagingWindow);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		game() {
 | 
			
		||||
			this.$root.new(MkGameWindow);
 | 
			
		||||
		},
 | 
			
		||||
@@ -126,7 +102,7 @@ export default Vue.extend({
 | 
			
		||||
				display inline-block
 | 
			
		||||
				z-index 1
 | 
			
		||||
				height 100%
 | 
			
		||||
				padding 0 24px
 | 
			
		||||
				padding 0 20px
 | 
			
		||||
				font-size 13px
 | 
			
		||||
				font-variant small-caps
 | 
			
		||||
				color var(--desktopHeaderFg)
 | 
			
		||||
 
 | 
			
		||||
@@ -16,9 +16,10 @@
 | 
			
		||||
				<div class="right">
 | 
			
		||||
					<x-search/>
 | 
			
		||||
					<x-account v-if="$store.getters.isSignedIn"/>
 | 
			
		||||
					<x-messaging v-if="$store.getters.isSignedIn"/>
 | 
			
		||||
					<x-notifications 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>
 | 
			
		||||
@@ -37,6 +38,7 @@ import XAccount from './ui.header.account.vue';
 | 
			
		||||
import XNotifications from './ui.header.notifications.vue';
 | 
			
		||||
import XPost from './ui.header.post.vue';
 | 
			
		||||
import XClock from './ui.header.clock.vue';
 | 
			
		||||
import XMessaging from './ui.header.messaging.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n(),
 | 
			
		||||
@@ -45,6 +47,7 @@ export default Vue.extend({
 | 
			
		||||
		XSearch,
 | 
			
		||||
		XAccount,
 | 
			
		||||
		XNotifications,
 | 
			
		||||
		XMessaging,
 | 
			
		||||
		XPost,
 | 
			
		||||
		XClock
 | 
			
		||||
	},
 | 
			
		||||
@@ -116,7 +119,7 @@ export default Vue.extend({
 | 
			
		||||
			> .container
 | 
			
		||||
				display flex
 | 
			
		||||
				width 100%
 | 
			
		||||
				max-width 1300px
 | 
			
		||||
				max-width 1208px
 | 
			
		||||
				margin 0 auto
 | 
			
		||||
 | 
			
		||||
				> *
 | 
			
		||||
@@ -152,7 +155,7 @@ export default Vue.extend({
 | 
			
		||||
						vertical-align top
 | 
			
		||||
 | 
			
		||||
					@media (max-width 1100px)
 | 
			
		||||
						> .mk-ui-header-search
 | 
			
		||||
						> .clock
 | 
			
		||||
							display none
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,20 +6,20 @@
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="nav" v-if="$store.getters.isSignedIn">
 | 
			
		||||
			<template v-if="$store.state.device.deckDefault">
 | 
			
		||||
				<div class="deck" :class="{ active: $route.name == 'deck' || $route.name == 'index' }" @click="goToTop">
 | 
			
		||||
			<template v-if="$store.state.device.deckMode">
 | 
			
		||||
				<div class="deck active" @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 class="home">
 | 
			
		||||
					<a @click="toggleDeckMode(false)"><fa icon="home"/></a>
 | 
			
		||||
				</div>
 | 
			
		||||
			</template>
 | 
			
		||||
			<template v-else>
 | 
			
		||||
				<div class="home" :class="{ active: $route.name == 'home' || $route.name == 'index' }" @click="goToTop">
 | 
			
		||||
				<div class="home active" @click="goToTop">
 | 
			
		||||
					<router-link to="/"><fa icon="home"/></router-link>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="deck" :class="{ active: $route.name == 'deck' }" @click="goToTop">
 | 
			
		||||
					<router-link to="/deck"><fa icon="columns"/></router-link>
 | 
			
		||||
				<div class="deck">
 | 
			
		||||
					<a @click="toggleDeckMode(true)"><fa icon="columns"/></a>
 | 
			
		||||
				</div>
 | 
			
		||||
			</template>
 | 
			
		||||
			<div class="messaging">
 | 
			
		||||
@@ -122,6 +122,11 @@ export default Vue.extend({
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		toggleDeckMode(deck) {
 | 
			
		||||
			this.$store.commit('device/set', { key: 'deckMode', value: deck });
 | 
			
		||||
			location.reload();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onReversiInvited() {
 | 
			
		||||
			this.hasGameInvitations = true;
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
<div class="zvdbznxvfixtmujpsigoccczftvpiwqh">
 | 
			
		||||
	<div class="banner" :style="bannerStyle"></div>
 | 
			
		||||
	<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
 | 
			
		||||
	<mk-follow-button :user="user" class="follow" mini/>
 | 
			
		||||
	<mk-follow-button v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" class="follow" mini/>
 | 
			
		||||
	<div class="body">
 | 
			
		||||
		<router-link :to="user | userPage" class="name">
 | 
			
		||||
			<mk-user-name :user="user"/>
 | 
			
		||||
@@ -41,7 +41,6 @@ export default Vue.extend({
 | 
			
		||||
	height 280px
 | 
			
		||||
	overflow hidden
 | 
			
		||||
	font-size 13px
 | 
			
		||||
	text-align center
 | 
			
		||||
	background $bg
 | 
			
		||||
	box-shadow 0 2px 4px rgba(0, 0, 0, 0.1)
 | 
			
		||||
	color var(--faceText)
 | 
			
		||||
@@ -54,7 +53,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
	> .avatar
 | 
			
		||||
		display block
 | 
			
		||||
		margin -40px auto 0 auto
 | 
			
		||||
		margin -40px 0 0 16px
 | 
			
		||||
		width 80px
 | 
			
		||||
		height 80px
 | 
			
		||||
		border-radius 100%
 | 
			
		||||
@@ -67,6 +66,7 @@ export default Vue.extend({
 | 
			
		||||
 | 
			
		||||
	> .body
 | 
			
		||||
		padding 0px 24px
 | 
			
		||||
		margin-top -40px
 | 
			
		||||
 | 
			
		||||
		> .name
 | 
			
		||||
			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">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../../i18n';
 | 
			
		||||
import Menu from '../../../../common/views/components/menu.vue';
 | 
			
		||||
import { countIf } from '../../../../../../prelude/array';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import Menu from '../../../common/views/components/menu.vue';
 | 
			
		||||
import { countIf } from '../../../../../prelude/array';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('deck'),
 | 
			
		||||
@@ -65,6 +65,16 @@ export default Vue.extend({
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			count: 0,
 | 
			
		||||
			active: true,
 | 
			
		||||
			dragging: false,
 | 
			
		||||
			draghover: false,
 | 
			
		||||
			dropready: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		isTemporaryColumn(): boolean {
 | 
			
		||||
			return this.column == null;
 | 
			
		||||
@@ -84,16 +94,6 @@ export default Vue.extend({
 | 
			
		||||
		getColumnVm: { from: 'getColumnVm' }
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			count: 0,
 | 
			
		||||
			active: true,
 | 
			
		||||
			dragging: false,
 | 
			
		||||
			draghover: false,
 | 
			
		||||
			dropready: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		active(v) {
 | 
			
		||||
			if (v && this.isScrollTop()) {
 | 
			
		||||
@@ -109,7 +109,8 @@ export default Vue.extend({
 | 
			
		||||
		return {
 | 
			
		||||
			column: this,
 | 
			
		||||
			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() {
 | 
			
		||||
			this.$store.commit('device/set', {
 | 
			
		||||
				key: 'deckTemporaryColumn',
 | 
			
		||||
				value: null
 | 
			
		||||
			});
 | 
			
		||||
			this.$router.push('/');
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		goTop() {
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../../i18n';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import XColumn from './deck.column.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>
 | 
			
		||||
							
								
								
									
										59
									
								
								src/client/app/desktop/views/deck/deck.featured-column.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/client/app/desktop/views/deck/deck.featured-column.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
<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 => {
 | 
			
		||||
					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">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../../i18n';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import XColumn from './deck.column.vue';
 | 
			
		||||
import XMentions from './deck.mentions.vue';
 | 
			
		||||
 | 
			
		||||
@@ -18,10 +18,10 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../../i18n';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import XColumn from './deck.column.vue';
 | 
			
		||||
import XNotes from './deck.notes.vue';
 | 
			
		||||
import XNote from '../../components/note.vue';
 | 
			
		||||
import XNote from '../components/note.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n(),
 | 
			
		||||
@@ -31,13 +31,6 @@ export default Vue.extend({
 | 
			
		||||
		XNote
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		noteId: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: true
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			note: null,
 | 
			
		||||
@@ -45,11 +38,25 @@ export default Vue.extend({
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		$route: 'fetch'
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	created() {
 | 
			
		||||
		this.$root.api('notes/show', { noteId: this.noteId }).then(note => {
 | 
			
		||||
			this.note = note;
 | 
			
		||||
			this.fetching = false;
 | 
			
		||||
		});
 | 
			
		||||
		this.fetch();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		fetch() {
 | 
			
		||||
			this.fetching = true;
 | 
			
		||||
 | 
			
		||||
			this.$root.api('notes/show', {
 | 
			
		||||
				noteId: this.$route.params.note
 | 
			
		||||
			}).then(note => {
 | 
			
		||||
				this.note = note;
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
@@ -38,10 +38,10 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../../i18n';
 | 
			
		||||
import shouldMuteNote from '../../../../common/scripts/should-mute-note';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import shouldMuteNote from '../../../common/scripts/should-mute-note';
 | 
			
		||||
 | 
			
		||||
import XNote from '../../components/note.vue';
 | 
			
		||||
import XNote from '../components/note.vue';
 | 
			
		||||
 | 
			
		||||
const displayLimit = 20;
 | 
			
		||||
 | 
			
		||||
@@ -96,8 +96,8 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import getNoteSummary from '../../../../../../misc/get-note-summary';
 | 
			
		||||
import XNote from '../../components/note.vue';
 | 
			
		||||
import getNoteSummary from '../../../../../misc/get-note-summary';
 | 
			
		||||
import XNote from '../components/note.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../../i18n';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import XColumn from './deck.column.vue';
 | 
			
		||||
import XNotifications from './deck.notifications.vue';
 | 
			
		||||
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../../i18n';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import XNotification from './deck.notification.vue';
 | 
			
		||||
 | 
			
		||||
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">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../../i18n';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import XColumn from './deck.column.vue';
 | 
			
		||||
import XTl from './deck.tl.vue';
 | 
			
		||||
import XListTl from './deck.list-tl.vue';
 | 
			
		||||
							
								
								
									
										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>
 | 
			
		||||
			<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 v-if="temporaryColumn">
 | 
			
		||||
			<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>
 | 
			
		||||
		<router-view></router-view>
 | 
			
		||||
		<button ref="add" @click="add" :title="$t('@deck.add-column')"><fa icon="plus"/></button>
 | 
			
		||||
	</div>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
@@ -21,20 +17,17 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../../i18n';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import XColumnCore from './deck.column-core.vue';
 | 
			
		||||
import Menu from '../../../../common/views/components/menu.vue';
 | 
			
		||||
import MkUserListsWindow from '../../components/user-lists-window.vue';
 | 
			
		||||
import Menu from '../../../common/views/components/menu.vue';
 | 
			
		||||
import MkUserListsWindow from '../components/user-lists-window.vue';
 | 
			
		||||
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('deck'),
 | 
			
		||||
	components: {
 | 
			
		||||
		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)
 | 
			
		||||
		XColumnCore
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
@@ -55,10 +48,6 @@ export default Vue.extend({
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		temporaryColumn(): any {
 | 
			
		||||
			return this.$store.state.device.deckTemporaryColumn;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		keymap(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				't': this.focus
 | 
			
		||||
@@ -67,15 +56,14 @@ export default Vue.extend({
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		temporaryColumn() {
 | 
			
		||||
			if (this.temporaryColumn != null) {
 | 
			
		||||
				this.$nextTick(() => {
 | 
			
		||||
					this.$refs.body.scrollTo({
 | 
			
		||||
						left: this.$refs.body.scrollWidth - this.$refs.body.clientWidth,
 | 
			
		||||
						behavior: 'smooth'
 | 
			
		||||
					});
 | 
			
		||||
		$route() {
 | 
			
		||||
			if (this.$route.name == 'index') return;
 | 
			
		||||
			this.$nextTick(() => {
 | 
			
		||||
				this.$refs.body.scrollTo({
 | 
			
		||||
					left: this.$refs.body.scrollWidth - this.$refs.body.clientWidth,
 | 
			
		||||
					behavior: 'smooth'
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -86,8 +74,6 @@ export default Vue.extend({
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	created() {
 | 
			
		||||
		this.$store.commit('navHook', this.onNav);
 | 
			
		||||
 | 
			
		||||
		if (this.$store.state.settings.deck == null) {
 | 
			
		||||
			const deck = {
 | 
			
		||||
				columns: [/*{
 | 
			
		||||
@@ -133,8 +119,6 @@ export default Vue.extend({
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.$store.commit('navHook', null);
 | 
			
		||||
 | 
			
		||||
		document.documentElement.style.overflow = 'auto';
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -143,39 +127,6 @@ export default Vue.extend({
 | 
			
		||||
			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() {
 | 
			
		||||
			this.$root.new(Menu, {
 | 
			
		||||
				source: this.$refs.add,
 | 
			
		||||
@@ -50,7 +50,7 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../../../i18n';
 | 
			
		||||
import i18n from '../../../i18n';
 | 
			
		||||
import XColumn from './deck.column.vue';
 | 
			
		||||
import * as XDraggable from 'vuedraggable';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
@@ -1,16 +1,14 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<main v-if="!fetching">
 | 
			
		||||
		<sequential-entrance animation="entranceFromTop" delay="25">
 | 
			
		||||
			<template v-for="favorite in favorites">
 | 
			
		||||
				<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
 | 
			
		||||
			</template>
 | 
			
		||||
		</sequential-entrance>
 | 
			
		||||
		<div class="more" v-if="existMore">
 | 
			
		||||
			<ui-button inline @click="more">{{ $t('@.load-more') }}</ui-button>
 | 
			
		||||
		</div>
 | 
			
		||||
	</main>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
<div class="ecsvsegy" v-if="!fetching">
 | 
			
		||||
	<sequential-entrance animation="entranceFromTop" delay="25">
 | 
			
		||||
		<template v-for="favorite in favorites">
 | 
			
		||||
			<mk-note-detail class="post" :note="favorite.note" :key="favorite.note.id"/>
 | 
			
		||||
		</template>
 | 
			
		||||
	</sequential-entrance>
 | 
			
		||||
	<div class="more" v-if="existMore">
 | 
			
		||||
		<ui-button inline @click="more">{{ $t('@.load-more') }}</ui-button>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
@@ -72,10 +70,8 @@ export default Vue.extend({
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
main
 | 
			
		||||
.ecsvsegy
 | 
			
		||||
	margin 0 auto
 | 
			
		||||
	padding 16px
 | 
			
		||||
	max-width 700px
 | 
			
		||||
 | 
			
		||||
	> * > .post
 | 
			
		||||
		margin-bottom 16px
 | 
			
		||||
							
								
								
									
										54
									
								
								src/client/app/desktop/views/home/featured.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/client/app/desktop/views/home/featured.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
<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 => {
 | 
			
		||||
				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>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<main v-if="!fetching">
 | 
			
		||||
		<mk-note-detail :note="note"/>
 | 
			
		||||
		<footer>
 | 
			
		||||
			<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>
 | 
			
		||||
		</footer>
 | 
			
		||||
	</main>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
<div v-if="!fetching" class="kcthdwmv">
 | 
			
		||||
	<mk-note-detail :note="note"/>
 | 
			
		||||
	<footer>
 | 
			
		||||
		<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>
 | 
			
		||||
	</footer>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
@@ -48,8 +46,7 @@ export default Vue.extend({
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
main
 | 
			
		||||
	padding 16px
 | 
			
		||||
.kcthdwmv
 | 
			
		||||
	text-align center
 | 
			
		||||
 | 
			
		||||
	> footer
 | 
			
		||||
@@ -59,8 +56,4 @@ main
 | 
			
		||||
			display inline-block
 | 
			
		||||
			margin 0 16px
 | 
			
		||||
 | 
			
		||||
	> .mk-note-detail
 | 
			
		||||
		margin 0 auto
 | 
			
		||||
		width 640px
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<header :class="$style.header">
 | 
			
		||||
		<h1>{{ q }}</h1>
 | 
			
		||||
	</header>
 | 
			
		||||
	<p :class="$style.notAvailable" v-if="!fetching && notAvailable">{{ $t('not-available') }}</p>
 | 
			
		||||
	<p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('not-found', { q }) }}</p>
 | 
			
		||||
	<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
<div class="oxgbmvii">
 | 
			
		||||
	<div class="notes">
 | 
			
		||||
		<header>
 | 
			
		||||
			<span><fa icon="search"/> {{ q }}</span>
 | 
			
		||||
		</header>
 | 
			
		||||
		<p v-if="!fetching && notAvailable">{{ $t('not-available') }}</p>
 | 
			
		||||
		<p v-if="!fetching && empty"><fa icon="search"/> {{ $t('not-found', { q }) }}</p>
 | 
			
		||||
		<mk-notes ref="timeline" :more="existMore ? more : null"/>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
@@ -106,45 +108,23 @@ export default Vue.extend({
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" module>
 | 
			
		||||
.header
 | 
			
		||||
	width 100%
 | 
			
		||||
	max-width 600px
 | 
			
		||||
	margin 0 auto
 | 
			
		||||
	color #555
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.oxgbmvii
 | 
			
		||||
	> .notes
 | 
			
		||||
		background var(--face)
 | 
			
		||||
		box-shadow var(--shadow)
 | 
			
		||||
		border-radius var(--round)
 | 
			
		||||
		overflow hidden
 | 
			
		||||
 | 
			
		||||
.notes
 | 
			
		||||
	max-width 600px
 | 
			
		||||
	margin 0 auto
 | 
			
		||||
	border solid 1px rgba(#000, 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
	overflow hidden
 | 
			
		||||
		> header
 | 
			
		||||
			padding 0 8px
 | 
			
		||||
			z-index 10
 | 
			
		||||
			background var(--faceHeader)
 | 
			
		||||
			box-shadow 0 var(--lineWidth) var(--desktopTimelineHeaderShadow)
 | 
			
		||||
 | 
			
		||||
.empty
 | 
			
		||||
	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
 | 
			
		||||
 | 
			
		||||
.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
 | 
			
		||||
			> span
 | 
			
		||||
				padding 0 8px
 | 
			
		||||
				font-size 0.9em
 | 
			
		||||
				line-height 42px
 | 
			
		||||
				color var(--text)
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,11 +1,8 @@
 | 
			
		||||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<header :class="$style.header">
 | 
			
		||||
		<h1>#{{ $route.params.tag }}</h1>
 | 
			
		||||
	</header>
 | 
			
		||||
<div>
 | 
			
		||||
	<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-ui>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
@@ -96,17 +93,10 @@ export default Vue.extend({
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" module>
 | 
			
		||||
.header
 | 
			
		||||
	width 100%
 | 
			
		||||
	max-width 600px
 | 
			
		||||
	margin 0 auto
 | 
			
		||||
	color #555
 | 
			
		||||
 | 
			
		||||
.notes
 | 
			
		||||
	width 600px
 | 
			
		||||
	margin 0 auto
 | 
			
		||||
	border solid 1px rgba(#000, 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
	background var(--face)
 | 
			
		||||
	box-shadow var(--shadow)
 | 
			
		||||
	border-radius var(--round)
 | 
			
		||||
	overflow hidden
 | 
			
		||||
 | 
			
		||||
.empty
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user