From 35b6dc53d97b906d9a7dd186e83a59947545726d Mon Sep 17 00:00:00 2001 From: dev Date: Mon, 2 Nov 2026 04:31:52 +0000 Subject: [PATCH] Update static/js/06-pages.js --- static/js/06-pages.js | 426 ++++++++---------------------------------- 1 file changed, 77 insertions(+), 349 deletions(-) diff --git a/static/js/06-pages.js b/static/js/06-pages.js index 0523480..6dad0b8 100644 --- a/static/js/06-pages.js +++ b/static/js/06-pages.js @@ -1,6 +1,7 @@ // static/js/06-pages.js "use strict"; window.Glow = window.Glow || {}; +// comments, version history, and share links intentionally disabled in production until backend support complete Glow.renderPagesEmptyState = message => { Glow.els.pagesBox.replaceChildren(); @@ -15,49 +16,21 @@ Glow.renderPagesEmptyState = message => { Glow.pageTypeLabel = type => { const labels = { - project: "Project", - document: "Document", - update: "Update", - resource: "Resource" + project: "project", + document: "document", + update: "update", + resource: "resource" }; - return labels[type] || "Page"; + return labels[type] || "page"; }; -Glow.apiListPageVersions = (teamSlug, pageSlug) => - Glow.apiRequest(`/api/teams/${encodeURIComponent(teamSlug)}/pages/${encodeURIComponent(pageSlug)}/versions`); -Glow.apiGetPageVersion = versionId => - Glow.apiRequest(`/api/page-versions/${encodeURIComponent(versionId)}`); -Glow.apiRestorePageVersion = versionId => - Glow.apiRequest(`/api/page-versions/${encodeURIComponent(versionId)}/restore`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({}) - }); -Glow.apiListPageComments = (teamSlug, pageSlug) => - Glow.apiRequest(`/api/teams/${encodeURIComponent(teamSlug)}/pages/${encodeURIComponent(pageSlug)}/comments`); -Glow.apiCreatePageComment = (teamSlug, pageSlug, payload) => - Glow.apiRequest(`/api/teams/${encodeURIComponent(teamSlug)}/pages/${encodeURIComponent(pageSlug)}/comments`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload) - }); -Glow.apiDeletePageComment = commentId => - Glow.apiRequest(`/api/page-comments/${encodeURIComponent(commentId)}`, { - method: "DELETE" - }); -Glow.apiCreatePageShare = (teamSlug, pageSlug) => - Glow.apiRequest(`/api/teams/${encodeURIComponent(teamSlug)}/pages/${encodeURIComponent(pageSlug)}/share`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({}) - }); - Glow.loadPages = async (pageType = Glow.state.pageTypeFilter) => { const team = Glow.state.activeTeam; if (!team) { - Glow.renderPagesEmptyState("Select a team to view pages."); + Glow.renderPagesEmptyState("select a team to view pages."); return; } + Glow.els.pagesBox.setAttribute("aria-busy", "true"); try { const query = pageType ? `?type=${encodeURIComponent(pageType)}` : ""; @@ -69,8 +42,8 @@ Glow.loadPages = async (pageType = Glow.state.pageTypeFilter) => { Glow.state.pageTypeFilter = pageType || ""; Glow.renderPages(); } catch (error) { - Glow.renderPagesEmptyState(error.message || "Could not load pages."); - Glow.showToast(error.message || "Could not load pages.", "error"); + Glow.renderPagesEmptyState(error.message || "could not load pages."); + Glow.showToast(error.message || "could not load pages.", "error"); } finally { Glow.els.pagesBox.setAttribute("aria-busy", "false"); } @@ -84,11 +57,12 @@ Glow.renderPages = () => { const header = document.createElement("div"); header.className = "pages-header"; + const copy = document.createElement("div"); const title = document.createElement("h2"); - title.textContent = `${team?.name || "Team"} pages`; + title.textContent = `${team?.name || "team"} pages`; const subtitle = document.createElement("p"); - subtitle.textContent = "Shared team knowledge and public resources."; + subtitle.textContent = "shared team knowledge and public resources."; copy.append(title, subtitle); header.append(copy); @@ -96,21 +70,23 @@ Glow.renderPages = () => { const create = document.createElement("button"); create.className = "button"; create.type = "button"; - create.textContent = "New page"; + create.textContent = "new page"; create.addEventListener("click", Glow.createPage); header.append(create); } + box.append(header); const filters = document.createElement("div"); filters.className = "page-filters compact"; const filterOptions = [ - ["", "All"], - ["project", "Projects"], - ["document", "Documents"], - ["update", "Updates"], - ["resource", "Resources"] + ["", "all"], + ["project", "projects"], + ["document", "documents"], + ["update", "updates"], + ["resource", "resources"] ]; + for (const [value, label] of filterOptions) { const button = document.createElement("button"); button.className = "page-filter"; @@ -120,6 +96,7 @@ Glow.renderPages = () => { button.addEventListener("click", () => Glow.loadPages(value)); filters.append(button); } + box.append(filters); if (!pages.length) { @@ -127,7 +104,7 @@ Glow.renderPages = () => { empty.className = "empty-state page-empty-state"; const card = document.createElement("div"); card.className = "empty-card"; - card.textContent = "No pages match this filter."; + card.textContent = "no pages match this filter."; empty.append(card); box.append(empty); return; @@ -135,9 +112,11 @@ Glow.renderPages = () => { const list = document.createElement("div"); list.className = "pages-list"; + for (const page of pages) { list.append(Glow.buildPageCard(page)); } + box.append(list); }; @@ -164,17 +143,17 @@ Glow.buildPageCard = page => { const footer = document.createElement("div"); footer.className = "page-card-footer"; + const context = document.createElement("span"); - context.textContent = page.parent_page_title - ? `Child of ${page.parent_page_title}` - : page.channel_name - ? `#${page.channel_name}` - : "Team page"; + context.textContent = page.channel_name + ? `#${page.channel_name}` + : "team page"; const updated = document.createElement("time"); updated.dateTime = page.updated_at; updated.title = new Date(page.updated_at).toLocaleString(); - updated.textContent = `Updated ${Glow.relativeTime(page.updated_at)}`; + updated.textContent = `updated ${Glow.relativeTime(page.updated_at)}`; + footer.append(context, updated); const open = () => Glow.openPage(page.slug); @@ -201,10 +180,12 @@ Glow.openPages = async () => { Glow.openPage = async pageSlug => { const team = Glow.state.activeTeam; if (!team) return; + Glow.state.activeView = "page"; Glow.state.activePage = null; Glow.updateViewUI(); Glow.els.pageDetailBox.setAttribute("aria-busy", "true"); + try { const data = await Glow.apiRequest( `/api/teams/${encodeURIComponent(team.slug)}/pages/${encodeURIComponent(pageSlug)}` @@ -212,244 +193,13 @@ Glow.openPage = async pageSlug => { Glow.state.activePage = data.page; Glow.renderPageDetail(); } catch (error) { - Glow.showToast(error.message || "Could not load page.", "error"); + Glow.showToast(error.message || "could not load page.", "error"); Glow.openPages(); } finally { Glow.els.pageDetailBox.setAttribute("aria-busy", "false"); } }; -Glow.renderPageComments = page => { - const section = document.createElement("section"); - section.className = "profile-section"; - const heading = document.createElement("h3"); - heading.textContent = "Comments"; - section.append(heading); - - const list = document.createElement("div"); - list.className = "page-comments-list"; - - const comments = Array.isArray(page.comments) ? page.comments : []; - if (!comments.length) { - const empty = document.createElement("div"); - empty.className = "profile-empty"; - empty.textContent = "No comments yet."; - list.append(empty); - } else { - for (const comment of comments) { - const item = document.createElement("article"); - item.className = "page-comment"; - - const top = document.createElement("div"); - top.className = "page-comment-meta"; - const author = document.createElement("strong"); - author.textContent = comment.username; - author.style.color = comment.user_name_color || ""; - const time = document.createElement("time"); - time.dateTime = comment.created_at; - time.textContent = new Date(comment.created_at).toLocaleString(); - top.append(author, time); - - const body = document.createElement("div"); - body.className = "page-comment-body"; - body.textContent = comment.body; - - item.append(top, body); - - const canDelete = - Glow.state.currentUser && - (Number(Glow.state.currentUser.id) === Number(comment.user_id) || - Glow.state.currentUser.role === "admin" || - page.can_edit); - if (canDelete) { - const remove = document.createElement("button"); - remove.className = "small-button danger"; - remove.type = "button"; - remove.textContent = "Delete"; - remove.addEventListener("click", async () => { - try { - await Glow.apiDeletePageComment(comment.id); - await Glow.openPage(page.slug); - Glow.showToast("Comment deleted."); - } catch (error) { - Glow.showToast(error.message || "Could not delete comment.", "error"); - } - }); - item.append(remove); - } - - list.append(item); - } - } - section.append(list); - - if (Glow.state.currentUser) { - const form = document.createElement("form"); - form.className = "page-comment-form"; - - const area = document.createElement("textarea"); - area.className = "field"; - area.rows = 4; - area.maxLength = 3000; - area.placeholder = "Add a comment"; - - const submit = document.createElement("button"); - submit.className = "button"; - submit.type = "submit"; - submit.textContent = "Post comment"; - - form.append(area, submit); - form.addEventListener("submit", async event => { - event.preventDefault(); - const body = area.value.trim(); - if (!body) return; - try { - await Glow.apiCreatePageComment(page.team_slug, page.slug, { body }); - await Glow.openPage(page.slug); - Glow.showToast("Comment added."); - } catch (error) { - Glow.showToast(error.message || "Could not add comment.", "error"); - } - }); - - section.append(form); - } - - return section; -}; - -Glow.renderPageBacklinks = page => { - const section = document.createElement("section"); - section.className = "profile-section"; - const heading = document.createElement("h3"); - heading.textContent = "Backlinks"; - section.append(heading); - - const backlinks = Array.isArray(page.backlinks) ? page.backlinks : []; - if (!backlinks.length) { - const empty = document.createElement("div"); - empty.className = "profile-empty"; - empty.textContent = "No backlinks yet."; - section.append(empty); - return section; - } - - const list = document.createElement("div"); - list.className = "page-related-list"; - for (const link of backlinks) { - const button = document.createElement("button"); - button.className = "small-button"; - button.type = "button"; - button.textContent = link.title; - button.addEventListener("click", () => Glow.openPage(link.slug)); - list.append(button); - } - section.append(list); - return section; -}; - -Glow.renderPageChildren = page => { - const section = document.createElement("section"); - section.className = "profile-section"; - const heading = document.createElement("h3"); - heading.textContent = "Child pages"; - section.append(heading); - - const children = Array.isArray(page.children) ? page.children : []; - if (!children.length) { - const empty = document.createElement("div"); - empty.className = "profile-empty"; - empty.textContent = "No child pages."; - section.append(empty); - return section; - } - - const list = document.createElement("div"); - list.className = "page-related-list"; - for (const child of children) { - const button = document.createElement("button"); - button.className = "small-button"; - button.type = "button"; - button.textContent = child.title; - button.addEventListener("click", () => Glow.openPage(child.slug)); - list.append(button); - } - section.append(list); - return section; -}; - -Glow.openPageVersionHistory = async page => { - try { - const data = await Glow.apiListPageVersions(page.team_slug, page.slug); - const container = document.createElement("div"); - container.className = "member-list"; - - const versions = data.versions || []; - if (!versions.length) { - const empty = document.createElement("div"); - empty.className = "member-row"; - empty.textContent = "No version history yet."; - container.append(empty); - } else { - for (const version of versions) { - const row = document.createElement("div"); - row.className = "member-row"; - const left = document.createElement("button"); - left.className = "small-button"; - left.type = "button"; - left.textContent = `v${version.version_number} · ${version.title}`; - left.addEventListener("click", async () => { - try { - const detail = await Glow.apiGetPageVersion(version.id); - const body = document.createElement("div"); - body.className = "page-body markdown-body"; - body.innerHTML = detail.version.body_html || "

