170 lines
6.0 KiB
JavaScript
170 lines
6.0 KiB
JavaScript
/**
|
||
* 表格弹窗预览:将单元格内空 mycite 转为可见 [n],与 word.vue renderCiteLabels 一致(纯函数,无 Vue 依赖)
|
||
*/
|
||
|
||
export function formatCiteNumbers(nums) {
|
||
if (!nums || !nums.length) return '';
|
||
const sorted = [...new Set(nums)].sort((a, b) => a - b);
|
||
const result = [];
|
||
let i = 0;
|
||
while (i < sorted.length) {
|
||
let j = i;
|
||
while (j < sorted.length - 1 && sorted[j + 1] === sorted[j] + 1) j++;
|
||
if (j - i >= 2) {
|
||
result.push(`${sorted[i]}–${sorted[j]}`);
|
||
} else {
|
||
for (let k = i; k <= j; k++) result.push(sorted[k]);
|
||
}
|
||
i = j + 1;
|
||
}
|
||
return result.join(', ');
|
||
}
|
||
|
||
export function sortAutociteIdsByCiteNumber(ids, citeMap) {
|
||
const map = citeMap || {};
|
||
const uniq = [...new Set(ids.map(String))];
|
||
return uniq.sort((a, b) => {
|
||
const na = map[a];
|
||
const nb = map[b];
|
||
const ha = na != null && na !== '';
|
||
const hb = nb != null && nb !== '';
|
||
if (ha && hb) return Number(na) - Number(nb);
|
||
if (ha) return -1;
|
||
if (hb) return 1;
|
||
return String(a).localeCompare(String(b));
|
||
});
|
||
}
|
||
|
||
/**
|
||
* @param {Array} refs - chanFerForm,项含 p_refer_id
|
||
* @param {Array<string|number>} bodyCiteIdOrder - 正文首次出现顺序;空则按列表行序 1..n
|
||
*/
|
||
export function buildCiteMapFromRefs(refs, bodyCiteIdOrder) {
|
||
const refList = Array.isArray(refs) ? refs : [];
|
||
const refIdSet = new Set(
|
||
refList.map((r) => (r && r.p_refer_id != null ? String(r.p_refer_id) : '')).filter(Boolean)
|
||
);
|
||
|
||
let orderedIds = [];
|
||
if (Array.isArray(bodyCiteIdOrder) && bodyCiteIdOrder.length > 0) {
|
||
orderedIds = bodyCiteIdOrder.map(String);
|
||
}
|
||
|
||
if (orderedIds.length === 0) {
|
||
const map = {};
|
||
refList.forEach((row, idx) => {
|
||
const key = row && row.p_refer_id != null ? String(row.p_refer_id) : '';
|
||
if (key) map[key] = idx + 1;
|
||
});
|
||
return map;
|
||
}
|
||
|
||
const filtered = [];
|
||
orderedIds.forEach((id) => {
|
||
if (refIdSet.has(id) && !filtered.includes(id)) filtered.push(id);
|
||
});
|
||
const map = {};
|
||
filtered.forEach((id, idx) => {
|
||
map[id] = idx + 1;
|
||
});
|
||
let next = filtered.length + 1;
|
||
refList.forEach((r) => {
|
||
const key = r && r.p_refer_id != null ? String(r.p_refer_id) : '';
|
||
if (!key || map[key] != null) return;
|
||
map[key] = next++;
|
||
});
|
||
return map;
|
||
}
|
||
|
||
function escAttr(s) {
|
||
return String(s || '')
|
||
.replace(/&/g, '&')
|
||
.replace(/"/g, '"')
|
||
.replace(/</g, '<');
|
||
}
|
||
|
||
/**
|
||
* @param {string} html
|
||
* @param {Array} refs - chanFerForm
|
||
* @param {Object} citeMap - p_refer_id -> 显示序号
|
||
*/
|
||
export function renderCiteLabelsInHtml(html, refs, citeMap) {
|
||
if (!html || typeof html !== 'string') return html || '';
|
||
if (!Array.isArray(refs) || refs.length === 0) return html;
|
||
|
||
/** 与稿面一致:历史/表格 JSON 中可能为 <autocite> */
|
||
let h = html.replace(/<\/autocite>/gi, '</mycite>').replace(/<autocite\b/gi, '<mycite');
|
||
|
||
const refList = refs;
|
||
const refMap = refList.reduce((acc, item) => {
|
||
const key = item && item.p_refer_id != null ? String(item.p_refer_id) : '';
|
||
if (key) acc[key] = item;
|
||
return acc;
|
||
}, {});
|
||
|
||
let normalized = h.replace(/<mycite\b[^>]*\/?>[\s\S]*?(?:<\/mycite>)?/gi, (fullTag) => {
|
||
const attrMatch = fullTag.match(/\bdata-id\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i);
|
||
const dataId = (attrMatch && (attrMatch[1] || attrMatch[2] || attrMatch[3])) || '';
|
||
if (!dataId) return '';
|
||
return `<mycite data-id="${escAttr(dataId)}"></mycite>`;
|
||
});
|
||
const citeGroupRe = /(?:<mycite\s+data-id="([^"]*)"\s*><\/mycite>\s*)+/gi;
|
||
normalized = normalized.replace(citeGroupRe, (groupMatch) => {
|
||
const ids = [];
|
||
const innerRe = /<mycite\s+data-id="([^"]*)"\s*><\/mycite>/gi;
|
||
let m;
|
||
while ((m = innerRe.exec(groupMatch)) !== null) {
|
||
m[1].split(',').forEach((part) => {
|
||
const id = part.trim();
|
||
if (id) ids.push(id);
|
||
});
|
||
}
|
||
const sortedAll = sortAutociteIdsByCiteNumber(ids, citeMap);
|
||
const validIds = sortedAll.filter((id) => refMap[id]);
|
||
const sortedIds = validIds.length ? sortAutociteIdsByCiteNumber(validIds, citeMap) : [];
|
||
const map = citeMap || {};
|
||
|
||
const parts = sortedIds.map((id) => {
|
||
const ref = refMap[id];
|
||
const no = ref ? map[id] : null;
|
||
const num = no != null && no !== '' ? String(no) : null;
|
||
return { id, ref, num };
|
||
});
|
||
|
||
const numsForLabel = parts
|
||
.map((p) => p.num)
|
||
.filter((n) => n != null && n !== '')
|
||
.map(Number);
|
||
const label = numsForLabel.length > 0 ? formatCiteNumbers(numsForLabel) : '';
|
||
if (!label) {
|
||
return '';
|
||
}
|
||
const dataIdAttr = escAttr(sortedIds.join(','));
|
||
return `<mycite data-id="${dataIdAttr}">[${label}]</mycite>`;
|
||
});
|
||
return normalized.replace(/<mycite[^>]*data-id=""[^>]*>[\s\S]*?<\/mycite>/gi, '');
|
||
}
|
||
|
||
export function applyCiteLabelsToTableRows(rows, refs, citeMap) {
|
||
if (!Array.isArray(rows) || !rows.length) return rows;
|
||
if (!Array.isArray(refs) || refs.length === 0) return rows;
|
||
|
||
return rows.map((row) => {
|
||
if (!Array.isArray(row)) return row;
|
||
const nextRow = row.map((cell) => {
|
||
if (!cell || typeof cell !== 'object') return cell;
|
||
const t = cell.text;
|
||
const lower = typeof t === 'string' ? t.toLowerCase() : '';
|
||
if (!lower || (!lower.includes('mycite') && !lower.includes('autocite'))) return cell;
|
||
const next = renderCiteLabelsInHtml(t, refs, citeMap);
|
||
if (next === t) return cell;
|
||
return { ...cell, text: next };
|
||
});
|
||
// TableUtils.addRowIdToData 会给数组行对象挂 rowId,渲染 oddColor 依赖它,不能在 map 后丢失
|
||
if (row.rowId != null) {
|
||
nextRow.rowId = row.rowId;
|
||
}
|
||
return nextRow;
|
||
});
|
||
}
|