From 1d38c0c326a3d402cd86277704388dba96ed3670 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 28 Apr 2018 17:25:56 +0900 Subject: [PATCH 01/12] wip --- .../views/components/visibility-chooser.vue | 213 ++++++++++++++++++ .../desktop/views/components/post-form.vue | 32 +-- src/models/note.ts | 11 +- .../activitypub/kernel/announce/note.ts | 4 +- src/remote/activitypub/models/note.ts | 4 +- 5 files changed, 244 insertions(+), 20 deletions(-) create mode 100644 src/client/app/common/views/components/visibility-chooser.vue diff --git a/src/client/app/common/views/components/visibility-chooser.vue b/src/client/app/common/views/components/visibility-chooser.vue new file mode 100644 index 000000000..71e92a85f --- /dev/null +++ b/src/client/app/common/views/components/visibility-chooser.vue @@ -0,0 +1,213 @@ + + + + + diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue index 80d9d6675..d1d7076a3 100644 --- a/src/client/app/desktop/views/components/post-form.vue +++ b/src/client/app/desktop/views/components/post-form.vue @@ -30,7 +30,7 @@ -

{{ '%i18n:!@text-remain%'.replace('{}', 1000 - text.length) }}

+ @@ -43,10 +43,12 @@ import Vue from 'vue'; import * as XDraggable from 'vuedraggable'; import getKao from '../../../common/scripts/get-kao'; +import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue'; export default Vue.extend({ components: { - XDraggable + XDraggable, + MkVisibilityChooser }, props: ['reply', 'renote'], @@ -61,6 +63,7 @@ export default Vue.extend({ useCw: false, cw: null, geo: null, + visibility: 'public', autocomplete: null, draghover: false }; @@ -246,6 +249,16 @@ export default Vue.extend({ this.$emit('geo-dettached'); }, + setVisibility() { + const w = (this as any).os.new(MkVisibilityChooser, { + source: this.$refs.visibilityButton, + v: this.visibility + }); + w.$once('chosen', v => { + this.visibility = v; + }); + }, + post() { this.posting = true; @@ -256,6 +269,7 @@ export default Vue.extend({ renoteId: this.renote ? this.renote.id : undefined, poll: this.poll ? (this.$refs.poll as any).get() : undefined, cw: this.useCw ? this.cw || '' : undefined, + visibility: this.visibility, geo: this.geo ? { coordinates: [this.geo.longitude, this.geo.latitude], altitude: this.geo.altitude, @@ -450,19 +464,6 @@ root(isDark) input[type='file'] display none - .text-count - pointer-events none - display block - position absolute - bottom 16px - right 138px - margin 0 - line-height 40px - color rgba($theme-color, 0.5) - - &.over - color #ec3828 - .submit display block position absolute @@ -532,6 +533,7 @@ root(isDark) > .kao > .poll > .geo + > .visibility display inline-block cursor pointer padding 0 diff --git a/src/models/note.ts b/src/models/note.ts index 3c835ed19..2f95cbfd6 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -46,7 +46,16 @@ export type INote = { repliesCount: number; reactionCounts: any; mentions: mongo.ObjectID[]; - visibility: 'public' | 'unlisted' | 'private' | 'direct'; + + /** + * public ... 公開 + * home ... ホームタイムライン(ユーザーページのタイムライン含む)のみに流す + * followers ... フォロワーのみ + * mentioned ... 言及したユーザーのみ + * private ... 自分のみ + */ + visibility: 'public' | 'home' | 'followers' | 'mentioned' | 'private'; + geo: { coordinates: number[]; altitude: number; diff --git a/src/remote/activitypub/kernel/announce/note.ts b/src/remote/activitypub/kernel/announce/note.ts index a288dd499..e2f3806d7 100644 --- a/src/remote/activitypub/kernel/announce/note.ts +++ b/src/remote/activitypub/kernel/announce/note.ts @@ -30,8 +30,8 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity: //#region Visibility let visibility = 'public'; - if (!activity.to.includes('https://www.w3.org/ns/activitystreams#Public')) visibility = 'unlisted'; - if (activity.cc.length == 0) visibility = 'private'; + if (!activity.to.includes('https://www.w3.org/ns/activitystreams#Public')) visibility = 'home'; + if (activity.cc.length == 0) visibility = 'followers'; // TODO if (visibility != 'public') throw new Error('unspported visibility'); //#endergion diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts index f830370a2..c0f67cb2f 100644 --- a/src/remote/activitypub/models/note.ts +++ b/src/remote/activitypub/models/note.ts @@ -65,8 +65,8 @@ export async function createNote(value: any, resolver?: Resolver, silent = false //#region Visibility let visibility = 'public'; - if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) visibility = 'unlisted'; - if (note.cc.length == 0) visibility = 'private'; + if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) visibility = 'home'; + if (note.cc.length == 0) visibility = 'followers'; // TODO if (visibility != 'public') return null; //#endergion From 537d01ba5d66ee2fadddaa5014e063e9927c4526 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 28 Apr 2018 17:27:41 +0900 Subject: [PATCH 02/12] oops --- src/client/app/common/views/components/visibility-chooser.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/app/common/views/components/visibility-chooser.vue b/src/client/app/common/views/components/visibility-chooser.vue index 71e92a85f..e37717f2c 100644 --- a/src/client/app/common/views/components/visibility-chooser.vue +++ b/src/client/app/common/views/components/visibility-chooser.vue @@ -181,7 +181,7 @@ root(isDark) background isDark ? #21242b : #ddd &.active - cursor $theme-color-foreground + color $theme-color-foreground background $theme-color > * From 060fc2525cc086470e4907be13fccc173d4c601e Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 28 Apr 2018 17:29:46 +0900 Subject: [PATCH 03/12] :v: --- src/client/app/common/views/components/visibility-chooser.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/app/common/views/components/visibility-chooser.vue b/src/client/app/common/views/components/visibility-chooser.vue index e37717f2c..b6a8e9b66 100644 --- a/src/client/app/common/views/components/visibility-chooser.vue +++ b/src/client/app/common/views/components/visibility-chooser.vue @@ -19,14 +19,14 @@
%fa:unlock%
フォロワー - 自分のフォロワーのみに公開 + 自分のフォロワーにのみ公開
%fa:envelope%
メンション - 言及したユーザーのみに公開 + 言及したユーザーにのみ公開
From aa908ea359dc7dd219aa21da077764033586d774 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 29 Apr 2018 04:30:51 +0900 Subject: [PATCH 04/12] wip --- .../views/components/visibility-chooser.vue | 6 +-- src/models/note.ts | 53 ++++++++++++++++++- src/server/api/endpoints/notes/create.ts | 16 +++++- src/services/note/create.ts | 6 +++ 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/src/client/app/common/views/components/visibility-chooser.vue b/src/client/app/common/views/components/visibility-chooser.vue index b6a8e9b66..dd36d32e7 100644 --- a/src/client/app/common/views/components/visibility-chooser.vue +++ b/src/client/app/common/views/components/visibility-chooser.vue @@ -22,11 +22,11 @@ 自分のフォロワーにのみ公開
-
+
%fa:envelope%
- メンション - 言及したユーザーにのみ公開 + ダイレクト + 指定したユーザーにのみ公開
diff --git a/src/models/note.ts b/src/models/note.ts index 2f95cbfd6..5c4ac8635 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -12,6 +12,7 @@ import NoteWatching, { deleteNoteWatching } from './note-watching'; import NoteReaction from './note-reaction'; import Favorite, { deleteFavorite } from './favorite'; import Notification, { deleteNotification } from './notification'; +import Following from './following'; const Note = db.get('notes'); @@ -51,10 +52,12 @@ export type INote = { * public ... 公開 * home ... ホームタイムライン(ユーザーページのタイムライン含む)のみに流す * followers ... フォロワーのみ - * mentioned ... 言及したユーザーのみ + * specified ... visibleUserIds で指定したユーザーのみ * private ... 自分のみ */ - visibility: 'public' | 'home' | 'followers' | 'mentioned' | 'private'; + visibility: 'public' | 'home' | 'followers' | 'specified' | 'private'; + + visibleUserIds: mongo.ObjectID[]; geo: { coordinates: number[]; @@ -190,6 +193,52 @@ export const pack = async ( if (!_note) throw `invalid note arg ${note}`; + let hide = false; + + // visibility が private かつ投稿者のIDが自分のIDではなかったら非表示 + if (_note.visibility == 'private' && (meId == null || !meId.equals(_note.userId))) { + hide = true; + } + + // visibility が specified かつ自分が指定されていなかったら非表示 + if (_note.visibility == 'specified') { + if (meId == null) { + hide = true; + } else if (meId.equals(_note.userId)) { + hide = false; + } else { + // 指定されているかどうか + const specified = _note.visibleUserIds.test(id => id.equals(meId)); + + if (specified) { + hide = false; + } else { + hide = true; + } + } + } + + // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示 + if (_note.visibility == 'followers') { + if (meId == null) { + hide = true; + } else if (meId.equals(_note.userId)) { + hide = false; + } else { + // フォロワーかどうか + const following = await Following.findOne({ + followeeId: _note.userId, + followerId: meId + }); + + if (following == null) { + hide = true; + } else { + hide = false; + } + } + } + const id = _note._id; // Rename _id to id diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index af4f36522..52c6068fd 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -3,7 +3,7 @@ */ import $ from 'cafy'; import ID from '../../../../cafy-id'; import Note, { INote, isValidText, isValidCw, pack } from '../../../../models/note'; -import { ILocalUser } from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; import Channel, { IChannel } from '../../../../models/channel'; import DriveFile from '../../../../models/drive-file'; import create from '../../../../services/note/create'; @@ -14,9 +14,20 @@ import { IApp } from '../../../../models/app'; */ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { // Get 'visibility' parameter - const [visibility = 'public', visibilityErr] = $(params.visibility).optional.string().or(['public', 'unlisted', 'private', 'direct']).get(); + const [visibility = 'public', visibilityErr] = $(params.visibility).optional.string().or(['public', 'home', 'followers', 'specified', 'private']).get(); if (visibilityErr) return rej('invalid visibility'); + // Get 'visibleUserIds' parameter + const [visibleUserIds, visibleUserIdsErr] = $(params.visibleUserIds).optional.array($().type(ID)).unique().min(1).get(); + if (visibleUserIdsErr) return rej('invalid visibleUserIds'); + + let visibleUsers = []; + if (visibleUserIds !== undefined) { + visibleUsers = await Promise.all(visibleUserIds.map(id => User.findOne({ + _id: id + }))); + } + // Get 'text' parameter const [text = null, textErr] = $(params.text).optional.nullable.string().pipe(isValidText).get(); if (textErr) return rej('invalid text'); @@ -191,6 +202,7 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res app, viaMobile, visibility, + visibleUsers, geo }); diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 4808edfda..e8070595c 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -30,6 +30,7 @@ export default async (user: IUser, data: { tags?: string[]; cw?: string; visibility?: string; + visibleUsers?: IUser[]; uri?: string; app?: IApp; }, silent = false) => new Promise(async (res, rej) => { @@ -57,6 +58,10 @@ export default async (user: IUser, data: { }); } + if (data.visibleUsers) { + data.visibleUsers = data.visibleUsers.filter(x => x != null); + } + const insert: any = { createdAt: data.createdAt, mediaIds: data.media ? data.media.map(file => file._id) : [], @@ -71,6 +76,7 @@ export default async (user: IUser, data: { geo: data.geo || null, appId: data.app ? data.app._id : null, visibility: data.visibility, + visibleUserIds: data.visibleUsers ? data.visibleUsers.map(u => u._id) : [], // 以下非正規化データ _reply: data.reply ? { userId: data.reply.userId } : null, From 5aefaa1246e3c0908019e53f1cb2a5e63d1b7e99 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 29 Apr 2018 04:44:58 +0900 Subject: [PATCH 05/12] wip --- src/remote/activitypub/kernel/announce/note.ts | 15 +++++++++++---- src/remote/activitypub/models/note.ts | 13 ++++++++++--- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/remote/activitypub/kernel/announce/note.ts b/src/remote/activitypub/kernel/announce/note.ts index e2f3806d7..fe645b07b 100644 --- a/src/remote/activitypub/kernel/announce/note.ts +++ b/src/remote/activitypub/kernel/announce/note.ts @@ -5,6 +5,7 @@ import post from '../../../../services/note/create'; import { IRemoteUser } from '../../../../models/user'; import { IAnnounce, INote } from '../../type'; import { fetchNote, resolveNote } from '../../models/note'; +import { resolvePerson } from '../../models/person'; const log = debug('misskey:activitypub'); @@ -30,16 +31,22 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity: //#region Visibility let visibility = 'public'; - if (!activity.to.includes('https://www.w3.org/ns/activitystreams#Public')) visibility = 'home'; - if (activity.cc.length == 0) visibility = 'followers'; - // TODO - if (visibility != 'public') throw new Error('unspported visibility'); + let visibleUsers = []; + if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) { + if (note.cc.includes('https://www.w3.org/ns/activitystreams#Public')) { + visibility = 'home'; + } else { + visibility = 'specified'; + visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri))); + } + } if (activity.cc.length == 0) visibility = 'followers'; //#endergion await post(actor, { createdAt: new Date(activity.published), renote, visibility, + visibleUsers, uri }); } diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts index c0f67cb2f..9a0cc4e0c 100644 --- a/src/remote/activitypub/models/note.ts +++ b/src/remote/activitypub/models/note.ts @@ -65,10 +65,16 @@ export async function createNote(value: any, resolver?: Resolver, silent = false //#region Visibility let visibility = 'public'; - if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) visibility = 'home'; + let visibleUsers = []; + if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) { + if (note.cc.includes('https://www.w3.org/ns/activitystreams#Public')) { + visibility = 'home'; + } else { + visibility = 'specified'; + visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri))); + } + } if (note.cc.length == 0) visibility = 'followers'; - // TODO - if (visibility != 'public') return null; //#endergion // 添付メディア @@ -99,6 +105,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false viaMobile: false, geo: undefined, visibility, + visibleUsers, uri: note.id }, silent); } From a16792533ac9ce8d4d6f071c7f75867b631269cf Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 29 Apr 2018 04:51:19 +0900 Subject: [PATCH 06/12] wip --- src/models/note.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/models/note.ts b/src/models/note.ts index 5c4ac8635..918ef6d69 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -262,7 +262,7 @@ export const pack = async ( } // Populate media - if (_note.mediaIds) { + if (_note.mediaIds && !hide) { _note.media = Promise.all(_note.mediaIds.map(fileId => packFile(fileId) )); @@ -321,7 +321,7 @@ export const pack = async ( } // Poll - if (meId && _note.poll) { + if (meId && _note.poll && !hide) { _note.poll = (async (poll) => { const vote = await PollVote .findOne({ @@ -362,5 +362,12 @@ export const pack = async ( // resolve promises in _note object _note = await rap(_note); + if (hide) { + _note.mediaIds = []; + _note.text = null; + _note.poll = null; + _note.isHidden = true; + } + return _note; }; From d032127ce5b74e43706322ce98182f639387195b Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 29 Apr 2018 05:28:34 +0900 Subject: [PATCH 07/12] wip --- .../desktop/views/components/post-form.vue | 30 +++++++++++++++++++ src/services/note/create.ts | 6 +++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue index d1d7076a3..a9ce1354d 100644 --- a/src/client/app/desktop/views/components/post-form.vue +++ b/src/client/app/desktop/views/components/post-form.vue @@ -6,6 +6,10 @@ @drop.stop="onDrop" >
+
+ {{ u | userName }}[x] + +ユーザーを追加 +
@@ -27,6 +31,7 @@ +
@@ -35,11 +40,13 @@ From c345758adc33844d976810fbdd15dec4a03a798e Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 29 Apr 2018 09:19:05 +0900 Subject: [PATCH 11/12] wip --- .../views/components/notes.note.sub.vue | 28 +++++++++++++++---- .../desktop/views/components/notes.note.vue | 15 +++++++--- .../app/mobile/views/components/note.sub.vue | 28 +++++++++++++++---- .../app/mobile/views/components/note.vue | 16 ++++++++--- 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/client/app/desktop/views/components/notes.note.sub.vue b/src/client/app/desktop/views/components/notes.note.sub.vue index 3e1b75c29..4472ddefb 100644 --- a/src/client/app/desktop/views/components/notes.note.sub.vue +++ b/src/client/app/desktop/views/components/notes.note.sub.vue @@ -7,9 +7,18 @@
{{ note.user | userName }} @{{ note.user | acct }} - - - +
+ %fa:mobile-alt% + + + + + + + + + +
@@ -85,9 +94,18 @@ root(isDark) margin 0 .5em 0 0 color isDark ? #606984 : #d1d8da - > .created-at + > .info margin-left auto - color isDark ? #606984 : #b2b8bb + font-size 0.9em + + > * + color isDark ? #606984 : #b2b8bb + + > .mobile + margin-right 6px + + > .visibility + margin-left 6px > .body max-height 128px diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue index 4d7e6ee8b..ee24543eb 100644 --- a/src/client/app/desktop/views/components/notes.note.vue +++ b/src/client/app/desktop/views/components/notes.note.vue @@ -28,6 +28,12 @@ + + + + + +
@@ -442,18 +448,19 @@ root(isDark) margin-left auto font-size 0.9em + > * + color isDark ? #606984 : #c0c0c0 + > .mobile margin-right 8px - color isDark ? #606984 : #ccc > .app margin-right 8px padding-right 8px - color #ccc border-right solid 1px #eaeaea - > .created-at - color isDark ? #606984 : #c0c0c0 + > .visibility + margin-left 8px > .body diff --git a/src/client/app/mobile/views/components/note.sub.vue b/src/client/app/mobile/views/components/note.sub.vue index 8e3835ac2..01f02bdb5 100644 --- a/src/client/app/mobile/views/components/note.sub.vue +++ b/src/client/app/mobile/views/components/note.sub.vue @@ -7,9 +7,18 @@
{{ note.user | userName }} @{{ note.user | acct }} - - - +
+ %fa:mobile-alt% + + + + + + + + + +
@@ -92,9 +101,18 @@ root(isDark) margin 0 color isDark ? #606984 : #d1d8da - > .created-at + > .info margin-left auto - color isDark ? #606984 : #b2b8bb + font-size 0.9em + + > * + color isDark ? #606984 : #b2b8bb + + > .mobile + margin-right 6px + + > .visibility + margin-left 6px > .body max-height 128px diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue index 5202e0eb5..07e18544d 100644 --- a/src/client/app/mobile/views/components/note.vue +++ b/src/client/app/mobile/views/components/note.vue @@ -27,6 +27,12 @@ + + + + + +
@@ -379,12 +385,14 @@ root(isDark) margin-left auto font-size 0.9em - > .mobile - margin-right 6px + > * color isDark ? #606984 : #c0c0c0 - > .created-at - color isDark ? #606984 : #c0c0c0 + > .mobile + margin-right 6px + + > .visibility + margin-left 6px > .body From 5247292ce85bd0de3ff24712ffb1eaee6d77b0b4 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 29 Apr 2018 09:29:39 +0900 Subject: [PATCH 12/12] wip --- src/services/note/create.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 8d6626713..c2c03516e 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -123,7 +123,7 @@ export default async (user: IUser, data: { if (note.channelId == null) { if (!silent) { if (isLocalUser(user)) { - if (note.visibility == 'private') { + if (note.visibility == 'private' || note.visibility == 'followers' || note.visibility == 'specified') { // Publish event to myself's stream stream(note.userId, 'note', await pack(note, user, { detail: true @@ -133,7 +133,9 @@ export default async (user: IUser, data: { stream(note.userId, 'note', noteObj); // Publish note to local timeline stream - publishLocalTimelineStream(noteObj); + if (note.visibility != 'home') { + publishLocalTimelineStream(noteObj); + } } }