No content.

"; - const result = await Glow.openDialog({ - title: `Version ${detail.version.version_number}`, - body, - confirmText: page.can_edit ? "Restore version" : "Close", - cancelText: "Close", - danger: page.can_edit - }); - if (result && page.can_edit) { - await Glow.apiRestorePageVersion(version.id); - Glow.showToast("Version restored."); - await Glow.openPage(page.slug); - } - } catch (error) { - Glow.showToast(error.message || "Could not load version.", "error"); - } - }); - - const right = document.createElement("span"); - right.textContent = new Date(version.created_at).toLocaleString(); - row.append(left, right); - container.append(row); - } - } - - await Glow.openDialog({ - title: "Version history", - body: container, - confirmText: "Close", - cancelText: "Close" - }); - } catch (error) { - Glow.showToast(error.message || "Could not load version history.", "error"); - } -}; - -Glow.shareCurrentPage = async page => { - try { - const data = await Glow.apiCreatePageShare(page.team_slug, page.slug); - const url = new URL(data.share.url, window.location.origin).toString(); - await navigator.clipboard.writeText(url); - Glow.showToast("Page share link copied."); - } catch (error) { - Glow.showToast(error.message || "Could not create page share.", "error"); - } -}; - Glow.renderPageDetail = () => { const box = Glow.els.pageDetailBox; const page = Glow.state.activePage; @@ -460,7 +210,7 @@ Glow.renderPageDetail = () => { state.className = "empty-state"; const card = document.createElement("div"); card.className = "empty-card"; - card.textContent = "Page not found."; + card.textContent = "page not found."; state.append(card); box.append(state); return; @@ -484,7 +234,7 @@ Glow.renderPageDetail = () => { updated.className = "page-updated"; updated.dateTime = page.updated_at; updated.title = new Date(page.updated_at).toLocaleString(); - updated.textContent = `Updated ${Glow.relativeTime(page.updated_at)}`; + updated.textContent = `updated ${Glow.relativeTime(page.updated_at)}`; meta.append(type, visibility, updated); @@ -493,57 +243,32 @@ Glow.renderPageDetail = () => { const context = document.createElement("p"); context.className = "page-detail-context"; - context.textContent = page.parent_page_title - ? `${page.team_name} · Child of ${page.parent_page_title}` - : page.channel_name - ? `${page.team_name} · #${page.channel_name}` - : page.team_name; + context.textContent = page.channel_name + ? `${page.team_name} · #${page.channel_name}` + : page.team_name; header.append(meta, title, context); box.append(header); const body = document.createElement("article"); body.className = "page-body markdown-body"; - body.innerHTML = page.body_html || "

