some more fixes

This commit is contained in:
pj committed 2026-10-03 22:48:49 +05:30
1 parent 7a3c04f170
commit 2b86a407ba
63 files changed
+12172 -44

No files matched your search

+16 -1
View File
@@ -1,4 +1,4 @@
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { listen } from "@tauri-apps/api/event";
import { openUrl } from "@tauri-apps/plugin-opener";
import { getCurrentWindow } from "@tauri-apps/api/window";
@@ -7,6 +7,7 @@ import { EditorView } from "./components/EditorView";
import { BackupSettings } from "./components/BackupSettings";
import { ExportPreview } from "./components/ExportPreview";
import { UpdateDialog } from "./components/UpdateDialog";
import { KeyboardShortcuts } from "./components/KeyboardShortcuts";
import { useBook } from "./store/useBook";
import { useBackup } from "./store/useBackup";
import { useExportPreview } from "./store/useExportPreview";
@@ -18,6 +19,18 @@ import { checkForUpdates } from "./updater";
function App() {
const book = useBook((s) => s.book);
const openBook = useBook((s) => s.openBook);
const [shortcutsOpen, setShortcutsOpen] = useState(false);
useEffect(() => {
const onKey = (event: KeyboardEvent) => {
if ((event.metaKey || event.ctrlKey) && event.shiftKey && !event.altKey && event.code === "Slash") {
event.preventDefault();
setShortcutsOpen(true);
}
};
window.addEventListener("keydown", onKey, true);
return () => window.removeEventListener("keydown", onKey, true);
}, []);
useEffect(() => {
if (!isDesktop) return;
@@ -30,6 +43,7 @@ function App() {
}
else if (event.payload === "export-epub") runExport("epub");
else if (event.payload === "check-updates") checkForUpdates(false);
else if (event.payload === "keyboard-shortcuts") setShortcutsOpen(true);
else if (event.payload === "report-issue")
openUrl("https://github.com/priyanshujain/margin/issues").catch(() => {});
});
@@ -105,6 +119,7 @@ function App() {
{isDesktop && <BackupSettings />}
{isDesktop && <ExportPreview />}
{isDesktop && <UpdateDialog />}
<KeyboardShortcuts open={shortcutsOpen} onClose={() => setShortcutsOpen(false)} />
</>
);
}
+1 -1
View File
@@ -162,7 +162,7 @@ export function BackupSettings() {
title="Restore from Google Drive"
message={
<>
This replaces your local books with the copies in Google Drive. Any local changes that haven't been backed
This replaces your local projects with the copies in Google Drive. Any local changes that haven't been backed
up will be lost.
</>
}
+2 -2
View File
@@ -473,8 +473,8 @@ export function EditorView() {
coords={proofPopover.coords}
onReplace={(suggestion) => {
const { from, to } = proofPopover.issue;
if (suggestion === "") editor.chain().focus().deleteRange({ from, to }).run();
else editor.chain().focus().insertContentAt({ from, to }, suggestion).run();
if (suggestion === "") editor.chain().focus(undefined, { scrollIntoView: false }).deleteRange({ from, to }).run();
else editor.chain().focus(undefined, { scrollIntoView: false }).insertContentAt({ from, to }, suggestion).run();
setProofPopover(null);
}}
onIgnore={() => {
+118
View File
@@ -0,0 +1,118 @@
import { useRef } from "react";
import { useEscapeLayer } from "../escape";
import { useFocusTrap } from "../focus";
import { isDesktop } from "../ipc";
import { Icon } from "./Icon";
import "../styles/shortcuts.css";
const mac = navigator.platform.includes("Mac");
const mod = mac ? "⌘" : "Ctrl";
const alt = mac ? "⌥" : "Alt";
const shift = mac ? "⇧" : "Shift";
const groups: { title: string; shortcuts: [string, string[]][] }[] = [
{
title: "Projects",
shortcuts: [
["New project", [mod, "N"]],
["Save", [mod, "S"]],
["Settings", [mod, ","]],
["Export PDF", [mod, shift, "P"]],
["Export EPUB", [mod, shift, "E"]],
["Keyboard shortcuts", [mod, shift, "/"]],
...(mac && isDesktop ? [["Open window", [mod, shift, "M"]] as [string, string[]]] : []),
],
},
{
title: "Editing",
shortcuts: [
["Undo", [mod, "Z"]],
["Redo", [mod, shift, "Z"]],
["Cut", [mod, "X"]],
["Copy", [mod, "C"]],
["Paste", [mod, "V"]],
["Paste without formatting", [mod, shift, "V"]],
["Select all", [mod, "A"]],
],
},
{
title: "Formatting",
shortcuts: [
["Bold", [mod, "B"]],
["Italic", [mod, "I"]],
["Underline", [mod, "U"]],
["Strikethrough", [mod, shift, "X"]],
["Add or edit link", [mod, "K"]],
["Heading", [mod, alt, "2 / 3"]],
["Paragraph", [mod, alt, "0"]],
["Bullet list", [mod, shift, "8"]],
["Numbered list", [mod, shift, "7"]],
["Task list", [mod, shift, "9"]],
["Quote", [mod, shift, "B"]],
["Align left", [mod, shift, "L"]],
...(!isDesktop ? [["Align center", [mod, shift, "E"]] as [string, string[]]] : []),
["Align right", [mod, shift, "R"]],
["Indent / remove indent", ["Tab / Shift Tab"]],
["Line break", [shift, "Enter"]],
],
},
{
title: "Navigation and search",
shortcuts: [
["Find", [mod, "F"]],
...(mac ? [
["Find and replace", [mod, alt, "F"]],
["Toggle chapters", [mod, "\\"]],
["Next / previous chapter", [mod, alt, "↓ / ↑"]],
["Next / previous chapter", [mod, alt, "→ / ←"]],
] as [string, string[]][] : []),
["Next chapter", ["Ctrl", "Tab"]],
["Previous chapter", ["Ctrl", shift, "Tab"]],
["Next / previous search result", ["Enter / Shift Enter"]],
["Close popup", ["Esc"]],
["Return to all projects", ["Esc twice"]],
],
},
...(mac && isDesktop ? [{
title: "Apple Writing Tools",
shortcuts: [
["Proofread", [alt, shift, "F"]],
["Rewrite", [alt, shift, "R"]],
] as [string, string[]][],
}] : []),
];
export function KeyboardShortcuts({ open, onClose }: { open: boolean; onClose: () => void }) {
const ref = useRef<HTMLDivElement>(null);
useEscapeLayer(open, onClose);
useFocusTrap(ref, open);
if (!open) return null;
return (
<div className="overlay" onClick={onClose}>
<div ref={ref} className="panel shortcuts-panel" role="dialog" aria-modal="true" aria-labelledby="shortcuts-title" onClick={(event) => event.stopPropagation()}>
<div className="panel-head">
<h2 id="shortcuts-title">Keyboard shortcuts</h2>
<button className="icon-btn" onClick={onClose} title="Close" aria-label="Close keyboard shortcuts">
<Icon d="M6 6l12 12M18 6L6 18" />
</button>
</div>
<div className="panel-body shortcuts-groups">
{groups.map((group) => (
<section key={group.title} className="shortcuts-group">
<h3>{group.title}</h3>
<dl>
{group.shortcuts.map(([label, keys]) => (
<div className="shortcut-row" key={label + keys.join()}>
<dt>{label}</dt>
<dd>{keys.map((key) => <kbd key={key}>{key}</kbd>)}</dd>
</div>
))}
</dl>
</section>
))}
</div>
</div>
</div>
);
}
+1 -1
View File
@@ -146,7 +146,7 @@ export function RowMenu({ label, onDuplicate, onMove, onDelete, onToggleTitle, t
{onMove && (
<button className="row-menu-item" onClick={moveOut}>
<Icon d="M4 12h13M13 8l4 4-4 4M20 4v16" size={14} />
Move to book…
Move to project…
</button>
)}
<button className="row-menu-item danger" onClick={remove}>
+18
View File
@@ -8,6 +8,20 @@ import { RowMenu } from "./RowMenu";
import { MoveChapterDialog } from "./MoveChapterDialog";
import { relativeTime } from "../time";
import { isDesktop } from "../ipc";
import type { JSONContent } from "@tiptap/core";
function countTasks(content: JSONContent): { completed: number; total: number } {
const count = { completed: 0, total: 0 };
const visit = (node: JSONContent) => {
if (node.type === "taskItem") {
count.total++;
if (node.attrs?.checked) count.completed++;
}
node.content?.forEach(visit);
};
visit(content);
return count;
}
interface Row {
chapter: Chapter;
@@ -194,6 +208,7 @@ export function Sidebar({ onNavigate }: { onNavigate?: () => void }) {
<ul className="chapters">
{group.rows.map((row, i) => {
const isPart = chapterKind(row.chapter) === "part";
const tasks = countTasks(row.chapter.content);
const partLabel = `Part ${partRoman(row.part ?? 0)}`;
const rowLabel = isPart
? row.chapter.title
@@ -241,6 +256,9 @@ export function Sidebar({ onNavigate }: { onNavigate?: () => void }) {
{row.chapter.id === activeChapterId && (
<span className="meta">Edited {relativeTime(row.chapter.updatedAt, now)}</span>
)}
{tasks.total > 0 && (
<span className="meta">{tasks.completed} of {tasks.total} tasks completed</span>
)}
</span>
<RowMenu
label="Page options"
+47 -10
View File
@@ -1,4 +1,5 @@
import { useEffect, useReducer, useRef, useState, type ReactNode } from "react";
import { useEffect, useLayoutEffect, useReducer, useRef, useState, type ReactNode } from "react";
import { createPortal } from "react-dom";
import type { Editor } from "@tiptap/react";
import { Icon } from "../components/Icon";
import { useEscapeLayer } from "../escape";
@@ -33,6 +34,7 @@ export function FloatingToolbar({ editor }: { editor: Editor | null }) {
const alignPopRef = useRef<HTMLDivElement>(null);
const [linkOpen, setLinkOpen] = useState(false);
const [linkValue, setLinkValue] = useState("");
const [linkPosition, setLinkPosition] = useState({ left: 0, top: 0 });
const [alignOpen, setAlignOpen] = useState(false);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
@@ -45,6 +47,39 @@ export function FloatingToolbar({ editor }: { editor: Editor | null }) {
useFocusTrap(linkPopRef, linkOpen);
useFocusTrap(alignPopRef, alignOpen);
useLayoutEffect(() => {
if (!editor || !linkOpen) return;
const position = () => {
const popover = linkPopRef.current;
if (!popover) return;
const { from, to } = editor.state.selection;
const start = editor.view.coordsAtPos(from);
const end = editor.view.coordsAtPos(to);
const pane = editor.view.dom.closest(".editor-pane")?.getBoundingClientRect();
const width = popover.offsetWidth;
const height = popover.offsetHeight;
const leftEdge = Math.max(12, pane?.left ?? 0);
const rightEdge = Math.min(window.innerWidth - 12, pane?.right ?? window.innerWidth);
const topEdge = Math.max(12, pane?.top ?? 0);
const bottomEdge = Math.min(window.innerHeight - 12, pane?.bottom ?? window.innerHeight);
const above = start.top - height - 8;
const below = end.bottom + 8;
setLinkPosition({
left: Math.max(leftEdge, Math.min(start.left, rightEdge - width)),
top: Math.max(topEdge, Math.min(above >= topEdge ? above : below, bottomEdge - height)),
});
};
position();
window.addEventListener("scroll", position, true);
window.addEventListener("resize", position);
editor.on("transaction", position);
return () => {
window.removeEventListener("scroll", position, true);
window.removeEventListener("resize", position);
editor.off("transaction", position);
};
}, [editor, linkOpen]);
useEffect(() => {
if (!editor) return;
const update = () => forceUpdate();
@@ -71,7 +106,7 @@ export function FloatingToolbar({ editor }: { editor: Editor | null }) {
useEscapeLayer(linkOpen, () => {
setLinkOpen(false);
editor?.commands.focus();
editor?.commands.focus(undefined, { scrollIntoView: false });
});
useEscapeLayer(alignOpen, () => setAlignOpen(false));
@@ -85,17 +120,17 @@ export function FloatingToolbar({ editor }: { editor: Editor | null }) {
const applyLink = () => {
const href = normalizeUrl(linkValue);
if (!href) {
editor.chain().focus().extendMarkRange("link").unsetLink().run();
editor.chain().focus(undefined, { scrollIntoView: false }).extendMarkRange("link").unsetLink().run();
} else if (editor.state.selection.empty && !editor.isActive("link")) {
editor.chain().focus().insertContent({ type: "text", text: href, marks: [{ type: "link", attrs: { href } }] }).run();
editor.chain().focus(undefined, { scrollIntoView: false }).insertContent({ type: "text", text: href, marks: [{ type: "link", attrs: { href } }] }).run();
} else {
editor.chain().focus().extendMarkRange("link").setLink({ href }).run();
editor.chain().focus(undefined, { scrollIntoView: false }).extendMarkRange("link").setLink({ href }).run();
}
setLinkOpen(false);
};
const removeLink = () => {
editor.chain().focus().extendMarkRange("link").unsetLink().run();
editor.chain().focus(undefined, { scrollIntoView: false }).extendMarkRange("link").unsetLink().run();
setLinkOpen(false);
};
@@ -126,6 +161,7 @@ export function FloatingToolbar({ editor }: { editor: Editor | null }) {
{tool(editor.isActive("heading", { level: 2 }), () => editor.chain().focus().toggleHeading({ level: 2 }).run(), "Heading", <Icon d="M5 5v14M5 12h8M13 5v14" />)}
{tool(editor.isActive("blockquote"), () => editor.chain().focus().toggleBlockquote().run(), "Quote", <Icon d="M7 8h4v4a4 4 0 0 1-4 4M14 8h4v4a4 4 0 0 1-4 4" />)}
{tool(editor.isActive("bulletList"), () => editor.chain().focus().toggleBulletList().run(), "Bulleted list", <Icon d="M8 6h12M8 12h12M8 18h12M3.5 6h.01M3.5 12h.01M3.5 18h.01" />)}
{tool(editor.isActive("taskList"), () => editor.chain().focus().toggleTaskList().run(), "Task list (⌘⇧9)", <Icon d="M4 4h6v6H4zM5 7l1.5 1.5L9 5M14 7h6M4 15h6v6H4zM14 18h6" />)}
<span className="tool-wrap">
{tool(alignOpen || align !== "left", () => setAlignOpen((v) => !v), "Align", <Icon d={ALIGN_ICONS[align]} />)}
{alignOpen && (
@@ -152,11 +188,12 @@ export function FloatingToolbar({ editor }: { editor: Editor | null }) {
<span className="tool-wrap">
{tool(editor.isActive("link") || linkOpen, () => (linkOpen ? setLinkOpen(false) : openLink()), "Link (⌘K)", <Icon d="M10 13a5 5 0 0 0 7 0l2-2a5 5 0 0 0-7-7l-1 1M14 11a5 5 0 0 0-7 0l-2 2a5 5 0 0 0 7 7l1-1" />)}
{linkOpen && (
<>
<div className="link-pop-backdrop" onMouseDown={() => setLinkOpen(false)} />
<div ref={linkPopRef} className="link-pop" onMouseDown={(e) => e.stopPropagation()}>
createPortal(<>
<div className="link-pop-backdrop link-editor-backdrop" onMouseDown={() => setLinkOpen(false)} />
<div ref={linkPopRef} className="link-pop" style={linkPosition} role="dialog" aria-label="Edit link" onMouseDown={(e) => e.stopPropagation()}>
<input
className="link-input"
aria-label="Link URL"
value={linkValue}
placeholder="https://…"
spellCheck={false}
@@ -177,7 +214,7 @@ export function FloatingToolbar({ editor }: { editor: Editor | null }) {
</button>
)}
</div>
</>
</>, document.body)
)}
</span>
{tool(false, () => editor.chain().focus().setHorizontalRule().run(), "Scene break", <Icon d="M5 12h5M14 12h5" />)}
+3
View File
@@ -1,6 +1,7 @@
import type { Extensions } from "@tiptap/core";
import StarterKit from "@tiptap/starter-kit";
import Placeholder from "@tiptap/extension-placeholder";
import { TaskList, TaskItem } from "@tiptap/extension-list";
import { Figure } from "./figure";
import { ParagraphIndent } from "./indent";
import { TextAlign } from "./align";
@@ -19,6 +20,8 @@ export const editorExtensions: Extensions = [
placeholder: ({ node }) => (node.type.name === "heading" ? "" : "Begin your chapter…"),
}),
Figure,
TaskList,
TaskItem.configure({ nested: true }),
ParagraphIndent,
TextAlign,
SearchHighlight,
+2 -2
View File
@@ -22,12 +22,12 @@ export const ParagraphIndent = Extension.create({
addKeyboardShortcuts() {
return {
Tab: () => {
if (this.editor.isActive("listItem")) return false;
if (this.editor.isActive("listItem") || this.editor.isActive("taskItem")) return false;
this.editor.commands.updateAttributes("paragraph", { indent: true });
return true;
},
"Shift-Tab": () => {
if (this.editor.isActive("listItem")) return false;
if (this.editor.isActive("listItem") || this.editor.isActive("taskItem")) return false;
this.editor.commands.updateAttributes("paragraph", { indent: false });
return true;
},
+16 -7
View File
@@ -1,6 +1,9 @@
import { Extension } from "@tiptap/core";
import type { JSONContent } from "@tiptap/core";
import type { Editor, JSONContent } from "@tiptap/core";
import { Plugin } from "@tiptap/pm/state";
import { readText } from "@tauri-apps/plugin-clipboard-manager";
import { isDesktop } from "../ipc";
import { useBook } from "../store/useBook";
function readImage(file: File): Promise<string> {
return new Promise((resolve, reject) => {
@@ -35,6 +38,17 @@ function plainContent(text: string): JSONContent[] {
});
}
async function pastePlain(editor: Editor): Promise<void> {
try {
const text = await (isDesktop ? readText() : navigator.clipboard.readText());
if (text && !editor.isDestroyed) {
editor.chain().focus(undefined, { scrollIntoView: false }).insertContent(plainContent(text)).run();
}
} catch (error) {
useBook.getState().setNotice(`Could not paste without formatting: ${error}`);
}
}
export const Paste = Extension.create({
name: "pasteHandler",
@@ -42,12 +56,7 @@ export const Paste = Extension.create({
const editor = this.editor;
return {
"Mod-Shift-v": () => {
navigator.clipboard
.readText()
.then((text) => {
if (text) editor.chain().focus().insertContent(plainContent(text)).run();
})
.catch(() => {});
void pastePlain(editor);
return true;
},
};
+17
View File
@@ -159,6 +159,10 @@ function block(node: JSONContent, paths: Map<string, string>): string {
return `<ul>${(node.content ?? []).map((li) => listItem(li, paths)).join("")}</ul>`;
case "orderedList":
return `<ol>${(node.content ?? []).map((li) => listItem(li, paths)).join("")}</ol>`;
case "taskList":
return `<ul data-type="taskList">${(node.content ?? []).map((item) =>
`<li data-type="taskItem" data-checked="${!!item.attrs?.checked}"><span aria-label="${item.attrs?.checked ? "Completed" : "Incomplete"}">${item.attrs?.checked ? "&#9745;" : "&#9744;"}</span><div>${(item.content ?? []).map((child) => block(child, paths)).join("")}</div></li>`,
).join("")}</ul>`;
case "horizontalRule":
return `<hr class="scene-break"/>`;
case "figure":
@@ -500,6 +504,19 @@ li {
text-align: left;
}
ul[data-type="taskList"] {
list-style: none;
padding-left: 0;
}
li[data-type="taskItem"] > span {
float: left;
}
li[data-type="taskItem"] > div {
margin-left: 1.6em;
}
hr.scene-break {
border: 0;
margin: 1.6em 0;
+4
View File
@@ -111,6 +111,10 @@ function block(node: JSONContent, paths: Map<string, string>): string {
return `#list(${(node.content ?? []).map((li) => listItem(li, paths)).join(", ")})`;
case "orderedList":
return `#enum(${(node.content ?? []).map((li) => listItem(li, paths)).join(", ")})`;
case "taskList":
return (node.content ?? []).map((item) =>
`#list(marker: [${item.attrs?.checked ? "\\[x\\]" : "\\[ \\]"}], ${listItem(item, paths)})`,
).join("\n\n");
case "horizontalRule":
return "#scenebreak";
case "figure":
+5 -5
View File
@@ -25,7 +25,7 @@ export function useFocusTrap(ref: RefObject<HTMLElement | null>, active = true):
const root = ref.current;
if (!active || !root) return;
openTraps++;
if (!root.contains(document.activeElement)) focusable(root)[0]?.focus();
if (!root.contains(document.activeElement)) focusable(root)[0]?.focus({ preventScroll: true });
const onKeyDown = (e: KeyboardEvent) => {
if (e.key !== "Tab") return;
@@ -36,13 +36,13 @@ export function useFocusTrap(ref: RefObject<HTMLElement | null>, active = true):
const current = document.activeElement as HTMLElement | null;
if (!current || !root.contains(current)) {
e.preventDefault();
(e.shiftKey ? last : first).focus();
(e.shiftKey ? last : first).focus({ preventScroll: true });
} else if (e.shiftKey && current === first) {
e.preventDefault();
last.focus();
last.focus({ preventScroll: true });
} else if (!e.shiftKey && current === last) {
e.preventDefault();
first.focus();
first.focus({ preventScroll: true });
}
};
@@ -50,7 +50,7 @@ export function useFocusTrap(ref: RefObject<HTMLElement | null>, active = true):
return () => {
openTraps--;
root.removeEventListener("keydown", onKeyDown);
if (opener.current?.isConnected) opener.current.focus();
if (opener.current?.isConnected) opener.current.focus({ preventScroll: true });
};
}, [ref, active]);
}
+13 -1
View File
@@ -233,6 +233,18 @@ function blockFromElement(el: Element, depth: number): JSONContent[] {
return [{ type: "blockquote", content: inner.length ? inner : [{ type: "paragraph" }] }];
}
case "ul": {
if (el.getAttribute("data-type") === "taskList") {
const items = Array.from(el.children).filter((child) => child.localName === "li").map((item) => {
const body = item.querySelector(":scope > div") ?? item;
const content = blocksFrom(body, depth + 1);
return {
type: "taskItem",
attrs: { checked: item.getAttribute("data-checked") === "true" },
content: content.length ? content : [{ type: "paragraph" }],
};
});
return items.length ? [{ type: "taskList", content: items }] : [];
}
const items = listItems(el, depth);
return items.length ? [{ type: "bulletList", content: items }] : [];
}
@@ -467,7 +479,7 @@ function buildChapter(
};
}
export function filesToBook(files: RawFile[], fallbackName = "Imported book"): Book {
export function filesToBook(files: RawFile[], fallbackName = "Imported project"): Book {
const byPath = new Map<string, RawFile>();
files.forEach((f) => byPath.set(normalize(f.path), f));
+1 -1
View File
@@ -82,7 +82,7 @@ export async function createAndOpenBook(
try {
await saveBook(book);
} catch (e) {
onError?.(`Could not create book: ${e}`);
onError?.(`Could not create project: ${e}`);
}
}
open(book);
+56 -8
View File
@@ -75,6 +75,8 @@ button {
padding: 0 14px 0 84px;
background: var(--shell);
border-bottom: 1px solid var(--line);
user-select: none;
-webkit-user-select: none;
}
.titlebar .lead {
@@ -547,14 +549,20 @@ body.resizing .sidebar {
font-weight: 500;
}
.prose a {
color: var(--accent);
.prose a,
.page-body a,
.device-body a {
color: #1769c2;
text-decoration: underline;
text-decoration-color: var(--line-strong);
text-decoration-color: currentColor;
text-underline-offset: 2px;
cursor: pointer;
}
[data-theme="dark"] .prose a {
color: #79b5ff;
}
.prose blockquote {
margin: 1.7rem 0;
padding-left: 1.2rem;
@@ -930,11 +938,9 @@ body.resizing .sidebar {
}
.link-pop {
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%);
z-index: 7;
position: fixed;
z-index: 19;
max-width: calc(100vw - 24px);
display: flex;
align-items: center;
gap: 4px;
@@ -966,6 +972,7 @@ body.resizing .sidebar {
}
.link-input {
min-width: 0;
width: 220px;
height: 28px;
padding: 0 12px;
@@ -976,6 +983,40 @@ body.resizing .sidebar {
font-size: var(--t-2);
}
.link-editor-backdrop {
z-index: 18;
}
:is(.prose, .page-body, .device-body) ul[data-type="taskList"] {
list-style: none;
padding-left: 0;
}
:is(.prose, .page-body, .device-body) li[data-type="taskItem"] {
display: flex;
align-items: baseline;
gap: 0.65em;
}
:is(.prose, .page-body, .device-body) li[data-type="taskItem"] > label {
flex: none;
user-select: none;
}
.prose li[data-type="taskItem"] input {
cursor: pointer;
accent-color: var(--accent);
}
:is(.prose, .page-body, .device-body) li[data-type="taskItem"] > div {
flex: 1;
min-width: 0;
}
:is(.prose, .page-body, .device-body) li[data-type="taskItem"] p {
text-align: left;
}
.link-input::placeholder {
color: var(--ink-faint);
}
@@ -1501,6 +1542,8 @@ body.resizing .sidebar {
align-items: center;
justify-content: flex-end;
padding: 0 14px;
user-select: none;
-webkit-user-select: none;
}
.shelf {
@@ -1514,6 +1557,7 @@ body.resizing .sidebar {
.card {
position: relative;
min-width: 0;
aspect-ratio: 5 / 7;
display: flex;
flex-direction: column;
@@ -1554,6 +1598,8 @@ body.resizing .sidebar {
}
.card-title {
max-width: 100%;
overflow-wrap: anywhere;
font-family: var(--font-book);
font-size: 20px;
font-weight: 500;
@@ -1562,6 +1608,8 @@ body.resizing .sidebar {
}
.card-author {
max-width: 100%;
overflow-wrap: anywhere;
font-family: var(--font-ui);
font-size: var(--t-2);
color: var(--ink-soft);
+54
View File
@@ -0,0 +1,54 @@
.panel.shortcuts-panel {
width: min(900px, calc(100vw - 32px));
}
.panel-body.shortcuts-groups {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 28px 36px;
}
.shortcuts-group h3 {
margin: 0 0 12px;
font-size: var(--t-3);
font-weight: 600;
color: var(--ink);
}
.shortcuts-group dl {
margin: 0;
}
.shortcut-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
min-height: 34px;
font-size: var(--t-2);
color: var(--ink-soft);
}
.shortcut-row dd {
display: flex;
gap: 4px;
margin: 0;
flex-shrink: 0;
}
.shortcut-row kbd {
min-width: 22px;
padding: 3px 5px;
border: 1px solid var(--line);
border-radius: var(--r-sm);
background: var(--raised);
color: var(--ink);
font-family: var(--font-ui);
text-align: center;
}
@media (max-width: 700px) {
.panel-body.shortcuts-groups {
grid-template-columns: minmax(0, 1fr);
}
}