Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
3b974428fc | |||
580191fb17 | |||
be0cb88b6c | |||
95c4e4497e |
@ -1,6 +1,12 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
10.66.1
|
||||
-------
|
||||
* ActivityPubのsharedInboxに関して修正
|
||||
* MFMでのカッコの判定を改善
|
||||
* バグ修正
|
||||
|
||||
10.66.0
|
||||
-------
|
||||
* ユーザーごとのRSSフィードを提供するように
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "10.66.0",
|
||||
"clientVersion": "2.0.12855",
|
||||
"version": "10.66.1",
|
||||
"clientVersion": "2.0.12859",
|
||||
"codename": "nighthike",
|
||||
"main": "./built/index.js",
|
||||
"private": true,
|
||||
|
@ -3,7 +3,7 @@
|
||||
<header :class="$style.header">
|
||||
<h1>#{{ $route.params.tag }}</h1>
|
||||
</header>
|
||||
<p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q }) }}</p>
|
||||
<p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q: $route.params.tag }) }}</p>
|
||||
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
|
||||
</mk-ui>
|
||||
</template>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<span slot="header"><span style="margin-right:4px;"><fa icon="hashtag"/></span>{{ $route.params.tag }}</span>
|
||||
|
||||
<main>
|
||||
<p v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q }) }}</p>
|
||||
<p v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q: $route.params.tag }) }}</p>
|
||||
<mk-notes ref="timeline" :more="existMore ? more : null"/>
|
||||
</main>
|
||||
</mk-ui>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as P from 'parsimmon';
|
||||
import parseAcct from '../misc/acct/parse';
|
||||
import { toUnicode } from 'punycode';
|
||||
import { takeWhile } from '../prelude/array';
|
||||
import { takeWhile, cumulativeSum } from '../prelude/array';
|
||||
import { Tree } from '../prelude/tree';
|
||||
import * as T from '../prelude/tree';
|
||||
|
||||
@ -42,30 +42,18 @@ export function createTree(type: string, children: MfmForest, props: any): MfmTr
|
||||
return T.createTree({ type, props }, children);
|
||||
}
|
||||
|
||||
function getTrailingPosition(x: string): number {
|
||||
const brackets = [
|
||||
['(', ')'],
|
||||
['「', '」'],
|
||||
];
|
||||
const pendingBrackets = [] as any;
|
||||
const end = x.split('').findIndex(char => {
|
||||
const closeMatch = brackets.map(x => x[1]).indexOf(char);
|
||||
const openMatch = brackets.map(x => x[0]).indexOf(char);
|
||||
if (closeMatch != -1) {
|
||||
if (pendingBrackets[closeMatch] > 0) {
|
||||
pendingBrackets[closeMatch]--;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else if (openMatch != -1) {
|
||||
pendingBrackets[openMatch] = (pendingBrackets[openMatch] || 0) + 1;
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return end > 0 ? end : x.length;
|
||||
export function removeOrphanedBrackets(s: string): string {
|
||||
const openBrackets = ['(', '「'];
|
||||
const closeBrackets = [')', '」'];
|
||||
const xs = cumulativeSum(s.split('').map(c => {
|
||||
if (openBrackets.includes(c)) return 1;
|
||||
if (closeBrackets.includes(c)) return -1;
|
||||
return 0;
|
||||
}));
|
||||
const firstOrphanedCloseBracket = xs.findIndex(x => x < 0);
|
||||
if (firstOrphanedCloseBracket !== -1) return s.substr(0, firstOrphanedCloseBracket);
|
||||
const lastMatched = xs.lastIndexOf(0);
|
||||
return s.substr(0, lastMatched + 1);
|
||||
}
|
||||
|
||||
const newline = P((input, i) => {
|
||||
@ -220,7 +208,7 @@ const mfm = P.createLanguage({
|
||||
const match = text.match(/^#([^\s\.,!\?#]+)/i);
|
||||
if (!match) return P.makeFailure(i, 'not a hashtag');
|
||||
let hashtag = match[1];
|
||||
hashtag = hashtag.substr(0, getTrailingPosition(hashtag));
|
||||
hashtag = removeOrphanedBrackets(hashtag);
|
||||
if (hashtag.match(/^[0-9]+$/)) return P.makeFailure(i, 'not a hashtag');
|
||||
if (input[i - 1] != null && input[i - 1].match(/[a-z0-9]/i)) return P.makeFailure(i, 'not a hashtag');
|
||||
if (hashtag.length > 50) return P.makeFailure(i, 'not a hashtag');
|
||||
@ -390,7 +378,7 @@ const mfm = P.createLanguage({
|
||||
const match = text.match(/^https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.,=\+\-]+/);
|
||||
if (!match) return P.makeFailure(i, 'not a url');
|
||||
let url = match[0];
|
||||
url = url.substr(0, getTrailingPosition(url));
|
||||
url = removeOrphanedBrackets(url);
|
||||
if (url.endsWith('.')) url = url.substr(0, url.lastIndexOf('.'));
|
||||
if (url.endsWith(',')) url = url.substr(0, url.lastIndexOf(','));
|
||||
return P.makeSuccess(i + url.length, url);
|
||||
|
@ -109,3 +109,9 @@ export function takeWhile<T>(f: Predicate<T>, xs: T[]): T[] {
|
||||
}
|
||||
return ys;
|
||||
}
|
||||
|
||||
export function cumulativeSum(xs: number[]): number[] {
|
||||
const ys = Array.from(xs); // deep copy
|
||||
for (let i = 1; i < ys.length; i++) ys[i] += ys[i - 1];
|
||||
return ys;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import Instance from '../../../models/instance';
|
||||
import getDriveFileUrl from '../../../misc/get-drive-file-url';
|
||||
import { IEmoji } from '../../../models/emoji';
|
||||
import { ITag } from './tag';
|
||||
import Following from '../../../models/following';
|
||||
|
||||
const log = debug('misskey:activitypub');
|
||||
|
||||
@ -164,7 +165,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
|
||||
publicKeyPem: person.publicKey.publicKeyPem
|
||||
},
|
||||
inbox: person.inbox,
|
||||
sharedInbox: person.sharedInbox,
|
||||
sharedInbox: person.sharedInbox || person.endpoints ? person.endpoints.sharedInbox : undefined,
|
||||
featured: person.featured,
|
||||
endpoints: person.endpoints,
|
||||
uri: person.id,
|
||||
@ -340,7 +341,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
||||
$set: {
|
||||
lastFetchedAt: new Date(),
|
||||
inbox: person.inbox,
|
||||
sharedInbox: person.sharedInbox,
|
||||
sharedInbox: person.sharedInbox || person.endpoints ? person.endpoints.sharedInbox : undefined,
|
||||
featured: person.featured,
|
||||
avatarId: avatar ? avatar._id : null,
|
||||
bannerId: banner ? banner._id : null,
|
||||
@ -368,6 +369,15 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
||||
}
|
||||
});
|
||||
|
||||
// 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする
|
||||
await Following.update({
|
||||
followerId: exist._id
|
||||
}, {
|
||||
$set: {
|
||||
'_follower.sharedInbox': person.sharedInbox || person.endpoints ? person.endpoints.sharedInbox : undefined
|
||||
}
|
||||
});
|
||||
|
||||
await updateFeatured(exist._id).catch(err => console.log(err));
|
||||
}
|
||||
|
||||
|
@ -63,6 +63,7 @@ export default async (user: ILocalUser) => {
|
||||
following: `${id}/following`,
|
||||
featured: `${id}/collections/featured`,
|
||||
sharedInbox: `${config.url}/inbox`,
|
||||
endpoints: { sharedInbox: `${config.url}/inbox` },
|
||||
url: `${config.url}/@${user.username}`,
|
||||
preferredUsername: user.username,
|
||||
name: user.name,
|
||||
|
@ -56,7 +56,7 @@ export interface IPerson extends IObject {
|
||||
following: any;
|
||||
featured?: any;
|
||||
outbox: any;
|
||||
endpoints: string[];
|
||||
endpoints: any;
|
||||
}
|
||||
|
||||
export const isCollection = (object: IObject): object is ICollection =>
|
||||
|
95
test/mfm.ts
95
test/mfm.ts
@ -6,7 +6,7 @@ import * as assert from 'assert';
|
||||
|
||||
import analyze from '../src/mfm/parse';
|
||||
import toHtml from '../src/mfm/html';
|
||||
import { createTree as tree, createLeaf as leaf, MfmTree } from '../src/mfm/parser';
|
||||
import { createTree as tree, createLeaf as leaf, MfmTree, removeOrphanedBrackets } from '../src/mfm/parser';
|
||||
|
||||
function text(text: string): MfmTree {
|
||||
return leaf('text', { text });
|
||||
@ -49,6 +49,99 @@ describe('createTree', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeOrphanedBrackets', () => {
|
||||
it('single (contained)', () => {
|
||||
const input = '(foo)';
|
||||
const expected = '(foo)';
|
||||
const actual = removeOrphanedBrackets(input);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('single (head)', () => {
|
||||
const input = '(foo)bar';
|
||||
const expected = '(foo)bar';
|
||||
const actual = removeOrphanedBrackets(input);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('single (tail)', () => {
|
||||
const input = 'foo(bar)';
|
||||
const expected = 'foo(bar)';
|
||||
const actual = removeOrphanedBrackets(input);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('a', () => {
|
||||
const input = '(foo';
|
||||
const expected = '';
|
||||
const actual = removeOrphanedBrackets(input);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('b', () => {
|
||||
const input = ')foo';
|
||||
const expected = '';
|
||||
const actual = removeOrphanedBrackets(input);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('nested', () => {
|
||||
const input = 'foo(「(bar)」)';
|
||||
const expected = 'foo(「(bar)」)';
|
||||
const actual = removeOrphanedBrackets(input);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('no brackets', () => {
|
||||
const input = 'foo';
|
||||
const expected = 'foo';
|
||||
const actual = removeOrphanedBrackets(input);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('with foreign bracket (single)', () => {
|
||||
const input = 'foo(bar))';
|
||||
const expected = 'foo(bar)';
|
||||
const actual = removeOrphanedBrackets(input);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('with foreign bracket (open)', () => {
|
||||
const input = 'foo(bar';
|
||||
const expected = 'foo';
|
||||
const actual = removeOrphanedBrackets(input);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('with foreign bracket (close)', () => {
|
||||
const input = 'foo)bar';
|
||||
const expected = 'foo';
|
||||
const actual = removeOrphanedBrackets(input);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('with foreign bracket (close and open)', () => {
|
||||
const input = 'foo)(bar';
|
||||
const expected = 'foo';
|
||||
const actual = removeOrphanedBrackets(input);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('various bracket type', () => {
|
||||
const input = 'foo「(bar)」(';
|
||||
const expected = 'foo「(bar)」';
|
||||
const actual = removeOrphanedBrackets(input);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('intersected', () => {
|
||||
const input = 'foo(「)」';
|
||||
const expected = 'foo(「)」';
|
||||
const actual = removeOrphanedBrackets(input);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('MFM', () => {
|
||||
it('can be analyzed', () => {
|
||||
const tokens = analyze('@himawari @hima_sub@namori.net お腹ペコい :cat: #yryr');
|
||||
|
Reference in New Issue
Block a user