From 9f0b5b00f7ada4c5735230d0f93e04a3c58d4d7a Mon Sep 17 00:00:00 2001 From: Kit La Touche Date: Sun, 11 May 2025 21:56:33 -0400 Subject: Fix up input to be a contenteditable div --- ui/lib/components/MessageInput.svelte | 18 ++++++++++++++---- ui/styles/textarea.css | 34 +++++++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 13 deletions(-) (limited to 'ui') diff --git a/ui/lib/components/MessageInput.svelte b/ui/lib/components/MessageInput.svelte index 69a8298..1f4ba99 100644 --- a/ui/lib/components/MessageInput.svelte +++ b/ui/lib/components/MessageInput.svelte @@ -1,7 +1,10 @@ -
- + +
diff --git a/ui/styles/textarea.css b/ui/styles/textarea.css index 4b8602c..4dc804a 100644 --- a/ui/styles/textarea.css +++ b/ui/styles/textarea.css @@ -4,28 +4,44 @@ flex-direction: row; justify-content: flex-start; align-items: stretch; +} - @media (width <= 640px) { - margin: 0 0.5rem 1rem 0.5rem; - } +/* The second selector is more generally useful, but it doesn't seem to work, + * so I added in the first selector. */ +.textarea:empty:before, +[contenteditable='true']:empty:before { + content: attr(placeholder); + pointer-events: none; + display: block; /* For Firefox */ } -.create-message textarea { - padding: 0.5rem; - border-radius: 0.5rem 0 0 0.5rem; +.create-message .textarea { + padding: 1rem; border: 1px solid var(--colour-input-border); z-index: 1; /* Just to make the focus-active border go over the following button. */ flex-grow: 1; background-color: var(--colour-input-bg); color: var(--colour-input-text); - font-family: 'FiraCode', monospace; + min-height: 1rem; + max-height: 10rem; + overflow-x: hidden; + overflow-y: auto; + + @media (width <= 640px) { + border-radius: 0; + min-height: 2.5rem; + } } .create-message button { - border-radius: 0 0.5rem 0.5rem 0; border: 1px solid var(--colour-input-border); background-color: var(--colour-input-bg); color: var(--colour-input-text); + padding: 1rem; + + @media (width <= 640px) { + border-radius: 0; + } } main { @@ -38,7 +54,7 @@ main { /* Workaround for iOS zooming in on the textarea when it gets focus. */ @media screen and (-webkit-min-device-pixel-ratio: 0) { select, - textarea, + .textarea, input { font-size: 16px; } -- cgit v1.2.3 From 6eb8487d89ab14f8f11a40b78d7db72e3266840d Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Tue, 13 May 2025 18:08:21 -0400 Subject: Send multi-line messages as multiple lines. For whatever reason, `innerText` captures interior line breaks, while `textContent` does not. Wild. DOM APIs. --- ui/lib/components/MessageInput.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui') diff --git a/ui/lib/components/MessageInput.svelte b/ui/lib/components/MessageInput.svelte index 1f4ba99..71b7b8f 100644 --- a/ui/lib/components/MessageInput.svelte +++ b/ui/lib/components/MessageInput.svelte @@ -31,7 +31,7 @@
Date: Tue, 13 May 2025 17:53:01 -0400 Subject: Dim out placeholder text. It's not much, but it makes it a bit easier to see that the placeholder text _is_ a placeholder. Not sure what to do about it vanishing permanently once the element is edited, until the element is formally `reset()`, though. --- ui/styles/forms.css | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ui') diff --git a/ui/styles/forms.css b/ui/styles/forms.css index eb98743..59dda30 100644 --- a/ui/styles/forms.css +++ b/ui/styles/forms.css @@ -22,3 +22,7 @@ form.form > button { border: 1px solid var(--colour-input-border); margin: 0.25rem; } + +[contenteditable]:empty { + color: var(--light-text); +} -- cgit v1.2.3 From 361623aaccf08063712500a213548a2756dd13c4 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Tue, 13 May 2025 18:01:19 -0400 Subject: Implement the disabled state: * Suppress input (including paste) while the input is disabled. * Style the input to make it visible that it's not accepting input. --- ui/lib/components/MessageInput.svelte | 20 ++++++++++++++++---- ui/styles/forms.css | 4 ++++ 2 files changed, 20 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/lib/components/MessageInput.svelte b/ui/lib/components/MessageInput.svelte index 71b7b8f..848321d 100644 --- a/ui/lib/components/MessageInput.svelte +++ b/ui/lib/components/MessageInput.svelte @@ -3,8 +3,6 @@ let form = $state(null); let value = $state(''); - // This doesn't seem to work with a contenteditable two-way bound svelte - // thing: let disabled = $state(false); async function onsubmit(event) { @@ -19,21 +17,35 @@ } function onkeydown(event) { + if (disabled) { + event.preventDefault(); + return; + } + let modifier = event.shiftKey || event.altKey || event.ctrlKey || event.metaKey; if (!modifier && event.key === 'Enter') { event.preventDefault(); form?.requestSubmit?.(); } } + + function onpaste(event) { + if (disabled) { + event.preventDefault(); + } + }
diff --git a/ui/styles/forms.css b/ui/styles/forms.css index 59dda30..907f1bf 100644 --- a/ui/styles/forms.css +++ b/ui/styles/forms.css @@ -26,3 +26,7 @@ form.form > button { [contenteditable]:empty { color: var(--light-text); } + +.disabled { + color: var(--light-text); +} -- cgit v1.2.3 From a0568ada5568bd8d6538e26e003b26c8409e2cb7 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Tue, 13 May 2025 18:14:04 -0400 Subject: Make the resulting form more amenable to normal DOM operations. This is purely an aesthetic choice: * The DOM `reset()` function can be used to clear the form, but can't be used to clear editable DIVs. Binding the editable div to a (hidden) form field allows `reset()` to clear both. * We can find the target `form` element out of the event, but the API needed to do so differs between events dispatched to form controls and events dispatched to random DOM nodes. Using `closest('form')` works for both kinds of event target. In practice, there is little need to make sure the message input form uses "normal" DOM APIs for functional reasons. Everything inside `MessageInput` is controllable through the component's script. This change isn't based on a functional need, but rather in the hopes that integrating with the DOM APIs makes it easier for _code we don't control_ - screen readers, password managers, saved-form support in browsers, &c - to integrate with Pilcrow. It is purely speculative. (This also used to be necessary because Firefox didn't support `contenteditable="plaintext-only"`, but [support was added in March][ff-pto] [ff-pto]: https://www.mozilla.org/en-US/firefox/136.0/releasenotes/#:~:text=The%20value%20plaintext%2Donly%20can%20now%20be%20specified%20for%20the%20contenteditable%20attribute%2C%20making%20the%20raw%20text%20of%20an%20element%20editable%20but%20without%20supporting%20rich%20text%20formatting. --- ui/lib/components/MessageInput.svelte | 8 ++++---- ui/styles/forms.css | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/lib/components/MessageInput.svelte b/ui/lib/components/MessageInput.svelte index 848321d..6c22b29 100644 --- a/ui/lib/components/MessageInput.svelte +++ b/ui/lib/components/MessageInput.svelte @@ -1,7 +1,6 @@ - + +
button { .disabled { color: var(--light-text); } + +.hidden { + display: none; +} -- cgit v1.2.3 From e623ab68e7f4d1a9bd8222aa8d8f5d9108a238c0 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Tue, 13 May 2025 20:02:25 -0400 Subject: Support non-mouse accessibility. * Give the input `div` a marker to tell screen readers &c that it is a textbox. * Ensure that it participates in tab order. (Zero is a sentinel value, see .) --- ui/lib/components/MessageInput.svelte | 2 ++ 1 file changed, 2 insertions(+) (limited to 'ui') diff --git a/ui/lib/components/MessageInput.svelte b/ui/lib/components/MessageInput.svelte index 6c22b29..b230939 100644 --- a/ui/lib/components/MessageInput.svelte +++ b/ui/lib/components/MessageInput.svelte @@ -38,6 +38,8 @@
Date: Tue, 13 May 2025 20:40:23 -0400 Subject: Restore the placeholder when the editable input is emptied out after modification. This also avoids using `placeholder` on elements where it's nonstandard, like `
`s. --- ui/lib/components/MessageInput.svelte | 12 +++++++++++- ui/styles/forms.css | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) (limited to 'ui') diff --git a/ui/lib/components/MessageInput.svelte b/ui/lib/components/MessageInput.svelte index b230939..cdb855e 100644 --- a/ui/lib/components/MessageInput.svelte +++ b/ui/lib/components/MessageInput.svelte @@ -4,6 +4,16 @@ let value = $state(''); let disabled = $state(false); + // Entering and removing text from the input area leaves a stray line break behind. This effect + // looks for that scenario and removes it, restoring the "empty" state (and bringing the + // placeholder back again in the process). + $effect(() => { + const trimmed = value.trim(); + if (trimmed !== value && trimmed === '') { + value = trimmed; + } + }); + async function onsubmit(event) { event.preventDefault(); disabled = true; @@ -48,7 +58,7 @@ bind:innerText={value} {onkeydown} {onpaste} - placeholder="Say something..." + data-placeholder="Say something..." >
diff --git a/ui/styles/forms.css b/ui/styles/forms.css index 1c1ae5c..a8789b1 100644 --- a/ui/styles/forms.css +++ b/ui/styles/forms.css @@ -23,7 +23,8 @@ form.form > button { margin: 0.25rem; } -[contenteditable]:empty { +[data-placeholder]:empty:before { + content: attr(data-placeholder); color: var(--light-text); } -- cgit v1.2.3 From a4ad808eb804e32edf3b848c229542b759db0102 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Thu, 15 May 2025 20:46:49 -0400 Subject: Move placeholder-related CSS into `textarea.css`. --- ui/styles/forms.css | 5 ----- ui/styles/textarea.css | 8 +++----- 2 files changed, 3 insertions(+), 10 deletions(-) (limited to 'ui') diff --git a/ui/styles/forms.css b/ui/styles/forms.css index a8789b1..98fd43e 100644 --- a/ui/styles/forms.css +++ b/ui/styles/forms.css @@ -23,11 +23,6 @@ form.form > button { margin: 0.25rem; } -[data-placeholder]:empty:before { - content: attr(data-placeholder); - color: var(--light-text); -} - .disabled { color: var(--light-text); } diff --git a/ui/styles/textarea.css b/ui/styles/textarea.css index 4dc804a..d315bcf 100644 --- a/ui/styles/textarea.css +++ b/ui/styles/textarea.css @@ -6,11 +6,9 @@ align-items: stretch; } -/* The second selector is more generally useful, but it doesn't seem to work, - * so I added in the first selector. */ -.textarea:empty:before, -[contenteditable='true']:empty:before { - content: attr(placeholder); +[data-placeholder]:empty:before { + content: attr(data-placeholder); + color: var(--light-text); pointer-events: none; display: block; /* For Firefox */ } -- cgit v1.2.3