Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5c3fd4cc4d
commit
943763c774
@ -55,6 +55,15 @@ describe("текст события", () => {
|
||||
expect(eventText(ev({ kind: "comment", field: "comment", from: null, to: null }))).toBe("комментарий: —"); // M-6
|
||||
});
|
||||
|
||||
it("комментарий в ленте — без вики-разметки (С6)", () => {
|
||||
expect(eventText(ev({ kind: "comment", field: "comment", from: null, to: "h3. Итог\n*Готово*, см. [протокол|https://x.ru/p] и [~ivanov]" })))
|
||||
.toBe("комментарий: Итог Готово, см. протокол и @ivanov");
|
||||
});
|
||||
|
||||
it("пустой комментарий — по-прежнему «—»", () => {
|
||||
expect(eventText(ev({ kind: "comment", field: "comment", from: null, to: " " }))).toBe("комментарий: —");
|
||||
});
|
||||
|
||||
it("прочие поля — подпись из словаря или имя поля", () => {
|
||||
expect(eventText(ev({ kind: "other", field: "assignee", from: null, to: "ivanov" }))).toBe("исполнитель: «—» → «ivanov»");
|
||||
expect(eventText(ev({ kind: "other", field: "Компания", from: "А", to: "Б" }))).toBe("Компания: «А» → «Б»");
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { ChangeEvent, ChangeKind } from "../api/types.ts";
|
||||
import { wikiPlain } from "../issues/wiki/plain.ts";
|
||||
|
||||
// период ленты: границы по местному времени браузера; «Сегодня» — по умолчанию и в адрес не пишется (spec web-app «Лента изменений»)
|
||||
export type Period = "2h" | "today" | "yesterday" | "7d";
|
||||
@ -70,7 +71,7 @@ export function eventText(e: ChangeEvent): string {
|
||||
case "description": return "описание изменено";
|
||||
case "dates": return `${DATE_LABELS[e.field] ?? e.field} ${day(e.from_value, e.from)} → ${day(e.to_value, e.to)}`;
|
||||
case "comment": { // первые 300 символов текста и «…» (M-4); без текста — «—» (M-6)
|
||||
const s = oneLine(e.to);
|
||||
const s = oneLine(wikiPlain(e.to ?? ""));
|
||||
return "комментарий: " + (!s ? "—" : s.length <= 300 ? s : s.slice(0, 300) + "…");
|
||||
}
|
||||
default: {
|
||||
|
||||
24
web/src/modules/jira/issues/wiki/plain.test.ts
Normal file
24
web/src/modules/jira/issues/wiki/plain.test.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { wikiPlain } from "./plain.ts";
|
||||
|
||||
const norm = (s: string) => s.replace(/\s+/g, " ").trim();
|
||||
|
||||
describe("wikiPlain", () => {
|
||||
it("выделение, ссылки, упоминание, заголовок", () => {
|
||||
expect(norm(wikiPlain("h3. Итог\n*Готово*, см. [протокол|https://x.ru/p] и [~ivanov]"))).toBe("Итог Готово, см. протокол и @ivanov");
|
||||
});
|
||||
it("[адрес] — сам адрес, {{моно}} и {code} — содержимое, ---- — пусто", () => {
|
||||
expect(norm(wikiPlain("[https://x.ru] {{a_b}}\n----\n{code}\nx = 1\n{code}"))).toBe("https://x.ru a_b x = 1");
|
||||
});
|
||||
it("ключ задачи и вложение — текстом", () => {
|
||||
const opts = { projectKey: "AURORA", attachments: [{ id: "1", filename: "скрин.png", created: null }] };
|
||||
expect(wikiPlain("см. AURORA-1 и !скрин.png!", opts).trim()).toBe("см. AURORA-1 и скрин.png");
|
||||
});
|
||||
it("незакрытая и неизвестная разметка — как есть, пустой текст — пусто (Review Focus 3)", () => {
|
||||
expect(wikiPlain("{color:red}важно *без конца").trim()).toBe("{color:red}важно *без конца");
|
||||
expect(wikiPlain("")).toBe("");
|
||||
});
|
||||
it("списки, таблицы, цитаты — содержимое через пробел", () => {
|
||||
expect(norm(wikiPlain("* a\n** b\n# c\n||h1||h2||\n|x|y|\nbq. цит"))).toBe("a b c h1 h2 x y цит");
|
||||
});
|
||||
});
|
||||
35
web/src/modules/jira/issues/wiki/plain.ts
Normal file
35
web/src/modules/jira/issues/wiki/plain.ts
Normal file
@ -0,0 +1,35 @@
|
||||
// Текст вики-разметки Jira без разметки — как читается отрендеренным (лента изменений, С6).
|
||||
// Блоки соединены пробелом; «одну строку» и обрезку делает вызывающий.
|
||||
import type { Inline, WikiOptions } from "./inline.ts";
|
||||
import { type Block, type ListBlock, parseWiki } from "./parse.ts";
|
||||
|
||||
function inline(nodes: Inline[]): string {
|
||||
return nodes.map((n): string => {
|
||||
switch (n.t) {
|
||||
case "text": case "code": return n.text;
|
||||
case "mention": return "@" + n.login;
|
||||
case "br": return " ";
|
||||
case "issue": return n.children.length ? inline(n.children) : n.key;
|
||||
default: return inline(n.children);
|
||||
}
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function list(l: ListBlock): string[] {
|
||||
return l.items.flatMap((it) => [inline(it.children), ...it.lists.flatMap(list)]);
|
||||
}
|
||||
|
||||
function block(b: Block): string[] {
|
||||
switch (b.t) {
|
||||
case "heading": case "p": return [inline(b.children)];
|
||||
case "list": return list(b);
|
||||
case "table": return b.rows.flatMap((r) => r.map((c) => inline(c.children)));
|
||||
case "pre": return [b.text];
|
||||
case "quote": return b.children.flatMap(block);
|
||||
case "hr": return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function wikiPlain(text: string, opts: WikiOptions = {}): string {
|
||||
return parseWiki(text, opts).flatMap(block).join(" ");
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user