Changes
9 changed files (+409/-181)
-
-
@@ -62,8 +62,8 @@ - [x] Creation / Update date- [ ] Tags - [x] Config for site logo - [x] Tool's logo - [ ] Proper styling - [ ] Switch to drawer menu when side navigation cannot fit - [x] Proper styling - [x] Switch to drawer menu when side navigation cannot fit - [ ] CLI for opinionated build - [ ] Simple JSONC config - [ ] Usage document
-
-
-
@@ -59,6 +59,15 @@ return Array.from(css.chunks.values()).join("\n");} /** * Join class names. */ export function cx( ...classNames: (string | null | false | undefined)[] ): string { return classNames.filter((c): c is string => !!c).join(" "); } /** * This function returns an object whose every propery value is * class name string. *
-
-
-
@@ -14,13 +14,13 @@ line-height: var(--baseline);--color-primary: rgb(217, 59, 133); --color-bg: #fafafa; --color-bg-accent: #eaeaea; --color-bg-light: #f4f4f3; --color-bg: hsl(0deg 0% 98%); --color-bg-accent: hsl(0deg 0% 95%); --color-bg-light: hsl(60deg 4% 96%); --color-fg: #333; --color-fg-sub: #534c37; --color-fg-light: #c5c5c5; --color-border: #b4af9d; --color-border: hsl(47deg 13% 66% / 0.3); --color-subtle-overlay: hsl(0deg 0% 0% / 0.035); color: var(--color-fg);
-
@@ -60,13 +60,12 @@ }@media (prefers-color-scheme: dark) { :root { --color-bg: #222228; --color-bg-accent: #323135; --color-bg-light: #2b2b2d; --color-bg: hsl(240deg 8% 15%); --color-bg-accent: hsl(250deg 3% 17%); --color-bg-light: hsl(245deg 2% 16%); --color-fg: #fafafa; --color-fg-sub: #f3edd9; --color-fg-light: #c5c5c5; --color-border: #b4af9d; --color-subtle-overlay: hsl(0deg 0% 100% / 0.1); --canvas-node-bg-opacity: 0.1;
-
-
-
@@ -337,3 +337,23 @@ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></svg> ); } export function menu({ className, ...rest }: LucideIconProps) { return ( <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" {...rest} class={cls(c.icon, className)} > <line x1="4" x2="20" y1="12" y2="12" /> <line x1="4" x2="20" y1="6" y2="6" /> <line x1="4" x2="20" y1="18" y2="18" /> </svg> ); }
-
-
-
@@ -459,6 +459,8 @@ );} export interface JSONCanvasProps { className?: string; data: JSONCanvas<Hast.Nodes>; radius?: number;
-
@@ -467,7 +469,7 @@ arrowSize?: number;} export function jsonCanvas( { data, radius = 6, arrowSize = 20 }: JSONCanvasProps, { className, data, radius = 6, arrowSize = 20 }: JSONCanvasProps, ) { const boundingBox = getBoundingBox(data);
-
@@ -488,8 +490,11 @@ );return ( <svg className={className} xmlns="http://www.w3.org/2000/svg" viewbox={viewBox} width={boundingBox.width} height={boundingBox.height} data-original-width={boundingBox.width} data-original-height={boundingBox.height} >
-
-
-
@@ -10,27 +10,63 @@import type { JSONCanvas } from "../../../content_parser/json_canvas/types.ts"; import type { BuildContext } from "../context.ts"; import * as css from "../css.ts"; import { buildClasses, css, join } from "../css.ts"; import { layout, layoutStyles } from "../widgets/layout.tsx"; import { documentTree, documentTreeStyles } from "../widgets/document_tree.tsx"; import { footer, footerStyles } from "../widgets/footer.tsx"; import { title, titleStyles } from "../widgets/title.tsx"; import { pageMetadata, pageMetadataScript } from "../widgets/page_metadata.tsx"; import { jsonCanvas, jsonCanvasStyles, wrappedJsonCanvas, } from "../json_canvas/mod.tsx"; import { jsonCanvas, jsonCanvasStyles } from "../json_canvas/mod.tsx"; import { embedTemplate, template } from "./template.tsx"; export const jsonCanvasPageStyles = css.join( const c = buildClasses("p-jc", [ "meta", "title", "canvas", "svg", ]); const ownStyles = css` .${c.meta} { position: absolute; left: 8px; top: 8px; padding: 4px; border: 1px solid var(--color-border); border-radius: 2px; background-color: var(--color-bg-accent); z-index: 2; } .${c.title} { margin: 0; font-weight: normal; font-size: 1rem; line-height: 1.5; } .${c.canvas} { position: absolute; inset: 0; overflow: auto; z-index: 1; } .${c.svg} { padding: 8rem; overflow: visible; } `; export const jsonCanvasPageStyles = join( layoutStyles, documentTreeStyles, footerStyles, titleStyles, jsonCanvasStyles, ownStyles, ); export interface JsonCanvasPageProps {
-
@@ -46,14 +82,22 @@ template({context, scripts: [pageMetadataScript], body: layout({ fullscreen: true, nav: documentTree({ context }), footer: footer({ copyright: context.copyright }), main: h(null, [ title({ children: context.document.metadata.title }), pageMetadata({ context }), wrappedJsonCanvas({ data: content }), ]), }, context), main: ( <div> <div class={c.meta}> <h1 class={c.title}>{context.document.metadata.title}</h1> {pageMetadata({ context })} </div> <div class={c.canvas}> {jsonCanvas({ className: c.svg, data: content })} </div> </div> ), context, }), }), ]); }
-
-
-
@@ -8,7 +8,7 @@ import { h } from "../../../deps/esm.sh/hastscript/mod.ts";import type * as Hast from "../../../deps/esm.sh/hast/types.ts"; import type { BuildContext } from "../context.ts"; import * as css from "../css.ts"; import { buildClasses, css, join } from "../css.ts"; import type { TocItem } from "../hast/hast_util_toc_mut.ts"; import { layout, layoutStyles } from "../widgets/layout.tsx";
-
@@ -20,12 +20,54 @@ import { pageMetadata, pageMetadataScript } from "../widgets/page_metadata.tsx";import { embedTemplate, template } from "./template.tsx"; export const markdownPageStyles = css.join( const c = buildClasses("p-md", [ "main", "toc", "tocInner", ]); const ownStyles = css` .${c.main} { max-width: 50rem; padding: calc(var(--baseline) * 1rem) 1rem; margin: 0 auto; } @media (min-width: 110rem) { .${c.main} { max-width: 100%; display: grid; grid-template-columns: 50rem minmax(0, 1fr); grid-template-rows: min-content max-content; column-gap: 2rem; margin: 0; padding-inline-start: 64px; } .${c.main} > * { grid-column: 1; } .${c.toc} { grid-column: 2; grid-row: 1 / -1; } .${c.tocInner} { position: sticky; top: calc(var(--baseline) * 1rem); } } `; export const markdownPageStyles = join( layoutStyles, tocStyles, documentTreeStyles, footerStyles, titleStyles, ownStyles, ); export interface MarkdownPageProps {
-
@@ -45,15 +87,26 @@ template({context, scripts: [pageMetadataScript], body: layout({ aside: tocItems.length > 0 ? toc({ toc: tocItems }) : undefined, nav: documentTree({ context }), footer: footer({ copyright: context.copyright }), main: h(null, [ title({ children: context.document.metadata.title }), pageMetadata({ context }), content, ]), }, context), main: ( <div class={c.main}> <div> {title({ children: context.document.metadata.title })} {pageMetadata({ context })} </div> {tocItems.length > 0 ? ( <div class={c.toc}> {toc({ className: c.tocInner, toc: tocItems })} </div> ) : undefined} {content} </div> ), context, }), }), ]); }
-
-
-
@@ -7,212 +7,308 @@import { h, type Result } from "../../../deps/esm.sh/hastscript/mod.ts"; import type { BuildContext } from "../context.ts"; import { buildClasses, css } from "../css.ts"; import { buildClasses, css, join } from "../css.ts"; import * as lucide from "../icons/lucide.tsx"; const c = buildClasses("w-l", [ "layout", "headerBg", "header", "logo", "logoLink", "nav", "navInner", "footerBg", "footer", "appbar", "menu", "doctree", "siteInfo", "homeLink", "logoImage", "hiddenInput", "iconButton", "closeMenu", "main", "aside", "asideInner", "leftPadding", "paddingAppbarBorder", "fullscreenLayout", ]); export const layoutStyles = css` .${c.layout} { --_ends-shadow-color: hsl(0deg 0% 0% / 0.03); export const layoutStyles = join( lucide.lucideIconStyles, css` html { scroll-padding-top: calc(8px + 2.5rem); } display: grid; grid-template-columns: minmax(100vw, 1fr); min-height: 100vh; } .${c.fullscreenLayout} { display: flex; flex-direction: column; height: 100vh; height: 100dvh; .${c.layout} > * { padding: calc(var(--baseline) * 1rem) 16px; } overflow: auto; } .${c.layout} > * > :first-child { margin-block-start: 0; } .${c.appbar}, .${c.menu} { padding: 4px 8px; } .${c.headerBg}, .${c.header} { padding: calc(var(--baseline) * 0.5rem) 16px; } .${c.appbar} { position: sticky; top: 0; display: flex; justify-content: space-between; align-items: center; border-block-end: 1px solid var(--color-border); .${c.headerBg} { position: sticky; top: calc(var(--baseline) * -1rem); grid-row: 1; grid-column: 1 / -1; margin-block-end: calc(var(--baseline) * 2rem); border-block-end: 1px solid var(--_ends-shadow-color); background-color: var(--color-bg-accent); z-index: 100; } background-color: var(--color-bg-accent); } .${c.menu} { position: fixed; inset: 0; display: flex; flex-direction: column; justify-content: start; align-items: end; gap: 1rem; .${c.header} { grid-row: 1; grid-column: 1; margin-block-end: calc(var(--baseline) * 2rem); display: flex; position: sticky; top: calc(var(--baseline) * -0.5rem); } background-color: var(--color-bg-accent); z-index: 150; overflow-y: scroll; overflow-x: hidden; overscroll-behavior-y: contain; .${c.logoLink} { display: flex; transform: translateX(100%); transition: transform 0.15s ease-out; } border-radius: 4px; } .${c.logoLink}:hover { background-color: var(--color-subtle-overlay); } .${c.siteInfo} { display: flex; justify-content: start; align-items: center; gap: 0.5em; font-size: 0.9rem; line-height: 1.5; } .${c.logo} { padding: 4px; } .${c.homeLink} { display: flex; align-items: center; gap: 0.4em; font-size: 0.9rem; line-height: 1.25; font-weight: bold; .${c.nav} { grid-column: 1; } color: var(--color-fg); text-decoration: none; } .${c.homeLink}:hover { text-decoration: underline; } .${c.navInner}, .${c.asideInner} { position: sticky; top: calc(var(--baseline) * 1rem); } .${c.logoImage} { height: 1.5rem; width: auto; } @media (hover: hover) { .${c.navInner}, .${c.asideInner} { opacity: 0.7; transition: opacity 8s ease; .${c.hiddenInput} { display: none; } .${c.navInner}:hover, .${c.navInner}:focus-within, .${c.asideInner}:hover, .${c.asideInner}:focus-within { opacity: 1; transition-duration: 0.2s; #__macana_menu_open:checked ~ .${c.menu}, .${c.menu}:focus-within { transform: translateX(0); } } .${c.main} { grid-column: 1 / -1; } .${c.iconButton} { display: inline-flex; padding: 8px; font-size: 1.1rem; .${c.aside} { grid-column: 1 / -1; } border-radius: 3px; cursor: pointer; } .${c.iconButton}:hover { background-color: var(--color-subtle-overlay); } .${c.footerBg} { grid-row: 999; grid-column: 1 / -1; margin-block-start: calc(var(--baseline) * 2rem); border-block-start: 1px solid var(--_ends-shadow-color); .${c.closeMenu} { position: sticky; top: 4px; background-color: var(--color-bg-accent); } background-color: inherit; } .${c.footer} { grid-row: 999; grid-column: 1; margin-block-start: calc(var(--baseline) * 2rem); .${c.main} { position: relative; flex: 1; } color: var(--color-fg-sub); } .${c.leftPadding} { display: none; } @media (min-width: 700px) { .${c.layout} { grid-template-columns: 200px minmax(0, 1fr); } @media (pointer: fine) { .${c.iconButton} { padding: 6px; } } .${c.header}, .${c.footer} { grid-column: 1 / -1; } } .${c.doctree} { align-self: stretch; flex-grow: 1; } @media (min-width: 1000px) { .${c.layout} { grid-template-columns: 1fr min(700px, 100%) 1fr; } @media (min-width: 65rem) { html { scroll-padding-top: calc(var(--baseline) * 1rem); } .${c.main}, .${c.header}, .${c.footer} { grid-column: 2; } .${c.layout} { --_appbar-height: calc(var(--baseline) * 2rem); .${c.aside} { grid-column: 3; display: grid; grid-template-columns: 20rem minmax(0, 1fr); grid-template-rows: 100vh minmax(0, 1fr); grid-template-rows: 100dvh minmax(0, 1fr); grid-template-areas: "appbar main" "_ main"; } .${c.appbar} { grid-area: appbar; padding-block: 0; height: var(--_appbar-height); align-self: start; border-inline-end: 1px solid var(--color-border); z-index: 200; } .${c.menu} { grid-area: appbar; position: sticky; top: var(--_appbar-height); height: calc(100% - var(--_appbar-height)); padding-block-start: calc(var(--baseline) * 0.5rem); border-inline-end: 1px solid var(--color-border); transform: none; } .${c.iconButton} { display: none; } .${c.main} { grid-area: main; } } .${c.navInner}, .${c.asideInner} { margin-inline-start: auto; margin-inline-end: 0; max-width: 400px; @media (min-width: 100rem) { .${c.layout} { grid-template-columns: minmax(0, 1fr) 25rem minmax(0, 6fr); grid-template-areas: "padding appbar main" "padding _ main"; } .${c.leftPadding} { display: block; grid-area: padding; background-color: var(--color-bg-accent); } .${c.paddingAppbarBorder} { height: var(--_appbar-height); border-block-end: 1px solid var(--color-border); position: sticky; top: 0; } } } `; `, ); export interface LayoutProps { fullscreen?: boolean; /** * Site navigation content. */ nav: Result; aside?: Result; /** * Content shown inside `<main>`. */ main: Result; footer: Result; context: BuildContext; } export function layout({ fullscreen = false, nav, aside, main, footer, }: LayoutProps, ctx: BuildContext) { const { assets, resolvePath, documentTree: { defaultDocument } } = ctx; context, }: LayoutProps) { const { assets, resolvePath, documentTree: { defaultDocument }, websiteTitle, } = context; return ( <div class={c.layout}> <div class={c.headerBg} /> {assets.siteLogo && ( <header class={c.header}> <a class={c.logoLink} href={resolvePath([...defaultDocument.path, ""]).join("/")} title={defaultDocument.metadata.title} lang={defaultDocument.metadata.language} > <div class={[c.layout, fullscreen && c.fullscreenLayout].filter(( s, ): s is string => !!s)} > <div class={c.leftPadding}> <div class={c.paddingAppbarBorder} /> </div> <input class={c.hiddenInput} type="checkbox" id="__macana_menu_open" aria-label="Menu opened" /> <header class={c.appbar}> <a class={c.homeLink} href={resolvePath([...defaultDocument.path, ""]).join("/")} title={defaultDocument.metadata.title} lang={defaultDocument.metadata.language} > {assets.siteLogo && ( <img class={c.logo} class={c.logoImage} src={resolvePath(assets.siteLogo).join("/")} width={32} height={32} /> </a> </header> )} <nav class={c.nav}> <div class={c.navInner}>{nav}</div> </nav> )} <span>{websiteTitle}</span> </a> <label class={c.iconButton} for="__macana_menu_open" aria-hidden="true"> {lucide.menu({})} </label> </header> <div class={c.menu}> <label className={[c.iconButton, c.closeMenu]} for="__macana_menu_open" aria-hidden="true" > {lucide.x({})} </label> <nav class={c.doctree}>{nav}</nav> <footer>{footer}</footer> </div> <main class={c.main}>{main}</main> {aside && ( <aside class={c.aside}> <div class={c.asideInner}>{aside}</div> </aside> )} <div class={c.footerBg} /> <footer class={c.footer}>{footer}</footer> </div> ); }
-
-
-
@@ -7,7 +7,7 @@import { h } from "../../../deps/esm.sh/hastscript/mod.ts"; import type { TocItem } from "../hast/hast_util_toc_mut.ts"; import { buildClasses, css } from "../css.ts"; import { buildClasses, css, cx } from "../css.ts"; const c = buildClasses("w-toc", ["root", "list", "item", "link"]);
-
@@ -39,12 +39,14 @@ }`; export interface TocProps { className?: string; toc: readonly TocItem[]; } export function toc({ toc }: TocProps) { export function toc({ className, toc }: TocProps) { return ( <div class={c.root}> <div class={cx(c.root, className)}> {items({ toc })} </div> );
-