From 50d56bdc2585a3bbd4ad996426f62d6ed186a34d Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 17 Apr 2018 22:31:52 +0900 Subject: Clean up --- src/server/api/endpoints/notes/create.ts | 42 -------------------------------- 1 file changed, 42 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 7e79912b1b..4435a6a3c8 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -97,31 +97,7 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res return rej('cannot renote to renote'); } - // Fetch recently note - const latestNote = await Note.findOne({ - userId: user._id - }, { - sort: { - _id: -1 - } - }); - isQuote = text != null || files != null; - - // 直近と同じRenote対象かつ引用じゃなかったらエラー - if (latestNote && - latestNote.renoteId && - latestNote.renoteId.equals(renote._id) && - !isQuote) { - return rej('cannot renote same note that already reposted in your latest note'); - } - - // 直近がRenote対象かつ引用じゃなかったらエラー - if (latestNote && - latestNote._id.equals(renote._id) && - !isQuote) { - return rej('cannot renote your latest note'); - } } // Get 'replyId' parameter @@ -208,24 +184,6 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res return rej('text, mediaIds, renoteId or poll is required'); } - // 直近の投稿と重複してたらエラー - // TODO: 直近の投稿が一日前くらいなら重複とは見なさない - if (user.latestNote) { - if (deepEqual({ - text: user.latestNote.text, - reply: user.latestNote.replyId ? user.latestNote.replyId.toString() : null, - renote: user.latestNote.renoteId ? user.latestNote.renoteId.toString() : null, - mediaIds: (user.latestNote.mediaIds || []).map(id => id.toString()) - }, { - text: text, - reply: reply ? reply._id.toString() : null, - renote: renote ? renote._id.toString() : null, - mediaIds: (files || []).map(file => file._id.toString()) - })) { - return rej('duplicate'); - } - } - // 投稿を作成 const note = await create(user, { createdAt: new Date(), -- cgit v1.2.3-freya From a447ed1d106451b1e55f106d6aa4545680de3b82 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 17 Apr 2018 22:33:27 +0900 Subject: oops --- src/models/user.ts | 2 +- src/server/api/endpoints/notes/create.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'src/server/api') diff --git a/src/models/user.ts b/src/models/user.ts index 00c249849e..bcb2a73e24 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -2,7 +2,7 @@ import * as mongo from 'mongodb'; import deepcopy = require('deepcopy'); import rap from '@prezzemolo/rap'; import db from '../db/mongodb'; -import Note, { INote, pack as packNote, deleteNote } from './note'; +import Note, { pack as packNote, deleteNote } from './note'; import Following, { deleteFollowing } from './following'; import Mute, { deleteMute } from './mute'; import getFriends from '../server/api/common/get-friends'; diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 4435a6a3c8..14e906eb9d 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -2,7 +2,6 @@ * Module dependencies */ import $ from 'cafy'; -import deepEqual = require('deep-equal'); import Note, { INote, isValidText, isValidCw, pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; import Channel, { IChannel } from '../../../../models/channel'; -- cgit v1.2.3-freya From ea9c4de45b53fc49af27f62c3a10da14461d8e9b Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 18 Apr 2018 18:46:38 +0900 Subject: Fix #1510 --- src/server/api/endpoints/users/recommendation.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index e367e65a6c..2a6d36b75b 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -5,6 +5,7 @@ const ms = require('ms'); import $ from 'cafy'; import User, { pack } from '../../../../models/user'; import getFriends from '../../common/get-friends'; +import Mute from '../../../../models/mute'; /** * Get recommended users @@ -25,13 +26,18 @@ module.exports = (params, me) => new Promise(async (res, rej) => { // ID list of the user itself and other users who the user follows const followingIds = await getFriends(me._id); + // ミュートしているユーザーを取得 + const mutedUserIds = (await Mute.find({ + muterId: me._id + })).map(m => m.muteeId); + const users = await User .find({ _id: { - $nin: followingIds + $nin: followingIds.concat(mutedUserIds) }, $or: [{ - 'lastUsedAt': { + lastUsedAt: { $gte: new Date(Date.now() - ms('7days')) } }, { -- cgit v1.2.3-freya 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 --- locales/en.yml | 3 ++ locales/fr.yml | 3 ++ locales/ja.yml | 3 ++ migration/2018-04-19.js | 49 ++++++++++++++++++ .../app/desktop/views/pages/user/user.profile.vue | 30 ++++++++++- src/models/following.ts | 11 ++++ src/models/note.ts | 5 +- src/models/user.ts | 43 ++++++++-------- src/server/api/common/get-friends.ts | 29 +++++++++-- src/server/api/endpoints.ts | 18 +++++++ src/server/api/endpoints/following/stalk.ts | 36 +++++++++++++ src/server/api/endpoints/following/unstalk.ts | 35 +++++++++++++ src/server/api/endpoints/i/notifications.ts | 4 +- src/server/api/endpoints/mute/list.ts | 4 +- src/server/api/endpoints/notes/mentions.ts | 4 +- src/server/api/endpoints/notes/search.ts | 4 +- src/server/api/endpoints/notes/timeline.ts | 60 +++++++++++++++------- src/server/api/endpoints/users/followers.ts | 4 +- src/server/api/endpoints/users/following.ts | 4 +- src/server/api/endpoints/users/recommendation.ts | 4 +- src/services/following/create.ts | 13 ++++- src/services/note/create.ts | 24 +++------ 22 files changed, 310 insertions(+), 80 deletions(-) create mode 100644 migration/2018-04-19.js create mode 100644 src/server/api/endpoints/following/stalk.ts create mode 100644 src/server/api/endpoints/following/unstalk.ts (limited to 'src/server/api') diff --git a/locales/en.yml b/locales/en.yml index 9388aedbf8..4eb0a3446c 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -419,6 +419,9 @@ desktop/views/pages/user/user.photos.vue: desktop/views/pages/user/user.profile.vue: follows-you: "Follows you" + stalk: "Stalk" + stalking: "Stalking" + unstalk: "Unstalk" mute: "Mute" muted: "Muting" unmute: "Unmute" diff --git a/locales/fr.yml b/locales/fr.yml index cd756194ab..0e0019e565 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -419,6 +419,9 @@ desktop/views/pages/user/user.photos.vue: desktop/views/pages/user/user.profile.vue: follows-you: "Vous suis" + stalk: "ストークする" + stalking: "ストーキングしています" + unstalk: "ストーク解除" mute: "Mettre en sourdine" muted: "Muting" unmute: "Enlever la sourdine" diff --git a/locales/ja.yml b/locales/ja.yml index 96b4a1d0ea..ed32deae57 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -419,6 +419,9 @@ desktop/views/pages/user/user.photos.vue: desktop/views/pages/user/user.profile.vue: follows-you: "フォローされています" + stalk: "ストークする" + stalking: "ストーキングしています" + unstalk: "ストーク解除" mute: "ミュートする" muted: "ミュートしています" unmute: "ミュート解除" diff --git a/migration/2018-04-19.js b/migration/2018-04-19.js new file mode 100644 index 0000000000..b0df22c009 --- /dev/null +++ b/migration/2018-04-19.js @@ -0,0 +1,49 @@ +// for Node.js interpret + +const { default: User } = require('../built/models/user'); +const { default: Following } = require('../built/models/following'); +const { default: zip } = require('@prezzemolo/zip') + +const migrate = async (following) => { + const follower = await User.findOne({ _id: following.followerId }); + const followee = await User.findOne({ _id: following.followeeId }); + const result = await Following.update(following._id, { + $set: { + stalk: true, + _follower: { + host: follower.host, + inbox: follower.host != null ? follower.inbox : undefined + }, + _followee: { + host: followee.host, + inbox: followee.host != null ? followee.inbox : undefined + } + } + }); + return result.ok === 1; +} + +async function main() { + const count = await Following.count({}); + + const dop = Number.parseInt(process.argv[2]) || 5 + const idop = ((count - (count % dop)) / dop) + 1 + + return zip( + 1, + async (time) => { + console.log(`${time} / ${idop}`) + const doc = await Following.find({}, { + limit: dop, skip: time * dop + }) + return Promise.all(doc.map(migrate)) + }, + idop + ).then(a => { + const rv = [] + a.forEach(e => rv.push(...e)) + return rv + }) +} + +main().then(console.dir).catch(console.error) diff --git a/src/client/app/desktop/views/pages/user/user.profile.vue b/src/client/app/desktop/views/pages/user/user.profile.vue index 72750e1b3d..774f300a38 100644 --- a/src/client/app/desktop/views/pages/user/user.profile.vue +++ b/src/client/app/desktop/views/pages/user/user.profile.vue @@ -3,8 +3,14 @@

