graph functionality
This commit is contained in:
@@ -341,6 +341,6 @@ The special translations page is now one page named `wiki-translations`. It is p
|
|||||||
Version 0.2.13 centers the wiki name in the sidebar and makes it a link to the first/start page. The sticky top chrome now starts without top padding (`padding-top: 0`).
|
Version 0.2.13 centers the wiki name in the sidebar and makes it a link to the first/start page. The sticky top chrome now starts without top padding (`padding-top: 0`).
|
||||||
|
|
||||||
|
|
||||||
## Version 0.2.14
|
## 0.2.15
|
||||||
|
|
||||||
Version 0.2.14 fixes the wiki-name/home navigation so clicking the wiki name, including while a special view such as Todo is open, always returns to the first/start page. It also adds a lightweight Wiki Graph view. The graph uses the existing pages and their internal Markdown links, requires no extra JavaScript dependency or database migration, and opens a page when its node is selected.
|
The page table of contents now includes a `(context)` link. It opens a graph containing the current page and all pages that link directly to it or are linked directly from it. The current page is highlighted. Heading anchors also reserve space for the sticky top navigation so a selected heading remains visible.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#lang info
|
#lang info
|
||||||
|
|
||||||
(define pkg-authors '(hnmdijkema))
|
(define pkg-authors '(hnmdijkema))
|
||||||
(define version "0.2.14")
|
(define version "0.2.15")
|
||||||
(define license 'MIT)
|
(define license 'MIT)
|
||||||
(define collection "racket-wiki")
|
(define collection "racket-wiki")
|
||||||
(define pkg-desc
|
(define pkg-desc
|
||||||
|
|||||||
@@ -1014,3 +1014,37 @@ body.editor-mode .editor-metadata-row {
|
|||||||
fill: #1e2f6d;
|
fill: #1e2f6d;
|
||||||
stroke: #1e2f6d;
|
stroke: #1e2f6d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 0.2.15 context navigation and sticky-anchor correction ---------------- */
|
||||||
|
.context-link {
|
||||||
|
color: var(--wiki-link);
|
||||||
|
font-size: .76rem;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: normal;
|
||||||
|
text-transform: none;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body h1,
|
||||||
|
.markdown-body h2,
|
||||||
|
.markdown-body h3,
|
||||||
|
.markdown-body h4,
|
||||||
|
.markdown-body h5,
|
||||||
|
.markdown-body h6 {
|
||||||
|
scroll-margin-top: 82px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-graph-node-focus circle {
|
||||||
|
r: 15px;
|
||||||
|
fill: #1e2f6d;
|
||||||
|
stroke: #1e2f6d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wiki-graph-node-focus text {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|||||||
+2
-2
@@ -22,7 +22,7 @@
|
|||||||
<input id="search-input" type="search" placeholder="Search wiki" data-tr-placeholder="search-wiki" autocomplete="off" aria-label="Search wiki">
|
<input id="search-input" type="search" placeholder="Search wiki" data-tr-placeholder="search-wiki" autocomplete="off" aria-label="Search wiki">
|
||||||
</form>
|
</form>
|
||||||
<section class="sidebar-section toc-section">
|
<section class="sidebar-section toc-section">
|
||||||
<div class="sidebar-heading" data-tr="contents">Contents</div>
|
<div class="sidebar-heading"><span data-tr="contents">Contents</span> <a id="context-link" class="context-link hidden" href="#">(<span data-tr="context">context</span>)</a></div>
|
||||||
<nav id="toc-list" class="toc-list"></nav>
|
<nav id="toc-list" class="toc-list"></nav>
|
||||||
</section>
|
</section>
|
||||||
<details class="sidebar-section pages-section">
|
<details class="sidebar-section pages-section">
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
|
|
||||||
<section id="graph-view" class="hidden">
|
<section id="graph-view" class="hidden">
|
||||||
<header class="page-header">
|
<header class="page-header">
|
||||||
<h1 data-tr="wiki-graph">Wiki graph</h1>
|
<h1 id="graph-title" data-tr="wiki-graph">Wiki graph</h1>
|
||||||
<div id="graph-summary" class="muted"></div>
|
<div id="graph-summary" class="muted"></div>
|
||||||
</header>
|
</header>
|
||||||
<div class="wiki-graph-shell">
|
<div class="wiki-graph-shell">
|
||||||
|
|||||||
+38
-2
@@ -23,6 +23,7 @@
|
|||||||
$(id).classList.toggle("hidden", id !== viewId);
|
$(id).classList.toggle("hidden", id !== viewId);
|
||||||
}
|
}
|
||||||
$("page-action-links").classList.toggle("hidden", viewId !== "page-view");
|
$("page-action-links").classList.toggle("hidden", viewId !== "page-view");
|
||||||
|
$("context-link").classList.toggle("hidden", !(state.currentPage && ["page-view", "editor-view", "history-view"].includes(viewId)));
|
||||||
document.body.classList.toggle("editor-mode", viewId === "editor-view");
|
document.body.classList.toggle("editor-mode", viewId === "editor-view");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1182,7 +1183,7 @@
|
|||||||
return { nodes: state.pages, edges };
|
return { nodes: state.pages, edges };
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderWikiGraph(data) {
|
function renderWikiGraph(data, focusSlug = null) {
|
||||||
const svg = $("wiki-graph");
|
const svg = $("wiki-graph");
|
||||||
svg.replaceChildren();
|
svg.replaceChildren();
|
||||||
|
|
||||||
@@ -1222,7 +1223,7 @@
|
|||||||
for (const page of nodes) {
|
for (const page of nodes) {
|
||||||
const position = positions.get(page.slug);
|
const position = positions.get(page.slug);
|
||||||
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
||||||
group.setAttribute("class", "wiki-graph-node");
|
group.setAttribute("class", page.slug === focusSlug ? "wiki-graph-node wiki-graph-node-focus" : "wiki-graph-node");
|
||||||
group.setAttribute("transform", `translate(${position.x} ${position.y})`);
|
group.setAttribute("transform", `translate(${position.x} ${position.y})`);
|
||||||
group.setAttribute("tabindex", "0");
|
group.setAttribute("tabindex", "0");
|
||||||
group.setAttribute("role", "link");
|
group.setAttribute("role", "link");
|
||||||
@@ -1259,6 +1260,7 @@
|
|||||||
]);
|
]);
|
||||||
renderToc([], () => {});
|
renderToc([], () => {});
|
||||||
show("graph-view");
|
show("graph-view");
|
||||||
|
$("graph-title").textContent = tr("wiki-graph", "Wiki graph");
|
||||||
|
|
||||||
const data = await loadGraphData();
|
const data = await loadGraphData();
|
||||||
$("graph-summary").textContent = tr("graph-summary", "{pages} pages, {links} links")
|
$("graph-summary").textContent = tr("graph-summary", "{pages} pages, {links} links")
|
||||||
@@ -1267,6 +1269,39 @@
|
|||||||
renderWikiGraph(data);
|
renderWikiGraph(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function showContextGraph() {
|
||||||
|
if (!state.currentPage) return;
|
||||||
|
|
||||||
|
const page = state.currentPage;
|
||||||
|
state.previousView = "page-view";
|
||||||
|
renderBreadcrumbs([
|
||||||
|
{ label: state.siteTitle, href: "/" },
|
||||||
|
{ label: page.title, href: `#/${encodeURIComponent(page.slug)}` },
|
||||||
|
{ label: tr("context", "context") }
|
||||||
|
]);
|
||||||
|
renderToc([], () => {});
|
||||||
|
show("graph-view");
|
||||||
|
$("graph-title").textContent = tr("context-graph", "Context graph");
|
||||||
|
|
||||||
|
const data = await loadGraphData();
|
||||||
|
const relatedSlugs = new Set([page.slug]);
|
||||||
|
const contextEdges = [];
|
||||||
|
|
||||||
|
for (const edge of data.edges) {
|
||||||
|
if (edge.from === page.slug || edge.to === page.slug) {
|
||||||
|
relatedSlugs.add(edge.from);
|
||||||
|
relatedSlugs.add(edge.to);
|
||||||
|
contextEdges.push(edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextNodes = data.nodes.filter((node) => relatedSlugs.has(node.slug));
|
||||||
|
$("graph-summary").textContent = tr("context-graph-summary", "{pages} related pages, {links} links")
|
||||||
|
.replace("{pages}", String(Math.max(0, contextNodes.length - 1)))
|
||||||
|
.replace("{links}", String(contextEdges.length));
|
||||||
|
renderWikiGraph({ nodes: contextNodes, edges: contextEdges }, page.slug);
|
||||||
|
}
|
||||||
|
|
||||||
function setConnectionStatus(status) {
|
function setConnectionStatus(status) {
|
||||||
const node = $("connection-status");
|
const node = $("connection-status");
|
||||||
node.classList.remove("hidden", "online", "offline");
|
node.classList.remove("hidden", "online", "offline");
|
||||||
@@ -1339,6 +1374,7 @@
|
|||||||
});
|
});
|
||||||
$("todo-link").addEventListener("click", (event) => { event.preventDefault(); showTodos().catch((error) => console.error(error)); });
|
$("todo-link").addEventListener("click", (event) => { event.preventDefault(); showTodos().catch((error) => console.error(error)); });
|
||||||
$("graph-link").addEventListener("click", (event) => { event.preventDefault(); showGraph().catch((error) => console.error(error)); });
|
$("graph-link").addEventListener("click", (event) => { event.preventDefault(); showGraph().catch((error) => console.error(error)); });
|
||||||
|
$("context-link").addEventListener("click", (event) => { event.preventDefault(); showContextGraph().catch((error) => console.error(error)); });
|
||||||
$("logout-link").addEventListener("click", async (event) => {
|
$("logout-link").addEventListener("click", async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
await api("/api/logout", { method: "POST", body: "{}" });
|
await api("/api/logout", { method: "POST", body: "{}" });
|
||||||
|
|||||||
+2
-2
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
(define english
|
(define english
|
||||||
(hash
|
(hash
|
||||||
'users "Users" 'translations "Translations" 'todo "Todo" 'todo-list "Todo list" 'graph "Graph" 'wiki-graph "Wiki graph" 'graph-summary "{pages} pages, {links} links" 'admin "Admin" 'role-reader "reader" 'role-editor "editor" 'role-admin "admin"
|
'users "Users" 'translations "Translations" 'todo "Todo" 'todo-list "Todo list" 'graph "Graph" 'wiki-graph "Wiki graph" 'graph-summary "{pages} pages, {links} links" 'context "context" 'context-graph "Context graph" 'context-graph-summary "{pages} related pages, {links} links" 'admin "Admin" 'role-reader "reader" 'role-editor "editor" 'role-admin "admin"
|
||||||
'search-wiki "Search wiki" 'contents "Contents" 'pages "Pages"
|
'search-wiki "Search wiki" 'contents "Contents" 'pages "Pages"
|
||||||
'edit "Edit" 'history "History" 'delete "Delete" 'page-not-found "Page not found"
|
'edit "Edit" 'history "History" 'delete "Delete" 'page-not-found "Page not found"
|
||||||
'cancel "Cancel" 'save "Save" 'page-title "Page title" 'tags "Tags (comma separated)"
|
'cancel "Cancel" 'save "Save" 'page-title "Page title" 'tags "Tags (comma separated)"
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
(define dutch
|
(define dutch
|
||||||
(hash
|
(hash
|
||||||
'users "Gebruikers" 'translations "Vertalingen" 'todo "Todo" 'todo-list "Todo-lijst" 'graph "Graph" 'wiki-graph "Wiki-graaf" 'graph-summary "{pages} pagina's, {links} links" 'admin "Admin" 'role-reader "lezer" 'role-editor "redacteur" 'role-admin "admin"
|
'users "Gebruikers" 'translations "Vertalingen" 'todo "Todo" 'todo-list "Todo-lijst" 'graph "Graph" 'wiki-graph "Wiki-graaf" 'graph-summary "{pages} pagina's, {links} links" 'context "context" 'context-graph "Contextgraaf" 'context-graph-summary "{pages} gerelateerde pagina's, {links} links" 'admin "Admin" 'role-reader "lezer" 'role-editor "redacteur" 'role-admin "admin"
|
||||||
'search-wiki "Wiki doorzoeken" 'contents "Inhoud" 'pages "Pagina's"
|
'search-wiki "Wiki doorzoeken" 'contents "Inhoud" 'pages "Pagina's"
|
||||||
'edit "Bewerken" 'history "Geschiedenis" 'delete "Verwijderen" 'page-not-found "Pagina niet gevonden"
|
'edit "Bewerken" 'history "Geschiedenis" 'delete "Verwijderen" 'page-not-found "Pagina niet gevonden"
|
||||||
'cancel "Annuleren" 'save "Opslaan" 'page-title "Paginatitel" 'tags "Tags (komma-gescheiden)"
|
'cancel "Annuleren" 'save "Opslaan" 'page-title "Paginatitel" 'tags "Tags (komma-gescheiden)"
|
||||||
|
|||||||
Reference in New Issue
Block a user