@traptitech/traq-markdown-it
Advanced tools
Comparing version 5.3.3 to 5.3.4
@@ -1,12 +0,143 @@ | ||
export type { Store } from './Store'; | ||
export type { Embedding, EmbeddingOrUrl, ExternalUrl, EmbeddingFile, EmbeddingMessage } from './embeddingExtractor'; | ||
export { MarkdownRenderResult, traQMarkdownIt } from './traQMarkdownIt'; | ||
export { AnimeEffect, SizeEffect } from './stamp'; | ||
export { createHighlightFunc } from './highlight'; | ||
import { User, Channel, UserGroup, Stamp } from '@traptitech/traq'; | ||
import Token from 'markdown-it/lib/token'; | ||
import MarkdownIt, { Options } from 'markdown-it'; | ||
import katexE from 'katex'; | ||
import Renderer, { RenderRuleRecord } from 'markdown-it/lib/renderer'; | ||
export { default as markPlugin } from 'markdown-it-mark'; | ||
export { default as spoilerPlugin } from '@traptitech/markdown-it-spoiler'; | ||
export { default as stampPlugin, animeEffectSet, sizeEffectSet } from './stamp'; | ||
export { default as jsonPlugin } from './json'; | ||
export { default as katexPlugin } from '@traptitech/markdown-it-katex'; | ||
export { useContainer } from './container'; | ||
export { stampCssPlugin } from './stampCss'; | ||
interface Store { | ||
getUser(id: string): Readonly<Pick<User, 'id'>> | undefined; | ||
getChannel(id: string): Readonly<Pick<Channel, 'id'>> | undefined; | ||
getChannelPath(id: string): string; | ||
getUserGroup(id: string): Readonly<Pick<UserGroup, 'members'>> | undefined; | ||
getMe(): Readonly<Pick<User, 'id'>> | undefined; | ||
getStampByName(name: string): Readonly<Pick<Stamp, 'name' | 'fileId'>> | undefined; | ||
getUserByName(name: string): Readonly<Pick<User, 'iconFileId'>> | undefined; | ||
} | ||
declare type Embedding = EmbeddingFile | EmbeddingMessage; | ||
declare type EmbeddingOrUrl = InternalUrl | ExternalUrl | Embedding; | ||
declare type EmbeddingBase = { | ||
type: EmbeddingType; | ||
id: string; | ||
}; | ||
declare type InternalUrl = { | ||
type: 'internal'; | ||
}; | ||
declare type ExternalUrl = { | ||
type: 'url'; | ||
url: string; | ||
}; | ||
declare type EmbeddingType = 'file' | 'message'; | ||
declare type EmbeddingFile = EmbeddingBase & { | ||
type: 'file'; | ||
}; | ||
declare type EmbeddingMessage = EmbeddingBase & { | ||
type: 'message'; | ||
}; | ||
interface TokenWithEmbeddingData extends Token { | ||
children: TokenWithEmbeddingData[] | null; | ||
embedding?: EmbeddingOrUrl; | ||
} | ||
declare class EmbeddingExtractor { | ||
readonly pathNameEmbeddingTypeMap: ReadonlyMap<string, Embedding['type']>; | ||
readonly embeddingOrigin: string; | ||
constructor(embeddingOrigin: string); | ||
extractType(url: Readonly<URL>): EmbeddingOrUrl['type']; | ||
extractId(url: Readonly<URL>, type: EmbeddingOrUrl['type']): Embedding['id'] | undefined; | ||
urlToEmbeddingData(url: string): EmbeddingOrUrl | undefined; | ||
/** | ||
* パースされたトークンの木構造から再帰的にURLを抽出する | ||
* 同時にトークンにURLの情報を付与する | ||
*/ | ||
extractUrlsFromTokens(tokens: readonly TokenWithEmbeddingData[], embeddings?: EmbeddingOrUrl[]): EmbeddingOrUrl[]; | ||
/** | ||
* markdownからURL・埋め込みURLを抽出する | ||
* | ||
* @param typeIdExtractor マッチ結果から埋め込み/通常URLの種別を返す関数 | ||
* @param idExtractor マッチ結果から埋め込みidを返す関数(通常URL時は`undefined`) | ||
*/ | ||
extract(tokens: readonly Token[]): EmbeddingOrUrl[]; | ||
/** | ||
* paragraphのchildのtokenの配列から末尾の埋め込みを取り除く | ||
* | ||
* @param tokens ネストされていないtokenの配列 | ||
* @returns 取り除かれたらtrue | ||
*/ | ||
removeTailEmbeddingsFromTailParagraph(tokens: Array<Readonly<TokenWithEmbeddingData>>): boolean; | ||
/** | ||
* 末尾の埋め込みURLを除去する | ||
* | ||
* 末尾から「改行」の連続(あってもなくてもよい)、「「埋め込み」を含むparagraph」の場合だけを確認する | ||
*/ | ||
removeTailEmbeddings(tokens: Array<Readonly<TokenWithEmbeddingData>>): void; | ||
/** | ||
* markdownから埋め込みURLを抽出してすべて置換する | ||
*/ | ||
replace(tokens: readonly TokenWithEmbeddingData[]): void; | ||
} | ||
declare class InlineRenderer extends Renderer { | ||
/** | ||
* Rulesのblockルールは使われない | ||
* blockRulesに入れたもののみ利用される | ||
*/ | ||
private blockRules; | ||
setRules(defaultRules?: RenderRuleRecord | null, overrideRules?: RenderRuleRecord): void; | ||
renderContent(token: Token): string; | ||
render(tokens: Token[], options: Options, env: any): string; | ||
} | ||
declare type MarkdownRenderResult = { | ||
embeddings: EmbeddingOrUrl[]; | ||
rawText: string; | ||
renderedText: string; | ||
}; | ||
declare class traQMarkdownIt { | ||
readonly mdOptions: { | ||
readonly breaks: true; | ||
readonly linkify: true; | ||
readonly highlight: (code: string, lang: string) => string; | ||
}; | ||
readonly katexOptions: { | ||
readonly katex: typeof katexE; | ||
readonly output: "html"; | ||
readonly strict: (errCode: string) => string; | ||
readonly maxSize: 100; | ||
readonly blockClass: "is-scroll"; | ||
}; | ||
readonly md: MarkdownIt; | ||
readonly embeddingExtractor: EmbeddingExtractor; | ||
readonly inlineRenderer: InlineRenderer; | ||
constructor(store: Readonly<Store>, whitelist: readonly string[] | undefined, embeddingOrigin: string); | ||
setPlugin(store: Readonly<Store>, whitelist: readonly string[]): void; | ||
setRendererRule(): void; | ||
_render(tokens: Token[]): string; | ||
render(text: string): MarkdownRenderResult; | ||
_renderInline(tokens: Token[]): string; | ||
renderInline(text: string): MarkdownRenderResult; | ||
} | ||
declare const animeEffects: readonly ["rotate", "rotate-inv", "wiggle", "parrot", "zoom", "inversion", "turn", "turn-v", "happa", "pyon", "flashy", "pull", "atsumori", "stretch", "stretch-v", "conga", "conga-inv", "marquee", "marquee-inv", "rainbow", "ascension", "shake", "party", "attract"]; | ||
declare const sizeEffects: readonly ["ex-large", "large", "small"]; | ||
declare const animeEffectSet: ReadonlySet<string>; | ||
declare const sizeEffectSet: ReadonlySet<string>; | ||
declare type AnimeEffect = typeof animeEffects[number]; | ||
declare type SizeEffect = typeof sizeEffects[number]; | ||
/** | ||
* @param _baseUrl スタンプの画像の`/api/v3`の前につくURLの部分 (tailing slashなし) | ||
*/ | ||
declare function stampPlugin(md: MarkdownIt, _store: Readonly<Pick<Store, 'getUserByName' | 'getStampByName'>>, _baseUrl?: string): void; | ||
declare const createHighlightFunc: (preClass: string, withCaption?: boolean, useSubsetForAuto?: boolean) => (code: string, lang: string) => string; | ||
declare function messageExtendsPlugin(md: MarkdownIt, _store: Readonly<Store>): void; | ||
declare const useContainer: (md: MarkdownIt, labels?: readonly string[]) => void; | ||
declare const stampCssPlugin: (md: MarkdownIt, stamps: readonly string[], enableSize?: boolean) => void; | ||
export { AnimeEffect, Embedding, EmbeddingFile, EmbeddingMessage, EmbeddingOrUrl, ExternalUrl, MarkdownRenderResult, SizeEffect, Store, animeEffectSet, createHighlightFunc, messageExtendsPlugin as jsonPlugin, sizeEffectSet, stampCssPlugin, stampPlugin, traQMarkdownIt, useContainer }; |
@@ -0,8 +1,760 @@ | ||
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var __defProp = Object.defineProperty; | ||
var __defProps = Object.defineProperties; | ||
var __getOwnPropDescs = Object.getOwnPropertyDescriptors; | ||
var __getOwnPropSymbols = Object.getOwnPropertySymbols; | ||
var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
var __propIsEnum = Object.prototype.propertyIsEnumerable; | ||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; | ||
var __spreadValues = (a, b) => { | ||
for (var prop in b || (b = {})) | ||
if (__hasOwnProp.call(b, prop)) | ||
__defNormalProp(a, prop, b[prop]); | ||
if (__getOwnPropSymbols) | ||
for (var prop of __getOwnPropSymbols(b)) { | ||
if (__propIsEnum.call(b, prop)) | ||
__defNormalProp(a, prop, b[prop]); | ||
} | ||
return a; | ||
}; | ||
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); | ||
'use strict' | ||
// src/traQMarkdownIt.ts | ||
var _markdownit = require('markdown-it'); var _markdownit2 = _interopRequireDefault(_markdownit); | ||
var _markdownitmark = require('markdown-it-mark'); var _markdownitmark2 = _interopRequireDefault(_markdownitmark); | ||
var _markdownitspoiler = require('@traptitech/markdown-it-spoiler'); var _markdownitspoiler2 = _interopRequireDefault(_markdownitspoiler); | ||
if (process.env.NODE_ENV === 'production') { | ||
module.exports = require('./traq-markdown-it.cjs.production.min.js') | ||
} else { | ||
module.exports = require('./traq-markdown-it.cjs.development.js') | ||
// src/stamp.ts | ||
var _markdownitregexp = require('@traptitech/markdown-it-regexp'); var _markdownitregexp2 = _interopRequireDefault(_markdownitregexp); | ||
// src/util.ts | ||
var htmlReplaceRe = /[&<>"]/g; | ||
var htmlReplaceMap = new Map([ | ||
["&", "&"], | ||
["<", "<"], | ||
[">", ">"], | ||
['"', """] | ||
]); | ||
var replaceHtmlChar = (ch) => htmlReplaceMap.get(ch); | ||
var escapeHtml = (html) => { | ||
if (htmlReplaceRe.test(html)) { | ||
return html.replace(htmlReplaceRe, replaceHtmlChar); | ||
} | ||
return html; | ||
}; | ||
// src/data/stampEffects.ts | ||
var animeEffects = [ | ||
"rotate", | ||
"rotate-inv", | ||
"wiggle", | ||
"parrot", | ||
"zoom", | ||
"inversion", | ||
"turn", | ||
"turn-v", | ||
"happa", | ||
"pyon", | ||
"flashy", | ||
"pull", | ||
"atsumori", | ||
"stretch", | ||
"stretch-v", | ||
"conga", | ||
"conga-inv", | ||
"marquee", | ||
"marquee-inv", | ||
"rainbow", | ||
"ascension", | ||
"shake", | ||
"party", | ||
"attract" | ||
]; | ||
var sizeEffects = ["ex-large", "large", "small"]; | ||
// src/stamp.ts | ||
var store; | ||
var baseUrl = ""; | ||
var animeEffectSet = new Set(animeEffects); | ||
var sizeEffectSet = new Set(sizeEffects); | ||
var animeEffectAliasMap = new Map([ | ||
["marquee", "conga"], | ||
["marquee-inv", "conga-inv"] | ||
]); | ||
var maxEffectCount = 5; | ||
var wrapWithEffect = (stampHtml, animeEffects2, sizeEffect) => { | ||
const filterOpenTag = animeEffects2.map((e, i) => `<span class="emoji-effect ${e}${i === 0 && sizeEffect ? ` ${sizeEffect}` : ""}">`).join(""); | ||
const filterCloseTag = "</span>".repeat(animeEffects2.length); | ||
return filterOpenTag + stampHtml + filterCloseTag; | ||
}; | ||
var isSizeEffect = (e) => sizeEffectSet.has(e); | ||
var isAnimeEffect = (e) => animeEffectSet.has(e); | ||
var renderStampDomWithStyle = (rawMatch, stampName, imgTitle, style, effects) => { | ||
const escapedTitle = escapeHtml(imgTitle); | ||
const escapedStyle = escapeHtml(style); | ||
const escapedName = escapeHtml(stampName); | ||
const sizeEffects2 = effects.filter(isSizeEffect); | ||
const animeEffects2 = effects.filter(isAnimeEffect); | ||
if (sizeEffects2.length + animeEffects2.length < effects.length) { | ||
return rawMatch; | ||
} | ||
if (animeEffects2.length > maxEffectCount) { | ||
return rawMatch; | ||
} | ||
const replacedAnimeEffects = animeEffects2.map((e) => { | ||
var _a; | ||
return (_a = animeEffectAliasMap.get(e)) != null ? _a : e; | ||
}); | ||
const sizeEffectClass = sizeEffects2[sizeEffects2.length - 1] || ""; | ||
const stampHtml = `<i class="emoji message-emoji ${sizeEffectClass}" title=":${escapedTitle}:" style="${escapedStyle};">:${escapedName}:</i>`; | ||
return wrapWithEffect(stampHtml, replacedAnimeEffects, sizeEffectClass); | ||
}; | ||
var renderStampDom = (rawMatch, stampName, imgTitle, imgUrl, effects) => renderStampDomWithStyle(rawMatch, stampName, imgTitle, `background-image: url(${imgUrl})`, effects); | ||
var stampReg = /^[a-zA-Z0-9+_-]{1,32}$/; | ||
var hslReg = /(?<color>hsl\(\d+,\s*[\d]+(?:\.[\d]+)?%,\s*[\d]+(?:\.[\d]+)?%\))(?<effects>.*)/; | ||
var hexReg = /0x(?<color>[0-9a-fA-F]{6})(?<effects>.*)/; | ||
var renderHslStamp = (match) => { | ||
const { color, effects } = match.groups; | ||
return renderStampDomWithStyle(`:${match[0]}:`, color, color, `background-color: ${color}`, effects === "" ? [] : effects.split(".").slice(1)); | ||
}; | ||
var renderHexStamp = (match) => { | ||
const { color, effects } = match.groups; | ||
return renderStampDomWithStyle(`:${match[0]}:`, `0x${color}`, `0x${color}`, `background-color: #${color}`, effects === "" ? [] : effects.split(".").slice(1)); | ||
}; | ||
var renderUserStamp = (stampName, raw, effects) => { | ||
const userName = stampName.slice(1); | ||
const user = store.getUserByName(userName); | ||
if (!user) { | ||
return raw; | ||
} | ||
return renderStampDom(raw, stampName, stampName, `${baseUrl}/api/v3/files/${user.iconFileId}`, effects); | ||
}; | ||
var renderNormalStamp = (stampName, raw, effects) => { | ||
const stamp = store.getStampByName(stampName); | ||
if (!stamp) { | ||
return raw; | ||
} | ||
return renderStampDom(raw, stampName, stamp.name, `${baseUrl}/api/v3/files/${stamp.fileId}`, effects); | ||
}; | ||
var renderStamp = (match) => { | ||
const raw = match[0]; | ||
const { inner } = match.groups; | ||
const hexMatch = hexReg.exec(inner); | ||
if (hexMatch) { | ||
return renderHexStamp(hexMatch); | ||
} | ||
const hslMatch = hslReg.exec(inner); | ||
if (hslMatch) { | ||
return renderHslStamp(hslMatch); | ||
} | ||
const [stampName = "", ...effects] = inner.split("."); | ||
if (stampName.startsWith("@")) { | ||
return renderUserStamp(stampName, raw, effects); | ||
} | ||
if (!stampReg.exec(stampName)) { | ||
return raw; | ||
} | ||
return renderNormalStamp(stampName, raw, effects); | ||
}; | ||
var stampRegExp = /:(?<inner>(?:[a-zA-Z0-9+_-]{1,32}|@(?:Webhook#)?[a-zA-Z0-9_-]+|\w+\([^:<>"'=+!?]+\))[\w+-.]*):/; | ||
function stampPlugin(md, _store, _baseUrl) { | ||
store = _store; | ||
if (_baseUrl) { | ||
baseUrl = _baseUrl; | ||
} | ||
_markdownitregexp2.default.call(void 0, stampRegExp, renderStamp)(md); | ||
} | ||
// src/json.ts | ||
var _markdownitjson = require('markdown-it-json'); var _markdownitjson2 = _interopRequireDefault(_markdownitjson); | ||
var store2; | ||
var isStructData = (data) => { | ||
const anyData = data; | ||
return typeof anyData["type"] === "string" && typeof anyData["raw"] === "string" && typeof anyData["id"] === "string"; | ||
}; | ||
var validate = (data) => { | ||
if (!isStructData(data)) { | ||
return false; | ||
} | ||
const { type, id } = data; | ||
return type === "user" || type === "channel" && !!store2.getChannel(id) || type === "group"; | ||
}; | ||
var transformUser = (state, { type, id, raw }) => { | ||
const attributes = []; | ||
const me = store2.getMe(); | ||
attributes.push(["href", `javascript:openUserModal('${id}')`]); | ||
if (id === (me == null ? void 0 : me.id)) { | ||
attributes.push(["class", "message-user-link-highlight message-user-link"]); | ||
} else { | ||
attributes.push(["class", "message-user-link"]); | ||
} | ||
let t = state.push("traq_extends_link_open", "a", 1); | ||
t.attrs = attributes; | ||
t.meta = { type, data: id }; | ||
t = state.push("text", "", 0); | ||
t.content = raw; | ||
state.push("traq_extends_link_close", "a", -1); | ||
}; | ||
var transformUserGroup = (state, { type, id, raw }) => { | ||
var _a, _b; | ||
const attributes = []; | ||
const userGroup = store2.getUserGroup(id); | ||
const me = store2.getMe(); | ||
attributes.push(["href", `javascript:openGroupModal('${id}')`]); | ||
if ((_b = (_a = userGroup == null ? void 0 : userGroup.members) == null ? void 0 : _a.some((user) => user.id === (me == null ? void 0 : me.id))) != null ? _b : false) { | ||
attributes.push([ | ||
"class", | ||
"message-group-link-highlight message-group-link" | ||
]); | ||
} else { | ||
attributes.push(["class", "message-group-link"]); | ||
} | ||
let t = state.push("traq_extends_link_open", "a", 1); | ||
t.attrs = attributes; | ||
t.meta = { type, data: id }; | ||
t = state.push("text", "", 0); | ||
t.content = raw; | ||
state.push("traq_extends_link_close", "a", -1); | ||
}; | ||
var transformChannel = (state, { type, id, raw }) => { | ||
const attributes = []; | ||
attributes.push([ | ||
"href", | ||
`javascript:changeChannel('${store2.getChannelPath(id)}')` | ||
]); | ||
attributes.push(["class", "message-channel-link"]); | ||
let t = state.push("traq_extends_link_open", "a", 1); | ||
t.attrs = attributes; | ||
t.meta = { type, data: raw }; | ||
t = state.push("text", "", 0); | ||
t.content = raw; | ||
state.push("traq_extends_link_close", "a", -1); | ||
}; | ||
var transform = (state, data) => { | ||
if (data.type === "user") { | ||
transformUser(state, data); | ||
return; | ||
} | ||
if (data.type === "channel" && store2.getChannel(data.id)) { | ||
transformChannel(state, data); | ||
return; | ||
} | ||
if (data.type === "group") { | ||
transformUserGroup(state, data); | ||
return; | ||
} | ||
}; | ||
function messageExtendsPlugin(md, _store) { | ||
store2 = _store; | ||
_markdownitjson2.default.call(void 0, validate, transform)(md); | ||
} | ||
// src/traQMarkdownIt.ts | ||
var _markdownitkatex = require('@traptitech/markdown-it-katex'); var _markdownitkatex2 = _interopRequireDefault(_markdownitkatex); | ||
var _katex = require('katex'); var _katex2 = _interopRequireDefault(_katex); | ||
var _markdownitlinkattributes = require('markdown-it-link-attributes'); var _markdownitlinkattributes2 = _interopRequireDefault(_markdownitlinkattributes); | ||
var _markdownitimagefilter = require('markdown-it-image-filter'); var _markdownitimagefilter2 = _interopRequireDefault(_markdownitimagefilter); | ||
// src/highlight.ts | ||
var _highlightjs = require('highlight.js'); var _highlightjs2 = _interopRequireDefault(_highlightjs); | ||
// src/default/language_subset.ts | ||
var language_subset_default = [ | ||
"actionscript", | ||
"awk", | ||
"bash", | ||
"basic", | ||
"bnf", | ||
"brainfuck", | ||
"csharp", | ||
"h", | ||
"cpp", | ||
"cmake", | ||
"coq", | ||
"css", | ||
"clojure", | ||
"coffeescript", | ||
"crystal", | ||
"d", | ||
"dart", | ||
"delphi", | ||
"diff", | ||
"django", | ||
"dockerfile", | ||
"elixir", | ||
"elm", | ||
"fsharp", | ||
"fortran", | ||
"go", | ||
"gradle", | ||
"groovy", | ||
"xml", | ||
"http", | ||
"haml", | ||
"handlebars", | ||
"haxe", | ||
"ini", | ||
"json", | ||
"java", | ||
"javascript", | ||
"kotlin", | ||
"tex", | ||
"less", | ||
"lisp", | ||
"livescript", | ||
"lua", | ||
"makefile", | ||
"markdown", | ||
"mathematica", | ||
"matlab", | ||
"moonscript", | ||
"nginx", | ||
"nimrod", | ||
"ocaml", | ||
"objectivec", | ||
"glsl", | ||
"php", | ||
"perl", | ||
"plaintext", | ||
"pgsql", | ||
"powershell", | ||
"processing", | ||
"prolog", | ||
"protobuf", | ||
"python", | ||
"k", | ||
"r", | ||
"ruby", | ||
"scss", | ||
"sql", | ||
"scheme", | ||
"shell", | ||
"stylus", | ||
"swift", | ||
"twig", | ||
"typescript", | ||
"vbnet", | ||
"vbscript", | ||
"verilog", | ||
"vim", | ||
"x86asm", | ||
"xquery", | ||
"yaml", | ||
"zephir" | ||
]; | ||
// src/highlight.ts | ||
var noHighlightRe = /^(no-?highlight|plain|text)$/i; | ||
var createHighlightFunc = (preClass, withCaption = true, useSubsetForAuto = true) => (code, lang) => { | ||
let langName; | ||
let citeTag = ""; | ||
if (withCaption) { | ||
const [_langName, langCaption] = lang.split(":"); | ||
langName = _langName != null ? _langName : ""; | ||
if (langCaption) { | ||
citeTag = `<cite>${escapeHtml(langCaption)}</cite>`; | ||
} | ||
} else { | ||
langName = lang; | ||
} | ||
if (_highlightjs2.default.getLanguage(langName)) { | ||
const result = _highlightjs2.default.highlight(code, { language: langName }); | ||
return `<pre class="${preClass}">${citeTag}<code class="lang-${result.language}">${result.value}</code></pre>`; | ||
} else if (noHighlightRe.test(langName)) { | ||
return `<pre class="${preClass}">${citeTag}<code>${escapeHtml(code)}</code></pre>`; | ||
} else { | ||
const result = _highlightjs2.default.highlightAuto(code, useSubsetForAuto ? language_subset_default : void 0); | ||
return `<pre class="${preClass}">${citeTag}<code class="lang-${result.language}">${result.value}</code></pre>`; | ||
} | ||
}; | ||
// src/default/domain_whitelist.ts | ||
var domain_whitelist_default = [ | ||
"libra.tokyotech.org", | ||
"user-images.githubusercontent.com", | ||
"git.trap.jp", | ||
"wiki.trapti.tech", | ||
"wiki.trap.jp", | ||
"md.trapti.tech", | ||
"md.trap.jp", | ||
"trap.jp", | ||
"traq-dev.tokyotech.org" | ||
]; | ||
// src/embeddingExtractor.ts | ||
var uuidRegexp = /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}/; | ||
var EmbeddingExtractor = class { | ||
constructor(embeddingOrigin) { | ||
this.pathNameEmbeddingTypeMap = new Map([ | ||
["files", "file"], | ||
["messages", "message"] | ||
]); | ||
this.embeddingOrigin = embeddingOrigin; | ||
} | ||
extractType(url) { | ||
var _a, _b; | ||
if (url.origin !== this.embeddingOrigin) | ||
return "url"; | ||
const name = (_a = url.pathname.split("/")[1]) != null ? _a : ""; | ||
return (_b = this.pathNameEmbeddingTypeMap.get(name)) != null ? _b : "internal"; | ||
} | ||
extractId(url, type) { | ||
var _a; | ||
if (url.origin !== this.embeddingOrigin || type === "url" || type === "internal") | ||
return void 0; | ||
const id = (_a = url.pathname.split("/")[2]) != null ? _a : ""; | ||
return uuidRegexp.test(id) ? id : void 0; | ||
} | ||
urlToEmbeddingData(url) { | ||
let urlObj; | ||
try { | ||
urlObj = new URL(url); | ||
} catch (e2) { | ||
return; | ||
} | ||
if (urlObj.protocol !== "http:" && urlObj.protocol !== "https:") { | ||
return; | ||
} | ||
const type = this.extractType(urlObj); | ||
const id = this.extractId(urlObj, type); | ||
if (type === "internal") { | ||
return; | ||
} else if (type === "url") { | ||
return { type, url }; | ||
} else if (id) { | ||
return { type, id }; | ||
} | ||
return; | ||
} | ||
extractUrlsFromTokens(tokens, embeddings = []) { | ||
let inSpoilerCount = 0; | ||
for (const token of tokens) { | ||
if (token.children) { | ||
this.extractUrlsFromTokens(token.children, embeddings); | ||
continue; | ||
} | ||
if (token.type === "spoiler_open") { | ||
inSpoilerCount++; | ||
continue; | ||
} | ||
if (token.type === "spoiler_close" && inSpoilerCount > 0) { | ||
inSpoilerCount--; | ||
continue; | ||
} | ||
if (inSpoilerCount > 0) | ||
continue; | ||
if (token.type === "link_open") { | ||
const url = token.attrGet("href"); | ||
if (url) { | ||
const embedding = this.urlToEmbeddingData(url); | ||
if (embedding) { | ||
token.embedding = embedding; | ||
embeddings.push(embedding); | ||
} | ||
} | ||
} | ||
} | ||
return embeddings; | ||
} | ||
extract(tokens) { | ||
const embeddings = this.extractUrlsFromTokens(tokens); | ||
const knownIdSet = new Set(); | ||
const filteredEmbeddings = []; | ||
for (const embedding of embeddings) { | ||
if (embedding.type === "internal") | ||
continue; | ||
if (embedding.type === "url") { | ||
filteredEmbeddings.push(embedding); | ||
continue; | ||
} | ||
if (!knownIdSet.has(embedding.id)) { | ||
knownIdSet.add(embedding.id); | ||
filteredEmbeddings.push(embedding); | ||
} | ||
} | ||
return filteredEmbeddings; | ||
} | ||
removeTailEmbeddingsFromTailParagraph(tokens) { | ||
var _a; | ||
let isInLink = false; | ||
let removeStartIndex = -1; | ||
for (let i = tokens.length - 1; i >= 0; i--) { | ||
const token = tokens[i]; | ||
if (token.type === "softbreak") { | ||
if (removeStartIndex > 0 && !isInLink) { | ||
removeStartIndex = i; | ||
} | ||
continue; | ||
} | ||
if (token.type === "link_open" && token.markup === "linkify") { | ||
isInLink = false; | ||
const type = (_a = token.embedding) == null ? void 0 : _a.type; | ||
if (type === "file" || type === "message") { | ||
removeStartIndex = i; | ||
continue; | ||
} | ||
} | ||
if (isInLink) | ||
continue; | ||
if (token.type === "link_close" && token.markup === "linkify") { | ||
isInLink = true; | ||
continue; | ||
} | ||
if (removeStartIndex === -1) { | ||
return false; | ||
} | ||
break; | ||
} | ||
tokens.splice(removeStartIndex, tokens.length - removeStartIndex); | ||
return true; | ||
} | ||
removeTailEmbeddings(tokens) { | ||
var _a; | ||
let isInParagraph = false; | ||
let paragraphCloseToken = void 0; | ||
for (let i = tokens.length - 1; i >= 0; i--) { | ||
const token = tokens[i]; | ||
if (token.type === "hardbreak") | ||
continue; | ||
if (token.type === "paragraph_close") { | ||
isInParagraph = true; | ||
paragraphCloseToken = token; | ||
continue; | ||
} | ||
if (!(isInParagraph && token.type === "inline") || !token.children) { | ||
return; | ||
} | ||
const removed = this.removeTailEmbeddingsFromTailParagraph(token.children); | ||
if (removed) { | ||
tokens.splice(i + 1, tokens.length - (i + 1)); | ||
const lastToken = tokens[tokens.length - 1]; | ||
if (lastToken && lastToken.type === "inline" && ((_a = lastToken.children) == null ? void 0 : _a.length) === 0) { | ||
tokens.pop(); | ||
} | ||
if (paragraphCloseToken) { | ||
tokens.push(paragraphCloseToken); | ||
} | ||
} | ||
} | ||
} | ||
replace(tokens) { | ||
let linkOpenToken = void 0; | ||
for (const token of tokens) { | ||
if (token.children) { | ||
this.replace(token.children); | ||
continue; | ||
} | ||
if (token.embedding) { | ||
linkOpenToken = token; | ||
} | ||
if (!linkOpenToken) | ||
continue; | ||
if (token.type === "text") { | ||
if (linkOpenToken.embedding.type === "message") { | ||
token.content = "[[\u5F15\u7528\u30E1\u30C3\u30BB\u30FC\u30B8]]"; | ||
} else if (linkOpenToken.embedding.type === "file") { | ||
token.content = "[[\u6DFB\u4ED8\u30D5\u30A1\u30A4\u30EB]]"; | ||
} | ||
continue; | ||
} | ||
if (token.type === "link_close" && token.markup === "linkify") { | ||
linkOpenToken = void 0; | ||
continue; | ||
} | ||
} | ||
} | ||
}; | ||
// src/InlineRenderer.ts | ||
var _renderer = require('markdown-it/lib/renderer'); var _renderer2 = _interopRequireDefault(_renderer); | ||
var InlineRenderer = class extends _renderer2.default { | ||
constructor() { | ||
super(...arguments); | ||
this.blockRules = {}; | ||
} | ||
setRules(defaultRules = null, overrideRules = {}) { | ||
if (defaultRules !== null) { | ||
this.rules = __spreadValues({}, defaultRules); | ||
} | ||
this.rules.softbreak = () => " "; | ||
this.blockRules.softbreak = () => " "; | ||
this.rules.hardbreak = () => " "; | ||
this.blockRules.hardbreak = () => " "; | ||
this.rules.image = (tokens, idx) => { | ||
var _a, _b; | ||
const token = tokens[idx]; | ||
const attrsStr = ((_a = token.attrs) != null ? _a : []).filter(([attr]) => attr !== "src" && attr !== "alt").map(([key, val]) => `${escapeHtml(key)}="${escapeHtml(val)}"`).join(" "); | ||
return `<a href="${(_b = token.attrGet("src")) != null ? _b : ""}" ${attrsStr} data-is-image>${token.content}</a>`; | ||
}; | ||
this.blockRules.code_block = this.rules.code_inline; | ||
this.blockRules.fence = this.rules.code_inline; | ||
this.blockRules.hr = (tokens, idx) => ` ${tokens[idx].markup} `; | ||
this.blockRules.heading_open = (tokens, idx) => `${"#".repeat(+tokens[idx].tag.slice(1))} `; | ||
this.blockRules.blockquote_open = (tokens, idx) => `${tokens[idx].markup} `; | ||
this.blockRules.list_item_open = (tokens, idx) => `${tokens[idx].markup} `; | ||
this.blockRules.th_open = () => "| "; | ||
this.blockRules.td_open = () => "| "; | ||
this.blockRules.tr_close = () => " |"; | ||
for (const [key, val] of Object.entries(overrideRules)) { | ||
this.rules[key] = val; | ||
this.blockRules[key] = val; | ||
} | ||
} | ||
renderContent(token) { | ||
return escapeHtml(token.content); | ||
} | ||
render(tokens, options, env) { | ||
var _a, _b; | ||
let result = ""; | ||
for (const [i, token] of tokens.entries()) { | ||
const type = token.type; | ||
const blockRule = this.blockRules[type]; | ||
if (token.nesting === 1 && ((_a = tokens[i - 1]) == null ? void 0 : _a.nesting) === -1) { | ||
result += " "; | ||
} | ||
if (type === "inline") { | ||
result += this.renderInline((_b = token.children) != null ? _b : [], options, env); | ||
} else if (blockRule !== void 0) { | ||
result += blockRule(tokens, i, options, env, this); | ||
} else { | ||
result += this.renderContent(token); | ||
} | ||
} | ||
return result; | ||
} | ||
}; | ||
// src/traQMarkdownIt.ts | ||
var traQMarkdownIt = class { | ||
constructor(store3, whitelist = domain_whitelist_default, embeddingOrigin) { | ||
this.mdOptions = { | ||
breaks: true, | ||
linkify: true, | ||
highlight: createHighlightFunc("traq-code traq-lang") | ||
}; | ||
this.katexOptions = { | ||
katex: _katex2.default, | ||
output: "html", | ||
strict: (errCode) => errCode === "unicodeTextInMathMode" ? "ignore" : "warn", | ||
maxSize: 100, | ||
blockClass: "is-scroll" | ||
}; | ||
this.md = new (0, _markdownit2.default)(this.mdOptions); | ||
this.inlineRenderer = new InlineRenderer(); | ||
this.embeddingExtractor = new EmbeddingExtractor(embeddingOrigin); | ||
this.setRendererRule(); | ||
this.setPlugin(store3, whitelist); | ||
} | ||
setPlugin(store3, whitelist) { | ||
this.md.use(_markdownitmark2.default).use(_markdownitspoiler2.default, true).use(messageExtendsPlugin, store3).use(stampPlugin, store3).use(_markdownitkatex2.default, this.katexOptions).use(_markdownitlinkattributes2.default, { | ||
attrs: { | ||
target: "_blank", | ||
rel: "nofollow noopener noreferrer" | ||
} | ||
}).use(_markdownitimagefilter2.default.call(void 0, whitelist, { httpsOnly: true })); | ||
const dummyMd = new (0, _markdownit2.default)(this.mdOptions); | ||
dummyMd.use(_markdownitkatex2.default, __spreadProps(__spreadValues({}, this.katexOptions), { | ||
maxSize: 1, | ||
macros: { | ||
"\\Huge": () => "", | ||
"\\huge": () => "", | ||
"\\LARGE": () => "", | ||
"\\Large": () => "", | ||
"\\large": () => "" | ||
} | ||
})); | ||
this.inlineRenderer.setRules(this.md.renderer.rules, { | ||
math_inline: dummyMd.renderer.rules.math_inline, | ||
math_block: dummyMd.renderer.rules.math_inline | ||
}); | ||
} | ||
setRendererRule() { | ||
this.md.block.State.prototype.skipEmptyLines = function skipEmptyLines(from) { | ||
for (let max = this.lineMax; from < max; from++) { | ||
if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) { | ||
break; | ||
} | ||
this.push("hardbreak", "br", 0); | ||
} | ||
return from; | ||
}; | ||
} | ||
_render(tokens) { | ||
return this.md.renderer.render(tokens, this.mdOptions, {}); | ||
} | ||
render(text) { | ||
const parsed = this.md.parse(text, {}); | ||
const embeddings = this.embeddingExtractor.extract(parsed); | ||
this.embeddingExtractor.removeTailEmbeddings(parsed); | ||
return { | ||
embeddings, | ||
rawText: text, | ||
renderedText: this._render(parsed) | ||
}; | ||
} | ||
_renderInline(tokens) { | ||
return this.inlineRenderer.render(tokens, this.mdOptions, {}); | ||
} | ||
renderInline(text) { | ||
const parsed = this.md.parse(text, {}); | ||
const embeddings = this.embeddingExtractor.extract(parsed); | ||
this.embeddingExtractor.removeTailEmbeddings(parsed); | ||
this.embeddingExtractor.replace(parsed); | ||
return { | ||
embeddings, | ||
rawText: text, | ||
renderedText: this._renderInline(parsed) | ||
}; | ||
} | ||
}; | ||
// src/index.ts | ||
// src/container.ts | ||
var _markdownitcontainer = require('markdown-it-container'); var _markdownitcontainer2 = _interopRequireDefault(_markdownitcontainer); | ||
var defaultLabels = ["success", "info", "warning", "danger"]; | ||
var useContainer = (md, labels = defaultLabels) => { | ||
labels.forEach((label) => { | ||
md.use(_markdownitcontainer2.default, label); | ||
}); | ||
}; | ||
// src/stampCss.ts | ||
var stampRegExp2 = /:((?:[a-zA-Z0-9+_-]{1,32}|@[a-zA-Z0-9_-]+)[\w+-.]*):/; | ||
var stampRegExpWithSize = /:((?:[a-zA-Z0-9+_-]{1,32}|@[a-zA-Z0-9_-]+)[\w+-.]*)[:;]/; | ||
var renderUserStamp2 = (wrappedName, name, size) => { | ||
const username = name.slice(1); | ||
return `<i class="emoji s${size}" title="${wrappedName}" style="background-image: url(https://q.trap.jp/api/v3/public/icon/${username})">${wrappedName}</i>`; | ||
}; | ||
var renderStamp2 = (wrappedName, name, size) => { | ||
const className = `e_${name.replace(/\+/g, "_-plus-_")}`; | ||
return `<i class="emoji s${size} ${className}" title="${wrappedName}">${wrappedName}</i>`; | ||
}; | ||
var stampCssPlugin = (md, stamps, enableSize = false) => { | ||
_markdownitregexp2.default.call(void 0, enableSize ? stampRegExpWithSize : stampRegExp2, ([wrappedName = "", name = ""]) => { | ||
const size = enableSize ? wrappedName.endsWith(":") ? 32 : 16 : 24; | ||
if (name.startsWith("@")) { | ||
return renderUserStamp2(wrappedName, name, size); | ||
} | ||
if (!stamps.includes(name)) | ||
return wrappedName; | ||
return renderStamp2(wrappedName, name, size); | ||
})(md); | ||
}; | ||
exports.animeEffectSet = animeEffectSet; exports.createHighlightFunc = createHighlightFunc; exports.jsonPlugin = messageExtendsPlugin; exports.katexPlugin = _markdownitkatex2.default; exports.markPlugin = _markdownitmark2.default; exports.sizeEffectSet = sizeEffectSet; exports.spoilerPlugin = _markdownitspoiler2.default; exports.stampCssPlugin = stampCssPlugin; exports.stampPlugin = stampPlugin; exports.traQMarkdownIt = traQMarkdownIt; exports.useContainer = useContainer; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@traptitech/traq-markdown-it", | ||
"version": "5.3.3", | ||
"version": "5.3.4", | ||
"description": "Markdown parser for traQ.", | ||
"main": "dist/index.js", | ||
"module": "dist/traq-markdown-it.esm.js", | ||
"module": "dist/index.mjs", | ||
"typings": "dist/index.d.ts", | ||
@@ -13,10 +13,11 @@ "files": [ | ||
"engines": { | ||
"node": ">=10" | ||
"node": ">=12" | ||
}, | ||
"scripts": { | ||
"start": "tsdx watch", | ||
"build": "tsdx build && npm run build:css", | ||
"start": "tsup src/index.ts --watch src", | ||
"build": "tsup src/index.ts --dts --sourcemap --format esm,cjs,iife && npm run build:css", | ||
"build:css": "sass ./src/css/index.scss ./dist/index.css", | ||
"test": "tsdx test", | ||
"lint": "tsdx lint" | ||
"test": "jest", | ||
"lint": "eslint --cache \"{src,test}/**/*.ts\"", | ||
"prepare": "husky install" | ||
}, | ||
@@ -37,6 +38,5 @@ "repository": { | ||
"@traptitech/markdown-it-spoiler": "^1.1.6", | ||
"core-js": "^3.12.0", | ||
"highlight.js": "^11.0.0", | ||
"katex": "^0.13.9", | ||
"markdown-it": "^12.0.6", | ||
"highlight.js": "^11.1.0", | ||
"katex": "^0.13.13", | ||
"markdown-it": "^12.1.0", | ||
"markdown-it-container": "^3.0.0", | ||
@@ -49,29 +49,22 @@ "markdown-it-image-filter": "^1.1.0", | ||
"devDependencies": { | ||
"@babel/preset-env": "^7.14.1", | ||
"@traptitech/traq": "3.6.6-0", | ||
"@types/jest": "^25.2.3", | ||
"@types/katex": "^0.11.0", | ||
"@types/markdown-it": "12.0.2", | ||
"@typescript-eslint/eslint-plugin": "^4.22.1", | ||
"@typescript-eslint/parser": "^4.22.1", | ||
"eslint": "^7.25.0", | ||
"eslint-config-prettier": "^7.2.0", | ||
"@traptitech/traq": "3.7.7-3", | ||
"@types/jest": "^26.0.24", | ||
"@types/katex": "^0.11.1", | ||
"@types/markdown-it": "12.0.3", | ||
"@typescript-eslint/eslint-plugin": "^4.28.4", | ||
"@typescript-eslint/parser": "^4.28.4", | ||
"es-jest": "^1.2.0", | ||
"eslint": "^7.31.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"eslint-plugin-prettier": "^3.4.0", | ||
"eslint-plugin-tree-shaking": "^1.9.0", | ||
"husky": "^4.3.0", | ||
"prettier": "^2.2.1", | ||
"sass": "^1.34.0", | ||
"eslint-plugin-tree-shaking": "^1.9.2", | ||
"husky": "^7.0.1", | ||
"jest": "^27.0.6", | ||
"prettier": "^2.3.2", | ||
"sass": "^1.35.2", | ||
"ts-dedent": "^2.1.1", | ||
"tsdx": "^0.14.1", | ||
"tslib": "^2.2.0", | ||
"typescript": "^4.2.4" | ||
"tsup": "^4.12.5", | ||
"typescript": "^4.3.5" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "tsdx lint" | ||
} | ||
}, | ||
"sideEffects": [ | ||
"core-js/*" | ||
] | ||
"sideEffects": false | ||
} |
@@ -6,30 +6,16 @@ # traq-markdown-it | ||
[![codecov](https://codecov.io/gh/traPtitech/traq-markdown-it/branch/master/graph/badge.svg)](https://codecov.io/gh/traPtitech/traq-markdown-it) | ||
[![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=traPtitech/traq-markdown-it)](https://dependabot.com) | ||
Markdown parser for traQ. | ||
This project was bootstrapped with [TSDX](https://github.com/jaredpalmer/tsdx). | ||
## Local Development | ||
Below is a list of commands you will probably find useful. | ||
### `npm start` | ||
Rebuild when ts files changed. | ||
Runs the project in development/watch mode. Your project will be rebuilt upon changes. TSDX has a special logger for you convenience. Error messages are pretty printed and formatted for compatibility VS Code's Problems tab. | ||
<img src="https://user-images.githubusercontent.com/4060187/52168303-574d3a00-26f6-11e9-9f3b-71dbec9ebfcb.gif" width="600" /> | ||
Your library will be rebuilt if you make edits. | ||
### `npm run build` | ||
Build ts & sass. | ||
Bundles the package to the `dist` folder. | ||
The package is optimized and bundled with Rollup into multiple formats (CommonJS, UMD, and ES Module). | ||
<img src="https://user-images.githubusercontent.com/4060187/52168322-a98e5b00-26f6-11e9-8cf6-222d716b75ef.gif" width="600" /> | ||
### `npm test` | ||
Runs tests. | ||
Runs the test watcher (Jest) in an interactive mode. | ||
By default, runs tests related to files changed since the last commit. | ||
### `npm run lint` | ||
Runs lint. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
11
4365
0
301161
54
21
1
- Removedcore-js@^3.12.0
- Removedcore-js@3.39.0(transitive)
Updatedhighlight.js@^11.1.0
Updatedkatex@^0.13.13
Updatedmarkdown-it@^12.1.0