feat(library-desk): fetch page ID via GraphQL in wikijs-integration.js

Use pages.singleByPath(path, locale) GraphQL query to get page ID
during initialization instead of searching through page list.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-10 21:30:18 +01:00
co-authored by Claude Opus 4.5
parent d582f27098
commit ed7792ef98
@@ -28,6 +28,10 @@
}
}
// Store page ID globally once fetched
let CACHED_PAGE_ID = null;
let CACHED_PAGE_PATH = null;
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
@@ -39,11 +43,11 @@
log('Initializing Library Desk integration...');
log('Library Desk URL:', CONFIG.libraryDeskUrl);
// Wait for Wiki.js to fully render
// Wait for page to be ready, then add buttons
setTimeout(addButtons, 1500);
}
function addButtons() {
async function addButtons() {
const pagePath = getPagePath();
if (!pagePath) {
log('Skipping buttons - not on a content page');
@@ -58,6 +62,11 @@
return;
}
// Fetch and cache page ID during initialization
CACHED_PAGE_PATH = pagePath;
CACHED_PAGE_ID = await getPageIdFromPath(pagePath);
log('Cached page ID for this session:', CACHED_PAGE_ID);
if (CONFIG.buttonPosition === 'floating') {
addFloatingButtons(pagePath);
} else {
@@ -210,33 +219,15 @@
}
try {
log('Fetching page list...');
const listUrl = CONFIG.libraryDeskUrl + '/wiki/pages?user=' + CONFIG.user + '&limit=200';
const listResponse = await fetch(listUrl, {
headers: { 'Authorization': 'Bearer ' + CONFIG.apiKey }
});
if (!listResponse.ok) {
throw new Error('Failed to fetch page list: ' + listResponse.status);
// Use cached page ID from initialization
if (!CACHED_PAGE_ID) {
throw new Error('Page ID not available - was not fetched during initialization');
}
const pageList = await listResponse.json();
log('Fetched', pageList.pages.length, 'pages');
log('Re-indexing page', CACHED_PAGE_ID, '...');
// Find matching page
const page = findMatchingPage(pageList.pages, pagePath);
if (!page) {
log('Available pages:', pageList.pages.map(p => p.path));
throw new Error('Page not found in Library Desk. Check console for available pages.');
}
log('Matched page:', page.id, page.path, page.title);
log('Re-indexing...');
// Re-index
const response = await fetch(CONFIG.libraryDeskUrl + '/ingest/batch', {
// Re-index directly
const response = await fetch(CONFIG.libraryDeskUrl + '/ingest/page', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + CONFIG.apiKey,
@@ -244,15 +235,14 @@
},
body: JSON.stringify({
user: CONFIG.user,
page_ids: [page.id]
page_id: CACHED_PAGE_ID,
force_refresh: true
})
});
const result = await response.json();
if (response.ok && result.successful > 0) {
const pageResult = result.results[0];
if (response.ok && result.success) {
if (isFloating) {
button.innerHTML = '✓ Done!';
button.style.background = '#4caf50';
@@ -261,11 +251,11 @@
icon.style.color = '#4caf50';
}
const message = 'Re-indexed: ' + page.title + '\n\n' +
'✓ Vectors: ' + pageResult.vector_chunks_created + ' chunks\n' +
'✓ Entities: ' + pageResult.graph_entities_extracted + '\n' +
'✓ Relationships: ' + pageResult.graph_relationships_created + '\n' +
'⏱ Time: ' + Math.round(pageResult.processing_time_ms) + 'ms';
const message = 'Re-indexed: ' + result.page_title + '\n\n' +
'✓ Vectors: ' + result.vector_chunks_created + ' chunks\n' +
'✓ Entities: ' + result.graph_entities_extracted + '\n' +
'✓ Relationships: ' + result.graph_relationships_created + '\n' +
'⏱ Time: ' + Math.round(result.processing_time_ms) + 'ms';
log('Success:', message);
@@ -333,28 +323,15 @@
}
try {
log('Fetching page list...');
// Step 1: Get page ID
const listUrl = CONFIG.libraryDeskUrl + '/wiki/pages?user=' + CONFIG.user + '&limit=200';
const listResponse = await fetch(listUrl, {
headers: { 'Authorization': 'Bearer ' + CONFIG.apiKey }
});
if (!listResponse.ok) {
throw new Error('Failed to fetch page list');
// Get page ID from Wiki.js GraphQL API
const pageId = await getPageIdFromPath(pagePath);
if (!pageId) {
throw new Error('Could not get page ID from Wiki.js');
}
const pageList = await listResponse.json();
const page = findMatchingPage(pageList.pages, pagePath);
log('Linking entities for page', pageId, '...');
if (!page) {
throw new Error('Page not found in Library Desk');
}
log('Found page:', page.id, page.title);
// Step 2: Call entity linking endpoint
// Call entity linking endpoint
const response = await fetch(CONFIG.libraryDeskUrl + '/entity-linking/link-page', {
method: 'POST',
headers: {
@@ -363,7 +340,7 @@
},
body: JSON.stringify({
user: CONFIG.user,
page_id: page.id,
page_id: pageId,
create_relationships: true,
re_index_if_changed: true
})
@@ -447,29 +424,94 @@
// SHARED UTILITIES
// ============================================================================
function findMatchingPage(pages, pagePath) {
const possiblePaths = [
pagePath,
'users/' + CONFIG.user + '/' + pagePath,
pagePath.replace(/^\//, ''),
CONFIG.user + '/' + pagePath
];
async function getPageIdFromPath(pagePath) {
/**
* Query Wiki.js GraphQL API to get page ID from path.
* Uses singleByPath query which requires path and locale.
*/
try {
// Get locale from URL or default to 'en'
const urlLocaleMatch = window.location.pathname.match(/^\/([a-z]{2})\//);
const locale = urlLocaleMatch ? urlLocaleMatch[1] : 'en';
log('Looking for page matching:', possiblePaths);
log('Querying Wiki.js GraphQL for page:', pagePath, 'locale:', locale);
for (var i = 0; i < pages.length; i++) {
var page = pages[i];
for (var j = 0; j < possiblePaths.length; j++) {
var testPath = possiblePaths[j];
if (page.path === testPath ||
page.path.endsWith('/' + testPath) ||
('users/' + CONFIG.user + '/' + testPath) === page.path) {
return page;
const query = `
query ($path: String!, $locale: String!) {
pages {
singleByPath(path: $path, locale: $locale) {
id
}
}
}
`;
const response = await fetch('/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin',
body: JSON.stringify({
query: query,
variables: { path: pagePath, locale: locale }
})
});
const result = await response.json();
log('GraphQL response:', result);
if (result.data && result.data.pages && result.data.pages.singleByPath) {
const pageId = result.data.pages.singleByPath.id;
log('Found page ID from GraphQL:', pageId);
return parseInt(pageId, 10);
}
if (result.errors) {
log('GraphQL errors:', result.errors.map(e => e.message).join(', '));
}
log('GraphQL query returned no page');
return null;
} catch (error) {
log('GraphQL query failed:', error.message);
return null;
}
}
function getPageId() {
/**
* Synchronous wrapper - returns null and triggers async fetch.
* Actual buttons will need to call getPageIdFromPath() directly.
*/
log('getPageId() called - page ID must be fetched asynchronously via getPageIdFromPath()');
return null;
}
function waitForPageElement(callback, maxAttempts = 10, interval = 500) {
/**
* Wait for Vue store to be available, then call callback.
*/
let attempts = 0;
function check() {
attempts++;
// Check if Vue store or page data is available
const hasVueStore = window.$store && (window.$store.state || window.$store.get);
const hasPageData = window.$page || window.pageId;
if (hasVueStore || hasPageData) {
log('Vue/page data available after', attempts, 'attempts');
callback();
} else if (attempts < maxAttempts) {
log('Waiting for Vue/page data... attempt', attempts);
setTimeout(check, interval);
} else {
log('Vue/page data never appeared after', maxAttempts, 'attempts - adding buttons anyway');
callback(); // Add buttons anyway, they just won't work
}
}
return null;
check();
}
function findToolbar() {