Compare commits
	
		
			176 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | e41f74e77c | ||
|   | c21caad1c5 | ||
|   | 86fcd3a378 | ||
|   | 2b3687b3cb | ||
|   | 5d61c7c691 | ||
|   | 1bb266e7c7 | ||
|   | 1fca8d322c | ||
|   | 325cd03a59 | ||
|   | 2f7e6baa05 | ||
|   | d252e066fe | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | fe7bd9ab3c | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 84e3f41305 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 3e8cccad0d | ||
|   | a2b94d67f7 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 6ab61e73b0 | ||
|   | 051c6973af | ||
|   | 806a49ec3d | ||
|   | 3829fe128a | ||
|   | 649177985d | ||
|   | c15148b23c | ||
|   | 261a3f5d91 | ||
|   | 256ba78ba5 | ||
|   | 04aff8866e | ||
|   | 1a51b98700 | ||
|   | f64100226d | ||
|   | b7805e48a6 | ||
|   | 0d9556620d | ||
|   | a51828a7a2 | ||
|   | 7e2009f408 | ||
|   | 008d950a39 | ||
|   | 22d5862afb | ||
|   | de569147a5 | ||
|   | a82c3db750 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 80706d10af | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 93f01ed4df | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | a3a28e5557 | ||
|   | 8948a0d3a4 | ||
|   | d849ea9b41 | ||
|   | 0144575f3f | ||
|   | bdbe646ca7 | ||
|   | 1a1483a242 | ||
|   | 962346785b | ||
|   | a73da3cd70 | ||
|   | 9c27d0ae3f | ||
|   | 525d5218c1 | ||
|   | e23b13ec7f | ||
|   | 29b000e03c | ||
|   | 6a7b0df810 | ||
|   | 4142de9195 | ||
|   | 9195e1be00 | ||
|   | 75382d13fd | ||
|   | d444280a28 | ||
|   | 52fc0fe04a | ||
|   | 216bebadf1 | ||
|   | a5592931cb | ||
|   | a2228417ff | ||
|   | 3e1e292c3e | ||
|   | f2f039ae9e | ||
|   | 29dde1eda0 | ||
|   | 45d3792ce0 | ||
|   | 875d0aaebb | ||
|   | 26c9d8ff6f | ||
|   | 5e3372e932 | ||
|   | f7069dcd18 | ||
|   | 560bb65384 | ||
|   | 50cd6a036e | ||
|   | 441ab2b5f8 | ||
|   | ba5ed188a1 | ||
|   | 72e672f08d | ||
|   | 120474ec6a | ||
|   | eee57c47f5 | ||
|   | 4c160869b8 | ||
|   | 3720a7fbe0 | ||
|   | 7afa541a53 | ||
|   | 6f979c8275 | ||
|   | d399241e65 | ||
|   | e85dec030a | ||
|   | d0220764cc | ||
|   | 75c1df9531 | ||
|   | bca7156d6b | ||
|   | 64277b7157 | ||
|   | 4a72543f65 | ||
|   | 5b84d29807 | ||
|   | a11061ec2b | ||
|   | 24cfb93b2e | ||
|   | 502b42d63a | ||
|   | 612672b79c | ||
|   | abc670e1b1 | ||
|   | d589ccdd01 | ||
|   | acb07d9f7d | ||
|   | f4d2186719 | ||
|   | d0ede5c665 | ||
|   | 554cbb5e9b | ||
|   | dbd32a56bf | ||
|   | 7f500235c6 | ||
|   | 39a58084c8 | ||
|   | cde0fde836 | ||
|   | e70cca0fda | ||
|   | 919bd7eb82 | ||
|   | 312cff3d6f | ||
|   | 0d86eef3d7 | ||
|   | 13acf570e7 | ||
|   | fa17623fa8 | ||
|   | 06fd525950 | ||
|   | 4805b5115a | ||
|   | 108dcb3e61 | ||
|   | 780d272535 | ||
|   | 02ea4b81a5 | ||
|   | 7c1bdc6d36 | ||
|   | 78c7b8b836 | ||
|   | 227da30acb | ||
|   | 610805026f | ||
|   | c02399c3d2 | ||
|   | e0799d4153 | ||
|   | 6df83f1aa9 | ||
|   | efb5ad1d9b | ||
|   | 716976f016 | ||
|   | 7892f41b84 | ||
|   | d549e03b3f | ||
|   | c511ef21ff | ||
|   | d64dc45899 | ||
|   | bcb0588409 | ||
|   | 0975959eb9 | ||
|   | e985a6d9d3 | ||
|   | b893305974 | ||
|   | 724fdd44e4 | ||
|   | b480ef669c | ||
|   | 4b145da046 | ||
|   | 83d168ece3 | ||
|   | ae44fe7818 | ||
|   | f8981b3acb | ||
|   | 050b324885 | ||
|   | e74c0df6c6 | ||
|   | 22d0d11895 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 80d0c0cf74 | ||
|   | 518646b925 | ||
|   | 479d7e0087 | ||
|   | 8ea1a555f4 | ||
|   | 04024dc37c | ||
|   | 060ff9288f | ||
|   | 197116ee78 | ||
|   | a1e0015257 | ||
|   | 7e701ef9e0 | ||
|   | 3d6fb661bb | ||
|   | fc372496da | ||
|   | ad7258fe9c | ||
|   | bd707cb2a8 | ||
|   | 1839b5f205 | ||
|   | 02b47f963c | ||
|   | f8a7f9378a | ||
|   | 65cb253be4 | ||
|   | a12356b24b | ||
|   | 6a67ad7f93 | ||
|   | 140a7f0b1c | ||
|   | 00159bc6b5 | ||
|   | 9542260103 | ||
|   | 72074578df | ||
|   | 3b4750a988 | ||
|   | aeec5f0163 | ||
|   | 9c94d8c8d6 | ||
|   | 581712a2c8 | ||
|   | b25b51aaca | ||
|   | fb97e13a61 | ||
|   | 36e154fdb2 | ||
|   | ca273a24b4 | ||
|   | d828bf2889 | ||
|   | 87efccef18 | ||
|   | e0bf522e7f | ||
|   | 5b1cd3bd3c | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | f00489196d | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | dd53bf7e51 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 35a6da26d2 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | c8c8748a0b | ||
|   | 46d0065a90 | ||
|   | 990b0180a8 | ||
|   | f3bfb72251 | 
| @@ -9,6 +9,7 @@ mongodb: | |||||||
|   db: test-misskey |   db: test-misskey | ||||||
|   user: admin |   user: admin | ||||||
|   pass: '' |   pass: '' | ||||||
|  | # __REDIS__ | ||||||
| redis: | redis: | ||||||
|   host: localhost |   host: localhost | ||||||
|   port: 6379 |   port: 6379 | ||||||
							
								
								
									
										137
									
								
								.circleci/config.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								.circleci/config.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | |||||||
|  | version: 2.1 | ||||||
|  |  | ||||||
|  | general: | ||||||
|  |   branches: | ||||||
|  |     ignore: | ||||||
|  |       - l10n_develop | ||||||
|  |       - imgbot | ||||||
|  |  | ||||||
|  | executors: | ||||||
|  |   default: | ||||||
|  |     working_directory: /tmp/workspace | ||||||
|  |     docker: | ||||||
|  |       - image: misskey/ci:latest | ||||||
|  |       - image: circleci/mongo:latest | ||||||
|  |       - image: circleci/redis:latest | ||||||
|  |   docker: | ||||||
|  |     working_directory: /tmp/workspace | ||||||
|  |     docker: | ||||||
|  |       - image: docker:latest | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   build: | ||||||
|  |     executor: default | ||||||
|  |     steps: | ||||||
|  |       - checkout | ||||||
|  |       - restore_cache: | ||||||
|  |           name: Restore npm package caches | ||||||
|  |           keys: | ||||||
|  |             - npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-package-{{ checksum "package.json" }}-lock-{{ checksum "package-lock.json" }}- | ||||||
|  |             - npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-package-{{ checksum "package.json" }}- | ||||||
|  |             - npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}- | ||||||
|  |             - npm-v1-arch-{{ arch }}- | ||||||
|  |             - npm-v1- | ||||||
|  |       - run: | ||||||
|  |           name: Install Dependencies | ||||||
|  |           command: | | ||||||
|  |             npm install | ||||||
|  |       - run: | ||||||
|  |           name: Configure | ||||||
|  |           command: | | ||||||
|  |             cp .ci/default.yml .config | ||||||
|  |             cp .ci/test.yml .config | ||||||
|  |       - run: | ||||||
|  |           name: Build | ||||||
|  |           command: | | ||||||
|  |             npm run build || (echo -e '\033[0;34mRebuild modules\033[0;39m' && ls -1A node_modules | grep '^[^@]' | xargs npm rebuild && ls -1A node_modules | grep '^@' | xargs -I%1 sh -c 'ls -1A node_modules/'%1' | xargs -P0 -I%2 npm rebuild node_modules/'%1'/%2' && npm run build) | ||||||
|  |             ls -1ARl node_modules > ls | ||||||
|  |       - save_cache: | ||||||
|  |           name: Cache npm packages | ||||||
|  |           key: npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-package-{{ checksum "package.json" }}-lock-{{ checksum "package-lock.json" }}-ls-{{ checksum "ls" }} | ||||||
|  |           paths: | ||||||
|  |             - node_modules | ||||||
|  |       - store_artifacts: | ||||||
|  |           path: built | ||||||
|  |       - persist_to_workspace: | ||||||
|  |           root: . | ||||||
|  |           paths: | ||||||
|  |             - . | ||||||
|  |   test: | ||||||
|  |     parameters: | ||||||
|  |       without_redis: | ||||||
|  |         type: string | ||||||
|  |         default: "" | ||||||
|  |     executor: default | ||||||
|  |     steps: | ||||||
|  |       - attach_workspace: | ||||||
|  |           at: /tmp/workspace | ||||||
|  |       - when: | ||||||
|  |           condition: <<parameters.without_redis>> | ||||||
|  |           steps: | ||||||
|  |             - run: | ||||||
|  |                 name: Configure | ||||||
|  |                 command: | | ||||||
|  |                   mv .config/test.yml .config/test_redis.yml | ||||||
|  |                   touch .config/test.yml | ||||||
|  |                   cat .config/test_redis.yml | while IFS= read line; do if [[ "$line" = '# __REDIS__' ]]; then break; else echo "$line" >> .config/test.yml; fi; done | ||||||
|  |       - run: | ||||||
|  |           name: Test | ||||||
|  |           command: | | ||||||
|  |             npm run test || (npm rebuild && npm run test) || ((node-gyp configure && node-gyp build && npm run build || (echo -e '\033[0;34mRebuild modules\033[0;39m' && ls -1A node_modules | grep '^[^@]' | xargs npm rebuild && ls -1A node_modules | grep '^@' | xargs -I%1 sh -c 'ls -1A node_modules/'%1' | xargs -P0 -I%2 npm rebuild node_modules/'%1'/%2' && npm run build)) && npm run test) | ||||||
|  |             ls -1ARl node_modules > ls | ||||||
|  |       - save_cache: | ||||||
|  |           name: Cache npm packages | ||||||
|  |           key: npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-package-{{ checksum "package.json" }}-lock-{{ checksum "package-lock.json" }}-ls-{{ checksum "ls" }} | ||||||
|  |           paths: | ||||||
|  |             - node_modules | ||||||
|  |  | ||||||
|  |   docker: | ||||||
|  |     parameters: | ||||||
|  |       with_deploy: | ||||||
|  |         type: string | ||||||
|  |         default: "" | ||||||
|  |     executor: docker | ||||||
|  |     steps: | ||||||
|  |       - checkout | ||||||
|  |       - setup_remote_docker | ||||||
|  |       - run: | ||||||
|  |           name: Build | ||||||
|  |           command: | | ||||||
|  |             docker build . | tee docker.log | ||||||
|  |             tail -n 1 docker.log | read __Successfully __built tag | ||||||
|  |       - when: | ||||||
|  |           condition: <<parameters.with_deploy>> | ||||||
|  |           steps: | ||||||
|  |             - run: | ||||||
|  |                 name: Deploy | ||||||
|  |                 command: | | ||||||
|  |                   if [ "$DOCKERHUB_USERNAME$DOCKERHUB_PASSWORD" ] | ||||||
|  |                    then | ||||||
|  |                     docker tag $tag misskey/misskey | ||||||
|  |                     docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD | ||||||
|  |                     docker push misskey/misskey | ||||||
|  |                    else | ||||||
|  |                     echo -e '\033[0;33mAborted deploying to Docker Hub\033[0;39m' | ||||||
|  |                   fi | ||||||
|  |  | ||||||
|  | workflows: | ||||||
|  |   version: 2 | ||||||
|  |   build-and-test: | ||||||
|  |     jobs: | ||||||
|  |       - build | ||||||
|  |       - test: | ||||||
|  |           requires: | ||||||
|  |             - build | ||||||
|  |       - test: | ||||||
|  |           without_redis: "true" | ||||||
|  |           requires: | ||||||
|  |             - build | ||||||
|  |       - docker: | ||||||
|  |           filters: | ||||||
|  |             branches: | ||||||
|  |               ignore: master | ||||||
|  |       - docker: | ||||||
|  |           with_deploy: "true" | ||||||
|  |           filters: | ||||||
|  |             branches: | ||||||
|  |               only: master | ||||||
| @@ -35,7 +35,7 @@ before_script: | |||||||
|   - npm install |   - npm install | ||||||
|  |  | ||||||
|   # 設定ファイルを配置 |   # 設定ファイルを配置 | ||||||
|   - cp ./.travis/default.yml ./.config |   - cp ./.ci/default.yml ./.config | ||||||
|   - cp ./.travis/test.yml ./.config |   - cp ./.ci/test.yml ./.config | ||||||
|  |  | ||||||
|   - travis_wait npm run build |   - travis_wait npm run build | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -3,8 +3,9 @@ FROM alpine:3.8 AS base | |||||||
| ENV NODE_ENV=production | ENV NODE_ENV=production | ||||||
|  |  | ||||||
| RUN apk add --no-cache nodejs nodejs-npm zlib | RUN apk add --no-cache nodejs nodejs-npm zlib | ||||||
|  | RUN npm i -g npm@latest | ||||||
|  |  | ||||||
| WORKDIR /misskey | WORKDIR /misskey | ||||||
| COPY . ./ |  | ||||||
|  |  | ||||||
| FROM base AS builder | FROM base AS builder | ||||||
|  |  | ||||||
| @@ -21,18 +22,23 @@ RUN apk add --no-cache \ | |||||||
|     pkgconfig \ |     pkgconfig \ | ||||||
|     libtool \ |     libtool \ | ||||||
|     zlib-dev |     zlib-dev | ||||||
| RUN npm install \ | RUN npm i -g node-gyp | ||||||
|     && npm install -g node-gyp \ |  | ||||||
|     && node-gyp configure \ | COPY ./package.json ./ | ||||||
|  | RUN npm i | ||||||
|  |  | ||||||
|  | COPY . ./ | ||||||
|  | RUN node-gyp configure \ | ||||||
|  && node-gyp build \ |  && node-gyp build \ | ||||||
|  && npm run build |  && npm run build | ||||||
|  |  | ||||||
| FROM base AS runner | FROM base AS runner | ||||||
|  |  | ||||||
| COPY --from=builder /misskey/built ./built |  | ||||||
| COPY --from=builder /misskey/node_modules ./node_modules |  | ||||||
|  |  | ||||||
| RUN apk add --no-cache tini | RUN apk add --no-cache tini | ||||||
| ENTRYPOINT ["/sbin/tini", "--"] | ENTRYPOINT ["/sbin/tini", "--"] | ||||||
|  |  | ||||||
|  | COPY --from=builder /misskey/node_modules ./node_modules | ||||||
|  | COPY --from=builder /misskey/built ./built | ||||||
|  | COPY . ./ | ||||||
|  |  | ||||||
| CMD ["npm", "start"] | CMD ["npm", "start"] | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								README.md
									
									
									
									
									
								
							| @@ -3,6 +3,7 @@ | |||||||
