pdf-parse (was v2, broken API): - Downgraded to v1.1.1 — default export is a function again - pdfParse is not a function error fixed WebDAV file selection (style.display bug, same cascade issue): - lh-ai-webdav-selected had inline style="display:flex" always visible - selectWebdavFile() / deselectWebdavFile() now use element.style.display - On select: browser div hides, selected indicator shows with file name + X - On deselect (X): indicator hides, browser shows again Topic context for Upload and Nextcloud tabs: - Both tabs now have optional "Topic / context" field - Sent to backend as 'topic' param to help AI focus the generated content Inline refine bar (replaces window.prompt): - Refine Body button shows/hides an amber inline input bar below generate buttons - Enter instructions, press Apply — no browser dialog, works in all contexts CSP: remove unsafe-inline from scriptSrc (security issue #2): - Converted all 26 onclick= handlers in component HTML files to data-action / data-target / data-label attributes - Added delegated click handler in app.js for copy/speak/nc-export actions - script-src now 'self' only; script-src-attr 'none' WebDAV path endpoint (security issue #5): - New POST /api/user/webdav-path (authMiddleware only, not moderator) - nextcloud.js updated to use new endpoint nodemailer: already at 6.10.1 (vulnerability fixed).
304 lines
20 KiB
HTML
304 lines
20 KiB
HTML
<div class="module-header">
|
|
<h2><i class="fas fa-pen-to-square"></i> Content Manager</h2>
|
|
<p>Create and manage Learning Hub content, quizzes, and categories</p>
|
|
</div>
|
|
|
|
<!-- Stats bar -->
|
|
<div class="cms-stats-bar" id="lh-cms-stats-bar">
|
|
<div class="cms-stat"><span class="cms-stat-value" id="cms-stat-published">-</span><span class="cms-stat-label">Published</span></div>
|
|
<div class="cms-stat"><span class="cms-stat-value" id="cms-stat-drafts">-</span><span class="cms-stat-label">Drafts</span></div>
|
|
<div class="cms-stat"><span class="cms-stat-value" id="cms-stat-categories">-</span><span class="cms-stat-label">Categories</span></div>
|
|
<div class="cms-stat"><span class="cms-stat-value" id="cms-stat-quizzes">-</span><span class="cms-stat-label">Quizzes</span></div>
|
|
<div class="cms-stat"><span class="cms-stat-value" id="cms-stat-attempts">-</span><span class="cms-stat-label">Attempts</span></div>
|
|
</div>
|
|
|
|
<!-- CMS Layout: sidebar + main -->
|
|
<div class="cms-layout">
|
|
|
|
<!-- Sidebar: Categories + filters -->
|
|
<aside class="cms-sidebar">
|
|
<div class="cms-sidebar-section">
|
|
<h4><i class="fas fa-folder-tree"></i> Categories</h4>
|
|
<div id="lh-cms-categories" class="cms-cat-list"></div>
|
|
<div class="cms-add-cat">
|
|
<input type="text" id="lh-cms-cat-name" placeholder="New category..." class="cms-input-sm">
|
|
<button id="btn-lh-add-cat" class="cms-btn-add" title="Add category"><i class="fas fa-plus"></i></button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="cms-sidebar-section">
|
|
<h4><i class="fas fa-filter"></i> Filter</h4>
|
|
<select id="cms-filter-status" class="cms-input-sm" style="width:100%;margin-bottom:6px;">
|
|
<option value="all">All Status</option>
|
|
<option value="published">Published</option>
|
|
<option value="draft">Drafts</option>
|
|
</select>
|
|
<select id="cms-filter-category" class="cms-input-sm" style="width:100%;">
|
|
<option value="all">All Categories</option>
|
|
</select>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Main: Content list / Editor -->
|
|
<div class="cms-main">
|
|
|
|
<!-- Content list view -->
|
|
<div id="cms-list-view">
|
|
<div class="cms-toolbar">
|
|
<div style="display:flex;gap:8px;">
|
|
<button id="btn-lh-new-content" class="btn-sm btn-primary" data-type="article"><i class="fas fa-file-alt"></i> New Article</button>
|
|
<button id="btn-lh-new-quiz" class="btn-sm" style="background:var(--amber-light);color:#92400e;border:1px solid var(--amber);border-radius:6px;padding:6px 12px;font-size:13px;cursor:pointer;"><i class="fas fa-clipboard-question"></i> New Quiz</button>
|
|
<button id="btn-lh-new-pearl" class="btn-sm" style="background:var(--purple-light);color:var(--purple);border:1px solid var(--purple);border-radius:6px;padding:6px 12px;font-size:13px;cursor:pointer;"><i class="fas fa-gem"></i> New Pearl</button>
|
|
<button id="btn-lh-new-presentation" class="btn-sm" style="background:#ecfdf5;color:#065f46;border:1px solid #6ee7b7;border-radius:6px;padding:6px 12px;font-size:13px;cursor:pointer;"><i class="fas fa-presentation-screen"></i> New Presentation</button>
|
|
</div>
|
|
<div class="cms-search">
|
|
<i class="fas fa-search"></i>
|
|
<input type="text" id="cms-search" placeholder="Search content..." class="cms-input-sm">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="cms-content-table">
|
|
<div class="cms-table-header">
|
|
<span class="cms-col-title">Title</span>
|
|
<span class="cms-col-cat">Category</span>
|
|
<span class="cms-col-type">Type</span>
|
|
<span class="cms-col-status">Status</span>
|
|
<span class="cms-col-date">Updated</span>
|
|
<span class="cms-col-actions"></span>
|
|
</div>
|
|
<div id="lh-cms-content-list" class="cms-table-body">
|
|
<div style="text-align:center;padding:40px;color:var(--g400);">Loading...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Editor view (hidden until edit/new) -->
|
|
<div id="lh-cms-editor" class="cms-editor hidden">
|
|
<div class="cms-editor-header">
|
|
<button id="btn-lh-close-editor" class="btn-sm btn-ghost"><i class="fas fa-arrow-left"></i> Back to List</button>
|
|
<div class="cms-editor-actions">
|
|
<button id="btn-lh-ai-open" class="btn-sm" style="background:linear-gradient(135deg,#7c3aed,#2563eb);color:white;border:none;"><i class="fas fa-wand-magic-sparkles"></i> AI Generate</button>
|
|
<button id="btn-lh-delete-content" class="btn-sm hidden" style="background:var(--red-light);color:var(--red);"><i class="fas fa-trash"></i> Delete</button>
|
|
<select id="lh-cms-edit-published" class="cms-input-sm">
|
|
<option value="false">Draft</option>
|
|
<option value="true">Published</option>
|
|
</select>
|
|
<button id="btn-lh-save-content" class="btn-sm btn-primary"><i class="fas fa-save"></i> Save</button>
|
|
</div>
|
|
</div>
|
|
<!-- Inline delete confirmation bar — message built dynamically in JS -->
|
|
<div id="lh-delete-confirm-bar" class="lh-delete-confirm-bar hidden">
|
|
<i class="fas fa-triangle-exclamation"></i>
|
|
<span id="lh-delete-confirm-msg"></span>
|
|
<div style="display:flex;gap:8px;margin-left:auto;flex-shrink:0;">
|
|
<button id="btn-lh-delete-cancel" class="btn-sm btn-ghost">Cancel</button>
|
|
<button id="btn-lh-delete-yes" class="btn-sm" style="background:var(--red);color:white;">Yes, Delete</button>
|
|
</div>
|
|
</div>
|
|
|
|
<input type="hidden" id="lh-cms-edit-id" value="">
|
|
|
|
<!-- Title -->
|
|
<input type="text" id="lh-cms-edit-title" class="cms-title-input" placeholder="Enter title...">
|
|
|
|
<!-- Meta row -->
|
|
<div class="cms-meta-row">
|
|
<div class="cms-meta-field">
|
|
<label>Category</label>
|
|
<select id="lh-cms-edit-category" class="cms-input-sm">
|
|
<option value="">Uncategorized</option>
|
|
</select>
|
|
</div>
|
|
<div class="cms-meta-field">
|
|
<label>Subject</label>
|
|
<input type="text" id="lh-cms-edit-subject" class="cms-input-sm" placeholder="e.g., Development">
|
|
</div>
|
|
<div class="cms-meta-field">
|
|
<label>Type</label>
|
|
<select id="lh-cms-edit-type" class="cms-input-sm">
|
|
<option value="article">Article</option>
|
|
<option value="quiz">Quiz Only</option>
|
|
<option value="pearl">Clinical Pearl</option>
|
|
<option value="presentation">Presentation</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- AI Generate panel -->
|
|
<div id="lh-ai-panel" class="lh-ai-panel hidden">
|
|
<div class="lh-ai-panel-header">
|
|
<div style="display:flex;align-items:center;gap:8px;">
|
|
<span style="font-size:18px;">✨</span>
|
|
<strong style="font-size:14px;color:var(--g800);">AI Content Generator</strong>
|
|
</div>
|
|
<button id="btn-lh-ai-close" class="btn-sm btn-ghost" style="padding:2px 8px;">✕ Close</button>
|
|
</div>
|
|
|
|
<!-- Source tabs -->
|
|
<div class="lh-ai-tabs">
|
|
<button class="lh-ai-tab active" data-aitab="topic"><i class="fas fa-lightbulb"></i> By Topic</button>
|
|
<button class="lh-ai-tab" data-aitab="upload"><i class="fas fa-file-upload"></i> Upload File</button>
|
|
<button class="lh-ai-tab" data-aitab="webdav" id="lh-ai-tab-webdav"><i class="fas fa-cloud"></i> Nextcloud</button>
|
|
</div>
|
|
|
|
<!-- Topic tab -->
|
|
<div class="lh-ai-tabpanel" id="lh-ai-tp-topic">
|
|
<div class="form-group" style="margin:0;">
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);">Topic</label>
|
|
<input type="text" id="lh-ai-topic" class="cms-input-sm" style="width:100%;" placeholder="e.g., Febrile seizures in children, ADHD management, Asthma exacerbation">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Upload tab -->
|
|
<div class="lh-ai-tabpanel hidden" id="lh-ai-tp-upload">
|
|
<label class="lh-ai-dropzone" id="lh-ai-dropzone">
|
|
<i class="fas fa-cloud-upload-alt" style="font-size:28px;color:var(--blue);margin-bottom:8px;display:block;"></i>
|
|
<span id="lh-ai-file-label">Drop a file here or click to browse</span>
|
|
<small style="color:var(--g400);display:block;margin-top:4px;">PDF, TXT, MD, HTML — max 20 MB</small>
|
|
<input type="file" id="lh-ai-file" accept=".pdf,.txt,.md,.html,.htm,.csv,.json" style="display:none;">
|
|
</label>
|
|
<div class="form-group" style="margin:8px 0 0;">
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);">Topic / context <span style="color:var(--g400);font-weight:400;">(optional — helps AI focus)</span></label>
|
|
<input type="text" id="lh-ai-upload-context" class="cms-input-sm" style="width:100%;" placeholder="e.g., Pediatric asthma management, focus on treatment ladder">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- WebDAV tab -->
|
|
<div class="lh-ai-tabpanel hidden" id="lh-ai-tp-webdav">
|
|
<!-- File browser — hidden once a file is selected -->
|
|
<div id="lh-ai-webdav-browser">
|
|
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
|
|
<span id="lh-ai-webdav-path-label" style="font-size:12px;color:var(--g500);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">/</span>
|
|
<button id="btn-lh-webdav-refresh" class="btn-sm btn-ghost" style="padding:3px 8px;font-size:12px;"><i class="fas fa-sync"></i></button>
|
|
</div>
|
|
<div id="lh-ai-webdav-list" style="max-height:200px;overflow-y:auto;border:1px solid var(--g200);border-radius:8px;background:white;">
|
|
<div style="padding:12px;text-align:center;color:var(--g400);font-size:13px;">Click to browse your Nextcloud files</div>
|
|
</div>
|
|
</div>
|
|
<!-- Selected file indicator — shown after selection, replaces browser -->
|
|
<div id="lh-ai-webdav-selected" style="display:none;flex-direction:column;gap:8px;">
|
|
<div style="display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--blue-light);border-radius:6px;font-size:13px;color:var(--blue);">
|
|
<i class="fas fa-file-check"></i>
|
|
<span id="lh-ai-webdav-selected-name" style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"></span>
|
|
<button id="btn-lh-webdav-deselect" style="background:none;border:none;cursor:pointer;color:var(--blue);padding:0;font-size:16px;line-height:1;" title="Remove selection">✕</button>
|
|
</div>
|
|
<div class="form-group" style="margin:0;">
|
|
<label style="font-size:12px;font-weight:600;color:var(--g600);">Topic / context <span style="color:var(--g400);font-weight:400;">(optional — helps AI focus)</span></label>
|
|
<input type="text" id="lh-ai-webdav-context" class="cms-input-sm" style="width:100%;" placeholder="e.g., Focus on management guidelines for pediatric residents">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Generation options -->
|
|
<div class="lh-ai-options">
|
|
<!-- Row 1: type + model -->
|
|
<div class="lh-ai-opt-row">
|
|
<div class="lh-ai-opt-field">
|
|
<label>Content type</label>
|
|
<select id="lh-ai-ctype" class="cms-input-sm">
|
|
<option value="article">Article</option>
|
|
<option value="quiz">Quiz</option>
|
|
<option value="pearl">Clinical Pearl</option>
|
|
<option value="presentation">Presentation</option>
|
|
</select>
|
|
</div>
|
|
<div class="lh-ai-opt-field" style="flex:2;">
|
|
<label>Model</label>
|
|
<select id="lh-ai-model" class="cms-input-sm tab-model-select" style="width:100%;"></select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Row 2: context-aware size options (shown/hidden via style.display in JS) -->
|
|
<div class="lh-ai-opt-row" id="lh-ai-size-row">
|
|
<div id="lh-ai-words-field" style="display:flex;flex-direction:column;gap:3px;">
|
|
<label style="font-size:11px;font-weight:600;color:var(--g500);text-transform:uppercase;letter-spacing:0.3px;">Approx. word count <span style="color:var(--g400);font-weight:400;">(optional)</span></label>
|
|
<input type="number" id="lh-ai-word-count" value="" min="100" max="3000" step="50" class="cms-input-sm" style="width:110px;" placeholder="e.g. 600">
|
|
</div>
|
|
<div id="lh-ai-slides-field" style="display:none;flex-direction:column;gap:3px;">
|
|
<label style="font-size:11px;font-weight:600;color:var(--g500);text-transform:uppercase;letter-spacing:0.3px;">Number of slides <span style="color:var(--g400);font-weight:400;">(optional)</span></label>
|
|
<input type="number" id="lh-ai-slide-count" value="" min="3" max="30" class="cms-input-sm" style="width:90px;" placeholder="e.g. 10">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Row 3: quiz questions card -->
|
|
<div id="lh-ai-quiz-row" class="lh-ai-quiz-card">
|
|
<div class="lh-ai-quiz-card-header" id="lh-ai-quiz-card-header">
|
|
<i class="fas fa-clipboard-question" style="color:var(--blue);font-size:14px;"></i>
|
|
<span id="lh-ai-quiz-card-title" style="font-weight:600;font-size:13px;color:var(--g800);">Quiz Questions</span>
|
|
</div>
|
|
<div class="lh-ai-quiz-card-body">
|
|
<label id="lh-ai-q-toggle-label" style="display:none;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:var(--g700);margin:0;">
|
|
<input type="checkbox" id="lh-ai-q-toggle" style="accent-color:var(--blue);width:15px;height:15px;flex-shrink:0;">
|
|
Also generate quiz questions for this content
|
|
</label>
|
|
<div id="lh-ai-q-count-wrap" style="display:flex;align-items:center;gap:8px;">
|
|
<label style="font-size:12px;color:var(--g600);margin:0;">Number of questions</label>
|
|
<input type="number" id="lh-ai-q-count" value="5" min="1" max="20" class="cms-input-sm" style="width:70px;">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Row 4: instructions -->
|
|
<div class="lh-ai-opt-field" style="width:100%;">
|
|
<label>Special instructions <span style="color:var(--g400);font-weight:400;">(optional)</span></label>
|
|
<input type="text" id="lh-ai-refinement" class="cms-input-sm" style="width:100%;" placeholder="e.g., Focus on ER management, suitable for residents, case-based format">
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display:flex;gap:8px;align-items:center;">
|
|
<button id="btn-lh-ai-generate" class="btn-sm btn-primary" style="flex:1;padding:10px;">
|
|
<i class="fas fa-wand-magic-sparkles"></i> Generate Content
|
|
</button>
|
|
<button id="btn-lh-ai-refine-body" class="btn-sm btn-ghost" style="padding:10px;">
|
|
<i class="fas fa-pen-to-square"></i> Refine Body
|
|
</button>
|
|
</div>
|
|
<!-- Inline refine input bar — shown when Refine Body is clicked -->
|
|
<div id="lh-ai-refine-bar" style="display:none;gap:6px;align-items:center;padding:10px 12px;background:#fffbeb;border:1.5px solid var(--amber);border-radius:8px;margin-top:4px;">
|
|
<input type="text" id="lh-ai-refine-input" class="cms-input-sm" style="flex:1;" placeholder='e.g. "Make it more concise" or "Add a clinical case example"'>
|
|
<button id="btn-lh-refine-submit" class="btn-sm btn-primary" style="padding:5px 14px;white-space:nowrap;">Apply</button>
|
|
<button id="btn-lh-refine-cancel" class="btn-sm btn-ghost" style="padding:5px 10px;">✕</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Body editor (Tiptap rich text) — hidden for presentations -->
|
|
<div id="lh-body-section" class="cms-body-section">
|
|
<label class="cms-label">Body</label>
|
|
<div id="lh-body-editor" class="cms-quill-body"></div>
|
|
</div>
|
|
|
|
<!-- Marp markdown editor — shown only for presentations -->
|
|
<div id="lh-marp-section" class="cms-body-section hidden">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;">
|
|
<label class="cms-label" style="margin:0;">Slides <span style="font-size:11px;color:var(--g400);font-weight:400;">(Marp markdown — separate slides with <code>---</code>)</span></label>
|
|
<div style="display:flex;gap:6px;">
|
|
<button id="btn-lh-preview-slides" class="btn-sm btn-ghost" style="font-size:12px;"><i class="fas fa-eye"></i> Preview</button>
|
|
<button id="btn-lh-download-pptx" class="btn-sm" style="background:#065f46;color:white;font-size:12px;border:none;border-radius:6px;padding:5px 12px;cursor:pointer;"><i class="fas fa-file-powerpoint"></i> Download PPTX</button>
|
|
</div>
|
|
</div>
|
|
<textarea id="lh-marp-editor" class="lh-marp-textarea" placeholder="✨ Click 'AI Generate' above to create your slides automatically.
|
|
|
|
After generation, you can edit the Marp markdown here if needed.
|
|
Each slide is separated by ---
|
|
|
|
Example structure:
|
|
# Title Slide
|
|
Subtitle here
|
|
---
|
|
# Topic Slide
|
|
- Key point one
|
|
- Key point two"></textarea>
|
|
</div>
|
|
|
|
<!-- Quiz builder -->
|
|
<div class="cms-quiz-section">
|
|
<div class="cms-quiz-header">
|
|
<h3><i class="fas fa-clipboard-question"></i> Quiz Questions</h3>
|
|
<button id="btn-lh-add-question" class="btn-sm btn-ghost"><i class="fas fa-plus"></i> Add Question</button>
|
|
</div>
|
|
<div id="lh-cms-questions" class="cms-questions-list"></div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|