feat/wp-schedule-and-board/main #31

Merged
admin merged 17 commits from feat/wp-schedule-and-board/main into master 2026-09-17 06:09:42 +00:00
Showing only changes of commit e494bd534e - Show all commits
+37 -15
View File
@@ -146,6 +146,12 @@ function isSeparator(cells) {
* 在指定段落裡就地更新(或補上)一行「前綴+值」。
*
* 用於人天估算這種「議題上只該有一行、重跑要覆蓋而不是累積」的欄位。
*
* 三件事都限定在目標段落之內,因為弄錯的代價是靜靜改壞別人的內容:
* - 標題要完全相同才算數,`## 關聯度說明` 不是 `## 關聯`。
* - 圍欄裡的假標題不算標題,沿用本檔共同的圍欄判斷。
* - 找既有那一行只在段落範圍內找,別的段落剛好有同前綴時不會被改掉。
*
* 段落不存在時補在 body 結尾——寧可放錯位置,也不要讓值靜靜消失。
*
* @param {string} body 議題 body
@@ -155,26 +161,42 @@ function isSeparator(cells) {
*/
export function upsertLineInSection(body, section, line) {
const prefix = line.slice(0, line.indexOf(':') + 1);
const existing = new RegExp(`^${escapeRegExp(prefix)}.*$`, 'm');
const rows = [...eachLine(body)];
const { start, end } = sectionBounds(rows, section);
if (existing.test(body)) {
const updated = body.replace(existing, line);
return updated;
}
const heading = `## ${section}`;
const start = body.indexOf(heading);
if (start === -1) {
return `${body.replace(/\n*$/, '')}\n\n${line}\n`;
}
// 段落的結尾是下一個標題之前,或整份 body 的結尾
const after = body.indexOf('\n## ', start + heading.length);
const end = after === -1 ? body.length : after;
const head = body.slice(0, end).replace(/\n*$/, '');
return `${head}\n${line}\n${body.slice(end).replace(/^\n+/, '\n')}`;
const text = rows.map((row) => row.line);
for (let i = start + 1; i < end; i += 1) {
if (rows[i].inFence || !text[i].startsWith(prefix)) continue;
if (text[i] === line) return body;
text[i] = line;
return text.join('\n');
}
// 插在段落內容的結尾,跳過段落與段落之間的空行
let insertAt = end;
while (insertAt > start + 1 && text[insertAt - 1].trim() === '') insertAt -= 1;
text.splice(insertAt, 0, line);
return text.join('\n');
}
function escapeRegExp(text) {
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
/** 找出段落的起訖行號;start 為標題那一行,end 為下一個標題(或結尾) */
function sectionBounds(rows, section) {
let start = -1;
for (let i = 0; i < rows.length; i += 1) {
if (rows[i].inFence) continue;
const heading = rows[i].line.match(/^##\s+(.+?)\s*$/);
if (!heading) continue;
if (start === -1) {
if (heading[1] === section) start = i;
continue;
}
return { start, end: i };
}
return { start, end: rows.length };
}