| [](https://misskey.xyz/) | [](https://misskey.xyz/) | ||||||
| ================================================================ | ================================================================ | ||||||
|  |  | ||||||
|  | [](https://circleci.com/gh/syuilo/misskey) | ||||||
| [![][travis-badge]][travis-link] | [![][travis-badge]][travis-link] | ||||||
| [![][dependencies-badge]][dependencies-link] | [![][dependencies-badge]][dependencies-link] | ||||||
| [](http://makeapullrequest.com) | [](http://makeapullrequest.com) | ||||||
| @@ -71,39 +72,46 @@ Please see [Contribution guide](./CONTRIBUTING.md). | |||||||
| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ||||||
| <!-- PATREON_START --> | <!-- PATREON_START --> | ||||||
| <table><tr> | <table><tr> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=CXe9AqlZy9AsYfiWd3OBYVOzvODoN47Litz0Tu4BFpU%3D" alt="Gargron"></td> |  | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D" alt="negao"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D" alt="negao"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1?token-time=2145916800&token-hash=d6P5MWHHsCMxUuBAEPAoVc5wLUR19mIhqAq7Ma9h9rI%3D" alt="ne_moni"></td> |  | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/2?token-time=2145916800&token-hash=mgPdX9TqZxEg4TTPuc477dxhIgYk9246qafjWZEqZ7g%3D" alt="Melilot"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/2?token-time=2145916800&token-hash=mgPdX9TqZxEg4TTPuc477dxhIgYk9246qafjWZEqZ7g%3D" alt="Melilot"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/2?token-time=2145916800&token-hash=rwZ8qvbm_kpA4ib3kc07tVKupXeySpY5ATQFGxfL9v0%3D" alt="Xeltica"></td> |  | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td> | ||||||
|  | <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1?token-time=2145916800&token-hash=I8lJVM8LeW6TSo5W6uIIRZ42cw83zp1wK_FsbzY0mcQ%3D" alt="mydarkstar"></td> | ||||||
| <td><img src="https://c8.patreon.com/2/100/12718187" alt="Peter G."></td> | <td><img src="https://c8.patreon.com/2/100/12718187" alt="Peter G."></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=zwSu01tOtn5xTUucDZHuPsCxF2HBEMVs9ROJKTlEV_o%3D" alt="nemu"></td> |  | ||||||
| </tr><tr> | </tr><tr> | ||||||
| <td><a href="https://www.patreon.com/mastodon">Gargron</a></td> |  | ||||||
| <td><a href="https://www.patreon.com/negao">negao</a></td> | <td><a href="https://www.patreon.com/negao">negao</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td> |  | ||||||
| <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td> | <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td> | ||||||
| <td><a href="https://www.patreon.com/AxellaMC">Xeltica</a></td> |  | ||||||
| <td><a href="https://www.patreon.com/user?u=3384329">べすれい</a></td> | <td><a href="https://www.patreon.com/user?u=3384329">べすれい</a></td> | ||||||
| <td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td> | <td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td> | <td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td> |  | ||||||
| </tr></table> | </tr></table> | ||||||
| <table><tr> | <table><tr> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/2?token-time=2145916800&token-hash=zElv7ZcPL3viGsXbNG_KWiKrbV0vvw1gk0panx8DJoo%3D" alt="Naoki Kosaka"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=zwSu01tOtn5xTUucDZHuPsCxF2HBEMVs9ROJKTlEV_o%3D" alt="nemu"></td> | ||||||
|  | <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3?token-time=2145916800&token-hash=qsdn0-e6yLaLI6hUX9JAkyTR6a5UdnSp7T1foniBvGQ%3D" alt="YUKIMOCHI"></td> | ||||||
|  | <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/8241184/39e18850e87a449e9c9a71acb3310ebd/2?token-time=2145916800&token-hash=iUXOQzRyJDv3PJxwS7Mjwg1459dzh2trOq6NFtXu_OM%3D" alt="Acid Chicken"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=UERBN4OyP7Nh5XwwdDg0N0IE5cD6_qUQMO81Z5Wizso%3D" alt="Hiratake"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=UERBN4OyP7Nh5XwwdDg0N0IE5cD6_qUQMO81Z5Wizso%3D" alt="Hiratake"></td> | ||||||
|  | <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/10789744/97175095d8f04c0f86225ff47cb98d40/1?token-time=2145916800&token-hash=P4BIzCX2I1CkEP66ottfhsC8Wr6BUSamjA-vq3pLqFI%3D" alt="Naoki Hirayama"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D" alt="dansup"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D" alt="dansup"></td> | ||||||
|  | <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=tB1e_r8RlZ5sFL0KV_e8dugapxatNBRK1Z3h67TO1g8%3D" alt="Gargron"></td> | ||||||
|  | <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1?token-time=2145916800&token-hash=VZUtwrjQa8Jml4twCjHYQQZ64wHEY4oIlGl7Kc-VYUQ%3D" alt="Nokotaro Takeda"></td> | ||||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D" alt="Takashi Shibuya"></td> | <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D" alt="Takashi Shibuya"></td> | ||||||
| </tr><tr> | </tr><tr> | ||||||
| <td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</a></td> | <td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/acid_chicken">Acid Chicken</a></td> | ||||||
| <td><a href="https://www.patreon.com/hiratake">Hiratake</a></td> | <td><a href="https://www.patreon.com/hiratake">Hiratake</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/spinlock">Naoki Hirayama</a></td> | ||||||
| <td><a href="https://www.patreon.com/dansup">dansup</a></td> | <td><a href="https://www.patreon.com/dansup">dansup</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/mastodon">Gargron</a></td> | ||||||
|  | <td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td> | ||||||
| <td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td> | <td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td> | ||||||
| </tr></table> | </tr></table> | ||||||
|  | <table><tr> | ||||||
|  | </tr><tr> | ||||||
|  | </tr></table> | ||||||
|  |  | ||||||
| **Last updated:** Tue, 02 Oct 2018 09:25:07 UTC | **Last updated:** Wed, 31 Oct 2018 23:21:06 UTC | ||||||
| <!-- PATREON_END --> | <!-- PATREON_END --> | ||||||
|  |  | ||||||
| :four_leaf_clover: Copyright | :four_leaf_clover: Copyright | ||||||
|   | |||||||
							
								
								
									
										38
									
								
								appveyor.yml
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								appveyor.yml
									
									
									
									
									
								
							| @@ -1,38 +0,0 @@ | |||||||
| # appveyor file |  | ||||||
| # http://www.appveyor.com/docs/appveyor-yml |  | ||||||
|  |  | ||||||
| environment: |  | ||||||
|   matrix: |  | ||||||
|     - nodejs_version: 11.0.0 |  | ||||||
|  |  | ||||||
| platform: |  | ||||||
|   - x64 |  | ||||||
|   - Any CPU |  | ||||||
|  |  | ||||||
| build: off |  | ||||||
|  |  | ||||||
| install: |  | ||||||
|   # Get the latest stable version of Node.js or io.js |  | ||||||
|   - ps: Install-Product node $env:nodejs_version |  | ||||||
|  |  | ||||||
|   # Update node-gyp |  | ||||||
|   # 必須! node-gyp のバージョンを上げないと、ネイティブモジュールのコンパイルに失敗します |  | ||||||
|   - npm install -g node-gyp |  | ||||||
|  |  | ||||||
|   - npm install |  | ||||||
|  |  | ||||||
| init: |  | ||||||
|   # git clone の際の改行を変換しないようにします |  | ||||||
|   - git config --global core.autocrlf false |  | ||||||
|  |  | ||||||
| before_test: |  | ||||||
|   # 設定ファイルを配置 |  | ||||||
|   - cp ./.travis/default.yml ./.config |  | ||||||
|   - cp ./.travis/test.yml ./.config |  | ||||||
|  |  | ||||||
|   - npm run build |  | ||||||
|  |  | ||||||
| test_script: |  | ||||||
|   - node --version |  | ||||||
|   - npm --version |  | ||||||
|   - npm test |  | ||||||
| @@ -1,13 +0,0 @@ | |||||||
| const deleteUser = require('../built/models/user').deleteUser; |  | ||||||
|  |  | ||||||
| const args = process.argv.slice(2); |  | ||||||
|  |  | ||||||
| const userId = args[0]; |  | ||||||
|  |  | ||||||
| console.log(`deleting ${userId}...`); |  | ||||||
|  |  | ||||||
| deleteUser(userId).then(() => { |  | ||||||
| 	console.log('done'); |  | ||||||
| }, e => { |  | ||||||
| 	console.error(e); |  | ||||||
| }); |  | ||||||
| @@ -1,23 +0,0 @@ | |||||||
| const mongo = require('mongodb'); |  | ||||||
| const User = require('../built/models/user').default; |  | ||||||
|  |  | ||||||
| const args = process.argv.slice(2); |  | ||||||
|  |  | ||||||
| const user = args[0]; |  | ||||||
|  |  | ||||||
| const q = user.startsWith('@') ? { |  | ||||||
| 	username: user.split('@')[1], |  | ||||||
| 	host: user.split('@')[2] || null |  | ||||||
| } : { _id: new mongo.ObjectID(user) }; |  | ||||||
|  |  | ||||||
| console.log(`Mark as verfied ${user}...`); |  | ||||||
|  |  | ||||||
| User.update(q, { |  | ||||||
| 	$set: { |  | ||||||
| 		isVerified: true |  | ||||||
| 	} |  | ||||||
| }).then(() => { |  | ||||||
| 	console.log(`Done ${user}`); |  | ||||||
| }, e => { |  | ||||||
| 	console.error(e); |  | ||||||
| }); |  | ||||||
| @@ -1,42 +0,0 @@ | |||||||
| const { default: Note } = require('../built/models/note'); |  | ||||||
| const { default: Meta } = require('../built/models/meta'); |  | ||||||
| const { default: User } = require('../built/models/user'); |  | ||||||
|  |  | ||||||
| async function main() { |  | ||||||
| 	const meta = await Meta.findOne({}); |  | ||||||
|  |  | ||||||
| 	const notesCount = await Note.count(); |  | ||||||
|  |  | ||||||
| 	const usersCount = await User.count(); |  | ||||||
|  |  | ||||||
| 	const originalNotesCount = await Note.count({ |  | ||||||
| 		'_user.host': null |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	const originalUsersCount = await User.count({ |  | ||||||
| 		host: null |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	const stats = { |  | ||||||
| 		notesCount, |  | ||||||
| 		usersCount, |  | ||||||
| 		originalNotesCount, |  | ||||||
| 		originalUsersCount |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	if (meta) { |  | ||||||
| 		await Meta.update({}, { |  | ||||||
| 			$set: { |  | ||||||
| 				stats |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		await Meta.insert({ |  | ||||||
| 			stats |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| main().then(() => { |  | ||||||
| 	console.log('done'); |  | ||||||
| }).catch(console.error); |  | ||||||
| @@ -18,6 +18,10 @@ If you find an untranslated part on Misskey: | |||||||
| 4. Add the text property using the `foo` keyword below the path that you found or created in step 2. Make sure to type your text in quotation marks. Text should always be inside of quotes. | 4. Add the text property using the `foo` keyword below the path that you found or created in step 2. Make sure to type your text in quotation marks. Text should always be inside of quotes. | ||||||
| 	-   For example, in this case we add timeline: `timeline: "タイムライン"` to `locales/ja-JP.yml`. | 	-   For example, in this case we add timeline: `timeline: "タイムライン"` to `locales/ja-JP.yml`. | ||||||
|  |  | ||||||
| 5. And done! | 5. When you add text to the ja-JP file (of syuilo/misskey), it will automatically be applied to all other local language files within 24-48 hours. Translations added in ja-JP file should contain the original Japanese strings (example see step 4).  | ||||||
|  |  | ||||||
|  | 6. The new strings will automatically appear in the localized language files in the original Japanese text. After that, please go to [CrowdIn](https://crowdin.com/project/misskey) to do the localized translations in your language. | ||||||
|  |  | ||||||
|  | 7. And done! | ||||||
|  |  | ||||||
| For more details, please refer to this [commit](https://github.com/syuilo/misskey/commit/10f6d5980fa7692ccb45fbc5f843458b69b7607c). | For more details, please refer to this [commit](https://github.com/syuilo/misskey/commit/10f6d5980fa7692ccb45fbc5f843458b69b7607c). | ||||||
|   | |||||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "プロフィール" |   profile: "プロフィール" | ||||||
|   notification: "通知" |   notification: "通知" | ||||||
|   apps: "アプリ" |   apps: "アプリ" | ||||||
|   mute: "ミュート" |   mute-and-block: "ミュート/ブロック" | ||||||
|  |   blocking: "ブロック" | ||||||
|   security: "セキュリティ" |   security: "セキュリティ" | ||||||
|   signin: "サインイン履歴" |   signin: "サインイン履歴" | ||||||
|   password: "パスワード" |   password: "パスワード" | ||||||
| @@ -847,21 +848,32 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "設定が完了しました!" |   success: "設定が完了しました!" | ||||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" |   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" |   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" |   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" |   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" |   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||||
|   regenerate-token: "トークンを再生成" |   regenerate-token: "トークンを再生成" | ||||||
|   token: "Token:" |   token: "Token:" | ||||||
|   enter-password: "パスワードを入力してください" |   enter-password: "パスワードを入力してください" | ||||||
|  |   console: | ||||||
|  |     title: 'APIコンソール' | ||||||
|  |     endpoint: 'エンドポイント' | ||||||
|  |     parameter: 'パラメータ' | ||||||
|  |     send: '送信' | ||||||
|  |     sending: '応答待ち' | ||||||
|  |     response: '結果' | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "連携しているアプリケーションはありません" |   no-apps: "連携しているアプリケーションはありません" | ||||||
| common/views/components/drive-settings.vue: | common/views/components/drive-settings.vue: | ||||||
|   max: "容量" |   max: "容量" | ||||||
|   in-use: "使用中" |   in-use: "使用中" | ||||||
|   stats: "統計" |   stats: "統計" | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "ミュートしているユーザーはいません" |   mute-and-block: "ミュートとブロック" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   block: "ブロック" | ||||||
|  |   no-muted-users: "ミュートしているユーザーはいません" | ||||||
|  |   no-blocked-users: "ブロックしているユーザーはいません" | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "パスワードを変更する" |   reset: "パスワードを変更する" | ||||||
|   enter-current-password: "現在のパスワードを入力してください" |   enter-current-password: "現在のパスワードを入力してください" | ||||||
| @@ -1048,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "ミュートする" |   mute: "ミュートする" | ||||||
|   muted: "ミュートしています" |   muted: "ミュートしています" | ||||||
|   unmute: "ミュート解除" |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロックする" | ||||||
|  |   unblock: "ブロック解除" | ||||||
|  |   block-confirm: "このユーザーをブロックしますか?" | ||||||
|   push-to-a-list: "リストに追加" |   push-to-a-list: "リストに追加" | ||||||
|   list-pushed: "{user}を{list}に追加しました。" |   list-pushed: "{user}を{list}に追加しました。" | ||||||
| desktop/views/pages/user/user.header.vue: | desktop/views/pages/user/user.header.vue: | ||||||
| @@ -1322,6 +1337,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "タイムライン" |   timeline: "タイムライン" | ||||||
|   media: "メディア" |   media: "メディア" | ||||||
|   is-suspended: "このユーザーは凍結されています。" |   is-suspended: "このユーザーは凍結されています。" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロック" | ||||||
|  |   unblock: "ブロック解除" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "最近の投稿" |   recent-notes: "最近の投稿" | ||||||
|   images: "画像" |   images: "画像" | ||||||
|   | |||||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "Profil" |   profile: "Profil" | ||||||
|   notification: "Mitteilungen" |   notification: "Mitteilungen" | ||||||
|   apps: "In App öffnen" |   apps: "In App öffnen" | ||||||
|   mute: "Stummschalten" |   mute-and-block: "ミュート/ブロック" | ||||||
|  |   blocking: "ブロック" | ||||||
|   security: "Sicherheit" |   security: "Sicherheit" | ||||||
|   signin: "サインイン履歴" |   signin: "サインイン履歴" | ||||||
|   password: "Passwort" |   password: "Passwort" | ||||||
| @@ -847,21 +848,32 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "設定が完了しました!" |   success: "設定が完了しました!" | ||||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" |   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" |   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" |   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" |   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" |   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||||
|   regenerate-token: "トークンを再生成" |   regenerate-token: "トークンを再生成" | ||||||
|   token: "Token:" |   token: "Token:" | ||||||
|   enter-password: "Bitte Passwort eingeben" |   enter-password: "パスワードを入力してください" | ||||||
|  |   console: | ||||||
|  |     title: 'APIコンソール' | ||||||
|  |     endpoint: 'エンドポイント' | ||||||
|  |     parameter: 'パラメータ' | ||||||
|  |     send: '送信' | ||||||
|  |     sending: '応答待ち' | ||||||
|  |     response: '結果' | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "連携しているアプリケーションはありません" |   no-apps: "連携しているアプリケーションはありません" | ||||||
| common/views/components/drive-settings.vue: | common/views/components/drive-settings.vue: | ||||||
|   max: "容量" |   max: "容量" | ||||||
|   in-use: "使用中" |   in-use: "使用中" | ||||||
|   stats: "統計" |   stats: "統計" | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "ミュートしているユーザーはいません" |   mute-and-block: "ミュートとブロック" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   block: "ブロック" | ||||||
|  |   no-muted-users: "ミュートしているユーザーはいません" | ||||||
|  |   no-blocked-users: "ブロックしているユーザーはいません" | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "Passwort ändern" |   reset: "Passwort ändern" | ||||||
|   enter-current-password: "Derzeitiges Passwort eingeben" |   enter-current-password: "Derzeitiges Passwort eingeben" | ||||||
| @@ -1048,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "ミュートする" |   mute: "ミュートする" | ||||||
|   muted: "ミュートしています" |   muted: "ミュートしています" | ||||||
|   unmute: "ミュート解除" |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロックする" | ||||||
|  |   unblock: "ブロック解除" | ||||||
|  |   block-confirm: "このユーザーをブロックしますか?" | ||||||
|   push-to-a-list: "リストに追加" |   push-to-a-list: "リストに追加" | ||||||
|   list-pushed: "{user}を{list}に追加しました。" |   list-pushed: "{user}を{list}に追加しました。" | ||||||
| desktop/views/pages/user/user.header.vue: | desktop/views/pages/user/user.header.vue: | ||||||
| @@ -1322,6 +1337,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "タイムライン" |   timeline: "タイムライン" | ||||||
|   media: "メディア" |   media: "メディア" | ||||||
|   is-suspended: "このユーザーは凍結されています。" |   is-suspended: "このユーザーは凍結されています。" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロック" | ||||||
|  |   unblock: "ブロック解除" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "最近の投稿" |   recent-notes: "最近の投稿" | ||||||
|   images: "画像" |   images: "画像" | ||||||
|   | |||||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "Profile" |   profile: "Profile" | ||||||
|   notification: "Notification" |   notification: "Notification" | ||||||
|   apps: "Apps" |   apps: "Apps" | ||||||
|   mute: "Mute" |   mute-and-block: "Mute / Block" | ||||||
|  |   blocking: "Blocking" | ||||||
|   security: "Security" |   security: "Security" | ||||||
|   signin: "Sign in history" |   signin: "Sign in history" | ||||||
|   password: "Password" |   password: "Password" | ||||||
| @@ -847,21 +848,32 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "Settings saved!" |   success: "Settings saved!" | ||||||
|   failed: "Failed to setup. Please ensure that the token is correct." |   failed: "Failed to setup. Please ensure that the token is correct." | ||||||
|   info: "From the next time you sign in to Misskey, the token displayed on your device will be necessary too, as well as the password." |   info: "From the next time you sign in to Misskey, the token displayed on your device will be necessary too, as well as the password." | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "To access the API, set this token as the key 'i' of request parameters." |   intro: "To access the API, set this token as the key 'i' of request parameters." | ||||||
|   caution: "Do not enter this token to any apps nor tell this token to others otherwise your account may get compromised." |   caution: "Do not enter this token to any apps nor tell this token to others otherwise your account may get compromised." | ||||||
|   regeneration-of-token: "If your token gets leaked, you can regenerate it." |   regeneration-of-token: "If your token gets leaked, you can regenerate it." | ||||||
|   regenerate-token: "Regenerate the token" |   regenerate-token: "Regenerate the token" | ||||||
|   token: "Token:" |   token: "Token:" | ||||||
|   enter-password: "Please enter the password" |   enter-password: "Enter the password" | ||||||
|  |   console: | ||||||
|  |     title: 'API console' | ||||||
|  |     endpoint: 'Endpoint' | ||||||
|  |     parameter: 'Parameters' | ||||||
|  |     send: 'Send' | ||||||
|  |     sending: 'Sending' | ||||||
|  |     response: 'Result' | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "No linked applications" |   no-apps: "No linked applications" | ||||||
| common/views/components/drive-settings.vue: | common/views/components/drive-settings.vue: | ||||||
|   max: "Max" |   max: "Max" | ||||||
|   in-use: "In use" |   in-use: "In use" | ||||||
|   stats: "Statistics" |   stats: "Statistics" | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "No muted users" |   mute-and-block: "Mute / Block" | ||||||
|  |   mute: "Mute" | ||||||
|  |   block: "Blocking" | ||||||
|  |   no-muted-users: "No muted users" | ||||||
|  |   no-blocked-users: "No blocked users" | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "Change password" |   reset: "Change password" | ||||||
|   enter-current-password: "Enter the current password" |   enter-current-password: "Enter the current password" | ||||||
| @@ -1048,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "Mute" |   mute: "Mute" | ||||||
|   muted: "Muting" |   muted: "Muting" | ||||||
|   unmute: "Unmute" |   unmute: "Unmute" | ||||||
|  |   block: "Block" | ||||||
|  |   unblock: "Unblock" | ||||||
|  |   block-confirm: "Are you sure block this user?" | ||||||
|   push-to-a-list: "Add to list" |   push-to-a-list: "Add to list" | ||||||
|   list-pushed: "Successfully added {user} to {list}." |   list-pushed: "Successfully added {user} to {list}." | ||||||
| desktop/views/pages/user/user.header.vue: | desktop/views/pages/user/user.header.vue: | ||||||
| @@ -1322,6 +1337,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "Timeline" |   timeline: "Timeline" | ||||||
|   media: "Media" |   media: "Media" | ||||||
|   is-suspended: "This account has been suspended." |   is-suspended: "This account has been suspended." | ||||||
|  |   mute: "Mute" | ||||||
|  |   unmute: "Unmute" | ||||||
|  |   block: "Block" | ||||||
|  |   unblock: "Unblock" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "Recent notes" |   recent-notes: "Recent notes" | ||||||
|   images: "Images" |   images: "Images" | ||||||
|   | |||||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "Perfil" |   profile: "Perfil" | ||||||
|   notification: "Notificación" |   notification: "Notificación" | ||||||
|   apps: "Aplicaciones" |   apps: "Aplicaciones" | ||||||
|   mute: "Silenciar" |   mute-and-block: "ミュート/ブロック" | ||||||
|  |   blocking: "ブロック" | ||||||
|   security: "Seguridad" |   security: "Seguridad" | ||||||
|   signin: "Historial de inicios de sesión" |   signin: "Historial de inicios de sesión" | ||||||
|   password: "Contraseña" |   password: "Contraseña" | ||||||
| @@ -847,21 +848,32 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "¡Configuraciones guardadas!" |   success: "¡Configuraciones guardadas!" | ||||||
|   failed: "Error al configurar. Por favor asegúrate de que el token es correcto." |   failed: "Error al configurar. Por favor asegúrate de que el token es correcto." | ||||||
|   info: "Desde ahora, ingresa el token que se muestra en tu dispositivo adicionalmente a tu contraseña cuando inicies sesión en Misskey" |   info: "Desde ahora, ingresa el token que se muestra en tu dispositivo adicionalmente a tu contraseña cuando inicies sesión en Misskey" | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "Para acceder al API, configura este token como la letra \"i\" de los parámetros requeridos." |   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||||
|   caution: "Por favor no muestres este token a otros (no lo ingreses en otro lugar que no sea aquí). De otra forma, tu cuenta puede llegar a ser comprometida." |   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||||
|   regeneration-of-token: "En el caso no deseado de que este token lo tenga otra persona, puedes regenerarlo." |   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||||
|   regenerate-token: "Regenerar el token" |   regenerate-token: "トークンを再生成" | ||||||
|   token: "Token:" |   token: "Token:" | ||||||
|   enter-password: "Por favor ingresa tu contraseña" |   enter-password: "パスワードを入力してください" | ||||||
|  |   console: | ||||||
|  |     title: 'APIコンソール' | ||||||
|  |     endpoint: 'エンドポイント' | ||||||
|  |     parameter: 'パラメータ' | ||||||
|  |     send: '送信' | ||||||
|  |     sending: '応答待ち' | ||||||
|  |     response: '結果' | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "No hay aplicaciones asociadas" |   no-apps: "No hay aplicaciones asociadas" | ||||||
| common/views/components/drive-settings.vue: | common/views/components/drive-settings.vue: | ||||||
|   max: "容量" |   max: "容量" | ||||||
|   in-use: "使用中" |   in-use: "使用中" | ||||||
|   stats: "統計" |   stats: "統計" | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "No hay usuarios silenciados" |   mute-and-block: "ミュートとブロック" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   block: "ブロック" | ||||||
|  |   no-muted-users: "ミュートしているユーザーはいません" | ||||||
|  |   no-blocked-users: "ブロックしているユーザーはいません" | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "Cambiar contraseña" |   reset: "Cambiar contraseña" | ||||||
|   enter-current-password: "Ingresar contraseña actual" |   enter-current-password: "Ingresar contraseña actual" | ||||||
| @@ -1048,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "ミュートする" |   mute: "ミュートする" | ||||||
|   muted: "ミュートしています" |   muted: "ミュートしています" | ||||||
|   unmute: "ミュート解除" |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロックする" | ||||||
|  |   unblock: "ブロック解除" | ||||||
|  |   block-confirm: "このユーザーをブロックしますか?" | ||||||
|   push-to-a-list: "リストに追加" |   push-to-a-list: "リストに追加" | ||||||
|   list-pushed: "{user}を{list}に追加しました。" |   list-pushed: "{user}を{list}に追加しました。" | ||||||
| desktop/views/pages/user/user.header.vue: | desktop/views/pages/user/user.header.vue: | ||||||
| @@ -1322,6 +1337,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "タイムライン" |   timeline: "タイムライン" | ||||||
|   media: "メディア" |   media: "メディア" | ||||||
|   is-suspended: "このユーザーは凍結されています。" |   is-suspended: "このユーザーは凍結されています。" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロック" | ||||||
|  |   unblock: "ブロック解除" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "最近の投稿" |   recent-notes: "最近の投稿" | ||||||
|   images: "画像" |   images: "画像" | ||||||
|   | |||||||
| @@ -28,11 +28,11 @@ common: | |||||||
|   BSoD: |   BSoD: | ||||||
|     fatal-error: ":( 致命的な問題が発生しました。" |     fatal-error: ":( 致命的な問題が発生しました。" | ||||||
|     update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。" |     update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。" | ||||||
|     error-code: "エラーコード" |     error-code: "Code d’erreur" | ||||||
|     browser-version: "ブラウザ バージョン" |     browser-version: "Version du navigateur" | ||||||
|     client-version: "クライアント バージョン" |     client-version: "La version du client" | ||||||
|     email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。" |     email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。" | ||||||
|     thanks: "Thank you for using Misskey." |     thanks: "Merci d’avoir choisi d’utiliser Misskey." | ||||||
|   got-it: "J'ai compris !" |   got-it: "J'ai compris !" | ||||||
|   customization-tips: |   customization-tips: | ||||||
|     title: "Conseils de personnalisation" |     title: "Conseils de personnalisation" | ||||||
| @@ -62,7 +62,7 @@ common: | |||||||
|     years_ago: "Il y a {} an·s" |     years_ago: "Il y a {} an·s" | ||||||
|   month-and-day: "{month} mois/{day} jour" |   month-and-day: "{month} mois/{day} jour" | ||||||
|   trash: "Corbeille" |   trash: "Corbeille" | ||||||
|   drive: "ドライブ" |   drive: "Drive" | ||||||
|   weekday-short: |   weekday-short: | ||||||
|     sunday: "D" |     sunday: "D" | ||||||
|     monday: "L" |     monday: "L" | ||||||
| @@ -124,12 +124,12 @@ common: | |||||||
|   reduce-motion: "Réduire les animations dans l’interface utilisateur" |   reduce-motion: "Réduire les animations dans l’interface utilisateur" | ||||||
|   this-setting-is-this-device-only: "Uniquement sur cet appareil" |   this-setting-is-this-device-only: "Uniquement sur cet appareil" | ||||||
|   do-not-use-in-production: 'Il s’agit d’une version de développement. Ne pas utiliser dans un environnement de production.' |   do-not-use-in-production: 'Il s’agit d’une version de développement. Ne pas utiliser dans un environnement de production.' | ||||||
|   is-remote-user: "このユーザー情報はコピーです。" |   is-remote-user: "Ces informations utilisateur ont été copiées." | ||||||
|   is-remote-post: "この投稿情報はコピーです。" |   is-remote-post: "この投稿情報はコピーです。" | ||||||
|   view-on-remote: "正確な情報を見る" |   view-on-remote: "Consulter le profil complet" | ||||||
|   error: |   error: | ||||||
|     title: '問題が発生しました' |     title: 'Une erreur est survenue' | ||||||
|     retry: 'やり直す' |     retry: 'Réessayer' | ||||||
|   reversi: |   reversi: | ||||||
|     drawn: "Partie nulle" |     drawn: "Partie nulle" | ||||||
|     my-turn: "C’est votre tour" |     my-turn: "C’est votre tour" | ||||||
| @@ -185,7 +185,7 @@ common: | |||||||
|     rename: "Renommer" |     rename: "Renommer" | ||||||
|     stack-left: "Vers la gauche" |     stack-left: "Vers la gauche" | ||||||
|     pop-right: "Vers la droite" |     pop-right: "Vers la droite" | ||||||
|   dev: "アプリの作成に失敗しました。再度お試しください。" |   dev: "Échec lors de la création de l’application. Veuillez réessayer." | ||||||
| auth/views/form.vue: | auth/views/form.vue: | ||||||
|   share-access: "Désirez-vous <b>autoriser</b> <i>{{ app.name }}</i> à avoir accès à votre compte ?" |   share-access: "Désirez-vous <b>autoriser</b> <i>{{ app.name }}</i> à avoir accès à votre compte ?" | ||||||
|   permission-ask: "Cette application nécessite les autorisations suivantes :" |   permission-ask: "Cette application nécessite les autorisations suivantes :" | ||||||
| @@ -542,24 +542,24 @@ desktop/views/components/charts.vue: | |||||||
|   title: "Graphiques" |   title: "Graphiques" | ||||||
|   per-day: "par jour" |   per-day: "par jour" | ||||||
|   per-hour: "par heure" |   per-hour: "par heure" | ||||||
|   federation: "フェデレーション" |   federation: "Fédération" | ||||||
|   notes: "Publications" |   notes: "Publications" | ||||||
|   users: "Utilisateurs" |   users: "Utilisateurs" | ||||||
|   drive: "Drive" |   drive: "Drive" | ||||||
|   network: "Réseau" |   network: "Réseau" | ||||||
|   charts: |   charts: | ||||||
|     federation-instances: "インスタンスの増減" |     federation-instances: "インスタンスの増減" | ||||||
|     federation-instances-total: "インスタンスの積算" |     federation-instances-total: "Nombre total d’instances" | ||||||
|     notes: "投稿の増減 (統合)" |     notes: "投稿の増減 (統合)" | ||||||
|     local-notes: "投稿の増減 (ローカル)" |     local-notes: "投稿の増減 (ローカル)" | ||||||
|     remote-notes: "投稿の増減 (リモート)" |     remote-notes: "投稿の増減 (リモート)" | ||||||
|     notes-total: "Total des notes" |     notes-total: "Total des notes" | ||||||
|     users: "Nombre d’utilisateurs·trices : augmentation/diminution" |     users: "Nombre d’utilisateurs·trices : augmentation/diminution" | ||||||
|     users-total: "ユーザーの積算" |     users-total: "Nombre total des utilisateurs·rices" | ||||||
|     drive: "ドライブ使用量の増減" |     drive: "ドライブ使用量の増減" | ||||||
|     drive-total: "ドライブ使用量の積算" |     drive-total: "Utilisation totale du lecteur" | ||||||
|     drive-files: "ドライブのファイル数の増減" |     drive-files: "ドライブのファイル数の増減" | ||||||
|     drive-files-total: "ドライブのファイル数の積算" |     drive-files-total: "Nombre total de fichiers sur le lecteur" | ||||||
|     network-requests: "Requêtes" |     network-requests: "Requêtes" | ||||||
|     network-time: "Temps de réponse" |     network-time: "Temps de réponse" | ||||||
|     network-usage: "Traffic" |     network-usage: "Traffic" | ||||||
| @@ -679,10 +679,10 @@ desktop/views/components/note.vue: | |||||||
|   reposted-by: "Partagé par {}" |   reposted-by: "Partagé par {}" | ||||||
|   reply: "Répondre" |   reply: "Répondre" | ||||||
|   renote: "Partager" |   renote: "Partager" | ||||||
|   add-reaction: "リアクション" |   add-reaction: "Ajouter votre réaction" | ||||||
|   detail: "詳細" |   detail: "Détails" | ||||||
|   private: "この投稿は非公開です" |   private: "Cette publication est privée" | ||||||
|   deleted: "この投稿は削除されました" |   deleted: "Cette publication a été supprimée" | ||||||
| desktop/views/components/notes.vue: | desktop/views/components/notes.vue: | ||||||
|   error: "Échec du chargement." |   error: "Échec du chargement." | ||||||
|   retry: "Réessayer" |   retry: "Réessayer" | ||||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "Profil" |   profile: "Profil" | ||||||
|   notification: "Notification" |   notification: "Notification" | ||||||
|   apps: "Applications" |   apps: "Applications" | ||||||
|   mute: "Mettre en sourdine" |   mute-and-block: "ミュート/ブロック" | ||||||
|  |   blocking: "ブロック" | ||||||
|   security: "Sécurité" |   security: "Sécurité" | ||||||
|   signin: "Historique de connexion" |   signin: "Historique de connexion" | ||||||
|   password: "Mot de Passe" |   password: "Mot de Passe" | ||||||
| @@ -765,7 +766,7 @@ desktop/views/components/settings.vue: | |||||||
|   deck-default: "デッキをデフォルトのUIにする" |   deck-default: "デッキをデフォルトのUIにする" | ||||||
|   display: "Affichage et design" |   display: "Affichage et design" | ||||||
|   customize: "Personnaliser l'Accueil" |   customize: "Personnaliser l'Accueil" | ||||||
|   wallpaper: "壁紙" |   wallpaper: "Arrière plan" | ||||||
|   choose-wallpaper: "Sélectionner un fond d'écran" |   choose-wallpaper: "Sélectionner un fond d'écran" | ||||||
|   delete-wallpaper: "Supprimer le fond d'écran" |   delete-wallpaper: "Supprimer le fond d'écran" | ||||||
|   dark-mode: "Mode nuit" |   dark-mode: "Mode nuit" | ||||||
| @@ -777,14 +778,14 @@ desktop/views/components/settings.vue: | |||||||
|   suggest-recent-hashtags: "Afficher les hashtags populaires dans le champs de saisie" |   suggest-recent-hashtags: "Afficher les hashtags populaires dans le champs de saisie" | ||||||
|   show-clock-on-header: "Afficher l'horloge à droite sur le coté supérieur" |   show-clock-on-header: "Afficher l'horloge à droite sur le coté supérieur" | ||||||
|   show-reply-target: "Afficher les réponses" |   show-reply-target: "Afficher les réponses" | ||||||
|   timeline: "タイムライン" |   timeline: "Chronologie" | ||||||
|   show-my-renotes: "Afficher mes republications dans le fil" |   show-my-renotes: "Afficher mes republications dans le fil" | ||||||
|   show-renoted-my-notes: "Afficher mes republications dans les fils" |   show-renoted-my-notes: "Afficher mes republications dans les fils" | ||||||
|   show-local-renotes: "ローカルの投稿のRenoteをタイムラインに表示する" |   show-local-renotes: "ローカルの投稿のRenoteをタイムラインに表示する" | ||||||
|   show-maps: "Afficher la carte" |   show-maps: "Afficher la carte" | ||||||
|   deck-column-align: "デッキのカラムの位置" |   deck-column-align: "デッキのカラムの位置" | ||||||
|   deck-column-align-center: "中央" |   deck-column-align-center: "Centrer" | ||||||
|   deck-column-align-left: "左" |   deck-column-align-left: "À gauche" | ||||||
|   sound: "Son" |   sound: "Son" | ||||||
|   enable-sounds: "Activer le son" |   enable-sounds: "Activer le son" | ||||||
|   enable-sounds-desc: "Jouer un son lorsque vous recevez un message. Ce paramètre est sauvegardé dans le navigateur." |   enable-sounds-desc: "Jouer un son lorsque vous recevez un message. Ce paramètre est sauvegardé dans le navigateur." | ||||||
| @@ -825,7 +826,7 @@ desktop/views/components/settings.vue: | |||||||
|   tools: "Outils" |   tools: "Outils" | ||||||
|   task-manager: "Gestionnaire de tâches" |   task-manager: "Gestionnaire de tâches" | ||||||
|   third-parties: "Services tiers" |   third-parties: "Services tiers" | ||||||
|   navbar-position: "ナビゲーションバーの位置" |   navbar-position: "Position de la barre de navigation" | ||||||
|   navbar-position-top: "En haut" |   navbar-position-top: "En haut" | ||||||
|   navbar-position-left: "à gauche" |   navbar-position-left: "à gauche" | ||||||
|   navbar-position-right: "à droite" |   navbar-position-right: "à droite" | ||||||
| @@ -847,21 +848,32 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "L'operation a été complétée avec succès!" |   success: "L'operation a été complétée avec succès!" | ||||||
|   failed: "L'operation a échoué. Veuillez vous assurer que le token a été entrer correctement." |   failed: "L'operation a échoué. Veuillez vous assurer que le token a été entrer correctement." | ||||||
|   info: "À partir de maintenant, à chaque fois que vous vous connecter entrez votre mot de passe ainsi que le token généré sur votre appareil." |   info: "À partir de maintenant, à chaque fois que vous vous connecter entrez votre mot de passe ainsi que le token généré sur votre appareil." | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" |   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" |   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||||
|   regeneration-of-token: "Si votre jeton est compromis, vous pouvez le régénérer." |   regeneration-of-token: "Si votre jeton est compromis, vous pouvez le régénérer." | ||||||
|   regenerate-token: "Regenerer le token" |   regenerate-token: "Régénérer le jeton" | ||||||
|   token: "Jeton :" |   token: "Jeton :" | ||||||
|   enter-password: "Veuillez entrer le mot de passe" |   enter-password: "Entrez le mot de passe" | ||||||
|  |   console: | ||||||
|  |     title: 'Console API' | ||||||
|  |     endpoint: 'Point de terminaison' | ||||||
|  |     parameter: 'Paramètres' | ||||||
|  |     send: 'Envoyer' | ||||||
|  |     sending: 'Envoi en cours' | ||||||
|  |     response: 'Résultat' | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "Aucune application autorisée" |   no-apps: "Aucune application autorisée" | ||||||
| common/views/components/drive-settings.vue: | common/views/components/drive-settings.vue: | ||||||
|   max: "容量" |   max: "容量" | ||||||
|   in-use: "使用中" |   in-use: "utilisé" | ||||||
|   stats: "統計" |   stats: "Statistiques" | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "Aucun utilisateurs mis en sourdine" |   mute-and-block: "ミュートとブロック" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   block: "ブロック" | ||||||
|  |   no-muted-users: "ミュートしているユーザーはいません" | ||||||
|  |   no-blocked-users: "ブロックしているユーザーはいません" | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "Changer votre mot de passe" |   reset: "Changer votre mot de passe" | ||||||
|   enter-current-password: "Entrez votre mot de passe actuel" |   enter-current-password: "Entrez votre mot de passe actuel" | ||||||
| @@ -938,8 +950,8 @@ desktop/views/pages/admin/admin.vue: | |||||||
|   dashboard: "Tableau de bord" |   dashboard: "Tableau de bord" | ||||||
|   users: "Utilisateur·rice·s" |   users: "Utilisateur·rice·s" | ||||||
|   update: "Mises à jour" |   update: "Mises à jour" | ||||||
|   announcements: "お知らせ" |   announcements: "Annonces" | ||||||
|   hashtags: "ハッシュタグ" |   hashtags: "Hashtags" | ||||||
| desktop/views/pages/admin/admin.dashboard.vue: | desktop/views/pages/admin/admin.dashboard.vue: | ||||||
|   dashboard: "Tableau de bord" |   dashboard: "Tableau de bord" | ||||||
|   all-users: "Toutes les utilisateurrices" |   all-users: "Toutes les utilisateurrices" | ||||||
| @@ -947,9 +959,9 @@ desktop/views/pages/admin/admin.dashboard.vue: | |||||||
|   all-notes: "Toutes les publications" |   all-notes: "Toutes les publications" | ||||||
|   original-notes: "Publications sur cette instance" |   original-notes: "Publications sur cette instance" | ||||||
|   invite: "Invitation" |   invite: "Invitation" | ||||||
|   banner-url: "Banner URL" |   banner-url: "URL de la bannière" | ||||||
|   disableRegistration: "Disable new user registration" |   disableRegistration: "Désactiver l’enregistrement de nouveaux utilisateurs·rices" | ||||||
|   disableLocalTimeline: "Disable the local timeline" |   disableLocalTimeline: "Désactiver le fil local" | ||||||
| desktop/views/pages/admin/admin.suspend-user.vue: | desktop/views/pages/admin/admin.suspend-user.vue: | ||||||
|   suspend-user: "Suspendre un·e utilisateur·rice" |   suspend-user: "Suspendre un·e utilisateur·rice" | ||||||
|   suspend: "Suspendre" |   suspend: "Suspendre" | ||||||
| @@ -967,22 +979,22 @@ desktop/views/pages/admin/admin.unverify-user.vue: | |||||||
|   unverify: "Ôter la vérification du compte" |   unverify: "Ôter la vérification du compte" | ||||||
|   unverified: "Ce compte n'est pas vérifié" |   unverified: "Ce compte n'est pas vérifié" | ||||||
| desktop/views/pages/admin/admin.announcements.vue: | desktop/views/pages/admin/admin.announcements.vue: | ||||||
|   announcements: "お知らせ" |   announcements: "Annonces" | ||||||
| desktop/views/pages/admin/admin.hashtags.vue: | desktop/views/pages/admin/admin.hashtags.vue: | ||||||
|   hided-tags: "Hidden Tags" |   hided-tags: "Tags cachés" | ||||||
| desktop/views/pages/deck/deck.tl-column.vue: | desktop/views/pages/deck/deck.tl-column.vue: | ||||||
|   is-media-only: "Les publications médias uniquement" |   is-media-only: "Les publications médias uniquement" | ||||||
|   is-media-view: "Vue média" |   is-media-view: "Vue média" | ||||||
|   edit: "Options" |   edit: "Options" | ||||||
| desktop/views/pages/deck/deck.user-column.vue: | desktop/views/pages/deck/deck.user-column.vue: | ||||||
|   posts: "投稿" |   posts: "Publications" | ||||||
|   following: "フォロー" |   following: "Suit" | ||||||
|   followers: "フォロワー" |   followers: "Abonné·e·s" | ||||||
|   images: "画像" |   images: "Images" | ||||||
|   activity: "アクティビティ" |   activity: "Activité" | ||||||
|   timeline: "タイムライン" |   timeline: "Chronologie" | ||||||
|   pinned-notes: "ピン留めされた投稿" |   pinned-notes: "Publications épinglées" | ||||||
|   push-to-a-list: "リストに追加" |   push-to-a-list: "Ajouter à la liste" | ||||||
| desktop/views/pages/stats/stats.vue: | desktop/views/pages/stats/stats.vue: | ||||||
|   all-users: "Toutes les utilisateurrices" |   all-users: "Toutes les utilisateurrices" | ||||||
|   original-users: "Utilisateur·rice·s sur cette instance" |   original-users: "Utilisateur·rice·s sur cette instance" | ||||||
| @@ -1035,7 +1047,7 @@ desktop/views/pages/user/user.friends.vue: | |||||||
|   no-users: "Pas d'utilisateurs" |   no-users: "Pas d'utilisateurs" | ||||||
| desktop/views/pages/user/user.vue: | desktop/views/pages/user/user.vue: | ||||||
|   is-suspended: "Ce compte a été suspendu." |   is-suspended: "Ce compte a été suspendu." | ||||||
|   last-used-at: "最終アクセス" |   last-used-at: "Actif·ive pour la dernière fois" | ||||||
| desktop/views/pages/user/user.photos.vue: | desktop/views/pages/user/user.photos.vue: | ||||||
|   title: "Photos" |   title: "Photos" | ||||||
|   loading: "Chargement en cours" |   loading: "Chargement en cours" | ||||||
| @@ -1048,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "Mettre en sourdine" |   mute: "Mettre en sourdine" | ||||||
|   muted: "Muting" |   muted: "Muting" | ||||||
|   unmute: "Enlever la sourdine" |   unmute: "Enlever la sourdine" | ||||||
|  |   block: "Bloquer" | ||||||
|  |   unblock: "Débloquer" | ||||||
|  |   block-confirm: "Bloquer cet utilisateur ?" | ||||||
|   push-to-a-list: "Ajouter à la liste" |   push-to-a-list: "Ajouter à la liste" | ||||||
|   list-pushed: "Vous avez ajouté {user} à la liste {list}." |   list-pushed: "Vous avez ajouté {user} à la liste {list}." | ||||||
| desktop/views/pages/user/user.header.vue: | desktop/views/pages/user/user.header.vue: | ||||||
| @@ -1055,10 +1070,10 @@ desktop/views/pages/user/user.header.vue: | |||||||
|   following: "Suit" |   following: "Suit" | ||||||
|   followers: "Abonné·e·s" |   followers: "Abonné·e·s" | ||||||
|   is-bot: "Ce compte est un Bot" |   is-bot: "Ce compte est un Bot" | ||||||
|   years-old: "歳" |   years-old: "ans d’âge" | ||||||
|   year: "年" |   year: "Année" | ||||||
|   month: "月" |   month: "Mois" | ||||||
|   day: "日" |   day: "Jour" | ||||||
| desktop/views/pages/user/user.timeline.vue: | desktop/views/pages/user/user.timeline.vue: | ||||||
|   default: "Publications" |   default: "Publications" | ||||||
|   with-replies: "Publications et réponses" |   with-replies: "Publications et réponses" | ||||||
| @@ -1117,8 +1132,8 @@ mobile/views/components/drive.file-detail.vue: | |||||||
|   hash: "Hash (md5)" |   hash: "Hash (md5)" | ||||||
|   exif: "EXIF" |   exif: "EXIF" | ||||||
|   nsfw: "CW" |   nsfw: "CW" | ||||||
|   mark-as-sensitive: "閲覧注意に設定" |   mark-as-sensitive: "Marquer comme sensible" | ||||||
|   unmark-as-sensitive: "閲覧注意を解除" |   unmark-as-sensitive: "Ne pas marquer comme sensible" | ||||||
| mobile/views/components/media-image.vue: | mobile/views/components/media-image.vue: | ||||||
|   sensitive: "Le contenu est NSFW" |   sensitive: "Le contenu est NSFW" | ||||||
|   click-to-show: "Cliquer pour afficher" |   click-to-show: "Cliquer pour afficher" | ||||||
| @@ -1278,8 +1293,8 @@ mobile/views/pages/settings.vue: | |||||||
|   timeline: "Fil d'actualité" |   timeline: "Fil d'actualité" | ||||||
|   show-reply-target: "Afficher les réponses" |   show-reply-target: "Afficher les réponses" | ||||||
|   show-my-renotes: "Afficher mes republications" |   show-my-renotes: "Afficher mes republications" | ||||||
|   show-renoted-my-notes: "自分の投稿のRenoteを表示する" |   show-renoted-my-notes: "Afficher mes publications partagées" | ||||||
|   show-local-renotes: "ローカルの投稿のRenoteを表示する" |   show-local-renotes: "Afficher les publications partagées localement" | ||||||
|   post-style: "Style de la publication" |   post-style: "Style de la publication" | ||||||
|   post-style-standard: "Standard" |   post-style-standard: "Standard" | ||||||
|   post-style-smart: "Intelligent" |   post-style-smart: "Intelligent" | ||||||
| @@ -1312,7 +1327,7 @@ mobile/views/pages/settings.vue: | |||||||
|   signout: "Déconnexion" |   signout: "Déconnexion" | ||||||
|   sound: "Sons" |   sound: "Sons" | ||||||
|   enable-sounds: "Activer les sons" |   enable-sounds: "Activer les sons" | ||||||
|   mark-as-read-all-unread-notes: "すべての投稿を既読にする" |   mark-as-read-all-unread-notes: "Marquer toutes les publications comme lues" | ||||||
| mobile/views/pages/user.vue: | mobile/views/pages/user.vue: | ||||||
|   follows-you: "Vous suit" |   follows-you: "Vous suit" | ||||||
|   following: "Abonnements" |   following: "Abonnements" | ||||||
| @@ -1322,6 +1337,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "Fil d'actualité" |   timeline: "Fil d'actualité" | ||||||
|   media: "Media" |   media: "Media" | ||||||
|   is-suspended: "This account has been suspended." |   is-suspended: "This account has been suspended." | ||||||
|  |   mute: "Mettre en sourdine" | ||||||
|  |   unmute: "Enlever la sourdine" | ||||||
|  |   block: "Bloquer" | ||||||
|  |   unblock: "Débloquer" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "Notes récentes" |   recent-notes: "Notes récentes" | ||||||
|   images: "Images" |   images: "Images" | ||||||
| @@ -1368,28 +1387,28 @@ docs: | |||||||
| dev/views/index.vue: | dev/views/index.vue: | ||||||
|   manage-apps: "Gestion des applications" |   manage-apps: "Gestion des applications" | ||||||
| dev/views/apps.vue: | dev/views/apps.vue: | ||||||
|   manage-apps: "アプリを管理" |   manage-apps: "Gestion des applications" | ||||||
|   create-app: "アプリ作成" |   create-app: "Créer une app" | ||||||
|   app-missing: "アプリなし" |   app-missing: "Aucune application" | ||||||
| dev/views/new-app.vue: | dev/views/new-app.vue: | ||||||
|   create-app: "アプリケーションの作成" |   create-app: "Création d’une application" | ||||||
|   app-name: "アプリケーション名" |   app-name: "Nom de l’application" | ||||||
|   app-name-desc: "あなたのアプリの名称。" |   app-name-desc: "Le nom de votre application" | ||||||
|   app-name-ex: "ex) Misskey for iOS" |   app-name-ex: "p. ex. Misskey pour iOS" | ||||||
|   app-overview: "アプリの概要" |   app-overview: "Description courte de l’application" | ||||||
|   app-desc: "あなたのアプリの簡単な説明や紹介。" |   app-desc: "Brève description introductive à votre application." | ||||||
|   app-desc-ex: "ex) Misskey iOSクライアント。" |   app-desc-ex: "p. ex) Misskey pour iOS" | ||||||
|   callback-url: "コールバックURL (オプション)" |   callback-url: "コールバックURL (オプション)" | ||||||
|   callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。" |   callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。" | ||||||
|   authority: "権限" |   authority: "Autorisations " | ||||||
|   authority-desc: "ここで要求した機能だけがAPIからアクセスできます。" |   authority-desc: "ここで要求した機能だけがAPIからアクセスできます。" | ||||||
|   authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。" |   authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。" | ||||||
|   account-read: "アカウントの情報を見る。" |   account-read: "Afficher les informations du compte" | ||||||
|   account-write: "アカウントの情報を操作する。" |   account-write: "Modifications des informations du compte" | ||||||
|   note-write: "投稿する。" |   note-write: "Publications." | ||||||
|   reaction-write: "リアクションしたりリアクションをキャンセルする。" |   reaction-write: "Ajout et suppression de réactions." | ||||||
|   following-write: "フォローしたりフォロー解除する。" |   following-write: "S’abonner et se désabonner." | ||||||
|   drive-read: "ドライブを見る。" |   drive-read: "Lecture du Drive." | ||||||
|   drive-write: "ドライブを操作する。" |   drive-write: "Téléversement/suppression des fichiers de votre Lecteur." | ||||||
|   notification-read: "通知を見る。" |   notification-read: "Lire vos notifications." | ||||||
|   notification-write: "通知を操作する。" |   notification-write: "Gestion de vos notifications." | ||||||
|   | |||||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "プロフィール" |   profile: "プロフィール" | ||||||
|   notification: "通知" |   notification: "通知" | ||||||
|   apps: "アプリ" |   apps: "アプリ" | ||||||
|   mute: "ミュート" |   mute-and-block: "ミュート/ブロック" | ||||||
|  |   blocking: "ブロック" | ||||||
|   security: "セキュリティ" |   security: "セキュリティ" | ||||||
|   signin: "サインイン履歴" |   signin: "サインイン履歴" | ||||||
|   password: "パスワード" |   password: "パスワード" | ||||||
| @@ -847,21 +848,32 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "設定が完了しました!" |   success: "設定が完了しました!" | ||||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" |   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" |   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" |   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" |   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" |   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||||
|   regenerate-token: "トークンを再生成" |   regenerate-token: "トークンを再生成" | ||||||
|   token: "Token:" |   token: "Token:" | ||||||
|   enter-password: "パスワードを入力してください" |   enter-password: "パスワードを入力してください" | ||||||
|  |   console: | ||||||
|  |     title: 'APIコンソール' | ||||||
|  |     endpoint: 'エンドポイント' | ||||||
|  |     parameter: 'パラメータ' | ||||||
|  |     send: '送信' | ||||||
|  |     sending: '応答待ち' | ||||||
|  |     response: '結果' | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "連携しているアプリケーションはありません" |   no-apps: "連携しているアプリケーションはありません" | ||||||
| common/views/components/drive-settings.vue: | common/views/components/drive-settings.vue: | ||||||
|   max: "容量" |   max: "容量" | ||||||
|   in-use: "使用中" |   in-use: "使用中" | ||||||
|   stats: "統計" |   stats: "統計" | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "ミュートしているユーザーはいません" |   mute-and-block: "ミュートとブロック" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   block: "ブロック" | ||||||
|  |   no-muted-users: "ミュートしているユーザーはいません" | ||||||
|  |   no-blocked-users: "ブロックしているユーザーはいません" | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "パスワードを変更する" |   reset: "パスワードを変更する" | ||||||
|   enter-current-password: "現在のパスワードを入力してください" |   enter-current-password: "現在のパスワードを入力してください" | ||||||
| @@ -1048,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "ミュートする" |   mute: "ミュートする" | ||||||
|   muted: "ミュートしています" |   muted: "ミュートしています" | ||||||
|   unmute: "ミュート解除" |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロックする" | ||||||
|  |   unblock: "ブロック解除" | ||||||
|  |   block-confirm: "このユーザーをブロックしますか?" | ||||||
|   push-to-a-list: "リストに追加" |   push-to-a-list: "リストに追加" | ||||||
|   list-pushed: "{user}を{list}に追加しました。" |   list-pushed: "{user}を{list}に追加しました。" | ||||||
| desktop/views/pages/user/user.header.vue: | desktop/views/pages/user/user.header.vue: | ||||||
| @@ -1322,6 +1337,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "タイムライン" |   timeline: "タイムライン" | ||||||
|   media: "メディア" |   media: "メディア" | ||||||
|   is-suspended: "このユーザーは凍結されています。" |   is-suspended: "このユーザーは凍結されています。" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロック" | ||||||
|  |   unblock: "ブロック解除" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "最近の投稿" |   recent-notes: "最近の投稿" | ||||||
|   images: "画像" |   images: "画像" | ||||||
|   | |||||||
| @@ -832,7 +832,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "プロフィール" |   profile: "プロフィール" | ||||||
|   notification: "通知" |   notification: "通知" | ||||||
|   apps: "アプリ" |   apps: "アプリ" | ||||||
|   mute: "ミュート" |   mute-and-block: "ミュート/ブロック" | ||||||
|  |   blocking: "ブロック" | ||||||
|   security: "セキュリティ" |   security: "セキュリティ" | ||||||
|   signin: "サインイン履歴" |   signin: "サインイン履歴" | ||||||
|   password: "パスワード" |   password: "パスワード" | ||||||
| @@ -950,13 +951,20 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" |   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" |   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||||
|  |  | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" |   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" |   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" |   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||||
|   regenerate-token: "トークンを再生成" |   regenerate-token: "トークンを再生成" | ||||||
|   token: "Token:" |   token: "Token:" | ||||||
|   enter-password: "パスワードを入力してください" |   enter-password: "パスワードを入力してください" | ||||||
|  |   console: | ||||||
|  |     title: 'APIコンソール' | ||||||
|  |     endpoint: 'エンドポイント' | ||||||
|  |     parameter: 'パラメータ' | ||||||
|  |     send: '送信' | ||||||
|  |     sending: '応答待ち' | ||||||
|  |     response: '結果' | ||||||
|  |  | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "連携しているアプリケーションはありません" |   no-apps: "連携しているアプリケーションはありません" | ||||||
| @@ -966,8 +974,12 @@ common/views/components/drive-settings.vue: | |||||||
|   in-use: "使用中" |   in-use: "使用中" | ||||||
|   stats: "統計" |   stats: "統計" | ||||||
|  |  | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "ミュートしているユーザーはいません" |   mute-and-block: "ミュートとブロック" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   block: "ブロック" | ||||||
|  |   no-muted-users: "ミュートしているユーザーはいません" | ||||||
|  |   no-blocked-users: "ブロックしているユーザーはいません" | ||||||
|  |  | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "パスワードを変更する" |   reset: "パスワードを変更する" | ||||||
| @@ -1196,6 +1208,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "ミュートする" |   mute: "ミュートする" | ||||||
|   muted: "ミュートしています" |   muted: "ミュートしています" | ||||||
|   unmute: "ミュート解除" |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロックする" | ||||||
|  |   unblock: "ブロック解除" | ||||||
|  |   block-confirm: "このユーザーをブロックしますか?" | ||||||
|   push-to-a-list: "リストに追加" |   push-to-a-list: "リストに追加" | ||||||
|   list-pushed: "{user}を{list}に追加しました。" |   list-pushed: "{user}を{list}に追加しました。" | ||||||
|  |  | ||||||
| @@ -1524,6 +1539,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "タイムライン" |   timeline: "タイムライン" | ||||||
|   media: "メディア" |   media: "メディア" | ||||||
|   is-suspended: "このユーザーは凍結されています。" |   is-suspended: "このユーザーは凍結されています。" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロック" | ||||||
|  |   unblock: "ブロック解除" | ||||||
|  |  | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "最近の投稿" |   recent-notes: "最近の投稿" | ||||||
|   | |||||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "プロフィール" |   profile: "プロフィール" | ||||||
|   notification: "通知" |   notification: "通知" | ||||||
|   apps: "アプリ" |   apps: "アプリ" | ||||||
|   mute: "ミュート" |   mute-and-block: "ミュート/ブロック" | ||||||
|  |   blocking: "ブロック" | ||||||
|   security: "守護神セキュリティ" |   security: "守護神セキュリティ" | ||||||
|   signin: "こんな感じでサインインしたらしいで" |   signin: "こんな感じでサインインしたらしいで" | ||||||
|   password: "パスワード" |   password: "パスワード" | ||||||
| @@ -847,21 +848,32 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "設定が完了したで!" |   success: "設定が完了したで!" | ||||||
|   failed: "なんか設定に失敗したで。トークンを間違えとらんか確認してや。" |   failed: "なんか設定に失敗したで。トークンを間違えとらんか確認してや。" | ||||||
|   info: "次のサインインからは、パスワードに加えてデバイスに出とるトークンを入力してな。" |   info: "次のサインインからは、パスワードに加えてデバイスに出とるトークンを入力してな。" | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "APIを利用するには、上記のトークンを「i」っちゅうキーでパラメータに付加してリクエストしてや。" |   intro: "API使うんやったらこのトークンを「i」っちゅうパラメータにくっつけてリクエストできるで。" | ||||||
|   caution: "アカウントを不正利用されるかも知れんから、このトークンは第三者に教えたらあかんで(アプリなどにも入力しんといてな)。" |   caution: "アカウント勝手にいじられるかも知れんから、このトークンは教えたらあかんし、アプリにも書いたらあかんで(これはフリちゃうで)" | ||||||
|   regeneration-of-token: "万が一このトークンが漏れたとかその可能性があったらトークンを再生成できるで。" |   regeneration-of-token: "トークン漏れてもうたんやったらもっかい生成できるで。" | ||||||
|   regenerate-token: "トークンを再生成" |   regenerate-token: "トークンもっかい生成" | ||||||
|   token: "トークン:" |   token: "Token:" | ||||||
|   enter-password: "パスワードを入力してや" |   enter-password: "パスワードを入れてや" | ||||||
|  |   console: | ||||||
|  |     title: 'APIコンソール' | ||||||
|  |     endpoint: 'エンドポイント' | ||||||
|  |     parameter: 'パラメータ' | ||||||
|  |     send: '送る' | ||||||
|  |     sending: '応答待っとる' | ||||||
|  |     response: 'こんなん返ってきたわ' | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "連携しているアプリケーションはあらへんで" |   no-apps: "連携しているアプリケーションはあらへんで" | ||||||
| common/views/components/drive-settings.vue: | common/views/components/drive-settings.vue: | ||||||
|   max: "容量" |   max: "容量" | ||||||
|   in-use: "使用中" |   in-use: "使うとる" | ||||||
|   stats: "統計" |   stats: "統計" | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "ミュートしているユーザーはおらんで" |   mute-and-block: "ミュートとブロック" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   block: "ブロック" | ||||||
|  |   no-muted-users: "ミュートしているユーザーはいません" | ||||||
|  |   no-blocked-users: "ブロックしているユーザーはいません" | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "パスワードを変更する" |   reset: "パスワードを変更する" | ||||||
|   enter-current-password: "今のパスワードを入れてや" |   enter-current-password: "今のパスワードを入れてや" | ||||||
| @@ -938,7 +950,7 @@ desktop/views/pages/admin/admin.vue: | |||||||
|   dashboard: "ダッシュボード" |   dashboard: "ダッシュボード" | ||||||
|   users: "ユーザー" |   users: "ユーザー" | ||||||
|   update: "更新" |   update: "更新" | ||||||
|   announcements: "お知らせ" |   announcements: "知っといてや" | ||||||
|   hashtags: "ハッシュタグ" |   hashtags: "ハッシュタグ" | ||||||
| desktop/views/pages/admin/admin.dashboard.vue: | desktop/views/pages/admin/admin.dashboard.vue: | ||||||
|   dashboard: "ダッシュボード" |   dashboard: "ダッシュボード" | ||||||
| @@ -967,7 +979,7 @@ desktop/views/pages/admin/admin.unverify-user.vue: | |||||||
|   unverify: "公式アカウントにはさせへんで" |   unverify: "公式アカウントにはさせへんで" | ||||||
|   unverified: "公式アカウントを解除したで" |   unverified: "公式アカウントを解除したで" | ||||||
| desktop/views/pages/admin/admin.announcements.vue: | desktop/views/pages/admin/admin.announcements.vue: | ||||||
|   announcements: "お知らせ" |   announcements: "知っといてや" | ||||||
| desktop/views/pages/admin/admin.hashtags.vue: | desktop/views/pages/admin/admin.hashtags.vue: | ||||||
|   hided-tags: "Hidden Tags" |   hided-tags: "Hidden Tags" | ||||||
| desktop/views/pages/deck/deck.tl-column.vue: | desktop/views/pages/deck/deck.tl-column.vue: | ||||||
| @@ -979,10 +991,10 @@ desktop/views/pages/deck/deck.user-column.vue: | |||||||
|   following: "フォロー" |   following: "フォロー" | ||||||
|   followers: "フォロワー" |   followers: "フォロワー" | ||||||
|   images: "画像" |   images: "画像" | ||||||
|   activity: "アクティビティ" |   activity: "やっとること" | ||||||
|   timeline: "タイムライン" |   timeline: "タイムライン" | ||||||
|   pinned-notes: "ピン留めされた投稿" |   pinned-notes: "ピン留めしはった投稿" | ||||||
|   push-to-a-list: "リストに追加" |   push-to-a-list: "リストに入れたる" | ||||||
| desktop/views/pages/stats/stats.vue: | desktop/views/pages/stats/stats.vue: | ||||||
|   all-users: "全てのユーザー" |   all-users: "全てのユーザー" | ||||||
|   original-users: "ここの人らだけ" |   original-users: "ここの人らだけ" | ||||||
| @@ -1035,7 +1047,7 @@ desktop/views/pages/user/user.friends.vue: | |||||||
|   no-users: "よう話すツレは居らん" |   no-users: "よう話すツレは居らん" | ||||||
| desktop/views/pages/user/user.vue: | desktop/views/pages/user/user.vue: | ||||||
|   is-suspended: "このユーザーはあかんわ。凍結されとる。" |   is-suspended: "このユーザーはあかんわ。凍結されとる。" | ||||||
|   last-used-at: "最終アクセス" |   last-used-at: "最後いつ来はった?" | ||||||
| desktop/views/pages/user/user.photos.vue: | desktop/views/pages/user/user.photos.vue: | ||||||
|   title: "写真" |   title: "写真" | ||||||
|   loading: "読み込んどります" |   loading: "読み込んどります" | ||||||
| @@ -1048,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "ミュートする" |   mute: "ミュートする" | ||||||
|   muted: "ミュートしとるで" |   muted: "ミュートしとるで" | ||||||
|   unmute: "ミュート解除" |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロックする" | ||||||
|  |   unblock: "ブロック解除" | ||||||
|  |   block-confirm: "このユーザーをブロックしますか?" | ||||||
|   push-to-a-list: "リストに追加" |   push-to-a-list: "リストに追加" | ||||||
|   list-pushed: "{user}を{list}に追加したで。" |   list-pushed: "{user}を{list}に追加したで。" | ||||||
| desktop/views/pages/user/user.header.vue: | desktop/views/pages/user/user.header.vue: | ||||||
| @@ -1117,8 +1132,8 @@ mobile/views/components/drive.file-detail.vue: | |||||||
|   hash: "ハッシュ(md5)" |   hash: "ハッシュ(md5)" | ||||||
|   exif: "EXIF" |   exif: "EXIF" | ||||||
|   nsfw: "ちょっと見せられへんわ" |   nsfw: "ちょっと見せられへんわ" | ||||||
|   mark-as-sensitive: "閲覧注意に設定" |   mark-as-sensitive: "見たらあかん感じにしとく" | ||||||
|   unmark-as-sensitive: "閲覧注意を解除" |   unmark-as-sensitive: "やっぱ見せたるわ" | ||||||
| mobile/views/components/media-image.vue: | mobile/views/components/media-image.vue: | ||||||
|   sensitive: "見たらあかんで" |   sensitive: "見たらあかんで" | ||||||
|   click-to-show: "押してみ、見せたるわ" |   click-to-show: "押してみ、見せたるわ" | ||||||
| @@ -1312,7 +1327,7 @@ mobile/views/pages/settings.vue: | |||||||
|   signout: "さいなら" |   signout: "さいなら" | ||||||
|   sound: "サウンド" |   sound: "サウンド" | ||||||
|   enable-sounds: "サウンド鳴らす" |   enable-sounds: "サウンド鳴らす" | ||||||
|   mark-as-read-all-unread-notes: "すべての投稿を既読にする" |   mark-as-read-all-unread-notes: "全部もう読んだわ" | ||||||
| mobile/views/pages/user.vue: | mobile/views/pages/user.vue: | ||||||
|   follows-you: "フォローされとるで" |   follows-you: "フォローされとるで" | ||||||
|   following: "フォロー" |   following: "フォロー" | ||||||
| @@ -1322,6 +1337,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "タイムライン" |   timeline: "タイムライン" | ||||||
|   media: "メディア" |   media: "メディア" | ||||||
|   is-suspended: "このユーザーはあかんわ。凍結されとる。" |   is-suspended: "このユーザーはあかんわ。凍結されとる。" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロック" | ||||||
|  |   unblock: "ブロック解除" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "最近儲かりまっか?" |   recent-notes: "最近儲かりまっか?" | ||||||
|   images: "画像" |   images: "画像" | ||||||
| @@ -1369,27 +1388,27 @@ dev/views/index.vue: | |||||||
|   manage-apps: "アプリの管理" |   manage-apps: "アプリの管理" | ||||||
| dev/views/apps.vue: | dev/views/apps.vue: | ||||||
|   manage-apps: "アプリを管理" |   manage-apps: "アプリを管理" | ||||||
|   create-app: "アプリ作成" |   create-app: "アプリ作る" | ||||||
|   app-missing: "アプリなし" |   app-missing: "アプリあらへん" | ||||||
| dev/views/new-app.vue: | dev/views/new-app.vue: | ||||||
|   create-app: "アプリケーションの作成" |   create-app: "アプリケーション作る" | ||||||
|   app-name: "アプリケーション名" |   app-name: "アプリケーションの名前" | ||||||
|   app-name-desc: "あなたのアプリの名称。" |   app-name-desc: "あんたのアプリの名前。" | ||||||
|   app-name-ex: "ex) Misskey for iOS" |   app-name-ex: "ex) 関西ミスキー保安協会" | ||||||
|   app-overview: "アプリの概要" |   app-overview: "このアプリどんなん?" | ||||||
|   app-desc: "あなたのアプリの簡単な説明や紹介。" |   app-desc: "あんたのアプリどんなんか教えて" | ||||||
|   app-desc-ex: "ex) Misskey iOSクライアント。" |   app-desc-ex: "ex) 関西人なら誰でも口ずさめるこのCMがついにMisskeyへ。" | ||||||
|   callback-url: "コールバックURL (オプション)" |   callback-url: "コールバックURL (無くてもええで)" | ||||||
|   callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。" |   callback-url-desc: "ユーザーが認証フォームで認証した後どこに連れてくかを設定できるで" | ||||||
|   authority: "権限" |   authority: "権限" | ||||||
|   authority-desc: "ここで要求した機能だけがAPIからアクセスできます。" |   authority-desc: "ここにチェックした機能しかAPIからアクセスできひんから気ぃつけてな" | ||||||
|   authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。" |   authority-warning: "アプリ作った後でも変えれるけど、新しいやつ追加したらそん時関連付いてるユーザーキーは全部ほかされるで。" | ||||||
|   account-read: "アカウントの情報を見る。" |   account-read: "アカウントの情報見せて" | ||||||
|   account-write: "アカウントの情報を操作する。" |   account-write: "アカウントの情報いじらせて" | ||||||
|   note-write: "投稿する。" |   note-write: "投稿させて" | ||||||
|   reaction-write: "リアクションしたりリアクションをキャンセルする。" |   reaction-write: "リアクションしたりそれをキャンセルさせて" | ||||||
|   following-write: "フォローしたりフォロー解除する。" |   following-write: "フォローとかフォロー解除させて" | ||||||
|   drive-read: "ドライブを見る。" |   drive-read: "ドライブ見せて" | ||||||
|   drive-write: "ドライブを操作する。" |   drive-write: "ドライブいじらせて" | ||||||
|   notification-read: "通知を見る。" |   notification-read: "通知見せて" | ||||||
|   notification-write: "通知を操作する。" |   notification-write: "通知いじらせて" | ||||||
|   | |||||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "プロフィール" |   profile: "プロフィール" | ||||||
|   notification: "通知" |   notification: "通知" | ||||||
|   apps: "アプリ" |   apps: "アプリ" | ||||||
|   mute: "ミュート" |   mute-and-block: "ミュート/ブロック" | ||||||
|  |   blocking: "ブロック" | ||||||
|   security: "セキュリティ" |   security: "セキュリティ" | ||||||
|   signin: "サインイン履歴" |   signin: "サインイン履歴" | ||||||
|   password: "パスワード" |   password: "パスワード" | ||||||
| @@ -847,21 +848,32 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "設定が完了しました!" |   success: "設定が完了しました!" | ||||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" |   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" |   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" |   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" |   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" |   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||||
|   regenerate-token: "トークンを再生成" |   regenerate-token: "トークンを再生成" | ||||||
|   token: "Token:" |   token: "Token:" | ||||||
|   enter-password: "パスワードを入力してください" |   enter-password: "パスワードを入力してください" | ||||||
|  |   console: | ||||||
|  |     title: 'APIコンソール' | ||||||
|  |     endpoint: 'エンドポイント' | ||||||
|  |     parameter: 'パラメータ' | ||||||
|  |     send: '送信' | ||||||
|  |     sending: '応答待ち' | ||||||
|  |     response: '結果' | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "連携しているアプリケーションはありません" |   no-apps: "連携しているアプリケーションはありません" | ||||||
| common/views/components/drive-settings.vue: | common/views/components/drive-settings.vue: | ||||||
|   max: "容量" |   max: "容量" | ||||||
|   in-use: "使用中" |   in-use: "使用中" | ||||||
|   stats: "統計" |   stats: "統計" | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "ミュートしているユーザーはいません" |   mute-and-block: "ミュートとブロック" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   block: "ブロック" | ||||||
|  |   no-muted-users: "ミュートしているユーザーはいません" | ||||||
|  |   no-blocked-users: "ブロックしているユーザーはいません" | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "パスワードを変更する" |   reset: "パスワードを変更する" | ||||||
|   enter-current-password: "現在のパスワードを入力してください" |   enter-current-password: "現在のパスワードを入力してください" | ||||||
| @@ -1048,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "ミュートする" |   mute: "ミュートする" | ||||||
|   muted: "ミュートしています" |   muted: "ミュートしています" | ||||||
|   unmute: "ミュート解除" |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロックする" | ||||||
|  |   unblock: "ブロック解除" | ||||||
|  |   block-confirm: "このユーザーをブロックしますか?" | ||||||
|   push-to-a-list: "リストに追加" |   push-to-a-list: "リストに追加" | ||||||
|   list-pushed: "{user}を{list}に追加しました。" |   list-pushed: "{user}を{list}に追加しました。" | ||||||
| desktop/views/pages/user/user.header.vue: | desktop/views/pages/user/user.header.vue: | ||||||
| @@ -1322,6 +1337,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "タイムライン" |   timeline: "タイムライン" | ||||||
|   media: "メディア" |   media: "メディア" | ||||||
|   is-suspended: "このユーザーは凍結されています。" |   is-suspended: "このユーザーは凍結されています。" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロック" | ||||||
|  |   unblock: "ブロック解除" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "最近の投稿" |   recent-notes: "最近の投稿" | ||||||
|   images: "画像" |   images: "画像" | ||||||
|   | |||||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "Profiel" |   profile: "Profiel" | ||||||
|   notification: "Melding" |   notification: "Melding" | ||||||
|   apps: "Apps" |   apps: "Apps" | ||||||
|   mute: "Dempen" |   mute-and-block: "ミュート/ブロック" | ||||||
|  |   blocking: "ブロック" | ||||||
|   security: "Beveiliging" |   security: "Beveiliging" | ||||||
|   signin: "Inloggeschiedenis" |   signin: "Inloggeschiedenis" | ||||||
|   password: "Wachtwoord" |   password: "Wachtwoord" | ||||||
| @@ -847,21 +848,32 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "Instellen voltooid!" |   success: "Instellen voltooid!" | ||||||
|   failed: "Instellen mislukt. Zorg ervoor dat de sleutel juist is." |   failed: "Instellen mislukt. Zorg ervoor dat de sleutel juist is." | ||||||
|   info: "Vanaf nu moet je ook de op je apparaat getoonde sleutel tonen bij het inloggen op Misskey." |   info: "Vanaf nu moet je ook de op je apparaat getoonde sleutel tonen bij het inloggen op Misskey." | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "Als je toegang wilt tot de API, stel deze sleutel dan in als 'i' bij de verzoekparameters." |   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||||
|   caution: "Laat deze sleutel niet zien aan derde partijen (en voer hem nergens anders in dan hier), anders kan je account gehackt worden." |   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||||
|   regeneration-of-token: "Mocht deze sleutel tóch uitlekken, dan kun je hem opnieuw genereren." |   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||||
|   regenerate-token: "Sleutel opnieuw genereren" |   regenerate-token: "トークンを再生成" | ||||||
|   token: "Sleutel:" |   token: "Token:" | ||||||
|   enter-password: "Voer je wachtwoord in" |   enter-password: "パスワードを入力してください" | ||||||
|  |   console: | ||||||
|  |     title: 'APIコンソール' | ||||||
|  |     endpoint: 'エンドポイント' | ||||||
|  |     parameter: 'パラメータ' | ||||||
|  |     send: '送信' | ||||||
|  |     sending: '応答待ち' | ||||||
|  |     response: '結果' | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "連携しているアプリケーションはありません" |   no-apps: "連携しているアプリケーションはありません" | ||||||
| common/views/components/drive-settings.vue: | common/views/components/drive-settings.vue: | ||||||
|   max: "容量" |   max: "容量" | ||||||
|   in-use: "使用中" |   in-use: "使用中" | ||||||
|   stats: "統計" |   stats: "統計" | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "Geen gedempte gebruikers" |   mute-and-block: "ミュートとブロック" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   block: "ブロック" | ||||||
|  |   no-muted-users: "ミュートしているユーザーはいません" | ||||||
|  |   no-blocked-users: "ブロックしているユーザーはいません" | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "Wachtwoord wijzigen" |   reset: "Wachtwoord wijzigen" | ||||||
|   enter-current-password: "Voer je huidige wachtwoord in" |   enter-current-password: "Voer je huidige wachtwoord in" | ||||||
| @@ -1048,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "Dempen" |   mute: "Dempen" | ||||||
|   muted: "Dempend" |   muted: "Dempend" | ||||||
|   unmute: "Ontdempen" |   unmute: "Ontdempen" | ||||||
|  |   block: "ブロックする" | ||||||
|  |   unblock: "ブロック解除" | ||||||
|  |   block-confirm: "このユーザーをブロックしますか?" | ||||||
|   push-to-a-list: "リストに追加" |   push-to-a-list: "リストに追加" | ||||||
|   list-pushed: "{user}を{list}に追加しました。" |   list-pushed: "{user}を{list}に追加しました。" | ||||||
| desktop/views/pages/user/user.header.vue: | desktop/views/pages/user/user.header.vue: | ||||||
| @@ -1322,6 +1337,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "Tijdlijn" |   timeline: "Tijdlijn" | ||||||
|   media: "Media" |   media: "Media" | ||||||
|   is-suspended: "Dit account is geschorst." |   is-suspended: "Dit account is geschorst." | ||||||
|  |   mute: "ミュート" | ||||||
|  |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロック" | ||||||
|  |   unblock: "ブロック解除" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "Recente notities" |   recent-notes: "Recente notities" | ||||||
|   images: "Afbeeldingen" |   images: "Afbeeldingen" | ||||||
|   | |||||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "プロフィール" |   profile: "プロフィール" | ||||||
|   notification: "Notifikasjon" |   notification: "Notifikasjon" | ||||||
|   apps: "Apper" |   apps: "Apper" | ||||||
|   mute: "Demp" |   mute-and-block: "ミュート/ブロック" | ||||||
|  |   blocking: "ブロック" | ||||||
|   security: "セキュリティ" |   security: "セキュリティ" | ||||||
|   signin: "サインイン履歴" |   signin: "サインイン履歴" | ||||||
|   password: "Passord" |   password: "Passord" | ||||||
| @@ -847,21 +848,32 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "設定が完了しました!" |   success: "設定が完了しました!" | ||||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" |   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" |   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" |   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" |   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" |   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||||
|   regenerate-token: "トークンを再生成" |   regenerate-token: "トークンを再生成" | ||||||
|   token: "Token:" |   token: "Token:" | ||||||
|   enter-password: "パスワードを入力してください" |   enter-password: "パスワードを入力してください" | ||||||
|  |   console: | ||||||
|  |     title: 'APIコンソール' | ||||||
|  |     endpoint: 'エンドポイント' | ||||||
|  |     parameter: 'パラメータ' | ||||||
|  |     send: '送信' | ||||||
|  |     sending: '応答待ち' | ||||||
|  |     response: '結果' | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "連携しているアプリケーションはありません" |   no-apps: "連携しているアプリケーションはありません" | ||||||
| common/views/components/drive-settings.vue: | common/views/components/drive-settings.vue: | ||||||
|   max: "容量" |   max: "容量" | ||||||
|   in-use: "使用中" |   in-use: "使用中" | ||||||
|   stats: "統計" |   stats: "統計" | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "ミュートしているユーザーはいません" |   mute-and-block: "ミュートとブロック" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   block: "ブロック" | ||||||
|  |   no-muted-users: "ミュートしているユーザーはいません" | ||||||
|  |   no-blocked-users: "ブロックしているユーザーはいません" | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "パスワードを変更する" |   reset: "パスワードを変更する" | ||||||
|   enter-current-password: "現在のパスワードを入力してください" |   enter-current-password: "現在のパスワードを入力してください" | ||||||
| @@ -1048,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "ミュートする" |   mute: "ミュートする" | ||||||
|   muted: "ミュートしています" |   muted: "ミュートしています" | ||||||
|   unmute: "ミュート解除" |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロックする" | ||||||
|  |   unblock: "ブロック解除" | ||||||
|  |   block-confirm: "このユーザーをブロックしますか?" | ||||||
|   push-to-a-list: "リストに追加" |   push-to-a-list: "リストに追加" | ||||||
|   list-pushed: "{user}を{list}に追加しました。" |   list-pushed: "{user}を{list}に追加しました。" | ||||||
| desktop/views/pages/user/user.header.vue: | desktop/views/pages/user/user.header.vue: | ||||||
| @@ -1322,6 +1337,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "タイムライン" |   timeline: "タイムライン" | ||||||
|   media: "Media" |   media: "Media" | ||||||
|   is-suspended: "このユーザーは凍結されています。" |   is-suspended: "このユーザーは凍結されています。" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロック" | ||||||
|  |   unblock: "ブロック解除" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "Nylige innlegg" |   recent-notes: "Nylige innlegg" | ||||||
|   images: "Bilder" |   images: "Bilder" | ||||||
|   | |||||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "Profil" |   profile: "Profil" | ||||||
|   notification: "Powiadomienia" |   notification: "Powiadomienia" | ||||||
|   apps: "Aplikacje" |   apps: "Aplikacje" | ||||||
|   mute: "Wyciszanie" |   mute-and-block: "ミュート/ブロック" | ||||||
|  |   blocking: "ブロック" | ||||||
|   security: "Bezpieczeństwo" |   security: "Bezpieczeństwo" | ||||||
|   signin: "Historia logowań" |   signin: "Historia logowań" | ||||||
|   password: "Hasło" |   password: "Hasło" | ||||||
| @@ -847,21 +848,32 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "Pomyślnie ukończono konfigurację!" |   success: "Pomyślnie ukończono konfigurację!" | ||||||
|   failed: "Nie udało się skonfigurować uwierzytelniania dwuetapowego, upewnij się że wprowadziłeś prawidłowy token." |   failed: "Nie udało się skonfigurować uwierzytelniania dwuetapowego, upewnij się że wprowadziłeś prawidłowy token." | ||||||
|   info: "Od teraz, wprowadzaj token wyświetlany na urządzeniu przy każdym logowaniu do Misskey." |   info: "Od teraz, wprowadzaj token wyświetlany na urządzeniu przy każdym logowaniu do Misskey." | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "Aby uzyskać dostęp do API, ustaw ten token jako klucz 'i' parametrów żądań." |   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||||
|   caution: "Nie pokazuj tego tokenu osobom trzecim (nie wprowadzaj go nigdzie indziej), aby konto nie trafiło w niepowołane ręce." |   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||||
|   regeneration-of-token: "W przypadku wycieku tokenu, możesz wygenerować nowy." |   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||||
|   regenerate-token: "Wygeneruj nowy token" |   regenerate-token: "トークンを再生成" | ||||||
|   token: "Token:" |   token: "Token:" | ||||||
|   enter-password: "Wprowadź hasło" |   enter-password: "パスワードを入力してください" | ||||||
|  |   console: | ||||||
|  |     title: 'APIコンソール' | ||||||
|  |     endpoint: 'エンドポイント' | ||||||
|  |     parameter: 'パラメータ' | ||||||
|  |     send: '送信' | ||||||
|  |     sending: '応答待ち' | ||||||
|  |     response: '結果' | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "Brak zautoryzowanych aplikacji" |   no-apps: "Brak zautoryzowanych aplikacji" | ||||||
| common/views/components/drive-settings.vue: | common/views/components/drive-settings.vue: | ||||||
|   max: "容量" |   max: "容量" | ||||||
|   in-use: "使用中" |   in-use: "使用中" | ||||||
|   stats: "統計" |   stats: "統計" | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "Brak wyciszonych użytkowników" |   mute-and-block: "ミュートとブロック" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   block: "ブロック" | ||||||
|  |   no-muted-users: "ミュートしているユーザーはいません" | ||||||
|  |   no-blocked-users: "ブロックしているユーザーはいません" | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "Zmień hasło" |   reset: "Zmień hasło" | ||||||
|   enter-current-password: "Wprowadź obecne hasło" |   enter-current-password: "Wprowadź obecne hasło" | ||||||
| @@ -1048,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "Wycisz" |   mute: "Wycisz" | ||||||
|   muted: "Wyciszyłeś" |   muted: "Wyciszyłeś" | ||||||
|   unmute: "Cofnij wyciszenie" |   unmute: "Cofnij wyciszenie" | ||||||
|  |   block: "ブロックする" | ||||||
|  |   unblock: "ブロック解除" | ||||||
|  |   block-confirm: "このユーザーをブロックしますか?" | ||||||
|   push-to-a-list: "Dodaj do listy" |   push-to-a-list: "Dodaj do listy" | ||||||
|   list-pushed: "Dodałeś(-aś) {user} do {list}." |   list-pushed: "Dodałeś(-aś) {user} do {list}." | ||||||
| desktop/views/pages/user/user.header.vue: | desktop/views/pages/user/user.header.vue: | ||||||
| @@ -1322,6 +1337,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "Oś czasu" |   timeline: "Oś czasu" | ||||||
|   media: "Multimedia" |   media: "Multimedia" | ||||||
|   is-suspended: "To konto zostało zablokowane" |   is-suspended: "To konto zostało zablokowane" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロック" | ||||||
|  |   unblock: "ブロック解除" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "Ostatnie wpisy" |   recent-notes: "Ostatnie wpisy" | ||||||
|   images: "Zdjęcia" |   images: "Zdjęcia" | ||||||
|   | |||||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "プロフィール" |   profile: "プロフィール" | ||||||
|   notification: "通知" |   notification: "通知" | ||||||
|   apps: "アプリ" |   apps: "アプリ" | ||||||
|   mute: "ミュート" |   mute-and-block: "ミュート/ブロック" | ||||||
|  |   blocking: "ブロック" | ||||||
|   security: "セキュリティ" |   security: "セキュリティ" | ||||||
|   signin: "サインイン履歴" |   signin: "サインイン履歴" | ||||||
|   password: "パスワード" |   password: "パスワード" | ||||||
| @@ -847,21 +848,32 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "設定が完了しました!" |   success: "設定が完了しました!" | ||||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" |   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" |   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" |   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" |   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" |   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||||
|   regenerate-token: "トークンを再生成" |   regenerate-token: "トークンを再生成" | ||||||
|   token: "Token:" |   token: "Token:" | ||||||
|   enter-password: "パスワードを入力してください" |   enter-password: "パスワードを入力してください" | ||||||
|  |   console: | ||||||
|  |     title: 'APIコンソール' | ||||||
|  |     endpoint: 'エンドポイント' | ||||||
|  |     parameter: 'パラメータ' | ||||||
|  |     send: '送信' | ||||||
|  |     sending: '応答待ち' | ||||||
|  |     response: '結果' | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "連携しているアプリケーションはありません" |   no-apps: "連携しているアプリケーションはありません" | ||||||
| common/views/components/drive-settings.vue: | common/views/components/drive-settings.vue: | ||||||
|   max: "容量" |   max: "容量" | ||||||
|   in-use: "使用中" |   in-use: "使用中" | ||||||
|   stats: "統計" |   stats: "統計" | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "ミュートしているユーザーはいません" |   mute-and-block: "ミュートとブロック" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   block: "ブロック" | ||||||
|  |   no-muted-users: "ミュートしているユーザーはいません" | ||||||
|  |   no-blocked-users: "ブロックしているユーザーはいません" | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "パスワードを変更する" |   reset: "パスワードを変更する" | ||||||
|   enter-current-password: "現在のパスワードを入力してください" |   enter-current-password: "現在のパスワードを入力してください" | ||||||
| @@ -1048,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "ミュートする" |   mute: "ミュートする" | ||||||
|   muted: "ミュートしています" |   muted: "ミュートしています" | ||||||
|   unmute: "ミュート解除" |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロックする" | ||||||
|  |   unblock: "ブロック解除" | ||||||
|  |   block-confirm: "このユーザーをブロックしますか?" | ||||||
|   push-to-a-list: "リストに追加" |   push-to-a-list: "リストに追加" | ||||||
|   list-pushed: "{user}を{list}に追加しました。" |   list-pushed: "{user}を{list}に追加しました。" | ||||||
| desktop/views/pages/user/user.header.vue: | desktop/views/pages/user/user.header.vue: | ||||||
| @@ -1322,6 +1337,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "Linha do tempo" |   timeline: "Linha do tempo" | ||||||
|   media: "Mídia" |   media: "Mídia" | ||||||
|   is-suspended: "Esta conta foi suspensa" |   is-suspended: "Esta conta foi suspensa" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロック" | ||||||
|  |   unblock: "ブロック解除" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "Notas recentes" |   recent-notes: "Notas recentes" | ||||||
|   images: "Imagens" |   images: "Imagens" | ||||||
|   | |||||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "プロフィール" |   profile: "プロフィール" | ||||||
|   notification: "通知" |   notification: "通知" | ||||||
|   apps: "アプリ" |   apps: "アプリ" | ||||||
|   mute: "ミュート" |   mute-and-block: "ミュート/ブロック" | ||||||
|  |   blocking: "ブロック" | ||||||
|   security: "セキュリティ" |   security: "セキュリティ" | ||||||
|   signin: "サインイン履歴" |   signin: "サインイン履歴" | ||||||
|   password: "パスワード" |   password: "パスワード" | ||||||
| @@ -847,21 +848,32 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "設定が完了しました!" |   success: "設定が完了しました!" | ||||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" |   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" |   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" |   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" |   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" |   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||||
|   regenerate-token: "トークンを再生成" |   regenerate-token: "トークンを再生成" | ||||||
|   token: "Token:" |   token: "Token:" | ||||||
|   enter-password: "パスワードを入力してください" |   enter-password: "パスワードを入力してください" | ||||||
|  |   console: | ||||||
|  |     title: 'APIコンソール' | ||||||
|  |     endpoint: 'エンドポイント' | ||||||
|  |     parameter: 'パラメータ' | ||||||
|  |     send: '送信' | ||||||
|  |     sending: '応答待ち' | ||||||
|  |     response: '結果' | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "連携しているアプリケーションはありません" |   no-apps: "連携しているアプリケーションはありません" | ||||||
| common/views/components/drive-settings.vue: | common/views/components/drive-settings.vue: | ||||||
|   max: "容量" |   max: "容量" | ||||||
|   in-use: "使用中" |   in-use: "使用中" | ||||||
|   stats: "統計" |   stats: "統計" | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "ミュートしているユーザーはいません" |   mute-and-block: "ミュートとブロック" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   block: "ブロック" | ||||||
|  |   no-muted-users: "ミュートしているユーザーはいません" | ||||||
|  |   no-blocked-users: "ブロックしているユーザーはいません" | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "パスワードを変更する" |   reset: "パスワードを変更する" | ||||||
|   enter-current-password: "現在のパスワードを入力してください" |   enter-current-password: "現在のパスワードを入力してください" | ||||||
| @@ -1048,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "ミュートする" |   mute: "ミュートする" | ||||||
|   muted: "ミュートしています" |   muted: "ミュートしています" | ||||||
|   unmute: "ミュート解除" |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロックする" | ||||||
|  |   unblock: "ブロック解除" | ||||||
|  |   block-confirm: "このユーザーをブロックしますか?" | ||||||
|   push-to-a-list: "リストに追加" |   push-to-a-list: "リストに追加" | ||||||
|   list-pushed: "{user}を{list}に追加しました。" |   list-pushed: "{user}を{list}に追加しました。" | ||||||
| desktop/views/pages/user/user.header.vue: | desktop/views/pages/user/user.header.vue: | ||||||
| @@ -1322,6 +1337,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "タイムライン" |   timeline: "タイムライン" | ||||||
|   media: "メディア" |   media: "メディア" | ||||||
|   is-suspended: "このユーザーは凍結されています。" |   is-suspended: "このユーザーは凍結されています。" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロック" | ||||||
|  |   unblock: "ブロック解除" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "最近の投稿" |   recent-notes: "最近の投稿" | ||||||
|   images: "画像" |   images: "画像" | ||||||
|   | |||||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | |||||||
|   profile: "プロフィール" |   profile: "プロフィール" | ||||||
|   notification: "通知" |   notification: "通知" | ||||||
|   apps: "アプリ" |   apps: "アプリ" | ||||||
|   mute: "ミュート" |   mute-and-block: "ミュート/ブロック" | ||||||
|  |   blocking: "ブロック" | ||||||
|   security: "セキュリティ" |   security: "セキュリティ" | ||||||
|   signin: "サインイン履歴" |   signin: "サインイン履歴" | ||||||
|   password: "パスワード" |   password: "パスワード" | ||||||
| @@ -847,21 +848,32 @@ desktop/views/components/settings.2fa.vue: | |||||||
|   success: "設定が完了しました!" |   success: "設定が完了しました!" | ||||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" |   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" |   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||||
| desktop/views/components/settings.api.vue: | common/views/components/api-settings.vue: | ||||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" |   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" |   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" |   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||||
|   regenerate-token: "トークンを再生成" |   regenerate-token: "トークンを再生成" | ||||||
|   token: "Token:" |   token: "Token:" | ||||||
|   enter-password: "パスワードを入力してください" |   enter-password: "パスワードを入力してください" | ||||||
|  |   console: | ||||||
|  |     title: 'APIコンソール' | ||||||
|  |     endpoint: 'エンドポイント' | ||||||
|  |     parameter: 'パラメータ' | ||||||
|  |     send: '送信' | ||||||
|  |     sending: '応答待ち' | ||||||
|  |     response: '結果' | ||||||
| desktop/views/components/settings.apps.vue: | desktop/views/components/settings.apps.vue: | ||||||
|   no-apps: "連携しているアプリケーションはありません" |   no-apps: "連携しているアプリケーションはありません" | ||||||
| common/views/components/drive-settings.vue: | common/views/components/drive-settings.vue: | ||||||
|   max: "容量" |   max: "容量" | ||||||
|   in-use: "使用中" |   in-use: "使用中" | ||||||
|   stats: "統計" |   stats: "統計" | ||||||
| desktop/views/components/settings.mute.vue: | common/views/components/mute-and-block.vue: | ||||||
|   no-users: "ミュートしているユーザーはいません" |   mute-and-block: "ミュートとブロック" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   block: "ブロック" | ||||||
|  |   no-muted-users: "ミュートしているユーザーはいません" | ||||||
|  |   no-blocked-users: "ブロックしているユーザーはいません" | ||||||
| desktop/views/components/settings.password.vue: | desktop/views/components/settings.password.vue: | ||||||
|   reset: "パスワードを変更する" |   reset: "パスワードを変更する" | ||||||
|   enter-current-password: "現在のパスワードを入力してください" |   enter-current-password: "現在のパスワードを入力してください" | ||||||
| @@ -1048,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | |||||||
|   mute: "ミュートする" |   mute: "ミュートする" | ||||||
|   muted: "ミュートしています" |   muted: "ミュートしています" | ||||||
|   unmute: "ミュート解除" |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロックする" | ||||||
|  |   unblock: "ブロック解除" | ||||||
|  |   block-confirm: "このユーザーをブロックしますか?" | ||||||
|   push-to-a-list: "リストに追加" |   push-to-a-list: "リストに追加" | ||||||
|   list-pushed: "{user}を{list}に追加しました。" |   list-pushed: "{user}を{list}に追加しました。" | ||||||
| desktop/views/pages/user/user.header.vue: | desktop/views/pages/user/user.header.vue: | ||||||
| @@ -1322,6 +1337,10 @@ mobile/views/pages/user.vue: | |||||||
|   timeline: "タイムライン" |   timeline: "タイムライン" | ||||||
|   media: "メディア" |   media: "メディア" | ||||||
|   is-suspended: "このユーザーは凍結されています。" |   is-suspended: "このユーザーは凍結されています。" | ||||||
|  |   mute: "ミュート" | ||||||
|  |   unmute: "ミュート解除" | ||||||
|  |   block: "ブロック" | ||||||
|  |   unblock: "ブロック解除" | ||||||
| mobile/views/pages/user/home.vue: | mobile/views/pages/user/home.vue: | ||||||
|   recent-notes: "最近の投稿" |   recent-notes: "最近の投稿" | ||||||
|   images: "画像" |   images: "画像" | ||||||
|   | |||||||
							
								
								
									
										123
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										123
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
| 	"name": "misskey", | 	"name": "misskey", | ||||||
| 	"version": "10.31.0", | 	"version": "10.36.0", | ||||||
| 	"lockfileVersion": 1, | 	"lockfileVersion": 1, | ||||||
| 	"requires": true, | 	"requires": true, | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
| @@ -544,9 +544,9 @@ | |||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"@types/mocha": { | 		"@types/mocha": { | ||||||
| 			"version": "5.2.3", | 			"version": "5.2.5", | ||||||
| 			"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.3.tgz", | 			"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz", | ||||||
| 			"integrity": "sha512-C1wVVr7xhKu6c3Mb27dFzNYR05qvHwgtpN+JOYTGc1pKA7dCEDDYpscn7kul+bCUwa3NoGDbzI1pdznSOa397w==" | 			"integrity": "sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww==" | ||||||
| 		}, | 		}, | ||||||
| 		"@types/mongodb": { | 		"@types/mongodb": { | ||||||
| 			"version": "3.1.12", | 			"version": "3.1.12", | ||||||
| @@ -691,9 +691,12 @@ | |||||||
| 			"integrity": "sha512-XWwqRvaSsf4yq/4SYL5/7n5i2RWqf+jtIRlasj65cuZZDZ01wtkDfAIxrEpYcLvzT1HMqBmsnMEcZjK+OMeojQ==" | 			"integrity": "sha512-XWwqRvaSsf4yq/4SYL5/7n5i2RWqf+jtIRlasj65cuZZDZ01wtkDfAIxrEpYcLvzT1HMqBmsnMEcZjK+OMeojQ==" | ||||||
| 		}, | 		}, | ||||||
| 		"@types/speakeasy": { | 		"@types/speakeasy": { | ||||||
| 			"version": "2.0.2", | 			"version": "2.0.3", | ||||||
| 			"resolved": "https://registry.npmjs.org/@types/speakeasy/-/speakeasy-2.0.2.tgz", | 			"resolved": "https://registry.npmjs.org/@types/speakeasy/-/speakeasy-2.0.3.tgz", | ||||||
| 			"integrity": "sha512-h8KW3wSd3/l4oKRGYPxExCaos5VmjcnwDG3RK25tfcoWQR9iLmM9UbwvF1Pd+UT5aY1Z3LdQGt4xU0u9Zk/C2Q==" | 			"integrity": "sha512-lDc49Aec4WnQPRhW3n90ct14CH0Zyrj48RGMK1SSQP6BOf8HamWg+PG9uj1DVnaa6t+lhQU1j1lhGA7d9baxWw==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"@types/node": "*" | ||||||
|  | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"@types/superagent": { | 		"@types/superagent": { | ||||||
| 			"version": "3.8.4", | 			"version": "3.8.4", | ||||||
| @@ -1259,9 +1262,9 @@ | |||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"apexcharts": { | 		"apexcharts": { | ||||||
| 			"version": "2.1.5", | 			"version": "2.1.9", | ||||||
| 			"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-2.1.5.tgz", | 			"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-2.1.9.tgz", | ||||||
| 			"integrity": "sha512-4eKh2HyQVr5ct2t7cWkvDSUyJM9KGw6dRHAlojqo6HJz+XtrnnvL8uP18rzNq4M80P3Ul+yDjpCt5EXlYPfo5Q==", | 			"integrity": "sha512-Qs/jLUa03wqPR53yMk8QAwq+qrX/Odc3IIXH2WVVjdWyFXS1lYzGSDbVcVDnOKkxoLdAlzPI3icb2bMjskwfXQ==", | ||||||
| 			"requires": { | 			"requires": { | ||||||
| 				"babel-polyfill": "^6.26.0", | 				"babel-polyfill": "^6.26.0", | ||||||
| 				"core-js": "^2.5.7", | 				"core-js": "^2.5.7", | ||||||
| @@ -3332,12 +3335,12 @@ | |||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"data-urls": { | 		"data-urls": { | ||||||
| 			"version": "1.0.1", | 			"version": "1.1.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.0.1.tgz", | 			"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", | ||||||
| 			"integrity": "sha512-0HdcMZzK6ubMUnsMmQmG0AcLQPvbvb47R0+7CCZQCYgcd8OUWG91CG7sM6GoXgjz+WLl4ArFzHtBMy/QqSF4eg==", | 			"integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", | ||||||
| 			"requires": { | 			"requires": { | ||||||
| 				"abab": "^2.0.0", | 				"abab": "^2.0.0", | ||||||
| 				"whatwg-mimetype": "^2.1.0", | 				"whatwg-mimetype": "^2.2.0", | ||||||
| 				"whatwg-url": "^7.0.0" | 				"whatwg-url": "^7.0.0" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| @@ -4755,9 +4758,9 @@ | |||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"eslint": { | 		"eslint": { | ||||||
| 			"version": "5.7.0", | 			"version": "5.8.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.7.0.tgz", | 			"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.8.0.tgz", | ||||||
| 			"integrity": "sha512-zYCeFQahsxffGl87U2aJ7DPyH8CbWgxBC213Y8+TCanhUTf2gEvfq3EKpHmEcozTLyPmGe9LZdMAwC/CpJBM5A==", | 			"integrity": "sha512-Zok6Bru3y2JprqTNm14mgQ15YQu/SMDkWdnmHfFg770DIUlmMFd/gqqzCHekxzjHZJxXv3tmTpH0C1icaYJsRQ==", | ||||||
| 			"requires": { | 			"requires": { | ||||||
| 				"@babel/code-frame": "^7.0.0", | 				"@babel/code-frame": "^7.0.0", | ||||||
| 				"ajv": "^6.5.3", | 				"ajv": "^6.5.3", | ||||||
| @@ -5311,9 +5314,9 @@ | |||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"file-type": { | 		"file-type": { | ||||||
| 			"version": "10.1.0", | 			"version": "10.2.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/file-type/-/file-type-10.1.0.tgz", | 			"resolved": "https://registry.npmjs.org/file-type/-/file-type-10.2.0.tgz", | ||||||
| 			"integrity": "sha512-fkjfXnqBRrdUFTS6opakWyMXb+uzDv8zOCqjSOWPbzMLnYnmnUEv/RNY9igkk4nc8TVL44Xd1OCC2fJXH3eb7Q==" | 			"integrity": "sha512-eqX81S1PWdLDPW39yyB214TVVOsUQjSmPcyUjeVH6ksH+94Y2YA/ItiIwa53rJiSofJZLK6lGsuCE3rwt0vp4w==" | ||||||
| 		}, | 		}, | ||||||
| 		"filename-regex": { | 		"filename-regex": { | ||||||
| 			"version": "2.0.1", | 			"version": "2.0.1", | ||||||
| @@ -7224,14 +7227,14 @@ | |||||||
| 			"integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" | 			"integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" | ||||||
| 		}, | 		}, | ||||||
| 		"html-minifier": { | 		"html-minifier": { | ||||||
| 			"version": "3.5.20", | 			"version": "3.5.21", | ||||||
| 			"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.20.tgz", | 			"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", | ||||||
| 			"integrity": "sha512-ZmgNLaTp54+HFKkONyLFEfs5dd/ZOtlquKaTnqIWFmx3Av5zG6ZPcV2d0o9XM2fXOTxxIf6eDcwzFFotke/5zA==", | 			"integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", | ||||||
| 			"requires": { | 			"requires": { | ||||||
| 				"camel-case": "3.0.x", | 				"camel-case": "3.0.x", | ||||||
| 				"clean-css": "4.2.x", | 				"clean-css": "4.2.x", | ||||||
| 				"commander": "2.17.x", | 				"commander": "2.17.x", | ||||||
| 				"he": "1.1.x", | 				"he": "1.2.x", | ||||||
| 				"param-case": "2.1.x", | 				"param-case": "2.1.x", | ||||||
| 				"relateurl": "0.2.x", | 				"relateurl": "0.2.x", | ||||||
| 				"uglify-js": "3.4.x" | 				"uglify-js": "3.4.x" | ||||||
| @@ -7242,14 +7245,10 @@ | |||||||
| 					"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", | 					"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", | ||||||
| 					"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" | 					"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" | ||||||
| 				}, | 				}, | ||||||
| 				"uglify-js": { | 				"he": { | ||||||
| 					"version": "3.4.9", | 					"version": "1.2.0", | ||||||
| 					"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", | 					"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", | ||||||
| 					"integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", | 					"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" | ||||||
| 					"requires": { |  | ||||||
| 						"commander": "~2.17.1", |  | ||||||
| 						"source-map": "~0.6.1" |  | ||||||
| 					} |  | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| @@ -8355,9 +8354,9 @@ | |||||||
| 			"integrity": "sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ==" | 			"integrity": "sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ==" | ||||||
| 		}, | 		}, | ||||||
| 		"jsdom": { | 		"jsdom": { | ||||||
| 			"version": "12.2.0", | 			"version": "13.0.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-12.2.0.tgz", | 			"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-13.0.0.tgz", | ||||||
| 			"integrity": "sha512-QPOggIJ8fquWPLaYYMoh+zqUmdphDtu1ju0QGTitZT1Yd8I5qenPpXM1etzUegu3MjVp8XPzgZxdn8Yj7e40ig==", | 			"integrity": "sha512-Kmq4ASMNkgpY+YufE322EnIKoiz0UWY2DRkKlU7d5YrIW4xiVRhWFrZV1fr6w/ZNxQ50wGAH5gGRzydgnmkkvw==", | ||||||
| 			"requires": { | 			"requires": { | ||||||
| 				"abab": "^2.0.0", | 				"abab": "^2.0.0", | ||||||
| 				"acorn": "^6.0.2", | 				"acorn": "^6.0.2", | ||||||
| @@ -8378,6 +8377,7 @@ | |||||||
| 				"symbol-tree": "^3.2.2", | 				"symbol-tree": "^3.2.2", | ||||||
| 				"tough-cookie": "^2.4.3", | 				"tough-cookie": "^2.4.3", | ||||||
| 				"w3c-hr-time": "^1.0.1", | 				"w3c-hr-time": "^1.0.1", | ||||||
|  | 				"w3c-xmlserializer": "^1.0.0", | ||||||
| 				"webidl-conversions": "^4.0.2", | 				"webidl-conversions": "^4.0.2", | ||||||
| 				"whatwg-encoding": "^1.0.5", | 				"whatwg-encoding": "^1.0.5", | ||||||
| 				"whatwg-mimetype": "^2.2.0", | 				"whatwg-mimetype": "^2.2.0", | ||||||
| @@ -15573,9 +15573,9 @@ | |||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"ts-loader": { | 		"ts-loader": { | ||||||
| 			"version": "5.2.2", | 			"version": "5.3.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.2.2.tgz", | 			"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.3.0.tgz", | ||||||
| 			"integrity": "sha512-vM/TrEKXBqRYq5yLatsXyKFnYSpv53klmGtrILGlNqcMsxPVi8+e4yr1Agbu9oMZepx/4szDVn5QpFo83IQdQg==", | 			"integrity": "sha512-lGSNs7szRFj/rK9T1EQuayE3QNLg6izDUxt5jpmq0RG1rU2bapAt7E7uLckLCUPeO1jwxCiet2oRaWovc53UAg==", | ||||||
| 			"requires": { | 			"requires": { | ||||||
| 				"chalk": "^2.3.0", | 				"chalk": "^2.3.0", | ||||||
| 				"enhanced-resolve": "^4.0.0", | 				"enhanced-resolve": "^4.0.0", | ||||||
| @@ -15687,16 +15687,17 @@ | |||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"typescript": { | 		"typescript": { | ||||||
| 			"version": "3.1.3", | 			"version": "3.1.4", | ||||||
| 			"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.3.tgz", | 			"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.4.tgz", | ||||||
| 			"integrity": "sha512-+81MUSyX+BaSo+u2RbozuQk/UWx6hfG0a5gHu4ANEM4sU96XbuIyAB+rWBW1u70c6a5QuZfuYICn3s2UjuHUpA==" | 			"integrity": "sha512-JZHJtA6ZL15+Q3Dqkbh8iCUmvxD3iJ7ujXS+fVkKnwIVAdHc5BJTDNM0aTrnr2luKulFjU7W+SRhDZvi66Ru7Q==" | ||||||
| 		}, | 		}, | ||||||
| 		"typescript-eslint-parser": { | 		"typescript-eslint-parser": { | ||||||
| 			"version": "20.0.0", | 			"version": "20.1.1", | ||||||
| 			"resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-20.0.0.tgz", | 			"resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-20.1.1.tgz", | ||||||
| 			"integrity": "sha512-HZEoGA+LnS3etUlVAPX6I8sZ7872Yb0vPvQv6QDCIE44KD3bFmvPEQ4LbiD+qGkcxh6segjVK0v3rxpb2R6oSA==", | 			"integrity": "sha512-IJhpqHK60Pz2J5pe8rJUQ10DcMcGwhQnvRFcPV79coEV3bpNfSiHkgpS+sf6zx2ANDWgBhmtZbK9ICOy+v3FKA==", | ||||||
| 			"requires": { | 			"requires": { | ||||||
| 				"eslint": "4.19.1", | 				"eslint": "4.19.1", | ||||||
|  | 				"eslint-visitor-keys": "^1.0.0", | ||||||
| 				"typescript-estree": "2.1.0" | 				"typescript-estree": "2.1.0" | ||||||
| 			}, | 			}, | ||||||
| 			"dependencies": { | 			"dependencies": { | ||||||
| @@ -15829,7 +15830,7 @@ | |||||||
| 				}, | 				}, | ||||||
| 				"fast-deep-equal": { | 				"fast-deep-equal": { | ||||||
| 					"version": "1.1.0", | 					"version": "1.1.0", | ||||||
| 					"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", | 					"resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", | ||||||
| 					"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" | 					"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" | ||||||
| 				}, | 				}, | ||||||
| 				"ignore": { | 				"ignore": { | ||||||
| @@ -15911,6 +15912,22 @@ | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"uglify-js": { | ||||||
|  | 			"version": "3.4.9", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", | ||||||
|  | 			"integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"commander": "~2.17.1", | ||||||
|  | 				"source-map": "~0.6.1" | ||||||
|  | 			}, | ||||||
|  | 			"dependencies": { | ||||||
|  | 				"commander": { | ||||||
|  | 					"version": "2.17.1", | ||||||
|  | 					"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", | ||||||
|  | 					"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"uglify-to-browserify": { | 		"uglify-to-browserify": { | ||||||
| 			"version": "1.0.2", | 			"version": "1.0.2", | ||||||
| 			"resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", | 			"resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", | ||||||
| @@ -16763,6 +16780,16 @@ | |||||||
| 				"browser-process-hrtime": "^0.1.2" | 				"browser-process-hrtime": "^0.1.2" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"w3c-xmlserializer": { | ||||||
|  | 			"version": "1.0.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.0.0.tgz", | ||||||
|  | 			"integrity": "sha512-0et1+9uXYiIRAecx1D5Z1nk60+vimniGdIKl4XjeqkWi6acoHNlXMv1VR5jV+jF4ooeO08oWbYxeAJOcon1oMA==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"domexception": "^1.0.1", | ||||||
|  | 				"webidl-conversions": "^4.0.2", | ||||||
|  | 				"xml-name-validator": "^3.0.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"ware": { | 		"ware": { | ||||||
| 			"version": "1.3.0", | 			"version": "1.3.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/ware/-/ware-1.3.0.tgz", | 			"resolved": "https://registry.npmjs.org/ware/-/ware-1.3.0.tgz", | ||||||
| @@ -16808,9 +16835,9 @@ | |||||||
| 			"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" | 			"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" | ||||||
| 		}, | 		}, | ||||||
| 		"webpack": { | 		"webpack": { | ||||||
| 			"version": "4.23.0", | 			"version": "4.23.1", | ||||||
| 			"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.23.0.tgz", | 			"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.23.1.tgz", | ||||||
| 			"integrity": "sha512-Osh/3U9y4swhEKDjy8fF48v2Qx5VC6VvdQ8bEm1HMaVVddiQBw4+mIyDrzVcVRCPT/+4uJFOcklPuoB+I3Zw0w==", | 			"integrity": "sha512-iE5Cu4rGEDk7ONRjisTOjVHv3dDtcFfwitSxT7evtYj/rANJpt1OuC/Kozh1pBa99AUBr1L/LsaNB+D9Xz3CEg==", | ||||||
| 			"requires": { | 			"requires": { | ||||||
| 				"@webassemblyjs/ast": "1.7.10", | 				"@webassemblyjs/ast": "1.7.10", | ||||||
| 				"@webassemblyjs/helper-module-context": "1.7.10", | 				"@webassemblyjs/helper-module-context": "1.7.10", | ||||||
|   | |||||||
							
								
								
									
										33
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,8 +1,8 @@ | |||||||
| { | { | ||||||
| 	"name": "misskey", | 	"name": "misskey", | ||||||
| 	"author": "syuilo <i@syuilo.com>", | 	"author": "syuilo <i@syuilo.com>", | ||||||
| 	"version": "10.32.0", | 	"version": "10.37.0", | ||||||
| 	"clientVersion": "1.0.11138", | 	"clientVersion": "1.0.11314", | ||||||
| 	"codename": "nighthike", | 	"codename": "nighthike", | ||||||
| 	"main": "./built/index.js", | 	"main": "./built/index.js", | ||||||
| 	"private": true, | 	"private": true, | ||||||
| @@ -58,7 +58,7 @@ | |||||||
| 		"@types/koa__cors": "2.2.3", | 		"@types/koa__cors": "2.2.3", | ||||||
| 		"@types/minio": "7.0.0", | 		"@types/minio": "7.0.0", | ||||||
| 		"@types/mkdirp": "0.5.2", | 		"@types/mkdirp": "0.5.2", | ||||||
| 		"@types/mocha": "5.2.3", | 		"@types/mocha": "5.2.5", | ||||||
| 		"@types/mongodb": "3.1.12", | 		"@types/mongodb": "3.1.12", | ||||||
| 		"@types/ms": "0.7.30", | 		"@types/ms": "0.7.30", | ||||||
| 		"@types/node": "10.12.0", | 		"@types/node": "10.12.0", | ||||||
| @@ -74,7 +74,7 @@ | |||||||
| 		"@types/sharp": "0.21.0", | 		"@types/sharp": "0.21.0", | ||||||
| 		"@types/showdown": "1.7.5", | 		"@types/showdown": "1.7.5", | ||||||
| 		"@types/single-line-log": "1.1.0", | 		"@types/single-line-log": "1.1.0", | ||||||
| 		"@types/speakeasy": "2.0.2", | 		"@types/speakeasy": "2.0.3", | ||||||
| 		"@types/systeminformation": "3.23.0", | 		"@types/systeminformation": "3.23.0", | ||||||
| 		"@types/tinycolor2": "1.4.1", | 		"@types/tinycolor2": "1.4.1", | ||||||
| 		"@types/tmp": "0.0.33", | 		"@types/tmp": "0.0.33", | ||||||
| @@ -84,7 +84,7 @@ | |||||||
| 		"@types/websocket": "0.0.40", | 		"@types/websocket": "0.0.40", | ||||||
| 		"@types/ws": "6.0.1", | 		"@types/ws": "6.0.1", | ||||||
| 		"animejs": "2.2.0", | 		"animejs": "2.2.0", | ||||||
| 		"apexcharts": "2.1.5", | 		"apexcharts": "2.1.9", | ||||||
| 		"autobind-decorator": "2.1.0", | 		"autobind-decorator": "2.1.0", | ||||||
| 		"autosize": "4.0.2", | 		"autosize": "4.0.2", | ||||||
| 		"autwh": "0.1.0", | 		"autwh": "0.1.0", | ||||||
| @@ -108,11 +108,11 @@ | |||||||
| 		"elasticsearch": "15.1.1", | 		"elasticsearch": "15.1.1", | ||||||
| 		"emojilib": "2.3.0", | 		"emojilib": "2.3.0", | ||||||
| 		"escape-regexp": "0.0.1", | 		"escape-regexp": "0.0.1", | ||||||
| 		"eslint": "5.7.0", | 		"eslint": "5.8.0", | ||||||
| 		"eslint-plugin-vue": "4.7.1", | 		"eslint-plugin-vue": "4.7.1", | ||||||
| 		"eventemitter3": "3.1.0", | 		"eventemitter3": "3.1.0", | ||||||
| 		"file-loader": "2.0.0", | 		"file-loader": "2.0.0", | ||||||
| 		"file-type": "10.1.0", | 		"file-type": "10.2.0", | ||||||
| 		"fuckadblock": "3.2.1", | 		"fuckadblock": "3.2.1", | ||||||
| 		"gulp": "3.9.1", | 		"gulp": "3.9.1", | ||||||
| 		"gulp-cssnano": "2.1.3", | 		"gulp-cssnano": "2.1.3", | ||||||
| @@ -129,13 +129,13 @@ | |||||||
| 		"gulp-uglify": "3.0.1", | 		"gulp-uglify": "3.0.1", | ||||||
| 		"gulp-util": "3.0.8", | 		"gulp-util": "3.0.8", | ||||||
| 		"hard-source-webpack-plugin": "0.12.0", | 		"hard-source-webpack-plugin": "0.12.0", | ||||||
| 		"html-minifier": "3.5.20", | 		"html-minifier": "3.5.21", | ||||||
| 		"http-signature": "1.2.0", | 		"http-signature": "1.2.0", | ||||||
| 		"insert-text-at-cursor": "0.1.1", | 		"insert-text-at-cursor": "0.1.1", | ||||||
| 		"is-root": "2.0.0", | 		"is-root": "2.0.0", | ||||||
| 		"is-url": "1.2.4", | 		"is-url": "1.2.4", | ||||||
| 		"js-yaml": "3.12.0", | 		"js-yaml": "3.12.0", | ||||||
| 		"jsdom": "12.2.0", | 		"jsdom": "13.0.0", | ||||||
| 		"json5": "2.1.0", | 		"json5": "2.1.0", | ||||||
| 		"json5-loader": "1.0.1", | 		"json5-loader": "1.0.1", | ||||||
| 		"koa": "2.6.1", | 		"koa": "2.6.1", | ||||||
| @@ -201,11 +201,11 @@ | |||||||
| 		"textarea-caret": "3.1.0", | 		"textarea-caret": "3.1.0", | ||||||
| 		"tinycolor2": "1.4.1", | 		"tinycolor2": "1.4.1", | ||||||
| 		"tmp": "0.0.33", | 		"tmp": "0.0.33", | ||||||
| 		"ts-loader": "5.2.2", | 		"ts-loader": "5.3.0", | ||||||
| 		"ts-node": "7.0.1", | 		"ts-node": "7.0.1", | ||||||
| 		"tslint": "5.10.0", | 		"tslint": "5.10.0", | ||||||
| 		"typescript": "3.1.3", | 		"typescript": "3.1.4", | ||||||
| 		"typescript-eslint-parser": "20.0.0", | 		"typescript-eslint-parser": "20.1.1", | ||||||
| 		"uglify-es": "3.3.9", | 		"uglify-es": "3.3.9", | ||||||
| 		"url-loader": "1.1.2", | 		"url-loader": "1.1.2", | ||||||
| 		"uuid": "3.3.2", | 		"uuid": "3.3.2", | ||||||
| @@ -229,17 +229,10 @@ | |||||||
| 		"vuex-persistedstate": "2.5.4", | 		"vuex-persistedstate": "2.5.4", | ||||||
| 		"web-push": "3.3.3", | 		"web-push": "3.3.3", | ||||||
| 		"webfinger.js": "2.6.6", | 		"webfinger.js": "2.6.6", | ||||||
| 		"webpack": "4.23.0", | 		"webpack": "4.23.1", | ||||||
| 		"webpack-cli": "3.1.2", | 		"webpack-cli": "3.1.2", | ||||||
| 		"websocket": "1.0.28", | 		"websocket": "1.0.28", | ||||||
| 		"ws": "6.1.0", | 		"ws": "6.1.0", | ||||||
| 		"xev": "2.0.1" | 		"xev": "2.0.1" | ||||||
| 	}, |  | ||||||
| 	"greenkeeper": { |  | ||||||
| 		"ignore": [ |  | ||||||
| 			"deepcopy", |  | ||||||
| 			"cafy", |  | ||||||
| 			"@types/gulp" |  | ||||||
| 		] |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										72
									
								
								src/client/app/common/views/components/api-settings.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/client/app/common/views/components/api-settings.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | <template> | ||||||
|  | <ui-card> | ||||||
|  | 	<div slot="title">%fa:key% API</div> | ||||||
|  |  | ||||||
|  | 	<section class="fit-top"> | ||||||
|  | 		<ui-input :value="$store.state.i.token" readonly> | ||||||
|  | 			<span>%i18n:@token%</span> | ||||||
|  | 		</ui-input> | ||||||
|  | 		<p>%i18n:@intro%</p> | ||||||
|  | 		<ui-info warn>%i18n:@caution%</ui-info> | ||||||
|  | 		<p>%i18n:@regeneration-of-token%</p> | ||||||
|  | 		<ui-button @click="regenerateToken">%fa:sync-alt% %i18n:@regenerate-token%</ui-button> | ||||||
|  | 	</section> | ||||||
|  |  | ||||||
|  | 	<section> | ||||||
|  | 		<header>%fa:terminal% %i18n:@console.title%</header> | ||||||
|  | 		<ui-input v-model="endpoint"> | ||||||
|  | 			<span>%i18n:@console.endpoint%</span> | ||||||
|  | 		</ui-input> | ||||||
|  | 		<ui-textarea v-model="body"> | ||||||
|  | 			<span>%i18n:@console.parameter% (JSON or JSON5)</span> | ||||||
|  | 		</ui-textarea> | ||||||
|  | 		<ui-button @click="send" :disabled="sending"> | ||||||
|  | 			<template v-if="sending">%i18n:@console.sending%</template> | ||||||
|  | 			<template v-else>%fa:paper-plane% %i18n:@console.send%</template> | ||||||
|  | 		</ui-button> | ||||||
|  | 		<ui-textarea v-if="res" v-model="res" readonly tall> | ||||||
|  | 			<span>%i18n:@console.response%</span> | ||||||
|  | 		</ui-textarea> | ||||||
|  | 	</section> | ||||||
|  | </ui-card> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import * as JSON5 from 'json5'; | ||||||
|  |  | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			endpoint: '', | ||||||
|  | 			body: '{}', | ||||||
|  | 			res: null, | ||||||
|  | 			sending: false | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	methods: { | ||||||
|  | 		regenerateToken() { | ||||||
|  | 			(this as any).apis.input({ | ||||||
|  | 				title: '%i18n:@enter-password%', | ||||||
|  | 				type: 'password' | ||||||
|  | 			}).then(password => { | ||||||
|  | 				(this as any).api('i/regenerate_token', { | ||||||
|  | 					password: password | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		send() { | ||||||
|  | 			this.sending = true; | ||||||
|  | 			(this as any).api(this.endpoint, JSON5.parse(this.body)).then(res => { | ||||||
|  | 				this.sending = false; | ||||||
|  | 				this.res = JSON5.stringify(res, null, 2); | ||||||
|  | 			}, err => { | ||||||
|  | 				this.sending = false; | ||||||
|  | 				this.res = JSON5.stringify(err, null, 2); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
| @@ -14,7 +14,8 @@ | |||||||
| 	</ol> | 	</ol> | ||||||
| 	<ol class="emojis" ref="suggests" v-if="emojis.length > 0"> | 	<ol class="emojis" ref="suggests" v-if="emojis.length > 0"> | ||||||
| 		<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1"> | 		<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1"> | ||||||
| 			<span class="emoji">{{ emoji.emoji }}</span> | 			<span class="emoji" v-if="emoji.url"><img :src="emoji.url" :alt="emoji.emoji"/></span> | ||||||
|  | 			<span class="emoji" v-else>{{ emoji.emoji }}</span> | ||||||
| 			<span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span> | 			<span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span> | ||||||
| 			<span class="alias" v-if="emoji.alias">({{ emoji.alias }})</span> | 			<span class="alias" v-if="emoji.alias">({{ emoji.alias }})</span> | ||||||
| 		</li> | 		</li> | ||||||
| @@ -169,22 +170,45 @@ export default Vue.extend({ | |||||||
| 				} | 				} | ||||||
| 			} else if (this.type == 'emoji') { | 			} else if (this.type == 'emoji') { | ||||||
| 				const matched = []; | 				const matched = []; | ||||||
|  | 				const max = 30; | ||||||
|  |  | ||||||
|  | 				const customEmojis = (this.os.getMetaSync() || { emojis: [] }).emojis; | ||||||
|  | 				customEmojis.some(x => { | ||||||
|  | 					if (x.name.startsWith(this.q)) matched.push({ | ||||||
|  | 						name: x.name, | ||||||
|  | 						emoji: `:${x.name}:`, | ||||||
|  | 						url: x.url | ||||||
|  | 					}); | ||||||
|  | 					return matched.length == max; | ||||||
|  | 				}); | ||||||
|  | 				customEmojis.some(x => { | ||||||
|  | 					const alias = (x.aliases || []).find(a => a.startsWith(this.q)); | ||||||
|  | 					if (alias) matched.push({ | ||||||
|  | 						alias: x.name, | ||||||
|  | 						name: alias, | ||||||
|  | 						emoji: `:${x.name}:`, | ||||||
|  | 						url: x.url | ||||||
|  | 					}); | ||||||
|  | 					return matched.length == max; | ||||||
|  | 				}); | ||||||
|  |  | ||||||
| 				emjdb.some(x => { | 				emjdb.some(x => { | ||||||
| 					if (x.name.indexOf(this.q) == 0 && !x.alias && !matched.some(y => y.emoji == x.emoji)) matched.push(x); | 					if (x.name.indexOf(this.q) == 0 && !x.alias && !matched.some(y => y.emoji == x.emoji)) matched.push(x); | ||||||
| 					return matched.length == 30; | 					return matched.length == max; | ||||||
| 				}); | 				}); | ||||||
| 				if (matched.length < 30) { | 				if (matched.length < max) { | ||||||
| 					emjdb.some(x => { | 					emjdb.some(x => { | ||||||
| 						if (x.name.indexOf(this.q) == 0 && !matched.some(y => y.emoji == x.emoji)) matched.push(x); | 						if (x.name.indexOf(this.q) == 0 && !matched.some(y => y.emoji == x.emoji)) matched.push(x); | ||||||
| 						return matched.length == 30; | 						return matched.length == max; | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
| 				if (matched.length < 30) { | 				if (matched.length < max) { | ||||||
| 					emjdb.some(x => { | 					emjdb.some(x => { | ||||||
| 						if (x.name.indexOf(this.q) > -1 && !matched.some(y => y.emoji == x.emoji)) matched.push(x); | 						if (x.name.indexOf(this.q) > -1 && !matched.some(y => y.emoji == x.emoji)) matched.push(x); | ||||||
| 						return matched.length == 30; | 						return matched.length == max; | ||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				this.emojis = matched; | 				this.emojis = matched; | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| @@ -340,6 +364,10 @@ export default Vue.extend({ | |||||||
| 			margin 0 4px 0 0 | 			margin 0 4px 0 0 | ||||||
| 			width 24px | 			width 24px | ||||||
|  |  | ||||||
|  | 			> img | ||||||
|  | 				width 24px | ||||||
|  | 				vertical-align bottom | ||||||
|  |  | ||||||
| 		.name | 		.name | ||||||
| 			color var(--autocompleteItemText) | 			color var(--autocompleteItemText) | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								src/client/app/common/views/components/error.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/client/app/common/views/components/error.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | <template> | ||||||
|  | <div class="wjqjnyhzogztorhrdgcpqlkxhkmuetgj"> | ||||||
|  | 	<p>%fa:exclamation-triangle% %i18n:common.error.title%</p> | ||||||
|  | 	<ui-button @click="() => $emit('retry')">%i18n:common.error.retry%</ui-button> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <style lang="stylus" scoped> | ||||||
|  | .wjqjnyhzogztorhrdgcpqlkxhkmuetgj | ||||||
|  | 	max-width 350px | ||||||
|  | 	margin 0 auto | ||||||
|  | 	padding 32px | ||||||
|  | 	text-align center | ||||||
|  | 	color var(--text) | ||||||
|  |  | ||||||
|  | 	> p | ||||||
|  | 		margin 0 0 8px 0 | ||||||
|  |  | ||||||
|  | </style> | ||||||
| @@ -1,5 +1,8 @@ | |||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
|  |  | ||||||
|  | import muteAndBlock from './mute-and-block.vue'; | ||||||
|  | import error from './error.vue'; | ||||||
|  | import apiSettings from './api-settings.vue'; | ||||||
| import driveSettings from './drive-settings.vue'; | import driveSettings from './drive-settings.vue'; | ||||||
| import profileEditor from './profile-editor.vue'; | import profileEditor from './profile-editor.vue'; | ||||||
| import noteSkeleton from './note-skeleton.vue'; | import noteSkeleton from './note-skeleton.vue'; | ||||||
| @@ -44,9 +47,13 @@ import uiTextarea from './ui/textarea.vue'; | |||||||
| import uiSwitch from './ui/switch.vue'; | import uiSwitch from './ui/switch.vue'; | ||||||
| import uiRadio from './ui/radio.vue'; | import uiRadio from './ui/radio.vue'; | ||||||
| import uiSelect from './ui/select.vue'; | import uiSelect from './ui/select.vue'; | ||||||
|  | import uiInfo from './ui/info.vue'; | ||||||
| import formButton from './ui/form/button.vue'; | import formButton from './ui/form/button.vue'; | ||||||
| import formRadio from './ui/form/radio.vue'; | import formRadio from './ui/form/radio.vue'; | ||||||
|  |  | ||||||
|  | Vue.component('mk-mute-and-block', muteAndBlock); | ||||||
|  | Vue.component('mk-error', error); | ||||||
|  | Vue.component('mk-api-settings', apiSettings); | ||||||
| Vue.component('mk-drive-settings', driveSettings); | Vue.component('mk-drive-settings', driveSettings); | ||||||
| Vue.component('mk-profile-editor', profileEditor); | Vue.component('mk-profile-editor', profileEditor); | ||||||
| Vue.component('mk-note-skeleton', noteSkeleton); | Vue.component('mk-note-skeleton', noteSkeleton); | ||||||
| @@ -91,5 +98,6 @@ Vue.component('ui-textarea', uiTextarea); | |||||||
| Vue.component('ui-switch', uiSwitch); | Vue.component('ui-switch', uiSwitch); | ||||||
| Vue.component('ui-radio', uiRadio); | Vue.component('ui-radio', uiRadio); | ||||||
| Vue.component('ui-select', uiSelect); | Vue.component('ui-select', uiSelect); | ||||||
|  | Vue.component('ui-info', uiInfo); | ||||||
| Vue.component('form-button', formButton); | Vue.component('form-button', formButton); | ||||||
| Vue.component('form-radio', formRadio); | Vue.component('form-radio', formRadio); | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ import * as emojilib from 'emojilib'; | |||||||
| import { length } from 'stringz'; | import { length } from 'stringz'; | ||||||
| import parse from '../../../../../mfm/parse'; | import parse from '../../../../../mfm/parse'; | ||||||
| import getAcct from '../../../../../misc/acct/render'; | import getAcct from '../../../../../misc/acct/render'; | ||||||
| import { url } from '../../../config'; |  | ||||||
| import MkUrl from './url.vue'; | import MkUrl from './url.vue'; | ||||||
| import MkGoogle from './google.vue'; | import MkGoogle from './google.vue'; | ||||||
| import { concat } from '../../../../../prelude/array'; | import { concat } from '../../../../../prelude/array'; | ||||||
| @@ -186,6 +185,21 @@ export default Vue.component('misskey-flavored-markdown', { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				case 'emoji': { | 				case 'emoji': { | ||||||
|  | 					//#region カスタム絵文字 | ||||||
|  | 					const customEmojis = (this.os.getMetaSync() || { emojis: [] }).emojis; | ||||||
|  | 					const customEmoji = customEmojis.find(e => e.name == token.emoji || (e.aliases || []).includes(token.emoji)); | ||||||
|  | 					if (customEmoji) { | ||||||
|  | 						return [createElement('img', { | ||||||
|  | 							attrs: { | ||||||
|  | 								src: customEmoji.url, | ||||||
|  | 								alt: token.emoji, | ||||||
|  | 								title: token.emoji, | ||||||
|  | 								style: 'height: 2.5em; vertical-align: middle;' | ||||||
|  | 							} | ||||||
|  | 						})]; | ||||||
|  | 					} | ||||||
|  | 					//#endregion | ||||||
|  |  | ||||||
| 					const emoji = emojilib.lib[token.emoji]; | 					const emoji = emojilib.lib[token.emoji]; | ||||||
| 					return [createElement('span', emoji ? emoji.char : token.content)]; | 					return [createElement('span', emoji ? emoji.char : token.content)]; | ||||||
| 				} | 				} | ||||||
|   | |||||||
							
								
								
									
										52
									
								
								src/client/app/common/views/components/mute-and-block.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/client/app/common/views/components/mute-and-block.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | <template> | ||||||
|  | <ui-card> | ||||||
|  | 	<div slot="title">%fa:ban% %i18n:@mute-and-block%</div> | ||||||
|  |  | ||||||
|  | 	<section> | ||||||
|  | 		<header>%i18n:@mute%</header> | ||||||
|  | 		<ui-info v-if="!muteFetching && mute.length == 0">%i18n:@no-muted-users%</ui-info> | ||||||
|  | 		<div class="users" v-if="mute.length != 0"> | ||||||
|  | 			<div v-for="user in mute" :key="user.id"> | ||||||
|  | 				<p><b>{{ user | userName }}</b> @{{ user | acct }}</p> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 	</section> | ||||||
|  |  | ||||||
|  | 	<section> | ||||||
|  | 		<header>%i18n:@block%</header> | ||||||
|  | 		<ui-info v-if="!blockFetching && block.length == 0">%i18n:@no-blocked-users%</ui-info> | ||||||
|  | 		<div class="users" v-if="block.length != 0"> | ||||||
|  | 			<div v-for="user in block" :key="user.id"> | ||||||
|  | 				<p><b>{{ user | userName }}</b> @{{ user | acct }}</p> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 	</section> | ||||||
|  | </ui-card> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  |  | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			muteFetching: true, | ||||||
|  | 			blockFetching: true, | ||||||
|  | 			mute: [], | ||||||
|  | 			block: [] | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	mounted() { | ||||||
|  | 		(this as any).api('mute/list').then(mute => { | ||||||
|  | 			this.mute = mute.map(x => x.mutee); | ||||||
|  | 			this.muteFetching = false; | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		(this as any).api('blocking/list').then(blocking => { | ||||||
|  | 			this.block = blocking.map(x => x.blockee); | ||||||
|  | 			this.blockFetching = false; | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
| @@ -2,11 +2,11 @@ | |||||||
| <header class="bvonvjxbwzaiskogyhbwgyxvcgserpmu"> | <header class="bvonvjxbwzaiskogyhbwgyxvcgserpmu"> | ||||||
| 	<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle == 'smart'"/> | 	<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle == 'smart'"/> | ||||||
| 	<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link> | 	<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link> | ||||||
| 	<span class="is-verified" v-if="note.user.isVerified" title="%i18n:common.verified-user%">%fa:star%</span> |  | ||||||
| 	<span class="is-admin" v-if="note.user.isAdmin">admin</span> | 	<span class="is-admin" v-if="note.user.isAdmin">admin</span> | ||||||
| 	<span class="is-bot" v-if="note.user.isBot">bot</span> | 	<span class="is-bot" v-if="note.user.isBot">bot</span> | ||||||
| 	<span class="is-cat" v-if="note.user.isCat">cat</span> | 	<span class="is-cat" v-if="note.user.isCat">cat</span> | ||||||
| 	<span class="username"><mk-acct :user="note.user"/></span> | 	<span class="username"><mk-acct :user="note.user"/></span> | ||||||
|  | 	<span class="is-verified" v-if="note.user.isVerified" title="%i18n:common.verified-user%">%fa:star%</span> | ||||||
| 	<div class="info"> | 	<div class="info"> | ||||||
| 		<span class="app" v-if="note.app && !mini">via <b>{{ note.app.name }}</b></span> | 		<span class="app" v-if="note.app && !mini">via <b>{{ note.app.name }}</b></span> | ||||||
| 		<span class="mobile" v-if="note.viaMobile">%fa:mobile-alt%</span> | 		<span class="mobile" v-if="note.viaMobile">%fa:mobile-alt%</span> | ||||||
| @@ -68,10 +68,6 @@ export default Vue.extend({ | |||||||
| 		&:hover | 		&:hover | ||||||
| 			text-decoration underline | 			text-decoration underline | ||||||
|  |  | ||||||
| 	> .is-verified |  | ||||||
| 		margin-right 8px |  | ||||||
| 		color #4dabf7 |  | ||||||
|  |  | ||||||
| 	> .is-admin | 	> .is-admin | ||||||
| 	> .is-bot | 	> .is-bot | ||||||
| 	> .is-cat | 	> .is-cat | ||||||
| @@ -95,6 +91,10 @@ export default Vue.extend({ | |||||||
| 		color var(--noteHeaderAcct) | 		color var(--noteHeaderAcct) | ||||||
| 		flex-shrink 2147483647 | 		flex-shrink 2147483647 | ||||||
|  |  | ||||||
|  | 	> .is-verified | ||||||
|  | 		margin 0 .5em 0 0 | ||||||
|  | 		color #4dabf7 | ||||||
|  |  | ||||||
| 	> .info | 	> .info | ||||||
| 		margin-left auto | 		margin-left auto | ||||||
| 		font-size 0.9em | 		font-size 0.9em | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								src/client/app/common/views/components/ui/info.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/client/app/common/views/components/ui/info.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | <template> | ||||||
|  | <div class="ymxyweixqwsxauxldgpvecjepnwxbylu" :class="{ warn }"> | ||||||
|  | 	<i v-if="warn">%fa:exclamation-triangle%</i> | ||||||
|  | 	<i v-else>%fa:info-circle%</i> | ||||||
|  | 	<slot></slot> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	props: { | ||||||
|  | 		warn: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false, | ||||||
|  | 			default: false | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="stylus" scoped> | ||||||
|  | .ymxyweixqwsxauxldgpvecjepnwxbylu | ||||||
|  | 	margin 16px 0 | ||||||
|  | 	padding 16px | ||||||
|  | 	font-size 90% | ||||||
|  | 	background var(--infoBg) | ||||||
|  | 	color var(--infoFg) | ||||||
|  |  | ||||||
|  | 	&.warn | ||||||
|  | 		background var(--infoWarnBg) | ||||||
|  | 		color var(--infoWarnFg) | ||||||
|  |  | ||||||
|  | 	&:first-child | ||||||
|  | 		margin-top 0 | ||||||
|  |  | ||||||
|  | 	&:last-child | ||||||
|  | 		margin-bottom 0 | ||||||
|  |  | ||||||
|  | 	> i | ||||||
|  | 		margin-right 4px | ||||||
|  |  | ||||||
|  | </style> | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| <template> | <template> | ||||||
| <div class="ui-textarea" :class="{ focused, filled }"> | <div class="ui-textarea" :class="{ focused, filled, tall }"> | ||||||
| 	<div class="input"> | 	<div class="input"> | ||||||
| 		<span class="label" ref="label"><slot></slot></span> | 		<span class="label" ref="label"><slot></slot></span> | ||||||
| 		<textarea ref="input" | 		<textarea ref="input" | ||||||
| @@ -10,8 +10,8 @@ | |||||||
| 			:autocomplete="autocomplete" | 			:autocomplete="autocomplete" | ||||||
| 			@input="$emit('input', $event.target.value)" | 			@input="$emit('input', $event.target.value)" | ||||||
| 			@focus="focused = true" | 			@focus="focused = true" | ||||||
| 				@blur="focused = false"> | 			@blur="focused = false" | ||||||
| 		</textarea> | 		></textarea> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div class="text"><slot name="text"></slot></div> | 	<div class="text"><slot name="text"></slot></div> | ||||||
| </div> | </div> | ||||||
| @@ -41,7 +41,12 @@ export default Vue.extend({ | |||||||
| 		autocomplete: { | 		autocomplete: { | ||||||
| 			type: String, | 			type: String, | ||||||
| 			required: false | 			required: false | ||||||
| 		} | 		}, | ||||||
|  | 		tall: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false, | ||||||
|  | 			default: false | ||||||
|  | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| @@ -66,6 +71,9 @@ export default Vue.extend({ | |||||||
| root(fill) | root(fill) | ||||||
| 	margin 42px 0 32px 0 | 	margin 42px 0 32px 0 | ||||||
|  |  | ||||||
|  | 	&:last-child | ||||||
|  | 		margin-bottom 0 | ||||||
|  |  | ||||||
| 	> .input | 	> .input | ||||||
| 		padding 12px | 		padding 12px | ||||||
|  |  | ||||||
| @@ -157,6 +165,11 @@ root(fill) | |||||||
| 				left 0 !important | 				left 0 !important | ||||||
| 				transform scale(0.75) | 				transform scale(0.75) | ||||||
|  |  | ||||||
|  | 	&.tall | ||||||
|  | 		> .input | ||||||
|  | 			> textarea | ||||||
|  | 				min-height 200px | ||||||
|  |  | ||||||
| .ui-textarea.fill | .ui-textarea.fill | ||||||
| 	root(true) | 	root(true) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -222,13 +222,15 @@ class Autocomplete { | |||||||
| 			const trimmedBefore = before.substring(0, before.lastIndexOf(':')); | 			const trimmedBefore = before.substring(0, before.lastIndexOf(':')); | ||||||
| 			const after = source.substr(caret); | 			const after = source.substr(caret); | ||||||
|  |  | ||||||
|  | 			if (value.startsWith(':')) value = value + ' '; | ||||||
|  |  | ||||||
| 			// 挿入 | 			// 挿入 | ||||||
| 			this.text = trimmedBefore + value + after; | 			this.text = trimmedBefore + value + after; | ||||||
|  |  | ||||||
| 			// キャレットを戻す | 			// キャレットを戻す | ||||||
| 			this.vm.$nextTick(() => { | 			this.vm.$nextTick(() => { | ||||||
| 				this.textarea.focus(); | 				this.textarea.focus(); | ||||||
| 				const pos = trimmedBefore.length + 1; | 				const pos = trimmedBefore.length + (value.startsWith(':') ? value.length : 1); | ||||||
| 				this.textarea.setSelectionRange(pos, pos); | 				this.textarea.setSelectionRange(pos, pos); | ||||||
| 			}); | 			}); | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -67,12 +67,12 @@ export default Vue.extend({ | |||||||
| 				text: '%i18n:@contextmenu.rename%', | 				text: '%i18n:@contextmenu.rename%', | ||||||
| 				icon: '%fa:i-cursor%', | 				icon: '%fa:i-cursor%', | ||||||
| 				action: this.rename | 				action: this.rename | ||||||
| 			}/*, null, { | 			}, null, { | ||||||
| 				type: 'item', | 				type: 'item', | ||||||
| 				text: '%i18n:common.delete%', | 				text: '%i18n:common.delete%', | ||||||
| 				icon: '%fa:R trash-alt%', | 				icon: '%fa:R trash-alt%', | ||||||
| 				action: this.deleteFolder | 				action: this.deleteFolder | ||||||
| 			}*/], { | 			}], { | ||||||
| 					closed: () => { | 					closed: () => { | ||||||
| 						this.isContextmenuShowing = false; | 						this.isContextmenuShowing = false; | ||||||
| 					} | 					} | ||||||
| @@ -207,7 +207,9 @@ export default Vue.extend({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		deleteFolder() { | 		deleteFolder() { | ||||||
| 			alert('not implemented yet'); | 			(this as any).api('drive/folders/delete', { | ||||||
|  | 				folderId: this.folder.id | ||||||
|  | 			}); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -98,7 +98,7 @@ export default Vue.extend({ | |||||||
| 			hierarchyFolders: [], | 			hierarchyFolders: [], | ||||||
| 			selectedFiles: [], | 			selectedFiles: [], | ||||||
| 			uploadings: [], | 			uploadings: [], | ||||||
| 			connection: null | 			connection: null, | ||||||
|  |  | ||||||
| 			/** | 			/** | ||||||
| 			 * ドロップされようとしているか | 			 * ドロップされようとしているか | ||||||
| @@ -122,6 +122,7 @@ export default Vue.extend({ | |||||||
| 		this.connection.on('fileDeleted', this.onStreamDriveFileDeleted); | 		this.connection.on('fileDeleted', this.onStreamDriveFileDeleted); | ||||||
| 		this.connection.on('folderCreated', this.onStreamDriveFolderCreated); | 		this.connection.on('folderCreated', this.onStreamDriveFolderCreated); | ||||||
| 		this.connection.on('folderUpdated', this.onStreamDriveFolderUpdated); | 		this.connection.on('folderUpdated', this.onStreamDriveFolderUpdated); | ||||||
|  | 		this.connection.on('folderDeleted', this.onStreamDriveFolderDeleted); | ||||||
|  |  | ||||||
| 		if (this.initFolder) { | 		if (this.initFolder) { | ||||||
| 			this.move(this.initFolder); | 			this.move(this.initFolder); | ||||||
| @@ -182,6 +183,10 @@ export default Vue.extend({ | |||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | 		onStreamDriveFolderDeleted(folderId) { | ||||||
|  | 			this.removeFolder(folderId); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
| 		onChangeUploaderUploads(uploads) { | 		onChangeUploaderUploads(uploads) { | ||||||
| 			this.uploadings = uploads; | 			this.uploadings = uploads; | ||||||
| 		}, | 		}, | ||||||
|   | |||||||
| @@ -40,8 +40,8 @@ export default Vue.extend({ | |||||||
|  |  | ||||||
| 	mounted() { | 	mounted() { | ||||||
| 		this.connection = (this as any).os.stream.useSharedConnection('main'); | 		this.connection = (this as any).os.stream.useSharedConnection('main'); | ||||||
| 		this.connection.on('follow', this.onFollow); | 		this.connection.on('follow', this.onFollowChange); | ||||||
| 		this.connection.on('unfollow', this.onUnfollow); | 		this.connection.on('unfollow', this.onFollowChange); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	beforeDestroy() { | 	beforeDestroy() { | ||||||
| @@ -49,17 +49,11 @@ export default Vue.extend({ | |||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	methods: { | 	methods: { | ||||||
| 		onFollow(user) { | 		onFollowChange(user) { | ||||||
| 			if (user.id == this.u.id) { |  | ||||||
| 				this.u.isFollowing = user.isFollowing; |  | ||||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
|  |  | ||||||
| 		onUnfollow(user) { |  | ||||||
| 			if (user.id == this.u.id) { | 			if (user.id == this.u.id) { | ||||||
| 				this.u.isFollowing = user.isFollowing; | 				this.u.isFollowing = user.isFollowing; | ||||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||||
|  | 				this.$forceUpdate(); | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,10 +4,7 @@ | |||||||
|  |  | ||||||
| 	<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot> | 	<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot> | ||||||
|  |  | ||||||
| 	<div v-if="!fetching && requestInitPromise != null" class="error"> | 	<mk-error v-if="!fetching && requestInitPromise != null" @retry="resolveInitPromise"/> | ||||||
| 		<p>%fa:exclamation-triangle% %i18n:common.error.title%</p> |  | ||||||
| 		<ui-button @click="resolveInitPromise">%i18n:common.error.retry%</ui-button> |  | ||||||
| 	</div> |  | ||||||
|  |  | ||||||
| 	<div class="placeholder" v-if="fetching"> | 	<div class="placeholder" v-if="fetching"> | ||||||
| 		<template v-for="i in 10"> | 		<template v-for="i in 10"> | ||||||
| @@ -215,16 +212,6 @@ export default Vue.extend({ | |||||||
| 		> * | 		> * | ||||||
| 			transition transform .3s ease, opacity .3s ease | 			transition transform .3s ease, opacity .3s ease | ||||||
|  |  | ||||||
| 	> .error |  | ||||||
| 		max-width 300px |  | ||||||
| 		margin 0 auto |  | ||||||
| 		padding 32px |  | ||||||
| 		text-align center |  | ||||||
| 		color var(--text) |  | ||||||
|  |  | ||||||
| 		> p |  | ||||||
| 			margin 0 0 8px 0 |  | ||||||
|  |  | ||||||
| 	> .placeholder | 	> .placeholder | ||||||
| 		padding 32px | 		padding 32px | ||||||
| 		opacity 0.3 | 		opacity 0.3 | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| <template> | <template> | ||||||
| <div class="2fa"> | <div class="2fa"> | ||||||
| 	<p>%i18n:@intro%<a href="%i18n:@url%" target="_blank">%i18n:@detail%</a></p> | 	<p style="margin-top:0;">%i18n:@intro%<a href="%i18n:@url%" target="_blank">%i18n:@detail%</a></p> | ||||||
| 	<div class="ui info warn"><p>%fa:exclamation-triangle%%i18n:@caution%</p></div> | 	<ui-info warn>%i18n:@caution%</ui-info> | ||||||
| 	<p v-if="!data && !$store.state.i.twoFactorEnabled"><ui-button @click="register">%i18n:@register%</ui-button></p> | 	<p v-if="!data && !$store.state.i.twoFactorEnabled"><ui-button @click="register">%i18n:@register%</ui-button></p> | ||||||
| 	<template v-if="$store.state.i.twoFactorEnabled"> | 	<template v-if="$store.state.i.twoFactorEnabled"> | ||||||
| 		<p>%i18n:@already-registered%</p> | 		<p>%i18n:@already-registered%</p> | ||||||
| @@ -72,9 +72,3 @@ export default Vue.extend({ | |||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="stylus" scoped> |  | ||||||
| .2fa |  | ||||||
| 	color #4a535a |  | ||||||
|  |  | ||||||
| </style> |  | ||||||
|   | |||||||
| @@ -1,40 +0,0 @@ | |||||||
| <template> |  | ||||||
| <div class="root api"> |  | ||||||
| 	<ui-input :value="$store.state.i.token" readonly> |  | ||||||
| 		<span>%i18n:@token%</span> |  | ||||||
| 	</ui-input> |  | ||||||
| 	<p>%i18n:@intro%</p> |  | ||||||
| 	<div class="ui info warn"><p>%fa:exclamation-triangle%%i18n:@caution%</p></div> |  | ||||||
| 	<p>%i18n:@regeneration-of-token%</p> |  | ||||||
| 	<ui-button @click="regenerateToken">%i18n:@regenerate-token%</ui-button> |  | ||||||
| </div> |  | ||||||
| </template> |  | ||||||
|  |  | ||||||
| <script lang="ts"> |  | ||||||
| import Vue from 'vue'; |  | ||||||
|  |  | ||||||
| export default Vue.extend({ |  | ||||||
| 	methods: { |  | ||||||
| 		regenerateToken() { |  | ||||||
| 			(this as any).apis.input({ |  | ||||||
| 				title: '%i18n:@enter-password%', |  | ||||||
| 				type: 'password' |  | ||||||
| 			}).then(password => { |  | ||||||
| 				(this as any).api('i/regenerate_token', { |  | ||||||
| 					password: password |  | ||||||
| 				}); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <style lang="stylus" scoped> |  | ||||||
| .root.api |  | ||||||
| 	code |  | ||||||
| 		display inline-block |  | ||||||
| 		padding 4px 6px |  | ||||||
| 		color #555 |  | ||||||
| 		background #eee |  | ||||||
| 		border-radius 2px |  | ||||||
| </style> |  | ||||||
| @@ -1,8 +1,6 @@ | |||||||
| <template> | <template> | ||||||
| <div class="root"> | <div class="root"> | ||||||
| 	<div class="none ui info" v-if="!fetching && apps.length == 0"> | 	<ui-info v-if="!fetching && apps.length == 0">%i18n:@no-apps%</ui-info> | ||||||
| 		<p>%fa:info-circle%%i18n:@no-apps%</p> |  | ||||||
| 	</div> |  | ||||||
| 	<div class="apps" v-if="apps.length != 0"> | 	<div class="apps" v-if="apps.length != 0"> | ||||||
| 		<div v-for="app in apps"> | 		<div v-for="app in apps"> | ||||||
| 			<p><b>{{ app.name }}</b></p> | 			<p><b>{{ app.name }}</b></p> | ||||||
|   | |||||||
| @@ -1,31 +0,0 @@ | |||||||
| <template> |  | ||||||
| <div> |  | ||||||
| 	<div class="none ui info" v-if="!fetching && users.length == 0"> |  | ||||||
| 		<p>%fa:info-circle%%i18n:@no-users%</p> |  | ||||||
| 	</div> |  | ||||||
| 	<div class="users" v-if="users.length != 0"> |  | ||||||
| 		<div v-for="user in users" :key="user.id"> |  | ||||||
| 			<p><b>{{ user | userName }}</b> @{{ user | acct }}</p> |  | ||||||
| 		</div> |  | ||||||
| 	</div> |  | ||||||
| </div> |  | ||||||
| </template> |  | ||||||
|  |  | ||||||
| <script lang="ts"> |  | ||||||
| import Vue from 'vue'; |  | ||||||
|  |  | ||||||
| export default Vue.extend({ |  | ||||||
| 	data() { |  | ||||||
| 		return { |  | ||||||
| 			fetching: true, |  | ||||||
| 			users: [] |  | ||||||
| 		}; |  | ||||||
| 	}, |  | ||||||
| 	mounted() { |  | ||||||
| 		(this as any).api('mute/list').then(x => { |  | ||||||
| 			this.users = x.users; |  | ||||||
| 			this.fetching = false; |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
| </script> |  | ||||||
| @@ -7,7 +7,7 @@ | |||||||
| 		<p :class="{ active: page == 'notification' }" @mousedown="page = 'notification'">%fa:R bell .fw%%i18n:@notification%</p> | 		<p :class="{ active: page == 'notification' }" @mousedown="page = 'notification'">%fa:R bell .fw%%i18n:@notification%</p> | ||||||
| 		<p :class="{ active: page == 'drive' }" @mousedown="page = 'drive'">%fa:cloud .fw%%i18n:common.drive%</p> | 		<p :class="{ active: page == 'drive' }" @mousedown="page = 'drive'">%fa:cloud .fw%%i18n:common.drive%</p> | ||||||
| 		<p :class="{ active: page == 'hashtags' }" @mousedown="page = 'hashtags'">%fa:hashtag .fw%%i18n:@tags%</p> | 		<p :class="{ active: page == 'hashtags' }" @mousedown="page = 'hashtags'">%fa:hashtag .fw%%i18n:@tags%</p> | ||||||
| 		<p :class="{ active: page == 'mute' }" @mousedown="page = 'mute'">%fa:ban .fw%%i18n:@mute%</p> | 		<p :class="{ active: page == 'muteAndBlock' }" @mousedown="page = 'muteAndBlock'">%fa:ban .fw%%i18n:@mute-and-block%</p> | ||||||
| 		<p :class="{ active: page == 'apps' }" @mousedown="page = 'apps'">%fa:puzzle-piece .fw%%i18n:@apps%</p> | 		<p :class="{ active: page == 'apps' }" @mousedown="page = 'apps'">%fa:puzzle-piece .fw%%i18n:@apps%</p> | ||||||
| 		<p :class="{ active: page == 'security' }" @mousedown="page = 'security'">%fa:unlock-alt .fw%%i18n:@security%</p> | 		<p :class="{ active: page == 'security' }" @mousedown="page = 'security'">%fa:unlock-alt .fw%%i18n:@security%</p> | ||||||
| 		<p :class="{ active: page == 'api' }" @mousedown="page = 'api'">%fa:key .fw%API</p> | 		<p :class="{ active: page == 'api' }" @mousedown="page = 'api'">%fa:key .fw%API</p> | ||||||
| @@ -200,12 +200,9 @@ | |||||||
| 			</section> | 			</section> | ||||||
| 		</ui-card> | 		</ui-card> | ||||||
|  |  | ||||||
| 		<ui-card class="mute" v-show="page == 'mute'"> | 		<div class="muteAndBlock" v-show="page == 'muteAndBlock'"> | ||||||
| 			<div slot="title">%fa:ban% %i18n:@mute%</div> | 			<mk-mute-and-block/> | ||||||
| 			<section> | 		</div> | ||||||
| 				<x-mute/> |  | ||||||
| 			</section> |  | ||||||
| 		</ui-card> |  | ||||||
|  |  | ||||||
| 		<ui-card class="apps" v-show="page == 'apps'"> | 		<ui-card class="apps" v-show="page == 'apps'"> | ||||||
| 			<div slot="title">%fa:puzzle-piece% %i18n:@apps%</div> | 			<div slot="title">%fa:puzzle-piece% %i18n:@apps%</div> | ||||||
| @@ -235,12 +232,9 @@ | |||||||
| 			</section> | 			</section> | ||||||
| 		</ui-card> | 		</ui-card> | ||||||
|  |  | ||||||
| 		<ui-card class="api" v-show="page == 'api'"> | 		<div class="api" v-show="page == 'api'"> | ||||||
| 			<div slot="title">%fa:key% API</div> | 			<mk-api-settings/> | ||||||
| 			<section class="fit-top"> | 		</div> | ||||||
| 				<x-api/> |  | ||||||
| 			</section> |  | ||||||
| 		</ui-card> |  | ||||||
|  |  | ||||||
| 		<ui-card class="other" v-show="page == 'other'"> | 		<ui-card class="other" v-show="page == 'other'"> | ||||||
| 			<div slot="title">%fa:info-circle% %i18n:@about%</div> | 			<div slot="title">%fa:info-circle% %i18n:@about%</div> | ||||||
| @@ -292,10 +286,8 @@ | |||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import XMute from './settings.mute.vue'; |  | ||||||
| import XPassword from './settings.password.vue'; | import XPassword from './settings.password.vue'; | ||||||
| import X2fa from './settings.2fa.vue'; | import X2fa from './settings.2fa.vue'; | ||||||
| import XApi from './settings.api.vue'; |  | ||||||
| import XApps from './settings.apps.vue'; | import XApps from './settings.apps.vue'; | ||||||
| import XSignins from './settings.signins.vue'; | import XSignins from './settings.signins.vue'; | ||||||
| import XTags from './settings.tags.vue'; | import XTags from './settings.tags.vue'; | ||||||
| @@ -304,10 +296,8 @@ import checkForUpdate from '../../../common/scripts/check-for-update'; | |||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	components: { | 	components: { | ||||||
| 		XMute, |  | ||||||
| 		XPassword, | 		XPassword, | ||||||
| 		X2fa, | 		X2fa, | ||||||
| 		XApi, |  | ||||||
| 		XApps, | 		XApps, | ||||||
| 		XSignins, | 		XSignins, | ||||||
| 		XTags | 		XTags | ||||||
|   | |||||||
| @@ -287,7 +287,7 @@ export default Vue.extend({ | |||||||
|  |  | ||||||
| 			e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; | 			e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; | ||||||
|  |  | ||||||
| 			if (!this.dragging) this.draghover = true; | 			if (!this.dragging && isDeckColumn) this.draghover = true; | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		onDragleave() { | 		onDragleave() { | ||||||
|   | |||||||
| @@ -8,10 +8,7 @@ | |||||||
| 		</template> | 		</template> | ||||||
| 	</div> | 	</div> | ||||||
|  |  | ||||||
| 	<div v-if="!fetching && requestInitPromise != null" class="error"> | 	<mk-error v-if="!fetching && requestInitPromise != null" @retry="resolveInitPromise"/> | ||||||
| 		<p>%fa:exclamation-triangle% %i18n:common.error.title%</p> |  | ||||||
| 		<ui-button @click="resolveInitPromise">%i18n:common.error.retry%</ui-button> |  | ||||||
| 	</div> |  | ||||||
|  |  | ||||||
| 	<!-- トランジションを有効にするとなぜかメモリリークする --> | 	<!-- トランジションを有効にするとなぜかメモリリークする --> | ||||||
| 	<!--<transition-group name="mk-notes" class="transition" ref="notes">--> | 	<!--<transition-group name="mk-notes" class="transition" ref="notes">--> | ||||||
| @@ -221,13 +218,6 @@ export default Vue.extend({ | |||||||
| 		> * | 		> * | ||||||
| 			transition transform .3s ease, opacity .3s ease | 			transition transform .3s ease, opacity .3s ease | ||||||
|  |  | ||||||
| 	> .error |  | ||||||
| 		max-width 300px |  | ||||||
| 		margin 0 auto |  | ||||||
| 		padding 16px |  | ||||||
| 		text-align center |  | ||||||
| 		color var(--text) |  | ||||||
|  |  | ||||||
| 	> .placeholder | 	> .placeholder | ||||||
| 		padding 16px | 		padding 16px | ||||||
| 		opacity 0.3 | 		opacity 0.3 | ||||||
|   | |||||||
| @@ -11,7 +11,11 @@ | |||||||
| 	<div class="action-form"> | 	<div class="action-form"> | ||||||
| 		<ui-button @click="user.isMuted ? unmute() : mute()" v-if="$store.state.i.id != user.id"> | 		<ui-button @click="user.isMuted ? unmute() : mute()" v-if="$store.state.i.id != user.id"> | ||||||
| 			<span v-if="user.isMuted">%fa:eye% %i18n:@unmute%</span> | 			<span v-if="user.isMuted">%fa:eye% %i18n:@unmute%</span> | ||||||
| 			<span v-if="!user.isMuted">%fa:eye-slash% %i18n:@mute%</span> | 			<span v-else>%fa:eye-slash% %i18n:@mute%</span> | ||||||
|  | 		</ui-button> | ||||||
|  | 		<ui-button @click="user.isBlocking ? unblock() : block()" v-if="$store.state.i.id != user.id"> | ||||||
|  | 			<span v-if="user.isBlocking">%fa:user% %i18n:@unblock%</span> | ||||||
|  | 			<span v-else>%fa:user-slash% %i18n:@block%</span> | ||||||
| 		</ui-button> | 		</ui-button> | ||||||
| 		<ui-button @click="list">%fa:list% %i18n:@push-to-a-list%</ui-button> | 		<ui-button @click="list">%fa:list% %i18n:@push-to-a-list%</ui-button> | ||||||
| 	</div> | 	</div> | ||||||
| @@ -66,6 +70,27 @@ export default Vue.extend({ | |||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | 		block() { | ||||||
|  | 			if (!window.confirm('%i18n:@block-confirm%')) return; | ||||||
|  | 			(this as any).api('blocking/create', { | ||||||
|  | 				userId: this.user.id | ||||||
|  | 			}).then(() => { | ||||||
|  | 				this.user.isBlocking = true; | ||||||
|  | 			}, () => { | ||||||
|  | 				alert('error'); | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		unblock() { | ||||||
|  | 			(this as any).api('blocking/delete', { | ||||||
|  | 				userId: this.user.id | ||||||
|  | 			}).then(() => { | ||||||
|  | 				this.user.isBlocking = false; | ||||||
|  | 			}, () => { | ||||||
|  | 				alert('error'); | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
| 		list() { | 		list() { | ||||||
| 			const w = (this as any).os.new(MkUserListsWindow); | 			const w = (this as any).os.new(MkUserListsWindow); | ||||||
| 			w.$once('choosen', async list => { | 			w.$once('choosen', async list => { | ||||||
| @@ -114,7 +139,6 @@ export default Vue.extend({ | |||||||
| 	> .action-form | 	> .action-form | ||||||
| 		padding 16px | 		padding 16px | ||||||
| 		text-align center | 		text-align center | ||||||
| 		border-bottom solid 1px var(--faceDivider) |  | ||||||
|  |  | ||||||
| 		> * | 		> * | ||||||
| 			width 100% | 			width 100% | ||||||
|   | |||||||
| @@ -510,6 +510,14 @@ export default class MiOS extends EventEmitter { | |||||||
| 		return promise; | 		return promise; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Misskeyのメタ情報を取得します | ||||||
|  | 	 */ | ||||||
|  | 	@autobind | ||||||
|  | 	public getMetaSync() { | ||||||
|  | 		return this.meta ? this.meta.data : null; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * Misskeyのメタ情報を取得します | 	 * Misskeyのメタ情報を取得します | ||||||
| 	 * @param force キャッシュを無視するか否か | 	 * @param force キャッシュを無視するか否か | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ | |||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	props: { | 	props: { | ||||||
| 		user: { | 		user: { | ||||||
| @@ -24,6 +25,7 @@ export default Vue.extend({ | |||||||
| 			required: true | 			required: true | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			u: this.user, | 			u: this.user, | ||||||
| @@ -31,28 +33,24 @@ export default Vue.extend({ | |||||||
| 			connection: null | 			connection: null | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	mounted() { | 	mounted() { | ||||||
| 		this.connection = (this as any).os.stream.useSharedConnection('main'); | 		this.connection = (this as any).os.stream.useSharedConnection('main'); | ||||||
|  |  | ||||||
| 		this.connection.on('follow', this.onFollow); | 		this.connection.on('follow', this.onFollowChange); | ||||||
| 		this.connection.on('unfollow', this.onUnfollow); | 		this.connection.on('unfollow', this.onFollowChange); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	beforeDestroy() { | 	beforeDestroy() { | ||||||
| 		this.connection.dispose(); | 		this.connection.dispose(); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	methods: { | 	methods: { | ||||||
|  | 		onFollowChange(user) { | ||||||
| 		onFollow(user) { |  | ||||||
| 			if (user.id == this.u.id) { |  | ||||||
| 				this.u.isFollowing = user.isFollowing; |  | ||||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
|  |  | ||||||
| 		onUnfollow(user) { |  | ||||||
| 			if (user.id == this.u.id) { | 			if (user.id == this.u.id) { | ||||||
| 				this.u.isFollowing = user.isFollowing; | 				this.u.isFollowing = user.isFollowing; | ||||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||||
|  | 				this.$forceUpdate(); | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| @@ -90,8 +88,6 @@ export default Vue.extend({ | |||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="stylus" scoped> | <style lang="stylus" scoped> | ||||||
|  |  | ||||||
|  |  | ||||||
| .mk-follow-button | .mk-follow-button | ||||||
| 	display block | 	display block | ||||||
| 	user-select none | 	user-select none | ||||||
|   | |||||||
| @@ -12,7 +12,6 @@ import noteCard from './note-card.vue'; | |||||||
| import userCard from './user-card.vue'; | import userCard from './user-card.vue'; | ||||||
| import noteDetail from './note-detail.vue'; | import noteDetail from './note-detail.vue'; | ||||||
| import followButton from './follow-button.vue'; | import followButton from './follow-button.vue'; | ||||||
| import muteButton from './mute-button.vue'; |  | ||||||
| import friendsMaker from './friends-maker.vue'; | import friendsMaker from './friends-maker.vue'; | ||||||
| import notification from './notification.vue'; | import notification from './notification.vue'; | ||||||
| import notifications from './notifications.vue'; | import notifications from './notifications.vue'; | ||||||
| @@ -37,7 +36,6 @@ Vue.component('mk-note-card', noteCard); | |||||||
| Vue.component('mk-user-card', userCard); | Vue.component('mk-user-card', userCard); | ||||||
| Vue.component('mk-note-detail', noteDetail); | Vue.component('mk-note-detail', noteDetail); | ||||||
| Vue.component('mk-follow-button', followButton); | Vue.component('mk-follow-button', followButton); | ||||||
| Vue.component('mk-mute-button', muteButton); |  | ||||||
| Vue.component('mk-friends-maker', friendsMaker); | Vue.component('mk-friends-maker', friendsMaker); | ||||||
| Vue.component('mk-notification', notification); | Vue.component('mk-notification', notification); | ||||||
| Vue.component('mk-notifications', notifications); | Vue.component('mk-notifications', notifications); | ||||||
|   | |||||||
| @@ -1,79 +0,0 @@ | |||||||
| <template> |  | ||||||
| <button |  | ||||||
|   class="mk-mute-button" |  | ||||||
|   :class="{ active: user.isMuted }" |  | ||||||
|   @click="onClick"> |  | ||||||
|   <span v-if="!user.isMuted">%fa:eye-slash% %i18n:@mute%</span> |  | ||||||
|   <span v-else>%fa:eye% %i18n:@unmute%</span> |  | ||||||
| </button> |  | ||||||
| </template> |  | ||||||
|  |  | ||||||
| <script lang="ts"> |  | ||||||
| import Vue from 'vue' |  | ||||||
| export default Vue.extend({ |  | ||||||
|   props: { |  | ||||||
|     user: { |  | ||||||
|       type: Object, |  | ||||||
|       required: true |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
|   methods: { |  | ||||||
|     onClick() { |  | ||||||
|       if (!this.user.isMuted) { |  | ||||||
|         this.mute(); |  | ||||||
|       } else { |  | ||||||
|         this.unmute(); |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     mute() { |  | ||||||
|       (this as any).api('mute/create', { userId: this.user.id}) |  | ||||||
|         .then(() => { this.user.isMuted = true }) |  | ||||||
|         .catch(() => { alert('error')}) |  | ||||||
|     }, |  | ||||||
|     unmute() { |  | ||||||
|       (this as any).api('mute/delete', { userId: this.user.id }) |  | ||||||
|         .then(() => { this.user.isMuted = false }) |  | ||||||
|         .catch(() => { alert('error') }) |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
| }) |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
|  |  | ||||||
| <style lang="stylus" scoped> |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .mk-mute-button |  | ||||||
|   display block |  | ||||||
|   user-select none |  | ||||||
|   cursor pointer |  | ||||||
|   padding 0 16px |  | ||||||
|   margin 0 |  | ||||||
|   min-width 100px |  | ||||||
|   line-height 36px |  | ||||||
|   font-size 14px |  | ||||||
|   font-weight bold |  | ||||||
|   color var(--primary) |  | ||||||
|   background transparent |  | ||||||
|   outline none |  | ||||||
|   border solid 1px var(--primary) |  | ||||||
|   border-radius 36px |  | ||||||
|  |  | ||||||
|   &:hover |  | ||||||
|     background var(--primaryAlpha01) |  | ||||||
|  |  | ||||||
|   &:active |  | ||||||
|     background var(--primaryAlpha02) |  | ||||||
|  |  | ||||||
|   &.active |  | ||||||
|     color var(--primaryForeground) |  | ||||||
|     background var(--primary) |  | ||||||
|  |  | ||||||
|     &:hover |  | ||||||
|       background var(--primaryLighten10) |  | ||||||
|       border-color var(--primaryLighten10) |  | ||||||
|     &:active |  | ||||||
|       background var(--primaryDarken10) |  | ||||||
|       border-color var(--primaryDarken10) |  | ||||||
|  |  | ||||||
| </style> |  | ||||||
| @@ -26,6 +26,9 @@ | |||||||
| 					<ui-switch v-model="iLikeSushi">%i18n:common.i-like-sushi%</ui-switch> | 					<ui-switch v-model="iLikeSushi">%i18n:common.i-like-sushi%</ui-switch> | ||||||
| 					<ui-switch v-model="disableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch> | 					<ui-switch v-model="disableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch> | ||||||
| 					<ui-switch v-model="alwaysShowNsfw">%i18n:common.always-show-nsfw% (%i18n:common.this-setting-is-this-device-only%)</ui-switch> | 					<ui-switch v-model="alwaysShowNsfw">%i18n:common.always-show-nsfw% (%i18n:common.this-setting-is-this-device-only%)</ui-switch> | ||||||
|  | 				</section> | ||||||
|  |  | ||||||
|  | 				<section> | ||||||
| 					<ui-switch v-model="games_reversi_showBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch> | 					<ui-switch v-model="games_reversi_showBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch> | ||||||
| 					<ui-switch v-model="games_reversi_useContrastStones">%i18n:common.use-contrast-reversi-stones%</ui-switch> | 					<ui-switch v-model="games_reversi_useContrastStones">%i18n:common.use-contrast-reversi-stones%</ui-switch> | ||||||
| 				</section> | 				</section> | ||||||
| @@ -82,6 +85,8 @@ | |||||||
|  |  | ||||||
| 			<mk-drive-settings/> | 			<mk-drive-settings/> | ||||||
|  |  | ||||||
|  | 			<mk-mute-and-block/> | ||||||
|  |  | ||||||
| 			<ui-card> | 			<ui-card> | ||||||
| 				<div slot="title">%fa:volume-up% %i18n:@sound%</div> | 				<div slot="title">%fa:volume-up% %i18n:@sound%</div> | ||||||
|  |  | ||||||
| @@ -120,6 +125,8 @@ | |||||||
| 				</section> | 				</section> | ||||||
| 			</ui-card> | 			</ui-card> | ||||||
|  |  | ||||||
|  | 			<mk-api-settings /> | ||||||
|  |  | ||||||
| 			<ui-card> | 			<ui-card> | ||||||
| 				<div slot="title">%fa:sync-alt% %i18n:@update%</div> | 				<div slot="title">%fa:sync-alt% %i18n:@update%</div> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
| 					<a class="avatar"> | 					<a class="avatar"> | ||||||
| 						<img :src="user.avatarUrl" alt="avatar"/> | 						<img :src="user.avatarUrl" alt="avatar"/> | ||||||
| 					</a> | 					</a> | ||||||
| 					<mk-mute-button v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/> | 					<button class="menu" ref="menu" @click="menu">%fa:ellipsis-h%</button> | ||||||
| 					<mk-follow-button v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/> | 					<mk-follow-button v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/> | ||||||
| 				</div> | 				</div> | ||||||
| 				<div class="title"> | 				<div class="title"> | ||||||
| @@ -67,6 +67,7 @@ import Vue from 'vue'; | |||||||
| import * as age from 's-age'; | import * as age from 's-age'; | ||||||
| import parseAcct from '../../../../../misc/acct/parse'; | import parseAcct from '../../../../../misc/acct/parse'; | ||||||
| import Progress from '../../../common/scripts/loading'; | import Progress from '../../../common/scripts/loading'; | ||||||
|  | import Menu from '../../../common/views/components/menu.vue'; | ||||||
| import XHome from './user/home.vue'; | import XHome from './user/home.vue'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| @@ -109,8 +110,62 @@ export default Vue.extend({ | |||||||
| 				Progress.done(); | 				Progress.done(); | ||||||
| 				document.title = `${Vue.filter('userName')(this.user)} | ${(this as any).os.instanceName}`; | 				document.title = `${Vue.filter('userName')(this.user)} | ${(this as any).os.instanceName}`; | ||||||
| 			}); | 			}); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		menu() { | ||||||
|  | 			let menu = [{ | ||||||
|  | 				icon: this.user.isMuted ? '%fa:eye%' : '%fa:eye-slash%', | ||||||
|  | 				text: this.user.isMuted ? '%i18n:@unmute%' : '%i18n:@mute%', | ||||||
|  | 				action: () => { | ||||||
|  | 					if (this.user.isMuted) { | ||||||
|  | 						(this as any).api('mute/delete', { | ||||||
|  | 							userId: this.user.id | ||||||
|  | 						}).then(() => { | ||||||
|  | 							this.user.isMuted = false; | ||||||
|  | 						}, () => { | ||||||
|  | 							alert('error'); | ||||||
|  | 						}); | ||||||
|  | 					} else { | ||||||
|  | 						(this as any).api('mute/create', { | ||||||
|  | 							userId: this.user.id | ||||||
|  | 						}).then(() => { | ||||||
|  | 							this.user.isMuted = true; | ||||||
|  | 						}, () => { | ||||||
|  | 							alert('error'); | ||||||
|  | 						}); | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  | 			}, { | ||||||
|  | 				icon: this.user.isBlocking ? '%fa:user%' : '%fa:user-slash%', | ||||||
|  | 				text: this.user.isBlocking ? '%i18n:@unblock%' : '%i18n:@block%', | ||||||
|  | 				action: () => { | ||||||
|  | 					if (this.user.isBlocking) { | ||||||
|  | 						(this as any).api('blocking/delete', { | ||||||
|  | 							userId: this.user.id | ||||||
|  | 						}).then(() => { | ||||||
|  | 							this.user.isBlocking = false; | ||||||
|  | 						}, () => { | ||||||
|  | 							alert('error'); | ||||||
|  | 						}); | ||||||
|  | 					} else { | ||||||
|  | 						(this as any).api('blocking/create', { | ||||||
|  | 							userId: this.user.id | ||||||
|  | 						}).then(() => { | ||||||
|  | 							this.user.isBlocking = true; | ||||||
|  | 						}, () => { | ||||||
|  | 							alert('error'); | ||||||
|  | 						}); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			}]; | ||||||
|  |  | ||||||
|  | 			this.os.new(Menu, { | ||||||
|  | 				source: this.$refs.menu, | ||||||
|  | 				compact: true, | ||||||
|  | 				items: menu | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| @@ -156,14 +211,10 @@ main | |||||||
| 			max-width 600px | 			max-width 600px | ||||||
|  |  | ||||||
| 			> .top | 			> .top | ||||||
| 				&:after | 				display flex | ||||||
| 					content '' |  | ||||||
| 					display block |  | ||||||
| 					clear both |  | ||||||
|  |  | ||||||
| 				> .avatar | 				> .avatar | ||||||
| 					display block | 					display block | ||||||
| 					float left |  | ||||||
| 					width 25% | 					width 25% | ||||||
| 					height 40px | 					height 40px | ||||||
|  |  | ||||||
| @@ -183,11 +234,15 @@ main | |||||||
| 							border 4px solid $bg | 							border 4px solid $bg | ||||||
| 							border-radius 12px | 							border-radius 12px | ||||||
|  |  | ||||||
| 				> .mk-mute-button | 				> .menu | ||||||
| 					float right | 					margin 0 0 0 auto | ||||||
|  | 					padding 8px | ||||||
|  | 					margin-right 8px | ||||||
|  | 					font-size 18px | ||||||
|  | 					color var(--text) | ||||||
|  |  | ||||||
| 				> .mk-follow-button | 				> .mk-follow-button | ||||||
| 					float right | 					margin 0 | ||||||
|  |  | ||||||
| 			> .title | 			> .title | ||||||
| 				margin 8px 0 | 				margin 8px 0 | ||||||
|   | |||||||
| @@ -131,6 +131,11 @@ | |||||||
| 		remoteInfoBg: '#42321c', | 		remoteInfoBg: '#42321c', | ||||||
| 		remoteInfoFg: '#ffbd3e', | 		remoteInfoFg: '#ffbd3e', | ||||||
|  |  | ||||||
|  | 		infoBg: '#253142', | ||||||
|  | 		infoFg: '#fff', | ||||||
|  | 		infoWarnBg: '#42321c', | ||||||
|  | 		infoWarnFg: '#ffbd3e', | ||||||
|  |  | ||||||
| 		messagingRoomBg: '@bg', | 		messagingRoomBg: '@bg', | ||||||
| 		messagingRoomInfo: '#fff', | 		messagingRoomInfo: '#fff', | ||||||
| 		messagingRoomDateDividerLine: 'rgba(255, 255, 255, 0.1)', | 		messagingRoomDateDividerLine: 'rgba(255, 255, 255, 0.1)', | ||||||
|   | |||||||
| @@ -131,6 +131,11 @@ | |||||||
| 		remoteInfoBg: '#fff0db', | 		remoteInfoBg: '#fff0db', | ||||||
| 		remoteInfoFg: '#573c08', | 		remoteInfoFg: '#573c08', | ||||||
|  |  | ||||||
|  | 		infoBg: '#e5f5ff', | ||||||
|  | 		infoFg: '#72818a', | ||||||
|  | 		infoWarnBg: '#fff0db', | ||||||
|  | 		infoWarnFg: '#573c08', | ||||||
|  |  | ||||||
| 		messagingRoomBg: '#fff', | 		messagingRoomBg: '#fff', | ||||||
| 		messagingRoomInfo: '#000', | 		messagingRoomInfo: '#000', | ||||||
| 		messagingRoomDateDividerLine: 'rgba(0, 0, 0, 0.1)', | 		messagingRoomDateDividerLine: 'rgba(0, 0, 0, 0.1)', | ||||||
|   | |||||||
| @@ -14,11 +14,13 @@ export type Source = { | |||||||
| 		 * メンテナの連絡先(URLかmailto形式のURL) | 		 * メンテナの連絡先(URLかmailto形式のURL) | ||||||
| 		 */ | 		 */ | ||||||
| 		url: string; | 		url: string; | ||||||
|  | 		email?: string; | ||||||
| 		repository_url?: string; | 		repository_url?: string; | ||||||
| 		feedback_url?: string; | 		feedback_url?: string; | ||||||
| 	}; | 	}; | ||||||
| 	name?: string; | 	name?: string; | ||||||
| 	description?: string; | 	description?: string; | ||||||
|  | 	languages?: string[]; | ||||||
| 	welcome_bg_url?: string; | 	welcome_bg_url?: string; | ||||||
| 	url: string; | 	url: string; | ||||||
| 	port: number; | 	port: number; | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
|  |  | ||||||
| 以下のURLに、`i`というパラメータ名で認証情報を含めて、websocket接続してください。例: | 以下のURLに、`i`というパラメータ名で認証情報を含めて、websocket接続してください。例: | ||||||
| ``` | ``` | ||||||
| %URL%/streaming?i=xxxxxxxxxxxxxxx | %WS_URL%/streaming?i=xxxxxxxxxxxxxxx | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| 認証情報は、自分のAPIキーや、アプリケーションからストリームに接続する際はユーザーのアクセストークンのことを指します。 | 認証情報は、自分のAPIキーや、アプリケーションからストリームに接続する際はユーザーのアクセストークンのことを指します。 | ||||||
| @@ -22,7 +22,7 @@ | |||||||
| 認証情報は省略することもできますが、その場合非ログインでの利用ということになり、受信できる情報や可能な操作は限られます。例: | 認証情報は省略することもできますが、その場合非ログインでの利用ということになり、受信できる情報や可能な操作は限られます。例: | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| %URL%/streaming | %WS_URL%/streaming | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| --- | --- | ||||||
|   | |||||||
| @@ -9,9 +9,9 @@ export type TextElementHashtag = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| export default function(text: string, i: number) { | export default function(text: string, i: number) { | ||||||
| 	if (!(/^\s#[^\s\.,!\?]+/.test(text) || (i == 0 && /^#[^\s\.,!\?]+/.test(text)))) return null; | 	if (!(/^\s#[^\s\.,!\?#]+/.test(text) || (i == 0 && /^#[^\s\.,!\?#]+/.test(text)))) return null; | ||||||
| 	const isHead = text.startsWith('#'); | 	const isHead = text.startsWith('#'); | ||||||
| 	const hashtag = text.match(/^\s?#[^\s\.,!\?]+/)[0]; | 	const hashtag = text.match(/^\s?#[^\s\.,!\?#]+/)[0]; | ||||||
| 	const res: any[] = !isHead ? [{ | 	const res: any[] = !isHead ? [{ | ||||||
| 		type: 'text', | 		type: 'text', | ||||||
| 		content: text[0] | 		content: text[0] | ||||||
|   | |||||||
| @@ -17,7 +17,8 @@ export default function(text: string, index: number) { | |||||||
| 	const quote = match[1] | 	const quote = match[1] | ||||||
| 		.split('\n') | 		.split('\n') | ||||||
| 		.map(line => line.replace(/^>+/g, '').trim()) | 		.map(line => line.replace(/^>+/g, '').trim()) | ||||||
| 		.join('\n'); | 		.join('\n') | ||||||
|  | 		.trim(); | ||||||
|  |  | ||||||
| 	return { | 	return { | ||||||
| 		type: 'quote', | 		type: 'quote', | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								src/misc/get-drive-file-url.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/misc/get-drive-file-url.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | import { IDriveFile } from '../models/drive-file'; | ||||||
|  | import config from '../config'; | ||||||
|  |  | ||||||
|  | export default function(file: IDriveFile, thumbnail = false): string { | ||||||
|  | 	if (file == null) return null; | ||||||
|  |  | ||||||
|  | 	if (file.metadata.withoutChunks) { | ||||||
|  | 		if (thumbnail) { | ||||||
|  | 			return file.metadata.thumbnailUrl || file.metadata.url; | ||||||
|  | 		} else { | ||||||
|  | 			return file.metadata.url; | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		if (thumbnail) { | ||||||
|  | 			return `${config.drive_url}/${file._id}?thumbnail`; | ||||||
|  | 		} else { | ||||||
|  | 			return `${config.drive_url}/${file._id}`; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -1,6 +1,5 @@ | |||||||
| import * as mongo from 'mongodb'; | import * as mongo from 'mongodb'; | ||||||
| import db from '../db/mongodb'; | import db from '../db/mongodb'; | ||||||
| import isObjectId from '../misc/is-objectid'; |  | ||||||
|  |  | ||||||
| const AccessToken = db.get<IAccessToken>('accessTokens'); | const AccessToken = db.get<IAccessToken>('accessTokens'); | ||||||
| AccessToken.createIndex('token'); | AccessToken.createIndex('token'); | ||||||
| @@ -15,30 +14,3 @@ export type IAccessToken = { | |||||||
| 	token: string; | 	token: string; | ||||||
| 	hash: string; | 	hash: string; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * AccessTokenを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteAccessToken(accessToken: string | mongo.ObjectID | IAccessToken) { |  | ||||||
| 	let a: IAccessToken; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(accessToken)) { |  | ||||||
| 		a = await AccessToken.findOne({ |  | ||||||
| 			_id: accessToken |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof accessToken === 'string') { |  | ||||||
| 		a = await AccessToken.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(accessToken) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		a = accessToken as IAccessToken; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (a == null) return; |  | ||||||
|  |  | ||||||
| 	// このAccessTokenを削除 |  | ||||||
| 	await AccessToken.remove({ |  | ||||||
| 		_id: a._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -22,27 +22,28 @@ export type IApp = { | |||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Pack an app for API response |  * Pack an app for API response | ||||||
|  * |  | ||||||
|  * @param {any} app |  | ||||||
|  * @param {any} me? |  | ||||||
|  * @param {any} options? |  | ||||||
|  * @return {Promise<any>} |  | ||||||
|  */ |  */ | ||||||
| export const pack = ( | export const pack = ( | ||||||
| 	app: any, | 	app: any, | ||||||
| 	me?: any, | 	me?: any, | ||||||
| 	options?: { | 	options?: { | ||||||
|  | 		detail?: boolean, | ||||||
| 		includeSecret?: boolean, | 		includeSecret?: boolean, | ||||||
| 		includeProfileImageIds?: boolean | 		includeProfileImageIds?: boolean | ||||||
| 	} | 	} | ||||||
| ) => new Promise<any>(async (resolve, reject) => { | ) => new Promise<any>(async (resolve, reject) => { | ||||||
| 	const opts = options || { | 	const opts = Object.assign({ | ||||||
|  | 		detail: false, | ||||||
| 		includeSecret: false, | 		includeSecret: false, | ||||||
| 		includeProfileImageIds: false | 		includeProfileImageIds: false | ||||||
| 	}; | 	}, options); | ||||||
|  |  | ||||||
| 	let _app: any; | 	let _app: any; | ||||||
|  |  | ||||||
|  | 	const fields = opts.detail ? {} : { | ||||||
|  | 		name: true | ||||||
|  | 	}; | ||||||
|  |  | ||||||
| 	// Populate the app if 'app' is ID | 	// Populate the app if 'app' is ID | ||||||
| 	if (isObjectId(app)) { | 	if (isObjectId(app)) { | ||||||
| 		_app = await App.findOne({ | 		_app = await App.findOne({ | ||||||
| @@ -51,7 +52,7 @@ export const pack = ( | |||||||
| 	} else if (typeof app === 'string') { | 	} else if (typeof app === 'string') { | ||||||
| 		_app = await App.findOne({ | 		_app = await App.findOne({ | ||||||
| 			_id: new mongo.ObjectID(app) | 			_id: new mongo.ObjectID(app) | ||||||
| 		}); | 		}, { fields }); | ||||||
| 	} else { | 	} else { | ||||||
| 		_app = deepcopy(app); | 		_app = deepcopy(app); | ||||||
| 	} | 	} | ||||||
|   | |||||||
							
								
								
									
										56
									
								
								src/models/blocking.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/models/blocking.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | import * as mongo from 'mongodb'; | ||||||
|  | import db from '../db/mongodb'; | ||||||
|  | import isObjectId from '../misc/is-objectid'; | ||||||
|  | const deepcopy = require('deepcopy'); | ||||||
|  | import { pack as packUser, IUser } from './user'; | ||||||
|  |  | ||||||
|  | const Blocking = db.get<IBlocking>('blocking'); | ||||||
|  | Blocking.createIndex('blockerId'); | ||||||
|  | Blocking.createIndex('blockeeId'); | ||||||
|  | Blocking.createIndex(['blockerId', 'blockeeId'], { unique: true }); | ||||||
|  | export default Blocking; | ||||||
|  |  | ||||||
|  | export type IBlocking = { | ||||||
|  | 	_id: mongo.ObjectID; | ||||||
|  | 	createdAt: Date; | ||||||
|  | 	blockeeId: mongo.ObjectID; | ||||||
|  | 	blockerId: mongo.ObjectID; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const packMany = ( | ||||||
|  | 	blockings: (string | mongo.ObjectID | IBlocking)[], | ||||||
|  | 	me?: string | mongo.ObjectID | IUser | ||||||
|  | ) => { | ||||||
|  | 	return Promise.all(blockings.map(x => pack(x, me))); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const pack = ( | ||||||
|  | 	blocking: any, | ||||||
|  | 	me?: any | ||||||
|  | ) => new Promise<any>(async (resolve, reject) => { | ||||||
|  | 	let _blocking: any; | ||||||
|  |  | ||||||
|  | 	// Populate the blocking if 'blocking' is ID | ||||||
|  | 	if (isObjectId(blocking)) { | ||||||
|  | 		_blocking = await Blocking.findOne({ | ||||||
|  | 			_id: blocking | ||||||
|  | 		}); | ||||||
|  | 	} else if (typeof blocking === 'string') { | ||||||
|  | 		_blocking = await Blocking.findOne({ | ||||||
|  | 			_id: new mongo.ObjectID(blocking) | ||||||
|  | 		}); | ||||||
|  | 	} else { | ||||||
|  | 		_blocking = deepcopy(blocking); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Rename _id to id | ||||||
|  | 	_blocking.id = _blocking._id; | ||||||
|  | 	delete _blocking._id; | ||||||
|  |  | ||||||
|  | 	// Populate blockee | ||||||
|  | 	_blocking.blockee = await packUser(_blocking.blockeeId, me, { | ||||||
|  | 		detail: true | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	resolve(_blocking); | ||||||
|  | }); | ||||||
| @@ -1,6 +1,5 @@ | |||||||
| import * as mongo from 'mongodb'; | import * as mongo from 'mongodb'; | ||||||
| import monkDb, { nativeDbConn } from '../db/mongodb'; | import monkDb, { nativeDbConn } from '../db/mongodb'; | ||||||
| import isObjectId from '../misc/is-objectid'; |  | ||||||
|  |  | ||||||
| const DriveFileThumbnail = monkDb.get<IDriveFileThumbnail>('driveFileThumbnails.files'); | const DriveFileThumbnail = monkDb.get<IDriveFileThumbnail>('driveFileThumbnails.files'); | ||||||
| DriveFileThumbnail.createIndex('metadata.originalId', { sparse: true, unique: true }); | DriveFileThumbnail.createIndex('metadata.originalId', { sparse: true, unique: true }); | ||||||
| @@ -28,35 +27,3 @@ export type IDriveFileThumbnail = { | |||||||
| 	contentType: string; | 	contentType: string; | ||||||
| 	metadata: IMetadata; | 	metadata: IMetadata; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * DriveFileThumbnailを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteDriveFileThumbnail(driveFile: string | mongo.ObjectID | IDriveFileThumbnail) { |  | ||||||
| 	let d: IDriveFileThumbnail; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(driveFile)) { |  | ||||||
| 		d = await DriveFileThumbnail.findOne({ |  | ||||||
| 			_id: driveFile |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof driveFile === 'string') { |  | ||||||
| 		d = await DriveFileThumbnail.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(driveFile) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		d = driveFile as IDriveFileThumbnail; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (d == null) return; |  | ||||||
|  |  | ||||||
| 	// このDriveFileThumbnailのチャンクをすべて削除 |  | ||||||
| 	await DriveFileThumbnailChunk.remove({ |  | ||||||
| 		files_id: d._id |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	// このDriveFileThumbnailを削除 |  | ||||||
| 	await DriveFileThumbnail.remove({ |  | ||||||
| 		_id: d._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,13 +1,9 @@ | |||||||
| import * as mongo from 'mongodb'; | import * as mongo from 'mongodb'; | ||||||
| const deepcopy = require('deepcopy'); | const deepcopy = require('deepcopy'); | ||||||
| import { pack as packFolder } from './drive-folder'; | import { pack as packFolder } from './drive-folder'; | ||||||
| import config from '../config'; |  | ||||||
| import monkDb, { nativeDbConn } from '../db/mongodb'; | import monkDb, { nativeDbConn } from '../db/mongodb'; | ||||||
| import isObjectId from '../misc/is-objectid'; | import isObjectId from '../misc/is-objectid'; | ||||||
| import Note, { deleteNote } from './note'; | import getDriveFileUrl from '../misc/get-drive-file-url'; | ||||||
| import MessagingMessage, { deleteMessagingMessage } from './messaging-message'; |  | ||||||
| import User from './user'; |  | ||||||
| import DriveFileThumbnail, { deleteDriveFileThumbnail } from './drive-file-thumbnail'; |  | ||||||
|  |  | ||||||
| const DriveFile = monkDb.get<IDriveFile>('driveFiles.files'); | const DriveFile = monkDb.get<IDriveFile>('driveFiles.files'); | ||||||
| DriveFile.createIndex('md5'); | DriveFile.createIndex('md5'); | ||||||
| @@ -37,7 +33,14 @@ export type IMetadata = { | |||||||
| 	thumbnailUrl?: string; | 	thumbnailUrl?: string; | ||||||
| 	src?: string; | 	src?: string; | ||||||
| 	deletedAt?: Date; | 	deletedAt?: Date; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * このファイルの中身データがMongoDB内に保存されているのか否か | ||||||
|  | 	 * オブジェクトストレージを利用している or リモートサーバーへの直リンクである | ||||||
|  | 	 * な場合は false になります | ||||||
|  | 	 */ | ||||||
| 	withoutChunks?: boolean; | 	withoutChunks?: boolean; | ||||||
|  |  | ||||||
| 	storage?: string; | 	storage?: string; | ||||||
| 	storageProps?: any; | 	storageProps?: any; | ||||||
| 	isSensitive?: boolean; | 	isSensitive?: boolean; | ||||||
| @@ -77,71 +80,13 @@ export function validateFileName(name: string): boolean { | |||||||
| 	); | 	); | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | export const packMany = ( | ||||||
|  * DriveFileを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteDriveFile(driveFile: string | mongo.ObjectID | IDriveFile) { |  | ||||||
| 	let d: IDriveFile; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(driveFile)) { |  | ||||||
| 		d = await DriveFile.findOne({ |  | ||||||
| 			_id: driveFile |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof driveFile === 'string') { |  | ||||||
| 		d = await DriveFile.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(driveFile) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		d = driveFile as IDriveFile; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (d == null) return; |  | ||||||
|  |  | ||||||
| 	// このDriveFileを添付しているNoteをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await Note.find({ fileIds: d._id }) |  | ||||||
| 	).map(x => deleteNote(x))); |  | ||||||
|  |  | ||||||
| 	// このDriveFileを添付しているMessagingMessageをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await MessagingMessage.find({ fileId: d._id }) |  | ||||||
| 	).map(x => deleteMessagingMessage(x))); |  | ||||||
|  |  | ||||||
| 	// このDriveFileがアバターやバナーに使われていたらそれらのプロパティをnullにする |  | ||||||
| 	const u = await User.findOne({ _id: d.metadata.userId }); |  | ||||||
| 	if (u) { |  | ||||||
| 		if (u.avatarId && u.avatarId.equals(d._id)) { |  | ||||||
| 			await User.update({ _id: u._id }, { $set: { avatarId: null } }); |  | ||||||
| 		} |  | ||||||
| 		if (u.bannerId && u.bannerId.equals(d._id)) { |  | ||||||
| 			await User.update({ _id: u._id }, { $set: { bannerId: null } }); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// このDriveFileのDriveFileThumbnailをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await DriveFileThumbnail.find({ 'metadata.originalId': d._id }) |  | ||||||
| 	).map(x => deleteDriveFileThumbnail(x))); |  | ||||||
|  |  | ||||||
| 	// このDriveFileのチャンクをすべて削除 |  | ||||||
| 	await DriveFileChunk.remove({ |  | ||||||
| 		files_id: d._id |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	// このDriveFileを削除 |  | ||||||
| 	await DriveFile.remove({ |  | ||||||
| 		_id: d._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export const packMany = async ( |  | ||||||
| 	files: any[], | 	files: any[], | ||||||
| 	options?: { | 	options?: { | ||||||
| 		detail: boolean | 		detail: boolean | ||||||
| 	} | 	} | ||||||
| ) => { | ) => { | ||||||
| 	return (await Promise.all(files.map(f => pack(f, options)))).filter(x => x != null); | 	return Promise.all(files.map(f => pack(f, options))); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -190,8 +135,8 @@ export const pack = ( | |||||||
|  |  | ||||||
| 	_target = Object.assign(_target, _file.metadata); | 	_target = Object.assign(_target, _file.metadata); | ||||||
|  |  | ||||||
| 	_target.url = _file.metadata.url ? _file.metadata.url : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`; | 	_target.url = getDriveFileUrl(_file); | ||||||
| 	_target.thumbnailUrl = _file.metadata.thumbnailUrl ? _file.metadata.thumbnailUrl : _file.metadata.url ? _file.metadata.url : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}?thumbnail`; | 	_target.thumbnailUrl = getDriveFileUrl(_file, true); | ||||||
| 	_target.isRemote = _file.metadata.isRemote; | 	_target.isRemote = _file.metadata.isRemote; | ||||||
|  |  | ||||||
| 	if (_target.properties == null) _target.properties = {}; | 	if (_target.properties == null) _target.properties = {}; | ||||||
| @@ -218,6 +163,7 @@ export const pack = ( | |||||||
| 	delete _target.storage; | 	delete _target.storage; | ||||||
| 	delete _target.storageProps; | 	delete _target.storageProps; | ||||||
| 	delete _target.isRemote; | 	delete _target.isRemote; | ||||||
|  | 	delete _target._user; | ||||||
|  |  | ||||||
| 	resolve(_target); | 	resolve(_target); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -23,51 +23,6 @@ export function isValidFolderName(name: string): boolean { | |||||||
| 	); | 	); | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * DriveFolderを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteDriveFolder(driveFolder: string | mongo.ObjectID | IDriveFolder) { |  | ||||||
| 	let d: IDriveFolder; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(driveFolder)) { |  | ||||||
| 		d = await DriveFolder.findOne({ |  | ||||||
| 			_id: driveFolder |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof driveFolder === 'string') { |  | ||||||
| 		d = await DriveFolder.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(driveFolder) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		d = driveFolder as IDriveFolder; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (d == null) return; |  | ||||||
|  |  | ||||||
| 	// このDriveFolderに格納されているDriveFileがあればすべてルートに移動 |  | ||||||
| 	await DriveFile.update({ |  | ||||||
| 		'metadata.folderId': d._id |  | ||||||
| 	}, { |  | ||||||
| 		$set: { |  | ||||||
| 			'metadata.folderId': null |  | ||||||
| 		} |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	// このDriveFolderに格納されているDriveFolderがあればすべてルートに移動 |  | ||||||
| 	await DriveFolder.update({ |  | ||||||
| 		parentId: d._id |  | ||||||
| 	}, { |  | ||||||
| 		$set: { |  | ||||||
| 			parentId: null |  | ||||||
| 		} |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	// このDriveFolderを削除 |  | ||||||
| 	await DriveFolder.remove({ |  | ||||||
| 		_id: d._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Pack a drive folder for API response |  * Pack a drive folder for API response | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import isObjectId from '../misc/is-objectid'; | |||||||
| import { pack as packNote } from './note'; | import { pack as packNote } from './note'; | ||||||
|  |  | ||||||
| const Favorite = db.get<IFavorite>('favorites'); | const Favorite = db.get<IFavorite>('favorites'); | ||||||
|  | Favorite.createIndex('userId'); | ||||||
| Favorite.createIndex(['userId', 'noteId'], { unique: true }); | Favorite.createIndex(['userId', 'noteId'], { unique: true }); | ||||||
| export default Favorite; | export default Favorite; | ||||||
|  |  | ||||||
| @@ -15,38 +16,11 @@ export type IFavorite = { | |||||||
| 	noteId: mongo.ObjectID; | 	noteId: mongo.ObjectID; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** | export const packMany = ( | ||||||
|  * Favoriteを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteFavorite(favorite: string | mongo.ObjectID | IFavorite) { |  | ||||||
| 	let f: IFavorite; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(favorite)) { |  | ||||||
| 		f = await Favorite.findOne({ |  | ||||||
| 			_id: favorite |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof favorite === 'string') { |  | ||||||
| 		f = await Favorite.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(favorite) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		f = favorite as IFavorite; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (f == null) return; |  | ||||||
|  |  | ||||||
| 	// このFavoriteを削除 |  | ||||||
| 	await Favorite.remove({ |  | ||||||
| 		_id: f._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export const packMany = async ( |  | ||||||
| 	favorites: any[], | 	favorites: any[], | ||||||
| 	me: any | 	me: any | ||||||
| ) => { | ) => { | ||||||
| 	return (await Promise.all(favorites.map(f => pack(f, me)))).filter(x => x != null); | 	return Promise.all(favorites.map(f => pack(f, me))); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|   | |||||||
| @@ -5,6 +5,8 @@ import isObjectId from '../misc/is-objectid'; | |||||||
| import { pack as packUser } from './user'; | import { pack as packUser } from './user'; | ||||||
|  |  | ||||||
| const FollowRequest = db.get<IFollowRequest>('followRequests'); | const FollowRequest = db.get<IFollowRequest>('followRequests'); | ||||||
|  | FollowRequest.createIndex('followerId'); | ||||||
|  | FollowRequest.createIndex('followeeId'); | ||||||
| FollowRequest.createIndex(['followerId', 'followeeId'], { unique: true }); | FollowRequest.createIndex(['followerId', 'followeeId'], { unique: true }); | ||||||
| export default FollowRequest; | export default FollowRequest; | ||||||
|  |  | ||||||
| @@ -28,33 +30,6 @@ export type IFollowRequest = { | |||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * FollowRequestを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteFollowRequest(followRequest: string | mongo.ObjectID | IFollowRequest) { |  | ||||||
| 	let f: IFollowRequest; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(followRequest)) { |  | ||||||
| 		f = await FollowRequest.findOne({ |  | ||||||
| 			_id: followRequest |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof followRequest === 'string') { |  | ||||||
| 		f = await FollowRequest.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(followRequest) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		f = followRequest as IFollowRequest; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (f == null) return; |  | ||||||
|  |  | ||||||
| 	// このFollowingを削除 |  | ||||||
| 	await FollowRequest.remove({ |  | ||||||
| 		_id: f._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Pack a request for API response |  * Pack a request for API response | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -1,8 +1,9 @@ | |||||||
| import * as mongo from 'mongodb'; | import * as mongo from 'mongodb'; | ||||||
| import db from '../db/mongodb'; | import db from '../db/mongodb'; | ||||||
| import isObjectId from '../misc/is-objectid'; |  | ||||||
|  |  | ||||||
| const Following = db.get<IFollowing>('following'); | const Following = db.get<IFollowing>('following'); | ||||||
|  | Following.createIndex('followerId'); | ||||||
|  | Following.createIndex('followeeId'); | ||||||
| Following.createIndex(['followerId', 'followeeId'], { unique: true }); | Following.createIndex(['followerId', 'followeeId'], { unique: true }); | ||||||
| export default Following; | export default Following; | ||||||
|  |  | ||||||
| @@ -25,30 +26,3 @@ export type IFollowing = { | |||||||
| 		sharedInbox?: string; | 		sharedInbox?: string; | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Followingを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteFollowing(following: string | mongo.ObjectID | IFollowing) { |  | ||||||
| 	let f: IFollowing; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(following)) { |  | ||||||
| 		f = await Following.findOne({ |  | ||||||
| 			_id: following |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof following === 'string') { |  | ||||||
| 		f = await Following.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(following) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		f = following as IFollowing; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (f == null) return; |  | ||||||
|  |  | ||||||
| 	// このFollowingを削除 |  | ||||||
| 	await Following.remove({ |  | ||||||
| 		_id: f._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| import * as mongo from 'mongodb'; | import * as mongo from 'mongodb'; | ||||||
| import db from '../db/mongodb'; | import db from '../db/mongodb'; | ||||||
| import isObjectId from '../misc/is-objectid'; |  | ||||||
|  |  | ||||||
| const MessagingHistory = db.get<IMessagingHistory>('messagingHistories'); | const MessagingHistory = db.get<IMessagingHistory>('messagingHistories'); | ||||||
| export default MessagingHistory; | export default MessagingHistory; | ||||||
| @@ -12,30 +11,3 @@ export type IMessagingHistory = { | |||||||
| 	partnerId: mongo.ObjectID; | 	partnerId: mongo.ObjectID; | ||||||
| 	messageId: mongo.ObjectID; | 	messageId: mongo.ObjectID; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * MessagingHistoryを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteMessagingHistory(messagingHistory: string | mongo.ObjectID | IMessagingHistory) { |  | ||||||
| 	let m: IMessagingHistory; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(messagingHistory)) { |  | ||||||
| 		m = await MessagingHistory.findOne({ |  | ||||||
| 			_id: messagingHistory |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof messagingHistory === 'string') { |  | ||||||
| 		m = await MessagingHistory.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(messagingHistory) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		m = messagingHistory as IMessagingHistory; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (m == null) return; |  | ||||||
|  |  | ||||||
| 	// このMessagingHistoryを削除 |  | ||||||
| 	await MessagingHistory.remove({ |  | ||||||
| 		_id: m._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ import { pack as packUser } from './user'; | |||||||
| import { pack as packFile } from './drive-file'; | import { pack as packFile } from './drive-file'; | ||||||
| import db from '../db/mongodb'; | import db from '../db/mongodb'; | ||||||
| import isObjectId from '../misc/is-objectid'; | import isObjectId from '../misc/is-objectid'; | ||||||
| import MessagingHistory, { deleteMessagingHistory } from './messaging-history'; |  | ||||||
| import { length } from 'stringz'; | import { length } from 'stringz'; | ||||||
|  |  | ||||||
| const MessagingMessage = db.get<IMessagingMessage>('messagingMessages'); | const MessagingMessage = db.get<IMessagingMessage>('messagingMessages'); | ||||||
| @@ -24,38 +23,6 @@ export function isValidText(text: string): boolean { | |||||||
| 	return length(text.trim()) <= 1000 && text.trim() != ''; | 	return length(text.trim()) <= 1000 && text.trim() != ''; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * MessagingMessageを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteMessagingMessage(messagingMessage: string | mongo.ObjectID | IMessagingMessage) { |  | ||||||
| 	let m: IMessagingMessage; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(messagingMessage)) { |  | ||||||
| 		m = await MessagingMessage.findOne({ |  | ||||||
| 			_id: messagingMessage |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof messagingMessage === 'string') { |  | ||||||
| 		m = await MessagingMessage.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(messagingMessage) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		m = messagingMessage as IMessagingMessage; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (m == null) return; |  | ||||||
|  |  | ||||||
| 	// このMessagingMessageを指すMessagingHistoryをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await MessagingHistory.find({ messageId: m._id }) |  | ||||||
| 	).map(x => deleteMessagingHistory(x))); |  | ||||||
|  |  | ||||||
| 	// このMessagingMessageを削除 |  | ||||||
| 	await MessagingMessage.remove({ |  | ||||||
| 		_id: m._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Pack a messaging message for API response |  * Pack a messaging message for API response | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -15,4 +15,24 @@ export type IMeta = { | |||||||
| 	disableLocalTimeline?: boolean; | 	disableLocalTimeline?: boolean; | ||||||
| 	hidedTags?: string[]; | 	hidedTags?: string[]; | ||||||
| 	bannerUrl?: string; | 	bannerUrl?: string; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * カスタム絵文字定義 | ||||||
|  | 	 */ | ||||||
|  | 	emojis?: { | ||||||
|  | 		/** | ||||||
|  | 		 * 絵文字名 (例: thinking_ai) | ||||||
|  | 		 */ | ||||||
|  | 		name: string; | ||||||
|  |  | ||||||
|  | 		/** | ||||||
|  | 		 * エイリアス | ||||||
|  | 		 */ | ||||||
|  | 		aliases?: string[]; | ||||||
|  |  | ||||||
|  | 		/** | ||||||
|  | 		 * 絵文字画像のURL | ||||||
|  | 		 */ | ||||||
|  | 		url: string; | ||||||
|  | 	}[]; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,8 +1,12 @@ | |||||||
| import * as mongo from 'mongodb'; | import * as mongo from 'mongodb'; | ||||||
| import db from '../db/mongodb'; | import db from '../db/mongodb'; | ||||||
| import isObjectId from '../misc/is-objectid'; | import isObjectId from '../misc/is-objectid'; | ||||||
|  | const deepcopy = require('deepcopy'); | ||||||
|  | import { pack as packUser, IUser } from './user'; | ||||||
|  |  | ||||||
| const Mute = db.get<IMute>('mute'); | const Mute = db.get<IMute>('mute'); | ||||||
|  | Mute.createIndex('muterId'); | ||||||
|  | Mute.createIndex('muteeId'); | ||||||
| Mute.createIndex(['muterId', 'muteeId'], { unique: true }); | Mute.createIndex(['muterId', 'muteeId'], { unique: true }); | ||||||
| export default Mute; | export default Mute; | ||||||
|  |  | ||||||
| @@ -13,29 +17,40 @@ export interface IMute { | |||||||
| 	muteeId: mongo.ObjectID; | 	muteeId: mongo.ObjectID; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | export const packMany = ( | ||||||
|  * Muteを物理削除します | 	mutes: (string | mongo.ObjectID | IMute)[], | ||||||
|  */ | 	me?: string | mongo.ObjectID | IUser | ||||||
| export async function deleteMute(mute: string | mongo.ObjectID | IMute) { | ) => { | ||||||
| 	let m: IMute; | 	return Promise.all(mutes.map(x => pack(x, me))); | ||||||
|  | }; | ||||||
|  |  | ||||||
| 	// Populate | export const pack = ( | ||||||
|  | 	mute: any, | ||||||
|  | 	me?: any | ||||||
|  | ) => new Promise<any>(async (resolve, reject) => { | ||||||
|  | 	let _mute: any; | ||||||
|  |  | ||||||
|  | 	// Populate the mute if 'mute' is ID | ||||||
| 	if (isObjectId(mute)) { | 	if (isObjectId(mute)) { | ||||||
| 		m = await Mute.findOne({ | 		_mute = await Mute.findOne({ | ||||||
| 			_id: mute | 			_id: mute | ||||||
| 		}); | 		}); | ||||||
| 	} else if (typeof mute === 'string') { | 	} else if (typeof mute === 'string') { | ||||||
| 		m = await Mute.findOne({ | 		_mute = await Mute.findOne({ | ||||||
| 			_id: new mongo.ObjectID(mute) | 			_id: new mongo.ObjectID(mute) | ||||||
| 		}); | 		}); | ||||||
| 	} else { | 	} else { | ||||||
| 		m = mute as IMute; | 		_mute = deepcopy(mute); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (m == null) return; | 	// Rename _id to id | ||||||
|  | 	_mute.id = _mute._id; | ||||||
|  | 	delete _mute._id; | ||||||
|  |  | ||||||
| 	// このMuteを削除 | 	// Populate mutee | ||||||
| 	await Mute.remove({ | 	_mute.mutee = await packUser(_mute.muteeId, me, { | ||||||
| 		_id: m._id | 		detail: true | ||||||
| 	}); | 	}); | ||||||
| } |  | ||||||
|  | 	resolve(_mute); | ||||||
|  | }); | ||||||
|   | |||||||
| @@ -7,6 +7,8 @@ import Reaction from './note-reaction'; | |||||||
| import { pack as packUser } from './user'; | import { pack as packUser } from './user'; | ||||||
|  |  | ||||||
| const NoteReaction = db.get<INoteReaction>('noteReactions'); | const NoteReaction = db.get<INoteReaction>('noteReactions'); | ||||||
|  | NoteReaction.createIndex('noteId'); | ||||||
|  | NoteReaction.createIndex('userId'); | ||||||
| NoteReaction.createIndex(['userId', 'noteId'], { unique: true }); | NoteReaction.createIndex(['userId', 'noteId'], { unique: true }); | ||||||
| export default NoteReaction; | export default NoteReaction; | ||||||
|  |  | ||||||
| @@ -31,33 +33,6 @@ export const validateReaction = $.str.or([ | |||||||
| 	'pudding' | 	'pudding' | ||||||
| ]); | ]); | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * NoteReactionを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteNoteReaction(noteReaction: string | mongo.ObjectID | INoteReaction) { |  | ||||||
| 	let n: INoteReaction; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(noteReaction)) { |  | ||||||
| 		n = await NoteReaction.findOne({ |  | ||||||
| 			_id: noteReaction |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof noteReaction === 'string') { |  | ||||||
| 		n = await NoteReaction.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(noteReaction) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		n = noteReaction as INoteReaction; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (n == null) return; |  | ||||||
|  |  | ||||||
| 	// このNoteReactionを削除 |  | ||||||
| 	await NoteReaction.remove({ |  | ||||||
| 		_id: n._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Pack a reaction for API response |  * Pack a reaction for API response | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -2,6 +2,8 @@ import * as mongo from 'mongodb'; | |||||||
| import db from '../db/mongodb'; | import db from '../db/mongodb'; | ||||||
|  |  | ||||||
| const NoteUnread = db.get<INoteUnread>('noteUnreads'); | const NoteUnread = db.get<INoteUnread>('noteUnreads'); | ||||||
|  | NoteUnread.createIndex('userId'); | ||||||
|  | NoteUnread.createIndex('noteId'); | ||||||
| NoteUnread.createIndex(['userId', 'noteId'], { unique: true }); | NoteUnread.createIndex(['userId', 'noteId'], { unique: true }); | ||||||
| export default NoteUnread; | export default NoteUnread; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,8 +1,9 @@ | |||||||
| import * as mongo from 'mongodb'; | import * as mongo from 'mongodb'; | ||||||
| import db from '../db/mongodb'; | import db from '../db/mongodb'; | ||||||
| import isObjectId from '../misc/is-objectid'; |  | ||||||
|  |  | ||||||
| const NoteWatching = db.get<INoteWatching>('noteWatching'); | const NoteWatching = db.get<INoteWatching>('noteWatching'); | ||||||
|  | NoteWatching.createIndex('userId'); | ||||||
|  | NoteWatching.createIndex('noteId'); | ||||||
| NoteWatching.createIndex(['userId', 'noteId'], { unique: true }); | NoteWatching.createIndex(['userId', 'noteId'], { unique: true }); | ||||||
| export default NoteWatching; | export default NoteWatching; | ||||||
|  |  | ||||||
| @@ -12,30 +13,3 @@ export interface INoteWatching { | |||||||
| 	userId: mongo.ObjectID; | 	userId: mongo.ObjectID; | ||||||
| 	noteId: mongo.ObjectID; | 	noteId: mongo.ObjectID; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * NoteWatchingを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteNoteWatching(noteWatching: string | mongo.ObjectID | INoteWatching) { |  | ||||||
| 	let n: INoteWatching; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(noteWatching)) { |  | ||||||
| 		n = await NoteWatching.findOne({ |  | ||||||
| 			_id: noteWatching |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof noteWatching === 'string') { |  | ||||||
| 		n = await NoteWatching.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(noteWatching) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		n = noteWatching as INoteWatching; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (n == null) return; |  | ||||||
|  |  | ||||||
| 	// このNoteWatchingを削除 |  | ||||||
| 	await NoteWatching.remove({ |  | ||||||
| 		_id: n._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -6,13 +6,10 @@ import isObjectId from '../misc/is-objectid'; | |||||||
| import { length } from 'stringz'; | import { length } from 'stringz'; | ||||||
| import { IUser, pack as packUser } from './user'; | import { IUser, pack as packUser } from './user'; | ||||||
| import { pack as packApp } from './app'; | import { pack as packApp } from './app'; | ||||||
| import PollVote, { deletePollVote } from './poll-vote'; | import PollVote from './poll-vote'; | ||||||
| import Reaction, { deleteNoteReaction } from './note-reaction'; | import Reaction from './note-reaction'; | ||||||
| import { packMany as packFileMany, IDriveFile } from './drive-file'; | import { packMany as packFileMany, IDriveFile } from './drive-file'; | ||||||
| import NoteWatching, { deleteNoteWatching } from './note-watching'; | import Favorite from './favorite'; | ||||||
| import NoteReaction from './note-reaction'; |  | ||||||
| import Favorite, { deleteFavorite } from './favorite'; |  | ||||||
| import Notification, { deleteNotification } from './notification'; |  | ||||||
| import Following from './following'; | import Following from './following'; | ||||||
| import config from '../config'; | import config from '../config'; | ||||||
|  |  | ||||||
| @@ -108,72 +105,6 @@ export type INote = { | |||||||
| 	_files?: IDriveFile[]; | 	_files?: IDriveFile[]; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Noteを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteNote(note: string | mongo.ObjectID | INote) { |  | ||||||
| 	let n: INote; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(note)) { |  | ||||||
| 		n = await Note.findOne({ |  | ||||||
| 			_id: note |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof note === 'string') { |  | ||||||
| 		n = await Note.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(note) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		n = note as INote; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	console.log(n == null ? `Note: delete skipped ${note}` : `Note: deleting ${n._id}`); |  | ||||||
|  |  | ||||||
| 	if (n == null) return; |  | ||||||
|  |  | ||||||
| 	// このNoteへの返信をすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await Note.find({ replyId: n._id }) |  | ||||||
| 	).map(x => deleteNote(x))); |  | ||||||
|  |  | ||||||
| 	// このNoteのRenoteをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await Note.find({ renoteId: n._id }) |  | ||||||
| 	).map(x => deleteNote(x))); |  | ||||||
|  |  | ||||||
| 	// この投稿に対するNoteWatchingをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await NoteWatching.find({ noteId: n._id }) |  | ||||||
| 	).map(x => deleteNoteWatching(x))); |  | ||||||
|  |  | ||||||
| 	// この投稿に対するNoteReactionをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await NoteReaction.find({ noteId: n._id }) |  | ||||||
| 	).map(x => deleteNoteReaction(x))); |  | ||||||
|  |  | ||||||
| 	// この投稿に対するPollVoteをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await PollVote.find({ noteId: n._id }) |  | ||||||
| 	).map(x => deletePollVote(x))); |  | ||||||
|  |  | ||||||
| 	// この投稿に対するFavoriteをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await Favorite.find({ noteId: n._id }) |  | ||||||
| 	).map(x => deleteFavorite(x))); |  | ||||||
|  |  | ||||||
| 	// この投稿に対するNotificationをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await Notification.find({ noteId: n._id }) |  | ||||||
| 	).map(x => deleteNotification(x))); |  | ||||||
|  |  | ||||||
| 	// このNoteを削除 |  | ||||||
| 	await Note.remove({ |  | ||||||
| 		_id: n._id |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	console.log(`Note: deleted ${n._id}`); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export const hideNote = async (packedNote: any, meId: mongo.ObjectID) => { | export const hideNote = async (packedNote: any, meId: mongo.ObjectID) => { | ||||||
| 	let hide = false; | 	let hide = false; | ||||||
|  |  | ||||||
| @@ -233,7 +164,7 @@ export const hideNote = async (packedNote: any, meId: mongo.ObjectID) => { | |||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const packMany = async ( | export const packMany = ( | ||||||
| 	notes: (string | mongo.ObjectID | INote)[], | 	notes: (string | mongo.ObjectID | INote)[], | ||||||
| 	me?: string | mongo.ObjectID | IUser, | 	me?: string | mongo.ObjectID | IUser, | ||||||
| 	options?: { | 	options?: { | ||||||
| @@ -241,7 +172,7 @@ export const packMany = async ( | |||||||
| 		skipHide?: boolean; | 		skipHide?: boolean; | ||||||
| 	} | 	} | ||||||
| ) => { | ) => { | ||||||
| 	return (await Promise.all(notes.map(n => pack(n, me, options)))).filter(x => x != null); | 	return Promise.all(notes.map(n => pack(n, me, options))); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -328,11 +259,6 @@ export const pack = async ( | |||||||
|  |  | ||||||
| 	// When requested a detailed note data | 	// When requested a detailed note data | ||||||
| 	if (opts.detail) { | 	if (opts.detail) { | ||||||
| 		//#region 重いので廃止 |  | ||||||
| 		_note.prev = null; |  | ||||||
| 		_note.next = null; |  | ||||||
| 		//#endregion |  | ||||||
|  |  | ||||||
| 		if (_note.replyId) { | 		if (_note.replyId) { | ||||||
| 			// Populate reply to note | 			// Populate reply to note | ||||||
| 			_note.reply = pack(_note.replyId, meId, { | 			_note.reply = pack(_note.replyId, meId, { | ||||||
|   | |||||||
| @@ -51,37 +51,10 @@ export interface INotification { | |||||||
| 	isRead: Boolean; | 	isRead: Boolean; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | export const packMany = ( | ||||||
|  * Notificationを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteNotification(notification: string | mongo.ObjectID | INotification) { |  | ||||||
| 	let n: INotification; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(notification)) { |  | ||||||
| 		n = await Notification.findOne({ |  | ||||||
| 			_id: notification |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof notification === 'string') { |  | ||||||
| 		n = await Notification.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(notification) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		n = notification as INotification; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (n == null) return; |  | ||||||
|  |  | ||||||
| 	// このNotificationを削除 |  | ||||||
| 	await Notification.remove({ |  | ||||||
| 		_id: n._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export const packMany = async ( |  | ||||||
| 	notifications: any[] | 	notifications: any[] | ||||||
| ) => { | ) => { | ||||||
| 	return (await Promise.all(notifications.map(n => pack(n)))).filter(x => x != null); | 	return Promise.all(notifications.map(n => pack(n))); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| import * as mongo from 'mongodb'; | import * as mongo from 'mongodb'; | ||||||
| import db from '../db/mongodb'; | import db from '../db/mongodb'; | ||||||
| import isObjectId from '../misc/is-objectid'; |  | ||||||
|  |  | ||||||
| const PollVote = db.get<IPollVote>('pollVotes'); | const PollVote = db.get<IPollVote>('pollVotes'); | ||||||
| export default PollVote; | export default PollVote; | ||||||
| @@ -12,30 +11,3 @@ export interface IPollVote { | |||||||
| 	noteId: mongo.ObjectID; | 	noteId: mongo.ObjectID; | ||||||
| 	choice: number; | 	choice: number; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * PollVoteを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deletePollVote(pollVote: string | mongo.ObjectID | IPollVote) { |  | ||||||
| 	let p: IPollVote; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(pollVote)) { |  | ||||||
| 		p = await PollVote.findOne({ |  | ||||||
| 			_id: pollVote |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof pollVote === 'string') { |  | ||||||
| 		p = await PollVote.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(pollVote) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		p = pollVote as IPollVote; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (p == null) return; |  | ||||||
|  |  | ||||||
| 	// このPollVoteを削除 |  | ||||||
| 	await PollVote.remove({ |  | ||||||
| 		_id: p._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| import * as mongo from 'mongodb'; | import * as mongo from 'mongodb'; | ||||||
| import db from '../db/mongodb'; | import db from '../db/mongodb'; | ||||||
| import isObjectId from '../misc/is-objectid'; |  | ||||||
|  |  | ||||||
| const SwSubscription = db.get<ISwSubscription>('swSubscriptions'); | const SwSubscription = db.get<ISwSubscription>('swSubscriptions'); | ||||||
| export default SwSubscription; | export default SwSubscription; | ||||||
| @@ -12,30 +11,3 @@ export interface ISwSubscription { | |||||||
| 	auth: string; | 	auth: string; | ||||||
| 	publickey: string; | 	publickey: string; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * SwSubscriptionを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteSwSubscription(swSubscription: string | mongo.ObjectID | ISwSubscription) { |  | ||||||
| 	let s: ISwSubscription; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(swSubscription)) { |  | ||||||
| 		s = await SwSubscription.findOne({ |  | ||||||
| 			_id: swSubscription |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof swSubscription === 'string') { |  | ||||||
| 		s = await SwSubscription.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(swSubscription) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		s = swSubscription as ISwSubscription; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (s == null) return; |  | ||||||
|  |  | ||||||
| 	// このSwSubscriptionを削除 |  | ||||||
| 	await SwSubscription.remove({ |  | ||||||
| 		_id: s._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -14,33 +14,6 @@ export interface IUserList { | |||||||
| 	userIds: mongo.ObjectID[]; | 	userIds: mongo.ObjectID[]; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * UserListを物理削除します |  | ||||||
|  */ |  | ||||||
| export async function deleteUserList(userList: string | mongo.ObjectID | IUserList) { |  | ||||||
| 	let u: IUserList; |  | ||||||
|  |  | ||||||
| 	// Populate |  | ||||||
| 	if (isObjectId(userList)) { |  | ||||||
| 		u = await UserList.findOne({ |  | ||||||
| 			_id: userList |  | ||||||
| 		}); |  | ||||||
| 	} else if (typeof userList === 'string') { |  | ||||||
| 		u = await UserList.findOne({ |  | ||||||
| 			_id: new mongo.ObjectID(userList) |  | ||||||
| 		}); |  | ||||||
| 	} else { |  | ||||||
| 		u = userList as IUserList; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (u == null) return; |  | ||||||
|  |  | ||||||
| 	// このUserListを削除 |  | ||||||
| 	await UserList.remove({ |  | ||||||
| 		_id: u._id |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export const pack = ( | export const pack = ( | ||||||
| 	userList: string | mongo.ObjectID | IUserList | 	userList: string | mongo.ObjectID | IUserList | ||||||
| ) => new Promise<any>(async (resolve, reject) => { | ) => new Promise<any>(async (resolve, reject) => { | ||||||
|   | |||||||
| @@ -1,27 +1,15 @@ | |||||||
| import * as mongo from 'mongodb'; | import * as mongo from 'mongodb'; | ||||||
| const deepcopy = require('deepcopy'); | const deepcopy = require('deepcopy'); | ||||||
| const sequential = require('promise-sequential'); |  | ||||||
| import rap from '@prezzemolo/rap'; | import rap from '@prezzemolo/rap'; | ||||||
| import db from '../db/mongodb'; | import db from '../db/mongodb'; | ||||||
| import isObjectId from '../misc/is-objectid'; | import isObjectId from '../misc/is-objectid'; | ||||||
| import Note, { packMany as packNoteMany, deleteNote } from './note'; | import { packMany as packNoteMany } from './note'; | ||||||
| import Following, { deleteFollowing } from './following'; | import Following from './following'; | ||||||
| import Mute, { deleteMute } from './mute'; | import Blocking from './blocking'; | ||||||
|  | import Mute from './mute'; | ||||||
| import { getFriendIds } from '../server/api/common/get-friends'; | import { getFriendIds } from '../server/api/common/get-friends'; | ||||||
| import config from '../config'; | import config from '../config'; | ||||||
| import AccessToken, { deleteAccessToken } from './access-token'; | import FollowRequest from './follow-request'; | ||||||
| import NoteWatching, { deleteNoteWatching } from './note-watching'; |  | ||||||
| import Favorite, { deleteFavorite } from './favorite'; |  | ||||||
| import NoteReaction, { deleteNoteReaction } from './note-reaction'; |  | ||||||
| import MessagingMessage, { deleteMessagingMessage } from './messaging-message'; |  | ||||||
| import MessagingHistory, { deleteMessagingHistory } from './messaging-history'; |  | ||||||
| import DriveFile, { deleteDriveFile } from './drive-file'; |  | ||||||
| import DriveFolder, { deleteDriveFolder } from './drive-folder'; |  | ||||||
| import PollVote, { deletePollVote } from './poll-vote'; |  | ||||||
| import SwSubscription, { deleteSwSubscription } from './sw-subscription'; |  | ||||||
| import Notification, { deleteNotification } from './notification'; |  | ||||||
| import UserList, { deleteUserList } from './user-list'; |  | ||||||
| import FollowRequest, { deleteFollowRequest } from './follow-request'; |  | ||||||
|  |  | ||||||
| const User = db.get<IUser>('users'); | const User = db.get<IUser>('users'); | ||||||
|  |  | ||||||
| @@ -167,149 +155,48 @@ export function isValidBirthday(birthday: string): boolean { | |||||||
| } | } | ||||||
| //#endregion | //#endregion | ||||||
|  |  | ||||||
| /** | export async function getRelation(me: mongo.ObjectId, target: mongo.ObjectId) { | ||||||
|  * Userを物理削除します | 	const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([ | ||||||
|  */ | 		Following.findOne({ | ||||||
| export async function deleteUser(user: string | mongo.ObjectID | IUser) { | 			followerId: me, | ||||||
| 	let u: IUser; | 			followeeId: target | ||||||
|  | 		}), | ||||||
| 	// Populate | 		Following.findOne({ | ||||||
| 	if (isObjectId(user)) { | 			followerId: target, | ||||||
| 		u = await User.findOne({ | 			followeeId: me | ||||||
| 			_id: user | 		}), | ||||||
| 		}); | 		FollowRequest.findOne({ | ||||||
| 	} else if (typeof user === 'string') { | 			followerId: me, | ||||||
| 		u = await User.findOne({ | 			followeeId: target | ||||||
| 			_id: new mongo.ObjectID(user) | 		}), | ||||||
| 		}); | 		FollowRequest.findOne({ | ||||||
| 	} else { | 			followerId: target, | ||||||
| 		u = user as IUser; | 			followeeId: me | ||||||
| 	} | 		}), | ||||||
|  | 		Blocking.findOne({ | ||||||
| 	console.log(u == null ? `User: delete skipped ${user}` : `User: deleting ${u._id}`); | 			blockerId: me, | ||||||
|  | 			blockeeId: target | ||||||
| 	if (u == null) return; | 		}), | ||||||
|  | 		Blocking.findOne({ | ||||||
| 	// このユーザーのAccessTokenをすべて削除 | 			blockerId: target, | ||||||
| 	await Promise.all(( | 			blockeeId: me | ||||||
| 		await AccessToken.find({ userId: u._id }) | 		}), | ||||||
| 	).map(x => deleteAccessToken(x))); | 		Mute.findOne({ | ||||||
|  | 			muterId: me, | ||||||
| 	// このユーザーのNoteをすべて削除 | 			muteeId: target | ||||||
| 	await sequential(( |  | ||||||
| 		await Note.find({ userId: u._id }) |  | ||||||
| 	).map(x => () => deleteNote(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーのNoteReactionをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await NoteReaction.find({ userId: u._id }) |  | ||||||
| 	).map(x => deleteNoteReaction(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーのNoteWatchingをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await NoteWatching.find({ userId: u._id }) |  | ||||||
| 	).map(x => deleteNoteWatching(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーのPollVoteをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await PollVote.find({ userId: u._id }) |  | ||||||
| 	).map(x => deletePollVote(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーのFavoriteをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await Favorite.find({ userId: u._id }) |  | ||||||
| 	).map(x => deleteFavorite(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーのMessageをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await MessagingMessage.find({ userId: u._id }) |  | ||||||
| 	).map(x => deleteMessagingMessage(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーへのMessageをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await MessagingMessage.find({ recipientId: u._id }) |  | ||||||
| 	).map(x => deleteMessagingMessage(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーの関わるMessagingHistoryをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await MessagingHistory.find({ $or: [{ partnerId: u._id }, { userId: u._id }] }) |  | ||||||
| 	).map(x => deleteMessagingHistory(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーのDriveFileをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await DriveFile.find({ 'metadata.userId': u._id }) |  | ||||||
| 	).map(x => deleteDriveFile(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーのDriveFolderをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await DriveFolder.find({ userId: u._id }) |  | ||||||
| 	).map(x => deleteDriveFolder(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーのMuteをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await Mute.find({ muterId: u._id }) |  | ||||||
| 	).map(x => deleteMute(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーへのMuteをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await Mute.find({ muteeId: u._id }) |  | ||||||
| 	).map(x => deleteMute(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーのFollowingをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await Following.find({ followerId: u._id }) |  | ||||||
| 	).map(x => deleteFollowing(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーへのFollowingをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await Following.find({ followeeId: u._id }) |  | ||||||
| 	).map(x => deleteFollowing(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーのFollowRequestをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await FollowRequest.find({ followerId: u._id }) |  | ||||||
| 	).map(x => deleteFollowRequest(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーへのFollowRequestをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await FollowRequest.find({ followeeId: u._id }) |  | ||||||
| 	).map(x => deleteFollowRequest(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーのSwSubscriptionをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await SwSubscription.find({ userId: u._id }) |  | ||||||
| 	).map(x => deleteSwSubscription(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーのNotificationをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await Notification.find({ notifieeId: u._id }) |  | ||||||
| 	).map(x => deleteNotification(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーが原因となったNotificationをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await Notification.find({ notifierId: u._id }) |  | ||||||
| 	).map(x => deleteNotification(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーのUserListをすべて削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await UserList.find({ userId: u._id }) |  | ||||||
| 	).map(x => deleteUserList(x))); |  | ||||||
|  |  | ||||||
| 	// このユーザーが入っているすべてのUserListからこのユーザーを削除 |  | ||||||
| 	await Promise.all(( |  | ||||||
| 		await UserList.find({ userIds: u._id }) |  | ||||||
| 	).map(x => |  | ||||||
| 		UserList.update({ _id: x._id }, { |  | ||||||
| 			$pull: { userIds: u._id } |  | ||||||
| 		}) | 		}) | ||||||
| 	)); | 	]); | ||||||
|  |  | ||||||
| 	// このユーザーを削除 | 	return { | ||||||
| 	await User.remove({ | 		isFollowing: following1 !== null, | ||||||
| 		_id: u._id | 		isStalking: following1 && following1.stalk, | ||||||
| 	}); | 		hasPendingFollowRequestFromYou: followReq1 !== null, | ||||||
|  | 		hasPendingFollowRequestToYou: followReq2 !== null, | ||||||
| 	console.log(`User: deleted ${u._id}`); | 		isFollowed: following2 !== null, | ||||||
|  | 		isBlocking: toBlocking !== null, | ||||||
|  | 		isBlocked: fromBlocked !== null, | ||||||
|  | 		isMuted: mute !== null | ||||||
|  | 	}; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -336,13 +223,16 @@ export const pack = ( | |||||||
|  |  | ||||||
| 	let _user: any; | 	let _user: any; | ||||||
|  |  | ||||||
| 	const fields = opts.detail ? { | 	const fields = opts.detail ? {} : { | ||||||
| 	} : { | 		name: true, | ||||||
| 		settings: false, | 		username: true, | ||||||
| 		clientSettings: false, | 		host: true, | ||||||
| 		profile: false, | 		avatarColor: true, | ||||||
| 		keywords: false, | 		avatarUrl: true, | ||||||
| 		domains: false | 		isCat: true, | ||||||
|  | 		isBot: true, | ||||||
|  | 		isAdmin: true, | ||||||
|  | 		isVerified: true | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	// Populate the user if 'user' is ID | 	// Populate the user if 'user' is ID | ||||||
| @@ -377,6 +267,8 @@ export const pack = ( | |||||||
| 	_user.id = _user._id; | 	_user.id = _user._id; | ||||||
| 	delete _user._id; | 	delete _user._id; | ||||||
|  |  | ||||||
|  | 	delete _user.usernameLower; | ||||||
|  |  | ||||||
| 	if (_user.host == null) { | 	if (_user.host == null) { | ||||||
| 		// Remove private properties | 		// Remove private properties | ||||||
| 		delete _user.keypair; | 		delete _user.keypair; | ||||||
| @@ -384,7 +276,6 @@ export const pack = ( | |||||||
| 		delete _user.token; | 		delete _user.token; | ||||||
| 		delete _user.twoFactorTempSecret; | 		delete _user.twoFactorTempSecret; | ||||||
| 		delete _user.twoFactorSecret; | 		delete _user.twoFactorSecret; | ||||||
| 		delete _user.usernameLower; |  | ||||||
| 		if (_user.twitter) { | 		if (_user.twitter) { | ||||||
| 			delete _user.twitter.accessToken; | 			delete _user.twitter.accessToken; | ||||||
| 			delete _user.twitter.accessTokenSecret; | 			delete _user.twitter.accessTokenSecret; | ||||||
| @@ -407,16 +298,6 @@ export const pack = ( | |||||||
|  |  | ||||||
| 	if (_user.avatarUrl == null) { | 	if (_user.avatarUrl == null) { | ||||||
| 		_user.avatarUrl = `${config.drive_url}/default-avatar.jpg`; | 		_user.avatarUrl = `${config.drive_url}/default-avatar.jpg`; | ||||||
|  |  | ||||||
| 		// 互換性のため |  | ||||||
| 		if (_user.avatarId) { |  | ||||||
| 			_user.avatarUrl = `${config.drive_url}/${_user.avatarId}`; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// 互換性のため |  | ||||||
| 	if (_user.bannerId && _user.bannerUrl == null) { |  | ||||||
| 		_user.bannerUrl = `${config.drive_url}/${_user.bannerId}`; |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (!meId || !meId.equals(_user.id) || !opts.detail) { | 	if (!meId || !meId.equals(_user.id) || !opts.detail) { | ||||||
| @@ -426,42 +307,17 @@ export const pack = ( | |||||||
| 		delete _user.hasUnreadNotification; | 		delete _user.hasUnreadNotification; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (meId && !meId.equals(_user.id)) { | 	if (meId && !meId.equals(_user.id) && opts.detail) { | ||||||
| 		const [following1, following2, followReq1, followReq2, mute] = await Promise.all([ | 		const relation = await getRelation(meId, _user.id); | ||||||
| 			Following.findOne({ |  | ||||||
| 				followerId: meId, |  | ||||||
| 				followeeId: _user.id |  | ||||||
| 			}), |  | ||||||
| 			Following.findOne({ |  | ||||||
| 				followerId: _user.id, |  | ||||||
| 				followeeId: meId |  | ||||||
| 			}), |  | ||||||
| 			FollowRequest.findOne({ |  | ||||||
| 				followerId: meId, |  | ||||||
| 				followeeId: _user.id |  | ||||||
| 			}), |  | ||||||
| 			FollowRequest.findOne({ |  | ||||||
| 				followerId: _user.id, |  | ||||||
| 				followeeId: meId |  | ||||||
| 			}), |  | ||||||
| 			Mute.findOne({ |  | ||||||
| 				muterId: meId, |  | ||||||
| 				muteeId: _user.id |  | ||||||
| 			}) |  | ||||||
| 		]); |  | ||||||
|  |  | ||||||
| 		// Whether the user is following | 		_user.isFollowing = relation.isFollowing; | ||||||
| 		_user.isFollowing = following1 !== null; | 		_user.isFollowed = relation.isFollowed; | ||||||
| 		_user.isStalking = following1 && following1.stalk; | 		_user.isStalking = relation.isStalking; | ||||||
|  | 		_user.hasPendingFollowRequestFromYou = relation.hasPendingFollowRequestFromYou; | ||||||
| 		_user.hasPendingFollowRequestFromYou = followReq1 !== null; | 		_user.hasPendingFollowRequestToYou = relation.hasPendingFollowRequestToYou; | ||||||
| 		_user.hasPendingFollowRequestToYou = followReq2 !== null; | 		_user.isBlocking = relation.isBlocking; | ||||||
|  | 		_user.isBlocked = relation.isBlocked; | ||||||
| 		// Whether the user is followed | 		_user.isMuted = relation.isMuted; | ||||||
| 		_user.isFollowed = following2 !== null; |  | ||||||
|  |  | ||||||
| 		// Whether the user is muted |  | ||||||
| 		_user.isMuted = mute !== null; |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (opts.detail) { | 	if (opts.detail) { | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								src/remote/activitypub/kernel/block/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/remote/activitypub/kernel/block/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | import * as mongo from 'mongodb'; | ||||||
|  | import User, { IRemoteUser } from '../../../../models/user'; | ||||||
|  | import config from '../../../../config'; | ||||||
|  | import * as debug from 'debug'; | ||||||
|  | import { IBlock } from '../../type'; | ||||||
|  | import block from '../../../../services/blocking/create'; | ||||||
|  |  | ||||||
|  | const log = debug('misskey:activitypub'); | ||||||
|  |  | ||||||
|  | export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => { | ||||||
|  | 	const id = typeof activity.object == 'string' ? activity.object : activity.object.id; | ||||||
|  |  | ||||||
|  | 	const uri = activity.id || activity; | ||||||
|  |  | ||||||
|  | 	log(`Block: ${uri}`); | ||||||
|  |  | ||||||
|  | 	if (!id.startsWith(config.url + '/')) { | ||||||
|  | 		return null; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	const blockee = await User.findOne({ | ||||||
|  | 		_id: new mongo.ObjectID(id.split('/').pop()) | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	if (blockee === null) { | ||||||
|  | 		throw new Error('blockee not found'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (blockee.host != null) { | ||||||
|  | 		throw new Error('ブロックしようとしているユーザーはローカルユーザーではありません'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	block(actor, blockee); | ||||||
|  | }; | ||||||
| @@ -10,6 +10,7 @@ import accept from './accept'; | |||||||
| import reject from './reject'; | import reject from './reject'; | ||||||
| import add from './add'; | import add from './add'; | ||||||
| import remove from './remove'; | import remove from './remove'; | ||||||
|  | import block from './block'; | ||||||
|  |  | ||||||
| const self = async (actor: IRemoteUser, activity: Object): Promise<void> => { | const self = async (actor: IRemoteUser, activity: Object): Promise<void> => { | ||||||
| 	switch (activity.type) { | 	switch (activity.type) { | ||||||
| @@ -53,6 +54,10 @@ const self = async (actor: IRemoteUser, activity: Object): Promise<void> => { | |||||||
| 		await undo(actor, activity); | 		await undo(actor, activity); | ||||||
| 		break; | 		break; | ||||||
|  |  | ||||||
|  | 	case 'Block': | ||||||
|  | 		await block(actor, activity); | ||||||
|  | 		break; | ||||||
|  |  | ||||||
| 	case 'Collection': | 	case 'Collection': | ||||||
| 	case 'OrderedCollection': | 	case 'OrderedCollection': | ||||||
| 		// TODO | 		// TODO | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								src/remote/activitypub/kernel/undo/block.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/remote/activitypub/kernel/undo/block.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | import * as mongo from 'mongodb'; | ||||||
|  | import User, { IRemoteUser } from '../../../../models/user'; | ||||||
|  | import config from '../../../../config'; | ||||||
|  | import * as debug from 'debug'; | ||||||
|  | import { IBlock } from '../../type'; | ||||||
|  | import unblock from '../../../../services/blocking/delete'; | ||||||
|  |  | ||||||
|  | const log = debug('misskey:activitypub'); | ||||||
|  |  | ||||||
|  | export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => { | ||||||
|  | 	const id = typeof activity.object == 'string' ? activity.object : activity.object.id; | ||||||
|  |  | ||||||
|  | 	const uri = activity.id || activity; | ||||||
|  |  | ||||||
|  | 	log(`UnBlock: ${uri}`); | ||||||
|  |  | ||||||
|  | 	if (!id.startsWith(config.url + '/')) { | ||||||
|  | 		return null; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	const blockee = await User.findOne({ | ||||||
|  | 		_id: new mongo.ObjectID(id.split('/').pop()) | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	if (blockee === null) { | ||||||
|  | 		throw new Error('blockee not found'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (blockee.host != null) { | ||||||
|  | 		throw new Error('ブロック解除しようとしているユーザーはローカルユーザーではありません'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	unblock(actor, blockee); | ||||||
|  | }; | ||||||
| @@ -1,8 +1,9 @@ | |||||||
| import * as debug from 'debug'; | import * as debug from 'debug'; | ||||||
|  |  | ||||||
| import { IRemoteUser } from '../../../../models/user'; | import { IRemoteUser } from '../../../../models/user'; | ||||||
| import { IUndo, IFollow } from '../../type'; | import { IUndo, IFollow, IBlock } from '../../type'; | ||||||
| import unfollow from './follow'; | import unfollow from './follow'; | ||||||
|  | import unblock from './block'; | ||||||
| import Resolver from '../../resolver'; | import Resolver from '../../resolver'; | ||||||
|  |  | ||||||
| const log = debug('misskey:activitypub'); | const log = debug('misskey:activitypub'); | ||||||
| @@ -31,6 +32,9 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => { | |||||||
| 		case 'Follow': | 		case 'Follow': | ||||||
| 			unfollow(actor, object as IFollow); | 			unfollow(actor, object as IFollow); | ||||||
| 			break; | 			break; | ||||||
|  | 		case 'Block': | ||||||
|  | 			unblock(actor, object as IBlock); | ||||||
|  | 			break; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return null; | 	return null; | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ import { URL } from 'url'; | |||||||
| import { resolveNote } from './note'; | import { resolveNote } from './note'; | ||||||
| import registerInstance from '../../../services/register-instance'; | import registerInstance from '../../../services/register-instance'; | ||||||
| import Instance from '../../../models/instance'; | import Instance from '../../../models/instance'; | ||||||
|  | import getDriveFileUrl from '../../../misc/get-drive-file-url'; | ||||||
|  |  | ||||||
| const log = debug('misskey:activitypub'); | const log = debug('misskey:activitypub'); | ||||||
|  |  | ||||||
| @@ -209,8 +210,8 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU | |||||||
|  |  | ||||||
| 	const avatarId = avatar ? avatar._id : null; | 	const avatarId = avatar ? avatar._id : null; | ||||||
| 	const bannerId = banner ? banner._id : null; | 	const bannerId = banner ? banner._id : null; | ||||||
| 	const avatarUrl = (avatar && avatar.metadata.thumbnailUrl) ? avatar.metadata.thumbnailUrl : (avatar && avatar.metadata.url) ? avatar.metadata.url : null; | 	const avatarUrl = getDriveFileUrl(avatar, true); | ||||||
| 	const bannerUrl = (banner && banner.metadata.url) ? banner.metadata.url : null; | 	const bannerUrl = getDriveFileUrl(banner, false); | ||||||
|  |  | ||||||
| 	await User.update({ _id: user._id }, { | 	await User.update({ _id: user._id }, { | ||||||
| 		$set: { | 		$set: { | ||||||
| @@ -303,8 +304,8 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje | |||||||
| 			featured: person.featured, | 			featured: person.featured, | ||||||
| 			avatarId: avatar ? avatar._id : null, | 			avatarId: avatar ? avatar._id : null, | ||||||
| 			bannerId: banner ? banner._id : null, | 			bannerId: banner ? banner._id : null, | ||||||
| 			avatarUrl: (avatar && avatar.metadata.thumbnailUrl) ? avatar.metadata.thumbnailUrl : (avatar && avatar.metadata.url) ? avatar.metadata.url : null, | 			avatarUrl: getDriveFileUrl(avatar, true), | ||||||
| 			bannerUrl: banner && banner.metadata.url ? banner.metadata.url : null, | 			bannerUrl: getDriveFileUrl(banner, false), | ||||||
| 			description: htmlToMFM(person.summary), | 			description: htmlToMFM(person.summary), | ||||||
| 			followersCount, | 			followersCount, | ||||||
| 			followingCount, | 			followingCount, | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								src/remote/activitypub/renderer/block.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/remote/activitypub/renderer/block.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | import config from '../../../config'; | ||||||
|  | import { ILocalUser, IRemoteUser } from "../../../models/user"; | ||||||
|  |  | ||||||
|  | export default (blocker?: ILocalUser, blockee?: IRemoteUser) => ({ | ||||||
|  | 	type: 'Block', | ||||||
|  | 	actor: `${config.url}/users/${blocker._id}`, | ||||||
|  | 	object: blockee.uri | ||||||
|  | }); | ||||||
| @@ -1,8 +1,8 @@ | |||||||
| import config from '../../../config'; |  | ||||||
| import { IDriveFile } from '../../../models/drive-file'; | import { IDriveFile } from '../../../models/drive-file'; | ||||||
|  | import getDriveFileUrl from '../../../misc/get-drive-file-url'; | ||||||
|  |  | ||||||
| export default (file: IDriveFile) => ({ | export default (file: IDriveFile) => ({ | ||||||
| 	type: 'Document', | 	type: 'Document', | ||||||
| 	mediaType: file.contentType, | 	mediaType: file.contentType, | ||||||
| 	url: file.metadata.url || `${config.drive_url}/${file._id}` | 	url: getDriveFileUrl(file) | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| import config from '../../../config'; |  | ||||||
| import { IDriveFile } from '../../../models/drive-file'; | import { IDriveFile } from '../../../models/drive-file'; | ||||||
|  | import getDriveFileUrl from '../../../misc/get-drive-file-url'; | ||||||
|  |  | ||||||
| export default (file: IDriveFile) => ({ | export default (file: IDriveFile) => ({ | ||||||
| 	type: 'Image', | 	type: 'Image', | ||||||
| 	url: file.metadata.url || `${config.drive_url}/${file._id}`, | 	url: getDriveFileUrl(file), | ||||||
| 	sensitive: file.metadata.isSensitive | 	sensitive: file.metadata.isSensitive | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -108,6 +108,10 @@ export interface IAnnounce extends IActivity { | |||||||
| 	type: 'Announce'; | 	type: 'Announce'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export interface IBlock extends IActivity { | ||||||
|  | 	type: 'Block'; | ||||||
|  | } | ||||||
|  |  | ||||||
| export type Object = | export type Object = | ||||||
| 	ICollection | | 	ICollection | | ||||||
| 	IOrderedCollection | | 	IOrderedCollection | | ||||||
| @@ -120,4 +124,5 @@ export type Object = | |||||||
| 	IAdd | | 	IAdd | | ||||||
| 	IRemove | | 	IRemove | | ||||||
| 	ILike | | 	ILike | | ||||||
| 	IAnnounce; | 	IAnnounce | | ||||||
|  | 	IBlock; | ||||||
|   | |||||||
| @@ -1,36 +1,83 @@ | |||||||
| import { toUnicode, toASCII } from 'punycode'; | import { toUnicode, toASCII } from 'punycode'; | ||||||
| import User, { IUser } from '../models/user'; | import User, { IUser, IRemoteUser } from '../models/user'; | ||||||
| import webFinger from './webfinger'; | import webFinger from './webfinger'; | ||||||
| import config from '../config'; | import config from '../config'; | ||||||
| import { createPerson } from './activitypub/models/person'; | import { createPerson, updatePerson } from './activitypub/models/person'; | ||||||
|  | import { URL } from 'url'; | ||||||
|  | import * as debug from 'debug'; | ||||||
|  |  | ||||||
| export default async (username: string, _host: string, option?: any): Promise<IUser> => { | const log = debug('misskey:remote:resolve-user'); | ||||||
|  |  | ||||||
|  | export default async (username: string, _host: string, option?: any, resync?: boolean): Promise<IUser> => { | ||||||
| 	const usernameLower = username.toLowerCase(); | 	const usernameLower = username.toLowerCase(); | ||||||
|  |  | ||||||
| 	if (_host == null) { | 	if (_host == null) { | ||||||
| 		return await User.findOne({ usernameLower }); | 		log(`return local user: ${usernameLower}`); | ||||||
|  | 		return await User.findOne({ usernameLower, host: null }); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	const hostAscii = toASCII(_host).toLowerCase(); | 	const hostAscii = toASCII(_host).toLowerCase(); | ||||||
| 	const host = toUnicode(hostAscii); | 	const host = toUnicode(hostAscii); | ||||||
|  |  | ||||||
| 	if (config.host == host) { | 	if (config.host == host) { | ||||||
|  | 		log(`return local user: ${usernameLower}`); | ||||||
| 		return await User.findOne({ usernameLower, host: null }); | 		return await User.findOne({ usernameLower, host: null }); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	let user = await User.findOne({ usernameLower, host }, option); | 	const user = await User.findOne({ usernameLower, host }, option); | ||||||
|  |  | ||||||
| 	if (user === null) { |  | ||||||
| 	const acctLower = `${usernameLower}@${hostAscii}`; | 	const acctLower = `${usernameLower}@${hostAscii}`; | ||||||
|  |  | ||||||
|  | 	if (user === null) { | ||||||
|  | 		const self = await resolveSelf(acctLower); | ||||||
|  |  | ||||||
|  | 		log(`return new remote user: ${acctLower}`); | ||||||
|  | 		return await createPerson(self.href); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (resync) { | ||||||
|  | 		log(`try resync: ${acctLower}`); | ||||||
|  | 		const self = await resolveSelf(acctLower); | ||||||
|  |  | ||||||
|  | 		if ((user as IRemoteUser).uri !== self.href) { | ||||||
|  | 			// if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping. | ||||||
|  | 			log(`uri missmatch: ${acctLower}`); | ||||||
|  | 			console.log(`recovery missmatch uri for (username=${username}, host=${host}) from ${(user as IRemoteUser).uri} to ${self.href}`); | ||||||
|  |  | ||||||
|  | 			// validate uri | ||||||
|  | 			const uri = new URL(self.href); | ||||||
|  | 			if (uri.hostname !== hostAscii) { | ||||||
|  | 				throw new Error(`Invalied uri`); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			await User.update({ | ||||||
|  | 				usernameLower, | ||||||
|  | 				host: host | ||||||
|  | 			 }, { | ||||||
|  | 				$set: { | ||||||
|  | 					uri: self.href | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		} else { | ||||||
|  | 			log(`uri is fine: ${acctLower}`); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		await updatePerson(self.href); | ||||||
|  |  | ||||||
|  | 		log(`return resynced remote user: ${acctLower}`); | ||||||
|  | 		return await User.findOne({ uri: self.href }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 	log(`return existing remote user: ${acctLower}`); | ||||||
|  | 	return user; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | async function resolveSelf(acctLower: string) { | ||||||
|  | 	log(`WebFinger for ${acctLower}`); | ||||||
| 	const finger = await webFinger(acctLower); | 	const finger = await webFinger(acctLower); | ||||||
| 	const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self'); | 	const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self'); | ||||||
| 	if (!self) { | 	if (!self) { | ||||||
| 		throw new Error('self link not found'); | 		throw new Error('self link not found'); | ||||||
| 	} | 	} | ||||||
|  | 	return self; | ||||||
| 		user = await createPerson(self.href); | } | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return user; |  | ||||||
| }; |  | ||||||
|   | |||||||
| @@ -62,7 +62,15 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any) | |||||||
| 			console.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`); | 			console.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`); | ||||||
| 		} | 		} | ||||||
| 	} catch (e) { | 	} catch (e) { | ||||||
|  | 		if (e.name == 'INVALID_PARAM') { | ||||||
|  | 			rej({ | ||||||
|  | 				code: e.name, | ||||||
|  | 				param: e.param, | ||||||
|  | 				reason: e.message | ||||||
|  | 			}); | ||||||
|  | 		} else { | ||||||
| 			rej(e); | 			rej(e); | ||||||
|  | 		} | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,6 +17,12 @@ export const meta = { | |||||||
| 			} | 			} | ||||||
| 		}), | 		}), | ||||||
|  |  | ||||||
|  | 		emojis: $.arr($.obj()).optional.note({ | ||||||
|  | 			desc: { | ||||||
|  | 				'ja-JP': 'カスタム絵文字定義' | ||||||
|  | 			} | ||||||
|  | 		}), | ||||||
|  |  | ||||||
| 		disableRegistration: $.bool.optional.nullable.note({ | 		disableRegistration: $.bool.optional.nullable.note({ | ||||||
| 			desc: { | 			desc: { | ||||||
| 				'ja-JP': '招待制か否か' | 				'ja-JP': '招待制か否か' | ||||||
| @@ -53,6 +59,10 @@ export default (params: any) => new Promise(async (res, rej) => { | |||||||
| 		set.broadcasts = ps.broadcasts; | 		set.broadcasts = ps.broadcasts; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if (ps.emojis) { | ||||||
|  | 		set.emojis = ps.emojis; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if (typeof ps.disableRegistration === 'boolean') { | 	if (typeof ps.disableRegistration === 'boolean') { | ||||||
| 		set.disableRegistration = ps.disableRegistration; | 		set.disableRegistration = ps.disableRegistration; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ async function fetchAny(uri: string) { | |||||||
| 	// URIがこのサーバーを指しているなら、ローカルユーザーIDとしてDBからフェッチ | 	// URIがこのサーバーを指しているなら、ローカルユーザーIDとしてDBからフェッチ | ||||||
| 	if (uri.startsWith(config.url + '/')) { | 	if (uri.startsWith(config.url + '/')) { | ||||||
| 		const id = new mongo.ObjectID(uri.split('/').pop()); | 		const id = new mongo.ObjectID(uri.split('/').pop()); | ||||||
| 		const [ user, note ] = await Promise.all([ | 		const [user, note] = await Promise.all([ | ||||||
| 			User.findOne({ _id: id }), | 			User.findOne({ _id: id }), | ||||||
| 			Note.findOne({ _id: id }) | 			Note.findOne({ _id: id }) | ||||||
| 		]); | 		]); | ||||||
| @@ -52,7 +52,7 @@ async function fetchAny(uri: string) { | |||||||
|  |  | ||||||
| 	// URI(AP Object id)としてDB検索 | 	// URI(AP Object id)としてDB検索 | ||||||
| 	{ | 	{ | ||||||
| 		const [ user, note ] = await Promise.all([ | 		const [user, note] = await Promise.all([ | ||||||
| 			User.findOne({ uri: uri }), | 			User.findOne({ uri: uri }), | ||||||
| 			Note.findOne({ uri: uri }) | 			Note.findOne({ uri: uri }) | ||||||
| 		]); | 		]); | ||||||
| @@ -68,7 +68,7 @@ async function fetchAny(uri: string) { | |||||||
| 	// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する | 	// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する | ||||||
| 	// これはDBに存在する可能性があるため再度DB検索 | 	// これはDBに存在する可能性があるため再度DB検索 | ||||||
| 	if (uri !== object.id) { | 	if (uri !== object.id) { | ||||||
| 		const [ user, note ] = await Promise.all([ | 		const [user, note] = await Promise.all([ | ||||||
| 			User.findOne({ uri: object.id }), | 			User.findOne({ uri: object.id }), | ||||||
| 			Note.findOne({ uri: object.id }) | 			Note.findOne({ uri: object.id }) | ||||||
| 		]); | 		]); | ||||||
|   | |||||||
| @@ -44,6 +44,7 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res, | |||||||
|  |  | ||||||
| 	// Response | 	// Response | ||||||
| 	res(await pack(app, null, { | 	res(await pack(app, null, { | ||||||
|  | 		detail: true, | ||||||
| 		includeSecret: true | 		includeSecret: true | ||||||
| 	})); | 	})); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( | |||||||
|  |  | ||||||
| 	// Send response | 	// Send response | ||||||
| 	res(await pack(ap, user, { | 	res(await pack(ap, user, { | ||||||
|  | 		detail: true, | ||||||
| 		includeSecret: isSecure && ap.userId.equals(user._id) | 		includeSecret: isSecure && ap.userId.equals(user._id) | ||||||
| 	})); | 	})); | ||||||
| }); | }); | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user