リモートユーザーのHTMLで表現されたプロフィールをMFMに変換するように
This commit is contained in:
parent
3633d7ada1
commit
79d1bf30a4
29 changed files with 90 additions and 92 deletions
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import parse from '../../../../../text/parse';
|
import parse from '../../../../../mfm/parse';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import * as emojilib from 'emojilib';
|
import * as emojilib from 'emojilib';
|
||||||
import parse from '../../../../../text/parse';
|
import parse from '../../../../../mfm/parse';
|
||||||
import getAcct from '../../../../../acct/render';
|
import getAcct from '../../../../../acct/render';
|
||||||
import { url } from '../../../config';
|
import { url } from '../../../config';
|
||||||
import MkUrl from './url.vue';
|
import MkUrl from './url.vue';
|
||||||
|
|
|
@ -83,7 +83,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import dateStringify from '../../../common/scripts/date-stringify';
|
import dateStringify from '../../../common/scripts/date-stringify';
|
||||||
import parse from '../../../../../text/parse';
|
import parse from '../../../../../mfm/parse';
|
||||||
|
|
||||||
import MkPostFormWindow from './post-form-window.vue';
|
import MkPostFormWindow from './post-form-window.vue';
|
||||||
import MkRenoteFormWindow from './renote-form-window.vue';
|
import MkRenoteFormWindow from './renote-form-window.vue';
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import dateStringify from '../../../common/scripts/date-stringify';
|
import dateStringify from '../../../common/scripts/date-stringify';
|
||||||
import canHideText from '../../../common/scripts/can-hide-text';
|
import canHideText from '../../../common/scripts/can-hide-text';
|
||||||
import parse from '../../../../../text/parse';
|
import parse from '../../../../../mfm/parse';
|
||||||
|
|
||||||
import MkPostFormWindow from './post-form-window.vue';
|
import MkPostFormWindow from './post-form-window.vue';
|
||||||
import MkRenoteFormWindow from './renote-form-window.vue';
|
import MkRenoteFormWindow from './renote-form-window.vue';
|
||||||
|
|
|
@ -49,7 +49,7 @@ import Vue from 'vue';
|
||||||
import * as XDraggable from 'vuedraggable';
|
import * as XDraggable from 'vuedraggable';
|
||||||
import getKao from '../../../common/scripts/get-kao';
|
import getKao from '../../../common/scripts/get-kao';
|
||||||
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
|
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
|
||||||
import parse from '../../../../../text/parse';
|
import parse from '../../../../../mfm/parse';
|
||||||
import { host } from '../../../config';
|
import { host } from '../../../config';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
|
|
|
@ -67,7 +67,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import parse from '../../../../../../text/parse';
|
import parse from '../../../../../../mfm/parse';
|
||||||
import canHideText from '../../../../common/scripts/can-hide-text';
|
import canHideText from '../../../../common/scripts/can-hide-text';
|
||||||
|
|
||||||
import MkNoteMenu from '../../../../common/views/components/note-menu.vue';
|
import MkNoteMenu from '../../../../common/views/components/note-menu.vue';
|
||||||
|
|
|
@ -83,7 +83,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import parse from '../../../../../text/parse';
|
import parse from '../../../../../mfm/parse';
|
||||||
|
|
||||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
|
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
|
||||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
|
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import parse from '../../../../../text/parse';
|
import parse from '../../../../../mfm/parse';
|
||||||
import canHideText from '../../../common/scripts/can-hide-text';
|
import canHideText from '../../../common/scripts/can-hide-text';
|
||||||
|
|
||||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
|
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
|
||||||
|
|
|
@ -45,7 +45,7 @@ import Vue from 'vue';
|
||||||
import * as XDraggable from 'vuedraggable';
|
import * as XDraggable from 'vuedraggable';
|
||||||
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
|
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
|
||||||
import getKao from '../../../common/scripts/get-kao';
|
import getKao from '../../../common/scripts/get-kao';
|
||||||
import parse from '../../../../../text/parse';
|
import parse from '../../../../../mfm/parse';
|
||||||
import { host } from '../../../config';
|
import { host } from '../../../config';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
|
|
71
src/mfm/html-to-mfm.ts
Normal file
71
src/mfm/html-to-mfm.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
const parse5 = require('parse5');
|
||||||
|
|
||||||
|
export default function(html: string): string {
|
||||||
|
const dom = parse5.parseFragment(html);
|
||||||
|
|
||||||
|
let text = '';
|
||||||
|
|
||||||
|
dom.childNodes.forEach((n: any) => analyze(n));
|
||||||
|
|
||||||
|
return text.trim();
|
||||||
|
|
||||||
|
function getText(node: any) {
|
||||||
|
if (node.nodeName == '#text') return node.value;
|
||||||
|
|
||||||
|
if (node.childNodes) {
|
||||||
|
return node.childNodes.map((n: any) => getText(n)).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function analyze(node: any) {
|
||||||
|
switch (node.nodeName) {
|
||||||
|
case '#text':
|
||||||
|
text += node.value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'br':
|
||||||
|
text += '\n';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'a':
|
||||||
|
const txt = getText(node);
|
||||||
|
|
||||||
|
// メンション
|
||||||
|
if (txt.startsWith('@')) {
|
||||||
|
const part = txt.split('@');
|
||||||
|
|
||||||
|
if (part.length == 2) {
|
||||||
|
//#region ホスト名部分が省略されているので復元する
|
||||||
|
const href = new URL(node.attrs.find((x: any) => x.name == 'href').value);
|
||||||
|
const acct = txt + '@' + href.hostname;
|
||||||
|
text += acct;
|
||||||
|
break;
|
||||||
|
//#endregion
|
||||||
|
} else if (part.length == 3) {
|
||||||
|
text += txt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.childNodes) {
|
||||||
|
node.childNodes.forEach((n: any) => analyze(n));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
text += '\n\n';
|
||||||
|
if (node.childNodes) {
|
||||||
|
node.childNodes.forEach((n: any) => analyze(n));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (node.childNodes) {
|
||||||
|
node.childNodes.forEach((n: any) => analyze(n));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { INote } from '../../../models/note';
|
import { INote } from '../../../models/note';
|
||||||
import toHtml from '../../../text/html';
|
import toHtml from '../../../mfm/html';
|
||||||
import parse from '../../../text/parse';
|
import parse from '../../../mfm/parse';
|
||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
|
|
||||||
export default function(note: INote) {
|
export default function(note: INote) {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import * as mongo from 'mongodb';
|
import * as mongo from 'mongodb';
|
||||||
const parse5 = require('parse5');
|
|
||||||
import * as debug from 'debug';
|
import * as debug from 'debug';
|
||||||
|
|
||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
|
@ -10,79 +9,10 @@ import { INote as INoteActivityStreamsObject, IObject } from '../type';
|
||||||
import { resolvePerson, updatePerson } from './person';
|
import { resolvePerson, updatePerson } from './person';
|
||||||
import { resolveImage } from './image';
|
import { resolveImage } from './image';
|
||||||
import { IRemoteUser, IUser } from '../../../models/user';
|
import { IRemoteUser, IUser } from '../../../models/user';
|
||||||
|
import htmlToMFM from '../../../mfm/html-to-mfm';
|
||||||
|
|
||||||
const log = debug('misskey:activitypub');
|
const log = debug('misskey:activitypub');
|
||||||
|
|
||||||
function parse(html: string): string {
|
|
||||||
const dom = parse5.parseFragment(html);
|
|
||||||
|
|
||||||
let text = '';
|
|
||||||
|
|
||||||
dom.childNodes.forEach((n: any) => analyze(n));
|
|
||||||
|
|
||||||
return text.trim();
|
|
||||||
|
|
||||||
function getText(node: any) {
|
|
||||||
if (node.nodeName == '#text') return node.value;
|
|
||||||
|
|
||||||
if (node.childNodes) {
|
|
||||||
return node.childNodes.map((n: any) => getText(n)).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function analyze(node: any) {
|
|
||||||
switch (node.nodeName) {
|
|
||||||
case '#text':
|
|
||||||
text += node.value;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'br':
|
|
||||||
text += '\n';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'a':
|
|
||||||
const txt = getText(node);
|
|
||||||
|
|
||||||
// メンション
|
|
||||||
if (txt.startsWith('@')) {
|
|
||||||
const part = txt.split('@');
|
|
||||||
|
|
||||||
if (part.length == 2) {
|
|
||||||
//#region ホスト名部分が省略されているので復元する
|
|
||||||
const href = new URL(node.attrs.find((x: any) => x.name == 'href').value);
|
|
||||||
const acct = txt + '@' + href.hostname;
|
|
||||||
text += acct;
|
|
||||||
break;
|
|
||||||
//#endregion
|
|
||||||
} else if (part.length == 3) {
|
|
||||||
text += txt;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.childNodes) {
|
|
||||||
node.childNodes.forEach((n: any) => analyze(n));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'p':
|
|
||||||
text += '\n\n';
|
|
||||||
if (node.childNodes) {
|
|
||||||
node.childNodes.forEach((n: any) => analyze(n));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
if (node.childNodes) {
|
|
||||||
node.childNodes.forEach((n: any) => analyze(n));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Noteをフェッチします。
|
* Noteをフェッチします。
|
||||||
*
|
*
|
||||||
|
@ -158,7 +88,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
||||||
const reply = note.inReplyTo ? await resolveNote(note.inReplyTo, resolver) : null;
|
const reply = note.inReplyTo ? await resolveNote(note.inReplyTo, resolver) : null;
|
||||||
|
|
||||||
// テキストのパース
|
// テキストのパース
|
||||||
const text = parse(note.content);
|
const text = htmlToMFM(note.content);
|
||||||
|
|
||||||
// ユーザーの情報が古かったらついでに更新しておく
|
// ユーザーの情報が古かったらついでに更新しておく
|
||||||
if (actor.updatedAt == null || Date.now() - actor.updatedAt.getTime() > 1000 * 60 * 60 * 24) {
|
if (actor.updatedAt == null || Date.now() - actor.updatedAt.getTime() > 1000 * 60 * 60 * 24) {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import * as mongo from 'mongodb';
|
import * as mongo from 'mongodb';
|
||||||
import { JSDOM } from 'jsdom';
|
|
||||||
import { toUnicode } from 'punycode';
|
import { toUnicode } from 'punycode';
|
||||||
import * as debug from 'debug';
|
import * as debug from 'debug';
|
||||||
|
|
||||||
|
@ -11,6 +10,7 @@ import { resolveImage } from './image';
|
||||||
import { isCollectionOrOrderedCollection, IObject, IPerson } from '../type';
|
import { isCollectionOrOrderedCollection, IObject, IPerson } from '../type';
|
||||||
import { IDriveFile } from '../../../models/drive-file';
|
import { IDriveFile } from '../../../models/drive-file';
|
||||||
import Meta from '../../../models/meta';
|
import Meta from '../../../models/meta';
|
||||||
|
import htmlToMFM from '../../../mfm/html-to-mfm';
|
||||||
|
|
||||||
const log = debug('misskey:activitypub');
|
const log = debug('misskey:activitypub');
|
||||||
|
|
||||||
|
@ -80,7 +80,6 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const host = toUnicode(finger.subject.replace(/^.*?@/, '')).toLowerCase();
|
const host = toUnicode(finger.subject.replace(/^.*?@/, '')).toLowerCase();
|
||||||
const summaryDOM = JSDOM.fragment(person.summary);
|
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
let user: IRemoteUser;
|
let user: IRemoteUser;
|
||||||
|
@ -89,7 +88,7 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs
|
||||||
avatarId: null,
|
avatarId: null,
|
||||||
bannerId: null,
|
bannerId: null,
|
||||||
createdAt: Date.parse(person.published) || null,
|
createdAt: Date.parse(person.published) || null,
|
||||||
description: summaryDOM.textContent,
|
description: htmlToMFM(person.summary),
|
||||||
followersCount,
|
followersCount,
|
||||||
followingCount,
|
followingCount,
|
||||||
notesCount,
|
notesCount,
|
||||||
|
@ -211,8 +210,6 @@ export async function updatePerson(value: string | IObject, resolver?: Resolver)
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const summaryDOM = JSDOM.fragment(person.summary);
|
|
||||||
|
|
||||||
// アイコンとヘッダー画像をフェッチ
|
// アイコンとヘッダー画像をフェッチ
|
||||||
const [avatar, banner] = (await Promise.all<IDriveFile>([
|
const [avatar, banner] = (await Promise.all<IDriveFile>([
|
||||||
person.icon,
|
person.icon,
|
||||||
|
@ -231,7 +228,7 @@ export async function updatePerson(value: string | IObject, resolver?: Resolver)
|
||||||
bannerId: banner ? banner._id : null,
|
bannerId: banner ? banner._id : null,
|
||||||
avatarUrl: avatar && avatar.metadata.isMetaOnly ? avatar.metadata.url : null,
|
avatarUrl: avatar && avatar.metadata.isMetaOnly ? avatar.metadata.url : null,
|
||||||
bannerUrl: banner && banner.metadata.isMetaOnly ? banner.metadata.url : null,
|
bannerUrl: banner && banner.metadata.isMetaOnly ? banner.metadata.url : null,
|
||||||
description: summaryDOM.textContent,
|
description: htmlToMFM(person.summary),
|
||||||
followersCount,
|
followersCount,
|
||||||
followingCount,
|
followingCount,
|
||||||
notesCount,
|
notesCount,
|
||||||
|
|
|
@ -14,7 +14,7 @@ import watch from './watch';
|
||||||
import Mute from '../../models/mute';
|
import Mute from '../../models/mute';
|
||||||
import pushSw from '../../publishers/push-sw';
|
import pushSw from '../../publishers/push-sw';
|
||||||
import event from '../../publishers/stream';
|
import event from '../../publishers/stream';
|
||||||
import parse from '../../text/parse';
|
import parse from '../../mfm/parse';
|
||||||
import { IApp } from '../../models/app';
|
import { IApp } from '../../models/app';
|
||||||
import UserList from '../../models/user-list';
|
import UserList from '../../models/user-list';
|
||||||
import resolveUser from '../../remote/resolve-user';
|
import resolveUser from '../../remote/resolve-user';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
|
|
||||||
import analyze from '../src/text/parse';
|
import analyze from '../src/mfm/parse';
|
||||||
import syntaxhighlighter from '../src/text/parse/core/syntax-highlighter';
|
import syntaxhighlighter from '../src/mfm/parse/core/syntax-highlighter';
|
||||||
|
|
||||||
describe('Text', () => {
|
describe('Text', () => {
|
||||||
it('can be analyzed', () => {
|
it('can be analyzed', () => {
|
Loading…
Reference in a new issue