diff --git a/package.json b/package.json index bf49a6599f..2b81b03693 100644 --- a/package.json +++ b/package.json @@ -216,5 +216,8 @@ "websocket": "1.0.26", "ws": "5.2.0", "xev": "2.0.1" + }, + "devDependencies": { + "@types/jsdom": "11.0.5" } } diff --git a/src/text/html.ts b/src/text/html.ts index b55d9b80a7..41adb2e7f7 100644 --- a/src/text/html.ts +++ b/src/text/html.ts @@ -1,7 +1,8 @@ -import { lib as emojilib } from 'emojilib'; +const { lib: emojilib } = require('emojilib'); import { JSDOM } from 'jsdom'; import config from '../config'; import { INote } from '../models/note'; +import { TextElement } from './parse'; const handlers: {[key: string]: (window: any, token: any, mentionedRemoteUsers: INote["mentionedRemoteUsers"]) => void} = { bold({ document }, { bold }) { @@ -90,7 +91,7 @@ const handlers: {[key: string]: (window: any, token: any, mentionedRemoteUsers: } }; -export default (tokens, mentionedRemoteUsers: INote['mentionedRemoteUsers'] = []) => { +export default (tokens: TextElement[], mentionedRemoteUsers: INote['mentionedRemoteUsers'] = []) => { const { window } = new JSDOM(''); for (const token of tokens) { diff --git a/src/text/parse/core/syntax-highlighter.ts b/src/text/parse/core/syntax-highlighter.ts index c0396b1fc6..3fb7a3b73d 100644 --- a/src/text/parse/core/syntax-highlighter.ts +++ b/src/text/parse/core/syntax-highlighter.ts @@ -1,4 +1,4 @@ -function escape(text) { +function escape(text: string) { return text .replace(/>/g, '>') .replace(/ (Token | null); + +const elements: Element[] = [ // comment code => { if (code.substr(0, 2) != '//') return null; @@ -305,7 +312,7 @@ export default (source: string, lang?: string) => { let i = 0; - function push(token) { + function push(token: Token) { html += token.html; code = code.substr(token.next); i += token.next; diff --git a/src/text/parse/elements/bold.ts b/src/text/parse/elements/bold.ts index ce25764457..0566ace8b7 100644 --- a/src/text/parse/elements/bold.ts +++ b/src/text/parse/elements/bold.ts @@ -2,7 +2,13 @@ * Bold */ -module.exports = text => { +export type TextElementBold = { + type: "bold" + content: string + bold: string +}; + +export default function(text: string) { const match = text.match(/^\*\*(.+?)\*\*/); if (!match) return null; const bold = match[0]; @@ -10,5 +16,5 @@ module.exports = text => { type: 'bold', content: bold, bold: bold.substr(2, bold.length - 4) - }; -}; + } as TextElementBold; +} diff --git a/src/text/parse/elements/code.ts b/src/text/parse/elements/code.ts index 4821e95fe2..de87aa410b 100644 --- a/src/text/parse/elements/code.ts +++ b/src/text/parse/elements/code.ts @@ -4,7 +4,14 @@ import genHtml from '../core/syntax-highlighter'; -module.exports = text => { +export type TextElementCode = { + type: "code" + content: string + code: string + html: string +}; + +export default function(text: string) { const match = text.match(/^```([\s\S]+?)```/); if (!match) return null; const code = match[0]; @@ -13,5 +20,5 @@ module.exports = text => { content: code, code: code.substr(3, code.length - 6).trim(), html: genHtml(code.substr(3, code.length - 6).trim()) - }; -}; + } as TextElementCode; +} diff --git a/src/text/parse/elements/emoji.ts b/src/text/parse/elements/emoji.ts index e24231a223..d0eed88965 100644 --- a/src/text/parse/elements/emoji.ts +++ b/src/text/parse/elements/emoji.ts @@ -2,7 +2,13 @@ * Emoji */ -module.exports = text => { +export type TextElementEmoji = { + type: "emoji" + content: string + emoji: string +}; + +export default function(text: string) { const match = text.match(/^:[a-zA-Z0-9+-_]+:/); if (!match) return null; const emoji = match[0]; @@ -10,5 +16,5 @@ module.exports = text => { type: 'emoji', content: emoji, emoji: emoji.substr(1, emoji.length - 2) - }; -}; + } as TextElementEmoji; +} diff --git a/src/text/parse/elements/hashtag.ts b/src/text/parse/elements/hashtag.ts index ee57b140b8..cde0c2b224 100644 --- a/src/text/parse/elements/hashtag.ts +++ b/src/text/parse/elements/hashtag.ts @@ -2,7 +2,13 @@ * Hashtag */ -module.exports = (text, i) => { +export type TextElementHashtag = { + type: "hashtag" + content: string + hashtag: string +}; + +export default function(text: string, i: number) { if (!(/^\s#[^\s]+/.test(text) || (i == 0 && /^#[^\s]+/.test(text)))) return null; const isHead = text[0] == '#'; const hashtag = text.match(/^\s?#[^\s]+/)[0]; @@ -15,5 +21,5 @@ module.exports = (text, i) => { content: isHead ? hashtag : hashtag.substr(1), hashtag: isHead ? hashtag.substr(1) : hashtag.substr(2) }); - return res; -}; + return res as TextElementHashtag[]; +} diff --git a/src/text/parse/elements/inline-code.ts b/src/text/parse/elements/inline-code.ts index 9f9ef51a2b..bcb0bca0ad 100644 --- a/src/text/parse/elements/inline-code.ts +++ b/src/text/parse/elements/inline-code.ts @@ -4,7 +4,14 @@ import genHtml from '../core/syntax-highlighter'; -module.exports = text => { +export type TextElementInlineCode = { + type: "inline-code" + content: string + code: string + html: string +}; + +export default function(text: string) { const match = text.match(/^`(.+?)`/); if (!match) return null; const code = match[0]; @@ -13,5 +20,5 @@ module.exports = text => { content: code, code: code.substr(1, code.length - 2).trim(), html: genHtml(code.substr(1, code.length - 2).trim()) - }; -}; + } as TextElementInlineCode; +} diff --git a/src/text/parse/elements/link.ts b/src/text/parse/elements/link.ts index 35563ddc3d..7e0d6f5cf8 100644 --- a/src/text/parse/elements/link.ts +++ b/src/text/parse/elements/link.ts @@ -2,7 +2,15 @@ * Link */ -module.exports = text => { +export type TextElementLink = { + type: "link" + content: string + title: string + url: string + silent: boolean +}; + +export default function(text: string) { const match = text.match(/^\??\[([^\[\]]+?)\]\((https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.=\+\-]+?)\)/); if (!match) return null; const silent = text[0] == '?'; @@ -15,5 +23,5 @@ module.exports = text => { title: title, url: url, silent: silent - }; -}; + } as TextElementLink; +} diff --git a/src/text/parse/elements/mention.ts b/src/text/parse/elements/mention.ts index 2ad2788300..a4140458d4 100644 --- a/src/text/parse/elements/mention.ts +++ b/src/text/parse/elements/mention.ts @@ -3,7 +3,14 @@ */ import parseAcct from '../../../acct/parse'; -module.exports = text => { +export type TextElementMention = { + type: "mention" + content: string + username: string + host: string +}; + +export default function(text: string) { const match = text.match(/^@[a-z0-9_]+(?:@[a-z0-9\.\-]+[a-z0-9])?/i); if (!match) return null; const mention = match[0]; @@ -13,5 +20,5 @@ module.exports = text => { content: mention, username, host - }; -}; + } as TextElementMention; +} diff --git a/src/text/parse/elements/quote.ts b/src/text/parse/elements/quote.ts index cc8cfffdc4..56de561f3f 100644 --- a/src/text/parse/elements/quote.ts +++ b/src/text/parse/elements/quote.ts @@ -2,7 +2,13 @@ * Quoted text */ -module.exports = text => { +export type TextElementQuote = { + type: "quote" + content: string + quote: string +}; + +export default function(text: string) { const match = text.match(/^"([\s\S]+?)\n"/); if (!match) return null; const quote = match[0]; @@ -10,5 +16,5 @@ module.exports = text => { type: 'quote', content: quote, quote: quote.substr(1, quote.length - 2).trim(), - }; -}; + } as TextElementQuote; +} diff --git a/src/text/parse/elements/search.ts b/src/text/parse/elements/search.ts index 12ee8ecbb8..4bd19ee3fa 100644 --- a/src/text/parse/elements/search.ts +++ b/src/text/parse/elements/search.ts @@ -2,7 +2,13 @@ * Search */ -module.exports = text => { +export type TextElementSearch = { + type: "search" + content: string + query: string +}; + +export default function(text: string) { const match = text.match(/^(.+?) 検索(\n|$)/); if (!match) return null; return { @@ -10,4 +16,4 @@ module.exports = text => { content: match[0], query: match[1] }; -}; +} diff --git a/src/text/parse/elements/title.ts b/src/text/parse/elements/title.ts index 9f4708f5d6..11b3abc61b 100644 --- a/src/text/parse/elements/title.ts +++ b/src/text/parse/elements/title.ts @@ -2,7 +2,13 @@ * Title */ -module.exports = text => { +export type TextElementTitle = { + type: "title" + content: string + title: string +}; + +export default function(text: string) { const match = text.match(/^【(.+?)】\n/); if (!match) return null; const title = match[0]; @@ -10,5 +16,5 @@ module.exports = text => { type: 'title', content: title, title: title.substr(1, title.length - 3) - }; -}; + } as TextElementTitle; +} diff --git a/src/text/parse/elements/url.ts b/src/text/parse/elements/url.ts index 1003aff9c3..bbc27b4fd7 100644 --- a/src/text/parse/elements/url.ts +++ b/src/text/parse/elements/url.ts @@ -2,7 +2,13 @@ * URL */ -module.exports = text => { +export type TextElementUrl = { + type: "url" + content: string + url: string +}; + +export default function(text: string) { const match = text.match(/^https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.=\+\-]+/); if (!match) return null; const url = match[0]; @@ -10,5 +16,5 @@ module.exports = text => { type: 'url', content: url, url: url - }; -}; + } as TextElementUrl; +} diff --git a/src/text/parse/index.ts b/src/text/parse/index.ts index cfddd9f615..ccfef44591 100644 --- a/src/text/parse/index.ts +++ b/src/text/parse/index.ts @@ -2,6 +2,18 @@ * Misskey Text Analyzer */ +import { TextElementBold } from "./elements/bold"; +import { TextElementCode } from "./elements/code"; +import { TextElementEmoji } from "./elements/emoji"; +import { TextElementHashtag } from "./elements/hashtag"; +import { TextElementInlineCode } from "./elements/inline-code"; +import { TextElementLink } from "./elements/link"; +import { TextElementMention } from "./elements/mention"; +import { TextElementQuote } from "./elements/quote"; +import { TextElementSearch } from "./elements/search"; +import { TextElementTitle } from "./elements/title"; +import { TextElementUrl } from "./elements/url"; + const elements = [ require('./elements/bold'), require('./elements/title'), @@ -14,17 +26,31 @@ const elements = [ require('./elements/quote'), require('./elements/emoji'), require('./elements/search') -]; +].map(element => element.default as TextElementProcessor); -export default (source: string): any[] => { +export type TextElement = {type: "text", content: string} + | TextElementBold + | TextElementCode + | TextElementEmoji + | TextElementHashtag + | TextElementInlineCode + | TextElementLink + | TextElementMention + | TextElementQuote + | TextElementSearch + | TextElementTitle + | TextElementUrl; +export type TextElementProcessor = (text: string, i: number) => TextElement | TextElement[]; + +export default (source: string): TextElement[] => { if (source == '') { return null; } - const tokens = []; + const tokens: TextElement[] = []; - function push(token) { + function push(token: TextElement) { if (token != null) { tokens.push(token); source = source.substr(token.content.length); @@ -59,9 +85,8 @@ export default (source: string): any[] => { } // テキストを纏める - tokens[0] = [tokens[0]]; return tokens.reduce((a, b) => { - if (a[a.length - 1].type == 'text' && b.type == 'text') { + if (a.length && a[a.length - 1].type == 'text' && b.type == 'text') { const tail = a.pop(); return a.concat({ type: 'text', @@ -70,5 +95,5 @@ export default (source: string): any[] => { } else { return a.concat(b); } - }); + }, [] as TextElement[]); }; diff --git a/yarn.lock b/yarn.lock index 8b12a99c95..e4a53091e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -277,6 +277,15 @@ version "3.11.1" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.11.1.tgz#ac5bab26be5f9c6f74b6b23420f2cfa5a7a6ba40" +"@types/jsdom@11.0.5": + version "11.0.5" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-11.0.5.tgz#b12fffc73eb3731b218e9665a50f023b6b84b5cb" + dependencies: + "@types/events" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + parse5 "^3.0.2" + "@types/keygrip@*": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.1.tgz#ff540462d2fb4d0a88441ceaf27d287b01c3d878" @@ -8221,6 +8230,12 @@ parse5@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" +parse5@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" + dependencies: + "@types/node" "*" + parseurl@^1.3.0, parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"