No content yet.

"; + body.innerHTML = page.body_html || "

no content yet.

"; box.append(body); - const related = document.createElement("div"); - related.className = "profile-grid"; - related.append( - Glow.renderPageChildren(page), - Glow.renderPageBacklinks(page), - Glow.renderPageComments(page) - ); - box.append(related); - const actions = document.createElement("div"); actions.className = "page-detail-actions"; - const history = document.createElement("button"); - history.className = "button secondary"; - history.type = "button"; - history.textContent = "Version history"; - history.addEventListener("click", () => Glow.openPageVersionHistory(page)); - actions.append(history); - - const share = document.createElement("button"); - share.className = "button secondary"; - share.type = "button"; - share.textContent = "Share"; - share.addEventListener("click", () => Glow.shareCurrentPage(page)); - actions.append(share); - if (page.can_edit) { const edit = document.createElement("button"); edit.className = "button"; edit.type = "button"; - edit.textContent = "Edit"; + edit.textContent = "edit"; edit.addEventListener("click", () => Glow.editPage(page)); const remove = document.createElement("button"); remove.className = "danger-button"; remove.type = "button"; - remove.textContent = "Delete"; + remove.textContent = "delete"; remove.addEventListener("click", () => Glow.deletePage(page)); actions.append(edit, remove); @@ -555,40 +280,33 @@ Glow.renderPageDetail = () => { Glow.pageDialogFields = (page = null) => [ { name: "title", - label: "Title", + label: "title", value: page?.title || "", - placeholder: "Page title", + placeholder: "page title", maxLength: 160 }, { name: "page_type", - label: "Type: project, document, update, or resource", + label: "type: project, document, update, or resource", value: page?.page_type || "document", maxLength: 12 }, { name: "visibility", - label: "Visibility: team or public", + label: "visibility: team or public", value: page?.visibility || "team", maxLength: 6 }, { name: "channel_slug", - label: "Channel slug (optional)", + label: "channel slug (optional)", value: page?.channel_slug || "", placeholder: "general", maxLength: 48 }, - { - name: "parent_page_slug", - label: "Parent page slug (optional)", - value: page?.parent_page_slug || "", - placeholder: "project-overview", - maxLength: 48 - }, { name: "body", - label: "Markdown content", + label: "markdown content", value: page?.body || "", multiline: true, rows: 14, @@ -598,41 +316,46 @@ Glow.pageDialogFields = (page = null) => [ Glow.validatePageValues = values => { if (!values.title) { - Glow.showToast("Page title is required.", "error"); + Glow.showToast("page title is required.", "error"); return null; } + const pageType = values.page_type.toLowerCase(); if (!["project", "document", "update", "resource"].includes(pageType)) { - Glow.showToast("Choose a valid page type.", "error"); + Glow.showToast("choose a valid page type.", "error"); return null; } + const visibility = values.visibility.toLowerCase(); if (!["team", "public"].includes(visibility)) { - Glow.showToast("Visibility must be team or public.", "error"); + Glow.showToast("visibility must be team or public.", "error"); return null; } + return { title: values.title, body: values.body, page_type: pageType, visibility, - channel_slug: values.channel_slug.toLowerCase(), - parent_page_slug: values.parent_page_slug.toLowerCase() + channel_slug: values.channel_slug.toLowerCase() }; }; Glow.createPage = async () => { const team = Glow.state.activeTeam; if (!team?.can_manage) return; + const values = await Glow.openDialog({ - title: "Create page", - body: "Create a shared team page or a public resource. Markdown is supported.", - confirmText: "Create page", + title: "create page", + body: "create a shared team page or a public resource. markdown is supported.", + confirmText: "create page", fields: Glow.pageDialogFields() }); if (!values) return; + const payload = Glow.validatePageValues(values); if (!payload) return; + try { const data = await Glow.apiRequest( `/api/teams/${encodeURIComponent(team.slug)}/pages`, @@ -642,23 +365,26 @@ Glow.createPage = async () => { body: JSON.stringify(payload) } ); - Glow.showToast("Page created."); + Glow.showToast("page created."); await Glow.openPage(data.page.slug); } catch (error) { - Glow.showToast(error.message || "Could not create page.", "error"); + Glow.showToast(error.message || "could not create page.", "error"); } }; Glow.editPage = async page => { if (!page.can_edit) return; + const values = await Glow.openDialog({ - title: `Edit ${page.title}`, - confirmText: "Save", + title: `edit ${page.title}`, + confirmText: "save", fields: Glow.pageDialogFields(page) }); if (!values) return; + const payload = Glow.validatePageValues(values); if (!payload) return; + try { const data = await Glow.apiRequest( `/api/teams/${encodeURIComponent(page.team_slug)}/pages/${encodeURIComponent(page.slug)}`, @@ -670,29 +396,31 @@ Glow.editPage = async page => { ); Glow.state.activePage = data.page; Glow.renderPageDetail(); - Glow.showToast("Page updated."); + Glow.showToast("page updated."); } catch (error) { - Glow.showToast(error.message || "Could not update page.", "error"); + Glow.showToast(error.message || "could not update page.", "error"); } }; Glow.deletePage = async page => { if (!page.can_edit) return; + const result = await Glow.openDialog({ - title: `Delete ${page.title}?`, - body: "This page will no longer be available.", - confirmText: "Delete page", + title: `delete ${page.title}?`, + body: "this page will no longer be available.", + confirmText: "delete page", danger: true }); if (!result) return; + try { await Glow.apiRequest( `/api/teams/${encodeURIComponent(page.team_slug)}/pages/${encodeURIComponent(page.slug)}`, { method: "DELETE" } ); - Glow.showToast("Page deleted."); + Glow.showToast("page deleted."); await Glow.openPages(); } catch (error) { - Glow.showToast(error.message || "Could not delete page.", "error"); + Glow.showToast(error.message || "could not delete page.", "error"); } }; \ No newline at end of file