mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-11 07:15:28 +02:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9059c149dd | ||
|
|
7d8e70b2ac | ||
|
|
89105f5641 | ||
|
|
1813d17b4c | ||
|
|
ce27b36fd0 | ||
|
|
e635a87628 | ||
|
|
80c52433cc | ||
|
|
1472f0b141 | ||
|
|
4d914f5c0a | ||
|
|
0318f7344f | ||
|
|
413fbb3d0c | ||
|
|
8bc47baf4f | ||
|
|
e3f6d42a47 | ||
|
|
8230935fd3 |
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "2.37.7",
|
||||
"clientVersion": "1.0.6474",
|
||||
"version": "2.38.3",
|
||||
"clientVersion": "1.0.6486",
|
||||
"codename": "nighthike",
|
||||
"main": "./built/index.js",
|
||||
"private": true,
|
||||
|
||||
@@ -44,10 +44,10 @@ export default Vue.component('mk-note-html', {
|
||||
return;
|
||||
}
|
||||
|
||||
while (
|
||||
while (ast[ast.length - 1] && (
|
||||
ast[ast.length - 1].type == 'hashtag' ||
|
||||
(ast[ast.length - 1].type == 'text' && ast[ast.length - 1].content == ' ') ||
|
||||
(ast[ast.length - 1].type == 'text' && ast[ast.length - 1].content == '\n')) {
|
||||
(ast[ast.length - 1].type == 'text' && ast[ast.length - 1].content == '\n'))) {
|
||||
ast.pop();
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ export default Vue.component('mk-note-html', {
|
||||
case 'hashtag':
|
||||
return createElement('a', {
|
||||
attrs: {
|
||||
href: `${url}/tags/${token.content}`,
|
||||
href: `${url}/tags/${token.hashtag}`,
|
||||
target: '_blank'
|
||||
}
|
||||
}, token.content);
|
||||
|
||||
@@ -76,13 +76,8 @@ root(isDark)
|
||||
margin-right 4px
|
||||
|
||||
> div
|
||||
.chart-enter
|
||||
.chart-leave-to
|
||||
opacity 0
|
||||
transform translateY(-30px)
|
||||
|
||||
> *
|
||||
transition transform .3s ease, opacity .3s ease
|
||||
.chart-move
|
||||
transition transform 1s ease
|
||||
|
||||
> div
|
||||
display flex
|
||||
|
||||
@@ -50,6 +50,7 @@ import * as XDraggable from 'vuedraggable';
|
||||
import getKao from '../../../common/scripts/get-kao';
|
||||
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
|
||||
import parse from '../../../../../text/parse';
|
||||
import { host } from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@@ -129,6 +130,7 @@ export default Vue.extend({
|
||||
|
||||
// 自分は除外
|
||||
if (this.$store.state.i.username == x.username && x.host == null) return;
|
||||
if (this.$store.state.i.username == x.username && x.host == host) return;
|
||||
|
||||
// 重複は除外
|
||||
if (this.text.indexOf(`${mention} `) != -1) return;
|
||||
|
||||
@@ -42,6 +42,7 @@ import MkUserLists from './views/pages/user-lists.vue';
|
||||
import MkUserList from './views/pages/user-list.vue';
|
||||
import MkSettings from './views/pages/settings.vue';
|
||||
import MkOthello from './views/pages/othello.vue';
|
||||
import MkTag from './views/pages/tag.vue';
|
||||
|
||||
Vue.use(MdCard);
|
||||
Vue.use(MdButton);
|
||||
@@ -88,6 +89,7 @@ init((launch) => {
|
||||
{ path: '/i/drive/file/:file', component: MkDrive },
|
||||
{ path: '/selectdrive', component: MkSelectDrive },
|
||||
{ path: '/search', component: MkSearch },
|
||||
{ path: '/tags/:tag', component: MkTag },
|
||||
{ path: '/othello', name: 'othello', component: MkOthello },
|
||||
{ path: '/othello/:game', component: MkOthello },
|
||||
{ path: '/@:user', component: MkUser },
|
||||
|
||||
@@ -46,6 +46,7 @@ import * as XDraggable from 'vuedraggable';
|
||||
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
|
||||
import getKao from '../../../common/scripts/get-kao';
|
||||
import parse from '../../../../../text/parse';
|
||||
import { host } from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@@ -123,6 +124,7 @@ export default Vue.extend({
|
||||
|
||||
// 自分は除外
|
||||
if (this.$store.state.i.username == x.username && x.host == null) return;
|
||||
if (this.$store.state.i.username == x.username && x.host == host) return;
|
||||
|
||||
// 重複は除外
|
||||
if (this.text.indexOf(`${mention} `) != -1) return;
|
||||
|
||||
81
src/client/app/mobile/views/pages/tag.vue
Normal file
81
src/client/app/mobile/views/pages/tag.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<mk-ui>
|
||||
<span slot="header">%fa:hashtag%{{ $route.params.tag }}</span>
|
||||
|
||||
<main>
|
||||
<p v-if="!fetching && empty">%fa:search%「{{ q }}」に関する投稿は見つかりませんでした。</p>
|
||||
<mk-notes ref="timeline" :more="existMore ? more : null"/>
|
||||
</main>
|
||||
</mk-ui>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
|
||||
const limit = 20;
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
moreFetching: false,
|
||||
existMore: false,
|
||||
offset: 0,
|
||||
empty: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
$route: 'fetch'
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.fetch();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
fetch() {
|
||||
this.fetching = true;
|
||||
Progress.start();
|
||||
|
||||
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
|
||||
(this as any).api('notes/search_by_tag', {
|
||||
limit: limit + 1,
|
||||
offset: this.offset,
|
||||
tag: this.$route.params.tag
|
||||
}).then(notes => {
|
||||
if (notes.length == 0) this.empty = true;
|
||||
if (notes.length == limit + 1) {
|
||||
notes.pop();
|
||||
this.existMore = true;
|
||||
}
|
||||
res(notes);
|
||||
this.fetching = false;
|
||||
Progress.done();
|
||||
}, rej);
|
||||
}));
|
||||
},
|
||||
more() {
|
||||
this.offset += limit;
|
||||
|
||||
const promise = (this as any).api('notes/search_by_tag', {
|
||||
limit: limit + 1,
|
||||
offset: this.offset,
|
||||
tag: this.$route.params.tag
|
||||
});
|
||||
|
||||
promise.then(notes => {
|
||||
if (notes.length == limit + 1) {
|
||||
notes.pop();
|
||||
} else {
|
||||
this.existMore = false;
|
||||
}
|
||||
notes.forEach(n => (this.$refs.timeline as any).append(n));
|
||||
this.moreFetching = false;
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -48,6 +48,11 @@ export type INote = {
|
||||
repliesCount: number;
|
||||
reactionCounts: any;
|
||||
mentions: mongo.ObjectID[];
|
||||
mentionedRemoteUsers: Array<{
|
||||
uri: string;
|
||||
username: string;
|
||||
host: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* public ... 公開
|
||||
@@ -289,7 +294,7 @@ export const pack = async (
|
||||
|
||||
// Poll
|
||||
if (meId && _note.poll && !hide) {
|
||||
_note.poll = (async (poll) => {
|
||||
_note.poll = (async poll => {
|
||||
const vote = await PollVote
|
||||
.findOne({
|
||||
userId: meId,
|
||||
|
||||
@@ -15,6 +15,11 @@ const log = debug('misskey:activitypub');
|
||||
export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, note: INote): Promise<void> {
|
||||
const uri = activity.id || activity;
|
||||
|
||||
// アナウンサーが凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof uri !== 'string') {
|
||||
throw new Error('invalid announce');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import config from '../../../config';
|
||||
|
||||
export default tag => ({
|
||||
export default (tag: string) => ({
|
||||
type: 'Hashtag',
|
||||
href: `${config.url}/tags/${encodeURIComponent(tag)}`,
|
||||
name: '#' + tag
|
||||
|
||||
9
src/remote/activitypub/renderer/mention.ts
Normal file
9
src/remote/activitypub/renderer/mention.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export default (mention: {
|
||||
uri: string;
|
||||
username: string;
|
||||
host: string;
|
||||
}) => ({
|
||||
type: 'Mention',
|
||||
href: mention.uri,
|
||||
name: `@${mention.username}@${mention.host}`
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import renderDocument from './document';
|
||||
import renderHashtag from './hashtag';
|
||||
import renderMention from './mention';
|
||||
import config from '../../../config';
|
||||
import DriveFile from '../../../models/drive-file';
|
||||
import Note, { INote } from '../../../models/note';
|
||||
@@ -45,6 +46,18 @@ export default async function renderNote(note: INote, dive = true) {
|
||||
|
||||
const attributedTo = `${config.url}/users/${user._id}`;
|
||||
|
||||
const mentions = note.mentionedRemoteUsers && note.mentionedRemoteUsers.length > 0
|
||||
? note.mentionedRemoteUsers.map(x => x.uri)
|
||||
: [];
|
||||
|
||||
const cc = ['public', 'home', 'followers'].includes(note.visibility)
|
||||
? [`${attributedTo}/followers`].concat(mentions)
|
||||
: [];
|
||||
|
||||
const hashtagTags = (note.tags || []).map(renderHashtag);
|
||||
const mentionTags = (note.mentionedRemoteUsers || []).map(renderMention);
|
||||
const tag = hashtagTags.concat(mentionTags)
|
||||
|
||||
return {
|
||||
id: `${config.url}/notes/${note._id}`,
|
||||
type: 'Note',
|
||||
@@ -52,9 +65,9 @@ export default async function renderNote(note: INote, dive = true) {
|
||||
content: toHtml(note),
|
||||
published: note.createdAt.toISOString(),
|
||||
to: 'https://www.w3.org/ns/activitystreams#Public',
|
||||
cc: `${attributedTo}/followers`,
|
||||
cc,
|
||||
inReplyTo,
|
||||
attachment: (await promisedFiles).map(renderDocument),
|
||||
tag: (note.tags || []).map(renderHashtag)
|
||||
tag
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as debug from 'debug';
|
||||
|
||||
import User, { IUser } from '../../../models/user';
|
||||
import Mute from '../../../models/mute';
|
||||
import { pack as packNote } from '../../../models/note';
|
||||
import { pack as packNote, pack } from '../../../models/note';
|
||||
import readNotification from '../common/read-notification';
|
||||
import call from '../call';
|
||||
import { IApp } from '../../../models/app';
|
||||
@@ -48,6 +48,14 @@ export default async function(
|
||||
}
|
||||
//#endregion
|
||||
|
||||
// Renoteなら再pack
|
||||
if (x.type == 'note' && x.body.renoteId != null) {
|
||||
x.body.renote = await pack(x.body.renoteId, user, {
|
||||
detail: true
|
||||
});
|
||||
data = JSON.stringify(x);
|
||||
}
|
||||
|
||||
connection.send(data);
|
||||
} catch (e) {
|
||||
connection.send(data);
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as redis from 'redis';
|
||||
|
||||
import { IUser } from '../../../models/user';
|
||||
import Mute from '../../../models/mute';
|
||||
import { pack } from '../../../models/note';
|
||||
|
||||
export default async function(
|
||||
request: websocket.request,
|
||||
@@ -31,6 +32,13 @@ export default async function(
|
||||
}
|
||||
//#endregion
|
||||
|
||||
// Renoteなら再pack
|
||||
if (note.renoteId != null) {
|
||||
note.renote = await pack(note.renoteId, user, {
|
||||
detail: true
|
||||
});
|
||||
}
|
||||
|
||||
connection.send(JSON.stringify({
|
||||
type: 'note',
|
||||
body: note
|
||||
|
||||
@@ -204,6 +204,62 @@ export default async (user: IUser, data: {
|
||||
return packAp(content);
|
||||
};
|
||||
|
||||
//#region メンション
|
||||
if (data.text) {
|
||||
// TODO: Drop dupulicates
|
||||
const mentionTokens = tokens
|
||||
.filter(t => t.type == 'mention');
|
||||
|
||||
// TODO: Drop dupulicates
|
||||
const mentionedUsers = (await Promise.all(mentionTokens.map(async m => {
|
||||
try {
|
||||
return await resolveUser(m.username, m.host);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}))).filter(x => x != null);
|
||||
|
||||
// Append mentions data
|
||||
if (mentionedUsers.length > 0) {
|
||||
const set = {
|
||||
mentions: mentionedUsers.map(u => u._id),
|
||||
mentionedRemoteUsers: mentionedUsers.filter(u => isRemoteUser(u)).map(u => ({
|
||||
uri: (u as IRemoteUser).uri,
|
||||
username: u.username,
|
||||
host: u.host
|
||||
}))
|
||||
};
|
||||
|
||||
Note.update({ _id: note._id }, {
|
||||
$set: set
|
||||
});
|
||||
|
||||
Object.assign(note, set);
|
||||
}
|
||||
|
||||
mentionedUsers.filter(u => isLocalUser(u)).forEach(async u => {
|
||||
event(u, 'mention', noteObj);
|
||||
|
||||
// 既に言及されたユーザーに対する返信や引用renoteの場合も無視
|
||||
if (data.reply && data.reply.userId.equals(u._id)) return;
|
||||
if (data.renote && data.renote.userId.equals(u._id)) return;
|
||||
|
||||
// Create notification
|
||||
notify(u._id, user._id, 'mention', {
|
||||
noteId: note._id
|
||||
});
|
||||
|
||||
nm.push(u._id, 'mention');
|
||||
});
|
||||
|
||||
if (isLocalUser(user)) {
|
||||
mentionedUsers.filter(u => isRemoteUser(u)).forEach(async u => {
|
||||
deliver(user, await render(), (u as IRemoteUser).inbox);
|
||||
});
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
if (!silent) {
|
||||
if (isLocalUser(user)) {
|
||||
if (note.visibility == 'private' || note.visibility == 'followers' || note.visibility == 'specified') {
|
||||
@@ -287,55 +343,6 @@ export default async (user: IUser, data: {
|
||||
}
|
||||
//#endergion
|
||||
|
||||
//#region メンション
|
||||
if (data.text) {
|
||||
// TODO: Drop dupulicates
|
||||
const mentions = tokens
|
||||
.filter(t => t.type == 'mention');
|
||||
|
||||
let mentionedUsers = await Promise.all(mentions.map(async m => {
|
||||
try {
|
||||
return await resolveUser(m.username, m.host);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}));
|
||||
|
||||
// TODO: Drop dupulicates
|
||||
mentionedUsers = mentionedUsers.filter(x => x != null);
|
||||
|
||||
mentionedUsers.filter(u => isLocalUser(u)).forEach(async u => {
|
||||
event(u, 'mention', noteObj);
|
||||
|
||||
// 既に言及されたユーザーに対する返信や引用renoteの場合も無視
|
||||
if (data.reply && data.reply.userId.equals(u._id)) return;
|
||||
if (data.renote && data.renote.userId.equals(u._id)) return;
|
||||
|
||||
// Create notification
|
||||
notify(u._id, user._id, 'mention', {
|
||||
noteId: note._id
|
||||
});
|
||||
|
||||
nm.push(u._id, 'mention');
|
||||
});
|
||||
|
||||
if (isLocalUser(user)) {
|
||||
mentionedUsers.filter(u => isRemoteUser(u)).forEach(async u => {
|
||||
deliver(user, await render(), (u as IRemoteUser).inbox);
|
||||
});
|
||||
}
|
||||
|
||||
// Append mentions data
|
||||
if (mentionedUsers.length > 0) {
|
||||
Note.update({ _id: note._id }, {
|
||||
$set: {
|
||||
mentions: mentionedUsers.map(u => u._id)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
// If has in reply to note
|
||||
if (data.reply) {
|
||||
// Increment replies count
|
||||
|
||||
@@ -20,6 +20,7 @@ export default async function(user: IUser, note: INote) {
|
||||
$set: {
|
||||
deletedAt: new Date(),
|
||||
text: null,
|
||||
tags: [],
|
||||
mediaIds: [],
|
||||
poll: null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user