Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions extension/content/extractor-core.cjs

This file was deleted.

76 changes: 4 additions & 72 deletions extension/content/extractor.js
Original file line number Diff line number Diff line change
@@ -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
};
}
2 changes: 1 addition & 1 deletion extension/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"*://docs.google.com/document/*",
"<all_urls>"
],
"js": ["content/extractor.js"],
"js": ["shared/extractor-core.js", "content/extractor.js"],
"run_at": "document_idle"
}
],
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 = '';
Expand Down Expand Up @@ -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);
Expand All @@ -60,3 +63,11 @@ export function detectCourseContext(url, docTitle) {
}
}

if (typeof module !== 'undefined' && module.exports) {
module.exports = {
detectPageType,
extractContentFromDOM,
extractPageContent,
detectCourseContext
};
}
2 changes: 1 addition & 1 deletion extension/sidepanel/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ <h4>Privacy & FERPA Commitment</h4>
</section>
</main>


<script src="../shared/extractor-core.js"></script>
<script type="module" src="sidepanel.js"></script>
</body>
</html>
3 changes: 2 additions & 1 deletion extension/sidepanel/sidepanel.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
2 changes: 1 addition & 1 deletion test/test-doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 3 additions & 2 deletions test/test-extractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `
Expand Down Expand Up @@ -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');
Expand All @@ -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');
Expand Down
Loading