From 0d5bc3be6611a6877a71233ac57c0987027c0398 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 19 Apr 2018 12:43:25 +0900 Subject: ストーキング実装 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1511 --- src/models/note.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src/models/note.ts') diff --git a/src/models/note.ts b/src/models/note.ts index 3059593540..d4b16afa4c 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -58,6 +58,7 @@ export type INote = { }; uri: string; + // 非正規化 _reply?: { userId: mongo.ObjectID; }; @@ -66,9 +67,7 @@ export type INote = { }; _user: { host: string; - account: { - inbox?: string; - }; + inbox?: string; }; }; -- cgit v1.2.3-freya From 20e77196f28178c869402985761bd4c2fa74bf0a Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 22 Apr 2018 10:44:17 +0900 Subject: AP: 投票をレンダリング MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/app/desktop/views/components/notes.note.vue | 2 +- src/client/docs/api/entities/note.yaml | 6 ------ src/client/docs/api/entities/post.yaml | 6 ------ src/models/messaging-message.ts | 1 - src/models/note.ts | 1 - src/remote/activitypub/kernel/delete/note.ts | 1 - src/remote/activitypub/misc/get-note-html.ts | 18 ++++++++++++++++++ src/remote/activitypub/renderer/note.ts | 3 ++- src/server/api/endpoints/messaging/messages/create.ts | 3 --- src/services/note/create.ts | 2 -- 10 files changed, 21 insertions(+), 22 deletions(-) create mode 100644 src/remote/activitypub/misc/get-note-html.ts (limited to 'src/models/note.ts') diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue index 1352fc4ee7..ae2793db92 100644 --- a/src/client/app/desktop/views/components/notes.note.vue +++ b/src/client/app/desktop/views/components/notes.note.vue @@ -36,7 +36,7 @@

