adding better markdown extracting and filter out to not have broken staff, allow that session can list over multiple browsers
This commit is contained in:
+200
-12
@@ -659,6 +659,32 @@ function contentDispatch(funcName, args) {
|
||||
"li", "main", "nav", "ol", "p", "pre", "section", "table", "tbody", "td", "tfoot",
|
||||
"th", "thead", "tr", "ul"
|
||||
]);
|
||||
const NOISE_SELECTOR = [
|
||||
"script",
|
||||
"style",
|
||||
"noscript",
|
||||
"template",
|
||||
"svg",
|
||||
"canvas",
|
||||
"iframe",
|
||||
"dialog",
|
||||
"button",
|
||||
"input",
|
||||
"textarea",
|
||||
"select",
|
||||
"option",
|
||||
"form",
|
||||
"[hidden]",
|
||||
"[aria-hidden='true']",
|
||||
".sr-only",
|
||||
"[class*='sr-only']",
|
||||
"[class*='file-tile']",
|
||||
"form[data-type='unified-composer']",
|
||||
".composer-btn",
|
||||
"[data-composer-surface='true']",
|
||||
"#thread-bottom-container",
|
||||
"[data-testid*='action-button']",
|
||||
].join(", ");
|
||||
|
||||
function normalizeText(value) {
|
||||
return value.replace(/\s+/g, " ").trim();
|
||||
@@ -681,7 +707,7 @@ function contentDispatch(funcName, args) {
|
||||
}
|
||||
|
||||
function escapeMarkdown(text) {
|
||||
return text.replace(/([\\`*_{}\[\]()#+\-!|>])/g, "\\$1");
|
||||
return text.replace(/([\\`[\]])/g, "\\$1");
|
||||
}
|
||||
|
||||
function escapeTableCell(text) {
|
||||
@@ -692,12 +718,55 @@ function contentDispatch(funcName, args) {
|
||||
return attr || fallback || "";
|
||||
}
|
||||
|
||||
function isNoiseElement(node) {
|
||||
if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
|
||||
const tag = node.tagName.toLowerCase();
|
||||
if (["script", "style", "noscript", "template", "svg", "canvas", "iframe", "dialog"].includes(tag)) return true;
|
||||
if (["button", "input", "textarea", "select", "option", "form"].includes(tag)) return true;
|
||||
if (node.hasAttribute("hidden")) return true;
|
||||
if ((node.getAttribute("aria-hidden") || "").toLowerCase() === "true") return true;
|
||||
if (node.matches(".sr-only, [class*='sr-only']")) return true;
|
||||
if (node.matches("[class*='file-tile'], form[data-type='unified-composer'], .composer-btn, [data-composer-surface='true'], #thread-bottom-container")) return true;
|
||||
if (node.matches("[data-testid*='action-button']")) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function stripNoise(root) {
|
||||
const clone = root.cloneNode(true);
|
||||
clone.querySelectorAll("script, style, noscript, template").forEach(node => node.remove());
|
||||
clone.querySelectorAll(NOISE_SELECTOR).forEach(node => node.remove());
|
||||
return clone;
|
||||
}
|
||||
|
||||
function candidateScore(node) {
|
||||
const text = normalizeText(node.innerText || "");
|
||||
if (!text) return -Infinity;
|
||||
|
||||
const headings = node.querySelectorAll("h1, h2, h3, h4, h5, h6").length;
|
||||
const paragraphs = node.querySelectorAll("p").length;
|
||||
const listItems = node.querySelectorAll("li").length;
|
||||
const tables = node.querySelectorAll("table").length;
|
||||
const codeBlocks = node.querySelectorAll("pre, code").length;
|
||||
const images = node.querySelectorAll("img, figure").length;
|
||||
const mainLike = node.matches("main, article, [role='main']") ? 1 : 0;
|
||||
const proseBlocks = node.matches(".markdown, .prose, [data-message-author-role='assistant']") ? 1 : 0;
|
||||
const buttons = node.querySelectorAll("button, input, textarea, select").length;
|
||||
const forms = node.querySelectorAll("form").length;
|
||||
const svgs = node.querySelectorAll("svg, canvas").length;
|
||||
|
||||
return text.length
|
||||
+ (mainLike * 4000)
|
||||
+ (proseBlocks * 5000)
|
||||
+ (headings * 250)
|
||||
+ (paragraphs * 60)
|
||||
+ (listItems * 35)
|
||||
+ (tables * 80)
|
||||
+ (codeBlocks * 60)
|
||||
+ (images * 25)
|
||||
- (buttons * 120)
|
||||
- (forms * 200)
|
||||
- (svgs * 40);
|
||||
}
|
||||
|
||||
function pickRoot() {
|
||||
if (selector) {
|
||||
const matched = document.querySelector(selector);
|
||||
@@ -705,10 +774,12 @@ function contentDispatch(funcName, args) {
|
||||
return matched;
|
||||
}
|
||||
|
||||
const candidates = Array.from(document.querySelectorAll("main, article, [role='main']"))
|
||||
const candidates = Array.from(document.querySelectorAll(
|
||||
"main, article, [role='main'], section, .markdown, .prose, [data-message-author-role]"
|
||||
))
|
||||
.filter(node => normalizeText(node.innerText || "").length > 0);
|
||||
if (!candidates.length) return document.body;
|
||||
candidates.sort((a, b) => (b.innerText || "").length - (a.innerText || "").length);
|
||||
candidates.sort((a, b) => candidateScore(b) - candidateScore(a));
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
@@ -717,9 +788,9 @@ function contentDispatch(funcName, args) {
|
||||
return escapeMarkdown(node.textContent || "");
|
||||
}
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) return "";
|
||||
if (isNoiseElement(node)) return "";
|
||||
|
||||
const tag = node.tagName.toLowerCase();
|
||||
if (tag === "script" || tag === "style" || tag === "noscript" || tag === "template") return "";
|
||||
if (tag === "br") return "\n";
|
||||
if (tag === "img") {
|
||||
const src = absoluteUrl(node.getAttribute("src"), node.src);
|
||||
@@ -762,6 +833,92 @@ function contentDispatch(funcName, args) {
|
||||
return collapseBlankLines(normalizeInline(Array.from(node.childNodes).map(inlineText).join("")));
|
||||
}
|
||||
|
||||
function preserveNodeText(node) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
return node.textContent || "";
|
||||
}
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) return "";
|
||||
|
||||
const tag = node.tagName.toLowerCase();
|
||||
if (tag === "br") return "\n";
|
||||
|
||||
const parts = [];
|
||||
for (const child of node.childNodes) {
|
||||
const rendered = preserveNodeText(child);
|
||||
if (!rendered) continue;
|
||||
parts.push(rendered);
|
||||
}
|
||||
|
||||
if (["div", "p", "li"].includes(tag)) {
|
||||
return `${parts.join("")}\n`;
|
||||
}
|
||||
return parts.join("");
|
||||
}
|
||||
|
||||
function repairFlattenedDiagram(text) {
|
||||
if (text.includes("\n")) return text;
|
||||
const markerCount = (text.match(/[│▼├└]/g) || []).length;
|
||||
if (markerCount < 2) return text;
|
||||
|
||||
let repaired = text;
|
||||
repaired = repaired.replace(/\s{2,}([│▼])/g, "\n $1");
|
||||
repaired = repaired.replace(/([│▼])\s{2,}/g, "$1\n");
|
||||
repaired = repaired.replace(/([│▼])(?=[^\s\n│▼├└])/g, "$1\n");
|
||||
repaired = repaired.replace(/(?<=[^\s\n])([├└])/g, "\n$1");
|
||||
repaired = repaired.replace(/([^\s\n])(\()/g, "$1\n$2");
|
||||
return repaired
|
||||
.split("\n")
|
||||
.map(line => line.replace(/\s+$/, ""))
|
||||
.filter(line => line.trim())
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function convertDashListsToBranches(lines) {
|
||||
const converted = [];
|
||||
let index = 0;
|
||||
while (index < lines.length) {
|
||||
const match = lines[index].match(/^(\s*)-\s+(.*)$/);
|
||||
if (!match) {
|
||||
converted.push(lines[index]);
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const indent = match[1];
|
||||
const items = [];
|
||||
while (index < lines.length) {
|
||||
const nextMatch = lines[index].match(new RegExp(`^${indent.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}-\\s+(.*)$`));
|
||||
if (!nextMatch) break;
|
||||
items.push(nextMatch[1]);
|
||||
index += 1;
|
||||
}
|
||||
|
||||
items.forEach((item, itemIndex) => {
|
||||
const branch = itemIndex === items.length - 1 ? "└" : "├";
|
||||
converted.push(`${indent}${branch} ${item}`);
|
||||
});
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
function normalizeCodeBlock(text) {
|
||||
let lines = text.replace(/\r\n?/g, "\n").split("\n").map(line => line.replace(/\s+$/, ""));
|
||||
while (lines.length && !lines[0].trim()) lines.shift();
|
||||
while (lines.length && !lines[lines.length - 1].trim()) lines.pop();
|
||||
|
||||
const flattened = repairFlattenedDiagram(lines.join("\n"));
|
||||
lines = flattened ? flattened.split("\n") : [];
|
||||
lines = lines.map(line => {
|
||||
const trimmed = line.trim();
|
||||
if ((trimmed === "│" || trimmed === "▼") && !/^\s+[│▼]\s*$/.test(line)) {
|
||||
return ` ${trimmed}`;
|
||||
}
|
||||
return line;
|
||||
});
|
||||
lines = convertDashListsToBranches(lines);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function tableToMarkdown(table) {
|
||||
const rows = Array.from(table.querySelectorAll("tr"))
|
||||
.map(row => Array.from(row.children)
|
||||
@@ -780,10 +937,16 @@ function contentDispatch(funcName, args) {
|
||||
|
||||
let headers = normalizedRows[0];
|
||||
let bodyRows = normalizedRows.slice(1);
|
||||
const firstRowIsBlank = headers.every(cell => !cell.trim());
|
||||
if (firstRowIsBlank && normalizedRows.length > 1) {
|
||||
headers = normalizedRows[1];
|
||||
bodyRows = normalizedRows.slice(2);
|
||||
}
|
||||
|
||||
const firstRow = table.querySelector("tr");
|
||||
const thead = table.querySelector("thead");
|
||||
const firstRowHasTh = firstRow && Array.from(firstRow.children).some(cell => cell.tagName === "TH");
|
||||
if (!(thead || firstRowHasTh)) {
|
||||
if (!(thead || firstRowHasTh || firstRowIsBlank)) {
|
||||
headers = new Array(widths).fill("");
|
||||
bodyRows = normalizedRows;
|
||||
}
|
||||
@@ -818,7 +981,12 @@ function contentDispatch(funcName, args) {
|
||||
}
|
||||
|
||||
const line = collapseBlankLines(normalizeInline(content.join("")));
|
||||
if (line) items.push(`${indent}${marker}${line}`);
|
||||
if (line) {
|
||||
const lineParts = line.split("\n");
|
||||
items.push(`${indent}${marker}${lineParts[0]}`);
|
||||
const continuationIndent = `${indent}${" ".repeat(marker.length)}`;
|
||||
lineParts.slice(1).forEach(part => items.push(`${continuationIndent}${part}`));
|
||||
}
|
||||
nested.filter(Boolean).forEach(block => items.push(block));
|
||||
});
|
||||
return items.join("\n");
|
||||
@@ -829,13 +997,21 @@ function contentDispatch(funcName, args) {
|
||||
return normalizeText(node.textContent || "");
|
||||
}
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) return "";
|
||||
if (isNoiseElement(node)) return "";
|
||||
|
||||
const tag = node.tagName.toLowerCase();
|
||||
if (tag === "script" || tag === "style" || tag === "noscript" || tag === "template") return "";
|
||||
if (tag === "table") return tableToMarkdown(node);
|
||||
if (tag === "ul" || tag === "ol") return listToMarkdown(node);
|
||||
if (node.matches(".cm-editor[data-is-code-block-view='true']")) {
|
||||
const lines = Array.from(node.querySelectorAll(".cm-line")).map(line => {
|
||||
const text = preserveNodeText(line);
|
||||
return text === "\n" ? "" : text.replace(/\n$/, "");
|
||||
});
|
||||
const code = normalizeCodeBlock(lines.join("\n"));
|
||||
return code ? `\`\`\`\n${code}\n\`\`\`` : "";
|
||||
}
|
||||
if (tag === "pre") {
|
||||
const code = node.innerText.replace(/\n$/, "");
|
||||
const code = normalizeCodeBlock(preserveNodeText(node));
|
||||
return code ? `\`\`\`\n${code}\n\`\`\`` : "";
|
||||
}
|
||||
if (tag === "blockquote") {
|
||||
@@ -1011,9 +1187,21 @@ async function clientsRenameProfile({ alias }) {
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
async function getActiveTab() {
|
||||
const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
|
||||
if (!tab) throw new Error("No active tab found");
|
||||
return tab;
|
||||
const activeTabs = await chrome.tabs.query({ active: true });
|
||||
if (!activeTabs.length) throw new Error("No active tab found");
|
||||
|
||||
const windows = await chrome.windows.getAll({ populate: false });
|
||||
const focusedWindowIds = new Set(windows.filter(window => window.focused).map(window => window.id));
|
||||
|
||||
const chooseTab = (predicate) => activeTabs.find(predicate);
|
||||
const byFocusAndScriptable = tab => focusedWindowIds.has(tab.windowId) && isScriptableUrl(tab.url || tab.pendingUrl || "");
|
||||
const byScriptable = tab => isScriptableUrl(tab.url || tab.pendingUrl || "");
|
||||
const byFocus = tab => focusedWindowIds.has(tab.windowId);
|
||||
|
||||
return chooseTab(byFocusAndScriptable)
|
||||
|| chooseTab(byScriptable)
|
||||
|| chooseTab(byFocus)
|
||||
|| activeTabs[0];
|
||||
}
|
||||
|
||||
async function resolveGroupId(nameOrId) {
|
||||
|
||||
Reference in New Issue
Block a user