summaryrefslogtreecommitdiff
path: root/ui/lib/components
Commit message (Collapse)AuthorAge
* Show number of unread conversations in app badgenotifs-controlsKit La Touche2025-11-30
|
* De minimis "send me a notification" implementation.Owen Jacobson2025-11-08
| | | | | | | | | | | | | | | | | | When a user clicks "send a test notification," Pilcrow delivers a push message (with a fixed payload) to all active subscriptions. The included client then displays this as a notification, using browser APIs to do so. This lets us verify that push notification works, end to end - and it appears to. The API endpoint for sending a test notification is not documented. I didn't feel it prudent to extensively document an endpoint that is intended to be temporary and whose side effects are very much subject to change. However, for posterity, the endpoint is POST /api/push/ping {} and the push message payload is ping Subscriptions with permanent delivery failures are nuked when we encounter them. Subscriptions with temporary failures cause the `ping` endpoint to return an internal server error, and are not retried. We'll likely want retry logic - including retry logic to handle server restarts - for any more serious use, but for a smoke test, giving up immediately is fine. To make the push implementation testable, `App` is now generic over it. Tests use a dummy implementation that stores sent messages in memory. This has some significant limitations, documented in the test suite, but it beats sending real notifications to nowhere in tests.
* Add a button to the client to set up a push subscription.Owen Jacobson2025-11-07
| | | | | | Once a user has set up a push subscription, the client will re-establish it as needed whenever possible, falling back to manual intervention only when it is unable to create a push subscription. This change imposes some architectural changes to the client, though they're not huge: the `session` type now includes a body of state (`push`) whose methods also call into the Pilcrow API. Previously, calls to the API were not made within the `session` types, and were instead only made by page and layout code, but orchestrating that for the push subscription lifecycle proved too complex to deal with. This is an experimental alternative, but it might be something we explore further in the future.
* Render message markdown to HTML inside of `<Message />`.Owen Jacobson2025-08-19
| | | | This simplifies data flow, at the potential expense of re-rendering HTML more often than strictly necessary. Requiring every path that produces a message-shaped object to pre-render markdown made things more interdependent than intended and slowed me down.
* Create swatches for Svelte components.ojacobson2025-07-10
|\ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A swatch is a live, and ideally editable, example of an element of the service. They serve as: * Documentation: what is this element, how do you use it, what does it do? * Demonstration: what does this element look like? * Manual test scaffolding: when I change this element like _so_, what happens? Swatches are collectively available under `/.swatch/` on a running instance. They do not require setup or login for simplicity's sake and because they don't _do_ anything that requires either of those things. Swatches are manually curated. First, we lack the technical infrastructure needed to do this based on static analysis, and second, manual curation lets us include affordances like "interesting values," that would be tricky to express as part of the type or schema for the component. The tradeoff, however, is that they will fall out of step with the components if not reviewed regularly. Swatches are _possible_ because we've gone to efforts to avoid global data access or direct side effects (including API requests) in our components, delegating that upwards to `+page`s and `+layout`s. However, the isolation is imperfect. For example, the swatch for `Conversation`, which renders the conversation sidebar entries, causes actual attempts to boot the app as browsers pre-fetch the links on mouseover, and clicking them will take the user to the "real" application because they really are links. Merges swatch into main.
| * Event capture and display tools.Owen Jacobson2025-07-08
| | | | | | | | | | | | This is meant to be used in swatches, to display the events and callbacks generated by a component as part of the swatch. The usage pattern is described in the comments (in both places). Naturally, this has its own swatch.
* | Remove container divs for `MessageInput` and `CreateConversationForm`.ojacobson2025-07-09
|\ \ | | | | | | | | | | | | | | | | | | | | | The styles for the `MessageInput` and `CreateConversationForm` components assumed that a container div would be present, and the components did not render as intended without that div. Therefore: the divs were "part of" the component in most of the ways that matter, not part of the context in which the component is used. It turns out that those divs aren't necessary or interesting anyways - they were targets for layout, but the same layout can be achieved without them. This change removes the divs entirely. Merges component-div-nesting into main.
| * | A few semantically-thin wrapper divs.Owen Jacobson2025-07-08
| | | | | | | | | | | | This is an extension of the previous commit: we don't need these divs _at all_ to achieve the layout we want, and we aren't attaching behaviour or semantics to them, so, out they go.
| * | Move container divs for components into those components.Owen Jacobson2025-07-08
| | | | | | | | | | | | | | | | | | The styles for the `MessageInput` and `CreateConversationForm` components assume that the container div will be present, and the components will not render as intended without them. Therefore: they are "part of" the component in most of the ways that matter, not part of the context in which the component is used. Moving the divs into the component will make it easier to reuse these components (for example, in swatches). The diff for this looks worse than it is because of indentation changes.
* | | Stop sending `{}` to the `/api/auth/login` endpoint when the login form ↵ojacobson2025-07-09
|\ \ \ | |_|/ |/| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | hasn't been touched. Steps to reproduce: **Note**: You will need to watch the traffic in a DOM inspector; this has no user-observable symptoms because there's presently no error reporting for the login form. 1. In a new private tab, visit the `/login` page of a Pilcrow instance. 2. **Without touching the username or password fields**, click `sign in`. The client _should_ send a request to `/api/auth/login` with the following payload: ```json { "name": "", "password": "" } ``` However, it instead sends an empty payload, leading to a 422 Unprocessable Content response as the request is missing required fields. Subsequent requests, or any request after the user enters data in the input fields, are correctly serialized. Merges login-form-nulls into main.
| * | Set non-`undefined` initial values for the login form.Owen Jacobson2025-07-08
| |/ | | | | | | The default state of a `$state()` with no arguments is `undefined`, which was then leaking out of this component if the user clicks `sign in` without changing the values. Axiom, our HTTP client library, suppresses fields with `undefined` values in JSON payloads (sensibly enough), leading to empty requests.
* / Remove the (entirely unused and unusable) `body` property from `Message`.Owen Jacobson2025-07-08
|/
* Rename "channel" to "conversation" throughout the client.Owen Jacobson2025-07-03
| | | | Existing client state, stored in local storage, is migrated to new keys (that mention "conversation" instead of "channel" where appropriate) the first time the client loads.
* Move the `/ch` channel view to `/c` (for conversation).Owen Jacobson2025-07-03
|
* tools/reformatOwen Jacobson2025-06-11
|
* Use browser-native methods to hide elements, not CSS alone.Owen Jacobson2025-05-22
| | | | The hidden `textarea` used to attach the form value to the DOM was being included in the ARIA accessibility tree, at least in testing (I didn't check in a browser). While we could suppress this iwth `aria-role="hidden"`, the WHATWG recommendation is to Not Do That, and to find another way to hide the element, instead. Marking the element as hidden accomplishes that goal, _and_ gets rid of a style rule.
* Restore the placeholder when the editable input is emptied out after ↵Owen Jacobson2025-05-13
| | | | | | modification. This also avoids using `placeholder` on elements where it's nonstandard, like `<div>`s.
* Support non-mouse accessibility.Owen Jacobson2025-05-13
| | | | | * 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 <https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/tabindex>.)
* Make the resulting form more amenable to normal DOM operations.Owen Jacobson2025-05-13
| | | | | | | | | | | | | | 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.
* Implement the disabled state:Owen Jacobson2025-05-13
| | | | | * Suppress input (including paste) while the input is disabled. * Style the input to make it visible that it's not accepting input.
* Send multi-line messages as multiple lines.Owen Jacobson2025-05-13
| | | | For whatever reason, `innerText` captures interior line breaks, while `textContent` does not. Wild. DOM APIs.
* Fix up input to be a contenteditable divKit La Touche2025-05-11
|
* Render messages as ghosts when there's a pending delete, too.Owen Jacobson2025-05-06
|
* Render "ghost" messages for unsent messages.Owen Jacobson2025-05-06
| | | | | | | | | | | There is a subtle race conditon in this code, which is likely not fixable without a protocol change: * Ghost messages can disappear before their "real" message replacement shows up, if the client finishes sending (i.e., receives an HTTP response on the POST) before the server delivers the real message. * Ghost messages can be duplicated briefly, if the client receives the real message before the client finishes sending. Both happen in practice; we make no ordering guarantees between requests. To aviod this, we'd to give clients a way to correlate pending sends with received messages. This would require fundamentally the same capabilities, like per-operation nonces, that preventing duplicate operations will require.
* Un-nest `Message` from `MessageRun`.Owen Jacobson2025-05-06
| | | | | | A `MessageRun` is a visual container with a specific layout - bordered, with a drop shadow, with a name badge on the top-left, which is either positioned to the left (`other-message`) or right (`own-message`). It is content-agnostic. This facilitates putting things besides live messages inside of a message run. As a side effect, this gets rid of ActiveChannel; most of what it was doing makes more sense living in the channel view's `+page.svelte`.
* Don't scroll all the time as messages are addedKit La Touche2025-02-25
| | | | Only once on load, then once per new message.
* Hoist global state access out of individual components.Owen Jacobson2025-02-21
| | | | | | | | Access to "global" (maybe "external?") state is now handled at the top level of the component hierarchy, in `+page.svelte`, `+layout.svelte`, and their associated scripts. State is otherwise passed down through props, and changes are passed up through callbacks. This is - hopefully - groundwork for refactoring state management a bit. I wanted to move access to state out to a smaller number of places, so that I have fewer places to update to implement reconnect logic. My broader goal is to make it easier to refactor these kinds of external side effects, as well, though no such changes are in this branch. This change also makes testing a mile easier, since tests can interact with props and callbacks instead of emulating the whole HTTP request stack and the Pilcrow API. This change removes do-very-little tests.
* Add missing awaits on goto() callsOwen Jacobson2025-02-21
|
* Be a bit more careful with the nesting of anchors and list items.Owen Jacobson2025-02-21
| | | | Browsers cope with weird nestings mostly fine, but there's no upside for us in testing that.
* Remove unused type attribute on textareaOwen Jacobson2025-02-21
|
* npm formatOwen Jacobson2025-02-15
|
* Add placeholder to message inputKit La Touche2025-01-28
|
* npm run formatOwen Jacobson2025-01-11
|
* Use margins, rather than alternating float/non-float elements, to lay out ↵Owen Jacobson2025-01-11
| | | | own/other messages.
* Stylize channel badgesKit La Touche2025-01-05
|
* A bit more stylingKit La Touche2025-01-05
|
* Stylize more betterKit La Touche2025-01-05
|
* Fix invite copy buttonKit La Touche2025-01-04
|
* Add username class to username spanKit La Touche2025-01-04
|
* Just oh so many stylesKit La Touche2025-01-01
|
* Strip out Tailwind etcKit La Touche2024-12-30
|
* Merge branch 'main' into wip/stylizeKit La Touche2024-12-03
|\
| * Style Channels differently when they have unreadsKit La Touche2024-11-29
| | | | | | | | I dunno, I like the fleuron. Maybe it's too twee?
| * Use Luxon dates on Message store and componentKit La Touche2024-11-29
| | | | | | | | | | | | This includes jamming the "at" of a message into a data- attribute on the Message component, so that it can later be used by parent components via Plain Old Javascript and the .dataset attribute of an HTML node.
* | Add styling for pre blocks in messagesKit La Touche2024-11-30
| |
* | Merge branch 'main' into wip/stylizeKit La Touche2024-11-28
|\|
| * Best-guess an ARIA role for this element.Owen Jacobson2024-11-26
| | | | | | | | Svelte's lint complains if you add interaction to a div but don't give that div an ARIA role.
| * Use store.set where appropriateKit La Touche2024-11-22
| | | | | | | | If you're setting it to a static value, use set.
| * Clean up extraneous variablesKit La Touche2024-11-22
| | | | | | | | Just use state, don't derive from a non-state local variable.
| * HTML-ify message bodies once, on receipt, and not every time the message is ↵Owen Jacobson2024-11-15
| | | | | | | | | | | | rendered. ~16% of the `hi development` channel's rendering time was taken up on this.