diff --git a/src/client/docs/api/entities/note.yaml b/src/client/docs/api/entities/note.yaml index 718d331d13..6fd26543bb 100644 --- a/src/client/docs/api/entities/note.yaml +++ b/src/client/docs/api/entities/note.yaml @@ -29,12 +29,6 @@ props: desc: ja: "投稿の本文 (ローカルの場合Markdown風のフォーマット)" en: "The text of this note (in Markdown like format if local)" - - name: "textHtml" - type: "string" - optional: true - desc: - ja: "投稿の本文 (HTML) (投稿時は無視)" - en: "The text of this note (in HTML. Ignored when posting.)" - name: "mediaIds" type: "id(DriveFile)[]" optional: true diff --git a/src/client/docs/api/entities/post.yaml b/src/client/docs/api/entities/post.yaml index 718d331d13..6fd26543bb 100644 --- a/src/client/docs/api/entities/post.yaml +++ b/src/client/docs/api/entities/post.yaml @@ -29,12 +29,6 @@ props: desc: ja: "投稿の本文 (ローカルの場合Markdown風のフォーマット)" en: "The text of this note (in Markdown like format if local)" - - name: "textHtml" - type: "string" - optional: true - desc: - ja: "投稿の本文 (HTML) (投稿時は無視)" - en: "The text of this note (in HTML. Ignored when posting.)" - name: "mediaIds" type: "id(DriveFile)[]" optional: true diff --git a/src/models/messaging-message.ts b/src/models/messaging-message.ts index 9d62fab4fa..a6a50fc8cf 100644 --- a/src/models/messaging-message.ts +++ b/src/models/messaging-message.ts @@ -12,7 +12,6 @@ export interface IMessagingMessage { _id: mongo.ObjectID; createdAt: Date; text: string; - textHtml: string; userId: mongo.ObjectID; recipientId: mongo.ObjectID; isRead: boolean; diff --git a/src/models/note.ts b/src/models/note.ts index d4b16afa4c..02801b62de 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -38,7 +38,6 @@ export type INote = { poll: any; // todo text: string; tags: string[]; - textHtml: string; cw: string; userId: mongo.ObjectID; appId: mongo.ObjectID; diff --git a/src/remote/activitypub/kernel/delete/note.ts b/src/remote/activitypub/kernel/delete/note.ts index 64c342d39b..b2868f69a3 100644 --- a/src/remote/activitypub/kernel/delete/note.ts +++ b/src/remote/activitypub/kernel/delete/note.ts @@ -22,7 +22,6 @@ export default async function(actor: IRemoteUser, uri: string): Promise { $set: { deletedAt: new Date(), text: null, - textHtml: null, mediaIds: [], poll: null } diff --git a/src/remote/activitypub/misc/get-note-html.ts b/src/remote/activitypub/misc/get-note-html.ts new file mode 100644 index 0000000000..a63059a38d --- /dev/null +++ b/src/remote/activitypub/misc/get-note-html.ts @@ -0,0 +1,18 @@ +import { INote } from "../../../models/note"; +import toHtml from '../../../text/html'; +import parse from '../../../text/parse'; +import config from '../../../config'; + +export default function(note: INote) { + if (note.text == null) return null; + + let html = toHtml(parse(note.text)); + + if (note.poll != null) { + const url = `${config.url}/notes/${note._id}`; + // TODO: i18n + html += `

【投票】
${url}

`; + } + + return html; +} diff --git a/src/remote/activitypub/renderer/note.ts b/src/remote/activitypub/renderer/note.ts index c364b13249..a05c12b388 100644 --- a/src/remote/activitypub/renderer/note.ts +++ b/src/remote/activitypub/renderer/note.ts @@ -4,6 +4,7 @@ import config from '../../../config'; import DriveFile from '../../../models/drive-file'; import Note, { INote } from '../../../models/note'; import User from '../../../models/user'; +import toHtml from '../misc/get-note-html'; export default async function renderNote(note: INote, dive = true) { const promisedFiles = note.mediaIds @@ -48,7 +49,7 @@ export default async function renderNote(note: INote, dive = true) { id: `${config.url}/notes/${note._id}`, type: 'Note', attributedTo, - content: note.textHtml, + content: toHtml(note), published: note.createdAt.toISOString(), to: 'https://www.w3.org/ns/activitystreams#Public', cc: `${attributedTo}/followers`, diff --git a/src/server/api/endpoints/messaging/messages/create.ts b/src/server/api/endpoints/messaging/messages/create.ts index 085e75e6cf..0483b602b2 100644 --- a/src/server/api/endpoints/messaging/messages/create.ts +++ b/src/server/api/endpoints/messaging/messages/create.ts @@ -12,8 +12,6 @@ import { pack } from '../../../../../models/messaging-message'; import publishUserStream from '../../../../../publishers/stream'; import { publishMessagingStream, publishMessagingIndexStream } from '../../../../../publishers/stream'; import pushSw from '../../../../../publishers/push-sw'; -import html from '../../../../../text/html'; -import parse from '../../../../../text/parse'; import config from '../../../../../config'; /** @@ -77,7 +75,6 @@ module.exports = (params, user) => new Promise(async (res, rej) => { fileId: file ? file._id : undefined, recipientId: recipient._id, text: text ? text : undefined, - textHtml: text ? html(parse(text)) : undefined, userId: user._id, isRead: false }); diff --git a/src/services/note/create.ts b/src/services/note/create.ts index ca01b557d6..2380e64ce3 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -15,7 +15,6 @@ import Mute from '../../models/mute'; import pushSw from '../../publishers/push-sw'; import event from '../../publishers/stream'; import parse from '../../text/parse'; -import html from '../../text/html'; import { IApp } from '../../models/app'; export default async (user: IUser, data: { @@ -63,7 +62,6 @@ export default async (user: IUser, data: { replyId: data.reply ? data.reply._id : null, renoteId: data.renote ? data.renote._id : null, text: data.text, - textHtml: tokens === null ? null : html(tokens), poll: data.poll, cw: data.cw, tags, -- cgit v1.2.3-freya From d2d3a7d52b78b7d7d1da4380b062ac2279948cae Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 22 Apr 2018 17:04:52 +0900 Subject: CW --- .../app/desktop/views/components/notes.note.vue | 216 ++++++++++-------- .../app/desktop/views/components/post-form.vue | 77 +++++-- src/client/app/mobile/views/components/note.vue | 250 ++++++++++++--------- .../app/mobile/views/components/post-form.vue | 33 ++- src/models/note.ts | 2 +- src/server/api/endpoints/notes/create.ts | 16 +- src/services/note/create.ts | 2 +- 7 files changed, 369 insertions(+), 227 deletions(-) (limited to 'src/models/note.ts') diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue index ae2793db92..6d0e0c8f5c 100644 --- a/src/client/app/desktop/views/components/notes.note.vue +++ b/src/client/app/desktop/views/components/notes.note.vue @@ -34,24 +34,30 @@

{{ p.channel.title }}:

-
- %fa:reply% - - RP: -
-
- -
- -
- {{ tag }} -
- %fa:map-marker-alt% 位置情報 -
-
- +

+ {{ p.cw }} + {{ showContent ? '隠す' : 'もっと見る' }} +

+
+
+ %fa:reply% + + RP: +
+
+ +
+ +
+ {{ tag }} +
+ %fa:map-marker-alt% 位置情報 +
+
+ +
+
-
@@ -113,6 +119,7 @@ export default Vue.extend({ data() { return { + showContent: false, isDetailOpened: false, connection: null, connectionId: null @@ -456,7 +463,7 @@ root(isDark) > .body - > .text + > .cw cursor default display block margin 0 @@ -465,90 +472,117 @@ root(isDark) font-size 1.1em color isDark ? #fff : #717171 - >>> .title - display block - margin-bottom 4px - padding 4px - font-size 90% - text-align center - background isDark ? #2f3944 : #eef1f3 - border-radius 4px - - >>> .code - margin 8px 0 - - >>> .quote - margin 8px - padding 6px 12px - color isDark ? #6f808e : #aaa - border-left solid 3px isDark ? #637182 : #eee - - > .reply + > .text margin-right 8px - color isDark ? #99abbf : #717171 - - > .rp - margin-left 4px - font-style oblique - color #a0bf46 - > .location - margin 4px 0 - font-size 12px - color #ccc + > .toggle + display inline-block + padding 4px 8px + font-size 0.7em + color isDark ? #393f4f : #fff + background isDark ? #687390 : #b1b9c1 + border-radius 2px + cursor pointer + user-select none - > .map - width 100% - height 300px + &:hover + background isDark ? #707b97 : #bbc4ce - &:empty - display none + > .content - > .tags - margin 4px 0 0 0 + > .text + cursor default + display block + margin 0 + padding 0 + overflow-wrap break-word + font-size 1.1em + color isDark ? #fff : #717171 - > * - display inline-block - margin 0 8px 0 0 - padding 2px 8px 2px 16px - font-size 90% - color #8d969e - background #edf0f3 - border-radius 4px - - &:before - content "" + >>> .title display block - position absolute - top 0 - bottom 0 - left 4px - width 8px - height 8px - margin auto 0 - background #fff - border-radius 100% - - &:hover - text-decoration none - background #e2e7ec - - .mk-url-preview - margin-top 8px + margin-bottom 4px + padding 4px + font-size 90% + text-align center + background isDark ? #2f3944 : #eef1f3 + border-radius 4px + + >>> .code + margin 8px 0 + + >>> .quote + margin 8px + padding 6px 12px + color isDark ? #6f808e : #aaa + border-left solid 3px isDark ? #637182 : #eee + + > .reply + margin-right 8px + color isDark ? #99abbf : #717171 + + > .rp + margin-left 4px + font-style oblique + color #a0bf46 + + > .location + margin 4px 0 + font-size 12px + color #ccc - > .channel - margin 0 + > .map + width 100% + height 300px + + &:empty + display none + + > .tags + margin 4px 0 0 0 + + > * + display inline-block + margin 0 8px 0 0 + padding 2px 8px 2px 16px + font-size 90% + color #8d969e + background #edf0f3 + border-radius 4px + + &:before + content "" + display block + position absolute + top 0 + bottom 0 + left 4px + width 8px + height 8px + margin auto 0 + background #fff + border-radius 100% + + &:hover + text-decoration none + background #e2e7ec + + .mk-url-preview + margin-top 8px + + > .channel + margin 0 - > .mk-poll - font-size 80% + > .mk-poll + font-size 80% - > .renote - margin 8px 0 + > .renote + margin 8px 0 - > .mk-note-preview - padding 16px - border dashed 1px isDark ? #4e945e : #c0dac6 - border-radius 8px + > .mk-note-preview + padding 16px + border dashed 1px isDark ? #4e945e : #c0dac6 + border-radius 8px > footer > button diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue index 919f97f44d..80d9d66753 100644 --- a/src/client/app/desktop/views/components/post-form.vue +++ b/src/client/app/desktop/views/components/post-form.vue @@ -6,6 +6,7 @@ @drop.stop="onDrop" >
+
@@ -24,6 +25,7 @@ +
@@ -39,7 +41,9 @@ export default Vue.extend({ components: { XDraggable }, + props: ['reply'], + data() { return { posting: false, @@ -47,21 +51,27 @@ export default Vue.extend({ uploadings: [], files: [], poll: false, - geo: null + geo: null, + useCw: false, + cw: null }; }, + mounted() { this.$nextTick(() => { this.focus(); }); }, + methods: { focus() { (this.$refs.text as any).focus(); }, + chooseFile() { (this.$refs.file as any).click(); }, + chooseFileFromDrive() { (this as any).apis.chooseDriveFile({ multiple: true @@ -69,23 +79,29 @@ export default Vue.extend({ files.forEach(this.attachMedia); }); }, + attachMedia(driveFile) { this.files.push(driveFile); this.$emit('change-attached-media', this.files); }, + detachMedia(file) { this.files = this.files.filter(x => x.id != file.id); this.$emit('change-attached-media', this.files); }, + onChangeFile() { Array.from((this.$refs.file as any).files).forEach(this.upload); }, + upload(file) { (this.$refs.uploader as any).upload(file); }, + onChangeUploadings(uploads) { this.$emit('change-uploadings', uploads); }, + setGeo() { if (navigator.geolocation == null) { alert('お使いの端末は位置情報に対応していません'); @@ -100,15 +116,18 @@ export default Vue.extend({ enableHighAccuracy: true }); }, + removeGeo() { this.geo = null; }, + clear() { this.text = ''; this.files = []; this.poll = false; this.$emit('change-attached-media'); }, + post() { this.posting = true; const viaMobile = (this as any).os.i.clientSettings.disableViaMobile !== true; @@ -117,6 +136,7 @@ export default Vue.extend({ mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, replyId: this.reply ? this.reply.id : undefined, poll: this.poll ? (this.$refs.poll as any).get() : undefined, + cw: this.useCw ? this.cw || '' : undefined, geo: this.geo ? { coordinates: [this.geo.longitude, this.geo.latitude], altitude: this.geo.altitude, @@ -133,10 +153,12 @@ export default Vue.extend({ this.posting = false; }); }, + cancel() { this.$emit('cancel'); this.$destroy(); }, + kao() { this.text += getKao(); } @@ -236,14 +258,12 @@ export default Vue.extend({ > .file display none + > input > textarea display block padding 12px margin 0 width 100% - max-width 100% - min-width 100% - min-height 80px font-size 16px color #333 border none @@ -253,6 +273,11 @@ export default Vue.extend({ &:disabled opacity 0.5 + > textarea + max-width 100% + min-width 100% + min-height 80px + > .upload > .drive > .kao diff --git a/src/models/note.ts b/src/models/note.ts index 02801b62de..3c835ed190 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -24,7 +24,7 @@ export function isValidText(text: string): boolean { } export function isValidCw(text: string): boolean { - return text.length <= 100 && text.trim() != ''; + return text.length <= 100; } export type INote = { diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 14e906eb9d..ea1f41aae2 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -23,11 +23,11 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res if (visibilityErr) return rej('invalid visibility'); // Get 'text' parameter - const [text, textErr] = $(params.text).optional.string().pipe(isValidText).$; + const [text = null, textErr] = $(params.text).optional.nullable.string().pipe(isValidText).$; if (textErr) return rej('invalid text'); // Get 'cw' parameter - const [cw, cwErr] = $(params.cw).optional.string().pipe(isValidCw).$; + const [cw, cwErr] = $(params.cw).optional.nullable.string().pipe(isValidCw).$; if (cwErr) return rej('invalid cw'); // Get 'viaMobile' parameter @@ -187,14 +187,14 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res const note = await create(user, { createdAt: new Date(), media: files, - poll: poll, - text: text, + poll, + text, reply, renote, - cw: cw, - tags: tags, - app: app, - viaMobile: viaMobile, + cw, + tags, + app, + viaMobile, visibility, geo }); diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 2380e64ce3..e5ad96898f 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -63,7 +63,7 @@ export default async (user: IUser, data: { renoteId: data.renote ? data.renote._id : null, text: data.text, poll: data.poll, - cw: data.cw, + cw: data.cw == null ? null : data.cw, tags, userId: user._id, viaMobile: data.viaMobile, -- cgit v1.2.3-freya From 47ce59d555eab91b0642b4e4b62c8ecc4ff2288a Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 28 Apr 2018 17:25:56 +0900 Subject: wip --- .../common/views/components/visibility-chooser.vue | 213 +++++++++++++++++++++ .../app/desktop/views/components/post-form.vue | 32 ++-- src/models/note.ts | 11 +- src/remote/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 (limited to 'src/models/note.ts') 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 0000000000..71e92a85f2 --- /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 80d9d66753..d1d7076a30 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 3c835ed190..2f95cbfd65 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 a288dd499a..e2f3806d75 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 f830370a23..c0f67cb2f9 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 -- cgit v1.2.3-freya From ec4ed8fb2d87f02ff67cfbf6b9f8e2975bc49e4f Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 29 Apr 2018 04:30:51 +0900 Subject: wip --- .../common/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(-) (limited to 'src/models/note.ts') diff --git a/src/client/app/common/views/components/visibility-chooser.vue b/src/client/app/common/views/components/visibility-chooser.vue index b6a8e9b661..dd36d32e74 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 2f95cbfd65..5c4ac86353 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 af4f365226..52c6068fd0 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 4808edfda4..e8070595cd 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, -- cgit v1.2.3-freya From 6df010a71e4be1ee819c0c2730334a6c0f5b4387 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 29 Apr 2018 04:51:19 +0900 Subject: wip --- src/models/note.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src/models/note.ts') diff --git a/src/models/note.ts b/src/models/note.ts index 5c4ac86353..918ef6d69e 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; }; -- cgit v1.2.3-freya From e9940c92214f4b70b6f8dfdf1f4dd7ce1814e67d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 29 Apr 2018 07:01:47 +0900 Subject: wip --- .../app/desktop/views/components/notes.note.vue | 1 + .../app/desktop/views/components/post-form.vue | 2 +- .../desktop/views/components/sub-note-content.vue | 1 + src/client/app/mobile/views/components/note.vue | 5 +- .../mobile/views/components/sub-note-content.vue | 1 + src/models/note.ts | 19 +++--- src/services/note/create.ts | 75 +++++++++++++--------- 7 files changed, 62 insertions(+), 42 deletions(-) (limited to 'src/models/note.ts') diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue index 217587ec6d..e8fabe3c63 100644 --- a/src/client/app/desktop/views/components/notes.note.vue +++ b/src/client/app/desktop/views/components/notes.note.vue @@ -40,6 +40,7 @@

+ (この投稿は非公開です) %fa:reply% RP: diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue index a9ce1354da..d704cdc3ca 100644 --- a/src/client/app/desktop/views/components/post-form.vue +++ b/src/client/app/desktop/views/components/post-form.vue @@ -291,7 +291,7 @@ export default Vue.extend({ poll: this.poll ? (this.$refs.poll as any).get() : undefined, cw: this.useCw ? this.cw || '' : undefined, visibility: this.visibility, - visibleUserIds: this.visibleUsers.map(u => u.id), + visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined, geo: this.geo ? { coordinates: [this.geo.longitude, this.geo.latitude], altitude: this.geo.altitude, diff --git a/src/client/app/desktop/views/components/sub-note-content.vue b/src/client/app/desktop/views/components/sub-note-content.vue index 51ee93cba6..dd4012039b 100644 --- a/src/client/app/desktop/views/components/sub-note-content.vue +++ b/src/client/app/desktop/views/components/sub-note-content.vue @@ -1,6 +1,7 @@