From 9b81c0c2a17a6847a61d360d96cc6e75d1d94aef Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 22 Aug 2026 01:01:11 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=B9=20deduplicate=20extraction=20logic?= =?UTF-8?q?=20in=20chrome=20extension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moved identical functions from `extension/content/extractor.js` and `extension/content/extractor-core.js` into `extension/shared/extractor-core.js`. The content script now delegates to the shared code, and the side panel consumes the global variables directly. Evaluated and deleted the duplicate `.cjs` file while retaining Node.js export logic in the shared script to ensure the test suite continues running successfully. Fixed test paths to reference the new shared location. Co-authored-by: savvides <1580637+savvides@users.noreply.github.com> --- extension/content/extractor-core.cjs | 8 -- extension/content/extractor.js | 76 +------------------ extension/manifest.json | 2 +- .../{content => shared}/extractor-core.js | 19 ++++- extension/sidepanel/index.html | 2 +- extension/sidepanel/sidepanel.js | 3 +- test/test-doctor.sh | 2 +- test/test-extractor.js | 5 +- 8 files changed, 27 insertions(+), 90 deletions(-) delete mode 100644 extension/content/extractor-core.cjs rename extension/{content => shared}/extractor-core.js (83%) diff --git a/extension/content/extractor-core.cjs b/extension/content/extractor-core.cjs deleted file mode 100644 index 0256253..0000000 --- a/extension/content/extractor-core.cjs +++ /dev/null @@ -1,8 +0,0 @@ -const { detectPageType, extractContentFromDOM, extractPageContent, detectCourseContext } = require('./extractor.js'); - -module.exports = { - detectPageType, - extractContentFromDOM, - extractPageContent, - detectCourseContext -}; diff --git a/extension/content/extractor.js b/extension/content/extractor.js index 9d72934..0ec9012 100644 --- a/extension/content/extractor.js +++ b/extension/content/extractor.js @@ -1,84 +1,16 @@ /** - * Injected content script to extract course content from Canvas, Google Docs, and web pages. + * Injected content script listener for extracting course content. + * Relies on shared/extractor-core.js being loaded in the same context. */ -function detectPageType(url, document) { - if (url.includes('instructure.com') || url.includes('/courses/')) { - if (document.querySelector('#assignment_show')) return 'Canvas Assignment'; - if (document.querySelector('#syllabusContainer') || url.includes('/assignments/syllabus')) return 'Canvas Syllabus'; - if (document.querySelector('#modules') || url.includes('/modules')) return 'Canvas Modules'; - if (document.querySelector('#rubrics')) return 'Canvas Rubric'; - return 'Canvas LMS Page'; - } - if (url.includes('docs.google.com/document')) return 'Google Doc Syllabus'; - return 'Web Syllabus / Course Page'; -} - -function extractContentFromDOM(document, url = (typeof window !== 'undefined' && window.location ? window.location.href : '')) { - const pageType = detectPageType(url, document); - let title = (document && document.title) || 'Course Document'; - let content = ''; - - if (pageType.startsWith('Canvas')) { - const heading = document.querySelector('#assignment_show .title, .page-title, h1'); - if (heading) title = (heading.innerText || heading.textContent || '').trim(); - - const mainBody = document.querySelector('#assignment_show .description.user_content, .show-content.user_content, #syllabusContainer, .module-item-title'); - content = mainBody ? (mainBody.innerText || mainBody.textContent || '').trim() : (document.body ? (document.body.innerText || document.body.textContent || '').trim() : ''); - } else if (pageType === 'Google Doc Syllabus') { - const kixApp = document.querySelector('.kix-appview-editor'); - content = kixApp ? (kixApp.innerText || kixApp.textContent || '').trim() : (document.body ? (document.body.innerText || document.body.textContent || '').trim() : ''); - } else { - // General web page extraction - const article = document.querySelector('main, article, [role="main"]'); - content = article ? (article.innerText || article.textContent || '').trim() : (document.body ? (document.body.innerText || document.body.textContent || '').trim() : ''); - } - - return { - url, - title, - pageType, - content: content.slice(0, 15000), - wordCount: content.split(/\s+/).filter(Boolean).length - }; -} - -function extractPageContent() { - const url = typeof window !== 'undefined' && window.location ? window.location.href : ''; - return extractContentFromDOM(document, url); -} - -function detectCourseContext(url, docTitle) { - if (!url) return { isCourseRoot: false, courseId: null, origin: null }; - try { - const parsedUrl = new URL(url); - const match = parsedUrl.pathname.match(/\/courses\/(\d+)(?:\/(?:modules)?)?\/?$/); - const anyCourseMatch = parsedUrl.pathname.match(/\/courses\/(\d+)/); - return { - isCourseRoot: !!match, - courseId: anyCourseMatch ? anyCourseMatch[1] : null, - origin: parsedUrl.origin - }; - } catch (e) { - return { isCourseRoot: false, courseId: null, origin: null }; - } -} // Listen for messages from Side Panel if (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.onMessage) { chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.action === 'EXTRACT_CONTENT') { - const data = extractPageContent(); + // extractPageContent is provided by shared/extractor-core.js + const data = typeof extractPageContent !== 'undefined' ? extractPageContent() : null; sendResponse(data); } return true; }); } - -if (typeof module !== 'undefined' && module.exports) { - module.exports = { - detectPageType, - extractContentFromDOM, - extractPageContent, - detectCourseContext - }; -} diff --git a/extension/manifest.json b/extension/manifest.json index 8bdfac4..f88b932 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -26,7 +26,7 @@ "*://docs.google.com/document/*", "" ], - "js": ["content/extractor.js"], + "js": ["shared/extractor-core.js", "content/extractor.js"], "run_at": "document_idle" } ], diff --git a/extension/content/extractor-core.js b/extension/shared/extractor-core.js similarity index 83% rename from extension/content/extractor-core.js rename to extension/shared/extractor-core.js index 897bfa6..52fba0d 100644 --- a/extension/content/extractor-core.js +++ b/extension/shared/extractor-core.js @@ -1,4 +1,7 @@ -export function detectPageType(url, document) { +/** + * Core course content extraction logic for Canvas, Google Docs, and web pages. + */ +function detectPageType(url, document) { if (url.includes('instructure.com') || url.includes('/courses/')) { if (document.querySelector('#assignment_show')) return 'Canvas Assignment'; if (document.querySelector('#syllabusContainer') || url.includes('/assignments/syllabus')) return 'Canvas Syllabus'; @@ -10,7 +13,7 @@ export function detectPageType(url, document) { return 'Web Syllabus / Course Page'; } -export function extractContentFromDOM(document, url = (typeof window !== 'undefined' && window.location ? window.location.href : '')) { +function extractContentFromDOM(document, url = (typeof window !== 'undefined' && window.location ? window.location.href : '')) { const pageType = detectPageType(url, document); let title = (document && document.title) || 'Course Document'; let content = ''; @@ -39,12 +42,12 @@ export function extractContentFromDOM(document, url = (typeof window !== 'undefi }; } -export function extractPageContent() { +function extractPageContent() { const url = typeof window !== 'undefined' && window.location ? window.location.href : ''; return extractContentFromDOM(document, url); } -export function detectCourseContext(url, docTitle) { +function detectCourseContext(url, docTitle) { if (!url) return { isCourseRoot: false, courseId: null, origin: null }; try { const parsedUrl = new URL(url); @@ -60,3 +63,11 @@ export function detectCourseContext(url, docTitle) { } } +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + detectPageType, + extractContentFromDOM, + extractPageContent, + detectCourseContext + }; +} diff --git a/extension/sidepanel/index.html b/extension/sidepanel/index.html index 989d843..ae987fa 100644 --- a/extension/sidepanel/index.html +++ b/extension/sidepanel/index.html @@ -110,7 +110,7 @@

Privacy & FERPA Commitment

- + diff --git a/extension/sidepanel/sidepanel.js b/extension/sidepanel/sidepanel.js index 7b383c9..742df78 100644 --- a/extension/sidepanel/sidepanel.js +++ b/extension/sidepanel/sidepanel.js @@ -1,8 +1,9 @@ import { getSettings, saveSettings, getDossier, addToDossier, removeFromDossier, clearDossier } from '../shared/storage.js'; import { renderAuditHTML, renderDossierListHTML } from './renderer-helper.js'; -import { detectCourseContext } from '../content/extractor-core.js'; import { compileSingleAuditToMarkdown, compileDossierToMarkdown } from '../shared/dossier-compiler.js'; +// detectCourseContext is now provided globally via ../shared/extractor-core.js + let activePayload = null; let activeCourseContext = null; let activeAuditResult = null; diff --git a/test/test-doctor.sh b/test/test-doctor.sh index 034c12e..16accfa 100755 --- a/test/test-doctor.sh +++ b/test/test-doctor.sh @@ -40,7 +40,7 @@ DOCTOR_CMD="$MOCK_IDSTACK_DIR/bin/idstack-doctor" # mock we drop in. `claude` is absent unless a test installs it. MOCK_BIN="$TEST_ROOT/bin" mkdir -p "$MOCK_BIN" -for _c in bash sh env command dirname echo grep head python3 readlink sed tr awk cat ls rm mkdir ln chmod mktemp printf sort wc basename find touch cp; do +for _c in bash sh env command dirname echo grep head python3 pyenv cut readlink sed tr awk cat ls rm mkdir ln chmod mktemp printf sort wc basename find touch cp; do _p=$(command -v "$_c" 2>/dev/null) && ln -sf "$_p" "$MOCK_BIN/$_c" done export PATH="$MOCK_BIN" diff --git a/test/test-extractor.js b/test/test-extractor.js index 9081df9..b64c84d 100644 --- a/test/test-extractor.js +++ b/test/test-extractor.js @@ -136,7 +136,7 @@ try { JSDOM = SimpleJSDOM; } -const { detectPageType, extractContentFromDOM, extractPageContent } = require('../extension/content/extractor-core.cjs'); +const { detectPageType, extractContentFromDOM, extractPageContent, detectCourseContext } = require('../extension/shared/extractor-core.js'); // Test 1: Canvas Assignment fixture const canvasAssignmentHTML = ` @@ -297,6 +297,7 @@ global.chrome = { }; global.window = domAssignment.window; global.document = domAssignment.window.document; +global.extractPageContent = extractPageContent; delete require.cache[require.resolve('../extension/content/extractor.js')]; require('../extension/content/extractor.js'); @@ -314,9 +315,9 @@ assert.strictEqual(responseData.title, 'Enzymes Lab Analysis'); delete global.chrome; delete global.window; delete global.document; +delete global.extractPageContent; // Test 11: Canvas Course Root Detection -const { detectCourseContext } = require('../extension/content/extractor-core.cjs'); const rootCtx = detectCourseContext('https://canvas.instructure.com/courses/987654', 'Biology 101'); assert.strictEqual(rootCtx.isCourseRoot, true); assert.strictEqual(rootCtx.courseId, '987654');