Files
tougao_web/src/utils/manuscriptReferenceHtml.js
2026-06-24 16:52:24 +08:00

569 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { htmlToPlainText } from '@/utils/exportTableWord';
import { normalizeReferencePublicationDetails } from '@/utils/exportManuscriptWord';
const REF_COPY_DOI_COLOR = '0082AA';
const REF_COPY_FONT = 'Charis SIL, Georgia, Times New Roman, serif';
function escapeHtml(text) {
return String(text || '')
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
function hasItalicHtmlTag(html) {
return /<i\b|<em\b/i.test(String(html || ''));
}
function stripAvailableAtSuffix(html) {
return String(html || '')
.replace(/\s*Available at:\s*[\s\S]*$/i, '')
.trim();
}
function normalizeReferenceLink(url) {
const raw = String(url || '').trim();
if (!raw) {
return '';
}
if (/^https?:\/\//i.test(raw)) {
return raw;
}
if (/^10\./.test(raw)) {
return 'https://doi.org/' + raw;
}
return raw;
}
function getReferenceIsbnValue(ref) {
if (!ref) {
return '';
}
return String(ref.isbn || ref.doilink || '').trim();
}
function appendHtmlField(parts, html, suffix, options) {
const opts = options || {};
const raw = String(html || '').trim();
if (!raw) {
return;
}
if (opts.italics && !hasItalicHtmlTag(raw)) {
parts.push('<i>' + raw + '</i>');
} else {
parts.push(raw);
}
if (suffix) {
parts.push(escapeHtml(suffix));
}
}
/** 将接口参考文献转为可编辑 HTML支持后续粘贴带 <i> 的内容) */
export function referenceRecordToEditableHtml(ref) {
if (!ref) {
return '';
}
const referType = String(ref.refer_type || 'journal').toLowerCase();
if (!ref.author && ref.refer_frag) {
let html = stripAvailableAtSuffix(ref.refer_frag);
const link = normalizeReferenceLink(ref.doilink || getReferenceIsbnValue(ref));
if (link) {
html += '<br> Available at:<br>' + escapeHtml(ref.doilink || ref.isbn || link);
}
return html;
}
const parts = [];
if (referType === 'book') {
if (ref.author) {
appendHtmlField(parts, ref.author, ' ');
}
if (ref.title) {
appendHtmlField(parts, ref.title, '. ', { italics: true });
}
if (ref.dateno) {
appendHtmlField(parts, ref.dateno, '. ');
}
const isbnValue = getReferenceIsbnValue(ref);
if (isbnValue) {
parts.push(' Available at:<br>ISBN: ' + escapeHtml(isbnValue));
}
return parts.join('');
}
if (referType === 'other') {
let html = stripAvailableAtSuffix(ref.refer_frag);
const link = normalizeReferenceLink(ref.doilink);
if (link) {
html += '<br> Available at:<br>' + escapeHtml(ref.doilink || link);
}
return html;
}
if (ref.author) {
appendHtmlField(parts, ref.author, ' ');
}
if (ref.title) {
appendHtmlField(parts, ref.title, '. ');
}
if (ref.joura) {
appendHtmlField(parts, ref.joura, '. ', { italics: !hasItalicHtmlTag(ref.joura) });
}
if (ref.dateno) {
appendHtmlField(parts, ref.dateno, '. ');
}
const doiLink = normalizeReferenceLink(ref.doilink);
if (doiLink) {
parts.push(' Available at:<br>' + escapeHtml(ref.doilink || doiLink));
}
return parts.join('');
}
export function buildReferencesEditableHtml(references, labels) {
const list = (references || []).filter(Boolean);
const L = labels || {};
const pageTitle = L.pageTitle || 'References Editor';
const intro = L.intro || '';
const saveTip = L.saveTip || '';
const downloadBtn = L.downloadEdited || 'Download edited HTML';
const refTitle = L.referencesTitle || 'References';
const bulkHint = L.bulkHint || '';
const bulkContent = buildReferencesBulkHtml(list);
return (
'<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">' +
'<meta name="viewport" content="width=device-width,initial-scale=1">' +
'<title>' + escapeHtml(pageTitle) + '</title>' +
'<style>' +
'*{box-sizing:border-box}body{margin:0;font-family:Charis SIL,Georgia,"Times New Roman",serif;background:#f5f7fa;color:#303133;line-height:1.5}' +
'.page{max-width:1400px;margin:0 auto;padding:20px}' +
'.header{background:#fff;border-radius:8px;padding:16px 20px;margin-bottom:12px;box-shadow:0 1px 4px rgba(0,0,0,.08)}' +
'.header h1{margin:0 0 8px;font-size:20px;text-align:center;font-style:italic;font-weight:bold;color:#008080}' +
'.intro{color:#606266;font-size:14px;margin-bottom:12px;white-space:pre-wrap}' +
'.actions{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px}' +
'.btn{border:1px solid #409eff;background:#409eff;color:#fff;border-radius:4px;padding:8px 16px;font-size:13px;cursor:pointer}' +
'.ref-panel{background:#fff;border-radius:8px;padding:20px 24px;box-shadow:0 1px 4px rgba(0,0,0,.08);font-size:12px}' +
'.ref-bulk{min-height:480px;padding:12px 14px;outline:none;border:1px solid #dcdfe6;border-radius:4px;line-height:1.6;white-space:pre-wrap;word-break:break-word}' +
'.ref-bulk:focus{border-color:#409eff;box-shadow:0 0 0 2px rgba(64,158,255,.15)}' +
'.ref-bulk i,.ref-bulk em{font-style:italic}' +
'.bulk-hint{margin:0 0 10px;color:#909399;font-size:12px}' +
'</style></head><body><div class="page">' +
'<div class="header"><h1>' + escapeHtml(refTitle) + '</h1>' +
'<div class="intro">' + escapeHtml(intro) + '</div>' +
'<div class="actions">' +
'<button type="button" class="btn" id="download-edited-btn">' + escapeHtml(downloadBtn) + '</button>' +
'</div>' +
'<div class="intro" style="font-size:12px;color:#909399">' + escapeHtml(saveTip) + '</div>' +
'</div>' +
'<div class="ref-panel">' +
'<p class="bulk-hint">' + escapeHtml(bulkHint) + '</p>' +
'<div id="ref-bulk" class="ref-bulk" contenteditable="true" spellcheck="false">' +
bulkContent +
'</div></div></div>' +
'<script>(function(){' +
'function serializePage(){var clone=document.documentElement.cloneNode(true);' +
'var btn=clone.querySelector("#download-edited-btn");if(btn){btn.remove();}' +
'return "<!DOCTYPE html>"+clone.outerHTML;}' +
'document.getElementById("download-edited-btn").addEventListener("click",function(){' +
'var html=serializePage();var blob=new Blob([html],{type:"text/html;charset=utf-8"});' +
'var url=URL.createObjectURL(blob);var a=document.createElement("a");' +
'a.href=url;a.download=' + JSON.stringify(L.downloadFileName || 'references-edited.html') + ';' +
'document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(url);});' +
'})();<' +
'/script></body></html>'
);
}
/** 整段参考文献 HTML用于可编辑区初始内容 */
export function buildReferencesBulkHtml(references) {
const list = (references || []).filter(Boolean);
return list
.map(function (ref, index) {
const content = buildReferenceCopyItemHtml(ref);
if (!content) {
return '';
}
return (index + 1) + '. ' + content;
})
.filter(Boolean)
.join('<br><br>');
}
function stripLeadingReferenceNumber(text) {
return String(text || '')
.replace(/^\s*(?:<[^>]+>\s*)*\[\d+\]\s*/i, '')
.replace(/^\s*(?:<[^>]+>\s*)*\d+\.\s*/i, '')
.trim();
}
function unwrapReferenceHtmlSegment(segment) {
let seg = String(segment || '').trim();
if (!seg) {
return '';
}
seg = seg.replace(/^<p[^>]*>/i, '').replace(/<\/p>$/i, '');
seg = seg.replace(/^<div[^>]*>/i, '').replace(/<\/div>$/i, '');
return seg.trim();
}
/** contenteditable 编辑后常见 div 换行,统一成 br 便于拆分 */
function normalizeReferenceBulkHtml(html) {
return String(html || '')
.replace(/\r\n/g, '\n')
.replace(/<div>\s*<br\s*\/?>\s*<\/div>/gi, '<br><br>')
.replace(/<\/div>\s*<div[^>]*>/gi, '<br><br>')
.replace(/^<div[^>]*>/i, '')
.replace(/<\/div>$/i, '');
}
function splitReferenceSegmentsByBlockElements(raw) {
if (typeof DOMParser === 'undefined') {
return [];
}
const doc = new DOMParser().parseFromString('<div id="ref-root">' + raw + '</div>', 'text/html');
const root = doc.getElementById('ref-root');
if (!root) {
return [];
}
const directBlocks = Array.from(root.children).filter(function (el) {
const tag = el.tagName.toLowerCase();
return tag === 'p' || tag === 'div';
});
if (directBlocks.length > 1) {
return directBlocks
.map(function (node) {
return unwrapReferenceHtmlSegment(node.innerHTML || node.textContent || '');
})
.filter(Boolean);
}
if (/<(?:p|div)\b/i.test(raw)) {
const blocks = doc.querySelectorAll('#ref-root p, #ref-root div');
if (blocks.length > 1) {
return Array.from(blocks)
.map(function (node) {
return unwrapReferenceHtmlSegment(node.innerHTML || node.textContent || '');
})
.filter(Boolean);
}
}
return [];
}
function splitReferenceSegments(body) {
let raw = normalizeReferenceBulkHtml(body);
raw = String(raw || '').trim();
if (!raw) {
return [];
}
const blockSegments = splitReferenceSegmentsByBlockElements(raw);
if (blockSegments.length > 1) {
return blockSegments;
}
if (/<br\s*\/?>\s*<br\s*\/?>/i.test(raw)) {
return raw.split(/<br\s*\/?>\s*<br\s*\/?>/i).map(function (part) {
return part.trim();
}).filter(Boolean);
}
const normalized = raw.replace(/\r\n/g, '\n').replace(/<br\s*\/?>/gi, '\n');
if (/\n(?=\d+\.\s)/.test(normalized)) {
return normalized.split(/\n(?=\d+\.\s)/).map(function (part) {
return part.trim();
}).filter(Boolean);
}
if (/\n(?=\[\d+\])/.test(normalized)) {
return normalized.split(/\n(?=\[\d+\])/).map(function (part) {
return part.trim();
}).filter(Boolean);
}
const parts = normalized.split(/\n\s*\n+/);
if (parts.length > 1) {
const merged = [];
parts.forEach(function (part) {
const trimmed = part.trim();
if (!trimmed) {
return;
}
if (/^\d+\.\s/.test(trimmed) || /^\[\d+\]/.test(trimmed) || !merged.length) {
merged.push(trimmed);
return;
}
merged[merged.length - 1] += '\n' + trimmed;
});
return merged.filter(Boolean);
}
return blockSegments.length ? blockSegments : [raw];
}
function inferReferenceTypeFromHtml(html) {
const raw = String(html || '');
if (/ISBN\s*:/i.test(raw)) {
return 'book';
}
return 'journal';
}
function plainSegmentToReferenceHtml(segment) {
let seg = stripLeadingReferenceNumber(segment);
if (!seg) {
return '';
}
if (/<[a-z][\s\S]*>/i.test(seg)) {
return seg.replace(/\n/g, '<br>');
}
return escapeHtml(seg).replace(/\n/g, '<br>');
}
function segmentToReferenceItem(segment) {
const html = plainSegmentToReferenceHtml(segment);
if (!html) {
return null;
}
return {
refer_type: inferReferenceTypeFromHtml(html),
html: html
};
}
/** 将整段参考文献内容拆分为 Word 导出条目 */
export function parseReferencesBulkContent(content) {
const raw = String(content || '').trim();
if (!raw) {
return [];
}
let body = raw.replace(/^(\s*<[^>]+>\s*)*References(\s*<[^>]+>\s*)*\s*/i, '');
body = body.replace(/^References\s*\n+/i, '').trim();
if (!body) {
return [];
}
if (/<html[\s>]/i.test(body) && typeof DOMParser !== 'undefined') {
const doc = new DOMParser().parseFromString(body, 'text/html');
const bulkNode = doc.querySelector('#ref-bulk');
if (bulkNode) {
body = bulkNode.innerHTML || bulkNode.textContent || '';
} else if (doc.body) {
body = doc.body.innerHTML || doc.body.textContent || body;
}
}
return splitReferenceSegments(body)
.map(function (segment) {
return segmentToReferenceItem(segment);
})
.filter(Boolean);
}
/** 预览整段参考文献可解析条数(粘贴弹窗用) */
export function countParsedReferences(content) {
return parseReferencesBulkContent(content).length;
}
export function parseReferencesEditableHtml(htmlText) {
const raw = String(htmlText || '');
if (!raw.trim()) {
return [];
}
const doc = new DOMParser().parseFromString(raw, 'text/html');
const bulkNode = doc.querySelector('#ref-bulk');
if (bulkNode) {
return parseReferencesBulkContent(bulkNode.innerHTML || bulkNode.textContent || '');
}
const nodes = doc.querySelectorAll('#ref-list .ref-item, #ref-list li[data-ref-index], ol.ref-list li');
if (nodes.length) {
const items = [];
nodes.forEach(function (node) {
const html = (node.innerHTML || '').trim();
if (!html) {
return;
}
items.push({
refer_type: String(node.getAttribute('data-ref-type') || 'journal').toLowerCase(),
html: html
});
});
if (items.length) {
return items;
}
}
return parseReferencesBulkContent(raw);
}
export function downloadReferencesEditableHtml(references, labels, fileName) {
const html = buildReferencesEditableHtml(references, labels);
const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName || 'references-editor.html';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
export function buildReferencesHtmlLabels(translate) {
const t = translate || function (key) {
return key;
};
return {
pageTitle: t('commonTable.refHtmlPageTitle'),
referencesTitle: t('commonTable.refHtmlReferencesTitle'),
intro: t('commonTable.refHtmlIntro'),
saveTip: t('commonTable.refHtmlSaveTip'),
downloadEdited: t('commonTable.refHtmlDownloadEdited'),
downloadFileName: t('commonTable.refHtmlDownloadFileName'),
bulkHint: t('commonTable.refHtmlBulkHint')
};
}
function buildReferenceCopyItemHtml(ref) {
let html = referenceRecordToEditableHtml(ref);
if (!html) {
return '';
}
html = normalizeReferencePublicationDetails(html);
html = html.replace(/\s*Available at:\s*<br\s*\/?>/gi, ' Available at:<br>');
html = html.replace(/( Available at:<br\s*\/?>)([\s\S]*)$/i, function (_, prefix, linkPart) {
const trimmed = String(linkPart || '').trim();
if (!trimmed) {
return prefix;
}
if (/^<(a|span)\b/i.test(trimmed)) {
return prefix + trimmed;
}
return prefix + '<span style="color:#' + REF_COPY_DOI_COLOR + '">' + trimmed + '</span>';
});
return html;
}
/** 按 Word 导出格式生成可复制 HTML编号、期刊斜体、DOI 蓝色、Available at 换行) */
export function buildReferencesCopyHtml(references) {
const list = (references || []).filter(Boolean);
return list
.map(function (ref, index) {
const html = buildReferenceCopyItemHtml(ref);
if (!html) {
return '';
}
return (
'<p style="margin:0 0 8pt;line-height:1.5;font-family:' +
REF_COPY_FONT +
';font-size:12pt">' +
(index + 1) +
'. ' +
html +
'</p>'
);
})
.filter(Boolean)
.join('');
}
export function buildReferencesCopyHtmlDocument(references) {
return (
'<!DOCTYPE html><html><head><meta charset="utf-8"></head><body style="font-family:' +
REF_COPY_FONT +
';font-size:12pt">' +
buildReferencesCopyHtml(references) +
'</body></html>'
);
}
/** 按 Word 导出格式生成可复制纯文本(编号 + 换行) */
export function buildReferencesCopyText(references) {
const list = (references || []).filter(Boolean);
return list
.map(function (ref, index) {
const html = buildReferenceCopyItemHtml(ref);
const text = htmlToPlainText(String(html).replace(/<br\s*\/?>/gi, '\n')).trim();
if (!text) {
return '';
}
return index + 1 + '. ' + text;
})
.filter(Boolean)
.join('\n\n');
}
function copyTextToClipboard(text) {
const value = String(text || '').trim();
if (!value) {
return Promise.reject(new Error('EMPTY'));
}
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(value).catch(function () {
const ta = document.createElement('textarea');
ta.value = value;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
});
}
const ta = document.createElement('textarea');
ta.value = value;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
return Promise.resolve();
}
function copyRichTextToClipboard(plainText, htmlText) {
const plain = String(plainText || '').trim();
const html = String(htmlText || '').trim();
if (!plain) {
return Promise.reject(new Error('EMPTY'));
}
if (typeof navigator !== 'undefined' && navigator.clipboard && window.ClipboardItem && html) {
const htmlBlob = new Blob([html], { type: 'text/html' });
const plainBlob = new Blob([plain], { type: 'text/plain' });
return navigator.clipboard
.write([
new ClipboardItem({
'text/html': htmlBlob,
'text/plain': plainBlob
})
])
.catch(function () {
return copyTextToClipboard(plain);
});
}
return copyTextToClipboard(plain);
}
/** 复制参考文献到剪贴板Word 导出同款格式,含斜体与蓝色 DOI */
export function copyReferencesToClipboard(references) {
const text = buildReferencesCopyText(references);
const html = buildReferencesCopyHtmlDocument(references);
if (!text) {
return Promise.reject(new Error('EMPTY'));
}
return copyRichTextToClipboard(text, html);
}