feat: first take on sidebars
This commit is contained in:
@@ -65,7 +65,12 @@
|
||||
"Command failed": "Befehl fehlgeschlagen",
|
||||
"Command failed with HTTP %{status}": "Befehl mit HTTP %{status} fehlgeschlagen",
|
||||
"Create Project": "Projekt erstellen",
|
||||
"Create / Edit": "Erstellen / Bearbeiten",
|
||||
"Content": "Inhalte",
|
||||
"Data": "Daten",
|
||||
"Dashboard": "Instrumententafel",
|
||||
"AI conversations": "KI-Gespräche",
|
||||
"Automation helpers": "Automatisierungshilfen",
|
||||
"dashboard.postCount.one": "%{count} Beitrag",
|
||||
"dashboard.postCount.other": "%{count} Beiträge",
|
||||
"dashboard.section.categories": "Kategorien",
|
||||
@@ -87,6 +92,20 @@
|
||||
"dashboard.tagCloud.more": "+%{count} weitere",
|
||||
"dashboard.title": "Übersicht",
|
||||
"Desktop Runtime": "Desktop-Laufzeit",
|
||||
"Editor": "Editor",
|
||||
"Images and files": "Bilder und Dateien",
|
||||
"Import definitions": "Importdefinitionen",
|
||||
"Merge Tags": "Tags zusammenführen",
|
||||
"Project": "Projekt",
|
||||
"Project and publishing": "Projekt und Veröffentlichung",
|
||||
"Publishing": "Veröffentlichung",
|
||||
"Site rendering": "Website-Rendering",
|
||||
"Standalone pages": "Eigenständige Seiten",
|
||||
"Tag Cloud": "Tag-Wolke",
|
||||
"Tag management": "Tag-Verwaltung",
|
||||
"Technology": "Technik",
|
||||
"Working tree": "Arbeitsverzeichnis",
|
||||
"Working tree and history": "Arbeitsverzeichnis und Verlauf",
|
||||
"Desktop workbench content routed through the Elixir shell.": "Desktop-Arbeitsbereichsinhalte werden durch die Elixir-Shell geleitet.",
|
||||
"Desktop workbench shell wired through Elixir": "Desktop-Workbench-Shell über Elixir verdrahtet",
|
||||
"Diff Reports": "Diff-Berichte",
|
||||
|
||||
@@ -65,7 +65,12 @@
|
||||
"Command failed": "Command failed",
|
||||
"Command failed with HTTP %{status}": "Command failed with HTTP %{status}",
|
||||
"Create Project": "Create Project",
|
||||
"Create / Edit": "Create / Edit",
|
||||
"Content": "Content",
|
||||
"Data": "Data",
|
||||
"Dashboard": "Dashboard",
|
||||
"AI conversations": "AI conversations",
|
||||
"Automation helpers": "Automation helpers",
|
||||
"dashboard.postCount.one": "%{count} post",
|
||||
"dashboard.postCount.other": "%{count} posts",
|
||||
"dashboard.section.categories": "Categories",
|
||||
@@ -87,6 +92,20 @@
|
||||
"dashboard.tagCloud.more": "+%{count} more",
|
||||
"dashboard.title": "Dashboard",
|
||||
"Desktop Runtime": "Desktop Runtime",
|
||||
"Editor": "Editor",
|
||||
"Images and files": "Images and files",
|
||||
"Import definitions": "Import definitions",
|
||||
"Merge Tags": "Merge Tags",
|
||||
"Project": "Project",
|
||||
"Project and publishing": "Project and publishing",
|
||||
"Publishing": "Publishing",
|
||||
"Site rendering": "Site rendering",
|
||||
"Standalone pages": "Standalone pages",
|
||||
"Tag Cloud": "Tag Cloud",
|
||||
"Tag management": "Tag management",
|
||||
"Technology": "Technology",
|
||||
"Working tree": "Working tree",
|
||||
"Working tree and history": "Working tree and history",
|
||||
"Desktop workbench content routed through the Elixir shell.": "Desktop workbench content routed through the Elixir shell.",
|
||||
"Desktop workbench shell wired through Elixir": "Desktop workbench shell wired through Elixir",
|
||||
"Diff Reports": "Diff Reports",
|
||||
|
||||
@@ -65,7 +65,12 @@
|
||||
"Command failed": "El comando falló",
|
||||
"Command failed with HTTP %{status}": "El comando falló con HTTP %{status}",
|
||||
"Create Project": "Crear proyecto",
|
||||
"Create / Edit": "Crear / editar",
|
||||
"Content": "Contenido",
|
||||
"Data": "Datos",
|
||||
"Dashboard": "Panel",
|
||||
"AI conversations": "Conversaciones de IA",
|
||||
"Automation helpers": "Ayudas de automatización",
|
||||
"dashboard.postCount.one": "%{count} entrada",
|
||||
"dashboard.postCount.other": "%{count} entradas",
|
||||
"dashboard.section.categories": "Categorías",
|
||||
@@ -87,6 +92,20 @@
|
||||
"dashboard.tagCloud.more": "+%{count} más",
|
||||
"dashboard.title": "Panel",
|
||||
"Desktop Runtime": "Entorno de escritorio",
|
||||
"Editor": "Editor",
|
||||
"Images and files": "Imágenes y archivos",
|
||||
"Import definitions": "Definiciones de importación",
|
||||
"Merge Tags": "Combinar etiquetas",
|
||||
"Project": "Proyecto",
|
||||
"Project and publishing": "Proyecto y publicación",
|
||||
"Publishing": "Publicación",
|
||||
"Site rendering": "Renderizado del sitio",
|
||||
"Standalone pages": "Páginas independientes",
|
||||
"Tag Cloud": "Nube de etiquetas",
|
||||
"Tag management": "Gestión de etiquetas",
|
||||
"Technology": "Tecnología",
|
||||
"Working tree": "Árbol de trabajo",
|
||||
"Working tree and history": "Árbol de trabajo e historial",
|
||||
"Desktop workbench content routed through the Elixir shell.": "El contenido del área de trabajo de escritorio se enruta a través del shell de Elixir.",
|
||||
"Desktop workbench shell wired through Elixir": "Shell del área de trabajo de escritorio conectado mediante Elixir",
|
||||
"Diff Reports": "Informes de diff",
|
||||
|
||||
@@ -65,7 +65,12 @@
|
||||
"Command failed": "La commande a échoué",
|
||||
"Command failed with HTTP %{status}": "La commande a échoué avec HTTP %{status}",
|
||||
"Create Project": "Créer un projet",
|
||||
"Create / Edit": "Créer / modifier",
|
||||
"Content": "Contenu",
|
||||
"Data": "Données",
|
||||
"Dashboard": "Tableau de bord",
|
||||
"AI conversations": "Conversations IA",
|
||||
"Automation helpers": "Aides d’automatisation",
|
||||
"dashboard.postCount.one": "%{count} article",
|
||||
"dashboard.postCount.other": "%{count} articles",
|
||||
"dashboard.section.categories": "Catégories",
|
||||
@@ -87,6 +92,20 @@
|
||||
"dashboard.tagCloud.more": "+%{count} de plus",
|
||||
"dashboard.title": "Tableau de bord",
|
||||
"Desktop Runtime": "Exécution bureau",
|
||||
"Editor": "Éditeur",
|
||||
"Images and files": "Images et fichiers",
|
||||
"Import definitions": "Définitions d’import",
|
||||
"Merge Tags": "Fusionner les tags",
|
||||
"Project": "Projet",
|
||||
"Project and publishing": "Projet et publication",
|
||||
"Publishing": "Publication",
|
||||
"Site rendering": "Rendu du site",
|
||||
"Standalone pages": "Pages autonomes",
|
||||
"Tag Cloud": "Nuage de tags",
|
||||
"Tag management": "Gestion des tags",
|
||||
"Technology": "Technologie",
|
||||
"Working tree": "Arbre de travail",
|
||||
"Working tree and history": "Arbre de travail et historique",
|
||||
"Desktop workbench content routed through the Elixir shell.": "Le contenu de l’atelier bureau est acheminé via le shell Elixir.",
|
||||
"Desktop workbench shell wired through Elixir": "Shell d’atelier bureau câblé via Elixir",
|
||||
"Diff Reports": "Rapports de diff",
|
||||
|
||||
@@ -65,7 +65,12 @@
|
||||
"Command failed": "Comando non riuscito",
|
||||
"Command failed with HTTP %{status}": "Comando non riuscito con HTTP %{status}",
|
||||
"Create Project": "Crea progetto",
|
||||
"Create / Edit": "Crea / modifica",
|
||||
"Content": "Contenuti",
|
||||
"Data": "Dati",
|
||||
"Dashboard": "Dashboard",
|
||||
"AI conversations": "Conversazioni IA",
|
||||
"Automation helpers": "Strumenti di automazione",
|
||||
"dashboard.postCount.one": "%{count} post",
|
||||
"dashboard.postCount.other": "%{count} post",
|
||||
"dashboard.section.categories": "Categorie",
|
||||
@@ -87,6 +92,20 @@
|
||||
"dashboard.tagCloud.more": "+%{count} in più",
|
||||
"dashboard.title": "Dashboard",
|
||||
"Desktop Runtime": "Runtime desktop",
|
||||
"Editor": "Editor",
|
||||
"Images and files": "Immagini e file",
|
||||
"Import definitions": "Definizioni di importazione",
|
||||
"Merge Tags": "Unisci tag",
|
||||
"Project": "Progetto",
|
||||
"Project and publishing": "Progetto e pubblicazione",
|
||||
"Publishing": "Pubblicazione",
|
||||
"Site rendering": "Rendering del sito",
|
||||
"Standalone pages": "Pagine autonome",
|
||||
"Tag Cloud": "Nuvola di tag",
|
||||
"Tag management": "Gestione tag",
|
||||
"Technology": "Tecnologia",
|
||||
"Working tree": "Working tree",
|
||||
"Working tree and history": "Working tree e cronologia",
|
||||
"Desktop workbench content routed through the Elixir shell.": "I contenuti del banco di lavoro desktop vengono instradati tramite la shell Elixir.",
|
||||
"Desktop workbench shell wired through Elixir": "Shell del banco di lavoro desktop collegata tramite Elixir",
|
||||
"Diff Reports": "Report diff",
|
||||
|
||||
216
priv/ui/app.css
216
priv/ui/app.css
@@ -1441,6 +1441,218 @@ button {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar-section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 12px 8px;
|
||||
font-size: 12px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.section-icon.status-draft {
|
||||
color: var(--vscode-editorWarning-foreground);
|
||||
}
|
||||
|
||||
.section-icon.status-published {
|
||||
color: var(--vscode-testing-iconPassed);
|
||||
}
|
||||
|
||||
.section-icon.status-archived {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
.sidebar-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-post-item {
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.post-type-icon {
|
||||
width: 18px;
|
||||
flex: 0 0 18px;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.sidebar-item-content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.sidebar-item-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sidebar-item-title {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar-item-language-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 18px;
|
||||
padding: 0 5px;
|
||||
border-radius: 999px;
|
||||
background: rgba(79, 179, 255, 0.14);
|
||||
color: var(--vscode-titleBar-activeForeground);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.sidebar-item-meta {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.media-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
padding: 0 12px 12px;
|
||||
}
|
||||
|
||||
.media-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.media-item:hover {
|
||||
background: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
|
||||
.media-item.selected {
|
||||
background: var(--vscode-list-activeSelectionBackground);
|
||||
color: var(--vscode-list-activeSelectionForeground);
|
||||
}
|
||||
|
||||
.media-thumbnail {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 72px;
|
||||
border-radius: 8px;
|
||||
background: var(--vscode-input-background);
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.media-item-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.media-item-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.media-item-size {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.chat-list-item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.chat-list-item:hover {
|
||||
background: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
|
||||
.chat-list-item.active {
|
||||
background: var(--vscode-list-activeSelectionBackground);
|
||||
color: var(--vscode-list-activeSelectionForeground);
|
||||
}
|
||||
|
||||
.chat-item-content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.chat-item-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat-item-date {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.settings-nav-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 0 12px 12px;
|
||||
}
|
||||
|
||||
.settings-nav-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.settings-nav-entry:hover {
|
||||
background: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
|
||||
.settings-nav-entry-icon {
|
||||
width: 18px;
|
||||
flex: 0 0 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar-empty {
|
||||
padding: 16px 12px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.dashboard-stats {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -1450,4 +1662,8 @@ button {
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.media-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
266
priv/ui/app.js
266
priv/ui/app.js
@@ -144,7 +144,23 @@ function renderSidebar() {
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-content">
|
||||
${data.sections
|
||||
${renderSidebarBody(data, view)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderSidebarBody(data, view) {
|
||||
switch (data.layout) {
|
||||
case "post_list":
|
||||
return renderSidebarPostList(data, view);
|
||||
case "media_grid":
|
||||
return renderSidebarMediaGrid(data, view);
|
||||
case "entity_list":
|
||||
return renderSidebarEntityList(data, view);
|
||||
case "nav_list":
|
||||
return renderSidebarNavList(data, view);
|
||||
default:
|
||||
return (data.sections || [])
|
||||
.map(
|
||||
(section) => `
|
||||
<section class="sidebar-section">
|
||||
@@ -152,17 +168,85 @@ function renderSidebar() {
|
||||
<span data-testid="sidebar-section-title">${escapeHtml(tText(section.title))}</span>
|
||||
</div>
|
||||
<div class="sidebar-section-items">
|
||||
${section.items.map((item) => renderSidebarItem(item, view)).join("")}
|
||||
${(section.items || []).map((item) => renderSidebarItem(item, view)).join("")}
|
||||
</div>
|
||||
</section>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
.join("");
|
||||
}
|
||||
}
|
||||
|
||||
function renderSidebarPostList(data, view) {
|
||||
const sections = Array.isArray(data.sections) ? data.sections : [];
|
||||
const hasItems = sections.some((section) => (section.items || []).length > 0);
|
||||
|
||||
return `
|
||||
${sections
|
||||
.map(
|
||||
(section) => `
|
||||
<section class="sidebar-section">
|
||||
<div class="sidebar-section-title">
|
||||
<span class="section-icon status-${escapeHtmlAttribute(section.status || "draft")}">●</span>
|
||||
<span data-testid="sidebar-section-title">${escapeHtml(tText(section.title))}</span>
|
||||
<span class="sidebar-section-count">${escapeHtml(String(section.count || (section.items || []).length))}</span>
|
||||
</div>
|
||||
<div class="sidebar-list">
|
||||
${(section.items || []).map((item) => renderSidebarPostItem(item, view)).join("")}
|
||||
</div>
|
||||
</section>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
${hasItems ? "" : renderSidebarEmpty(data.empty_message || "No items")}
|
||||
`;
|
||||
}
|
||||
|
||||
function renderSidebarPostItem(item, view) {
|
||||
const tabRef = currentTabRef();
|
||||
const itemRoute = item.route || view.editor_route;
|
||||
const tabId = tabIdForItem(item, itemRoute);
|
||||
const active = tabRef && tabRef.type === itemRoute && tabRef.id === tabId;
|
||||
const postType = getSidebarPostType(item.categories || []);
|
||||
const languageBadge = Number(item.language_count) > 1
|
||||
? `<span class="sidebar-item-language-badge" title="${escapeHtmlAttribute(String(item.language_count))}">${escapeHtml(String(item.language_count))}</span>`
|
||||
: "";
|
||||
|
||||
return `
|
||||
<button
|
||||
class="sidebar-item sidebar-post-item post-type-${escapeHtmlAttribute(postType.type)} ${active ? "active" : ""}"
|
||||
data-open-tab="${escapeHtmlAttribute(tabId)}"
|
||||
data-open-route="${escapeHtmlAttribute(itemRoute)}"
|
||||
data-open-title="${escapeHtmlAttribute(item.title || routeLabel(itemRoute))}"
|
||||
type="button"
|
||||
>
|
||||
<span class="post-type-icon" title="${escapeHtmlAttribute(postType.type)}">${escapeHtml(postType.icon)}</span>
|
||||
<span class="sidebar-item-content">
|
||||
<span class="sidebar-item-title-row">
|
||||
<span class="sidebar-item-title">${escapeHtml(item.title || "")}</span>
|
||||
${languageBadge}
|
||||
</span>
|
||||
<span class="sidebar-item-meta">${escapeHtml(formatSidebarAbsoluteDate(item.meta_timestamp))}</span>
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderSidebarMediaGrid(data, view) {
|
||||
const items = Array.isArray(data.items) ? data.items : [];
|
||||
|
||||
if (!items.length) {
|
||||
return renderSidebarEmpty(data.empty_message || "No items");
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="sidebar-list media-grid">
|
||||
${items.map((item) => renderSidebarMediaItem(item, view)).join("")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderSidebarItem(item, view) {
|
||||
function renderSidebarMediaItem(item, view) {
|
||||
const tabRef = currentTabRef();
|
||||
const itemRoute = item.route || view.editor_route;
|
||||
const tabId = tabIdForItem(item, itemRoute);
|
||||
@@ -170,19 +254,92 @@ function renderSidebarItem(item, view) {
|
||||
|
||||
return `
|
||||
<button
|
||||
class="sidebar-item ${active ? "active" : ""}"
|
||||
data-open-tab="${tabId}"
|
||||
data-open-route="${itemRoute}"
|
||||
data-open-title="${escapeHtmlAttribute(tText(item.title))}"
|
||||
class="media-item ${active ? "selected" : ""}"
|
||||
data-open-tab="${escapeHtmlAttribute(tabId)}"
|
||||
data-open-route="${escapeHtmlAttribute(itemRoute)}"
|
||||
data-open-title="${escapeHtmlAttribute(item.title || routeLabel(itemRoute))}"
|
||||
type="button"
|
||||
title="${escapeHtmlAttribute(item.title || "") }"
|
||||
>
|
||||
<span class="media-thumbnail">${escapeHtml(mediaThumbnailGlyph(item.mime_type))}</span>
|
||||
<span class="media-item-info">
|
||||
<span class="media-item-name">${escapeHtml(item.title || "")}</span>
|
||||
<span class="media-item-size">${escapeHtml(item.meta || "")}</span>
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderSidebarEntityList(data, view) {
|
||||
const items = Array.isArray(data.items) ? data.items : [];
|
||||
|
||||
if (!items.length) {
|
||||
return renderSidebarEmpty(data.empty_message || "No items");
|
||||
}
|
||||
|
||||
return items.map((item) => renderSidebarEntityItem(item, view)).join("");
|
||||
}
|
||||
|
||||
function renderSidebarEntityItem(item, view) {
|
||||
const tabRef = currentTabRef();
|
||||
const itemRoute = item.route || view.editor_route;
|
||||
const tabId = tabIdForItem(item, itemRoute);
|
||||
const active = tabRef && tabRef.type === itemRoute && tabRef.id === tabId;
|
||||
const meta = item.updated_at ? formatSidebarRelativeDateMs(item.updated_at) : tText(item.meta || "");
|
||||
|
||||
return `
|
||||
<button
|
||||
class="chat-list-item ${active ? "active" : ""}"
|
||||
data-open-tab="${escapeHtmlAttribute(tabId)}"
|
||||
data-open-route="${escapeHtmlAttribute(itemRoute)}"
|
||||
data-open-title="${escapeHtmlAttribute(item.title || routeLabel(itemRoute))}"
|
||||
type="button"
|
||||
>
|
||||
<strong>${escapeHtml(tText(item.title))}</strong>
|
||||
<span>${escapeHtml(tText(item.meta || view.label))}</span>
|
||||
${item.badge ? `<span class="sidebar-badge">${escapeHtml(tText(item.badge))}</span>` : ""}
|
||||
<span class="chat-item-content">
|
||||
<span class="chat-item-title">${escapeHtml(item.title || "")}</span>
|
||||
<span class="chat-item-date">${escapeHtml(meta || "")}</span>
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderSidebarNavList(data, view) {
|
||||
const items = Array.isArray(data.items) ? data.items : [];
|
||||
|
||||
return `
|
||||
<div class="settings-nav-list">
|
||||
${items.map((item) => renderSidebarNavItem(item, view)).join("")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderSidebarNavItem(item, view) {
|
||||
const itemRoute = item.route || view.editor_route;
|
||||
const tabId = tabIdForItem(item, itemRoute);
|
||||
const tabTitle = routeLabel(itemRoute);
|
||||
|
||||
return `
|
||||
<button
|
||||
class="settings-nav-entry"
|
||||
data-open-tab="${escapeHtmlAttribute(tabId)}"
|
||||
data-open-route="${escapeHtmlAttribute(itemRoute)}"
|
||||
data-open-title="${escapeHtmlAttribute(tabTitle)}"
|
||||
type="button"
|
||||
>
|
||||
<span class="settings-nav-entry-icon">${escapeHtml(item.icon || "")}</span>
|
||||
<span>${escapeHtml(tText(item.title || ""))}</span>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderSidebarEmpty(message) {
|
||||
return `
|
||||
<div class="sidebar-empty">
|
||||
<p>${escapeHtml(tText(message))}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderTabs() {
|
||||
const tabs = state.session.tabs;
|
||||
const node = root.querySelector(".tab-bar");
|
||||
@@ -361,7 +518,7 @@ function renderDashboard() {
|
||||
<span class="recent-post-title">${escapeHtml(post.title || "")}</span>
|
||||
<span class="recent-post-status status-${escapeHtmlAttribute(post.status || "draft")}">${escapeHtml(dashboardStatusLabel(post.status || "draft"))}</span>
|
||||
<span class="recent-post-date">${escapeHtml(formatDashboardDate(post.updated_at))}</span>
|
||||
</button>
|
||||
if (route === "settings" || route === "tags" || route === "style") {
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
@@ -1146,6 +1303,7 @@ function closeActiveTab() {
|
||||
}
|
||||
|
||||
const index = state.session.tabs.findIndex((tab) => tab.type === active.type && tab.id === active.id);
|
||||
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
@@ -1225,8 +1383,16 @@ function activeItem() {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sections = Object.values(bootstrap.content.sidebar).flatMap((view) => view.sections);
|
||||
return sections.flatMap((section) => section.items).find((item) => tabIdForItem(item, item.route) === tab.id) || null;
|
||||
const items = Object.values(bootstrap.content.sidebar).flatMap(flattenSidebarItems);
|
||||
return items.find((item) => item.route === tab.type && tabIdForItem(item, item.route) === tab.id) || null;
|
||||
}
|
||||
|
||||
function flattenSidebarItems(view) {
|
||||
if (Array.isArray(view.sections)) {
|
||||
return view.sections.flatMap((section) => section.items || []);
|
||||
}
|
||||
|
||||
return Array.isArray(view.items) ? view.items : [];
|
||||
}
|
||||
|
||||
function tabMetadata(tab) {
|
||||
@@ -1442,7 +1608,7 @@ function formatPayloadValue(value) {
|
||||
}
|
||||
|
||||
function tabIdForItem(item, route) {
|
||||
if (route === "settings" || route === "tags") {
|
||||
if (route === "settings" || route === "tags" || route === "style") {
|
||||
return route;
|
||||
}
|
||||
|
||||
@@ -1714,6 +1880,76 @@ function statusLabel(status) {
|
||||
}
|
||||
}
|
||||
|
||||
function getSidebarPostType(categories) {
|
||||
const lowerCategories = (categories || []).map((category) => String(category).toLowerCase());
|
||||
|
||||
if (lowerCategories.includes("picture") || lowerCategories.includes("photo") || lowerCategories.includes("image")) {
|
||||
return { icon: "🖼️", type: "picture" };
|
||||
}
|
||||
|
||||
if (lowerCategories.includes("aside") || lowerCategories.includes("note") || lowerCategories.includes("quick")) {
|
||||
return { icon: "📝", type: "aside" };
|
||||
}
|
||||
|
||||
if (lowerCategories.includes("link") || lowerCategories.includes("bookmark")) {
|
||||
return { icon: "🔗", type: "link" };
|
||||
}
|
||||
|
||||
if (lowerCategories.includes("video")) {
|
||||
return { icon: "🎬", type: "video" };
|
||||
}
|
||||
|
||||
if (lowerCategories.includes("quote")) {
|
||||
return { icon: "💬", type: "quote" };
|
||||
}
|
||||
|
||||
return { icon: "📄", type: "article" };
|
||||
}
|
||||
|
||||
function formatSidebarAbsoluteDate(timestamp) {
|
||||
if (!timestamp) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat(formatLocaleFor(state.uiLanguage), {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
}).format(new Date(timestamp));
|
||||
}
|
||||
|
||||
function formatSidebarRelativeDateMs(timestamp) {
|
||||
if (!timestamp) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const date = new Date(timestamp);
|
||||
const now = new Date();
|
||||
const diffDays = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays === 0) {
|
||||
return date.toLocaleTimeString(formatLocaleFor(state.uiLanguage), { hour: "numeric", minute: "2-digit" });
|
||||
}
|
||||
|
||||
if (diffDays === 1) {
|
||||
return t("sidebar.chat.yesterday");
|
||||
}
|
||||
|
||||
if (diffDays < 7) {
|
||||
return date.toLocaleDateString(formatLocaleFor(state.uiLanguage), { weekday: "short" });
|
||||
}
|
||||
|
||||
return date.toLocaleDateString(formatLocaleFor(state.uiLanguage), { month: "short", day: "numeric" });
|
||||
}
|
||||
|
||||
function mediaThumbnailGlyph(mimeType) {
|
||||
if (String(mimeType || "").startsWith("image/")) {
|
||||
return "🖼️";
|
||||
}
|
||||
|
||||
return "📄";
|
||||
}
|
||||
|
||||
function buildDashboardTagCloudItems(items) {
|
||||
if (!Array.isArray(items) || !items.length) {
|
||||
return [];
|
||||
|
||||
@@ -53,11 +53,14 @@
|
||||
"sidebar_visible": true,
|
||||
"sidebar_width": 280,
|
||||
"active_view": "posts",
|
||||
"assistant_sidebar_visible": false,
|
||||
"layout": "post_list",
|
||||
"sections": [
|
||||
"assistant_sidebar_width": 360,
|
||||
"status": "draft",
|
||||
"count": 1,
|
||||
"panel": { "visible": false, "active_tab": "tasks" },
|
||||
"tabs": [],
|
||||
"active_tab": null,
|
||||
{ "id": "post-welcome", "title": "Welcome to bDS2", "meta_timestamp": 1774972800000, "language_count": 1, "categories": ["note"], "route": "post" }
|
||||
"dirty_tabs": []
|
||||
},
|
||||
"content": {
|
||||
@@ -65,98 +68,71 @@
|
||||
"posts": {
|
||||
"title": "Posts",
|
||||
"subtitle": "Drafts and publishing",
|
||||
"sections": [
|
||||
{
|
||||
"title": "Drafts",
|
||||
"items": [
|
||||
{ "id": "post-welcome", "title": "Welcome to bDS2", "meta": "Updated today", "badge": "draft", "route": "post" }
|
||||
]
|
||||
}
|
||||
]
|
||||
"layout": "post_list",
|
||||
"sections": []
|
||||
},
|
||||
"media": {
|
||||
"title": "Media",
|
||||
"subtitle": "Images and files",
|
||||
"sections": [
|
||||
{
|
||||
"title": "Media",
|
||||
"items": [
|
||||
{ "id": "media-hero", "title": "hero-shot.jpg", "meta": "Image asset", "route": "media" }
|
||||
]
|
||||
}
|
||||
"layout": "media_grid",
|
||||
"items": [
|
||||
{ "id": "media-hero", "title": "hero.jpg", "meta": "1.2 MB", "mime_type": "image/jpeg", "route": "media" }
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"title": "Scripts",
|
||||
"subtitle": "Automation helpers",
|
||||
"layout": "entity_list",
|
||||
"items": [
|
||||
{ "id": "script-sync", "title": "Sync tags", "updated_at": 1774800000000, "route": "scripts" }
|
||||
]
|
||||
},
|
||||
"templates": {
|
||||
"title": "Templates",
|
||||
"subtitle": "Site rendering",
|
||||
"layout": "entity_list",
|
||||
"items": [
|
||||
{ "id": "template-post", "title": "post.liquid", "updated_at": 1774713600000, "route": "templates" }
|
||||
]
|
||||
},
|
||||
"tags": {
|
||||
"title": "Tags",
|
||||
"subtitle": "Tag management",
|
||||
"layout": "nav_list",
|
||||
"items": [
|
||||
{ "id": "tags-cloud", "title": "Tag Cloud", "icon": "☁️", "route": "tags" },
|
||||
{ "id": "tags-manage", "title": "Create / Edit", "icon": "✏️", "route": "tags" },
|
||||
{ "id": "tags-merge", "title": "Merge Tags", "icon": "🔀", "route": "tags" }
|
||||
]
|
||||
},
|
||||
"chat": {
|
||||
"title": "Chat",
|
||||
"subtitle": "AI conversations",
|
||||
"layout": "entity_list",
|
||||
"items": [
|
||||
{ "id": "chat-planning", "title": "Planning session", "updated_at": 1774886400000, "route": "chat" }
|
||||
]
|
||||
},
|
||||
"import": {
|
||||
"title": "Import",
|
||||
"subtitle": "Import definitions",
|
||||
"layout": "entity_list",
|
||||
"items": []
|
||||
},
|
||||
"git": {
|
||||
"title": "Git",
|
||||
"subtitle": "Working tree and history",
|
||||
"layout": "entity_list",
|
||||
"items": [
|
||||
{ "id": "git-working-tree", "title": "Working tree", "meta": "Working tree and history", "route": "git_diff" }
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"subtitle": "Project preferences",
|
||||
"sections": [
|
||||
{
|
||||
"title": "Settings",
|
||||
"items": [
|
||||
{ "id": "settings", "title": "Project", "meta": "Defaults and paths", "route": "settings" }
|
||||
]
|
||||
}
|
||||
"layout": "nav_list",
|
||||
"items": [
|
||||
{ "id": "settings-project", "title": "Project", "icon": "📁", "route": "settings" },
|
||||
{ "id": "settings-style", "title": "Style", "icon": "🎨", "route": "style" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "dashboard.title",
|
||||
"subtitle": "dashboard.subtitle",
|
||||
"post_stats": {
|
||||
"total_posts": 42,
|
||||
"draft_count": 18,
|
||||
"published_count": 21,
|
||||
"archived_count": 3
|
||||
},
|
||||
"media_stats": {
|
||||
"media_count": 18,
|
||||
"image_count": 15,
|
||||
"total_bytes": 12884902
|
||||
},
|
||||
"timeline_entries": [
|
||||
{ "year": 2025, "month": 11, "count": 2 },
|
||||
{ "year": 2025, "month": 12, "count": 3 },
|
||||
{ "year": 2026, "month": 1, "count": 5 },
|
||||
{ "year": 2026, "month": 2, "count": 7 },
|
||||
{ "year": 2026, "month": 3, "count": 9 },
|
||||
{ "year": 2026, "month": 4, "count": 6 }
|
||||
],
|
||||
"tag_cloud_items": [
|
||||
{ "tag": "launch", "count": 12, "color": "#2962ff" },
|
||||
{ "tag": "writing", "count": 7, "color": "#00897b" },
|
||||
{ "tag": "elixir", "count": 5, "color": "#e65100" }
|
||||
],
|
||||
"category_counts": [
|
||||
{ "category": "notes", "count": 14 },
|
||||
{ "category": "projects", "count": 8 }
|
||||
],
|
||||
"recent_posts": [
|
||||
{ "id": "post-welcome", "title": "Welcome to bDS2", "status": "draft", "updated_at": 1774972800000 },
|
||||
{ "id": "post-roadmap", "title": "Roadmap", "status": "published", "updated_at": 1774540800000 }
|
||||
]
|
||||
},
|
||||
"assistant_cards": [
|
||||
{ "label": "Desktop Runtime", "text": "Static bundle mirrors the desktop shell layout." }
|
||||
],
|
||||
"editor_meta": {
|
||||
"dashboard": [
|
||||
{ "label": "Status", "value": "Ready" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"left": { "running_task_message": "Static preview", "running_task_overflow": 0 },
|
||||
"right": {
|
||||
"post_count": "42 posts",
|
||||
"media_count": "18 media",
|
||||
"theme_badge": "desktop-shell",
|
||||
"offline_mode": true,
|
||||
"ui_language": "en",
|
||||
"brand": "bDS"
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="./app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
},
|
||||
Reference in New Issue
Block a user