diff --git a/src/mfm/toString.ts b/src/mfm/toString.ts new file mode 100644 index 000000000..5b53105b3 --- /dev/null +++ b/src/mfm/toString.ts @@ -0,0 +1,112 @@ +import { MfmForest, MfmTree } from './prelude'; +import { nyaize } from '../misc/nyaize'; + +export type RestoreOptions = { + doNyaize?: boolean; +}; + +export function toString(tokens: MfmForest | null, opts?: RestoreOptions): string { + + if (tokens === null) return ''; + + function appendChildren(children: MfmForest, opts?: RestoreOptions): string { + return children.map(t => handlers[t.node.type](t, opts)).join(''); + } + + const handlers: { [key: string]: (token: MfmTree, opts?: RestoreOptions) => string } = { + bold(token, opts) { + return `**${appendChildren(token.children, opts)}**`; + }, + + big(token, opts) { + return `***${appendChildren(token.children, opts)}***`; + }, + + small(token, opts) { + return `${appendChildren(token.children, opts)}`; + }, + + strike(token, opts) { + return `~~${appendChildren(token.children, opts)}~~`; + }, + + italic(token, opts) { + return `${appendChildren(token.children, opts)}`; + }, + + motion(token, opts) { + return `${appendChildren(token.children, opts)}`; + }, + + spin(token, opts) { + return `${appendChildren(token.children, opts)}`; + }, + + jump(token, opts) { + return `${appendChildren(token.children, opts)}`; + }, + + flip(token, opts) { + return `${appendChildren(token.children, opts)}`; + }, + + blockCode(token) { + return `\`\`\`${token.node.props.lang || ''}\n${token.node.props.code}\n\`\`\`\n`; + }, + + center(token, opts) { + return `
${appendChildren(token.children, opts)}
`; + }, + + emoji(token) { + return (token.node.props.emoji ? token.node.props.emoji : `:${token.node.props.name}:`); + }, + + hashtag(token) { + return `#${token.node.props.hashtag}`; + }, + + inlineCode(token) { + return `\`${token.node.props.code}\``; + }, + + mathInline(token) { + return `\\(${token.node.props.formula}\\)`; + }, + + mathBlock(token) { + return `\\[${token.node.props.formula}\\]`; + }, + + link(token, opts) { + return `[${appendChildren(token.children, opts)}](${token.node.props.url})`; + }, + + mention(token) { + return token.node.props.canonical; + }, + + quote(token) { + return `${appendChildren(token.children, {doNyaize: false}).replace(/^/gm,'>').trim()}\n`; + }, + + title(token, opts) { + return `[${appendChildren(token.children, opts)}]\n`; + }, + + text(token, opts) { + return (opts && opts.doNyaize) ? nyaize(token.node.props.text) : token.node.props.text; + }, + + url(token) { + return `<${token.node.props.url}>`; + }, + + search(token, opts) { + const query = token.node.props.query; + return `${(opts && opts.doNyaize ? nyaize(query) : query)} [search]\n`; + } + }; + + return appendChildren(tokens, { doNyaize: (opts && opts.doNyaize) || false }).trim(); +} diff --git a/src/misc/nyaize.ts b/src/misc/nyaize.ts index 9fbfc8b50..6ee3b6847 100644 --- a/src/misc/nyaize.ts +++ b/src/misc/nyaize.ts @@ -1,8 +1,5 @@ -import rndstr from 'rndstr'; - export function nyaize(text: string): string { - const [toNyaize, exclusionMap] = exclude(text); - const nyaized = toNyaize + return text // ja-JP .replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ') // en-US @@ -13,34 +10,4 @@ export function nyaize(text: string): string { )) .replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, '다냥') .replace(/(야(?=\?))|(야$)|(야(?= ))/gm, '냥'); - return replaceExceptions(nyaized, exclusionMap); -} - -function exclude(text: string): [string, Record] { - const map: Record = {}; - function substitute(match: string): string { - let randomstr: string; - do { - randomstr = rndstr({ length: 16, chars: '🀀-🀫' }); - } while(Object.prototype.hasOwnProperty.call(map, randomstr)); - map[randomstr] = match; - return randomstr; - } - const replaced = text - .replace(/```(.+?)?\n([\s\S]+?)```(\n|$)/gm, match => substitute(match)) // code block - .replace(/`([^`\n]+?)`/g, match => substitute(match)) // inline code - .replace(/(https?:\/\/.*?)(?= |$)/gm, match => substitute(match)) // URL - .replace(/:([a-z0-9_+-]+):/gim, match => substitute(match)) // emoji - .replace(/#([^\s.,!?'"#:\/\[\]【】]+)/gm, match => substitute(match)) // hashtag - .replace(/@\w([\w-]*\w)?(?:@[\w.\-]+\w)?/gm, match => substitute(match)); // mention - return [replaced, map]; -} - -function replaceExceptions(text: string, map: Record): string { - for (const rule in map) { - if (Object.prototype.hasOwnProperty.call(map, rule)) { - text = text.replace(rule, map[rule]); - } - } - return text; } diff --git a/src/models/repositories/note.ts b/src/models/repositories/note.ts index cc856f2ba..73d7ad86e 100644 --- a/src/models/repositories/note.ts +++ b/src/models/repositories/note.ts @@ -1,12 +1,13 @@ import { EntityRepository, Repository, In } from 'typeorm'; import { Note } from '../entities/note'; import { User } from '../entities/user'; -import { nyaize } from '../../misc/nyaize'; import { Emojis, Users, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..'; import { ensure } from '../../prelude/ensure'; import { SchemaType } from '../../misc/schema'; import { awaitAll } from '../../prelude/await-all'; import { convertLegacyReaction, convertLegacyReactions } from '../../misc/reaction-lib'; +import { toString } from '../../mfm/toString'; +import { parse } from '../../mfm/parse'; export type PackedNote = SchemaType; @@ -217,7 +218,8 @@ export class NoteRepository extends Repository { }); if (packed.user.isCat && packed.text) { - packed.text = nyaize(packed.text); + const tokens = packed.text ? parse(packed.text) : []; + packed.text = toString(tokens, { doNyaize: true }); } if (!opts.skipHide) {