参考文献

This commit is contained in:
2026-04-09 11:52:33 +08:00
parent 7527a6ef54
commit 9c44064f51
5 changed files with 191 additions and 185 deletions

View File

@@ -0,0 +1,161 @@
/**
* Main_List 全文首次出现顺序(与 Edit Content / 稿面 / 底部参考文献列表共用)。
* 正文中 <mytable> 占位处需先插入对应表格段在 Main_List 中的表题/表体/表注引用,再计其后的 mycite。
*/
import { extractAutociteIdsFromHtmlString } from '@/utils/autociteHtml.js';
export function collectCiteStringsDeep(source, out, depth = 0) {
if (!source || depth > 6) return;
if (typeof source === 'string') {
const s = source.trim();
if (!s) return;
const maybeCite = /<(?:mycite|autocite)\b/i.test(s) || /\[[\d\s,\-–—]+\]/.test(s);
if (maybeCite) out.push(s);
return;
}
if (Array.isArray(source)) {
source.forEach((item) => collectCiteStringsDeep(item, out, depth + 1));
return;
}
if (typeof source === 'object') {
Object.keys(source).forEach((k) => {
const v = source[k];
collectCiteStringsDeep(v, out, depth + 1);
});
}
}
export function collectCiteHtmlSourcesForMainListItem(p, draftAmId, draftHtml) {
if (draftAmId != null && p && p.am_id == draftAmId && draftHtml != null) {
return [draftHtml];
}
const out = [];
const typ = p != null && p.type != null ? Number(p.type) : NaN;
if (typ === 2 && p.table) {
const t = p.table;
if (typeof t.title === 'string' && t.title.trim()) out.push(t.title);
if (typeof t.html_data === 'string' && t.html_data.trim()) {
out.push(t.html_data);
} else {
const pushRows = (rows) => {
if (!Array.isArray(rows)) return;
rows.forEach((row) => {
if (!Array.isArray(row)) return;
row.forEach((cell) => {
if (cell && typeof cell.text === 'string' && cell.text) out.push(cell.text);
});
});
};
pushRows(t.tableHeader);
pushRows(t.tableContent);
}
if (typeof t.note === 'string' && t.note.trim()) out.push(t.note);
collectCiteStringsDeep(t, out);
return out;
}
if (typ === 1 && p.image) {
const img = p.image;
if (typeof img.title === 'string' && img.title.trim()) out.push(img.title);
if (typeof img.note === 'string' && img.note.trim()) out.push(img.note);
if (out.length === 0) {
if (p && typeof p.content === 'string' && p.content.trim()) out.push(p.content);
if (p && typeof p.text === 'string' && p.text.trim()) out.push(p.text);
}
return out;
}
if (p && typeof p.text === 'string' && p.text.trim()) out.push(p.text);
if (p && typeof p.content === 'string' && p.content.trim()) out.push(p.content);
return out;
}
export function findMainListTableByLinkId(mainList, rawId) {
if (rawId == null || String(rawId).trim() === '') return null;
const sid = String(rawId).trim();
const list = Array.isArray(mainList) ? mainList : [];
return (
list.find((p) => {
if (!p || Number(p.type) !== 2) return false;
if (String(p.amt_id) === sid) return true;
if (String(p.am_id) === sid) return true;
if (p.p_main_table_id != null && String(p.p_main_table_id) === sid) return true;
if (p.table && p.table.amt_id != null && String(p.table.amt_id) === sid) return true;
if (p.table && p.table.am_id != null && String(p.table.am_id) === sid) return true;
if (p.table && p.table.p_main_table_id != null && String(p.table.p_main_table_id) === sid)
return true;
if (p.table && p.table.table_id != null && String(p.table.table_id) === sid) return true;
return false;
}) || null
);
}
/**
* @param {object} [options]
* @param {number|string|null} [options.tableDraftAmId] 正在编辑的表格段 am_idEdit Table 抽屉)
* @param {string|null} [options.tableDraftHtml] 与该表对应的 Title+表体+Note 合并稿;正文里 mytable 展开到该表时用此稿替代 Main_List 已存内容,使「全文 mytable 之前」的序号与抽屉内一致
*/
export function extractAutociteOrderFromDraftHtmlWithInlineTables(html, mainList, options = {}) {
const order = [];
const pushUnique = (id) => {
if (id && !order.includes(id)) order.push(id);
};
const pushFromRaw = (raw) => {
extractAutociteIdsFromHtmlString(raw).forEach((id) => pushUnique(id));
};
if (!html || typeof html !== 'string') return order;
const re = /<mytable\b[^>]*>[\s\S]*?<\/mytable>/gi;
let last = 0;
let m;
while ((m = re.exec(html)) !== null) {
const before = html.slice(last, m.index);
if (before) pushFromRaw(before);
const openEnd = html.indexOf('>', m.index) + 1;
const openTag = html.slice(m.index, openEnd);
const dm = openTag.match(/\bdata-id\s*=\s*["']([^"']*)["']/i);
const tid = dm ? String(dm[1]).trim() : '';
const tableP = findMainListTableByLinkId(mainList, tid);
if (tableP) {
const useDraft =
options.tableDraftAmId != null &&
options.tableDraftHtml != null &&
String(tableP.am_id) === String(options.tableDraftAmId);
const sources = useDraft
? [options.tableDraftHtml]
: collectCiteHtmlSourcesForMainListItem(tableP, null, null);
sources.forEach((raw) => pushFromRaw(raw));
}
last = m.index + m[0].length;
}
const after = html.slice(last);
if (after) pushFromRaw(after);
return order;
}
export function extractAutociteOrderFromMainList(mainList, draftAmId, draftHtml, options = {}) {
const list = Array.isArray(mainList) ? mainList : [];
const order = [];
list.forEach((p) => {
if (draftAmId != null && p && p.am_id == draftAmId && draftHtml != null) {
extractAutociteOrderFromDraftHtmlWithInlineTables(draftHtml, list, options).forEach((id) => {
if (!order.includes(id)) order.push(id);
});
return;
}
const candidates = collectCiteHtmlSourcesForMainListItem(p, draftAmId, draftHtml);
candidates.forEach((raw) => {
if (typeof raw === 'string' && /<mytable\b/i.test(raw)) {
extractAutociteOrderFromDraftHtmlWithInlineTables(raw, list, options).forEach((id) => {
if (!order.includes(id)) order.push(id);
});
} else {
extractAutociteIdsFromHtmlString(raw).forEach((id) => {
if (!order.includes(id)) order.push(id);
});
}
});
});
return order;
}