Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,7 @@ margin, hovering a passage shows who said what, and **F7** steps to the next com
**Comments** panel lists every thread, with what others wrote since you last opened the
manuscript marked as new, and can be filtered to one person's comments or your own.

The sidebar's tabs can be opened side by side: the split button beside them shows a second
panel beneath, so the outline and comments can be read together. Hiding the sidebar leaves a
rail of its tabs.
Hiding the sidebar leaves a rail of its tabs.

Click a block in the preview to jump to the corresponding text. The preview also follows the
editor's cursor. **Problems** in the top bar lists unknown citekeys, missing labels, unavailable
Expand Down
10 changes: 0 additions & 10 deletions apps/writer/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -253,16 +253,6 @@ kbd { font-family: var(--mono); font-size: 11.5px; padding: 1px 5px; border: 1px
.sidebar-tabs button:focus-visible { outline-offset: -2px; }
.tab-count { font-size: 11px; font-weight: 400; color: var(--faint); font-variant-numeric: tabular-nums; }
.sidebar-panel { min-height: 0; overflow: auto; padding: 0.5rem 0.5rem 1.5rem; scrollbar-gutter: stable; }
/* A second panel beneath the first, with its own small tabs. */
.outline.has-lower { grid-template-rows: auto minmax(0, 1fr) minmax(0, 1fr); }
.sidebar-lower { display: grid; grid-template-rows: auto minmax(0, 1fr); min-height: 0; border-top: 1px solid var(--line); }
.sidebar-lower-tabs { display: flex; align-items: center; gap: 0.9rem; min-height: 32px; padding: 0 0.5rem 0 1rem; border-bottom: 1px solid var(--line); }
.sidebar-lower-tabs > [role="tab"] { padding: 0; border: 0; background: none; color: var(--muted); font-size: 12px; cursor: pointer; }
.sidebar-lower-tabs > [role="tab"]:hover { color: var(--ink); }
.sidebar-lower-tabs > [role="tab"][aria-selected="true"] { color: var(--ink); font-weight: 700; }
.sidebar-lower-tabs > .mdbase-icon-button { margin-left: auto; }
.sidebar-split { margin-left: auto; align-self: center; color: var(--muted); }
.sidebar-split[aria-pressed="true"] { color: var(--ink); }
/* The sidebar hidden leaves a rail of its tabs. */
.sidebar-rail { grid-area: rail; display: grid; align-content: start; justify-items: center; gap: 0.25rem; padding: 0.5rem 0; border-right: 1px solid var(--line); background: var(--canvas); }
.rail-button { position: relative; display: grid; place-items: center; width: 32px; height: 32px; border: 0; border-radius: 6px; background: none; color: var(--muted); cursor: pointer; }
Expand Down
37 changes: 8 additions & 29 deletions apps/writer/src/ui/WorkspaceView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import {
PlusIcon,
SidebarIcon,
SplitIcon,
SplitVerticalIcon,
} from "./icons.js";
import { readerSourceHref } from "../apps.js";
import { errorMessage } from "../async.js";
Expand Down Expand Up @@ -213,11 +212,8 @@ export function WorkspaceView({ workspace, since, onClose }: { workspace: Manusc
const toggleLivePreview = useCallback(() => setLayout((l) => ({ ...l, livePreview: !l.livePreview })), [setLayout]);
const setSidebarTab = useCallback((tab: SidebarTab) => {
rememberedTabs.set(workspace.main, tab);
// The lower panel never repeats the upper: the two swap.
setLayout((l) => ({ ...l, tab, lowerTab: l.lowerTab === tab ? l.tab : l.lowerTab }));
setLayout((l) => ({ ...l, tab }));
}, [setLayout, workspace.main]);
const setLowerTab = useCallback((lowerTab: SidebarTab | null) => setLayout((l) => ({ ...l, lowerTab: lowerTab === l.tab ? null : lowerTab })), [setLayout]);
const toggleLower = useCallback(() => setLayout((l) => ({ ...l, lowerTab: l.lowerTab ? null : l.tab === "comments" ? "outline" : "comments" })), [setLayout]);

const order = useMemo(() => workspace.readingOrder(), [workspace, snap.records, snap.recordPaths, snap.annotationPaths]);
const activeView = snap.records.get(active);
Expand Down Expand Up @@ -282,7 +278,7 @@ export function WorkspaceView({ workspace, since, onClose }: { workspace: Manusc
}, [placed, active]);

const showComments = useCallback(() => {
setLayout((l) => (l.sidebar && (l.tab === "comments" || l.lowerTab === "comments") ? l : { ...l, sidebar: true, tab: "comments", lowerTab: l.lowerTab === "comments" ? l.tab : l.lowerTab }));
setLayout((l) => (l.sidebar && l.tab === "comments" ? l : { ...l, sidebar: true, tab: "comments" }));
setPane("outline");
}, [setLayout]);

Expand Down Expand Up @@ -742,9 +738,9 @@ export function WorkspaceView({ workspace, since, onClose }: { workspace: Manusc
request={sourcesRequest}
/>
);
/** A sidebar panel's contents (Sources in the upper slot is rendered by the sidebar itself, kept mounted). */
/** The outline or comments panel (Sources is rendered by the sidebar itself, kept mounted). */
const panelFor = (tab: SidebarTab) =>
tab === "sources" ? sourcesPanel : (
tab === "sources" ? null : (
tab === "outline" ? (
<>
<ManuscriptSearch
Expand Down Expand Up @@ -901,32 +897,19 @@ export function WorkspaceView({ workspace, since, onClose }: { workspace: Manusc
</div>
</InTopbar>

<aside className={`outline${layout.lowerTab ? " has-lower" : ""}`} aria-label="Manuscript sidebar">
<aside className="outline" aria-label="Manuscript sidebar">
<SidebarTabs
tab={layout.tab}
onTab={setSidebarTab}
sources={snap.library.length}
comments={openThreads.length}
commentsTitle={openCount(placed)}
split={layout.lowerTab !== null}
onSplit={toggleLower}
/>
<div ref={sidebarPanel} onScroll={(e) => sidebarScroll.current.set(layout.tab, e.currentTarget.scrollTop)} className="sidebar-panel" role="tabpanel" id={`sidebar-${layout.tab}`} aria-labelledby={`sidebar-tab-${layout.tab}`}>
{layout.tab !== "sources" && panelFor(layout.tab)}
{/* Sources stays mounted (hidden) so its search, expanded source and scroll last across tab switches. */}
{layout.lowerTab !== "sources" && <div hidden={layout.tab !== "sources"}>{sourcesPanel}</div>}
<div hidden={layout.tab !== "sources"}>{sourcesPanel}</div>
</div>
{layout.lowerTab && (
<div className="sidebar-lower">
<div className="sidebar-lower-tabs" role="tablist" aria-label="Lower panel">
{SIDEBAR_TABS.filter((t) => t !== layout.tab).map((t) => (
<button key={t} type="button" role="tab" aria-selected={layout.lowerTab === t} onClick={() => setLowerTab(t)}>{TAB_NAME[t]}</button>
))}
<button type="button" className="mdbase-icon-button is-small" aria-label="Close the lower panel" title="Close the lower panel" onClick={() => setLowerTab(null)}><CloseIcon /></button>
</div>
<div className="sidebar-panel is-lower" role="tabpanel">{panelFor(layout.lowerTab)}</div>
</div>
)}
<SidebarResizer width={layout.sidebarWidth} onChange={(sidebarWidth) => setLayout((l) => ({ ...l, sidebarWidth }))} />
</aside>

Expand Down Expand Up @@ -1159,7 +1142,6 @@ export function WorkspaceView({ workspace, since, onClose }: { workspace: Manusc
{ id: "comments", group: "Comments", label: "Show comments", shortcut: "mod+shift+m", run: showComments },
{ id: "next-comment", group: "Comments", label: "Next comment", shortcut: "F7", run: () => stepComment(1) },
{ id: "previous-comment", group: "Comments", label: "Previous comment", shortcut: "shift+F7", run: () => stepComment(-1) },
{ id: "lower-panel", group: "View", label: layout.lowerTab ? "Close the lower sidebar panel" : "Show a second sidebar panel", keywords: "split stack outline comments sources", run: toggleLower },
{ id: "next-problem", group: "Manuscript", label: "Next problem", shortcut: "F8", run: () => stepProblem(1) },
{ id: "manuscripts", group: "Manuscript", label: "All manuscripts", keywords: "back home close", run: () => void requestClose() },
{ id: "sidebar", group: "View", label: layout.sidebar ? "Hide the sidebar" : "Show the sidebar", shortcut: "mod+\\", run: () => setLayout((l) => ({ ...l, sidebar: !l.sidebar })) },
Expand Down Expand Up @@ -1221,8 +1203,8 @@ export function WorkspaceView({ workspace, since, onClose }: { workspace: Manusc
);
}

/** Outline, Sources and Comments, as tabs: ←/→ move between them. A second panel can open beneath. */
function SidebarTabs({ tab, onTab, sources, comments, commentsTitle, split, onSplit }: { tab: SidebarTab; onTab(tab: SidebarTab): void; sources: number; comments: number; commentsTitle: string; split: boolean; onSplit(): void }) {
/** Outline, Sources and Comments, as tabs: ←/→ move between them. */
function SidebarTabs({ tab, onTab, sources, comments, commentsTitle }: { tab: SidebarTab; onTab(tab: SidebarTab): void; sources: number; comments: number; commentsTitle: string }) {
const tabs = SIDEBAR_TABS;
const onKeyDown = (e: React.KeyboardEvent) => {
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight" && e.key !== "Home" && e.key !== "End") return;
Expand Down Expand Up @@ -1251,9 +1233,6 @@ function SidebarTabs({ tab, onTab, sources, comments, commentsTitle, split, onSp
{t === "comments" && comments > 0 && <span className="tab-count">{comments}</span>}
</button>
))}
<button type="button" className="mdbase-icon-button is-small sidebar-split" aria-pressed={split} onClick={onSplit} title={split ? "Close the lower panel" : "Show a second panel beneath (outline and comments together)"} aria-label="Second panel">
<SplitVerticalIcon />
</button>
</div>
);
}
Expand Down
2 changes: 0 additions & 2 deletions apps/writer/src/ui/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import { PlusIcon as PhPlus } from "@phosphor-icons/react/Plus";
import { PlusSquareIcon as PhPlusSquare } from "@phosphor-icons/react/PlusSquare";
import { ChatCircleIcon as PhChatCircle } from "@phosphor-icons/react/ChatCircle";
import { BooksIcon as PhBooks } from "@phosphor-icons/react/Books";
import { SquareSplitVerticalIcon as PhSquareSplitVertical } from "@phosphor-icons/react/SquareSplitVertical";
import { SidebarSimpleIcon as PhSidebarSimple } from "@phosphor-icons/react/SidebarSimple";
import { TextBIcon as PhTextB } from "@phosphor-icons/react/TextB";
import { TextItalicIcon as PhTextItalic } from "@phosphor-icons/react/TextItalic";
Expand Down Expand Up @@ -55,7 +54,6 @@ export const PlusIcon = icon(PhPlus);
export const InsertIcon = icon(PhPlusSquare);
export const ChatIcon = icon(PhChatCircle);
export const BooksIcon = icon(PhBooks);
export const SplitVerticalIcon = icon(PhSquareSplitVertical);
export const MinusIcon = icon(PhMinus);
export const KeyboardIcon = icon(PhKeyboard);
export const MoreIcon = icon(PhDotsThree);
Expand Down
5 changes: 1 addition & 4 deletions apps/writer/src/ui/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ export type SidebarTab = "outline" | "sources" | "comments";
export interface Layout {
readonly sidebar: boolean;
readonly tab: SidebarTab;
/** A second panel shown under the first, or none. */
readonly lowerTab: SidebarTab | null;
/** The sidebar's width in CSS pixels. */
readonly sidebarWidth: number;
readonly view: View;
Expand All @@ -21,7 +19,7 @@ export interface Layout {
}

const KEY = "mdbase-writer:layout";
export const DEFAULT_LAYOUT: Layout = { sidebar: true, tab: "outline", lowerTab: null, sidebarWidth: 272, view: "both", split: 0.5, zoom: "fit", joinLines: true, livePreview: true };
export const DEFAULT_LAYOUT: Layout = { sidebar: true, tab: "outline", sidebarWidth: 272, view: "both", split: 0.5, zoom: "fit", joinLines: true, livePreview: true };
export const ZOOM_STEPS = [0.5, 0.67, 0.75, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2] as const;

export const SIDEBAR_MIN = 200;
Expand All @@ -35,7 +33,6 @@ export function loadLayout(): Layout {
return {
sidebar: typeof raw.sidebar === "boolean" ? raw.sidebar : DEFAULT_LAYOUT.sidebar,
tab: raw.tab === "sources" || raw.tab === "comments" ? raw.tab : "outline",
lowerTab: raw.lowerTab === "outline" || raw.lowerTab === "sources" || raw.lowerTab === "comments" ? raw.lowerTab : null,
sidebarWidth: typeof raw.sidebarWidth === "number" ? clampSidebar(raw.sidebarWidth) : DEFAULT_LAYOUT.sidebarWidth,
view: raw.view === "write" || raw.view === "preview" || raw.view === "both" ? raw.view : DEFAULT_LAYOUT.view,
split: typeof raw.split === "number" ? clampSplit(raw.split) : DEFAULT_LAYOUT.split,
Expand Down