Add isolated legacy notes route and tasks view with resilient task APIs
This commit is contained in:
+171
-18
@@ -93,11 +93,17 @@
|
||||
<body class="min-h-screen bg-gradient-to-b from-slate-950 via-slate-950 to-slate-900 text-slate-100">
|
||||
<main class="mx-auto max-w-6xl px-6 py-10">
|
||||
<header class="panel mb-8">
|
||||
<div class="flex items-center gap-4">
|
||||
<img src="/images/prism.png" alt="Prism Notes" class="h-14 w-14 rounded-xl object-cover ring-1 ring-slate-600/60" />
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-[0.3em] text-cyan-400">Prism Notes</p>
|
||||
<h1 class="mt-1 text-3xl font-semibold tracking-tight">Fresh rebuild</h1>
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<img src="/images/prism.png" alt="Prism Notes" class="h-14 w-14 rounded-xl object-cover ring-1 ring-slate-600/60" />
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-[0.3em] text-cyan-400">Prism Notes</p>
|
||||
<h1 class="mt-1 text-3xl font-semibold tracking-tight">Fresh rebuild</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<a href="/notes" class="btn btn-secondary">Open Legacy Notes</a>
|
||||
<a href="/tasks" class="btn btn-secondary">Open Tasks View</a>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-3 text-sm text-slate-300">Clean baseline UI with PocketBase auth, OneNote delegated auth, note submit, and OneNote proof actions.</p>
|
||||
@@ -167,13 +173,14 @@
|
||||
<div>
|
||||
<label class="text-sm font-medium text-slate-300">Sync Mode</label>
|
||||
<select id="syncModeSelect" class="field mt-1">
|
||||
<option value="add">add new note block</option>
|
||||
<option value="edit">edit existing note block</option>
|
||||
<option value="add_to" selected>add to same box (default)</option>
|
||||
<option value="add_separate">add as separate box</option>
|
||||
<option value="edit">edit note</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-slate-300">Existing Note ID (edit mode)</label>
|
||||
<input id="syncEditNoteId" class="field mt-1" placeholder="Paste noteId to replace on selected page" />
|
||||
<label class="text-sm font-medium text-slate-300">Existing Note ID (add to/edit)</label>
|
||||
<input id="syncEditNoteId" class="field mt-1" placeholder="Pick from page list or paste noteId" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
@@ -188,6 +195,14 @@
|
||||
<label class="text-sm font-medium text-slate-300">OneNote Page (optional)</label>
|
||||
<select id="syncPageSelect" class="field mt-1"><option value="">— create new page in selected section —</option></select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-slate-300">Separate Insert Anchor (optional)</label>
|
||||
<input id="syncObjectAnchor" class="field mt-1" placeholder="Paste OneNote paragraph/object link or object-id token" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-slate-300">Notes on Selected Page</label>
|
||||
<select id="syncExistingNoteSelect" class="field mt-1"><option value="">— select a page first —</option></select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-slate-300">New Page Title (used when page is not selected)</label>
|
||||
<input id="syncPageTitle" class="field mt-1" placeholder="Optional (defaults to note title)" />
|
||||
@@ -253,8 +268,11 @@
|
||||
const syncEditNoteId = document.getElementById('syncEditNoteId');
|
||||
const syncSectionSelect = document.getElementById('syncSectionSelect');
|
||||
const syncPageSelect = document.getElementById('syncPageSelect');
|
||||
const syncObjectAnchor = document.getElementById('syncObjectAnchor');
|
||||
const syncExistingNoteSelect = document.getElementById('syncExistingNoteSelect');
|
||||
const syncPageTitle = document.getElementById('syncPageTitle');
|
||||
let resolvedSiteId = null;
|
||||
let syncPageNoteBlocks = [];
|
||||
|
||||
const noteTitle = document.getElementById('noteTitle');
|
||||
const noteJobNumber = document.getElementById('noteJobNumber');
|
||||
@@ -514,6 +532,46 @@
|
||||
};
|
||||
}
|
||||
|
||||
function isExistingNoteMode() {
|
||||
const mode = String(syncModeSelect.value || '').trim();
|
||||
return mode === 'add_to' || mode === 'edit';
|
||||
}
|
||||
|
||||
function parseObjectAnchor(rawValue) {
|
||||
const raw = String(rawValue || '').trim();
|
||||
if (!raw) return '';
|
||||
|
||||
try {
|
||||
const decoded = decodeURIComponent(raw);
|
||||
const m = decoded.match(/object-id=\{([0-9A-Fa-f-]{36})\}&([0-9A-Fa-f]+)/i);
|
||||
if (m) {
|
||||
return `{${m[1]}}&${m[2]}`;
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
|
||||
const direct = raw.match(/\{([0-9A-Fa-f-]{36})\}(?:&([0-9A-Fa-f]+))?/);
|
||||
if (direct) {
|
||||
return direct[2] ? `{${direct[1]}}&${direct[2]}` : `{${direct[1]}}`;
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
function getSelectedPageNote() {
|
||||
const noteId = syncExistingNoteSelect.value.trim();
|
||||
return syncPageNoteBlocks.find((block) => block.noteId === noteId) || null;
|
||||
}
|
||||
|
||||
function applySyncModeUi() {
|
||||
const needsExisting = isExistingNoteMode();
|
||||
syncEditNoteId.disabled = !needsExisting;
|
||||
syncExistingNoteSelect.disabled = !needsExisting;
|
||||
if (!needsExisting) {
|
||||
syncEditNoteId.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSyncTargets() {
|
||||
syncTargetsStatus.textContent = 'Loading sections…';
|
||||
if (!hasUsableToken()) {
|
||||
@@ -560,6 +618,8 @@
|
||||
async function loadSyncPagesForSelectedSection() {
|
||||
const sectionId = syncSectionSelect.value.trim();
|
||||
syncPageSelect.innerHTML = '<option value="">— create new page in selected section —</option>';
|
||||
syncExistingNoteSelect.innerHTML = '<option value="">— select a page first —</option>';
|
||||
syncPageNoteBlocks = [];
|
||||
|
||||
if (!sectionId) {
|
||||
syncTargetsStatus.textContent = 'Select a section first';
|
||||
@@ -589,6 +649,58 @@
|
||||
writeOutput({ message: `Loaded ${pages.length} page(s)`, sectionId, pages });
|
||||
}
|
||||
|
||||
async function loadPageNoteBlocksForSelectedPage() {
|
||||
const pageId = syncPageSelect.value.trim();
|
||||
syncPageNoteBlocks = [];
|
||||
syncExistingNoteSelect.innerHTML = '';
|
||||
|
||||
if (!pageId) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = '';
|
||||
opt.textContent = '— select a page first —';
|
||||
syncExistingNoteSelect.appendChild(opt);
|
||||
return;
|
||||
}
|
||||
|
||||
syncTargetsStatus.textContent = 'Loading page note blocks…';
|
||||
const resp = await fetch(`/api/onenote/page-note-blocks?pageId=${encodeURIComponent(pageId)}`, {
|
||||
headers: { Authorization: `Bearer ${delegatedAccessToken}` },
|
||||
});
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (!resp.ok || !data?.success) {
|
||||
const msg = data?.message || JSON.stringify(data?.error || data);
|
||||
syncTargetsStatus.textContent = `⚠ ${msg}`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
syncPageNoteBlocks = Array.isArray(data.noteBlocks) ? data.noteBlocks : [];
|
||||
if (!syncPageNoteBlocks.length) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = '';
|
||||
opt.textContent = '— no notes found on this page —';
|
||||
syncExistingNoteSelect.appendChild(opt);
|
||||
syncTargetsStatus.textContent = '✓ 0 notes found';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const block of syncPageNoteBlocks) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = block.noteId;
|
||||
const preview = String(block.preview || '').replace(/\s+/g, ' ').trim().slice(0, 70);
|
||||
const syncedLabel = block.syncedAt ? ` @ ${block.syncedAt}` : '';
|
||||
opt.textContent = `${block.noteId}${syncedLabel} — ${preview || '(empty)'}`;
|
||||
syncExistingNoteSelect.appendChild(opt);
|
||||
}
|
||||
|
||||
syncExistingNoteSelect.value = syncPageNoteBlocks[0].noteId || '';
|
||||
if (isExistingNoteMode()) {
|
||||
syncEditNoteId.value = syncExistingNoteSelect.value;
|
||||
}
|
||||
|
||||
syncTargetsStatus.textContent = `✓ ${syncPageNoteBlocks.length} note(s) loaded`;
|
||||
writeOutput({ message: `Loaded ${syncPageNoteBlocks.length} note(s)`, pageId, notes: syncPageNoteBlocks });
|
||||
}
|
||||
|
||||
async function submitNoteToPrismNotes() {
|
||||
const client = await ensurePocketBaseClient();
|
||||
if (!client.authStore.isValid || !client.authStore.token) {
|
||||
@@ -619,23 +731,36 @@
|
||||
throw new Error('OneNote connection required');
|
||||
}
|
||||
|
||||
const syncMode = syncModeSelect.value === 'edit' ? 'edit' : 'add';
|
||||
const requestedMode = String(syncModeSelect.value || '').trim();
|
||||
const syncMode = requestedMode === 'add_to' ? 'add_to' : requestedMode === 'edit' ? 'edit' : 'add_separate';
|
||||
const sectionId = syncSectionSelect.value.trim();
|
||||
const pageId = syncPageSelect.value.trim();
|
||||
const pageTitle = syncPageTitle.value.trim();
|
||||
const targetNoteId = syncEditNoteId.value.trim();
|
||||
const selectedNoteId = syncExistingNoteSelect.value.trim();
|
||||
const selectedPageNote = getSelectedPageNote();
|
||||
const manualAnchor = parseObjectAnchor(syncObjectAnchor.value);
|
||||
const targetNoteId = syncMode === 'add_to' || syncMode === 'edit'
|
||||
? (syncEditNoteId.value.trim() || selectedNoteId)
|
||||
: '';
|
||||
const targetObjectId = syncMode === 'add_separate'
|
||||
? (manualAnchor || syncPageNoteBlocks.at(-1)?.targetId || '')
|
||||
: (selectedPageNote?.targetId || '');
|
||||
|
||||
if (!sectionId) {
|
||||
throw new Error('Load sync targets and select a section first');
|
||||
}
|
||||
if (syncMode === 'edit' && !pageId) {
|
||||
throw new Error('Edit mode requires selecting an existing OneNote page');
|
||||
if ((syncMode === 'add_to' || syncMode === 'edit') && !pageId) {
|
||||
throw new Error('Add/Edit mode requires selecting an existing OneNote page');
|
||||
}
|
||||
if (syncMode === 'edit' && !targetNoteId) {
|
||||
throw new Error('Edit mode requires Existing Note ID');
|
||||
if ((syncMode === 'add_to' || syncMode === 'edit') && !targetNoteId) {
|
||||
throw new Error('Add/Edit mode requires Existing Note ID');
|
||||
}
|
||||
|
||||
const payload = buildNotePayload({ noteId: syncMode === 'edit' ? targetNoteId : '' });
|
||||
if (syncMode === 'add_to' || syncMode === 'edit') {
|
||||
syncEditNoteId.value = targetNoteId;
|
||||
}
|
||||
|
||||
const payload = buildNotePayload({ noteId: syncMode === 'add_separate' ? '' : targetNoteId });
|
||||
const resp = await fetch('/api/onenote/append', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -650,6 +775,7 @@
|
||||
pageId,
|
||||
pageTitle,
|
||||
targetNoteId,
|
||||
targetObjectId,
|
||||
}),
|
||||
});
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
@@ -657,14 +783,19 @@
|
||||
throw new Error(data?.message || 'OneNote sync failed');
|
||||
}
|
||||
|
||||
if (syncMode === 'add' && data?.pageId && !pageId) {
|
||||
if (syncMode === 'add_separate' && data?.pageId && !pageId) {
|
||||
const createdOpt = document.createElement('option');
|
||||
createdOpt.value = data.pageId;
|
||||
createdOpt.textContent = `${pageTitle || payload.title} — ${data.pageId}`;
|
||||
syncPageSelect.appendChild(createdOpt);
|
||||
syncPageSelect.value = data.pageId;
|
||||
await loadPageNoteBlocksForSelectedPage();
|
||||
} else if (pageId) {
|
||||
await loadPageNoteBlocksForSelectedPage();
|
||||
}
|
||||
if (syncMode === 'add_to' || syncMode === 'edit') {
|
||||
syncEditNoteId.value = payload.noteId;
|
||||
}
|
||||
syncEditNoteId.value = payload.noteId;
|
||||
|
||||
writeOutput({ message: `OneNote sync complete (${data?.mode || syncMode})`, response: data, payload });
|
||||
}
|
||||
@@ -730,10 +861,32 @@
|
||||
}
|
||||
});
|
||||
|
||||
syncPageSelect.addEventListener('change', async () => {
|
||||
try {
|
||||
await loadPageNoteBlocksForSelectedPage();
|
||||
} catch (error) {
|
||||
writeOutput({ error: error?.message || String(error) });
|
||||
}
|
||||
});
|
||||
|
||||
syncExistingNoteSelect.addEventListener('change', () => {
|
||||
if (isExistingNoteMode()) {
|
||||
syncEditNoteId.value = syncExistingNoteSelect.value.trim();
|
||||
}
|
||||
});
|
||||
|
||||
syncModeSelect.addEventListener('change', () => {
|
||||
applySyncModeUi();
|
||||
if (isExistingNoteMode() && syncExistingNoteSelect.value.trim()) {
|
||||
syncEditNoteId.value = syncExistingNoteSelect.value.trim();
|
||||
}
|
||||
});
|
||||
|
||||
ensurePocketBaseClient()
|
||||
.then(() => updatePbStatus())
|
||||
.catch((error) => writeOutput({ error: error?.message || String(error) }));
|
||||
updateOneNoteStatus();
|
||||
applySyncModeUi();
|
||||
loadHealth();
|
||||
loadGraphStatus();
|
||||
handleMsalRedirect().catch((error) => writeOutput({ error: error?.message || String(error) }));
|
||||
|
||||
Reference in New Issue
Block a user