Compare commits

...

48 Commits

Author SHA1 Message Date
7a406c1f13 Docker (#2867)
* Dockerize Misskey

* Add a new line at EOF

* Add support Elasticsearch

* /

* Add setup document for docker

* Add english document

* Edit docs

* docker -> Docker

* Arrange format

* Update docker.en.md

* Modify title
2018-10-09 15:09:50 +09:00
9432af2ab5 10.3.0 2018-10-09 15:09:24 +09:00
136b13e7ca Fix bug and refactor 2018-10-09 15:08:31 +09:00
ba1c823fb1 🎨 2018-10-09 14:58:37 +09:00
f1301a4780 fix(package): update @types/redis to version 2.8.7 (#2866) 2018-10-09 09:38:47 +09:00
7957cd4963 fix(package): update @types/node to version 10.11.5 (#2865) 2018-10-09 09:38:40 +09:00
ee6590d03f fix(package): update @types/mongodb to version 3.1.11 (#2864) 2018-10-09 09:38:31 +09:00
f2a1238b20 fix(package): update commander to version 2.19.0 (#2862) 2018-10-09 09:38:21 +09:00
c464183329 Improve theme manager 2018-10-09 06:46:52 +09:00
389f420cad Update src/tools/move-drive-files.ts 2018-10-09 05:46:21 +09:00
6b2888383c 10.2.1 2018-10-09 05:36:39 +09:00
3c38a867b4 Fix bug 2018-10-09 05:35:40 +09:00
7f5a69f4d8 Fix bug 2018-10-09 05:31:26 +09:00
bb9ab31d5e Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-10-09 05:15:48 +09:00
9def80af8a 10.2.0 2018-10-09 05:15:31 +09:00
9256bcdbe4 fix(package): update debug to version 4.1.0 (#2857) 2018-10-09 05:12:19 +09:00
9b775022bc Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-10-09 05:11:55 +09:00
32371ed2bd Fix #2858 2018-10-09 05:11:42 +09:00
8b98c08a81 Update README.md 2018-10-09 05:07:59 +09:00
7cf72f7447 Merge pull request #2860 from mei23/mei-1009-v10d
互換ストリーム / に main streamの内容も流す
2018-10-09 04:35:08 +09:00
913385b10d / に main stream も流す 2018-10-09 03:29:11 +09:00
7306468d08 Merge pull request #2856 from syuilo/greenkeeper/typescript-eslint-parser-20.0.0
Update typescript-eslint-parser to the latest version 🚀
2018-10-09 02:59:13 +09:00
11e5667778 fix(package): update typescript-eslint-parser to version 20.0.0 2018-10-08 17:15:23 +00:00
38cc02e261 Add tool 2018-10-09 02:14:03 +09:00
d52cf46cc1 10.1.0 2018-10-09 01:52:18 +09:00
c6110dd996 Fix bug
Closes #2855
2018-10-09 01:50:49 +09:00
51d8de2c38 Improve readability 2018-10-09 01:33:40 +09:00
4455a1aa9d Fix bug 2018-10-09 01:33:31 +09:00
040d395ddb 🎨 2018-10-09 01:26:04 +09:00
8296cac636 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2018-10-09 01:02:04 +09:00
3eafe8b87d Better api definition 2018-10-09 01:01:48 +09:00
c01512e261 Merge pull request #2854 from syuilo/greenkeeper/style-loader-0.23.1
Update style-loader to the latest version 🚀
2018-10-09 00:56:03 +09:00
e5cf3aecd5 Merge pull request #2830 from sei0o/fix-2346
fix #2346
2018-10-09 00:55:47 +09:00
a8f90b41b7 fix(package): update style-loader to version 0.23.1 2018-10-08 13:43:10 +00:00
b79169b975 Deleted dump.rdb 2018-10-08 22:26:26 +09:00
437d52e2ed Improve theme 2018-10-08 18:42:19 +09:00
1329721440 Merge pull request #2828 from hakaba-hitoyo/feature/external-user-recommendation
External user recommendation
2018-10-08 18:31:00 +09:00
6affb4fe97 Merge pull request #2851 from mei23/mei-1008-fix-apshow2
ap/showが返ってこないことがあるのを修正
2018-10-08 17:33:46 +09:00
15395686aa ap/showが返ってこないことがあるのを修正 2018-10-08 16:01:38 +09:00
5cf1956135 Added CSS variables for background of reactions-viewer 2018-10-06 22:14:30 +09:00
fff307d4bb fix #2346 2018-10-06 17:51:59 +09:00
2b7782ba03 replace var by const 2018-10-06 17:38:57 +09:00
96d961ee80 better readable code 2018-10-06 17:23:32 +09:00
9f064d76d9 better readable code 2018-10-06 17:21:27 +09:00
2e39106c4b better config handling 2018-10-06 17:19:41 +09:00
04650464f3 debug 2018-10-06 16:34:52 +09:00
3bc7e1e35c remove follow buttons in the friends-maker component/widget 2018-10-06 16:31:21 +09:00
7019ddbfc7 external user recommendation 2018-10-06 16:03:18 +09:00
46 changed files with 639 additions and 256 deletions

View File

@ -159,3 +159,10 @@ drive:
# Summaly proxy
# summalyProxy: "http://example.com"
# User recommendation
user_recommendation:
external: true
engine: http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-misskey-api.cgi?{{host}}+{{user}}+{{limit}}+{{offset}}
timeout: 300000

View File

@ -0,0 +1,13 @@
var user = {
user: 'example-misskey-user',
pwd: 'example-misskey-pass',
roles: [
{
role: 'readWrite',
db: 'misskey'
}
]
};
db.createUser(user);

12
.dockerignore Executable file
View File

@ -0,0 +1,12 @@
.autogen
.git
.github
.travis
.vscode
Dockerfile
build/
docker-compose.yml
node_modules/
mongo/
redis/
elasticsearch/

4
.gitignore vendored
View File

@ -1,5 +1,6 @@
/.config/*
!/.config/example.yml
!/.config/mongo_initdb_example.js
/.vscode
/node_modules
/build
@ -12,3 +13,6 @@ npm-debug.log
run.bat
api-docs.json
*.log
/redis
/mongo
/elasticsearch

28
Dockerfile Normal file
View File

@ -0,0 +1,28 @@
FROM alpine:latest AS base
ENV NODE_ENV=production
RUN apk add --no-cache nodejs nodejs-npm
RUN apk add vips fftw --update-cache --repository https://dl-3.alpinelinux.org/alpine/edge/testing/
WORKDIR /misskey
COPY . ./
FROM base AS builder
RUN apk add --no-cache gcc g++ python autoconf automake file make nasm
RUN apk add vips-dev fftw-dev --update-cache --repository https://dl-3.alpinelinux.org/alpine/edge/testing/
RUN npm install \
&& npm install -g node-gyp \
&& 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", "--"]
CMD ["npm", "start"]

View File

@ -71,6 +71,7 @@ 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>
@ -80,6 +81,7 @@ Please see [Contribution guide](./CONTRIBUTING.md).
<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>

52
docker-compose.yml Normal file
View File

@ -0,0 +1,52 @@
version: "3"
services:
web:
build: .
restart: always
links:
- mongo
- redis
# - es
ports:
- "127.0.0.1:3000:3000"
networks:
- internal_network
- external_network
redis:
restart: always
image: redis:4.0-alpine
networks:
- internal_network
### Uncomment to enable Redis persistance
# volumes:
# - ./redis:/data
mongo:
restart: always
image: mongo:4.1-bionic
networks:
- internal_network
environment:
MONGO_INITDB_DATABASE: "misskey"
volumes:
- ./.config/mongo_initdb.js:/docker-entrypoint-initdb.d/mongo_initdb.js:ro
### Uncomment to enable MongoDB persistance
# - ./mongo:/data
# es:
# restart: always
# image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.2
# environment:
# - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
# networks:
# - internal_network
#### Uncomment to enable ES persistence
## volumes:
## - ./elasticsearch:/usr/share/elasticsearch/data
networks:
internal_network:
internal: true
external_network:

47
docs/docker.en.md Normal file
View File

@ -0,0 +1,47 @@
Docker Guide
================================================================
This guide describes how to install and setup Misskey with Docker.
[Japanese version also available - 日本語版もあります](./docker.ja.md)
----------------------------------------------------------------
*1.* Make configuration files
----------------------------------------------------------------
1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`.
2. `cp .config/mongo_initdb_example.js .config/mongo_initdb.js` Copy the `.config/mongo_initdb_example.js` and rename it to `mongo_initdb.js`.
2. Edit `default.yml` and `mongo_initdb.js`.
*2.* Configure Docker
----------------------------------------------------------------
Edit `docker-compose.yml`.
*3.* Build Misskey
----------------------------------------------------------------
Build misskey with the following:
`docker-compose build`
*4.* That is it.
----------------------------------------------------------------
Well done! Now, you have an environment that run to Misskey.
### Launch normally
Just `docker-compose up -d`. GLHF!
### Way to Update to latest version of your Misskey
1. `git fetch`
2. `git stash`
3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
4. `git stash pop`
5. `docker-compose build`
6. Check [ChangeLog](../CHANGELOG.md) for migration information
7. `docker-compose stop && docker-compose up -d`
### Way to execute cli command:
`docker-compose run --rm web node cli/mark-admin @example`
----------------------------------------------------------------
If you have any questions or troubles, feel free to contact us!

48
docs/docker.ja.md Normal file
View File

@ -0,0 +1,48 @@
Dockerを使ったMisskey構築方法
================================================================
このガイドはDockerを使ったMisskeyセットアップ方法について解説します。
[英語版もあります - English version also available](./docker.en.md)
----------------------------------------------------------------
*1.* 設定ファイルを作成する
----------------------------------------------------------------
1. `cp .config/example.yml .config/default.yml` `.config/example.yml`をコピーし名前を`default.yml`にする
2. `cp .config/mongo_initdb_example.js .config/mongo_initdb.js` `.config/mongo_initdb_example.js`をコピーし名前を`mongo_initdb.js`にする
3. `default.yml``mongo_initdb.js`を編集する
*2.* Dockerの設定
----------------------------------------------------------------
`docker-compose.yml`を編集してください。
*3.* Misskeyのビルド
----------------------------------------------------------------
次のコマンドでMisskeyをビルドしてください:
`docker-compose build`
*4.* 以上です!
----------------------------------------------------------------
お疲れ様でした。これでMisskeyを動かす準備は整いました。
### 通常起動
`docker-compose up -d`するだけです。GLHF!
### Misskeyを最新バージョンにアップデートする方法:
1. `git fetch`
2. `git stash`
3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
4. `git stash pop`
5. `docker-compose build`
6. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する
7. `docker-compose stop && docker-compose up -d`
### cliコマンドを実行する方法:
`docker-compose run --rm web node cli/mark-admin @example`
----------------------------------------------------------------
なにかお困りのことがありましたらお気軽にご連絡ください。

View File

@ -307,6 +307,9 @@ common/views/components/theme.vue:
invalid-theme: "テーマが正しくありません。"
already-installed: "既にそのテーマはインストールされています。"
saved: "保存しました"
manage-themes: "テーマの管理"
builtin-themes: "標準テーマ"
my-themes: "マイテーマ"
installed-themes: "インストールされたテーマ"
select-theme: "テーマを選択してください"
uninstall: "アンインストール"

View File

@ -1,8 +1,8 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "10.0.0",
"clientVersion": "1.0.10328",
"version": "10.3.0",
"clientVersion": "1.0.10375",
"codename": "nighthike",
"main": "./built/index.js",
"private": true,
@ -58,14 +58,14 @@
"@types/minio": "7.0.0",
"@types/mkdirp": "0.5.2",
"@types/mocha": "5.2.3",
"@types/mongodb": "3.1.10",
"@types/mongodb": "3.1.11",
"@types/ms": "0.7.30",
"@types/node": "10.11.4",
"@types/node": "10.11.5",
"@types/portscanner": "2.1.0",
"@types/pug": "2.0.4",
"@types/qrcode": "1.3.0",
"@types/ratelimiter": "2.1.28",
"@types/redis": "2.8.6",
"@types/redis": "2.8.7",
"@types/request": "2.47.1",
"@types/request-promise-native": "1.0.15",
"@types/rimraf": "2.0.2",
@ -92,11 +92,11 @@
"cafy": "11.3.0",
"chalk": "2.4.1",
"chart.js": "2.7.2",
"commander": "2.17.1",
"commander": "2.19.0",
"crc-32": "1.2.0",
"css-loader": "1.0.0",
"dateformat": "3.0.3",
"debug": "4.0.1",
"debug": "4.1.0",
"deep-equal": "1.0.1",
"deepcopy": "0.6.3",
"diskusage": "0.2.5",
@ -191,7 +191,7 @@
"single-line-log": "1.1.2",
"speakeasy": "2.0.0",
"stringz": "1.0.0",
"style-loader": "0.23.0",
"style-loader": "0.23.1",
"stylus": "0.54.5",
"stylus-loader": "3.0.2",
"summaly": "2.2.0",
@ -204,7 +204,7 @@
"ts-node": "7.0.1",
"tslint": "5.10.0",
"typescript": "2.9.2",
"typescript-eslint-parser": "19.0.2",
"typescript-eslint-parser": "20.0.0",
"uglify-es": "3.3.9",
"url-loader": "1.1.1",
"uuid": "3.3.2",
@ -219,6 +219,7 @@
"vue-router": "3.0.1",
"vue-style-loader": "4.1.2",
"vue-svg-inline-loader": "1.2.0",
"vue-sweetalert2": "1.5.5",
"vue-template-compiler": "2.5.17",
"vuedraggable": "2.16.0",
"vuewordcloud": "18.7.11",

View File

@ -130,3 +130,16 @@ pre
[data-fa]
display inline-block
.swal2-popup
background var(--face) !important
.swal-icon-only
width 180px !important
> .swal2-header
> .swal2-icon
margin 1.25em auto 1.875em
> .swal2-title
display none

View File

@ -13,14 +13,14 @@ export default prop => ({
},
$_ns_isRenote(): boolean {
return (this.$_ns_note_.renote &&
return (this.$_ns_note_.renote != null &&
this.$_ns_note_.text == null &&
this.$_ns_note_.fileIds.length == 0 &&
this.$_ns_note_.poll == null);
},
$_ns_target(): any {
return this._ns_isRenote ? this.$_ns_note_.renote : this.$_ns_note_;
return this.$_ns_isRenote ? this.$_ns_note_.renote : this.$_ns_note_;
},
},
@ -86,8 +86,20 @@ export default prop => ({
switch (type) {
case 'reacted': {
const reaction = body.reaction;
if (this.$_ns_target.reactionCounts == null) Vue.set(this.$_ns_target, 'reactionCounts', {});
this.$_ns_target.reactionCounts[reaction] = (this.$_ns_target.reactionCounts[reaction] || 0) + 1;
if (this.$_ns_target.reactionCounts == null) {
Vue.set(this.$_ns_target, 'reactionCounts', {});
}
if (this.$_ns_target.reactionCounts[reaction] == null) {
Vue.set(this.$_ns_target.reactionCounts, reaction, 0);
}
this.$_ns_target.reactionCounts[reaction]++;
if (body.userId == this.$store.state.i.id) {
Vue.set(this.$_ns_target, 'myReaction', reaction);
}
break;
}

View File

@ -26,92 +26,6 @@ export default class Stream extends EventEmitter {
this.stream.addEventListener('open', this.onOpen);
this.stream.addEventListener('close', this.onClose);
this.stream.addEventListener('message', this.onMessage);
if (user) {
const main = this.useSharedConnection('main');
// 自分の情報が更新されたとき
main.on('meUpdated', i => {
os.store.dispatch('mergeMe', i);
});
main.on('readAllNotifications', () => {
os.store.dispatch('mergeMe', {
hasUnreadNotification: false
});
});
main.on('unreadNotification', () => {
os.store.dispatch('mergeMe', {
hasUnreadNotification: true
});
});
main.on('readAllMessagingMessages', () => {
os.store.dispatch('mergeMe', {
hasUnreadMessagingMessage: false
});
});
main.on('unreadMessagingMessage', () => {
os.store.dispatch('mergeMe', {
hasUnreadMessagingMessage: true
});
});
main.on('unreadMention', () => {
os.store.dispatch('mergeMe', {
hasUnreadMentions: true
});
});
main.on('readAllUnreadMentions', () => {
os.store.dispatch('mergeMe', {
hasUnreadMentions: false
});
});
main.on('unreadSpecifiedNote', () => {
os.store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: true
});
});
main.on('readAllUnreadSpecifiedNotes', () => {
os.store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: false
});
});
main.on('clientSettingUpdated', x => {
os.store.commit('settings/set', {
key: x.key,
value: x.value
});
});
main.on('homeUpdated', x => {
os.store.commit('settings/setHome', x);
});
main.on('mobileHomeUpdated', x => {
os.store.commit('settings/setMobileHome', x);
});
main.on('widgetUpdated', x => {
os.store.commit('settings/setWidget', {
id: x.id,
data: x.data
});
});
// トークンが再生成されたとき
// このままではMisskeyが利用できないので強制的にサインアウトさせる
main.on('myTokenRegenerated', () => {
alert('%i18n:common.my-token-regenerated%');
os.signout();
});
}
}
public useSharedConnection = (channel: string): SharedConnection => {
@ -253,14 +167,13 @@ abstract class Connection extends EventEmitter {
@autobind
public send(typeOrPayload, payload?) {
const data = payload === undefined ? typeOrPayload : {
type: typeOrPayload,
body: payload
};
const type = payload === undefined ? typeOrPayload.type : typeOrPayload;
const body = payload === undefined ? typeOrPayload.body : payload;
this.stream.send('channel', {
id: this.id,
body: data
type: type,
body: body
});
}

View File

@ -186,9 +186,8 @@ export default Vue.extend({
if (this.game.isStarted && !this.game.isEnded) {
this.pollingClock = setInterval(() => {
const crc32 = CRC32.str(this.logs.map(x => x.pos.toString()).join(''));
this.connection.send({
type: 'check',
crc32
this.connection.send('check', {
crc32: crc32
});
}, 3000);
}
@ -224,9 +223,8 @@ export default Vue.extend({
sound.play();
}
this.connection.send({
type: 'set',
pos
this.connection.send('set', {
pos: pos
});
this.checkEnd();

View File

@ -149,9 +149,9 @@ export default Vue.extend({
},
created() {
this.connection.on('change-accepts', this.onChangeAccepts);
this.connection.on('update-settings', this.onUpdateSettings);
this.connection.on('init-form', this.onInitForm);
this.connection.on('changeAccepts', this.onChangeAccepts);
this.connection.on('updateSettings', this.onUpdateSettings);
this.connection.on('initForm', this.onInitForm);
this.connection.on('message', this.onMessage);
if (this.game.user1Id != this.$store.state.i.id && this.game.settings.form1) this.form = this.game.settings.form1;
@ -159,9 +159,9 @@ export default Vue.extend({
},
beforeDestroy() {
this.connection.off('change-accepts', this.onChangeAccepts);
this.connection.off('update-settings', this.onUpdateSettings);
this.connection.off('init-form', this.onInitForm);
this.connection.off('changeAccepts', this.onChangeAccepts);
this.connection.off('updateSettings', this.onUpdateSettings);
this.connection.off('initForm', this.onInitForm);
this.connection.off('message', this.onMessage);
},
@ -171,15 +171,11 @@ export default Vue.extend({
},
accept() {
this.connection.send({
type: 'accept'
});
this.connection.send('accept', {});
},
cancel() {
this.connection.send({
type: 'cancel-accept'
});
this.connection.send('cancelAccept', {});
},
onChangeAccepts(accepts) {
@ -189,8 +185,7 @@ export default Vue.extend({
},
updateSettings() {
this.connection.send({
type: 'update-settings',
this.connection.send('updateSettings', {
settings: this.game.settings
});
},
@ -216,8 +211,7 @@ export default Vue.extend({
},
onChangeForm(item) {
this.connection.send({
type: 'update-form',
this.connection.send('updateForm', {
id: item.id,
value: item.value
});
@ -238,9 +232,9 @@ export default Vue.extend({
const y = Math.floor(pos / this.game.settings.map[0].length);
const newPixel =
pixel == ' ' ? '-' :
pixel == '-' ? 'b' :
pixel == 'b' ? 'w' :
' ';
pixel == '-' ? 'b' :
pixel == 'b' ? 'w' :
' ';
const line = this.game.settings.map[y].split('');
line[x] = newPixel;
this.$set(this.game.settings.map, y, line.join(''));

View File

@ -71,8 +71,7 @@ export default Vue.extend({
this.pingClock = setInterval(() => {
if (this.matching) {
this.connection.send({
type: 'ping',
this.connection.send('ping', {
id: this.matching.id
});
}

View File

@ -174,8 +174,7 @@ export default Vue.extend({
this.messages.push(message);
if (message.userId != this.$store.state.i.id && !document.hidden) {
this.connection.send({
type: 'read',
this.connection.send('read', {
id: message.id
});
}
@ -247,8 +246,7 @@ export default Vue.extend({
if (document.hidden) return;
this.messages.forEach(message => {
if (message.userId !== this.$store.state.i.id && !message.isRead) {
this.connection.send({
type: 'read',
this.connection.send('read', {
id: message.id
});
}

View File

@ -103,6 +103,12 @@ export default Vue.extend({
(this as any).api('notes/favorites/create', {
noteId: this.note.id
}).then(() => {
this.$swal({
type: 'success',
showConfirmButton: false,
timer: 1250,
customClass: 'swal-icon-only'
});
this.destroyDom();
});
},

View File

@ -1,16 +1,16 @@
<template>
<div class="mk-reactions-viewer">
<template v-if="reactions">
<span :class="{notReacted}" @click="react('like')" v-if="reactions.like"><mk-reaction-icon reaction="like"/><span>{{ reactions.like }}</span></span>
<span :class="{notReacted}" @click="react('love')" v-if="reactions.love"><mk-reaction-icon reaction="love"/><span>{{ reactions.love }}</span></span>
<span :class="{notReacted}" @click="react('laugh')" v-if="reactions.laugh"><mk-reaction-icon reaction="laugh"/><span>{{ reactions.laugh }}</span></span>
<span :class="{notReacted}" @click="react('hmm')" v-if="reactions.hmm"><mk-reaction-icon reaction="hmm"/><span>{{ reactions.hmm }}</span></span>
<span :class="{notReacted}" @click="react('surprise')" v-if="reactions.surprise"><mk-reaction-icon reaction="surprise"/><span>{{ reactions.surprise }}</span></span>
<span :class="{notReacted}" @click="react('congrats')" v-if="reactions.congrats"><mk-reaction-icon reaction="congrats"/><span>{{ reactions.congrats }}</span></span>
<span :class="{notReacted}" @click="react('angry')" v-if="reactions.angry"><mk-reaction-icon reaction="angry"/><span>{{ reactions.angry }}</span></span>
<span :class="{notReacted}" @click="react('confused')" v-if="reactions.confused"><mk-reaction-icon reaction="confused"/><span>{{ reactions.confused }}</span></span>
<span :class="{notReacted}" @click="react('rip')" v-if="reactions.rip"><mk-reaction-icon reaction="rip"/><span>{{ reactions.rip }}</span></span>
<span :class="{notReacted}" @click="react('pudding')" v-if="reactions.pudding"><mk-reaction-icon reaction="pudding"/><span>{{ reactions.pudding }}</span></span>
<span :class="{ reacted: note.myReaction == 'like' }" @click="react('like')" v-if="reactions.like"><mk-reaction-icon reaction="like"/><span>{{ reactions.like }}</span></span>
<span :class="{ reacted: note.myReaction == 'love' }" @click="react('love')" v-if="reactions.love"><mk-reaction-icon reaction="love"/><span>{{ reactions.love }}</span></span>
<span :class="{ reacted: note.myReaction == 'laugh' }" @click="react('laugh')" v-if="reactions.laugh"><mk-reaction-icon reaction="laugh"/><span>{{ reactions.laugh }}</span></span>
<span :class="{ reacted: note.myReaction == 'hmm' }" @click="react('hmm')" v-if="reactions.hmm"><mk-reaction-icon reaction="hmm"/><span>{{ reactions.hmm }}</span></span>
<span :class="{ reacted: note.myReaction == 'surprise' }" @click="react('surprise')" v-if="reactions.surprise"><mk-reaction-icon reaction="surprise"/><span>{{ reactions.surprise }}</span></span>
<span :class="{ reacted: note.myReaction == 'congrats' }" @click="react('congrats')" v-if="reactions.congrats"><mk-reaction-icon reaction="congrats"/><span>{{ reactions.congrats }}</span></span>
<span :class="{ reacted: note.myReaction == 'angry' }" @click="react('angry')" v-if="reactions.angry"><mk-reaction-icon reaction="angry"/><span>{{ reactions.angry }}</span></span>
<span :class="{ reacted: note.myReaction == 'confused' }" @click="react('confused')" v-if="reactions.confused"><mk-reaction-icon reaction="confused"/><span>{{ reactions.confused }}</span></span>
<span :class="{ reacted: note.myReaction == 'rip' }" @click="react('rip')" v-if="reactions.rip"><mk-reaction-icon reaction="rip"/><span>{{ reactions.rip }}</span></span>
<span :class="{ reacted: note.myReaction == 'pudding' }" @click="react('pudding')" v-if="reactions.pudding"><mk-reaction-icon reaction="pudding"/><span>{{ reactions.pudding }}</span></span>
</template>
</div>
</template>
@ -22,9 +22,6 @@ export default Vue.extend({
computed: {
reactions(): number {
return this.note.reactionCounts;
},
notReacted(): boolean {
return this.note.myReaction == null;
}
},
methods: {
@ -40,25 +37,42 @@ export default Vue.extend({
<style lang="stylus" scoped>
.mk-reactions-viewer
border-top dashed 1px var(--reactionViewerBorder)
border-bottom dashed 1px var(--reactionViewerBorder)
margin 4px 0
margin 6px 0
&:empty
display none
> span
margin-right 8px
display inline-block
height 32px
margin-right 6px
padding 0 6px
border-radius 4px
&.notReacted
*
user-select none
pointer-events none
&.reacted
background var(--primary)
> span
color var(--primaryForeground)
&:not(.reacted)
cursor pointer
background var(--reactionViewerButtonBg)
&:hover
background var(--reactionViewerButtonHoverBg)
> .mk-reaction-icon
font-size 1.4em
> span
margin-left 4px
font-size 1.2em
font-size 1.1em
line-height 32px
vertical-align middle
color var(--text)
</style>

View File

@ -67,22 +67,30 @@
</details>
<details>
<summary>%fa:folder-open% %i18n:@installed-themes%</summary>
<ui-select v-model="selectedInstalledThemeId" placeholder="%i18n:@select-theme%">
<option v-for="x in installedThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
<summary>%fa:folder-open% %i18n:@manage-themes%</summary>
<ui-select v-model="selectedThemeId" placeholder="%i18n:@select-theme%">
<optgroup label="%i18n:@builtin-themes%">
<option v-for="x in builtinThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
<optgroup label="%i18n:@my-themes%">
<option v-for="x in installedThemes.filter(t => t.author == this.$store.state.i.username)" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
<optgroup label="%i18n:@installed-themes%">
<option v-for="x in installedThemes.filter(t => t.author != this.$store.state.i.username)" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
</ui-select>
<template v-if="selectedInstalledTheme">
<ui-input readonly :value="selectedInstalledTheme.author">
<template v-if="selectedTheme">
<ui-input readonly :value="selectedTheme.author">
<span>%i18n:@author%</span>
</ui-input>
<ui-textarea v-if="selectedInstalledTheme.desc" readonly :value="selectedInstalledTheme.desc">
<ui-textarea v-if="selectedTheme.desc" readonly :value="selectedTheme.desc">
<span>%i18n:@desc%</span>
</ui-textarea>
<ui-textarea readonly :value="selectedInstalledThemeCode">
<ui-textarea readonly :value="selectedThemeCode">
<span>%i18n:@theme-code%</span>
</ui-textarea>
<ui-button @click="export_()" link :download="`${selectedInstalledTheme.name}.misskeytheme`" ref="export">%fa:box% %i18n:@export%</ui-button>
<ui-button @click="uninstall()">%fa:trash-alt R% %i18n:@uninstall%</ui-button>
<ui-button @click="export_()" link :download="`${selectedTheme.name}.misskeytheme`" ref="export">%fa:box% %i18n:@export%</ui-button>
<ui-button @click="uninstall()" v-if="!builtinThemes.some(t => t.id == selectedTheme.id)">%fa:trash-alt R% %i18n:@uninstall%</ui-button>
</template>
</details>
</div>
@ -117,8 +125,9 @@ export default Vue.extend({
data() {
return {
builtinThemes: builtinThemes,
installThemeCode: null,
selectedInstalledThemeId: null,
selectedThemeId: null,
myThemeBase: 'light',
myThemeName: '',
myThemeDesc: '',
@ -155,14 +164,14 @@ export default Vue.extend({
set(value) { this.$store.commit('device/set', { key: 'darkTheme', value }); }
},
selectedInstalledTheme() {
if (this.selectedInstalledThemeId == null) return null;
return this.installedThemes.find(x => x.id == this.selectedInstalledThemeId);
selectedTheme() {
if (this.selectedThemeId == null) return null;
return this.themes.find(x => x.id == this.selectedThemeId);
},
selectedInstalledThemeCode() {
if (this.selectedInstalledTheme == null) return null;
return JSON5.stringify(this.selectedInstalledTheme, null, '\t');
selectedThemeCode() {
if (this.selectedTheme == null) return null;
return JSON5.stringify(this.selectedTheme, null, '\t');
},
myTheme(): any {
@ -238,7 +247,7 @@ export default Vue.extend({
},
uninstall() {
const theme = this.selectedInstalledTheme;
const theme = this.selectedTheme;
const themes = this.$store.state.device.themes.filter(t => t.id != theme.id);
this.$store.commit('device/set', {
key: 'themes', value: themes
@ -251,7 +260,7 @@ export default Vue.extend({
}
export_() {
const blob = new Blob([this.selectedInstalledThemeCode], {
const blob = new Blob([this.selectedThemeCode], {
type: 'application/json5'
});
this.$refs.export.$el.href = window.URL.createObjectURL(blob);

View File

@ -113,8 +113,7 @@ export default define({
this.connection.on('stats', this.onStats);
this.connection.on('statsLog', this.onStatsLog);
this.connection.send({
type: 'requestLog',
this.connection.send('requestLog',{
id: Math.random().toString()
});
},

View File

@ -91,8 +91,7 @@ export default Vue.extend({
mounted() {
this.connection.on('stats', this.onStats);
this.connection.on('statsLog', this.onStatsLog);
this.connection.send({
type: 'requestLog',
this.connection.send('requestLog', {
id: Math.random().toString()
});
},

View File

@ -8,7 +8,6 @@
<router-link class="name" :to="user | userPage" v-user-preview="user.id">{{ user | userName }}</router-link>
<p class="username">@{{ user | acct }}</p>
</div>
<mk-follow-button :user="user"/>
</div>
</div>
<p class="empty" v-if="!fetching && users.length == 0">%i18n:@empty%</p>

View File

@ -181,8 +181,7 @@ export default Vue.extend({
onNotification(notification) {
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
this.connection.send({
type: 'readNotification',
(this as any).os.stream.send('readNotification', {
id: notification.id
});

View File

@ -26,12 +26,14 @@ export default Vue.extend({
this.init();
},
beforeDestroy() {
this.connection.close();
this.connection.dispose();
},
methods: {
init() {
if (this.connection) this.connection.close();
this.connection = new UserListStream((this as any).os, this.$store.state.i, this.list.id);
if (this.connection) this.connection.dispose();
this.connection = (this as any).os.stream.connectToChannel('userList', {
listId: this.list.id
});
this.connection.on('note', this.onNote);
this.connection.on('userAdded', this.onUserAdded);
this.connection.on('userRemoved', this.onUserRemoved);

View File

@ -77,8 +77,7 @@ export default Vue.extend({
mounted() {
this.connection.on('stats', this.onStats);
this.connection.on('statsLog', this.onStatsLog);
this.connection.send({
type: 'requestLog',
this.connection.send('requestLog', {
id: Math.random().toString(),
length: 200
});

View File

@ -46,8 +46,10 @@ export default Vue.extend({
},
mounted() {
if (this.connection) this.connection.close();
this.connection = new UserListStream((this as any).os, this.$store.state.i, this.list.id);
if (this.connection) this.connection.dispose();
this.connection = (this as any).os.stream.connectToChannel('userList', {
listId: this.list.id
});
this.connection.on('note', this.onNote);
this.connection.on('userAdded', this.onUserAdded);
this.connection.on('userRemoved', this.onUserRemoved);
@ -56,7 +58,7 @@ export default Vue.extend({
},
beforeDestroy() {
this.connection.close();
this.connection.dispose();
},
methods: {

View File

@ -113,8 +113,7 @@ export default Vue.extend({
onNotification(notification) {
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
this.connection.send({
type: 'readNotification',
(this as any).os.stream.send('readNotification', {
id: notification.id
});

View File

@ -13,7 +13,6 @@
<router-link class="name" :to="_user | userPage" v-user-preview="_user.id">{{ _user | userName }}</router-link>
<p class="username">@{{ _user | acct }}</p>
</div>
<mk-follow-button :user="_user"/>
</div>
</template>
<p class="empty" v-else>%i18n:@no-one%</p>

View File

@ -8,6 +8,7 @@ import VueRouter from 'vue-router';
import * as TreeView from 'vue-json-tree-view';
import VAnimateCss from 'v-animate-css';
import VModal from 'vue-js-modal';
import VueSweetalert2 from 'vue-sweetalert2';
import VueHotkey from './common/hotkey';
import App from './app.vue';
@ -26,6 +27,7 @@ Vue.use(TreeView);
Vue.use(VAnimateCss);
Vue.use(VModal);
Vue.use(VueHotkey);
Vue.use(VueSweetalert2);
// Register global directives
require('./common/views/directives');

View File

@ -212,7 +212,7 @@ export default class MiOS extends EventEmitter {
const fetched = () => {
this.emit('signedin');
this.stream = new Stream(this);
this.initStream();
// Finish init
callback();
@ -247,12 +247,103 @@ export default class MiOS extends EventEmitter {
// Finish init
callback();
this.stream = new Stream(this);
this.initStream();
}
});
}
}
@autobind
private initStream() {
this.stream = new Stream(this);
if (this.store.getters.isSignedIn) {
const main = this.stream.useSharedConnection('main');
// 自分の情報が更新されたとき
main.on('meUpdated', i => {
this.store.dispatch('mergeMe', i);
});
main.on('readAllNotifications', () => {
this.store.dispatch('mergeMe', {
hasUnreadNotification: false
});
});
main.on('unreadNotification', () => {
this.store.dispatch('mergeMe', {
hasUnreadNotification: true
});
});
main.on('readAllMessagingMessages', () => {
this.store.dispatch('mergeMe', {
hasUnreadMessagingMessage: false
});
});
main.on('unreadMessagingMessage', () => {
this.store.dispatch('mergeMe', {
hasUnreadMessagingMessage: true
});
});
main.on('unreadMention', () => {
this.store.dispatch('mergeMe', {
hasUnreadMentions: true
});
});
main.on('readAllUnreadMentions', () => {
this.store.dispatch('mergeMe', {
hasUnreadMentions: false
});
});
main.on('unreadSpecifiedNote', () => {
this.store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: true
});
});
main.on('readAllUnreadSpecifiedNotes', () => {
this.store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: false
});
});
main.on('clientSettingUpdated', x => {
this.store.commit('settings/set', {
key: x.key,
value: x.value
});
});
main.on('homeUpdated', x => {
this.store.commit('settings/setHome', x);
});
main.on('mobileHomeUpdated', x => {
this.store.commit('settings/setMobileHome', x);
});
main.on('widgetUpdated', x => {
this.store.commit('settings/setWidget', {
id: x.id,
data: x.data
});
});
// トークンが再生成されたとき
// このままではMisskeyが利用できないので強制的にサインアウトさせる
main.on('myTokenRegenerated', () => {
alert('%i18n:common.my-token-regenerated%');
this.signout();
});
}
}
/**
* Register service worker
*/

View File

@ -98,8 +98,7 @@ export default Vue.extend({
onNotification(notification) {
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
this.connection.send({
type: 'readNotification',
(this as any).os.stream.send('readNotification', {
id: notification.id
});

View File

@ -58,8 +58,7 @@ export default Vue.extend({
methods: {
onNotification(notification) {
// TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない
this.connection.send({
type: 'readNotification',
(this as any).os.stream.send('readNotification', {
id: notification.id
});

View File

@ -36,13 +36,15 @@ export default Vue.extend({
},
beforeDestroy() {
this.connection.close();
this.connection.dispose();
},
methods: {
init() {
if (this.connection) this.connection.close();
this.connection = new UserListStream((this as any).os, this.$store.state.i, this.list.id);
if (this.connection) this.connection.dispose();
this.connection = (this as any).os.stream.connectToChannel('userList', {
listId: this.list.id
});
this.connection.on('note', this.onNote);
this.connection.on('userAdded', this.onUserAdded);
this.connection.on('userRemoved', this.onUserRemoved);

View File

@ -26,7 +26,7 @@
face: '$secondary',
faceText: '#fff',
faceHeader: ':lighten<5<$secondary',
faceHeaderText: '#e3e5e8',
faceHeaderText: '$text',
faceDivider: 'rgba(0, 0, 0, 0.3)',
faceTextButton: '$text',
faceTextButtonHover: ':lighten<10<$text',
@ -84,7 +84,8 @@
reactionPickerButtonHoverBg: 'rgba(255, 255, 255, 0.18)',
reactionViewerBorder: 'rgba(255, 255, 255, 0.1)',
reactionViewerButtonBg: 'rgba(255, 255, 255, 0.1)',
reactionViewerButtonHoverBg: 'rgba(255, 255, 255, 0.2)',
pollEditorInputBg: 'rgba(0, 0, 0, 0.25)',

View File

@ -84,7 +84,8 @@
reactionPickerButtonHoverBg: '#eee',
reactionViewerBorder: 'rgba(0, 0, 0, 0.1)',
reactionViewerButtonBg: 'rgba(0, 0, 0, 0.05)',
reactionViewerButtonHoverBg: 'rgba(0, 0, 0, 0.1)',
pollEditorInputBg: '#fff',

View File

@ -96,6 +96,12 @@ export type Source = {
google_maps_api_key: string;
clusterLimit?: number;
user_recommendation: {
external: boolean;
engine: string;
timeout: number;
};
};
/**

View File

@ -17,7 +17,7 @@ const summarize = (note: any): string => {
summary += note.text ? note.text : '';
// ファイルが添付されているとき
if (note.files.length != 0) {
if ((note.files || []).length != 0) {
summary += ` (${note.files.length}つのファイル)`;
}

View File

@ -24,15 +24,15 @@ export const meta = {
},
};
export default (params: any) => new Promise(async (res, rej) => {
export default async (params: any) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
if (psErr) throw psErr;
const object = await fetchAny(ps.uri);
if (object !== null) return res(object);
if (object !== null) return object;
return rej('object not found');
});
throw new Error('object not found');
};
/***
* URIからUserかNoteを解決する

View File

@ -1,6 +1,3 @@
/**
* Module dependencies
*/
import * as os from 'os';
import config from '../../../config';
import Meta from '../../../models/meta';
@ -9,9 +6,17 @@ import { ILocalUser } from '../../../models/user';
const pkg = require('../../../../package.json');
const client = require('../../../../built/client/meta.json');
/**
* Show core info
*/
export const meta = {
desc: {
'ja-JP': 'インスタンス情報を取得します。',
'en-US': 'Get the information of this instance.'
},
requireCredential: false,
params: {},
};
export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
const meta: any = (await Meta.findOne()) || {};
@ -28,10 +33,12 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
machine: os.hostname(),
os: os.platform(),
node: process.version,
cpu: {
model: os.cpus()[0].model,
cores: os.cpus().length
},
broadcasts: meta.broadcasts || [],
disableRegistration: meta.disableRegistration,
disableLocalTimeline: meta.disableLocalTimeline,
@ -40,6 +47,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
swPublickey: config.sw ? config.sw.public_key : null,
hidedTags: (me && me.isAdmin) ? meta.hidedTags : undefined,
bannerUrl: meta.bannerUrl,
features: {
registration: !meta.disableRegistration,
localTimeLine: !meta.disableLocalTimeline,

View File

@ -3,6 +3,8 @@ import $ from 'cafy';
import User, { pack, ILocalUser } from '../../../../models/user';
import { getFriendIds } from '../../common/get-friends';
import Mute from '../../../../models/mute';
import * as request from 'request'
import config from '../../../../config'
export const meta = {
desc: {
@ -15,44 +17,74 @@ export const meta = {
};
export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
// Get 'limit' parameter
const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
if (limitErr) return rej('invalid limit param');
// Get 'offset' parameter
const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
if (offsetErr) return rej('invalid offset param');
// ID list of the user itself and other users who the user follows
const followingIds = await getFriendIds(me._id);
// ミュートしているユーザーを取得
const mutedUserIds = (await Mute.find({
muterId: me._id
})).map(m => m.muteeId);
const users = await User
.find({
_id: {
$nin: followingIds.concat(mutedUserIds)
if (config.user_recommendation && config.user_recommendation.external) {
const userName = me.username
const hostName = config.hostname
const limit = params.limit
const offset = params.offset
const timeout = config.user_recommendation.timeout
const engine = config.user_recommendation.engine
const url = engine
.replace('{{host}}', hostName)
.replace('{{user}}', userName)
.replace('{{limit}}', limit)
.replace('{{offset}}', offset)
request(
{
url: url,
timeout: timeout,
json: true,
followRedirect: true,
followAllRedirects: true
},
isLocked: false,
$or: [{
lastUsedAt: {
$gte: new Date(Date.now() - ms('7days'))
(error: any, response: any, body: any) => {
if (!error && response.statusCode == 200) {
res(body)
} else {
res([])
}
}, {
host: null
}]
}, {
limit: limit,
skip: offset,
sort: {
followersCount: -1
}
});
)
} else {
// Get 'limit' parameter
const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
if (limitErr) return rej('invalid limit param');
// Serialize
res(await Promise.all(users.map(async user =>
await pack(user, me, { detail: true }))));
// Get 'offset' parameter
const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
if (offsetErr) return rej('invalid offset param');
// ID list of the user itself and other users who the user follows
const followingIds = await getFriendIds(me._id);
// ミュートしているユーザーを取得
const mutedUserIds = (await Mute.find({
muterId: me._id
})).map(m => m.muteeId);
const users = await User
.find({
_id: {
$nin: followingIds.concat(mutedUserIds)
},
isLocked: false,
$or: [{
lastUsedAt: {
$gte: new Date(Date.now() - ms('7days'))
}
}, {
host: null
}]
}, {
limit: limit,
skip: offset,
sort: {
followersCount: -1
}
});
// Serialize
res(await Promise.all(users.map(async user =>
await pack(user, me, { detail: true }))));
}
});

View File

@ -23,10 +23,10 @@ export default class extends Channel {
public onMessage(type: string, body: any) {
switch (type) {
case 'accept': this.accept(true); break;
case 'cancel-accept': this.accept(false); break;
case 'update-settings': this.updateSettings(body.settings); break;
case 'init-form': this.initForm(body); break;
case 'update-form': this.updateForm(body.id, body.value); break;
case 'cancelAccept': this.accept(false); break;
case 'updateSettings': this.updateSettings(body.settings); break;
case 'initForm': this.initForm(body); break;
case 'updateForm': this.updateForm(body.id, body.value); break;
case 'message': this.message(body); break;
case 'set': this.set(body.pos); break;
case 'check': this.check(body.crc32); break;

View File

@ -44,6 +44,10 @@ module.exports = (server: http.Server) => {
request.resourceURL.pathname === '/local-timeline' ? channels.localTimeline :
request.resourceURL.pathname === '/hybrid-timeline' ? channels.hybridTimeline :
request.resourceURL.pathname === '/global-timeline' ? channels.globalTimeline : null);
if (request.resourceURL.pathname === '/') {
main.connectChannel(Math.random().toString(), null, channels.main);
}
}
connection.once('close', () => {

View File

@ -44,7 +44,8 @@ export default async (user: IUser, note: INote, reaction: string) => new Promise
});
publishNoteStream(note._id, 'reacted', {
reaction: reaction
reaction: reaction,
userId: user._id
});
// リアクションされたユーザーがローカルユーザーなら通知を作成

View File

@ -0,0 +1,67 @@
import * as Minio from 'minio';
import * as uuid from 'uuid';
const sequential = require('promise-sequential');
import DriveFile, { DriveFileChunk, getDriveFileBucket } from '../models/drive-file';
import DriveFileThumbnail, { DriveFileThumbnailChunk } from '../models/drive-file-thumbnail';
import config from '../config';
DriveFile.find({
$or: [{
withoutChunks: { $exists: false }
}, {
withoutChunks: false
}]
}).then(async files => {
await sequential(files.map(file => async () => {
const minio = new Minio.Client(config.drive.config);
const keyDir = `${config.drive.prefix}/${uuid.v4()}`;
const key = `${keyDir}/${name}`;
const thumbnailKeyDir = `${config.drive.prefix}/${uuid.v4()}`;
const thumbnailKey = `${thumbnailKeyDir}/${name}.thumbnail.jpg`;
const baseUrl = config.drive.baseUrl
|| `${ config.drive.config.useSSL ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? `:${config.drive.config.port}` : '' }/${ config.drive.bucket }`;
const bucket = await getDriveFileBucket();
const readable = bucket.openDownloadStream(file._id);
await minio.putObject(config.drive.bucket, key, readable, file.length, {
'Content-Type': file.contentType,
'Cache-Control': 'max-age=31536000, immutable'
});
await DriveFile.findOneAndUpdate({ _id: file._id }, {
$set: {
'metadata.withoutChunks': true,
'metadata.storage': 'minio',
'metadata.storageProps': {
key: key,
thumbnailKey: thumbnailKey
},
'metadata.url': `${ baseUrl }/${ keyDir }/${ encodeURIComponent(name) }`,
}
});
// チャンクをすべて削除
await DriveFileChunk.remove({
files_id: file._id
});
//#region サムネイルもあれば削除
const thumbnail = await DriveFileThumbnail.findOne({
'metadata.originalId': file._id
});
if (thumbnail) {
await DriveFileThumbnailChunk.remove({
files_id: thumbnail._id
});
await DriveFileThumbnail.remove({ _id: thumbnail._id });
}
//#endregion
console.log('done', file._id);
}));
});