%i18n:@follows-you%

-

%i18n:@muted% %i18n:@unmute%

-

%i18n:@mute%

+

+ %i18n:@stalking% %i18n:@unstalk% + %i18n:@stalk% +

+

+ %i18n:@muted% %i18n:@unmute% + %i18n:@mute% +

{{ user.description }}
@@ -47,6 +53,26 @@ export default Vue.extend({ }); }, + stalk() { + (this as any).api('following/stalk', { + userId: this.user.id + }).then(() => { + this.user.isStalking = true; + }, () => { + alert('error'); + }); + }, + + unstalk() { + (this as any).api('following/unstalk', { + userId: this.user.id + }).then(() => { + this.user.isStalking = false; + }, () => { + alert('error'); + }); + }, + mute() { (this as any).api('mute/create', { userId: this.user.id diff --git a/src/models/following.ts b/src/models/following.ts index f10e349ee9..4712379a70 100644 --- a/src/models/following.ts +++ b/src/models/following.ts @@ -10,6 +10,17 @@ export type IFollowing = { createdAt: Date; followeeId: mongo.ObjectID; followerId: mongo.ObjectID; + stalk: boolean; + + // 非正規化 + _followee: { + host: string; + inbox?: string; + }, + _follower: { + host: string; + inbox?: string; + } }; /** 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; }; }; diff --git a/src/models/user.ts b/src/models/user.ts index bcb2a73e24..ca1ca28937 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -5,7 +5,7 @@ import db from '../db/mongodb'; import Note, { pack as packNote, deleteNote } from './note'; import Following, { deleteFollowing } from './following'; import Mute, { deleteMute } from './mute'; -import getFriends from '../server/api/common/get-friends'; +import { getFriendIds } from '../server/api/common/get-friends'; import config from '../config'; import AccessToken, { deleteAccessToken } from './access-token'; import NoteWatching, { deleteNoteWatching } from './note-watching'; @@ -375,33 +375,30 @@ export const pack = ( } if (meId && !meId.equals(_user.id)) { - // Whether the user is following - _user.isFollowing = (async () => { - const follow = await Following.findOne({ + const [following1, following2, mute] = await Promise.all([ + Following.findOne({ followerId: meId, followeeId: _user.id - }); - return follow !== null; - })(); - - // Whether the user is followed - _user.isFollowed = (async () => { - const follow2 = await Following.findOne({ + }), + Following.findOne({ followerId: _user.id, followeeId: meId - }); - return follow2 !== null; - })(); + }), + Mute.findOne({ + muterId: meId, + muteeId: _user.id + }) + ]); + + // Whether the user is following + _user.isFollowing = following1 !== null; + _user.isStalking = following1 && following1.stalk; + + // Whether the user is followed + _user.isFollowed = following2 !== null; // Whether the user is muted - _user.isMuted = (async () => { - const mute = await Mute.findOne({ - muterId: meId, - muteeId: _user.id, - deletedAt: { $exists: false } - }); - return mute !== null; - })(); + _user.isMuted = mute !== null; } if (opts.detail) { @@ -413,7 +410,7 @@ export const pack = ( } if (meId && !meId.equals(_user.id)) { - const myFollowingIds = await getFriends(meId); + const myFollowingIds = await getFriendIds(meId); // Get following you know count _user.followingYouKnowCount = Following.count({ diff --git a/src/server/api/common/get-friends.ts b/src/server/api/common/get-friends.ts index c1cc3957d8..50ba71ea96 100644 --- a/src/server/api/common/get-friends.ts +++ b/src/server/api/common/get-friends.ts @@ -1,10 +1,10 @@ import * as mongodb from 'mongodb'; import Following from '../../../models/following'; -export default async (me: mongodb.ObjectID, includeMe: boolean = true) => { +export const getFriendIds = async (me: mongodb.ObjectID, includeMe = true) => { // Fetch relation to other users who the I follows // SELECT followee - const myfollowing = await Following + const followings = await Following .find({ followerId: me }, { @@ -14,7 +14,7 @@ export default async (me: mongodb.ObjectID, includeMe: boolean = true) => { }); // ID list of other users who the I follows - const myfollowingIds = myfollowing.map(follow => follow.followeeId); + const myfollowingIds = followings.map(following => following.followeeId); if (includeMe) { myfollowingIds.push(me); @@ -22,3 +22,26 @@ export default async (me: mongodb.ObjectID, includeMe: boolean = true) => { return myfollowingIds; }; + +export const getFriends = async (me: mongodb.ObjectID, includeMe = true) => { + // Fetch relation to other users who the I follows + const followings = await Following + .find({ + followerId: me + }); + + // ID list of other users who the I follows + const myfollowings = followings.map(following => ({ + id: following.followeeId, + stalk: following.stalk + })); + + if (includeMe) { + myfollowings.push({ + id: me, + stalk: true + }); + } + + return myfollowings; +}; diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts index e0223c23e0..7cf49debe9 100644 --- a/src/server/api/endpoints.ts +++ b/src/server/api/endpoints.ts @@ -426,6 +426,24 @@ const endpoints: Endpoint[] = [ }, kind: 'following-write' }, + { + name: 'following/stalk', + withCredential: true, + limit: { + duration: ms('1hour'), + max: 100 + }, + kind: 'following-write' + }, + { + name: 'following/unstalk', + withCredential: true, + limit: { + duration: ms('1hour'), + max: 100 + }, + kind: 'following-write' + }, { name: 'notes' diff --git a/src/server/api/endpoints/following/stalk.ts b/src/server/api/endpoints/following/stalk.ts new file mode 100644 index 0000000000..fc8be4924d --- /dev/null +++ b/src/server/api/endpoints/following/stalk.ts @@ -0,0 +1,36 @@ +import $ from 'cafy'; +import Following from '../../../../models/following'; +import { isLocalUser } from '../../../../models/user'; + +/** + * Stalk a user + */ +module.exports = (params, user) => new Promise(async (res, rej) => { + const follower = user; + + // Get 'userId' parameter + const [userId, userIdErr] = $(params.userId).id().$; + if (userIdErr) return rej('invalid userId param'); + + // Fetch following + const following = await Following.findOne({ + followerId: follower._id, + followeeId: userId + }); + + if (following === null) { + return rej('following not found'); + } + + // Stalk + await Following.update({ _id: following._id }, { + $set: { + stalk: true + } + }); + + // Send response + res(); + + // TODO: イベント +}); diff --git a/src/server/api/endpoints/following/unstalk.ts b/src/server/api/endpoints/following/unstalk.ts new file mode 100644 index 0000000000..d7593bcd00 --- /dev/null +++ b/src/server/api/endpoints/following/unstalk.ts @@ -0,0 +1,35 @@ +import $ from 'cafy'; +import Following from '../../../../models/following'; + +/** + * Unstalk a user + */ +module.exports = (params, user) => new Promise(async (res, rej) => { + const follower = user; + + // Get 'userId' parameter + const [userId, userIdErr] = $(params.userId).id().$; + if (userIdErr) return rej('invalid userId param'); + + // Fetch following + const following = await Following.findOne({ + followerId: follower._id, + followeeId: userId + }); + + if (following === null) { + return rej('following not found'); + } + + // Stalk + await Following.update({ _id: following._id }, { + $set: { + stalk: false + } + }); + + // Send response + res(); + + // TODO: イベント +}); diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts index 3b4899682d..69a8910898 100644 --- a/src/server/api/endpoints/i/notifications.ts +++ b/src/server/api/endpoints/i/notifications.ts @@ -5,7 +5,7 @@ import $ from 'cafy'; import Notification from '../../../../models/notification'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/notification'; -import getFriends from '../../common/get-friends'; +import { getFriendIds } from '../../common/get-friends'; import read from '../../common/read-notification'; /** @@ -62,7 +62,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { if (following) { // ID list of the user itself and other users who the user follows - const followingIds = await getFriends(user._id); + const followingIds = await getFriendIds(user._id); query.$and.push({ notifierId: { diff --git a/src/server/api/endpoints/mute/list.ts b/src/server/api/endpoints/mute/list.ts index bd80401445..0b8262d6c5 100644 --- a/src/server/api/endpoints/mute/list.ts +++ b/src/server/api/endpoints/mute/list.ts @@ -4,7 +4,7 @@ import $ from 'cafy'; import Mute from '../../../../models/mute'; import { pack } from '../../../../models/user'; -import getFriends from '../../common/get-friends'; +import { getFriendIds } from '../../common/get-friends'; /** * Get muted users of a user @@ -34,7 +34,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => { if (iknow) { // Get my friends - const myFriends = await getFriends(me._id); + const myFriends = await getFriendIds(me._id); query.muteeId = { $in: myFriends diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index c507acbaec..2d95606b3f 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -3,7 +3,7 @@ */ import $ from 'cafy'; import Note from '../../../../models/note'; -import getFriends from '../../common/get-friends'; +import { getFriendIds } from '../../common/get-friends'; import { pack } from '../../../../models/note'; /** @@ -46,7 +46,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { }; if (following) { - const followingIds = await getFriends(user._id); + const followingIds = await getFriendIds(user._id); query.userId = { $in: followingIds diff --git a/src/server/api/endpoints/notes/search.ts b/src/server/api/endpoints/notes/search.ts index bfa17b000e..3ff3fbbafa 100644 --- a/src/server/api/endpoints/notes/search.ts +++ b/src/server/api/endpoints/notes/search.ts @@ -6,7 +6,7 @@ const escapeRegexp = require('escape-regexp'); import Note from '../../../../models/note'; import User from '../../../../models/user'; import Mute from '../../../../models/mute'; -import getFriends from '../../common/get-friends'; +import { getFriendIds } from '../../common/get-friends'; import { pack } from '../../../../models/note'; /** @@ -156,7 +156,7 @@ async function search( } if (following != null && me != null) { - const ids = await getFriends(me._id, false); + const ids = await getFriendIds(me._id, false); push({ userId: following ? { $in: ids diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index b5feaac817..8cd23fd369 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -2,11 +2,10 @@ * Module dependencies */ import $ from 'cafy'; -import rap from '@prezzemolo/rap'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import ChannelWatching from '../../../../models/channel-watching'; -import getFriends from '../../common/get-friends'; +import { getFriends } from '../../common/get-friends'; import { pack } from '../../../../models/note'; /** @@ -38,41 +37,66 @@ module.exports = async (params, user, app) => { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } - const { followingIds, watchingChannelIds, mutedUserIds } = await rap({ - // ID list of the user itself and other users who the user follows - followingIds: getFriends(user._id), + const [followings, watchingChannelIds, mutedUserIds] = await Promise.all([ + // フォローを取得 + // Fetch following + getFriends(user._id), // Watchしているチャンネルを取得 - watchingChannelIds: ChannelWatching.find({ + ChannelWatching.find({ userId: user._id, // 削除されたドキュメントは除く deletedAt: { $exists: false } }).then(watches => watches.map(w => w.channelId)), // ミュートしているユーザーを取得 - mutedUserIds: Mute.find({ + Mute.find({ muterId: user._id }).then(ms => ms.map(m => m.muteeId)) - }); + ]); //#region Construct query const sort = { _id: -1 }; + const followQuery = followings.map(f => f.stalk ? { + userId: f.id + } : { + userId: f.id, + + // ストーキングしてないならリプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める) + $or: [{ + // リプライでない + replyId: null + }, { // または + // リプライだが返信先が投稿者自身の投稿 + $expr: { + '$_reply.userId': '$userId' + } + }, { // または + // リプライだが返信先が自分(フォロワー)の投稿 + '_reply.userId': user._id + }, { // または + // 自分(フォロワー)が送信したリプライ + userId: user._id + }] + }); + const query = { $or: [{ - // フォローしている人のタイムラインへの投稿 - userId: { - $in: followingIds - }, - // 「タイムラインへの」投稿に限定するためにチャンネルが指定されていないもののみに限る - $or: [{ - channelId: { - $exists: false - } + $and: [{ + // フォローしている人のタイムラインへの投稿 + $or: followQuery }, { - channelId: null + // 「タイムラインへの」投稿に限定するためにチャンネルが指定されていないもののみに限る + $or: [{ + channelId: { + $exists: false + } + }, { + channelId: null + }] }] }, { // Watchしているチャンネルへの投稿 diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts index 0222313e81..5f03326be8 100644 --- a/src/server/api/endpoints/users/followers.ts +++ b/src/server/api/endpoints/users/followers.ts @@ -5,7 +5,7 @@ import $ from 'cafy'; import User from '../../../../models/user'; import Following from '../../../../models/following'; import { pack } from '../../../../models/user'; -import getFriends from '../../common/get-friends'; +import { getFriendIds } from '../../common/get-friends'; /** * Get followers of a user @@ -52,7 +52,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => { // ログインしていてかつ iknow フラグがあるとき if (me && iknow) { // Get my friends - const myFriends = await getFriends(me._id); + const myFriends = await getFriendIds(me._id); query.followerId = { $in: myFriends diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts index 2372f57fbe..9fb135b24d 100644 --- a/src/server/api/endpoints/users/following.ts +++ b/src/server/api/endpoints/users/following.ts @@ -5,7 +5,7 @@ import $ from 'cafy'; import User from '../../../../models/user'; import Following from '../../../../models/following'; import { pack } from '../../../../models/user'; -import getFriends from '../../common/get-friends'; +import { getFriendIds } from '../../common/get-friends'; /** * Get following users of a user @@ -52,7 +52,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => { // ログインしていてかつ iknow フラグがあるとき if (me && iknow) { // Get my friends - const myFriends = await getFriends(me._id); + const myFriends = await getFriendIds(me._id); query.followeeId = { $in: myFriends diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index 2a6d36b75b..f72bb04bf1 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -4,7 +4,7 @@ const ms = require('ms'); import $ from 'cafy'; import User, { pack } from '../../../../models/user'; -import getFriends from '../../common/get-friends'; +import { getFriendIds } from '../../common/get-friends'; import Mute from '../../../../models/mute'; /** @@ -24,7 +24,7 @@ module.exports = (params, me) => new Promise(async (res, rej) => { if (offsetErr) return rej('invalid offset param'); // ID list of the user itself and other users who the user follows - const followingIds = await getFriends(me._id); + const followingIds = await getFriendIds(me._id); // ミュートしているユーザーを取得 const mutedUserIds = (await Mute.find({ diff --git a/src/services/following/create.ts b/src/services/following/create.ts index 375b028912..3424c55dae 100644 --- a/src/services/following/create.ts +++ b/src/services/following/create.ts @@ -13,7 +13,18 @@ export default async function(follower: IUser, followee: IUser, activity?) { const following = await Following.insert({ createdAt: new Date(), followerId: follower._id, - followeeId: followee._id + followeeId: followee._id, + stalk: true, + + // 非正規化 + _follower: { + host: follower.host, + inbox: isRemoteUser(follower) ? follower.inbox : undefined + }, + _followee: { + host: followee.host, + inbox: isRemoteUser(followee) ? followee.inbox : undefined + } }); //#region Increment following count diff --git a/src/services/note/create.ts b/src/services/note/create.ts index bdef5e09fc..32db77011f 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -124,19 +124,8 @@ export default async (user: IUser, data: { publishGlobalTimelineStream(noteObj); // Fetch all followers - const followers = await Following.aggregate([{ - $lookup: { - from: 'users', - localField: 'followerId', - foreignField: '_id', - as: 'user' - } - }, { - $match: { - followeeId: note.userId - } - }], { - _id: false + const followers = await Following.find({ + followeeId: note.userId }); if (!silent) { @@ -157,12 +146,15 @@ export default async (user: IUser, data: { deliver(user, await render(), data.renote._user.inbox); } - Promise.all(followers.map(async follower => { - follower = follower.user[0]; + Promise.all(followers.map(async following => { + const follower = following._follower; if (isLocalUser(follower)) { + // この投稿が返信かつstalkフォローでないならスキップ + if (note.replyId && !following.stalk) return; + // Publish event to followers stream - stream(follower._id, 'note', noteObj); + stream(following.followerId, 'note', noteObj); } else { // フォロワーがリモートユーザーかつ投稿者がローカルユーザーなら投稿を配信 if (isLocalUser(user)) { -- cgit v1.2.3-freya From d02afbee96f497f1b986a7a8656bd43192beec6c Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 19 Apr 2018 13:06:12 +0900 Subject: oops --- src/server/api/endpoints/notes/timeline.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/server/api') diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 8cd23fd369..cb14fa6eb0 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -72,7 +72,7 @@ module.exports = async (params, user, app) => { }, { // または // リプライだが返信先が投稿者自身の投稿 $expr: { - '$_reply.userId': '$userId' + $eq: ['$_reply.userId', '$userId'] } }, { // または // リプライだが返信先が自分(フォロワー)の投稿 -- cgit v1.2.3-freya From 4953842ff1c65fc850c856e11598e9901ff7c6e1 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 20 Apr 2018 13:31:43 +0900 Subject: :v: --- locales/en.yml | 2 + locales/fr.yml | 2 + locales/ja.yml | 2 + .../app/common/views/components/note-menu.vue | 10 +++ src/client/app/desktop/script.ts | 2 + src/client/app/desktop/views/pages/favorites.vue | 73 ++++++++++++++++++++++ src/models/favorite.ts | 34 ++++++++++ src/server/api/endpoints.ts | 6 ++ src/server/api/endpoints/i/favorites.ts | 59 +++++++++-------- 9 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 src/client/app/desktop/views/pages/favorites.vue (limited to 'src/server/api') diff --git a/locales/en.yml b/locales/en.yml index db7ad786b9..0a05393227 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -98,7 +98,9 @@ common/views/components/nav.vue: feedback: "Feedback" common/views/components/note-menu.vue: + favorite: "Favorite this note" pin: "Pin to profile page" + remote: "Show on origin" common/views/components/poll.vue: vote-to: "Vote for '{}'" diff --git a/locales/fr.yml b/locales/fr.yml index 09e5b3878c..e640c4883c 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -98,7 +98,9 @@ common/views/components/nav.vue: feedback: "フィードバック" common/views/components/note-menu.vue: + favorite: "Favorite this note" pin: "Épingler sur votre profile" + remote: "投稿元で見る" common/views/components/poll.vue: vote-to: "Voter pour '{}'" diff --git a/locales/ja.yml b/locales/ja.yml index 2401bf1fc1..3d023281cd 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -98,7 +98,9 @@ common/views/components/nav.vue: feedback: "フィードバック" common/views/components/note-menu.vue: + favorite: "お気に入り" pin: "ピン留め" + remote: "投稿元で見る" common/views/components/poll.vue: vote-to: "「{}」に投票する" diff --git a/src/client/app/common/views/components/note-menu.vue b/src/client/app/common/views/components/note-menu.vue index 877d2c16bb..3e4be425d9 100644 --- a/src/client/app/common/views/components/note-menu.vue +++ b/src/client/app/common/views/components/note-menu.vue @@ -2,6 +2,7 @@
+ %i18n:@remote%
@@ -58,6 +59,14 @@ export default Vue.extend({ }); }, + favorite() { + (this as any).api('notes/favorites/create', { + noteId: this.note.id + }).then(() => { + this.$destroy(); + }); + }, + close() { (this.$refs.backdrop as any).style.pointerEvents = 'none'; anime({ @@ -142,6 +151,7 @@ $border-color = rgba(27, 31, 35, 0.15) > a display block padding 8px 16px + width 100% &:hover color $theme-color-foreground diff --git a/src/client/app/desktop/script.ts b/src/client/app/desktop/script.ts index 8d95d81775..3b0ed48cd0 100644 --- a/src/client/app/desktop/script.ts +++ b/src/client/app/desktop/script.ts @@ -25,6 +25,7 @@ import updateBanner from './api/update-banner'; import MkIndex from './views/pages/index.vue'; import MkUser from './views/pages/user/user.vue'; +import MkFavorites from './views/pages/favorites.vue'; import MkSelectDrive from './views/pages/selectdrive.vue'; import MkDrive from './views/pages/drive.vue'; import MkHomeCustomize from './views/pages/home-customize.vue'; @@ -50,6 +51,7 @@ init(async (launch) => { routes: [ { path: '/', name: 'index', component: MkIndex }, { path: '/i/customize-home', component: MkHomeCustomize }, + { path: '/i/favorites', component: MkFavorites }, { path: '/i/messaging/:user', component: MkMessagingRoom }, { path: '/i/drive', component: MkDrive }, { path: '/i/drive/folder/:folder', component: MkDrive }, diff --git a/src/client/app/desktop/views/pages/favorites.vue b/src/client/app/desktop/views/pages/favorites.vue new file mode 100644 index 0000000000..d908c08f7c --- /dev/null +++ b/src/client/app/desktop/views/pages/favorites.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/src/models/favorite.ts b/src/models/favorite.ts index 5387b29457..d24833f191 100644 --- a/src/models/favorite.ts +++ b/src/models/favorite.ts @@ -1,5 +1,7 @@ import * as mongo from 'mongodb'; +import deepcopy = require('deepcopy'); import db from '../db/mongodb'; +import { pack as packNote } from './note'; const Favorite = db.get('favorites'); Favorite.createIndex(['userId', 'noteId'], { unique: true }); @@ -38,3 +40,35 @@ export async function deleteFavorite(favorite: string | mongo.ObjectID | IFavori _id: f._id }); } + +/** + * Pack a favorite for API response + */ +export const pack = ( + favorite: any, + me: any +) => new Promise(async (resolve, reject) => { + let _favorite: any; + + // Populate the favorite if 'favorite' is ID + if (mongo.ObjectID.prototype.isPrototypeOf(favorite)) { + _favorite = await Favorite.findOne({ + _id: favorite + }); + } else if (typeof favorite === 'string') { + _favorite = await Favorite.findOne({ + _id: new mongo.ObjectID(favorite) + }); + } else { + _favorite = deepcopy(favorite); + } + + // Rename _id to id + _favorite.id = _favorite._id; + delete _favorite._id; + + // Populate note + _favorite.note = await packNote(_favorite.noteId, me); + + resolve(_favorite); +}); diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts index 7cf49debe9..3686918147 100644 --- a/src/server/api/endpoints.ts +++ b/src/server/api/endpoints.ts @@ -233,6 +233,12 @@ const endpoints: Endpoint[] = [ kind: 'notification-read' }, + { + name: 'i/favorites', + withCredential: true, + kind: 'favorites-read' + }, + { name: 'othello/match', withCredential: true diff --git a/src/server/api/endpoints/i/favorites.ts b/src/server/api/endpoints/i/favorites.ts index b40f2b3887..f390ef9ec7 100644 --- a/src/server/api/endpoints/i/favorites.ts +++ b/src/server/api/endpoints/i/favorites.ts @@ -2,43 +2,52 @@ * Module dependencies */ import $ from 'cafy'; -import Favorite from '../../../../models/favorite'; -import { pack } from '../../../../models/note'; +import Favorite, { pack } from '../../../../models/favorite'; /** - * Get followers of a user - * - * @param {any} params - * @param {any} user - * @return {Promise} + * Get favorited notes */ module.exports = (params, user) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; if (limitErr) return rej('invalid limit param'); - // Get 'offset' parameter - const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; - if (offsetErr) return rej('invalid offset param'); + // Get 'sinceId' parameter + const [sinceId, sinceIdErr] = $(params.sinceId).optional.id().$; + if (sinceIdErr) return rej('invalid sinceId param'); - // Get 'sort' parameter - const [sort = 'desc', sortError] = $(params.sort).optional.string().or('desc asc').$; - if (sortError) return rej('invalid sort param'); + // Get 'untilId' parameter + const [untilId, untilIdErr] = $(params.untilId).optional.id().$; + if (untilIdErr) return rej('invalid untilId param'); + + // Check if both of sinceId and untilId is specified + if (sinceId && untilId) { + return rej('cannot set sinceId and untilId'); + } + + const query = { + userId: user._id + } as any; + + const sort = { + _id: -1 + }; + + if (sinceId) { + sort._id = 1; + query._id = { + $gt: sinceId + }; + } else if (untilId) { + query._id = { + $lt: untilId + }; + } // Get favorites const favorites = await Favorite - .find({ - userId: user._id - }, { - limit: limit, - skip: offset, - sort: { - _id: sort == 'asc' ? 1 : -1 - } - }); + .find(query, { limit, sort }); // Serialize - res(await Promise.all(favorites.map(async favorite => - await pack(favorite.noteId) - ))); + res(await Promise.all(favorites.map(favorite => pack(favorite, user)))); }); -- cgit v1.2.3-freya From a9a7a89b8b286f2cd00c00311c5b7b2ea49cafe6 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 21 Apr 2018 11:42:38 +0900 Subject: #1506 --- src/client/app/common/mios.ts | 4 +- .../app/desktop/views/components/settings.vue | 14 +++ .../app/desktop/views/components/timeline.core.vue | 23 ++++- .../app/mobile/views/components/timeline.vue | 33 ++++++- src/server/api/endpoints/notes/timeline.ts | 102 +++++++++++++++------ 5 files changed, 142 insertions(+), 34 deletions(-) (limited to 'src/server/api') diff --git a/src/client/app/common/mios.ts b/src/client/app/common/mios.ts index 6fed267d6e..463f763888 100644 --- a/src/client/app/common/mios.ts +++ b/src/client/app/common/mios.ts @@ -21,7 +21,9 @@ const defaultSettings = { showMaps: true, showPostFormOnTopOfTl: false, gradientWindowHeader: false, - showReplyTarget: true + showReplyTarget: true, + showMyRenotes: true, + showRenotedMyNotes: true }; //#region api requests diff --git a/src/client/app/desktop/views/components/settings.vue b/src/client/app/desktop/views/components/settings.vue index adfe43bb64..b5111dabc9 100644 --- a/src/client/app/desktop/views/components/settings.vue +++ b/src/client/app/desktop/views/components/settings.vue @@ -45,6 +45,8 @@
+ + 位置情報が添付された投稿のマップを自動的に展開します。 @@ -319,6 +321,18 @@ export default Vue.extend({ value: v }); }, + onChangeShowMyRenotes(v) { + (this as any).api('i/update_client_setting', { + name: 'showMyRenotes', + value: v + }); + }, + onChangeShowRenotedMyNotes(v) { + (this as any).api('i/update_client_setting', { + name: 'showRenotedMyNotes', + value: v + }); + }, onChangeShowMaps(v) { (this as any).api('i/update_client_setting', { name: 'showMaps', diff --git a/src/client/app/desktop/views/components/timeline.core.vue b/src/client/app/desktop/views/components/timeline.core.vue index 1e98f087e1..f66ae57885 100644 --- a/src/client/app/desktop/views/components/timeline.core.vue +++ b/src/client/app/desktop/views/components/timeline.core.vue @@ -90,7 +90,9 @@ export default Vue.extend({ (this as any).api(this.endpoint, { limit: 11, - untilDate: this.date ? this.date.getTime() : undefined + untilDate: this.date ? this.date.getTime() : undefined, + includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, + includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes }).then(notes => { if (notes.length == 11) { notes.pop(); @@ -108,7 +110,9 @@ export default Vue.extend({ this.moreFetching = true; (this as any).api(this.endpoint, { limit: 11, - untilId: this.notes[this.notes.length - 1].id + untilId: this.notes[this.notes.length - 1].id, + includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, + includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes }).then(notes => { if (notes.length == 11) { notes.pop(); @@ -121,6 +125,21 @@ export default Vue.extend({ }, onNote(note) { + const isMyNote = note.userId == (this as any).os.i.id; + const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null; + + if ((this as any).os.i.clientSettings.showMyRenotes === false) { + if (isMyNote && isPureRenote) { + return; + } + } + + if ((this as any).os.i.clientSettings.showRenotedMyNotes === false) { + if (isPureRenote && (note.renote.userId == (this as any).os.i.id)) { + return; + } + } + // サウンドを再生する if ((this as any).os.isEnableSounds) { const sound = new Audio(`${url}/assets/post.mp3`); diff --git a/src/client/app/mobile/views/components/timeline.vue b/src/client/app/mobile/views/components/timeline.vue index 11b82aa456..a6227996b8 100644 --- a/src/client/app/mobile/views/components/timeline.vue +++ b/src/client/app/mobile/views/components/timeline.vue @@ -30,6 +30,7 @@ export default Vue.extend({ default: null } }, + data() { return { fetching: true, @@ -40,11 +41,13 @@ export default Vue.extend({ connectionId: null }; }, + computed: { alone(): boolean { return (this as any).os.i.followingCount == 0; } }, + mounted() { this.connection = (this as any).os.stream.getConnection(); this.connectionId = (this as any).os.stream.use(); @@ -53,20 +56,24 @@ export default Vue.extend({ this.connection.on('follow', this.onChangeFollowing); this.connection.on('unfollow', this.onChangeFollowing); -this.fetch(); + this.fetch(); }, + beforeDestroy() { this.connection.off('note', this.onNote); this.connection.off('follow', this.onChangeFollowing); this.connection.off('unfollow', this.onChangeFollowing); (this as any).os.stream.dispose(this.connectionId); }, + methods: { fetch(cb?) { this.fetching = true; (this as any).api('notes/timeline', { limit: limit + 1, - untilDate: this.date ? (this.date as any).getTime() : undefined + untilDate: this.date ? (this.date as any).getTime() : undefined, + includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, + includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes }).then(notes => { if (notes.length == limit + 1) { notes.pop(); @@ -78,11 +85,14 @@ this.fetch(); if (cb) cb(); }); }, + more() { this.moreFetching = true; (this as any).api('notes/timeline', { limit: limit + 1, - untilId: this.notes[this.notes.length - 1].id + untilId: this.notes[this.notes.length - 1].id, + includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, + includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes }).then(notes => { if (notes.length == limit + 1) { notes.pop(); @@ -94,12 +104,29 @@ this.fetch(); this.moreFetching = false; }); }, + onNote(note) { + const isMyNote = note.userId == (this as any).os.i.id; + const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null; + + if ((this as any).os.i.clientSettings.showMyRenotes === false) { + if (isMyNote && isPureRenote) { + return; + } + } + + if ((this as any).os.i.clientSettings.showRenotedMyNotes === false) { + if (isPureRenote && (note.renote.userId == (this as any).os.i.id)) { + return; + } + } + this.notes.unshift(note); const isTop = window.scrollY > 8; if (isTop) this.notes.pop(); }, + onChangeFollowing() { this.fetch(); } diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index cb14fa6eb0..de30afea57 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -37,6 +37,14 @@ module.exports = async (params, user, app) => { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } + // Get 'includeMyRenotes' parameter + const [includeMyRenotes = true, includeMyRenotesErr] = $(params.includeMyRenotes).optional.boolean().$; + if (includeMyRenotesErr) throw 'invalid includeMyRenotes param'; + + // Get 'includeRenotedMyNotes' parameter + const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $(params.includeRenotedMyNotes).optional.boolean().$; + if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param'; + const [followings, watchingChannelIds, mutedUserIds] = await Promise.all([ // フォローを取得 // Fetch following @@ -84,38 +92,76 @@ module.exports = async (params, user, app) => { }); const query = { - $or: [{ - $and: [{ - // フォローしている人のタイムラインへの投稿 - $or: followQuery - }, { - // 「タイムラインへの」投稿に限定するためにチャンネルが指定されていないもののみに限る - $or: [{ - channelId: { - $exists: false - } + $and: [{ + $or: [{ + $and: [{ + // フォローしている人のタイムラインへの投稿 + $or: followQuery }, { - channelId: null + // 「タイムラインへの」投稿に限定するためにチャンネルが指定されていないもののみに限る + $or: [{ + channelId: { + $exists: false + } + }, { + channelId: null + }] }] - }] - }, { - // Watchしているチャンネルへの投稿 - channelId: { - $in: watchingChannelIds - } - }], - // mute - userId: { - $nin: mutedUserIds - }, - '_reply.userId': { - $nin: mutedUserIds - }, - '_renote.userId': { - $nin: mutedUserIds - }, + }, { + // Watchしているチャンネルへの投稿 + channelId: { + $in: watchingChannelIds + } + }], + // mute + userId: { + $nin: mutedUserIds + }, + '_reply.userId': { + $nin: mutedUserIds + }, + '_renote.userId': { + $nin: mutedUserIds + }, + }] } as any; + // MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。 + // つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。 + // for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws + + if (includeMyRenotes === false) { + query.$and.push({ + $or: [{ + userId: { $ne: user._id } + }, { + renoteId: null + }, { + text: { $ne: null } + }, { + mediaIds: { $ne: [] } + }, { + poll: { $ne: null } + }] + }); + } + + if (includeRenotedMyNotes === false) { + query.$and.push({ + $or: [{ + '_renote.userId': { $ne: user._id } + }, { + renoteId: null + }, { + text: { $ne: null } + }, { + mediaIds: { $ne: [] } + }, { + poll: { $ne: null } + }] + }); + } + if (sinceId) { sort._id = 1; query._id = { -- 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/server/api') 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/server/api') 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% 位置情報 +
+
+ +
+
-