The text is laid out on a page of one fixed size and the sheet scaled to the screen, so a phone breaks it exactly where a desktop does — twenty-one lines to the page on both. The type size is computed from the page and the line count rather than chosen. A PDF's full screen is the browser window, not the screen, and its button sits beside the zoom. "Recent Notes" is stamped by every write, not only by a rename. And a PDF that will not open says which of the four reasons it is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
384 lines
12 KiB
CSS
384 lines
12 KiB
CSS
/* Editor content styling.
|
|
*
|
|
* The rest of the app styles inline, but ProseMirror renders its own DOM, so
|
|
* these nodes can only be reached from a stylesheet. Colours come from custom
|
|
* properties the editor container sets per theme. */
|
|
|
|
.note-editor {
|
|
--ne-font: "Newsreader", Georgia, serif;
|
|
--ne-text: #1f2328;
|
|
}
|
|
.note-editor[data-font="sans"] {
|
|
--ne-font: "DM Sans", system-ui, sans-serif;
|
|
}
|
|
.note-editor[data-font="mono"] {
|
|
--ne-font: ui-monospace, "SFMono-Regular", "Menlo", "Consolas", monospace;
|
|
}
|
|
.note-editor {
|
|
--ne-muted: #656d76;
|
|
--ne-faint: #afb8c1;
|
|
--ne-border: #d0d7de;
|
|
--ne-accent: #0969da;
|
|
--ne-surface: #ffffff;
|
|
--ne-subtle: #f6f8fa;
|
|
/* A table's header row, which needs to read as one at a glance. Its own
|
|
tokens because "slightly off the page colour" is enough for a code block
|
|
sitting in the middle of text and not enough for a row that has to be told
|
|
from the rows under it. */
|
|
--ne-th-bg: #eaeef2;
|
|
--ne-th-rule: #afb8c1;
|
|
/* One line of body text. Named because the task list needs to know it: a
|
|
checkbox is centred against a line, and a line is this tall. */
|
|
--ne-line: 1.7;
|
|
}
|
|
.note-editor[data-theme="dark"] {
|
|
--ne-text: #e6edf3;
|
|
--ne-muted: #8b949e;
|
|
--ne-faint: #484f58;
|
|
--ne-border: #30363d;
|
|
--ne-accent: #388bfd;
|
|
--ne-surface: #0d1117;
|
|
--ne-subtle: #161b22;
|
|
/* Dark is where this mattered: #161b22 on a #0d1117 sheet is a difference you
|
|
have to look for, so a header row read as just another row. */
|
|
--ne-th-bg: #262c36;
|
|
--ne-th-rule: #6a7480;
|
|
}
|
|
|
|
.note-editor .ProseMirror {
|
|
/* About 73 characters to the line at this size — measured, not guessed. The
|
|
text used to run the full width of the pane, past 84, which is beyond the
|
|
point where the eye reliably finds the start of the next line. Capped here
|
|
rather than on the container so it holds on a page as well as off one. */
|
|
/* Roughly 80 characters to the line, leaving about 8% of the sheet as margin
|
|
on either side — a document's margin, not a picture frame. It was 527px,
|
|
which left 117px of empty paper down each edge and made the writing look
|
|
lost on the page. Past about 84 characters the eye stops reliably finding
|
|
the start of the next line, so this is close to as wide as it should go. */
|
|
max-width: 640px;
|
|
/* Centred on the sheet. Pinning the column to the left edge put every line in
|
|
the same place, but on a wide window it left the whole right half of the
|
|
pane as one dead block. */
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
outline: none;
|
|
color: var(--ne-text);
|
|
/* Set per account — see UserPreferences. The stack is the fallback chain, so
|
|
an unset preference still reads as the serif it always was. */
|
|
font-family: var(--ne-font, "Newsreader", Georgia, serif);
|
|
font-size: var(--ne-size, 15px);
|
|
line-height: var(--ne-line);
|
|
padding: 20px 24px;
|
|
min-height: 100%;
|
|
word-break: break-word;
|
|
}
|
|
.note-editor .ProseMirror > * + * {
|
|
margin-top: 0.6em;
|
|
}
|
|
/* The gap between two sheets, held open inside the text flow so no paragraph is
|
|
cut in half by it. Margins of its own would make the space it adds depend on
|
|
its neighbours — see pagination.js, which computes the height on the
|
|
assumption that it has none. */
|
|
.note-editor .ProseMirror > .ne-page-spacer {
|
|
margin: 0 !important;
|
|
padding: 0;
|
|
border: none;
|
|
pointer-events: none;
|
|
user-select: none;
|
|
}
|
|
/* The placeholder extension styles `[contenteditable]:empty::before`, and a
|
|
spacer is exactly that: empty, and not editable. So "Start writing..." was
|
|
printed at the foot of every page that had a break after it. */
|
|
.note-editor .ProseMirror > .ne-page-spacer::before,
|
|
.note-editor .ProseMirror > .ne-page-spacer::after {
|
|
content: none !important;
|
|
}
|
|
.note-editor .ProseMirror p {
|
|
margin: 0;
|
|
}
|
|
.note-editor .ProseMirror h1 {
|
|
font-size: 1.7em;
|
|
font-weight: 700;
|
|
line-height: 1.3;
|
|
margin-top: 0.8em;
|
|
}
|
|
.note-editor .ProseMirror h2 {
|
|
font-size: 1.4em;
|
|
font-weight: 700;
|
|
line-height: 1.3;
|
|
margin-top: 0.7em;
|
|
}
|
|
.note-editor .ProseMirror h3 {
|
|
font-size: 1.15em;
|
|
font-weight: 700;
|
|
line-height: 1.4;
|
|
margin-top: 0.6em;
|
|
}
|
|
.note-editor .ProseMirror ul,
|
|
.note-editor .ProseMirror ol {
|
|
padding-left: 1.4em;
|
|
}
|
|
.note-editor .ProseMirror li p {
|
|
margin: 0;
|
|
}
|
|
.note-editor .ProseMirror blockquote {
|
|
border-left: 3px solid var(--ne-border);
|
|
padding-left: 12px;
|
|
color: var(--ne-muted);
|
|
}
|
|
/* A quote may hold whole blocks — a heading, a list, a closing line — and inside
|
|
it they need the same rhythm as outside. The rule at the top of this file
|
|
reaches only the editor's own children, so everything in a quote stood jammed
|
|
against its neighbour, with the items of the list further apart than the
|
|
blocks around them. It comes after `p { margin: 0 }` and the headings on
|
|
purpose: those are exactly as specific, so written earlier they would win.
|
|
A paragraph inside a list item is more specific still and keeps its zero. */
|
|
.note-editor .ProseMirror blockquote > * + * {
|
|
margin-top: 0.6em;
|
|
}
|
|
/* A marker pen leaves the writing alone: dark ink on the colour whatever the
|
|
theme, or a light-grey word on pale yellow would be unreadable on the dark
|
|
one. An explicit text colour is an inline style and still wins over this. */
|
|
.note-editor .ProseMirror mark {
|
|
background-color: #fff3a3;
|
|
color: #1f2328;
|
|
padding: 0 1px;
|
|
border-radius: 2px;
|
|
}
|
|
.note-editor .ProseMirror code {
|
|
background: var(--ne-subtle);
|
|
border: 1px solid var(--ne-border);
|
|
border-radius: 4px;
|
|
padding: 1px 5px;
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
font-size: 0.88em;
|
|
}
|
|
.note-editor .ProseMirror pre {
|
|
background: var(--ne-subtle);
|
|
border: 1px solid var(--ne-border);
|
|
border-radius: 10px;
|
|
padding: 12px 14px;
|
|
overflow-x: auto;
|
|
}
|
|
.note-editor .ProseMirror pre code {
|
|
background: none;
|
|
border: none;
|
|
padding: 0;
|
|
}
|
|
.note-editor .ProseMirror hr {
|
|
border: none;
|
|
border-top: 1px solid var(--ne-border);
|
|
margin: 1.2em 0;
|
|
}
|
|
.note-editor .ProseMirror img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
border-radius: 8px;
|
|
display: block;
|
|
}
|
|
.note-editor .ProseMirror img.note-image {
|
|
background: var(--ne-subtle);
|
|
min-height: 24px;
|
|
}
|
|
/* An attachment whose bytes could not be fetched — visible, not a blank gap */
|
|
.note-editor .ProseMirror img[data-missing="true"] {
|
|
border: 1px dashed var(--ne-border);
|
|
padding: 12px;
|
|
min-width: 120px;
|
|
}
|
|
.note-editor .ProseMirror a {
|
|
color: var(--ne-accent);
|
|
}
|
|
|
|
/* Placeholder on the first empty line */
|
|
.note-editor .ProseMirror p.is-editor-empty:first-child::before {
|
|
content: attr(data-placeholder);
|
|
color: var(--ne-faint);
|
|
float: left;
|
|
height: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* The spacer that carries a table across the gap between two sheets. It is a
|
|
row, so it inherits every cell rule below — and has to shed all of them, or
|
|
the gap shows a tall bordered box. */
|
|
.note-editor .ProseMirror tr.ne-page-spacer-row,
|
|
.note-editor .ProseMirror tr.ne-page-spacer-row td {
|
|
border: none !important;
|
|
padding: 0 !important;
|
|
background: transparent !important;
|
|
}
|
|
.note-editor .ProseMirror tr.ne-page-spacer-row td::before,
|
|
.note-editor .ProseMirror tr.ne-page-spacer-row td::after {
|
|
content: none !important;
|
|
}
|
|
|
|
/* ── Tables ── */
|
|
.note-editor .ProseMirror table {
|
|
border-collapse: collapse;
|
|
table-layout: fixed;
|
|
width: 100%;
|
|
margin: 0.8em 0;
|
|
overflow: hidden;
|
|
}
|
|
.note-editor .ProseMirror td,
|
|
.note-editor .ProseMirror th {
|
|
border: 1px solid var(--ne-border);
|
|
padding: 7px 10px;
|
|
vertical-align: top;
|
|
position: relative;
|
|
min-width: 1em;
|
|
}
|
|
.note-editor .ProseMirror th {
|
|
background: var(--ne-th-bg);
|
|
font-weight: 700;
|
|
text-align: left;
|
|
/* The rule under the header does most of the work — the fill alone cannot be
|
|
made much stronger without the header turning into a banner. */
|
|
border-bottom: 2px solid var(--ne-th-rule);
|
|
}
|
|
.note-editor .ProseMirror .selectedCell::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
background: color-mix(in srgb, var(--ne-accent) 18%, transparent);
|
|
pointer-events: none;
|
|
}
|
|
/* The edge being dragged, drawn *on the cell* rather than in it.
|
|
*
|
|
* The editor marks the column under the pointer by putting a small div in each
|
|
* cell of it, and the obvious way to show that is to position the div along the
|
|
* cell's right edge. Which worked, and painted grey letters down the bar: an
|
|
* absolutely positioned child inside a cell of a `border-collapse: collapse`
|
|
* table sends Chromium down another painting path, and the cell's own text came
|
|
* out a second time, squeezed into the two pixels under the bar — one letter to
|
|
* a line. It appeared with the bar and went with it, which is what named the
|
|
* culprit.
|
|
*
|
|
* So the div is never drawn at all; it only marks which cells belong to the
|
|
* column, and the line is an inset shadow on those cells. Nothing is positioned,
|
|
* nothing paints twice, and the result is the same line — a continuous one down
|
|
* the column, because every cell in it carries its own piece. */
|
|
.note-editor .ProseMirror .column-resize-handle {
|
|
display: none;
|
|
}
|
|
.note-editor .ProseMirror td:has(> .column-resize-handle),
|
|
.note-editor .ProseMirror th:has(> .column-resize-handle) {
|
|
box-shadow: inset -2px 0 0 var(--ne-accent);
|
|
}
|
|
.note-editor .ProseMirror.resize-cursor {
|
|
cursor: col-resize;
|
|
}
|
|
|
|
/* ── Task lists ── */
|
|
.note-editor .ProseMirror ul[data-type="taskList"] {
|
|
list-style: none;
|
|
padding-left: 0;
|
|
}
|
|
.note-editor .ProseMirror ul[data-type="taskList"] li {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 8px;
|
|
}
|
|
/* The box is centred on the *letters* of the first line beside it — not on that
|
|
line's box, and not at a fixed nudge from the top.
|
|
|
|
`margin-top: 0.35em` was measured once against one font at one size. Centring
|
|
in the full line was the next attempt and is still not it: a line box is
|
|
taller than the letters in it by the depth of a descender, so its middle sits
|
|
below the middle of a capital. Measured, that left the box's top level with
|
|
the capitals and its bottom 4px past the baseline — a square hanging off the
|
|
line. Two tenths of an em off the line's height is where the capitals'
|
|
middle actually is, for every face on offer here. */
|
|
.note-editor .ProseMirror ul[data-type="taskList"] li > label {
|
|
flex: 0 0 auto;
|
|
display: flex;
|
|
align-items: center;
|
|
height: calc(1em * var(--ne-line) - 0.3em);
|
|
user-select: none;
|
|
}
|
|
.note-editor .ProseMirror ul[data-type="taskList"] li > div {
|
|
flex: 1 1 auto;
|
|
}
|
|
/* Sized in em, so the box stays the size of a capital whatever the note's type
|
|
size is. At 15px it was a 15px square against a 10px letter, which read as a
|
|
box the text was standing next to rather than part of the same line. */
|
|
.note-editor .ProseMirror ul[data-type="taskList"] input[type="checkbox"] {
|
|
accent-color: var(--ne-accent);
|
|
width: 0.9em;
|
|
height: 0.9em;
|
|
margin: 0;
|
|
cursor: pointer;
|
|
}
|
|
.note-editor .ProseMirror ul[data-type="taskList"] li[data-checked="true"] > div {
|
|
color: var(--ne-muted);
|
|
text-decoration: line-through;
|
|
}
|
|
|
|
/* ── Note links ── */
|
|
.note-editor .ProseMirror a.note-link,
|
|
.note-link {
|
|
display: inline-flex;
|
|
align-items: baseline;
|
|
gap: 3px;
|
|
padding: 1px 6px;
|
|
border-radius: 6px;
|
|
background: color-mix(in srgb, var(--ne-accent) 14%, transparent);
|
|
color: var(--ne-accent);
|
|
text-decoration: none;
|
|
font-family: "DM Sans", system-ui, sans-serif;
|
|
font-size: 0.9em;
|
|
white-space: nowrap;
|
|
cursor: pointer;
|
|
}
|
|
.note-editor .ProseMirror a.note-link:hover {
|
|
background: color-mix(in srgb, var(--ne-accent) 26%, transparent);
|
|
}
|
|
/* A link whose target is gone — still readable, visibly broken */
|
|
.note-editor .ProseMirror a.note-link[data-missing="true"] {
|
|
background: color-mix(in srgb, #cf222e 14%, transparent);
|
|
color: #cf222e;
|
|
text-decoration: line-through;
|
|
}
|
|
|
|
/* ── @-mention popup ── */
|
|
.note-link-popup {
|
|
position: fixed;
|
|
z-index: 3000;
|
|
max-height: 240px;
|
|
overflow-y: auto;
|
|
padding: 5px;
|
|
border-radius: 12px;
|
|
border: 1px solid var(--popup-border, #d0d7de);
|
|
background: var(--popup-bg, #ffffff);
|
|
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.22);
|
|
font-family: "DM Sans", system-ui, sans-serif;
|
|
}
|
|
.note-link-option {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
width: 100%;
|
|
padding: 7px 10px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
background: transparent;
|
|
color: var(--popup-text, #1f2328);
|
|
font-size: 13px;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
}
|
|
.note-link-option.is-selected {
|
|
background: var(--popup-hover, #eaeef2);
|
|
}
|
|
.note-link-option .note-link-label {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.note-link-empty {
|
|
padding: 9px 11px;
|
|
font-size: 12px;
|
|
color: var(--popup-muted, #656d76);
|
|
}
|