备份现场结果补录、图片修复与跳过文章功能
This commit is contained in:
1 parent
1e6504b4a2
commit
d157c179c8
20 files changed
+597
-47
No files matched your search
+63
-8
@@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name 广谈大湾网 · 本地文章目录导入
|
||||
// @namespace local.dww11.article-importer
|
||||
// @version 1.0.0
|
||||
// @version 1.0.1
|
||||
// @description 选择整个文章目录,导入 Markdown 标题、正文与本地配图;保留人工发布与程序接口。
|
||||
// @match https://www.dww11.com/forum.php*
|
||||
// @match https://dww11.com/forum.php*
|
||||
@@ -18,7 +18,7 @@
|
||||
else core.mount(root);
|
||||
})(typeof window === 'undefined' ? null : window, function () {
|
||||
'use strict';
|
||||
const VERSION = '1.0.0';
|
||||
const VERSION = '1.0.1';
|
||||
const MAX_IMAGE_SIZE = 20 * 1024 * 1024;
|
||||
const IMAGE_EXT = /\.(?:jpe?g|png|gif)$/i;
|
||||
|
||||
@@ -49,10 +49,11 @@
|
||||
const image = text.startsWith('![', start);
|
||||
if (!image && text[start] !== '[') return null;
|
||||
const left = start + (image ? 2 : 1);
|
||||
let end = left;
|
||||
let end = left, brackets = 1;
|
||||
for (; end < text.length; end++) {
|
||||
if (text[end] === '\\') { end++; continue; }
|
||||
if (text[end] === ']') break;
|
||||
if (text[end] === '[') brackets++;
|
||||
if (text[end] === ']' && --brackets === 0) break;
|
||||
}
|
||||
if (text.slice(end, end + 2) !== '](') return null;
|
||||
let pos = end + 2, depth = 1, quoted = '', angle = false;
|
||||
@@ -110,7 +111,7 @@
|
||||
let list = '', quote = false, code = false, fence = '';
|
||||
const closeList = () => { if (list) output.push('[/list]'); list = ''; };
|
||||
for (let index = 0; index < lines.length; index++) {
|
||||
const line = lines[index];
|
||||
let line = lines[index];
|
||||
const codeMark = line.match(/^\s*(`{3,}|~{3,})/);
|
||||
if (codeMark && (!code || codeMark[1][0] === fence)) {
|
||||
closeList();
|
||||
@@ -118,6 +119,19 @@
|
||||
code = !code; fence = codeMark[1][0]; continue;
|
||||
}
|
||||
if (code) { output.push(line.replace(/\[\/code\]/gi, '[/code]')); continue; }
|
||||
// A linked image/caption may span multiple Markdown lines. Keep the complete
|
||||
// construct together so its inner image is visited and uploaded exactly once.
|
||||
const remainder = lines.slice(index).join('\n');
|
||||
for (let pos = 0; pos < line.length; pos++) {
|
||||
const link = readLink(remainder, pos);
|
||||
if (!link) continue;
|
||||
if (link.end > line.length) {
|
||||
const endLine = remainder.slice(0, link.end).split('\n').length - 1;
|
||||
line = lines.slice(index, index + endLine + 1).join('\n');
|
||||
}
|
||||
pos = link.end - 1;
|
||||
}
|
||||
index += line.split('\n').length - 1;
|
||||
const quoted = /^\s*>\s?/.test(line);
|
||||
if (quoted !== quote) { closeList(); output.push(quoted ? '[quote]' : '[/quote]'); quote = quoted; }
|
||||
const content = quoted ? line.replace(/^\s*>\s?/, '') : line;
|
||||
@@ -217,6 +231,43 @@
|
||||
return value.replace(/"/g, '');
|
||||
}
|
||||
|
||||
function attachmentId(image) {
|
||||
// The live Discuz editor uses aid="attachimg_123", not necessarily an id.
|
||||
for (const value of [image.getAttribute('aid'), image.id]) {
|
||||
const match = String(value || '').match(/^attachimg_([1-9]\d*)$/);
|
||||
if (match) return match[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function verifyEditorImages(body, expected, signal) {
|
||||
signal?.throwIfAborted();
|
||||
if (!body?.querySelectorAll) throw new Error('无法读取可视化编辑器图片');
|
||||
const images = Array.from(body.querySelectorAll('img')).filter(image => attachmentId(image));
|
||||
if (JSON.stringify(images.map(attachmentId)) !== JSON.stringify(expected.map(String))) {
|
||||
throw new Error('正文配图数量或顺序不符,已停止;上传成功不代表正文插图成功。');
|
||||
}
|
||||
await Promise.all(images.map(image => new Promise((resolve, reject) => {
|
||||
let timer;
|
||||
const finish = error => {
|
||||
clearTimeout(timer);
|
||||
image.removeEventListener('load', onLoad);
|
||||
image.removeEventListener('error', onError);
|
||||
signal?.removeEventListener('abort', onAbort);
|
||||
error ? reject(error) : resolve();
|
||||
};
|
||||
const onLoad = () => image.naturalWidth > 0 ? finish() : onError();
|
||||
const onError = () => finish(new Error('网站附件图片无法显示(编号 ' + attachmentId(image) + ')。请核对附件缩略图及图片请求的 HTTP 错误;不会自动重传或发布。'));
|
||||
const onAbort = () => finish(new Error('流程已终止'));
|
||||
image.addEventListener('load', onLoad);
|
||||
image.addEventListener('error', onError);
|
||||
signal?.addEventListener('abort', onAbort, { once: true });
|
||||
timer = setTimeout(() => finish(new Error('等待正文图片加载超时(编号 ' + attachmentId(image) + ')')), 25000);
|
||||
if (signal?.aborted) onAbort();
|
||||
else if (image.complete) onLoad();
|
||||
})));
|
||||
}
|
||||
|
||||
function mount(win) {
|
||||
const doc = win.document;
|
||||
const params = new URLSearchParams(win.location.search);
|
||||
@@ -423,7 +474,8 @@
|
||||
setEditor(selected.title, bbcode);
|
||||
const raw = win.getEditorContents();
|
||||
if (Number(win.wysiwyg) === 1) {
|
||||
for (const id of new Set(aids.values())) if (!raw.includes('attachimg_' + id)) throw new Error('富文本图片转换未通过检查。');
|
||||
const expected = Array.from(bbcode.matchAll(/\[attachimg\](\d+)\[\/attachimg\]/g), match => match[1]);
|
||||
await verifyEditorImages(doc.querySelector('#e_iframe')?.contentDocument?.body || win.editdoc?.body, expected, signal);
|
||||
} else if (raw !== bbcode) throw new Error('正文填写校验失败。');
|
||||
if (doc.querySelector('#subject').value !== selected.title) throw new Error('标题填写校验失败。');
|
||||
committed = true;
|
||||
@@ -436,7 +488,9 @@
|
||||
const switcher = doc.querySelector('#e_switchercheck'); if (switcher) switcher.checked = !backup.mode;
|
||||
throw error;
|
||||
}
|
||||
lastResult = { title: selected.title, images: Object.fromEntries(aids), completedAt: new Date().toISOString() };
|
||||
lastResult = { title: selected.title, images: Object.fromEntries(aids),
|
||||
attachmentOrder: Array.from(bbcode.matchAll(/\[attachimg\](\d+)\[\/attachimg\]/g), match => match[1]),
|
||||
completedAt: new Date().toISOString() };
|
||||
notify('complete', `已填写标题、正文和 ${aids.size} 张配图。请检查排版,随后手动发表。`);
|
||||
return lastResult;
|
||||
} catch (error) {
|
||||
@@ -457,6 +511,7 @@
|
||||
// Public v1 interface. Staging is read-only; importPrepared is the explicit upload/fill step.
|
||||
// No postMessage listener, localhost service, publishing endpoint or saved credentials.
|
||||
const api = Object.freeze({ version: VERSION, schemaVersion: 1, stageFiles, stagePackage,
|
||||
attachmentId, verifyEditorImages,
|
||||
getState: summary, openPicker: () => { if (!busy) $('folder').click(); }, importPrepared,
|
||||
getBackup: () => savedBackup });
|
||||
win.DWW11Importer = api;
|
||||
@@ -476,5 +531,5 @@
|
||||
$('diagnostics').onclick = () => downloadJSON({ ...summary(), logs }, 'dww11-import-diagnostics.json');
|
||||
win.addEventListener('pagehide', releasePreviews, { once: true });
|
||||
}
|
||||
return { VERSION, normalizePath, imagePath, readLink, markdownToBBCode, compile, prepareFiles, preparePackage, parseAid, mount };
|
||||
return { VERSION, normalizePath, imagePath, readLink, markdownToBBCode, compile, prepareFiles, preparePackage, parseAid, attachmentId, verifyEditorImages, mount };
|
||||
});
|
||||
Reference in new issue
Block a user