Upgrade OneNote sync with section/page targeting and edit mode
- Add GET /api/onenote/section-pages endpoint for listing pages in a section - Extend /api/onenote/append with syncMode add/edit support - Support editing an existing note block by noteId on a selected page - Keep add mode for appending to selected page or creating new page in section - Add Note + Sync UI controls for sync mode, section/page target, existing noteId, and optional new page title - Wire frontend flow to load sync targets and pages, then sync with selected mode
This commit is contained in:
+161
-35
@@ -163,6 +163,35 @@
|
|||||||
<label class="text-sm font-medium text-slate-300">Body</label>
|
<label class="text-sm font-medium text-slate-300">Body</label>
|
||||||
<textarea id="noteBody" rows="8" class="field mt-1" placeholder="Write note content..."></textarea>
|
<textarea id="noteBody" rows="8" class="field mt-1" placeholder="Write note content..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid gap-4 md:grid-cols-2">
|
||||||
|
<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>
|
||||||
|
</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" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap items-center gap-3">
|
||||||
|
<button id="loadSyncTargetsBtn" class="btn btn-secondary">Load sync targets</button>
|
||||||
|
<span id="syncTargetsStatus" class="text-xs text-slate-400"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-sm font-medium text-slate-300">OneNote Section (sync target)</label>
|
||||||
|
<select id="syncSectionSelect" class="field mt-1"><option value="">— click Load sync targets —</option></select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<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">New Page Title (used when page is not selected)</label>
|
||||||
|
<input id="syncPageTitle" class="field mt-1" placeholder="Optional (defaults to note title)" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex flex-wrap gap-3">
|
<div class="mt-4 flex flex-wrap gap-3">
|
||||||
<button id="submitNoteBtn" class="btn btn-accent-indigo">Submit to Prism Notes</button>
|
<button id="submitNoteBtn" class="btn btn-accent-indigo">Submit to Prism Notes</button>
|
||||||
@@ -219,6 +248,12 @@
|
|||||||
const oneNoteAuthStatus = document.getElementById('oneNoteAuthStatus');
|
const oneNoteAuthStatus = document.getElementById('oneNoteAuthStatus');
|
||||||
const resolveStatus = document.getElementById('resolveStatus');
|
const resolveStatus = document.getElementById('resolveStatus');
|
||||||
const sectionSelect = document.getElementById('sectionSelect');
|
const sectionSelect = document.getElementById('sectionSelect');
|
||||||
|
const syncTargetsStatus = document.getElementById('syncTargetsStatus');
|
||||||
|
const syncModeSelect = document.getElementById('syncModeSelect');
|
||||||
|
const syncEditNoteId = document.getElementById('syncEditNoteId');
|
||||||
|
const syncSectionSelect = document.getElementById('syncSectionSelect');
|
||||||
|
const syncPageSelect = document.getElementById('syncPageSelect');
|
||||||
|
const syncPageTitle = document.getElementById('syncPageTitle');
|
||||||
let resolvedSiteId = null;
|
let resolvedSiteId = null;
|
||||||
|
|
||||||
const noteTitle = document.getElementById('noteTitle');
|
const noteTitle = document.getElementById('noteTitle');
|
||||||
@@ -230,6 +265,7 @@
|
|||||||
const proofBody = document.getElementById('proofBody');
|
const proofBody = document.getElementById('proofBody');
|
||||||
|
|
||||||
proofTitle.value = `Prism Notes Proof ${new Date().toISOString()}`;
|
proofTitle.value = `Prism Notes Proof ${new Date().toISOString()}`;
|
||||||
|
syncPageTitle.value = `Prism Notes Sync ${new Date().toISOString().slice(0, 10)}`;
|
||||||
proofBody.value = [
|
proofBody.value = [
|
||||||
'Proof that OneNote can be updated programmatically from outside OneNote.',
|
'Proof that OneNote can be updated programmatically from outside OneNote.',
|
||||||
`Created: ${new Date().toISOString()}`,
|
`Created: ${new Date().toISOString()}`,
|
||||||
@@ -461,14 +497,15 @@
|
|||||||
writeOutput({ message: 'PocketBase session cleared.' });
|
writeOutput({ message: 'PocketBase session cleared.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildNotePayload() {
|
function buildNotePayload(options = {}) {
|
||||||
const bodyPlain = noteBody.value.trim();
|
const bodyPlain = noteBody.value.trim();
|
||||||
if (!bodyPlain) {
|
if (!bodyPlain) {
|
||||||
throw new Error('Note body is required');
|
throw new Error('Note body is required');
|
||||||
}
|
}
|
||||||
const title = noteTitle.value.trim() || 'Untitled';
|
const title = noteTitle.value.trim() || 'Untitled';
|
||||||
|
const requestedNoteId = String(options.noteId || '').trim();
|
||||||
return {
|
return {
|
||||||
noteId: globalThis.crypto?.randomUUID?.() || `note-${Date.now()}`,
|
noteId: requestedNoteId || globalThis.crypto?.randomUUID?.() || `note-${Date.now()}`,
|
||||||
title,
|
title,
|
||||||
bodyPlain,
|
bodyPlain,
|
||||||
noteType: noteType.value || 'personal',
|
noteType: noteType.value || 'personal',
|
||||||
@@ -477,6 +514,81 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadSyncTargets() {
|
||||||
|
syncTargetsStatus.textContent = 'Loading sections…';
|
||||||
|
if (!hasUsableToken()) {
|
||||||
|
syncTargetsStatus.textContent = '⚠ Connect OneNote first';
|
||||||
|
throw new Error('OneNote connection required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const resp = await fetch('/api/onenote/site-sections', {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedSiteId = data.siteId;
|
||||||
|
const sections = Array.isArray(data.sections) ? data.sections : [];
|
||||||
|
|
||||||
|
syncSectionSelect.innerHTML = '';
|
||||||
|
sectionSelect.innerHTML = '';
|
||||||
|
for (const section of sections) {
|
||||||
|
const label = `${section.displayName || section.id} — ${section.id}`;
|
||||||
|
|
||||||
|
const syncOpt = document.createElement('option');
|
||||||
|
syncOpt.value = section.id;
|
||||||
|
syncOpt.textContent = label;
|
||||||
|
syncSectionSelect.appendChild(syncOpt);
|
||||||
|
|
||||||
|
const proofOpt = document.createElement('option');
|
||||||
|
proofOpt.value = section.id;
|
||||||
|
proofOpt.textContent = label;
|
||||||
|
sectionSelect.appendChild(proofOpt);
|
||||||
|
}
|
||||||
|
|
||||||
|
syncTargetsStatus.textContent = `✓ ${sections.length} section(s) loaded`;
|
||||||
|
resolveStatus.textContent = `✓ ${sections.length} section(s) loaded`;
|
||||||
|
|
||||||
|
await loadSyncPagesForSelectedSection();
|
||||||
|
writeOutput({ message: `Loaded ${sections.length} section(s)`, siteId: resolvedSiteId, sections });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSyncPagesForSelectedSection() {
|
||||||
|
const sectionId = syncSectionSelect.value.trim();
|
||||||
|
syncPageSelect.innerHTML = '<option value="">— create new page in selected section —</option>';
|
||||||
|
|
||||||
|
if (!sectionId) {
|
||||||
|
syncTargetsStatus.textContent = 'Select a section first';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
syncTargetsStatus.textContent = 'Loading pages…';
|
||||||
|
const resp = await fetch(`/api/onenote/section-pages?sectionId=${encodeURIComponent(sectionId)}`, {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = Array.isArray(data.pages) ? data.pages : [];
|
||||||
|
for (const page of pages) {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = page.id;
|
||||||
|
opt.textContent = `${page.title || '(untitled)'} — ${page.id}`;
|
||||||
|
syncPageSelect.appendChild(opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
syncTargetsStatus.textContent = `✓ ${pages.length} page(s) loaded`;
|
||||||
|
writeOutput({ message: `Loaded ${pages.length} page(s)`, sectionId, pages });
|
||||||
|
}
|
||||||
|
|
||||||
async function submitNoteToPrismNotes() {
|
async function submitNoteToPrismNotes() {
|
||||||
const client = await ensurePocketBaseClient();
|
const client = await ensurePocketBaseClient();
|
||||||
if (!client.authStore.isValid || !client.authStore.token) {
|
if (!client.authStore.isValid || !client.authStore.token) {
|
||||||
@@ -507,7 +619,23 @@
|
|||||||
throw new Error('OneNote connection required');
|
throw new Error('OneNote connection required');
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = buildNotePayload();
|
const syncMode = syncModeSelect.value === 'edit' ? 'edit' : 'add';
|
||||||
|
const sectionId = syncSectionSelect.value.trim();
|
||||||
|
const pageId = syncPageSelect.value.trim();
|
||||||
|
const pageTitle = syncPageTitle.value.trim();
|
||||||
|
const targetNoteId = syncEditNoteId.value.trim();
|
||||||
|
|
||||||
|
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 === 'edit' && !targetNoteId) {
|
||||||
|
throw new Error('Edit mode requires Existing Note ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = buildNotePayload({ noteId: syncMode === 'edit' ? targetNoteId : '' });
|
||||||
const resp = await fetch('/api/onenote/append', {
|
const resp = await fetch('/api/onenote/append', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -515,46 +643,35 @@
|
|||||||
Authorization: `Bearer ${delegatedAccessToken}`,
|
Authorization: `Bearer ${delegatedAccessToken}`,
|
||||||
'x-pb-token': client.authStore.token,
|
'x-pb-token': client.authStore.token,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify({
|
||||||
|
...payload,
|
||||||
|
syncMode,
|
||||||
|
sectionId,
|
||||||
|
pageId,
|
||||||
|
pageTitle,
|
||||||
|
targetNoteId,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
const data = await resp.json().catch(() => ({}));
|
const data = await resp.json().catch(() => ({}));
|
||||||
if (!resp.ok || !data?.success) {
|
if (!resp.ok || !data?.success) {
|
||||||
throw new Error(data?.message || 'OneNote sync failed');
|
throw new Error(data?.message || 'OneNote sync failed');
|
||||||
}
|
}
|
||||||
writeOutput({ message: 'Synced to configured OneNote target', response: data, payload });
|
|
||||||
|
if (syncMode === 'add' && 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;
|
||||||
|
}
|
||||||
|
syncEditNoteId.value = payload.noteId;
|
||||||
|
|
||||||
|
writeOutput({ message: `OneNote sync complete (${data?.mode || syncMode})`, response: data, payload });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveSiteSections() {
|
async function resolveSiteSections() {
|
||||||
resolveStatus.textContent = 'Resolving…';
|
resolveStatus.textContent = 'Resolving…';
|
||||||
if (!hasUsableToken()) {
|
await loadSyncTargets();
|
||||||
resolveStatus.textContent = '⚠ Connect OneNote first (section 2)';
|
|
||||||
throw new Error('OneNote connection required — click Connect OneNote in section 2 first');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const resp = await fetch('/api/onenote/site-sections', {
|
|
||||||
headers: { Authorization: `Bearer ${delegatedAccessToken}` },
|
|
||||||
});
|
|
||||||
const data = await resp.json().catch(() => ({}));
|
|
||||||
if (!resp.ok || !data?.success) {
|
|
||||||
const msg = data?.message || JSON.stringify(data?.error || data);
|
|
||||||
resolveStatus.textContent = `⚠ ${msg}`;
|
|
||||||
throw new Error(msg);
|
|
||||||
}
|
|
||||||
resolvedSiteId = data.siteId;
|
|
||||||
const sections = Array.isArray(data.sections) ? data.sections : [];
|
|
||||||
sectionSelect.innerHTML = '';
|
|
||||||
for (const s of sections) {
|
|
||||||
const opt = document.createElement('option');
|
|
||||||
opt.value = s.id;
|
|
||||||
opt.textContent = `${s.displayName || s.id} — ${s.id}`;
|
|
||||||
sectionSelect.appendChild(opt);
|
|
||||||
}
|
|
||||||
resolveStatus.textContent = `✓ ${sections.length} section(s) loaded`;
|
|
||||||
writeOutput({ message: `Resolved ${sections.length} section(s)`, siteId: resolvedSiteId, sections });
|
|
||||||
} catch (e) {
|
|
||||||
if (!resolveStatus.textContent.startsWith('⚠')) resolveStatus.textContent = `⚠ ${e.message}`;
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createProofPage() {
|
async function createProofPage() {
|
||||||
@@ -599,11 +716,20 @@
|
|||||||
bind('logoutBtn', logoutPocketBase);
|
bind('logoutBtn', logoutPocketBase);
|
||||||
bind('connectOneNoteBtn', connectOneNoteInteractive);
|
bind('connectOneNoteBtn', connectOneNoteInteractive);
|
||||||
bind('refreshOneNoteTokenBtn', refreshOneNoteToken);
|
bind('refreshOneNoteTokenBtn', refreshOneNoteToken);
|
||||||
bind('submitNoteBtn', submitNoteToPrismNotes);
|
bind('loadSyncTargetsBtn', loadSyncTargets);
|
||||||
|
bind('submitNoteBtn', submitNoteToPrismNotes);
|
||||||
bind('syncOneNoteBtn', syncCurrentNoteToOneNote);
|
bind('syncOneNoteBtn', syncCurrentNoteToOneNote);
|
||||||
bind('resolveSectionsBtn', resolveSiteSections);
|
bind('resolveSectionsBtn', resolveSiteSections);
|
||||||
bind('createProofPageBtn', createProofPage);
|
bind('createProofPageBtn', createProofPage);
|
||||||
|
|
||||||
|
syncSectionSelect.addEventListener('change', async () => {
|
||||||
|
try {
|
||||||
|
await loadSyncPagesForSelectedSection();
|
||||||
|
} catch (error) {
|
||||||
|
writeOutput({ error: error?.message || String(error) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ensurePocketBaseClient()
|
ensurePocketBaseClient()
|
||||||
.then(() => updatePbStatus())
|
.then(() => updatePbStatus())
|
||||||
.catch((error) => writeOutput({ error: error?.message || String(error) }));
|
.catch((error) => writeOutput({ error: error?.message || String(error) }));
|
||||||
|
|||||||
@@ -173,15 +173,43 @@ app.get('/api/onenote/site-sections', async (c) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /api/onenote/section-pages?sectionId=...
|
||||||
|
// Requires: Authorization: Bearer <delegated token>
|
||||||
|
app.get('/api/onenote/section-pages', async (c) => {
|
||||||
|
try {
|
||||||
|
const delegatedToken = getBearerToken(c.req.header('authorization'));
|
||||||
|
if (!delegatedToken) return c.json({ success: false, message: 'Missing delegated token' }, 401);
|
||||||
|
|
||||||
|
const sectionId = String(c.req.query('sectionId') || '').trim();
|
||||||
|
if (!sectionId) return c.json({ success: false, message: 'Missing sectionId' }, 400);
|
||||||
|
|
||||||
|
const siteId = await resolveSiteId(delegatedToken);
|
||||||
|
const resp = await fetch(
|
||||||
|
`https://graph.microsoft.com/v1.0/sites/${siteId}/onenote/sections/${encodeURIComponent(sectionId)}/pages?$top=100&$select=id,title,createdDateTime,lastModifiedDateTime`,
|
||||||
|
{ headers: { Authorization: `Bearer ${delegatedToken}` } },
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = await resp.json().catch(() => ({}));
|
||||||
|
if (!resp.ok) return c.json({ success: false, siteId, sectionId, error: data }, resp.status as any);
|
||||||
|
return c.json({ success: true, siteId, sectionId, pages: data?.value || [] });
|
||||||
|
} catch (e) {
|
||||||
|
return c.json({ success: false, message: (e as Error)?.message }, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// POST /api/onenote/append
|
// POST /api/onenote/append
|
||||||
// Requires: x-pb-token (PocketBase session) + Authorization: Bearer <delegated Graph token>
|
// Requires: x-pb-token (PocketBase session) + Authorization: Bearer <delegated Graph token>
|
||||||
// Body: { noteId, title, bodyPlain, noteType?, userEmail?, jobNumber?, sectionId? }
|
// Body: {
|
||||||
|
// noteId, title, bodyPlain, noteType?, userEmail?, jobNumber?,
|
||||||
|
// sectionId?, pageId?, pageTitle?, syncMode?: 'add'|'edit', targetNoteId?
|
||||||
|
// }
|
||||||
//
|
//
|
||||||
// Strategy:
|
// Strategy:
|
||||||
// 1. Resolve site ID via GET /sites/{host}:{path}
|
// 1. Resolve site ID via GET /sites/{host}:{path}
|
||||||
// 2. Try PATCH sites/{siteId}/onenote/pages/{pageId}/content → append to known page
|
// 2. If syncMode=edit: PATCH replace note block in known page by noteId
|
||||||
// 3. If that fails, POST sites/{siteId}/onenote/sections/{sectionId}/pages → create new page
|
// 3. If syncMode=add and pageId: PATCH append to known page
|
||||||
|
// 4. If syncMode=add and no pageId: POST create new page in known section
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
app.post('/api/onenote/append', async (c) => {
|
app.post('/api/onenote/append', async (c) => {
|
||||||
try {
|
try {
|
||||||
@@ -206,6 +234,9 @@ app.post('/api/onenote/append', async (c) => {
|
|||||||
const noteType = String(body?.noteType || 'personal').trim();
|
const noteType = String(body?.noteType || 'personal').trim();
|
||||||
const userEmail = String(body?.userEmail || 'unknown').trim();
|
const userEmail = String(body?.userEmail || 'unknown').trim();
|
||||||
const jobNumber = String(body?.jobNumber || '').trim();
|
const jobNumber = String(body?.jobNumber || '').trim();
|
||||||
|
const pageTitle = String(body?.pageTitle || title || ONENOTE_TARGET.pageTitle || 'Prism Notes').trim();
|
||||||
|
const syncMode = String(body?.syncMode || 'add').trim().toLowerCase() === 'edit' ? 'edit' : 'add';
|
||||||
|
const targetNoteId = String(body?.targetNoteId || noteId || '').trim();
|
||||||
|
|
||||||
if (!noteId) return c.json({ success: false, message: 'Missing noteId' }, 400);
|
if (!noteId) return c.json({ success: false, message: 'Missing noteId' }, 400);
|
||||||
if (!bodyPlain) return c.json({ success: false, message: 'Missing bodyPlain' }, 400);
|
if (!bodyPlain) return c.json({ success: false, message: 'Missing bodyPlain' }, 400);
|
||||||
@@ -225,7 +256,7 @@ app.post('/api/onenote/append', async (c) => {
|
|||||||
const pageHtml = [
|
const pageHtml = [
|
||||||
'<!DOCTYPE html>',
|
'<!DOCTYPE html>',
|
||||||
'<html>',
|
'<html>',
|
||||||
`<head><title>${escapeHtml(title)}</title></head>`,
|
`<head><title>${escapeHtml(pageTitle)}</title></head>`,
|
||||||
`<body>${appendHtml}</body>`,
|
`<body>${appendHtml}</body>`,
|
||||||
'</html>',
|
'</html>',
|
||||||
].join('');
|
].join('');
|
||||||
@@ -233,6 +264,12 @@ app.post('/api/onenote/append', async (c) => {
|
|||||||
const sectionId = String(body?.sectionId || ONENOTE_TARGET.sectionId || '').trim();
|
const sectionId = String(body?.sectionId || ONENOTE_TARGET.sectionId || '').trim();
|
||||||
const pageId = String(body?.pageId || ONENOTE_TARGET.pageId || '').trim();
|
const pageId = String(body?.pageId || ONENOTE_TARGET.pageId || '').trim();
|
||||||
if (!sectionId) return c.json({ success: false, message: 'No sectionId — run GET /api/onenote/site-sections first to retrieve valid IDs' }, 400);
|
if (!sectionId) return c.json({ success: false, message: 'No sectionId — run GET /api/onenote/site-sections first to retrieve valid IDs' }, 400);
|
||||||
|
if (syncMode === 'edit' && !pageId) {
|
||||||
|
return c.json({ success: false, message: 'Edit mode requires pageId' }, 400);
|
||||||
|
}
|
||||||
|
if (syncMode === 'edit' && !targetNoteId) {
|
||||||
|
return c.json({ success: false, message: 'Edit mode requires targetNoteId or noteId' }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
const authHeader = { Authorization: `Bearer ${delegatedToken}` };
|
const authHeader = { Authorization: `Bearer ${delegatedToken}` };
|
||||||
|
|
||||||
@@ -240,6 +277,40 @@ app.post('/api/onenote/append', async (c) => {
|
|||||||
const siteId = await resolveSiteId(delegatedToken);
|
const siteId = await resolveSiteId(delegatedToken);
|
||||||
const base = `https://graph.microsoft.com/v1.0/sites/${siteId}/onenote`;
|
const base = `https://graph.microsoft.com/v1.0/sites/${siteId}/onenote`;
|
||||||
|
|
||||||
|
if (syncMode === 'edit') {
|
||||||
|
const selectorNoteId = targetNoteId.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||||
|
const replaceResp = await fetch(
|
||||||
|
`${base}/pages/${pageId}/content`,
|
||||||
|
{
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { ...authHeader, 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify([
|
||||||
|
{
|
||||||
|
target: `div[data-prism-notes-note-id=\"${selectorNoteId}\"]`,
|
||||||
|
action: 'replace',
|
||||||
|
content: appendHtml,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (replaceResp.ok) {
|
||||||
|
console.log('onenote_edit_success', { mode: 'edit', noteId, targetNoteId, pageId, siteId });
|
||||||
|
return c.json({ success: true, mode: 'edited', pageId, noteId: targetNoteId, syncedAt: ts });
|
||||||
|
}
|
||||||
|
|
||||||
|
const replaceErr = await replaceResp.text();
|
||||||
|
console.warn('onenote_edit_failed', { status: replaceResp.status, error: replaceErr, pageId, targetNoteId });
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
message: `OneNote edit failed (${replaceResp.status})`,
|
||||||
|
details: { edit: replaceErr, pageId, targetNoteId, siteId },
|
||||||
|
},
|
||||||
|
502,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- 1: try append to known page (if we have a valid pageId) ---- */
|
/* ---- 1: try append to known page (if we have a valid pageId) ---- */
|
||||||
if (pageId) {
|
if (pageId) {
|
||||||
const patchResp = await fetch(
|
const patchResp = await fetch(
|
||||||
|
|||||||
Reference in New Issue
Block a user