v3: the editors stop fighting the person typing — a review of every edit box turned up an Enter that sent a chat message mid-IME, an emoji picker that grabbed focus on ":D" and never went away, markdown that folded every line break, and limits that were counted but not kept. TextEditor: an Enter confirming an IME candidate no longer also sends, saves or picks. The : trigger wants two letters (Unicode ones too), not one, so emoticons, "note:" and "12:30" type through, and it keeps focus in the textarea — the picker's search follows what's typed, Enter or Tab takes the best match (a name equal to the query, then one starting with it, rather than frimousse's first in emoji order, which made ":rocket" an astronaut), the arrows step into the grid and typing there carries on in the textarea, and Escape closes just the picker, not the chat around it. It closes on a press outside, which it never did, and neither autocomplete fires inside code or a ``` fence. A ☺ button in each editor's corner opens the picker for mouse users, with a skin-tone selector that's remembered, and its search takes focus only once the picker is placed (a hidden field can't). Emoji, mentions and pasted images go in through execCommand, so Ctrl+Z takes back the insertion instead of wrecking the undo stack. Uploading an image made the textarea disabled, which dropped focus for good; it's read-only now, says "Uploading image…" under the box rather than only in an empty one's placeholder, reports a failed upload instead of swallowing it, and holds Enter until it's done. Any dropped file is caught — a non-image used to be opened by the browser in place of the page, draft and all — and a paste carrying text (a spreadsheet's cells come with a picture of themselves) pastes the text. The mention list follows the caret (a stale lookup can't close a newer one, lookups abort), wraps, scrolls its selection into view, closes on blur, and is a real listbox with aria-activedescendant. The counter shows from 80% of the limit by default rather than a standing "0 / 5000". Limits: chat Send and edit Save refused nothing over 2000 and let the server bounce it; they're disabled past it now. RichTextField's bare maxLength rule failed without a message, so an over-long "Why?" made Save do nothing and say nothing; it says so now. Inline editors answer Escape through a new onCancel (comment reply and edit, profile description, playlist description and title), the playlist editor takes Ctrl+Enter and can't be saved without a title, and saving a comment unchanged no longer stamps it edited. Markdown: a single newline is a line break, so a message written over three lines stops reading as one; links into the app route in place instead of opening a tab; headings are sized for prose instead of 2em page titles, and dropped from card teasers; GFM tables scroll in their own box and task lists lose the bullet; images load lazily. A chat message of only an emoji or three is shown large. Themes: brutalist and geocities give the new buttons no chrome, and geocities' ghost-button list, split in two by the placeholder-thumbnail rules in 1cb904d, is whole again, so its modal ✕, emoji cells and the picker's ✕ are flat as intended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-09-27 10:24:30 +00:00
parent 5fd1d364da
commit 85d0703077
22 changed files with 1321 additions and 520 deletions

View File

@@ -65,6 +65,62 @@
font-weight: 700;
line-height: 1.25;
}
/* Headings inside a comment or a chat message are emphasis within prose, not
page titles: the browser's 2em h1 dwarfed everything around it. */
.md h1 {
font-size: 1.3em;
}
.md h2 {
font-size: 1.18em;
}
.md h3 {
font-size: 1.06em;
}
.md h4,
.md h5,
.md h6 {
font-size: 1em;
}
.md > :first-child {
margin-top: 0;
}
.md hr {
border: none;
border-top: 1px solid var(--color-border);
margin: 0.85em 0;
}
.md del {
opacity: 0.7;
}
/* GFM task lists: the checkbox stands in for the bullet. */
.md .contains-task-list {
list-style: none;
padding-left: 0.2em;
}
.md .task-list-item input {
margin: 0 0.4em 0 0;
vertical-align: -0.1em;
}
.md-table-wrap {
max-width: 100%;
overflow-x: auto;
scrollbar-width: thin;
margin: 0.6em 0;
}
.md table {
border-collapse: collapse;
font-size: 0.95em;
}
.md th,
.md td {
border: 1px solid var(--color-border);
padding: 0.3em 0.6em;
text-align: left;
}
.md th {
font-weight: 700;
background: var(--color-bg);
}
.md img {
max-width: 100%;
@@ -83,6 +139,19 @@
.md--inline li {
margin: 0;
}
.md--inline h1,
.md--inline h2,
.md--inline h3,
.md--inline h4,
.md--inline h5,
.md--inline h6 {
margin: 0;
font-size: inherit;
}
.md--inline hr,
.md--inline .md-table-wrap {
display: none;
}
.md--inline ul,
.md--inline ol {
padding-left: 1.2em;
@@ -3155,6 +3224,11 @@ body.has-player .chat-fab {
display: flex;
flex-direction: column;
}
.dump-edit-panel--details .text-editor-field {
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
.dump-edit-panel--details .mention-textarea-wrap textarea {
flex: 1 1 auto;
}
@@ -4522,6 +4596,15 @@ body.has-player .chat-fab {
line-height: 1.45;
}
/* A message that's only an emoji or three, shown large as chats do. */
.chat-message-text--jumbo {
font-size: 2.1em;
line-height: 1.2;
}
.chat-message-text--jumbo.md {
line-height: 1.2;
}
.chat-message-edited {
font-size: var(--text-2xs);
color: var(--color-text-muted);
@@ -4617,16 +4700,21 @@ body.has-player .chat-fab {
scrollbar-width: thin;
}
/* The counter (only shown near the limit) sits just above the input's right
edge, in the gap over the composer: inside the textarea, the text ran under
it, and in a row below it would knock the Send button out of alignment. */
.chat-composer .text-editor-count {
/* The counter (only shown near the limit) and the upload notice sit just
above the input, in the gap over the composer: inside the textarea, the text
ran under them, and in a row below they would knock the Send button out of
alignment. */
.chat-composer .text-editor-footer {
position: absolute;
left: 0.25rem;
right: 0.25rem;
bottom: calc(100% + 1px);
margin: 0;
line-height: 1;
}
.chat-composer .text-editor-footer > * {
margin: 0;
}
.chat-send-btn {
flex-shrink: 0;
@@ -6440,6 +6528,78 @@ body.has-player .chat-fab {
box-sizing: border-box;
}
/* The textarea and the emoji button in its corner. */
.text-editor-field {
position: relative;
}
/* Room for the button, so text never runs under it. Qualified to outrank the
per-editor and per-theme padding shorthands. */
.mention-textarea-wrap .text-editor-field > textarea {
padding-right: 2.25rem;
}
/* Top corner, not bottom: the bottom one is a resizable textarea's grip. */
.text-editor-emoji-btn {
position: absolute;
right: 0.3rem;
top: 0.3rem;
width: 1.75rem;
height: 1.75rem;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
border: none;
border-radius: var(--radius-sm);
background: none;
color: var(--color-text-muted);
opacity: 0.6;
cursor: pointer;
transition: opacity var(--dur-fast), color var(--dur-fast),
background var(--dur-fast);
}
.text-editor-emoji-btn svg {
width: 1.15rem;
height: 1.15rem;
fill: none;
stroke: currentColor;
stroke-width: 1.6;
stroke-linecap: round;
}
.text-editor-emoji-btn svg circle[r=".6"] {
fill: currentColor;
}
.text-editor-emoji-btn:hover:not(:disabled),
.text-editor-emoji-btn:focus-visible,
.text-editor-emoji-btn--open {
opacity: 1;
color: var(--color-accent);
}
.text-editor-emoji-btn:disabled {
opacity: 0.25;
cursor: default;
}
/* Clear of the scrollbar a long chat message grows. */
.chat-input + .text-editor-emoji-btn {
right: 0.6rem;
}
.text-editor-footer {
display: flex;
align-items: baseline;
gap: 0.75rem;
margin-top: 2px;
}
.text-editor-footer .text-editor-count {
margin: 0 0 0 auto;
}
.text-editor-notice {
font-size: var(--text-2xs);
color: var(--color-text-muted);
}
.text-editor-notice--error {
color: var(--color-danger, #c0392b);
}
.mention-textarea-wrap--dragover textarea {
outline: 2px dashed var(--color-accent, #6c8ebf);
outline-offset: -2px;
@@ -6536,10 +6696,29 @@ body.has-player .chat-fab {
font-size: var(--text-md);
outline: none;
}
/* The row has its own ✕; the browser's clear button would be a second one. */
.emoji-picker-float input::-webkit-search-cancel-button {
display: none;
}
.emoji-picker-float input::placeholder {
color: var(--color-text-muted, #888);
}
.emoji-picker-float [frimousse-skin-tone-selector] {
flex-shrink: 0;
padding: 0 6px;
height: 100%;
background: none;
border: none;
font-size: var(--text-md);
line-height: 1;
cursor: pointer;
border-radius: var(--radius-sm);
}
.emoji-picker-float [frimousse-skin-tone-selector]:hover {
background: var(--color-surface);
}
.emoji-picker-close-btn {
flex-shrink: 0;
padding: 0 10px;