Compare commits

..

117 Commits

Author SHA1 Message Date
syuilo
a73da3cd70 10.35.0 2018-10-31 11:30:49 +09:00
Acid Chicken (硫酸鶏)
9c27d0ae3f Add CircleCI badge (#3050) 2018-10-31 11:29:25 +09:00
syuilo
525d5218c1 🎨 2018-10-31 11:29:03 +09:00
syuilo
e23b13ec7f [API] Include detailed user information of blocking API responses 2018-10-31 11:24:36 +09:00
syuilo
29b000e03c Remove needless async/await 2018-10-31 11:22:49 +09:00
syuilo
26c9d8ff6f Clean up 2018-10-31 11:20:54 +09:00
syuilo
5e3372e932 Merge pull request #3047 from mei23/mei-1031-blokings-list
blockings list
2018-10-31 11:17:24 +09:00
syuilo
f7069dcd18 良い感じに 2018-10-31 11:16:13 +09:00
mei23
560bb65384 blockings list 2018-10-31 04:59:01 +09:00
Acid Chicken (硫酸鶏)
50cd6a036e Implement /api/v1/instance (#3045)
* Update mastodon.ts

* Update types.ts

* Update mastodon.ts
2018-10-31 02:17:54 +09:00
MeiMei
441ab2b5f8 Fix: can't recognize rebirthed instance user (#3046)
* resync uri from WebFinger

* trigger resync on user page

* allways update on resync

* Revert "trigger resync on user page"

This reverts commit 8ff139fb49.

* background resync
2018-10-31 02:16:13 +09:00
zwebmedia
ba5ed188a1 Add Crowdin info to translate docs (#3044) 2018-10-30 22:56:13 +09:00
syuilo
72e672f08d Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-10-30 21:55:25 +09:00
syuilo
120474ec6a Fix bug 2018-10-30 21:55:16 +09:00
かひわし4(バージョン1)
eee57c47f5 Use cache when default.yml update (#3042)
* Use cache when default.yml update

* Install latest npm in base image
2018-10-30 21:18:03 +09:00
syuilo
4c160869b8 Update test/api.ts 2018-10-30 21:17:26 +09:00
syuilo
3720a7fbe0 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-10-30 14:34:39 +09:00
syuilo
7afa541a53 Fix #3040 2018-10-30 14:34:32 +09:00
Acid Chicken (硫酸鶏)
6f979c8275 Configure CI (#3037)
* Update config.yml

* Configure CI

* Use Vesion 2.1

* Fix error

* Ensure binary builds

* Ensure misskey builds

* Store artifacts

* Ensure node-gyp builds

* Fix typo

* Fix typo

* Ensure binary builds

* Update working directory

* Cache test npm packages

* Revert "Update working directory"

* Ensure misskey builds

* Ensure node-gyp builds

* Fix missing configurations

* Configure deploy filters

* Use latest npm in Docker
2018-10-30 12:36:14 +09:00
syuilo
d399241e65 Refactor 2018-10-30 09:36:20 +09:00
syuilo
e85dec030a [Client] Fix bug 2018-10-30 09:27:57 +09:00
syuilo
64277b7157 10.34.0 2018-10-29 21:55:33 +09:00
syuilo
4a72543f65 Merge pull request #3019 from syuilo/l10n_develop
New Crowdin translations
2018-10-29 21:53:58 +09:00
syuilo
5b84d29807 Better indexes 2018-10-29 21:53:40 +09:00
syuilo
a11061ec2b New translations ja-JP.yml (English) 2018-10-29 21:51:37 +09:00
syuilo
24cfb93b2e Update .circleci/config.yml 2018-10-29 21:48:35 +09:00
syuilo
502b42d63a New translations ja-JP.yml (Norwegian) 2018-10-29 21:43:46 +09:00
syuilo
612672b79c New translations ja-JP.yml (Dutch) 2018-10-29 21:43:42 +09:00
syuilo
abc670e1b1 New translations ja-JP.yml (Japanese, Kansai) 2018-10-29 21:43:38 +09:00
syuilo
d589ccdd01 New translations ja-JP.yml (Spanish) 2018-10-29 21:43:34 +09:00
syuilo
acb07d9f7d New translations ja-JP.yml (Russian) 2018-10-29 21:43:29 +09:00
syuilo
f4d2186719 New translations ja-JP.yml (Portuguese) 2018-10-29 21:43:25 +09:00
syuilo
d0ede5c665 New translations ja-JP.yml (Polish) 2018-10-29 21:43:20 +09:00
syuilo
554cbb5e9b New translations ja-JP.yml (Korean) 2018-10-29 21:43:16 +09:00
syuilo
dbd32a56bf New translations ja-JP.yml (Italian) 2018-10-29 21:43:10 +09:00
syuilo
7f500235c6 New translations ja-JP.yml (German) 2018-10-29 21:43:05 +09:00
syuilo
39a58084c8 New translations ja-JP.yml (French) 2018-10-29 21:43:01 +09:00
syuilo
cde0fde836 New translations ja-JP.yml (English) 2018-10-29 21:42:56 +09:00
syuilo
e70cca0fda New translations ja-JP.yml (Chinese Simplified) 2018-10-29 21:42:52 +09:00
syuilo
919bd7eb82 New translations ja-JP.yml (Catalan) 2018-10-29 21:42:45 +09:00
syuilo
312cff3d6f Fix 2018-10-29 21:39:35 +09:00
syuilo
0d86eef3d7 Format 2018-10-29 21:38:09 +09:00
syuilo
13acf570e7 Improve performance 2018-10-29 21:35:46 +09:00
syuilo
fa17623fa8 モバイル版からブロックできるように 2018-10-29 21:32:38 +09:00
syuilo
06fd525950 Refactor 2018-10-29 21:32:21 +09:00
syuilo
4805b5115a 🎨 2018-10-29 21:09:32 +09:00
syuilo
108dcb3e61 物理削除系の処理を削除
これらの処理はパフォーマンス的に現実的でないし、すべてのモデルの関係を把握している必要があり保守が困難
論理削除でなんとかする
2018-10-29 21:06:23 +09:00
syuilo
780d272535 New translations ja-JP.yml (Norwegian) 2018-10-29 20:44:27 +09:00
syuilo
02ea4b81a5 New translations ja-JP.yml (Dutch) 2018-10-29 20:44:22 +09:00
syuilo
7c1bdc6d36 New translations ja-JP.yml (Japanese, Kansai) 2018-10-29 20:44:15 +09:00
syuilo
78c7b8b836 New translations ja-JP.yml (Spanish) 2018-10-29 20:44:10 +09:00
syuilo
227da30acb New translations ja-JP.yml (Russian) 2018-10-29 20:44:03 +09:00
syuilo
610805026f New translations ja-JP.yml (Portuguese) 2018-10-29 20:43:57 +09:00
syuilo
c02399c3d2 New translations ja-JP.yml (Polish) 2018-10-29 20:43:51 +09:00
syuilo
e0799d4153 New translations ja-JP.yml (Korean) 2018-10-29 20:43:45 +09:00
syuilo
6df83f1aa9 New translations ja-JP.yml (Italian) 2018-10-29 20:43:41 +09:00
syuilo
efb5ad1d9b New translations ja-JP.yml (German) 2018-10-29 20:43:35 +09:00
syuilo
716976f016 New translations ja-JP.yml (French) 2018-10-29 20:43:31 +09:00
syuilo
7892f41b84 New translations ja-JP.yml (English) 2018-10-29 20:43:26 +09:00
syuilo
d549e03b3f New translations ja-JP.yml (Chinese Simplified) 2018-10-29 20:43:22 +09:00
syuilo
c511ef21ff New translations ja-JP.yml (Catalan) 2018-10-29 20:43:18 +09:00
MeiMei
d64dc45899 User blocking (Following part) (#3035)
* block wip

* UndoBlock

* UnBlock

* wip

* follow

* UI

* fix
2018-10-29 20:32:42 +09:00
syuilo
bcb0588409 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-10-29 19:33:00 +09:00
syuilo
0975959eb9 Clean up 2018-10-29 19:32:20 +09:00
syuilo
e985a6d9d3 Update config.yml 2018-10-29 19:27:47 +09:00
syuilo
b893305974 Delete appveyor.yml 2018-10-29 19:19:41 +09:00
syuilo
724fdd44e4 Clean up 2018-10-29 19:13:16 +09:00
syuilo
b480ef669c Fix doc 2018-10-29 19:11:01 +09:00
syuilo
4b145da046 Fix MFM parsing 2018-10-29 19:09:24 +09:00
syuilo
83d168ece3 Fix API definition 2018-10-29 19:04:58 +09:00
syuilo
ae44fe7818 Refactor 2018-10-29 15:09:03 +09:00
syuilo
f8981b3acb Update src/server/api/endpoints/notes/reactions.ts 2018-10-29 15:07:50 +09:00
syuilo
050b324885 ? 2018-10-29 10:52:42 +09:00
syuilo
e74c0df6c6 Fix #3034 2018-10-29 10:52:36 +09:00
syuilo
22d0d11895 Update note-reaction.ts 2018-10-28 21:41:39 +09:00
dependabot[bot]
80d0c0cf74 Bump eslint from 5.7.0 to 5.8.0 (#3030)
Bumps [eslint](https://github.com/eslint/eslint) from 5.7.0 to 5.8.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v5.7.0...v5.8.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-10-28 21:38:27 +09:00
MeiMei
518646b925 Fix: Unexpected remote user is selected (#3032) 2018-10-28 07:49:58 +09:00
syuilo
479d7e0087 Merge pull request #3031 from syuilo/patch-autogen 2018-10-27 13:37:02 +09:00
Acid Chicken (硫酸鶏)
8ea1a555f4 Update README.md [AUTOGEN] 2018-10-27 13:36:06 +09:00
Acid Chicken (硫酸鶏)
04024dc37c Update README.md [AUTOGEN] (#3024) 2018-10-27 03:47:04 +09:00
Acid Chicken (硫酸鶏)
060ff9288f Build assets in CircleCI. (#3021) 2018-10-27 03:46:48 +09:00
syuilo
197116ee78 New translations ja-JP.yml (Japanese, Kansai) 2018-10-26 22:12:16 +09:00
syuilo
a1e0015257 New translations ja-JP.yml (Japanese, Kansai) 2018-10-26 22:01:37 +09:00
syuilo
7e701ef9e0 10.33.0 2018-10-26 15:23:10 +09:00
syuilo
3d6fb661bb Update src/client/app/desktop/views/components/settings.2fa.vue 2018-10-26 15:17:50 +09:00
syuilo
fc372496da Client: Make drive folder deletable 2018-10-26 15:06:55 +09:00
syuilo
ad7258fe9c Fix bug 2018-10-26 15:06:27 +09:00
syuilo
bd707cb2a8 🎨 2018-10-26 14:51:57 +09:00
syuilo
1839b5f205 Improve usability 2018-10-26 14:47:30 +09:00
syuilo
02b47f963c API: Better error response 2018-10-26 14:38:34 +09:00
syuilo
f8a7f9378a Improve usability 2018-10-26 14:38:22 +09:00
syuilo
65cb253be4 Update src/client/app/common/views/components/ui/textarea.vue 2018-10-26 14:38:03 +09:00
syuilo
a12356b24b Merge pull request #3010 from syuilo/l10n_develop
New Crowdin translations
2018-10-26 08:55:11 +09:00
syuilo
6a67ad7f93 New translations ja-JP.yml (English) 2018-10-26 08:51:31 +09:00
syuilo
140a7f0b1c New translations ja-JP.yml (Norwegian) 2018-10-26 08:42:29 +09:00
syuilo
00159bc6b5 New translations ja-JP.yml (Dutch) 2018-10-26 08:42:24 +09:00
syuilo
9542260103 New translations ja-JP.yml (Japanese, Kansai) 2018-10-26 08:42:20 +09:00
syuilo
72074578df New translations ja-JP.yml (Spanish) 2018-10-26 08:42:15 +09:00
syuilo
3b4750a988 New translations ja-JP.yml (Russian) 2018-10-26 08:42:11 +09:00
syuilo
aeec5f0163 New translations ja-JP.yml (Portuguese) 2018-10-26 08:42:05 +09:00
syuilo
9c94d8c8d6 New translations ja-JP.yml (Polish) 2018-10-26 08:41:59 +09:00
syuilo
581712a2c8 New translations ja-JP.yml (Korean) 2018-10-26 08:41:54 +09:00
syuilo
b25b51aaca New translations ja-JP.yml (Italian) 2018-10-26 08:41:50 +09:00
syuilo
fb97e13a61 New translations ja-JP.yml (German) 2018-10-26 08:41:44 +09:00
syuilo
36e154fdb2 New translations ja-JP.yml (French) 2018-10-26 08:41:40 +09:00
syuilo
ca273a24b4 New translations ja-JP.yml (English) 2018-10-26 08:41:36 +09:00
syuilo
d828bf2889 New translations ja-JP.yml (Chinese Simplified) 2018-10-26 08:41:32 +09:00
syuilo
87efccef18 New translations ja-JP.yml (Catalan) 2018-10-26 08:41:28 +09:00
syuilo
e0bf522e7f Client: Improve API settings 2018-10-26 08:37:30 +09:00
syuilo
5b1cd3bd3c Fix bug 2018-10-26 08:36:50 +09:00
dependabot[bot]
f00489196d Bump @types/mocha from 5.2.3 to 5.2.5 (#3006)
Bumps [@types/mocha](https://github.com/DefinitelyTyped/DefinitelyTyped) from 5.2.3 to 5.2.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-10-26 06:40:31 +09:00
dependabot[bot]
dd53bf7e51 Bump html-minifier from 3.5.20 to 3.5.21 (#3015)
Bumps [html-minifier](https://github.com/kangax/html-minifier) from 3.5.20 to 3.5.21.
- [Release notes](https://github.com/kangax/html-minifier/releases)
- [Commits](https://github.com/kangax/html-minifier/compare/v3.5.20...v3.5.21)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-10-26 06:40:01 +09:00
dependabot[bot]
35a6da26d2 Bump apexcharts from 2.1.5 to 2.1.6 (#3013)
Bumps [apexcharts](https://github.com/apexcharts/apexcharts.js) from 2.1.5 to 2.1.6.
- [Release notes](https://github.com/apexcharts/apexcharts.js/releases)
- [Changelog](https://github.com/apexcharts/apexcharts.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/apexcharts/apexcharts.js/compare/v2.1.5...v2.1.6)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-10-26 06:39:48 +09:00
dependabot[bot]
c8c8748a0b Bump webpack from 4.23.0 to 4.23.1 (#3011)
Bumps [webpack](https://github.com/webpack/webpack) from 4.23.0 to 4.23.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.23.0...v4.23.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-10-26 06:38:08 +09:00
syuilo
46d0065a90 New translations ja-JP.yml (Japanese, Kansai) 2018-10-26 01:01:40 +09:00
syuilo
990b0180a8 New translations ja-JP.yml (Japanese, Kansai) 2018-10-26 00:51:35 +09:00
syuilo
f3bfb72251 New translations ja-JP.yml (Japanese, Kansai) 2018-10-26 00:42:19 +09:00
125 changed files with 1863 additions and 1382 deletions

129
.circleci/config.yml Normal file
View File

@@ -0,0 +1,129 @@
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 .
- when:
condition: <<parameters.with_deploy>>
steps:
- run:
name: Deploy
command: |
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD && docker push misskey/misskey
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

View File

@@ -35,7 +35,7 @@ before_script:
- npm install
# 設定ファイルを配置
- cp ./.travis/default.yml ./.config
- cp ./.travis/test.yml ./.config
- cp ./.ci/default.yml ./.config
- cp ./.ci/test.yml ./.config
- travis_wait npm run build

View File

@@ -3,8 +3,9 @@ FROM alpine:3.8 AS base
ENV NODE_ENV=production
RUN apk add --no-cache nodejs nodejs-npm zlib
RUN npm i -g npm@latest
WORKDIR /misskey
COPY . ./
FROM base AS builder
@@ -21,18 +22,23 @@ RUN apk add --no-cache \
pkgconfig \
libtool \
zlib-dev
RUN npm install \
&& npm install -g node-gyp \
&& node-gyp configure \
&& node-gyp build \
&& npm run build
RUN npm i -g node-gyp
COPY ./package.json ./
RUN npm i
COPY . ./
RUN node-gyp configure \
&& node-gyp build \
&& npm run build
FROM base AS runner
COPY --from=builder /misskey/built ./built
COPY --from=builder /misskey/node_modules ./node_modules
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]
COPY --from=builder /misskey/node_modules ./node_modules
COPY --from=builder /misskey/built ./built
COPY . ./
CMD ["npm", "start"]

View File

@@ -3,6 +3,7 @@
[![Misskey](/assets/title.png)](https://misskey.xyz/)
================================================================
[![CircleCI](https://circleci.com/gh/syuilo/misskey.svg?style=svg)](https://circleci.com/gh/syuilo/misskey)
[![][travis-badge]][travis-link]
[![][dependencies-badge]][dependencies-link]
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
@@ -71,39 +72,46 @@ Please see [Contribution guide](./CONTRIBUTING.md).
----------------------------------------------------------------
<!-- PATREON_START -->
<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/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/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/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://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>
<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/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/AxellaMC">Xeltica</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/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=13039004">nemu</a></td>
</tr></table>
<table><tr>
<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/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/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/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/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>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td>
<td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</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/spinlock">Naoki Hirayama</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>
</tr></table>
<table><tr>
</tr><tr>
</tr></table>
**Last updated:** Tue, 02 Oct 2018 09:25:07 UTC
**Last updated:** Sat, 27 Oct 2018 04:36:06 UTC
<!-- PATREON_END -->
:four_leaf_clover: Copyright

View File

@@ -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

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -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);

View File

@@ -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.
- 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).

View File

@@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue:
success: "設定が完了しました!"
failed: "設定に失敗しました。トークンに誤りがないかご確認ください。"
info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。"
desktop/views/components/settings.api.vue:
common/views/components/api-settings.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
console:
title: 'APIコンソール'
endpoint: 'エンドポイント'
parameter: 'パラメータ'
send: '送信'
sending: '応答待ち'
response: '結果'
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
common/views/components/drive-settings.vue:
@@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "ミュートする"
muted: "ミュートしています"
unmute: "ミュート解除"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "リストに追加"
list-pushed: "{user}を{list}に追加しました。"
desktop/views/pages/user/user.header.vue:
@@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
images: "画像"

View File

@@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue:
success: "設定が完了しました!"
failed: "設定に失敗しました。トークンに誤りがないかご確認ください。"
info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。"
desktop/views/components/settings.api.vue:
common/views/components/api-settings.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "Bitte Passwort eingeben"
enter-password: "パスワードを入力してください"
console:
title: 'APIコンソール'
endpoint: 'エンドポイント'
parameter: 'パラメータ'
send: '送信'
sending: '応答待ち'
response: '結果'
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
common/views/components/drive-settings.vue:
@@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "ミュートする"
muted: "ミュートしています"
unmute: "ミュート解除"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "リストに追加"
list-pushed: "{user}を{list}に追加しました。"
desktop/views/pages/user/user.header.vue:
@@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
images: "画像"

View File

@@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue:
success: "Settings saved!"
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."
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."
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."
regenerate-token: "Regenerate the 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:
no-apps: "No linked applications"
common/views/components/drive-settings.vue:
@@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "Mute"
muted: "Muting"
unmute: "Unmute"
block: "Block"
unblock: "Unblock"
block-confirm: "Are you sure block this user?"
push-to-a-list: "Add to list"
list-pushed: "Successfully added {user} to {list}."
desktop/views/pages/user/user.header.vue:
@@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue:
timeline: "Timeline"
media: "Media"
is-suspended: "This account has been suspended."
mute: "Mute"
unmute: "Unmute"
block: "Block"
unblock: "Unblock"
mobile/views/pages/user/home.vue:
recent-notes: "Recent notes"
images: "Images"

View File

@@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue:
success: "¡Configuraciones guardadas!"
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"
desktop/views/components/settings.api.vue:
intro: "Para acceder al API, configura este token como la letra \"i\" de los parámetros requeridos."
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."
regeneration-of-token: "En el caso no deseado de que este token lo tenga otra persona, puedes regenerarlo."
regenerate-token: "Regenerar el token"
common/views/components/api-settings.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-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:
no-apps: "No hay aplicaciones asociadas"
common/views/components/drive-settings.vue:
@@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "ミュートする"
muted: "ミュートしています"
unmute: "ミュート解除"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "リストに追加"
list-pushed: "{user}を{list}に追加しました。"
desktop/views/pages/user/user.header.vue:
@@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
images: "画像"

View File

@@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue:
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."
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」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "Si votre jeton est compromis, vous pouvez le régénérer."
regenerate-token: "Regenerer le token"
token: "Jeton :"
enter-password: "Veuillez entrer le mot de passe"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
console:
title: 'APIコンソール'
endpoint: 'エンドポイント'
parameter: 'パラメータ'
send: '送信'
sending: '応答待ち'
response: '結果'
desktop/views/components/settings.apps.vue:
no-apps: "Aucune application autorisée"
common/views/components/drive-settings.vue:
@@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "Mettre en sourdine"
muted: "Muting"
unmute: "Enlever la sourdine"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "Ajouter à la liste"
list-pushed: "Vous avez ajouté {user} à la liste {list}."
desktop/views/pages/user/user.header.vue:
@@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue:
timeline: "Fil d'actualité"
media: "Media"
is-suspended: "This account has been suspended."
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
mobile/views/pages/user/home.vue:
recent-notes: "Notes récentes"
images: "Images"

View File

@@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue:
success: "設定が完了しました!"
failed: "設定に失敗しました。トークンに誤りがないかご確認ください。"
info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。"
desktop/views/components/settings.api.vue:
common/views/components/api-settings.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
console:
title: 'APIコンソール'
endpoint: 'エンドポイント'
parameter: 'パラメータ'
send: '送信'
sending: '応答待ち'
response: '結果'
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
common/views/components/drive-settings.vue:
@@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "ミュートする"
muted: "ミュートしています"
unmute: "ミュート解除"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "リストに追加"
list-pushed: "{user}を{list}に追加しました。"
desktop/views/pages/user/user.header.vue:
@@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
images: "画像"

View File

@@ -832,7 +832,8 @@ desktop/views/components/settings.vue:
profile: "プロフィール"
notification: "通知"
apps: "アプリ"
mute: "ミュート"
mute-and-block: "ミュート/ブロック"
blocking: "ブロック"
security: "セキュリティ"
signin: "サインイン履歴"
password: "パスワード"
@@ -950,13 +951,20 @@ desktop/views/components/settings.2fa.vue:
failed: "設定に失敗しました。トークンに誤りがないかご確認ください。"
info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。"
desktop/views/components/settings.api.vue:
common/views/components/api-settings.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
console:
title: 'APIコンソール'
endpoint: 'エンドポイント'
parameter: 'パラメータ'
send: '送信'
sending: '応答待ち'
response: '結果'
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
@@ -966,8 +974,12 @@ common/views/components/drive-settings.vue:
in-use: "使用中"
stats: "統計"
desktop/views/components/settings.mute.vue:
no-users: "ミュートしているユーザーはいません"
common/views/components/mute-and-block.vue:
mute-and-block: "ミュートとブロック"
mute: "ミュート"
block: "ブロック"
no-muted-users: "ミュートしているユーザーはいません"
no-blocked-users: "ブロックしているユーザーはいません"
desktop/views/components/settings.password.vue:
reset: "パスワードを変更する"
@@ -1196,6 +1208,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "ミュートする"
muted: "ミュートしています"
unmute: "ミュート解除"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "リストに追加"
list-pushed: "{user}を{list}に追加しました。"
@@ -1524,6 +1539,10 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"

View File

@@ -847,18 +847,25 @@ desktop/views/components/settings.2fa.vue:
success: "設定が完了したで!"
failed: "なんか設定に失敗したで。トークンを間違えとらんか確認してや。"
info: "次のサインインからは、パスワードに加えてデバイスに出とるトークンを入力してな。"
desktop/views/components/settings.api.vue:
intro: "APIを利用するには、上記のトークンを「i」っちゅうキーでパラメータに付加してリクエストしてや。"
caution: "アカウントを不正利用されるかも知れんから、このトークンは第三者に教えたらあかんで(アプリなどにも入力しんといてな)。"
regeneration-of-token: "万が一このトークン漏れたとかその可能性があったらトークンを再生成できるで。"
regenerate-token: "トークンを再生成"
token: "トークン:"
enter-password: "パスワードを入力してや"
common/views/components/api-settings.vue:
intro: "API使うんやったらこのトークンを「i」っちゅうパラメータにくっつけてリクエストできるで。"
caution: "アカウント勝手にいじられるかも知れんから、このトークンは教えたらあかんし、アプリにも書いたらあかんで(これはフリちゃうで)"
regeneration-of-token: "トークン漏れてもうたんやったらもっかい生成できるで。"
regenerate-token: "トークンもっかい生成"
token: "Token:"
enter-password: "パスワードを入てや"
console:
title: 'APIコンソール'
endpoint: 'エンドポイント'
parameter: 'パラメータ'
send: '送る'
sending: '応答待っとる'
response: 'こんなん返ってきたわ'
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはあらへんで"
common/views/components/drive-settings.vue:
max: "容量"
in-use: "使用中"
in-use: "使うとる"
stats: "統計"
desktop/views/components/settings.mute.vue:
no-users: "ミュートしているユーザーはおらんで"
@@ -938,7 +945,7 @@ desktop/views/pages/admin/admin.vue:
dashboard: "ダッシュボード"
users: "ユーザー"
update: "更新"
announcements: "お知らせ"
announcements: "知っといてや"
hashtags: "ハッシュタグ"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
@@ -967,7 +974,7 @@ desktop/views/pages/admin/admin.unverify-user.vue:
unverify: "公式アカウントにはさせへんで"
unverified: "公式アカウントを解除したで"
desktop/views/pages/admin/admin.announcements.vue:
announcements: "お知らせ"
announcements: "知っといてや"
desktop/views/pages/admin/admin.hashtags.vue:
hided-tags: "Hidden Tags"
desktop/views/pages/deck/deck.tl-column.vue:
@@ -979,10 +986,10 @@ desktop/views/pages/deck/deck.user-column.vue:
following: "フォロー"
followers: "フォロワー"
images: "画像"
activity: "アクティビティ"
activity: "やっとること"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
push-to-a-list: "リストに追加"
pinned-notes: "ピン留めしはった投稿"
push-to-a-list: "リストに入れたる"
desktop/views/pages/stats/stats.vue:
all-users: "全てのユーザー"
original-users: "ここの人らだけ"
@@ -1035,7 +1042,7 @@ desktop/views/pages/user/user.friends.vue:
no-users: "よう話すツレは居らん"
desktop/views/pages/user/user.vue:
is-suspended: "このユーザーはあかんわ。凍結されとる。"
last-used-at: "最終アクセス"
last-used-at: "最後いつ来はった?"
desktop/views/pages/user/user.photos.vue:
title: "写真"
loading: "読み込んどります"
@@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "ミュートする"
muted: "ミュートしとるで"
unmute: "ミュート解除"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "リストに追加"
list-pushed: "{user}を{list}に追加したで。"
desktop/views/pages/user/user.header.vue:
@@ -1117,8 +1127,8 @@ mobile/views/components/drive.file-detail.vue:
hash: "ハッシュ(md5)"
exif: "EXIF"
nsfw: "ちょっと見せられへんわ"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mark-as-sensitive: "見たらあかん感じにしとく"
unmark-as-sensitive: "やっぱ見せたるわ"
mobile/views/components/media-image.vue:
sensitive: "見たらあかんで"
click-to-show: "押してみ、見せたるわ"
@@ -1312,7 +1322,7 @@ mobile/views/pages/settings.vue:
signout: "さいなら"
sound: "サウンド"
enable-sounds: "サウンド鳴らす"
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
mark-as-read-all-unread-notes: "全部もう読んだわ"
mobile/views/pages/user.vue:
follows-you: "フォローされとるで"
following: "フォロー"
@@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーはあかんわ。凍結されとる。"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
mobile/views/pages/user/home.vue:
recent-notes: "最近儲かりまっか?"
images: "画像"
@@ -1369,27 +1383,27 @@ dev/views/index.vue:
manage-apps: "アプリの管理"
dev/views/apps.vue:
manage-apps: "アプリを管理"
create-app: "アプリ作"
app-missing: "アプリなし"
create-app: "アプリ作"
app-missing: "アプリあらへん"
dev/views/new-app.vue:
create-app: "アプリケーションの作成"
app-name: "アプリケーション"
app-name-desc: "あたのアプリの名。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
callback-url: "コールバックURL (オプション)"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
create-app: "アプリケーション作る"
app-name: "アプリケーションの名前"
app-name-desc: "あたのアプリの名。"
app-name-ex: "ex) 関西ミスキー保安協会"
app-overview: "このアプリどんなん?"
app-desc: "あたのアプリどんなんか教えて"
app-desc-ex: "ex) 関西人なら誰でも口ずさめるこのCMがついにMisskeyへ。"
callback-url: "コールバックURL (無くてもええで)"
callback-url-desc: "ユーザーが認証フォームで認証した後どこに連れてくかを設定できるで"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"
authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。"
account-read: "アカウントの情報を見る。"
account-write: "アカウントの情報を操作する。"
note-write: "投稿する。"
reaction-write: "リアクションしたりリアクションをキャンセルする。"
following-write: "フォローしたりフォロー解除する。"
drive-read: "ドライブを見る。"
drive-write: "ドライブを操作する。"
notification-read: "通知を見る。"
notification-write: "通知を操作する。"
authority-desc: "ここにチェックした機能しかAPIからアクセスできひんから気ぃつけてな"
authority-warning: "アプリ作った後でも変えれるけど、新しいやつ追加したらそん時関連付いてるユーザーキーは全部ほかされるで。"
account-read: "アカウントの情報見せて"
account-write: "アカウントの情報いじらせて"
note-write: "投稿させて"
reaction-write: "リアクションしたりそれをキャンセルさせて"
following-write: "フォローとかフォロー解除させて"
drive-read: "ドライブ見せて"
drive-write: "ドライブいじらせて"
notification-read: "通知見せて"
notification-write: "通知いじらせて"

View File

@@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue:
success: "設定が完了しました!"
failed: "設定に失敗しました。トークンに誤りがないかご確認ください。"
info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。"
desktop/views/components/settings.api.vue:
common/views/components/api-settings.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
console:
title: 'APIコンソール'
endpoint: 'エンドポイント'
parameter: 'パラメータ'
send: '送信'
sending: '応答待ち'
response: '結果'
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
common/views/components/drive-settings.vue:
@@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "ミュートする"
muted: "ミュートしています"
unmute: "ミュート解除"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "リストに追加"
list-pushed: "{user}を{list}に追加しました。"
desktop/views/pages/user/user.header.vue:
@@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
images: "画像"

View File

@@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue:
success: "Instellen voltooid!"
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."
desktop/views/components/settings.api.vue:
intro: "Als je toegang wilt tot de API, stel deze sleutel dan in als 'i' bij de verzoekparameters."
caution: "Laat deze sleutel niet zien aan derde partijen (en voer hem nergens anders in dan hier), anders kan je account gehackt worden."
regeneration-of-token: "Mocht deze sleutel tóch uitlekken, dan kun je hem opnieuw genereren."
regenerate-token: "Sleutel opnieuw genereren"
token: "Sleutel:"
enter-password: "Voer je wachtwoord in"
common/views/components/api-settings.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
console:
title: 'APIコンソール'
endpoint: 'エンドポイント'
parameter: 'パラメータ'
send: '送信'
sending: '応答待ち'
response: '結果'
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
common/views/components/drive-settings.vue:
@@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "Dempen"
muted: "Dempend"
unmute: "Ontdempen"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "リストに追加"
list-pushed: "{user}を{list}に追加しました。"
desktop/views/pages/user/user.header.vue:
@@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue:
timeline: "Tijdlijn"
media: "Media"
is-suspended: "Dit account is geschorst."
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
mobile/views/pages/user/home.vue:
recent-notes: "Recente notities"
images: "Afbeeldingen"

View File

@@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue:
success: "設定が完了しました!"
failed: "設定に失敗しました。トークンに誤りがないかご確認ください。"
info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。"
desktop/views/components/settings.api.vue:
common/views/components/api-settings.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
console:
title: 'APIコンソール'
endpoint: 'エンドポイント'
parameter: 'パラメータ'
send: '送信'
sending: '応答待ち'
response: '結果'
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
common/views/components/drive-settings.vue:
@@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "ミュートする"
muted: "ミュートしています"
unmute: "ミュート解除"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "リストに追加"
list-pushed: "{user}を{list}に追加しました。"
desktop/views/pages/user/user.header.vue:
@@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "Media"
is-suspended: "このユーザーは凍結されています。"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
mobile/views/pages/user/home.vue:
recent-notes: "Nylige innlegg"
images: "Bilder"

View File

@@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue:
success: "Pomyślnie ukończono konfigurację!"
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."
desktop/views/components/settings.api.vue:
intro: "Aby uzyskać dostęp do API, ustaw ten token jako klucz 'i' parametrów żądań."
caution: "Nie pokazuj tego tokenu osobom trzecim (nie wprowadzaj go nigdzie indziej), aby konto nie trafiło w niepowołane ręce."
regeneration-of-token: "W przypadku wycieku tokenu, możesz wygenerować nowy."
regenerate-token: "Wygeneruj nowy token"
common/views/components/api-settings.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "Wprowadź hasło"
enter-password: "パスワードを入力してください"
console:
title: 'APIコンソール'
endpoint: 'エンドポイント'
parameter: 'パラメータ'
send: '送信'
sending: '応答待ち'
response: '結果'
desktop/views/components/settings.apps.vue:
no-apps: "Brak zautoryzowanych aplikacji"
common/views/components/drive-settings.vue:
@@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "Wycisz"
muted: "Wyciszyłeś"
unmute: "Cofnij wyciszenie"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "Dodaj do listy"
list-pushed: "Dodałeś(-aś) {user} do {list}."
desktop/views/pages/user/user.header.vue:
@@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue:
timeline: "Oś czasu"
media: "Multimedia"
is-suspended: "To konto zostało zablokowane"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
mobile/views/pages/user/home.vue:
recent-notes: "Ostatnie wpisy"
images: "Zdjęcia"

View File

@@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue:
success: "設定が完了しました!"
failed: "設定に失敗しました。トークンに誤りがないかご確認ください。"
info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。"
desktop/views/components/settings.api.vue:
common/views/components/api-settings.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
console:
title: 'APIコンソール'
endpoint: 'エンドポイント'
parameter: 'パラメータ'
send: '送信'
sending: '応答待ち'
response: '結果'
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
common/views/components/drive-settings.vue:
@@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "ミュートする"
muted: "ミュートしています"
unmute: "ミュート解除"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "リストに追加"
list-pushed: "{user}を{list}に追加しました。"
desktop/views/pages/user/user.header.vue:
@@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue:
timeline: "Linha do tempo"
media: "Mídia"
is-suspended: "Esta conta foi suspensa"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
mobile/views/pages/user/home.vue:
recent-notes: "Notas recentes"
images: "Imagens"

View File

@@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue:
success: "設定が完了しました!"
failed: "設定に失敗しました。トークンに誤りがないかご確認ください。"
info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。"
desktop/views/components/settings.api.vue:
common/views/components/api-settings.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
console:
title: 'APIコンソール'
endpoint: 'エンドポイント'
parameter: 'パラメータ'
send: '送信'
sending: '応答待ち'
response: '結果'
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
common/views/components/drive-settings.vue:
@@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "ミュートする"
muted: "ミュートしています"
unmute: "ミュート解除"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "リストに追加"
list-pushed: "{user}を{list}に追加しました。"
desktop/views/pages/user/user.header.vue:
@@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
images: "画像"

View File

@@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue:
success: "設定が完了しました!"
failed: "設定に失敗しました。トークンに誤りがないかご確認ください。"
info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。"
desktop/views/components/settings.api.vue:
common/views/components/api-settings.vue:
intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
regenerate-token: "トークンを再生成"
token: "Token:"
enter-password: "パスワードを入力してください"
console:
title: 'APIコンソール'
endpoint: 'エンドポイント'
parameter: 'パラメータ'
send: '送信'
sending: '応答待ち'
response: '結果'
desktop/views/components/settings.apps.vue:
no-apps: "連携しているアプリケーションはありません"
common/views/components/drive-settings.vue:
@@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue:
mute: "ミュートする"
muted: "ミュートしています"
unmute: "ミュート解除"
block: "ブロックする"
unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
push-to-a-list: "リストに追加"
list-pushed: "{user}を{list}に追加しました。"
desktop/views/pages/user/user.header.vue:
@@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue:
timeline: "タイムライン"
media: "メディア"
is-suspended: "このユーザーは凍結されています。"
mute: "ミュート"
unmute: "ミュート解除"
block: "ブロック"
unblock: "ブロック解除"
mobile/views/pages/user/home.vue:
recent-notes: "最近の投稿"
images: "画像"

62
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "10.31.0",
"version": "10.33.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -544,9 +544,9 @@
}
},
"@types/mocha": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.3.tgz",
"integrity": "sha512-C1wVVr7xhKu6c3Mb27dFzNYR05qvHwgtpN+JOYTGc1pKA7dCEDDYpscn7kul+bCUwa3NoGDbzI1pdznSOa397w=="
"version": "5.2.5",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz",
"integrity": "sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww=="
},
"@types/mongodb": {
"version": "3.1.12",
@@ -1259,9 +1259,9 @@
}
},
"apexcharts": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-2.1.5.tgz",
"integrity": "sha512-4eKh2HyQVr5ct2t7cWkvDSUyJM9KGw6dRHAlojqo6HJz+XtrnnvL8uP18rzNq4M80P3Ul+yDjpCt5EXlYPfo5Q==",
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-2.1.6.tgz",
"integrity": "sha512-kIb4Q07bWwTGuTWhyzhDAOz6nrltDgyP8VUUwqetxr0o11mNH6PA6YVnR/e9nyd9HU6q3bFZN8eVuSatnqdxAQ==",
"requires": {
"babel-polyfill": "^6.26.0",
"core-js": "^2.5.7",
@@ -4755,9 +4755,9 @@
}
},
"eslint": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.7.0.tgz",
"integrity": "sha512-zYCeFQahsxffGl87U2aJ7DPyH8CbWgxBC213Y8+TCanhUTf2gEvfq3EKpHmEcozTLyPmGe9LZdMAwC/CpJBM5A==",
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.8.0.tgz",
"integrity": "sha512-Zok6Bru3y2JprqTNm14mgQ15YQu/SMDkWdnmHfFg770DIUlmMFd/gqqzCHekxzjHZJxXv3tmTpH0C1icaYJsRQ==",
"requires": {
"@babel/code-frame": "^7.0.0",
"ajv": "^6.5.3",
@@ -7224,14 +7224,14 @@
"integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8="
},
"html-minifier": {
"version": "3.5.20",
"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.20.tgz",
"integrity": "sha512-ZmgNLaTp54+HFKkONyLFEfs5dd/ZOtlquKaTnqIWFmx3Av5zG6ZPcV2d0o9XM2fXOTxxIf6eDcwzFFotke/5zA==",
"version": "3.5.21",
"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz",
"integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==",
"requires": {
"camel-case": "3.0.x",
"clean-css": "4.2.x",
"commander": "2.17.x",
"he": "1.1.x",
"he": "1.2.x",
"param-case": "2.1.x",
"relateurl": "0.2.x",
"uglify-js": "3.4.x"
@@ -7242,14 +7242,10 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
},
"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"
}
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
}
}
},
@@ -15911,6 +15907,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": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
@@ -16808,9 +16820,9 @@
"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="
},
"webpack": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.23.0.tgz",
"integrity": "sha512-Osh/3U9y4swhEKDjy8fF48v2Qx5VC6VvdQ8bEm1HMaVVddiQBw4+mIyDrzVcVRCPT/+4uJFOcklPuoB+I3Zw0w==",
"version": "4.23.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.23.1.tgz",
"integrity": "sha512-iE5Cu4rGEDk7ONRjisTOjVHv3dDtcFfwitSxT7evtYj/rANJpt1OuC/Kozh1pBa99AUBr1L/LsaNB+D9Xz3CEg==",
"requires": {
"@webassemblyjs/ast": "1.7.10",
"@webassemblyjs/helper-module-context": "1.7.10",

View File

@@ -1,8 +1,8 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "10.32.0",
"clientVersion": "1.0.11138",
"version": "10.35.0",
"clientVersion": "1.0.11255",
"codename": "nighthike",
"main": "./built/index.js",
"private": true,
@@ -58,7 +58,7 @@
"@types/koa__cors": "2.2.3",
"@types/minio": "7.0.0",
"@types/mkdirp": "0.5.2",
"@types/mocha": "5.2.3",
"@types/mocha": "5.2.5",
"@types/mongodb": "3.1.12",
"@types/ms": "0.7.30",
"@types/node": "10.12.0",
@@ -84,7 +84,7 @@
"@types/websocket": "0.0.40",
"@types/ws": "6.0.1",
"animejs": "2.2.0",
"apexcharts": "2.1.5",
"apexcharts": "2.1.6",
"autobind-decorator": "2.1.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
@@ -108,7 +108,7 @@
"elasticsearch": "15.1.1",
"emojilib": "2.3.0",
"escape-regexp": "0.0.1",
"eslint": "5.7.0",
"eslint": "5.8.0",
"eslint-plugin-vue": "4.7.1",
"eventemitter3": "3.1.0",
"file-loader": "2.0.0",
@@ -129,7 +129,7 @@
"gulp-uglify": "3.0.1",
"gulp-util": "3.0.8",
"hard-source-webpack-plugin": "0.12.0",
"html-minifier": "3.5.20",
"html-minifier": "3.5.21",
"http-signature": "1.2.0",
"insert-text-at-cursor": "0.1.1",
"is-root": "2.0.0",
@@ -229,17 +229,10 @@
"vuex-persistedstate": "2.5.4",
"web-push": "3.3.3",
"webfinger.js": "2.6.6",
"webpack": "4.23.0",
"webpack": "4.23.1",
"webpack-cli": "3.1.2",
"websocket": "1.0.28",
"ws": "6.1.0",
"xev": "2.0.1"
},
"greenkeeper": {
"ignore": [
"deepcopy",
"cafy",
"@types/gulp"
]
}
}

View 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>

View 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>

View File

@@ -1,5 +1,8 @@
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 profileEditor from './profile-editor.vue';
import noteSkeleton from './note-skeleton.vue';
@@ -44,9 +47,13 @@ import uiTextarea from './ui/textarea.vue';
import uiSwitch from './ui/switch.vue';
import uiRadio from './ui/radio.vue';
import uiSelect from './ui/select.vue';
import uiInfo from './ui/info.vue';
import formButton from './ui/form/button.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-profile-editor', profileEditor);
Vue.component('mk-note-skeleton', noteSkeleton);
@@ -91,5 +98,6 @@ Vue.component('ui-textarea', uiTextarea);
Vue.component('ui-switch', uiSwitch);
Vue.component('ui-radio', uiRadio);
Vue.component('ui-select', uiSelect);
Vue.component('ui-info', uiInfo);
Vue.component('form-button', formButton);
Vue.component('form-radio', formRadio);

View File

@@ -0,0 +1,56 @@
<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">
<p>%i18n:@no-muted-users%</p>
</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">
<p>%i18n:@no-blocked-users%</p>
</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>

View File

@@ -2,11 +2,11 @@
<header class="bvonvjxbwzaiskogyhbwgyxvcgserpmu">
<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>
<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-bot" v-if="note.user.isBot">bot</span>
<span class="is-cat" v-if="note.user.isCat">cat</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">
<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>
@@ -68,10 +68,6 @@ export default Vue.extend({
&:hover
text-decoration underline
> .is-verified
margin-right 8px
color #4dabf7
> .is-admin
> .is-bot
> .is-cat
@@ -95,6 +91,10 @@ export default Vue.extend({
color var(--noteHeaderAcct)
flex-shrink 2147483647
> .is-verified
margin 0 .5em 0 0
color #4dabf7
> .info
margin-left auto
font-size 0.9em

View File

@@ -0,0 +1,33 @@
<template>
<div class="ymxyweixqwsxauxldgpvecjepnwxbylu" :class="{ warn }">
<i v-if="warn">%fa:exclamation-triangle%</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%
> i
margin-right 4px
&.warn
background var(--infoWarnBg)
color var(--infoWarnFg)
</style>

View File

@@ -1,17 +1,17 @@
<template>
<div class="ui-textarea" :class="{ focused, filled }">
<div class="ui-textarea" :class="{ focused, filled, tall }">
<div class="input">
<span class="label" ref="label"><slot></slot></span>
<textarea ref="input"
:value="value"
:required="required"
:readonly="readonly"
:pattern="pattern"
:autocomplete="autocomplete"
@input="$emit('input', $event.target.value)"
@focus="focused = true"
@blur="focused = false">
</textarea>
:value="value"
:required="required"
:readonly="readonly"
:pattern="pattern"
:autocomplete="autocomplete"
@input="$emit('input', $event.target.value)"
@focus="focused = true"
@blur="focused = false"
></textarea>
</div>
<div class="text"><slot name="text"></slot></div>
</div>
@@ -41,7 +41,12 @@ export default Vue.extend({
autocomplete: {
type: String,
required: false
}
},
tall: {
type: Boolean,
required: false,
default: false
},
},
data() {
return {
@@ -66,6 +71,9 @@ export default Vue.extend({
root(fill)
margin 42px 0 32px 0
&:last-child
margin-bottom 0
> .input
padding 12px
@@ -157,6 +165,11 @@ root(fill)
left 0 !important
transform scale(0.75)
&.tall
> .input
> textarea
min-height 200px
.ui-textarea.fill
root(true)

View File

@@ -67,12 +67,12 @@ export default Vue.extend({
text: '%i18n:@contextmenu.rename%',
icon: '%fa:i-cursor%',
action: this.rename
}/*, null, {
}, null, {
type: 'item',
text: '%i18n:common.delete%',
icon: '%fa:R trash-alt%',
action: this.deleteFolder
}*/], {
}], {
closed: () => {
this.isContextmenuShowing = false;
}
@@ -207,7 +207,9 @@ export default Vue.extend({
},
deleteFolder() {
alert('not implemented yet');
(this as any).api('drive/folders/delete', {
folderId: this.folder.id
});
}
}
});

View File

@@ -98,7 +98,7 @@ export default Vue.extend({
hierarchyFolders: [],
selectedFiles: [],
uploadings: [],
connection: null
connection: null,
/**
* ドロップされようとしているか
@@ -122,6 +122,7 @@ export default Vue.extend({
this.connection.on('fileDeleted', this.onStreamDriveFileDeleted);
this.connection.on('folderCreated', this.onStreamDriveFolderCreated);
this.connection.on('folderUpdated', this.onStreamDriveFolderUpdated);
this.connection.on('folderDeleted', this.onStreamDriveFolderDeleted);
if (this.initFolder) {
this.move(this.initFolder);
@@ -182,6 +183,10 @@ export default Vue.extend({
}
},
onStreamDriveFolderDeleted(folderId) {
this.removeFolder(folderId);
},
onChangeUploaderUploads(uploads) {
this.uploadings = uploads;
},

View File

@@ -40,8 +40,8 @@ export default Vue.extend({
mounted() {
this.connection = (this as any).os.stream.useSharedConnection('main');
this.connection.on('follow', this.onFollow);
this.connection.on('unfollow', this.onUnfollow);
this.connection.on('follow', this.onFollowChange);
this.connection.on('unfollow', this.onFollowChange);
},
beforeDestroy() {
@@ -49,17 +49,11 @@ export default Vue.extend({
},
methods: {
onFollow(user) {
if (user.id == this.u.id) {
this.u.isFollowing = user.isFollowing;
this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
}
},
onUnfollow(user) {
onFollowChange(user) {
if (user.id == this.u.id) {
this.u.isFollowing = user.isFollowing;
this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
this.$forceUpdate();
}
},

View File

@@ -4,10 +4,7 @@
<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
<div v-if="!fetching && requestInitPromise != null" class="error">
<p>%fa:exclamation-triangle% %i18n:common.error.title%</p>
<ui-button @click="resolveInitPromise">%i18n:common.error.retry%</ui-button>
</div>
<mk-error v-if="!fetching && requestInitPromise != null" @retry="resolveInitPromise"/>
<div class="placeholder" v-if="fetching">
<template v-for="i in 10">
@@ -215,16 +212,6 @@ export default Vue.extend({
> *
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
padding 32px
opacity 0.3

View File

@@ -1,7 +1,7 @@
<template>
<div class="2fa">
<p>%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>
<p style="margin-top:0;">%i18n:@intro%<a href="%i18n:@url%" target="_blank">%i18n:@detail%</a></p>
<ui-info warn>%i18n:@caution%</ui-info>
<p v-if="!data && !$store.state.i.twoFactorEnabled"><ui-button @click="register">%i18n:@register%</ui-button></p>
<template v-if="$store.state.i.twoFactorEnabled">
<p>%i18n:@already-registered%</p>
@@ -72,9 +72,3 @@ export default Vue.extend({
}
});
</script>
<style lang="stylus" scoped>
.2fa
color #4a535a
</style>

View File

@@ -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>

View File

@@ -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>

View File

@@ -7,7 +7,7 @@
<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 == '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 == 'security' }" @mousedown="page = 'security'">%fa:unlock-alt .fw%%i18n:@security%</p>
<p :class="{ active: page == 'api' }" @mousedown="page = 'api'">%fa:key .fw%API</p>
@@ -200,12 +200,9 @@
</section>
</ui-card>
<ui-card class="mute" v-show="page == 'mute'">
<div slot="title">%fa:ban% %i18n:@mute%</div>
<section>
<x-mute/>
</section>
</ui-card>
<div class="muteAndBlock" v-show="page == 'muteAndBlock'">
<mk-mute-and-block/>
</div>
<ui-card class="apps" v-show="page == 'apps'">
<div slot="title">%fa:puzzle-piece% %i18n:@apps%</div>
@@ -235,12 +232,9 @@
</section>
</ui-card>
<ui-card class="api" v-show="page == 'api'">
<div slot="title">%fa:key% API</div>
<section class="fit-top">
<x-api/>
</section>
</ui-card>
<div class="api" v-show="page == 'api'">
<mk-api-settings/>
</div>
<ui-card class="other" v-show="page == 'other'">
<div slot="title">%fa:info-circle% %i18n:@about%</div>
@@ -292,10 +286,8 @@
<script lang="ts">
import Vue from 'vue';
import XMute from './settings.mute.vue';
import XPassword from './settings.password.vue';
import X2fa from './settings.2fa.vue';
import XApi from './settings.api.vue';
import XApps from './settings.apps.vue';
import XSignins from './settings.signins.vue';
import XTags from './settings.tags.vue';
@@ -304,10 +296,8 @@ import checkForUpdate from '../../../common/scripts/check-for-update';
export default Vue.extend({
components: {
XMute,
XPassword,
X2fa,
XApi,
XApps,
XSignins,
XTags

View File

@@ -1,6 +1,6 @@
<template>
<div class="dnpfarvgbnfmyzbdquhhzyxcmstpdqzs" :class="{ naked, narrow, active, isStacked, draghover, dragging, dropready }"
@dragover.prevent.stop="onDragover"
@dragenter.prevent.stop="onDragenter"
@dragleave="onDragleave"
@drop.prevent.stop="onDrop"
v-hotkey="keymap">
@@ -269,7 +269,7 @@ export default Vue.extend({
this.dragging = false;
},
onDragover(e) {
onDragenter(e) {
// テンポラリカラムにはドロップさせない
if (this.isTemporaryColumn) {
e.dataTransfer.dropEffect = 'none';
@@ -287,7 +287,7 @@ export default Vue.extend({
e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none';
if (!this.dragging) this.draghover = true;
if (!this.dragging && isDeckColumn) this.draghover = true;
},
onDragleave() {

View File

@@ -8,10 +8,7 @@
</template>
</div>
<div v-if="!fetching && requestInitPromise != null" class="error">
<p>%fa:exclamation-triangle% %i18n:common.error.title%</p>
<ui-button @click="resolveInitPromise">%i18n:common.error.retry%</ui-button>
</div>
<mk-error v-if="!fetching && requestInitPromise != null" @retry="resolveInitPromise"/>
<!-- トランジションを有効にするとなぜかメモリリークする -->
<!--<transition-group name="mk-notes" class="transition" ref="notes">-->
@@ -221,13 +218,6 @@ export default Vue.extend({
> *
transition transform .3s ease, opacity .3s ease
> .error
max-width 300px
margin 0 auto
padding 16px
text-align center
color var(--text)
> .placeholder
padding 16px
opacity 0.3

View File

@@ -11,7 +11,11 @@
<div class="action-form">
<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-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 @click="list">%fa:list% %i18n:@push-to-a-list%</ui-button>
</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() {
const w = (this as any).os.new(MkUserListsWindow);
w.$once('choosen', async list => {
@@ -114,7 +139,6 @@ export default Vue.extend({
> .action-form
padding 16px
text-align center
border-bottom solid 1px var(--faceDivider)
> *
width 100%

View File

@@ -17,6 +17,7 @@
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
user: {
@@ -24,6 +25,7 @@ export default Vue.extend({
required: true
}
},
data() {
return {
u: this.user,
@@ -31,28 +33,24 @@ export default Vue.extend({
connection: null
};
},
mounted() {
this.connection = (this as any).os.stream.useSharedConnection('main');
this.connection.on('follow', this.onFollow);
this.connection.on('unfollow', this.onUnfollow);
this.connection.on('follow', this.onFollowChange);
this.connection.on('unfollow', this.onFollowChange);
},
beforeDestroy() {
this.connection.dispose();
},
methods: {
onFollow(user) {
if (user.id == this.u.id) {
this.u.isFollowing = user.isFollowing;
this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
}
},
onUnfollow(user) {
onFollowChange(user) {
if (user.id == this.u.id) {
this.u.isFollowing = user.isFollowing;
this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
this.$forceUpdate();
}
},
@@ -90,8 +88,6 @@ export default Vue.extend({
</script>
<style lang="stylus" scoped>
.mk-follow-button
display block
user-select none

View File

@@ -12,7 +12,6 @@ import noteCard from './note-card.vue';
import userCard from './user-card.vue';
import noteDetail from './note-detail.vue';
import followButton from './follow-button.vue';
import muteButton from './mute-button.vue';
import friendsMaker from './friends-maker.vue';
import notification from './notification.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-note-detail', noteDetail);
Vue.component('mk-follow-button', followButton);
Vue.component('mk-mute-button', muteButton);
Vue.component('mk-friends-maker', friendsMaker);
Vue.component('mk-notification', notification);
Vue.component('mk-notifications', notifications);

View File

@@ -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>

View File

@@ -26,6 +26,9 @@
<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="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_useContrastStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
</section>
@@ -82,6 +85,8 @@
<mk-drive-settings/>
<mk-mute-and-block/>
<ui-card>
<div slot="title">%fa:volume-up% %i18n:@sound%</div>
@@ -120,6 +125,8 @@
</section>
</ui-card>
<mk-api-settings />
<ui-card>
<div slot="title">%fa:sync-alt% %i18n:@update%</div>

View File

@@ -11,7 +11,7 @@
<a class="avatar">
<img :src="user.avatarUrl" alt="avatar"/>
</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"/>
</div>
<div class="title">
@@ -67,6 +67,7 @@ import Vue from 'vue';
import * as age from 's-age';
import parseAcct from '../../../../../misc/acct/parse';
import Progress from '../../../common/scripts/loading';
import Menu from '../../../common/views/components/menu.vue';
import XHome from './user/home.vue';
export default Vue.extend({
@@ -109,7 +110,61 @@ export default Vue.extend({
Progress.done();
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>
@@ -156,14 +211,10 @@ main
max-width 600px
> .top
&:after
content ''
display block
clear both
display flex
> .avatar
display block
float left
width 25%
height 40px
@@ -183,11 +234,15 @@ main
border 4px solid $bg
border-radius 12px
> .mk-mute-button
float right
> .menu
margin 0 0 0 auto
padding 8px
margin-right 8px
font-size 18px
color var(--text)
> .mk-follow-button
float right
margin 0
> .title
margin 8px 0

View File

@@ -131,6 +131,9 @@
remoteInfoBg: '#42321c',
remoteInfoFg: '#ffbd3e',
infoWarnBg: '#42321c',
infoWarnFg: '#ffbd3e',
messagingRoomBg: '@bg',
messagingRoomInfo: '#fff',
messagingRoomDateDividerLine: 'rgba(255, 255, 255, 0.1)',

View File

@@ -131,6 +131,9 @@
remoteInfoBg: '#fff0db',
remoteInfoFg: '#573c08',
infoWarnBg: '#fff0db',
infoWarnFg: '#573c08',
messagingRoomBg: '#fff',
messagingRoomInfo: '#000',
messagingRoomDateDividerLine: 'rgba(0, 0, 0, 0.1)',

View File

@@ -14,11 +14,13 @@ export type Source = {
* メンテナの連絡先(URLかmailto形式のURL)
*/
url: string;
email?: string;
repository_url?: string;
feedback_url?: string;
};
name?: string;
description?: string;
languages?: string[];
welcome_bg_url?: string;
url: string;
port: number;

View File

@@ -8,7 +8,7 @@
以下のURLに、`i`というパラメータ名で認証情報を含めて、websocket接続してください。例:
```
%URL%/streaming?i=xxxxxxxxxxxxxxx
%WS_URL%/streaming?i=xxxxxxxxxxxxxxx
```
認証情報は、自分のAPIキーや、アプリケーションからストリームに接続する際はユーザーのアクセストークンのことを指します。
@@ -22,7 +22,7 @@
認証情報は省略することもできますが、その場合非ログインでの利用ということになり、受信できる情報や可能な操作は限られます。例:
```
%URL%/streaming
%WS_URL%/streaming
```
---

View File

@@ -17,7 +17,8 @@ export default function(text: string, index: number) {
const quote = match[1]
.split('\n')
.map(line => line.replace(/^>+/g, '').trim())
.join('\n');
.join('\n')
.trim();
return {
type: 'quote',

View File

@@ -1,6 +1,5 @@
import * as mongo from 'mongodb';
import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const AccessToken = db.get<IAccessToken>('accessTokens');
AccessToken.createIndex('token');
@@ -15,30 +14,3 @@ export type IAccessToken = {
token: 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
});
}

54
src/models/blocking.ts Normal file
View File

@@ -0,0 +1,54 @@
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);
resolve(_blocking);
});

View File

@@ -1,6 +1,5 @@
import * as mongo from 'mongodb';
import monkDb, { nativeDbConn } from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const DriveFileThumbnail = monkDb.get<IDriveFileThumbnail>('driveFileThumbnails.files');
DriveFileThumbnail.createIndex('metadata.originalId', { sparse: true, unique: true });
@@ -28,35 +27,3 @@ export type IDriveFileThumbnail = {
contentType: string;
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
});
}

View File

@@ -4,10 +4,6 @@ import { pack as packFolder } from './drive-folder';
import config from '../config';
import monkDb, { nativeDbConn } from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import Note, { deleteNote } from './note';
import MessagingMessage, { deleteMessagingMessage } from './messaging-message';
import User from './user';
import DriveFileThumbnail, { deleteDriveFileThumbnail } from './drive-file-thumbnail';
const DriveFile = monkDb.get<IDriveFile>('driveFiles.files');
DriveFile.createIndex('md5');
@@ -77,71 +73,13 @@ export function validateFileName(name: string): boolean {
);
}
/**
* 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 (
export const packMany = (
files: any[],
options?: {
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)));
};
/**

View File

@@ -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
*/

View File

@@ -5,6 +5,7 @@ import isObjectId from '../misc/is-objectid';
import { pack as packNote } from './note';
const Favorite = db.get<IFavorite>('favorites');
Favorite.createIndex('userId');
Favorite.createIndex(['userId', 'noteId'], { unique: true });
export default Favorite;
@@ -15,38 +16,11 @@ export type IFavorite = {
noteId: mongo.ObjectID;
};
/**
* 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 (
export const packMany = (
favorites: 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)));
};
/**

View File

@@ -5,6 +5,8 @@ import isObjectId from '../misc/is-objectid';
import { pack as packUser } from './user';
const FollowRequest = db.get<IFollowRequest>('followRequests');
FollowRequest.createIndex('followerId');
FollowRequest.createIndex('followeeId');
FollowRequest.createIndex(['followerId', 'followeeId'], { unique: true });
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
*/

View File

@@ -1,8 +1,9 @@
import * as mongo from 'mongodb';
import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const Following = db.get<IFollowing>('following');
Following.createIndex('followerId');
Following.createIndex('followeeId');
Following.createIndex(['followerId', 'followeeId'], { unique: true });
export default Following;
@@ -25,30 +26,3 @@ export type IFollowing = {
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
});
}

View File

@@ -1,6 +1,5 @@
import * as mongo from 'mongodb';
import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const MessagingHistory = db.get<IMessagingHistory>('messagingHistories');
export default MessagingHistory;
@@ -12,30 +11,3 @@ export type IMessagingHistory = {
partnerId: 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
});
}

View File

@@ -4,7 +4,6 @@ import { pack as packUser } from './user';
import { pack as packFile } from './drive-file';
import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import MessagingHistory, { deleteMessagingHistory } from './messaging-history';
import { length } from 'stringz';
const MessagingMessage = db.get<IMessagingMessage>('messagingMessages');
@@ -24,38 +23,6 @@ export function isValidText(text: string): boolean {
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
*/

View File

@@ -1,8 +1,12 @@
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 Mute = db.get<IMute>('mute');
Mute.createIndex('muterId');
Mute.createIndex('muteeId');
Mute.createIndex(['muterId', 'muteeId'], { unique: true });
export default Mute;
@@ -13,29 +17,38 @@ export interface IMute {
muteeId: mongo.ObjectID;
}
/**
* Muteを物理削除します
*/
export async function deleteMute(mute: string | mongo.ObjectID | IMute) {
let m: IMute;
export const packMany = (
mutes: (string | mongo.ObjectID | IMute)[],
me?: string | mongo.ObjectID | IUser
) => {
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)) {
m = await Mute.findOne({
_mute = await Mute.findOne({
_id: mute
});
} else if (typeof mute === 'string') {
m = await Mute.findOne({
_mute = await Mute.findOne({
_id: new mongo.ObjectID(mute)
});
} else {
m = mute as IMute;
_mute = deepcopy(mute);
}
if (m == null) return;
// Rename _id to id
_mute.id = _mute._id;
delete _mute._id;
// このMuteを削除
await Mute.remove({
_id: m._id
});
}
// Populate mutee
_mute.mutee = await packUser(_mute.muteeId, me);
resolve(_mute);
});

View File

@@ -7,6 +7,8 @@ import Reaction from './note-reaction';
import { pack as packUser } from './user';
const NoteReaction = db.get<INoteReaction>('noteReactions');
NoteReaction.createIndex('noteId');
NoteReaction.createIndex('userId');
NoteReaction.createIndex(['userId', 'noteId'], { unique: true });
export default NoteReaction;
@@ -31,33 +33,6 @@ export const validateReaction = $.str.or([
'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
*/

View File

@@ -2,6 +2,8 @@ import * as mongo from 'mongodb';
import db from '../db/mongodb';
const NoteUnread = db.get<INoteUnread>('noteUnreads');
NoteUnread.createIndex('userId');
NoteUnread.createIndex('noteId');
NoteUnread.createIndex(['userId', 'noteId'], { unique: true });
export default NoteUnread;

View File

@@ -1,8 +1,9 @@
import * as mongo from 'mongodb';
import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const NoteWatching = db.get<INoteWatching>('noteWatching');
NoteWatching.createIndex('userId');
NoteWatching.createIndex('noteId');
NoteWatching.createIndex(['userId', 'noteId'], { unique: true });
export default NoteWatching;
@@ -12,30 +13,3 @@ export interface INoteWatching {
userId: 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
});
}

View File

@@ -6,13 +6,10 @@ import isObjectId from '../misc/is-objectid';
import { length } from 'stringz';
import { IUser, pack as packUser } from './user';
import { pack as packApp } from './app';
import PollVote, { deletePollVote } from './poll-vote';
import Reaction, { deleteNoteReaction } from './note-reaction';
import PollVote from './poll-vote';
import Reaction from './note-reaction';
import { packMany as packFileMany, IDriveFile } from './drive-file';
import NoteWatching, { deleteNoteWatching } from './note-watching';
import NoteReaction from './note-reaction';
import Favorite, { deleteFavorite } from './favorite';
import Notification, { deleteNotification } from './notification';
import Favorite from './favorite';
import Following from './following';
import config from '../config';
@@ -108,72 +105,6 @@ export type INote = {
_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) => {
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)[],
me?: string | mongo.ObjectID | IUser,
options?: {
@@ -241,7 +172,7 @@ export const packMany = async (
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)));
};
/**

View File

@@ -51,37 +51,10 @@ export interface INotification {
isRead: Boolean;
}
/**
* 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 (
export const packMany = (
notifications: any[]
) => {
return (await Promise.all(notifications.map(n => pack(n)))).filter(x => x != null);
return Promise.all(notifications.map(n => pack(n)));
};
/**

View File

@@ -1,6 +1,5 @@
import * as mongo from 'mongodb';
import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const PollVote = db.get<IPollVote>('pollVotes');
export default PollVote;
@@ -12,30 +11,3 @@ export interface IPollVote {
noteId: mongo.ObjectID;
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
});
}

View File

@@ -1,6 +1,5 @@
import * as mongo from 'mongodb';
import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
const SwSubscription = db.get<ISwSubscription>('swSubscriptions');
export default SwSubscription;
@@ -12,30 +11,3 @@ export interface ISwSubscription {
auth: 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
});
}

View File

@@ -14,33 +14,6 @@ export interface IUserList {
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 = (
userList: string | mongo.ObjectID | IUserList
) => new Promise<any>(async (resolve, reject) => {

View File

@@ -1,27 +1,15 @@
import * as mongo from 'mongodb';
const deepcopy = require('deepcopy');
const sequential = require('promise-sequential');
import rap from '@prezzemolo/rap';
import db from '../db/mongodb';
import isObjectId from '../misc/is-objectid';
import Note, { packMany as packNoteMany, deleteNote } from './note';
import Following, { deleteFollowing } from './following';
import Mute, { deleteMute } from './mute';
import { packMany as packNoteMany } from './note';
import Following from './following';
import Blocking from './blocking';
import Mute from './mute';
import { getFriendIds } from '../server/api/common/get-friends';
import config from '../config';
import AccessToken, { deleteAccessToken } from './access-token';
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';
import FollowRequest from './follow-request';
const User = db.get<IUser>('users');
@@ -167,151 +155,6 @@ export function isValidBirthday(birthday: string): boolean {
}
//#endregion
/**
* Userを物理削除します
*/
export async function deleteUser(user: string | mongo.ObjectID | IUser) {
let u: IUser;
// Populate
if (isObjectId(user)) {
u = await User.findOne({
_id: user
});
} else if (typeof user === 'string') {
u = await User.findOne({
_id: new mongo.ObjectID(user)
});
} else {
u = user as IUser;
}
console.log(u == null ? `User: delete skipped ${user}` : `User: deleting ${u._id}`);
if (u == null) return;
// このユーザーのAccessTokenをすべて削除
await Promise.all((
await AccessToken.find({ userId: u._id })
).map(x => deleteAccessToken(x)));
// このユーザーのNoteをすべて削除
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 }
})
));
// このユーザーを削除
await User.remove({
_id: u._id
});
console.log(`User: deleted ${u._id}`);
}
/**
* Pack a user for API response
*
@@ -426,8 +269,8 @@ export const pack = (
delete _user.hasUnreadNotification;
}
if (meId && !meId.equals(_user.id)) {
const [following1, following2, followReq1, followReq2, mute] = await Promise.all([
if (meId && !meId.equals(_user.id) && opts.detail) {
const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([
Following.findOne({
followerId: meId,
followeeId: _user.id
@@ -444,6 +287,14 @@ export const pack = (
followerId: _user.id,
followeeId: meId
}),
Blocking.findOne({
blockerId: meId,
blockeeId: _user.id
}),
Blocking.findOne({
blockerId: _user.id,
blockeeId: meId
}),
Mute.findOne({
muterId: meId,
muteeId: _user.id
@@ -460,6 +311,12 @@ export const pack = (
// Whether the user is followed
_user.isFollowed = following2 !== null;
// Whether the user is blocking
_user.isBlocking = toBlocking !== null;
// Whether the user is blocked
_user.isBlocked = fromBlocked !== null;
// Whether the user is muted
_user.isMuted = mute !== null;
}

View 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);
};

View File

@@ -10,6 +10,7 @@ import accept from './accept';
import reject from './reject';
import add from './add';
import remove from './remove';
import block from './block';
const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
switch (activity.type) {
@@ -53,6 +54,10 @@ const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
await undo(actor, activity);
break;
case 'Block':
await block(actor, activity);
break;
case 'Collection':
case 'OrderedCollection':
// TODO

View 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);
};

View File

@@ -1,8 +1,9 @@
import * as debug from 'debug';
import { IRemoteUser } from '../../../../models/user';
import { IUndo, IFollow } from '../../type';
import { IUndo, IFollow, IBlock } from '../../type';
import unfollow from './follow';
import unblock from './block';
import Resolver from '../../resolver';
const log = debug('misskey:activitypub');
@@ -31,6 +32,9 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => {
case 'Follow':
unfollow(actor, object as IFollow);
break;
case 'Block':
unblock(actor, object as IBlock);
break;
}
return null;

View 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
});

View File

@@ -108,6 +108,10 @@ export interface IAnnounce extends IActivity {
type: 'Announce';
}
export interface IBlock extends IActivity {
type: 'Block';
}
export type Object =
ICollection |
IOrderedCollection |
@@ -120,4 +124,5 @@ export type Object =
IAdd |
IRemove |
ILike |
IAnnounce;
IAnnounce |
IBlock;

View File

@@ -1,36 +1,83 @@
import { toUnicode, toASCII } from 'punycode';
import User, { IUser } from '../models/user';
import User, { IUser, IRemoteUser } from '../models/user';
import webFinger from './webfinger';
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();
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 host = toUnicode(hostAscii);
if (config.host == host) {
log(`return local user: ${usernameLower}`);
return await User.findOne({ usernameLower, host: null });
}
let user = await User.findOne({ usernameLower, host }, option);
const user = await User.findOne({ usernameLower, host }, option);
const acctLower = `${usernameLower}@${hostAscii}`;
if (user === null) {
const acctLower = `${usernameLower}@${hostAscii}`;
const self = await resolveSelf(acctLower);
const finger = await webFinger(acctLower);
const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self');
if (!self) {
throw new Error('self link not found');
}
user = await createPerson(self.href);
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 self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self');
if (!self) {
throw new Error('self link not found');
}
return self;
}

View File

@@ -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)`);
}
} catch (e) {
rej(e);
if (e.name == 'INVALID_PARAM') {
rej({
code: e.name,
param: e.param,
reason: e.message
});
} else {
rej(e);
}
return;
}

View File

@@ -41,7 +41,7 @@ async function fetchAny(uri: string) {
// URIがこのサーバーを指しているなら、ローカルユーザーIDとしてDBからフェッチ
if (uri.startsWith(config.url + '/')) {
const id = new mongo.ObjectID(uri.split('/').pop());
const [ user, note ] = await Promise.all([
const [user, note] = await Promise.all([
User.findOne({ _id: id }),
Note.findOne({ _id: id })
]);
@@ -52,7 +52,7 @@ async function fetchAny(uri: string) {
// URI(AP Object id)としてDB検索
{
const [ user, note ] = await Promise.all([
const [user, note] = await Promise.all([
User.findOne({ uri: uri }),
Note.findOne({ uri: uri })
]);
@@ -68,7 +68,7 @@ async function fetchAny(uri: string) {
// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
// これはDBに存在する可能性があるため再度DB検索
if (uri !== object.id) {
const [ user, note ] = await Promise.all([
const [user, note] = await Promise.all([
User.findOne({ uri: object.id }),
Note.findOne({ uri: object.id })
]);

View File

@@ -0,0 +1,77 @@
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
const ms = require('ms');
import User, { pack, ILocalUser } from '../../../../models/user';
import Blocking from '../../../../models/blocking';
import create from '../../../../services/blocking/create';
import getParams from '../../get-params';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定したユーザーをブロックします。',
'en-US': 'Block a user.'
},
limit: {
duration: ms('1hour'),
max: 100
},
requireCredential: true,
kind: 'following-write',
params: {
userId: $.type(ID).note({
desc: {
'ja-JP': '対象のユーザーのID',
'en-US': 'Target user ID'
}
})
}
};
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
const blocker = user;
// 自分自身
if (user._id.equals(ps.userId)) {
return rej('blockee is yourself');
}
// Get blockee
const blockee = await User.findOne({
_id: ps.userId
}, {
fields: {
data: false,
profile: false
}
});
if (blockee === null) {
return rej('user not found');
}
// Check if already blocking
const exist = await Blocking.findOne({
blockerId: blocker._id,
blockeeId: blockee._id
});
if (exist !== null) {
return rej('already blocking');
}
// Create blocking
await create(blocker, blockee);
// Send response
res(await pack(blockee._id, user, {
detail: true
}));
});

View File

@@ -0,0 +1,77 @@
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
const ms = require('ms');
import User, { pack, ILocalUser } from '../../../../models/user';
import Blocking from '../../../../models/blocking';
import deleteBlocking from '../../../../services/blocking/delete';
import getParams from '../../get-params';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定したユーザーのブロックを解除します。',
'en-US': 'Unblock a user.'
},
limit: {
duration: ms('1hour'),
max: 100
},
requireCredential: true,
kind: 'following-write',
params: {
userId: $.type(ID).note({
desc: {
'ja-JP': '対象のユーザーのID',
'en-US': 'Target user ID'
}
})
}
};
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
const blocker = user;
// Check if the blockee is yourself
if (user._id.equals(ps.userId)) {
return rej('blockee is yourself');
}
// Get blockee
const blockee = await User.findOne({
_id: ps.userId
}, {
fields: {
data: false,
'profile': false
}
});
if (blockee === null) {
return rej('user not found');
}
// Check not blocking
const exist = await Blocking.findOne({
blockerId: blocker._id,
blockeeId: blockee._id
});
if (exist === null) {
return rej('already not blocking');
}
// Delete blocking
await deleteBlocking(blocker, blockee);
// Send response
res(await pack(blockee._id, user, {
detail: true
}));
});

View File

@@ -0,0 +1,64 @@
import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
import Blocking, { packMany } from '../../../../models/blocking';
import { ILocalUser } from '../../../../models/user';
import getParams from '../../get-params';
export const meta = {
desc: {
'ja-JP': 'ブロックしているユーザー一覧を取得します。',
'en-US': 'Get blocking users.'
},
requireCredential: true,
kind: 'following-read',
params: {
limit: $.num.optional.range(1, 100).note({
default: 30
}),
sinceId: $.type(ID).optional.note({
}),
untilId: $.type(ID).optional.note({
}),
}
};
export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
// Check if both of sinceId and untilId is specified
if (ps.sinceId && ps.untilId) {
return rej('cannot set sinceId and untilId');
}
const query = {
blockerId: me._id
} as any;
const sort = {
_id: -1
};
if (ps.sinceId) {
sort._id = 1;
query._id = {
$gt: ps.sinceId
};
} else if (ps.untilId) {
query._id = {
$lt: ps.untilId
};
}
const blockings = await Blocking
.find(query, {
limit: ps.limit,
sort: sort
});
res(await packMany(blockings, me));
});

View File

@@ -25,7 +25,7 @@ export const meta = {
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
if (psErr) return rej(psErr);
const stats = await driveChart.getChart(ps.span as any, ps.limit);

View File

@@ -25,7 +25,7 @@ export const meta = {
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
if (psErr) return rej(psErr);
const stats = await federationChart.getChart(ps.span as any, ps.limit);

View File

@@ -31,7 +31,7 @@ export const meta = {
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
if (psErr) return rej(psErr);
const stats = await hashtagChart.getChart(ps.span as any, ps.limit, ps.tag);

View File

@@ -25,7 +25,7 @@ export const meta = {
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
if (psErr) return rej(psErr);
const stats = await networkChart.getChart(ps.span as any, ps.limit);

View File

@@ -25,7 +25,7 @@ export const meta = {
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
if (psErr) return rej(psErr);
const stats = await notesChart.getChart(ps.span as any, ps.limit);

View File

@@ -33,7 +33,7 @@ export const meta = {
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
if (psErr) return rej(psErr);
const stats = await perUserDriveChart.getChart(ps.span as any, ps.limit, ps.userId);

View File

@@ -33,7 +33,7 @@ export const meta = {
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
if (psErr) return rej(psErr);
const stats = await perUserFollowingChart.getChart(ps.span as any, ps.limit, ps.userId);

View File

@@ -33,7 +33,7 @@ export const meta = {
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
if (psErr) return rej(psErr);
const stats = await perUserNotesChart.getChart(ps.span as any, ps.limit, ps.userId);

View File

@@ -33,7 +33,7 @@ export const meta = {
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
if (psErr) return rej(psErr);
const stats = await perUserReactionsChart.getChart(ps.span as any, ps.limit, ps.userId);

View File

@@ -25,7 +25,7 @@ export const meta = {
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
if (psErr) return rej(psErr);
const stats = await usersChart.getChart(ps.span as any, ps.limit);

View File

@@ -44,7 +44,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
const [childFoldersCount, childFilesCount] = await Promise.all([
DriveFolder.count({ parentId: folder._id }),
DriveFile.count({ folderId: folder._id })
DriveFile.count({ 'metadata.folderId': folder._id })
]);
if (childFoldersCount !== 0 || childFilesCount !== 0) {

View File

@@ -68,7 +68,11 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
}
// Create following
await create(follower, followee);
try {
await create(follower, followee);
} catch (e) {
return rej(e && e.message ? e.message : e);
}
// Send response
res(await pack(followee._id, user));

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