feat: add initial skill authoring workspace
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
179
skills/zhihu-write/scripts/fill_article_draft.js
Normal file
179
skills/zhihu-write/scripts/fill_article_draft.js
Normal file
@@ -0,0 +1,179 @@
|
||||
function cleanText(value) {
|
||||
return String(value || '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/\u200b/g, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function pageText() {
|
||||
const body = document.body;
|
||||
return cleanText(body && (body.innerText || body.textContent || ''));
|
||||
}
|
||||
|
||||
function isLoginBlocked(url, text) {
|
||||
return /\/signin\b|\/signup\b/.test(url) ||
|
||||
/登录|注册|验证码|安全验证|验证后继续|请先登录/.test(text);
|
||||
}
|
||||
|
||||
function isVisible(node) {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
const rect = typeof node.getBoundingClientRect === 'function' ? node.getBoundingClientRect() : null;
|
||||
return !rect || rect.width > 0 || rect.height > 0;
|
||||
}
|
||||
|
||||
function attrText(node, name) {
|
||||
if (!node || typeof node.getAttribute !== 'function') {
|
||||
return '';
|
||||
}
|
||||
return cleanText(node.getAttribute(name) || '');
|
||||
}
|
||||
|
||||
function looksLikeTitleInput(node) {
|
||||
const signals = [
|
||||
attrText(node, 'placeholder'),
|
||||
attrText(node, 'data-placeholder'),
|
||||
attrText(node, 'aria-label'),
|
||||
].filter(Boolean);
|
||||
return signals.some((value) => value.includes('标题'));
|
||||
}
|
||||
|
||||
function dispatchTextInput(node) {
|
||||
node.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
||||
node.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
}
|
||||
|
||||
function fillInput(node, value) {
|
||||
node.focus();
|
||||
if ('value' in node) {
|
||||
node.value = '';
|
||||
dispatchTextInput(node);
|
||||
node.value = value;
|
||||
dispatchTextInput(node);
|
||||
return;
|
||||
}
|
||||
|
||||
node.textContent = value;
|
||||
dispatchTextInput(node);
|
||||
}
|
||||
|
||||
function fillEditable(node, value) {
|
||||
node.focus();
|
||||
node.innerHTML = '';
|
||||
const lines = String(value || '').split(/\n/);
|
||||
lines.forEach((line, index) => {
|
||||
if (index > 0) {
|
||||
node.appendChild(document.createElement('br'));
|
||||
}
|
||||
node.appendChild(document.createTextNode(line));
|
||||
});
|
||||
dispatchTextInput(node);
|
||||
}
|
||||
|
||||
function findVisible(selectors) {
|
||||
for (const selector of selectors) {
|
||||
const nodes = Array.from(document.querySelectorAll(selector));
|
||||
const match = nodes.find((node) => {
|
||||
return isVisible(node);
|
||||
});
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findBodyEditor(titleInput) {
|
||||
for (const selector of [
|
||||
"div[contenteditable='true'][role='textbox']",
|
||||
"div.public-DraftEditor-content[contenteditable='true']",
|
||||
"[role='textbox'][contenteditable='true']",
|
||||
"[contenteditable='true'][data-placeholder]",
|
||||
"div[contenteditable='true']",
|
||||
]) {
|
||||
const nodes = Array.from(document.querySelectorAll(selector));
|
||||
const match = nodes.find((node) => isVisible(node) && node !== titleInput && !looksLikeTitleInput(node));
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findButtonByText(fragment) {
|
||||
const candidates = Array.from(document.querySelectorAll("button, [role='button'], a"));
|
||||
const wanted = cleanText(fragment);
|
||||
return candidates.find((node) => {
|
||||
const text = cleanText(node.textContent);
|
||||
if (!text || !text.includes(wanted)) {
|
||||
return false;
|
||||
}
|
||||
return isVisible(node);
|
||||
}) || null;
|
||||
}
|
||||
|
||||
const currentUrl = location.href;
|
||||
const text = pageText();
|
||||
if (isLoginBlocked(currentUrl, text)) {
|
||||
return {
|
||||
status: 'login_required',
|
||||
current_url: currentUrl,
|
||||
};
|
||||
}
|
||||
|
||||
const titleInput = findVisible([
|
||||
"textarea[placeholder*='标题']",
|
||||
"input[placeholder*='标题']",
|
||||
"textarea[data-placeholder*='标题']",
|
||||
"input[data-placeholder*='标题']",
|
||||
"[role='textbox'][aria-label*='标题']",
|
||||
"[contenteditable='true'][aria-label*='标题']",
|
||||
"[contenteditable='true'][data-placeholder*='标题']",
|
||||
]);
|
||||
const bodyEditor = findBodyEditor(titleInput);
|
||||
|
||||
if (!titleInput || !bodyEditor) {
|
||||
return {
|
||||
status: 'editor_not_ready',
|
||||
current_url: currentUrl,
|
||||
};
|
||||
}
|
||||
|
||||
fillInput(titleInput, String(args.title || ''));
|
||||
fillEditable(bodyEditor, String(args.body || ''));
|
||||
|
||||
const publishMode = String(args.publish_mode || '').toLowerCase() === 'true';
|
||||
if (!publishMode) {
|
||||
return {
|
||||
status: 'draft_ready',
|
||||
current_url: currentUrl,
|
||||
title: cleanText(args.title),
|
||||
};
|
||||
}
|
||||
|
||||
const publishButton = findButtonByText('发布');
|
||||
if (!publishButton) {
|
||||
return {
|
||||
status: 'publish_button_missing',
|
||||
current_url: currentUrl,
|
||||
title: cleanText(args.title),
|
||||
};
|
||||
}
|
||||
publishButton.click();
|
||||
|
||||
const confirmButton = findButtonByText('确认发布');
|
||||
if (!confirmButton) {
|
||||
return {
|
||||
status: 'publish_clicked',
|
||||
current_url: currentUrl,
|
||||
title: cleanText(args.title),
|
||||
};
|
||||
}
|
||||
confirmButton.click();
|
||||
|
||||
return {
|
||||
status: 'publish_submitted',
|
||||
current_url: currentUrl,
|
||||
title: cleanText(args.title),
|
||||
};
|
||||
Reference in New Issue
Block a user