OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-note / lib / views / notebook-ui.js

88 lines · javascript ref:
/**
 * Notebook View — Note list for a single notebook
 *
 * SSR view showing notebook metadata and a list of notes.
 */

import { renderPage } from '../page-layout.js'

function esc (s) {
  return (s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
}

/**
 * Render the notebook view page
 *
 * @param {Object} notebook - Notebook metadata
 * @param {Array} notes - Note list
 * @param {Object} opts - { pathPrefix, auth }
 * @returns {string} Full HTML page
 */
export function renderNotebookPage (notebook, notes, opts = {}) {
  const pathPrefix = opts.pathPrefix || ''
  const canWrite = opts.canWrite || false
  const slug = notebook.slug

  let content = ''

  // Header
  content += `<div class="nb-header">`
  content += `<div class="nb-header-left">`
  content += `<a class="back-link" href="${pathPrefix}/">&larr; Home</a>`
  content += `<h1>${esc(notebook.name || slug)}</h1>`
  if (notebook.description) {
    content += `<p class="meta">${esc(notebook.description)}</p>`
  }
  content += `<div class="meta">${notes.length} note${notes.length !== 1 ? 's' : ''}`
  if (notebook.visibility) {
    content += ` &middot; <span class="badge badge-secondary">${esc(notebook.visibility)}</span>`
  }
  content += `</div>`
  content += `</div>`
  if (canWrite) {
    content += `<div class="nb-header-right">`
    content += `<a class="btn btn-primary" href="${pathPrefix}/notebooks/${encodeURIComponent(slug)}/notes/new">New Note</a>`
    content += `</div>`
  }
  content += `</div>`

  // Note list
  if (notes.length === 0) {
    content += '<div class="alert alert-info">No notes yet. Create your first note to get started.</div>'
  } else {
    content += '<div class="note-grid">'
    for (const note of notes) {
      const href = `${pathPrefix}/notebooks/${encodeURIComponent(slug)}/notes/${encodeURIComponent(note.slug || note.id)}`
      content += `<a class="card note-card" href="${href}">`
      content += `<h3>${esc(note.title || 'Untitled')}</h3>`
      if (note.content) {
        const snippet = note.content.substring(0, 140).replace(/\n/g, ' ')
        content += `<div class="desc">${esc(snippet)}${note.content.length > 140 ? '...' : ''}</div>`
      }
      content += `<div class="note-card-meta">`
      if (note.tags && note.tags.length > 0) {
        for (const t of note.tags.slice(0, 3)) {
          content += `<span class="tag">${esc(t)}</span>`
        }
      }
      if (note.updatedAt) {
        content += `<span class="note-date">${new Date(note.updatedAt).toLocaleDateString()}</span>`
      }
      content += `</div>`
      content += '</a>'
    }
    content += '</div>'
  }

  const css = `
    .nb-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 24px; }
    .nb-header-left { flex: 1; }
    .nb-header-right { margin-left: 16px; padding-top: 28px; }
    .note-grid { display: flex; flex-direction: column; gap: 8px; }
    .note-card-meta { display: flex; align-items: center; gap: 6px; margin-top: 8px; }
    .note-date { font-size: 0.8rem; color: var(--text-muted); margin-left: auto; }
  `

  return renderPage(notebook.name || slug, content, { pathPrefix, activePage: '', css })
}