Вики-разметка: \#, макросы с параметрами, [~логин|текст], пробелы после {code}, непарная [ в таблице (#124, #56 M-5, M-6)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ba1db6568c
commit
5c3fd4cc4d
@ -116,4 +116,10 @@ describe("parseInline: ссылки на вложения", () => {
|
||||
it("внутри подписи ссылки вложения нет (кнопка в ссылке недопустима)", () => {
|
||||
expect(parseInline("[!a.pdf!|https://x.y]", A)).toEqual([link("https://x.y", t("!a.pdf!"))]);
|
||||
});
|
||||
it("\\# — символ решётки (M-5)", () => {
|
||||
expect(parseInline("\\# не пункт")).toEqual([{ t: "text", text: "# не пункт" }]);
|
||||
});
|
||||
it("[~логин|текст] — упоминание логина без подписи (M-6)", () => {
|
||||
expect(parseInline("[~ivanov|Иванов]")).toEqual([{ t: "mention", login: "ivanov" }]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -18,7 +18,7 @@ export type Inline =
|
||||
| { t: "br" };
|
||||
|
||||
const MARKS: Record<string, Mark> = { "*": "strong", _: "em", "-": "del", "+": "ins", "^": "sup", "~": "sub" };
|
||||
const ESCAPABLE = new Set([..."*_-+^~{}[]|!\\"]);
|
||||
const ESCAPABLE = new Set([..."*_-+^~{}[]|!#\\"]);
|
||||
const WORD = /[\p{L}\p{N}]/u;
|
||||
const SPACE = /\s/;
|
||||
const URL = /https?:\/\/[^\s\]|<>"]+/y;
|
||||
@ -147,7 +147,7 @@ function closing(line: string, i: number): number {
|
||||
// содержимое [...]: упоминание, ключ задачи, ссылка с проверенной схемой; иначе null — текст как есть
|
||||
function bracket(body: string, opts: WikiOptions, isKey: (s: string) => boolean): Inline | null {
|
||||
if (body.startsWith("~")) {
|
||||
const login = body.slice(1).trim();
|
||||
const login = body.slice(1).split("|")[0].trim();
|
||||
return login && !SPACE.test(login) ? { t: "mention", login } : null;
|
||||
}
|
||||
const ref = ATT_LINK.exec(body);
|
||||
|
||||
@ -77,4 +77,23 @@ describe("parseWiki: блоки-макросы", () => {
|
||||
it("{quote} не в начале строки — текст (R-8)", () => expect(parseWiki("см. {quote}x{quote}")).toEqual([p(t("см. {quote}x{quote}"))]));
|
||||
it.each(["{color:red}важно{color}", "!схема.png!", "{panel}x{panel}"])("неизвестное %s — текст как есть", (src) =>
|
||||
expect(parseWiki(src)).toEqual([p(t(src))]));
|
||||
it("\\# в начале строки — абзац, не список", () => {
|
||||
expect(parseWiki("\\# не пункт")).toEqual([{ t: "p", children: [{ t: "text", text: "# не пункт" }] }]);
|
||||
});
|
||||
it("quote и noformat с параметрами (M-5)", () => {
|
||||
expect(parseWiki("{quote:title=Итог}\nсогласовано\n{quote}")).toEqual([{ t: "quote", children: [{ t: "p", children: [{ t: "text", text: "согласовано" }] }] }]);
|
||||
expect(parseWiki("{noformat:borderStyle=solid}\n*x*\n{noformat}")).toEqual([{ t: "pre", text: "*x*" }]);
|
||||
});
|
||||
it("пробелы после открывающего {code} не дают пустой строки (M-6)", () => {
|
||||
expect(parseWiki("{code} \nx = 1\n{code}")).toEqual([{ t: "pre", text: "x = 1" }]);
|
||||
});
|
||||
it("непарная [ в ячейке не съедает разделители (M-6)", () => {
|
||||
const [table] = parseWiki("|[draft|итог|готово|");
|
||||
expect(table).toMatchObject({ t: "table" });
|
||||
expect((table as { rows: { children: { text: string }[] }[][] }).rows[0].map((c) => c.children[0].text)).toEqual(["[draft", "итог", "готово"]);
|
||||
});
|
||||
it("| внутри [текст|адрес] ячейку по-прежнему не делит", () => {
|
||||
const [table] = parseWiki("|[док|https://x.ru]|b|");
|
||||
expect((table as { rows: unknown[][] }).rows[0]).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
@ -14,7 +14,7 @@ export type Block =
|
||||
| { t: "quote"; children: Block[] }
|
||||
| { t: "hr" };
|
||||
|
||||
const MACRO = /^\s*\{(code(?::[^}]*)?|noformat|quote)\}/;
|
||||
const MACRO = /^\s*\{(code|noformat|quote)(?::[^}]*)?\}/;
|
||||
const HEADING = /^\s*h([1-6])\.\s+(.*)$/;
|
||||
const BQ = /^\s*bq\.\s+(.*)$/;
|
||||
const HR = /^\s*----\s*$/;
|
||||
@ -101,14 +101,14 @@ function blocks(src: string[], opts: WikiOptions): Block[] {
|
||||
|
||||
// закрывающий тег макроса в той же строке после открывающего или в последующих; null — не закрыт
|
||||
function closeMacro(lines: string[], start: number, open: RegExpExecArray): { name: string; body: string[]; end: number; after: string } | null {
|
||||
const name = open[1].split(":")[0];
|
||||
const name = open[1];
|
||||
const tag = `{${name}}`;
|
||||
const rest = lines[start].slice(open[0].length);
|
||||
const body: string[] = [];
|
||||
for (let k = start; k < lines.length; k++) {
|
||||
const text = k === start ? rest : lines[k];
|
||||
const at = text.indexOf(tag);
|
||||
if (at < 0) { if (k !== start || text) body.push(text); continue; }
|
||||
if (at < 0) { if (k !== start || text.trim()) body.push(text); continue; }
|
||||
if (text.slice(0, at)) body.push(text.slice(0, at));
|
||||
return { name, body, end: k, after: text.slice(at + tag.length) };
|
||||
}
|
||||
@ -126,7 +126,7 @@ function row(line: string, opts: WikiOptions): Cell[] {
|
||||
let depth = 0;
|
||||
// `|` внутри [ссылки|адрес] ячейку не режет
|
||||
while (j < s.length && (s[j] !== "|" || depth > 0)) {
|
||||
if (s[j] === "[") depth++;
|
||||
if (s[j] === "[") { if (s.indexOf("]", j + 1) >= 0) depth++; }
|
||||
else if (s[j] === "]" && depth > 0) depth--;
|
||||
j++;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user