@convokitapp/vue-ui 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +158 -0
- package/PARITY.md +60 -0
- package/README.md +185 -7
- package/dist/index.cjs +540 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +103 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +302 -14
- package/dist/index.d.ts +302 -14
- package/dist/index.js +547 -45
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,163 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
- Edit and delete your own messages. `useConversation` /
|
|
6
|
+
`ConversationController` gain `editingMessage` (the row being edited, as
|
|
7
|
+
the user saw it when editing started; null otherwise), `canEditMessages` /
|
|
8
|
+
`canDeleteMessages` (adapter support, decided once at construction),
|
|
9
|
+
`startEditing(messageId)` (a no-op unless the row is rendered, the viewer's
|
|
10
|
+
own, confirmed, not known to be gone, the viewer's role when known is not
|
|
11
|
+
`READ` and the adapter implements `editMessage`; sends nothing, not even a
|
|
12
|
+
typing update), `cancelEditing()`, `saveEdit(text)` and
|
|
13
|
+
`deleteMessage(messageId)` (both resolve to a boolean and reject when the
|
|
14
|
+
adapter lacks the member). `saveEdit` trims the text, sends `null` for an
|
|
15
|
+
empty caption, never sends an empty text for a text-only message, and always
|
|
16
|
+
sends the SNAPSHOT's `revision`, never the live row's; the response is
|
|
17
|
+
merged through the tombstone and precedence guards and edit mode ends. A
|
|
18
|
+
stale revision (409 `REVISION_CONFLICT`) reloads the row once through
|
|
19
|
+
`getMessage`, replaces `editingMessage` with it (so the next save carries
|
|
20
|
+
the fresh revision), reports the conflict through `error` and keeps edit
|
|
21
|
+
mode; when a row for the edited message with a higher revision reaches the
|
|
22
|
+
store by itself (a row image, a hydration, a reconcile or a refresh) the
|
|
23
|
+
same conflict state is entered without a request. A coded 404
|
|
24
|
+
(`MESSAGE_NOT_FOUND`, on the save or on the conflict reload) removes the
|
|
25
|
+
row and ends edit mode; any other failure, including a 403 and the uncoded
|
|
26
|
+
404 a 0.7 backend answers, is reported through `error` without evicting
|
|
27
|
+
anything and keeps edit mode. `deleteMessage` keeps the row until the server
|
|
28
|
+
answers (no optimistic removal): success or `MESSAGE_NOT_FOUND` tombstone
|
|
29
|
+
and remove it (late responses, row images and hydrations for the id are
|
|
30
|
+
dropped, the acknowledgement target is re-resolved, edit mode on it ends);
|
|
31
|
+
other failures keep it and report through `error`. A `message_deleted`
|
|
32
|
+
notification, a reconcile that no longer sees the row or a history eviction
|
|
33
|
+
also ends edit mode on it.
|
|
34
|
+
- Row precedence. Wherever rows meet (live events, hydration, reconcile,
|
|
35
|
+
older pages, send and edit responses) the higher `Message.revision` now
|
|
36
|
+
wins when both rows carry one and at least one is above 0, and a lower
|
|
37
|
+
revision never overwrites a higher one; equal revisions and rows without a
|
|
38
|
+
usable revision (pending rows, 0.7 backends where every row is 0) keep the
|
|
39
|
+
`updatedAt ?? createdAt` rule with its "complete row wins on ties"
|
|
40
|
+
heuristic. Pending rows are created with `revision: 0`.
|
|
41
|
+
- Default rows. Eligible rows (the viewer's own confirmed rows while the
|
|
42
|
+
viewer's role is not `READ`, or whatever `canEditMessage` says; never
|
|
43
|
+
pending rows) render `.ckui-message-actions` with `Edit message` and
|
|
44
|
+
`Delete message` icon buttons only while the matching callback is present
|
|
45
|
+
and the adapter supports the action. The actions are always in the DOM;
|
|
46
|
+
only `@media (hover: hover) and (pointer: fine)` conceals them
|
|
47
|
+
(`opacity: 0; pointer-events: none`, never `display: none`) until the row is
|
|
48
|
+
hovered or focused, so touch and keyboard users always see them. `Delete
|
|
49
|
+
message` opens an inline prompt (`role="group"`, accessible name
|
|
50
|
+
`Delete this message?`) with `Delete` (`Confirm delete`) and `Cancel`
|
|
51
|
+
(`Cancel delete`); `confirmDelete` replaces it. Rows with `revision > 0`
|
|
52
|
+
show `Edited` (`.ckui-message-edited`, accessible name `Edited`) beside the
|
|
53
|
+
time, for every sender, never on pending rows. Apart from that label, rows
|
|
54
|
+
that are not eligible, and every row when the callbacks are absent, render
|
|
55
|
+
byte-identically to 0.7.0.
|
|
56
|
+
- Composer edit mode (`ConversationView`, driven by `editingMessage`).
|
|
57
|
+
Entering stashes the unsent draft and prefills the field with the message
|
|
58
|
+
text without a typing update; the submit path (Enter, the primary button,
|
|
59
|
+
a custom composer's `send`) calls `onSaveEdit(message, text)` instead of
|
|
60
|
+
`onSendMessage`, without clearing the field first. `false` keeps edit mode
|
|
61
|
+
and the edited text; any other result, and Cancel (visible `Cancel`,
|
|
62
|
+
accessible name `Cancel editing`) or Escape, restore the stash and report
|
|
63
|
+
typing from it (`onTypingChange(stash.trim().length > 0)`). When edit mode
|
|
64
|
+
ends externally the field keeps text the user changed and restores the
|
|
65
|
+
stash only when it is empty or still the snapshot's text; a refreshed
|
|
66
|
+
snapshot for the same message leaves the draft alone. The primary button is
|
|
67
|
+
`Check` with the accessible name `Save message` (`Send message` otherwise)
|
|
68
|
+
and is enabled while the field has text or the edited row has attachments
|
|
69
|
+
(an empty caption clears it); a banner (`.ckui-composer__editing`,
|
|
70
|
+
`role="status"`) shows `Editing message` with the message's text or
|
|
71
|
+
attachment count. `#composer` slots receive `editing` and `cancelEdit`
|
|
72
|
+
while editing.
|
|
73
|
+
- Controlled views. `ConversationView` gains `editingMessage`,
|
|
74
|
+
`onEditMessage`, `onSaveEdit`, `onCancelEdit`, `onDeleteMessage`,
|
|
75
|
+
`canEditMessage` and `confirmDelete` (and the `edit-message`, `save-edit`,
|
|
76
|
+
`cancel-edit`, `delete-message` emits); `MessageListView` gains
|
|
77
|
+
`onEditMessage`, `onDeleteMessage`, `canEditMessage` and `confirmDelete`.
|
|
78
|
+
`#message` slots receive `isEdited`, `canEdit`, `canDelete` and, while
|
|
79
|
+
eligible, `edit()` and `remove()` (`remove` asks `confirmDelete` when the
|
|
80
|
+
view has one, otherwise deletes at once: a custom row owns its prompt). The
|
|
81
|
+
bound `Conversation` wires the composable (`editingMessage`,
|
|
82
|
+
`startEditing`, `saveEdit`, `cancelEditing`, `deleteMessage`) only while the
|
|
83
|
+
adapter supports each action, forwards `canEditMessage` and
|
|
84
|
+
`confirmDelete`, and emits the four events like `send-message`.
|
|
85
|
+
- Adapter: optional `editMessage?(messageId, { text, revision })` and
|
|
86
|
+
`deleteMessage?(messageId)`; the default adapter implements both. Without
|
|
87
|
+
them nothing renders and `saveEdit` / `deleteMessage` reject. (The Flutter
|
|
88
|
+
and Android UIs make the members required on their `ConvoKitUiClient`: a
|
|
89
|
+
breaking change for custom implementers there, like `listInbox` in 0.6.0.)
|
|
90
|
+
A custom adapter must reject a stale revision with `code:
|
|
91
|
+
'REVISION_CONFLICT'` and a gone message with `code: 'MESSAGE_NOT_FOUND'`;
|
|
92
|
+
an uncoded 404 is reported, never treated as a deleted message.
|
|
93
|
+
- Requires core SDK 0.8.x (`editMessage`, `deleteMessage`, the required
|
|
94
|
+
`Message.revision`, `isEditedMessage`) and the coordinated backend
|
|
95
|
+
(`PATCH`/`DELETE /api/v1/messages/:id/own`). Consumer-built `Message`
|
|
96
|
+
literals gain `revision`. Mixed fleet: against a 0.7 backend the author
|
|
97
|
+
routes answer an uncoded 404 (`HTTP_ERROR`) that surfaces as `error` and
|
|
98
|
+
keeps the row; every row parses with revision 0, so nothing reads `Edited`
|
|
99
|
+
and precedence falls back to timestamps. Deleting a message does not
|
|
100
|
+
retract files other members already received; stored files are reclaimed by
|
|
101
|
+
the existing user or app deletion cleanup.
|
|
102
|
+
|
|
103
|
+
## 0.7.0
|
|
104
|
+
|
|
105
|
+
- Private mark unread. `useConversationList` / `ConversationListController`
|
|
106
|
+
gain `markUnread(conversationId)` and
|
|
107
|
+
`clearUnread(conversationId, { ifVersion? })`; the latter resolves to the
|
|
108
|
+
response's `cleared` ("this request removed the marker"). On any 200 (a mark,
|
|
109
|
+
or a clear whether `cleared` is true or false) the row's current summary
|
|
110
|
+
takes the response's `unreadMarkedAt` and `privateStateVersion` as one unit,
|
|
111
|
+
only while the response is not older than the stored version, and
|
|
112
|
+
`isUnread` is recomputed as
|
|
113
|
+
`unreadCount > 0 || unreadCountCapped || unreadMarkedAt !== null`; a lower
|
|
114
|
+
version is ignored so a delayed response never resurrects a marker a newer
|
|
115
|
+
action removed. Both methods reject when the adapter lacks the new members
|
|
116
|
+
or the store is disposed or session-ended (nothing is sent); request
|
|
117
|
+
failures are reported through `error` without evicting rows and reject.
|
|
118
|
+
Other devices learn of the change through `inbox_activity`, which the
|
|
119
|
+
backend now also sends for the caller's own marker changes.
|
|
120
|
+
- Rows. The unread predicate (bold title, `data-unread`) is
|
|
121
|
+
`summary.isUnread || summary.unreadCount > 0 || summary.unreadCountCapped`,
|
|
122
|
+
so consumer-built summaries that omit `isUnread` keep their badges. A count
|
|
123
|
+
(or a capped count) keeps the numeric badge unchanged; `isUnread` without
|
|
124
|
+
one renders a numberless dot, `.ckui-unread-badge.ckui-unread-badge--dot`
|
|
125
|
+
(`role="img"`, accessible name `Unread`, never `0 unread`), in the `badge`
|
|
126
|
+
colour. It is spread into the row's meta span, so rows without a badge no
|
|
127
|
+
longer carry an empty comment vnode. `#conversation-item` slots receive the
|
|
128
|
+
same `summary` with its three new fields; no signature changes.
|
|
129
|
+
- Rooms. The store captures `conversation.membership.privateStateVersion` (and
|
|
130
|
+
whether `membership.unreadMarkedAt` was set) once per open, the first time
|
|
131
|
+
its `conversation` goes from null to a DTO: on `loadInitial`, or on the
|
|
132
|
+
reconcile that follows a transient first-load failure; a refresh that
|
|
133
|
+
replaces an existing DTO never recaptures, and the capture is dropped
|
|
134
|
+
whenever `conversation` returns to null (reload, dispose, authoritative
|
|
135
|
+
401/403/404). Every targeted acknowledgement of that open sends
|
|
136
|
+
`{ throughMessageId, privateStateVersion }`; a DTO without `membership`
|
|
137
|
+
(0.6 backend) sends the 0.6 body. An automatic acknowledgement for a row
|
|
138
|
+
received before the DTO (during the load, or while a transient first-load
|
|
139
|
+
failure stands) waits for the capture, like it waits while hidden, and is
|
|
140
|
+
issued with the version once the open captures. A room opened with a
|
|
141
|
+
marker that renders no non-pending, acknowledgeable row calls
|
|
142
|
+
`clearConversationUnread(conversationId, { ifVersion: captured })` once per
|
|
143
|
+
open under the load-acknowledgement triggers and visibility gating (also on
|
|
144
|
+
an explicit `markRead()`, never once a row is rendered); `cleared: false`
|
|
145
|
+
is not an error. The store still never sends an acknowledgement without a
|
|
146
|
+
target, fabricates no read state and holds no marker state.
|
|
147
|
+
- Adapter: optional `markConversationUnread?(conversationId)` and
|
|
148
|
+
`clearConversationUnread?(conversationId, { ifVersion? })`; the default
|
|
149
|
+
adapter implements both. `markConversationRead` options may now carry
|
|
150
|
+
`privateStateVersion`: custom adapters must forward the options object
|
|
151
|
+
unchanged, or acknowledgements advance the position but never clear the
|
|
152
|
+
marker. Adapters without `clearConversationUnread` leave the marker in
|
|
153
|
+
empty rooms.
|
|
154
|
+
- Requires core SDK 0.7.x (`markConversationUnread`, `clearConversationUnread`,
|
|
155
|
+
`Conversation.membership`, the required `InboxSummary` members `isUnread`,
|
|
156
|
+
`unreadMarkedAt` and `privateStateVersion`) and the coordinated backend.
|
|
157
|
+
Consumer-built `InboxSummary` literals gain those three members. Mixed
|
|
158
|
+
fleet: against a 0.6 backend the new adapter members fail with a 404
|
|
159
|
+
(`HTTP_ERROR`), acknowledgements carry no version and lists show no dots.
|
|
160
|
+
|
|
3
161
|
## 0.6.0
|
|
4
162
|
|
|
5
163
|
- Inbox previews and unread counts. SDK-backed lists page `listInbox` by
|
package/PARITY.md
CHANGED
|
@@ -20,7 +20,20 @@ while using Vue-native composition patterns.
|
|
|
20
20
|
| Row replacements receive `summary` / `currentUserId` | `rowBuilder` | Render props | `#conversation-item` slot |
|
|
21
21
|
| `inbox_activity` throttle (`activityRefreshWindowMs`) | Yes | Yes | Yes |
|
|
22
22
|
| Adapter `listInbox` / `onInboxActivity` | Required | Optional | Optional |
|
|
23
|
+
| List `markUnread` / `clearUnread` (0.7.0) | Controller | `useConversationList` | `useConversationList` |
|
|
24
|
+
| Numberless unread dot (`isUnread`, count 0, name `Unread`) | `ConvoKitUnreadDot` | `.ckui-badge--dot` | `.ckui-unread-badge--dot` |
|
|
25
|
+
| Acknowledgements carry the version captured at open | Yes | Yes | Yes |
|
|
26
|
+
| Empty marked room clears through `clearConversationUnread` | Yes | Yes | Yes |
|
|
27
|
+
| Adapter `markConversationUnread` / `clearConversationUnread` | Required | Optional | Optional |
|
|
23
28
|
| Inbox 404 fallback to the offset path | Yes | Yes | Yes |
|
|
29
|
+
| Edit / delete own messages (0.8.0: `editingMessage`, `startEditing`, `cancelEditing`, `saveEdit`, `deleteMessage`) | Controller | `useConversation` | `useConversation` |
|
|
30
|
+
| Row precedence by `Message.revision`, timestamps on ties | Yes | Yes | Yes |
|
|
31
|
+
| Default row actions + `Edited` label (accessible names `Edit message` / `Delete message` / `Edited`) | Long press + sheet | `.ckui-message-actions` hover/focus reveal | `.ckui-message-actions` hover/focus reveal |
|
|
32
|
+
| Delete confirmation (`Confirm delete` / `Cancel delete`), `confirmDelete` override | `showDialog` | Inline prompt | Inline prompt |
|
|
33
|
+
| Composer edit mode (banner, `Save message`, `Cancel editing`, caption clearing, draft stash) | Yes | Yes | Yes |
|
|
34
|
+
| Controlled edit surface (`editingMessage`, `onEditMessage`, `onSaveEdit`, `onCancelEdit`, `onDeleteMessage`, `canEditMessage`) | Named params | Props | Props + emits |
|
|
35
|
+
| Row replacements receive `isEdited` / `canEdit` / `canDelete` / `edit` / `remove` | Controller state | Render props | `#message` slot |
|
|
36
|
+
| Adapter `editMessage` / `deleteMessage` | Required | Optional | Optional |
|
|
24
37
|
| Search, archived, participant, predicate, sort filters | Yes | Yes | Yes |
|
|
25
38
|
| Realtime messages, typing, reads | Yes | Yes | Yes |
|
|
26
39
|
| Pending state without provisional timestamps or receipts | Yes | Yes | Yes |
|
|
@@ -68,3 +81,50 @@ unread badge (`99+` above 99 or when capped; accessible name
|
|
|
68
81
|
A 404 from the inbox route falls back to the offset path for the store's life
|
|
69
82
|
without evicting rows. The parity script checks API presence only; the store,
|
|
70
83
|
rendered-view and actual-SDK protocol tests verify behavior.
|
|
84
|
+
|
|
85
|
+
## Mark-unread parity (0.7.0)
|
|
86
|
+
|
|
87
|
+
Every UI keeps the marker private to the caller, exposes list-level
|
|
88
|
+
`markUnread` / `clearUnread` (the latter returning the response's `cleared`),
|
|
89
|
+
applies a mutation response to the current summary only when its
|
|
90
|
+
`privateStateVersion` is not older than the stored one, and recomputes
|
|
91
|
+
`isUnread` as `unreadCount > 0 || unreadCountCapped || unreadMarkedAt != null`.
|
|
92
|
+
Rows keep the numeric badge for a count and render a numberless dot named
|
|
93
|
+
`Unread` for a marker without one. Room stores capture the opened
|
|
94
|
+
`membership.privateStateVersion` once per open and send it with every
|
|
95
|
+
targeted acknowledgement; a room opened with a marker that renders nothing
|
|
96
|
+
clears it once through `clearConversationUnread(id, { ifVersion })`, never
|
|
97
|
+
through an untargeted acknowledgement. The adapter additions are optional in
|
|
98
|
+
the TypeScript UIs and required in the Dart and Kotlin ones (a breaking
|
|
99
|
+
change for custom implementers there, like `listInbox` in 0.6.0). The parity
|
|
100
|
+
script checks API presence only; the store, rendered-view and actual-SDK
|
|
101
|
+
protocol tests verify behavior.
|
|
102
|
+
|
|
103
|
+
## Message-edit parity (0.8.0)
|
|
104
|
+
|
|
105
|
+
Every UI lets the viewer edit and delete their own confirmed messages through
|
|
106
|
+
the room controller (`editingMessage`, `canEditMessages` /
|
|
107
|
+
`canDeleteMessages`, `startEditing`, `cancelEditing`, `saveEdit`,
|
|
108
|
+
`deleteMessage`), sends the SNAPSHOT's `revision` captured when editing
|
|
109
|
+
started (never the live row's), reloads the row once on 409
|
|
110
|
+
`REVISION_CONFLICT` and refreshes the snapshot so the next save carries the
|
|
111
|
+
fresh revision, enters the same conflict state without a request when a
|
|
112
|
+
newer row for the edited message arrives on its own, removes the row only on
|
|
113
|
+
a coded 404 `MESSAGE_NOT_FOUND` (an uncoded 404 from a 0.7 backend is an
|
|
114
|
+
error that keeps the row and edit mode), and keeps the row until the server
|
|
115
|
+
accepts a deletion. Rows merge by the higher `Message.revision` when both
|
|
116
|
+
carry one and at least one is above 0, falling back to `updatedAt ??
|
|
117
|
+
createdAt` on ties and for pending rows. Default rows offer `Edit message` /
|
|
118
|
+
`Delete message` (web: `.ckui-message-actions`, always in the DOM, concealed
|
|
119
|
+
only under `(hover: hover) and (pointer: fine)` until hover or focus; natives:
|
|
120
|
+
long press or context menu) with a confirmation (`Confirm delete` /
|
|
121
|
+
`Cancel delete`; `confirmDelete` overrides it) and show `Edited` for
|
|
122
|
+
`revision > 0`; rows that are not eligible render as in 0.7.0. The composer's
|
|
123
|
+
single submit path saves while editing, prefills the message text without a
|
|
124
|
+
typing update, stashes and restores the unsent draft, keeps the edited text on
|
|
125
|
+
failure or conflict, and allows an empty caption for a message with
|
|
126
|
+
attachments (`text: null`). The adapter additions are optional in the
|
|
127
|
+
TypeScript UIs and required in the Dart and Kotlin ones (a breaking change for
|
|
128
|
+
custom implementers there, like `listInbox` in 0.6.0); the Swift UI's bound
|
|
129
|
+
view gains `onController`. The parity script checks API presence only; the
|
|
130
|
+
store, rendered-view and actual-SDK protocol tests verify behavior.
|
package/README.md
CHANGED
|
@@ -11,6 +11,174 @@ React package: system typography, separated rows, subtle borders, compact
|
|
|
11
11
|
actions, and restrained radii. It remains framework-CSS independent and does
|
|
12
12
|
not require Tailwind in the host application.
|
|
13
13
|
|
|
14
|
+
## 0.8.0 edit and delete your own messages
|
|
15
|
+
|
|
16
|
+
This release requires JavaScript SDK 0.8.x (`editMessage`, `deleteMessage`,
|
|
17
|
+
the required `Message.revision`, `isEditedMessage`) and the coordinated
|
|
18
|
+
backend (`PATCH`/`DELETE /api/v1/messages/:id/own`). Consumer-built `Message`
|
|
19
|
+
literals (fixtures, controlled views) gain `revision`.
|
|
20
|
+
|
|
21
|
+
- Who may: the viewer's own confirmed messages, while the viewer's role (the
|
|
22
|
+
0.7 `membership`, else the viewer's participant row) is not `READ`. The
|
|
23
|
+
server checks the same rules (403 for other members' messages and `READ`
|
|
24
|
+
roles); custom row eligibility goes through `canEditMessage`.
|
|
25
|
+
- Controller members (`useConversation` and the `Conversation` controller):
|
|
26
|
+
`editingMessage` (the row being edited as the user saw it; null otherwise),
|
|
27
|
+
`canEditMessages` / `canDeleteMessages` (adapter support),
|
|
28
|
+
`startEditing(messageId)`, `cancelEditing()`, `saveEdit(text)` and
|
|
29
|
+
`deleteMessage(messageId)`. `saveEdit` sends the snapshot's `revision`,
|
|
30
|
+
never the live row's: a stale one answers 409 `REVISION_CONFLICT`, the
|
|
31
|
+
store reloads the row once, shows its current content as the new snapshot
|
|
32
|
+
(the banner and the row update, the draft stays) and reports the conflict
|
|
33
|
+
through `error`; the next save carries the fresh revision. A newer row for
|
|
34
|
+
the edited message arriving on its own (row image, hydration, reconcile)
|
|
35
|
+
enters the same state without a request. A coded 404 `MESSAGE_NOT_FOUND`
|
|
36
|
+
removes the row and ends edit mode; a 403, a 500, a network failure or the
|
|
37
|
+
uncoded 404 a 0.7 backend answers keep the row, the history and edit mode
|
|
38
|
+
and set `error`. `deleteMessage` removes nothing until the server accepts
|
|
39
|
+
(or answers `MESSAGE_NOT_FOUND`); other devices learn through the room's
|
|
40
|
+
deletion notification and late responses for a removed id are dropped.
|
|
41
|
+
- Precedence: when rows meet, the higher `Message.revision` wins when both
|
|
42
|
+
carry one and at least one is above 0; ties and rows without a usable
|
|
43
|
+
revision keep the `updatedAt ?? createdAt` rule.
|
|
44
|
+
- Default row actions: eligible rows render `.ckui-message-actions` with
|
|
45
|
+
`Edit message` and `Delete message` buttons, always in the DOM. Only
|
|
46
|
+
`@media (hover: hover) and (pointer: fine)` conceals them (`opacity` and
|
|
47
|
+
`pointer-events`, never `display: none`) until the row is hovered or
|
|
48
|
+
focused; touch and keyboard users always see them. `Delete message` opens an
|
|
49
|
+
inline prompt (`role="group"`, name `Delete this message?`) with `Delete`
|
|
50
|
+
(`Confirm delete`) and `Cancel` (`Cancel delete`); pass `confirmDelete` to
|
|
51
|
+
use your own dialog. Edited rows show `Edited` (name `Edited`) beside the
|
|
52
|
+
time. Apart from that label, rows that are not eligible, and every row
|
|
53
|
+
without the callbacks, render byte-identically to 0.7.0.
|
|
54
|
+
- Composer edit mode: entering stashes the unsent draft and prefills the
|
|
55
|
+
field with the message text without a typing update. Enter, the primary
|
|
56
|
+
button (`Check`, name `Save message`) or a custom composer's `send` saves;
|
|
57
|
+
Cancel (name `Cancel editing`) and Escape restore the stash. Save is
|
|
58
|
+
enabled while the field has text or the message has attachments: an empty
|
|
59
|
+
caption is sent as `null` and clears it (attachments are never changed). A
|
|
60
|
+
refused save keeps the edited text and edit mode; a successful one and a
|
|
61
|
+
cancel restore the stash. `#composer` slots receive `editing` and
|
|
62
|
+
`cancelEdit` while editing.
|
|
63
|
+
- Controlled views: `ConversationView` accepts `editingMessage`,
|
|
64
|
+
`onEditMessage`, `onSaveEdit`, `onCancelEdit`, `onDeleteMessage`,
|
|
65
|
+
`canEditMessage` and `confirmDelete` (also as `@edit-message`, `@save-edit`,
|
|
66
|
+
`@cancel-edit`, `@delete-message`); `MessageListView` accepts
|
|
67
|
+
`onEditMessage`, `onDeleteMessage`, `canEditMessage` and `confirmDelete`.
|
|
68
|
+
Without the callbacks nothing new renders. `#message` slots receive
|
|
69
|
+
`isEdited`, `canEdit`, `canDelete` and, while eligible, `edit()` and
|
|
70
|
+
`remove()` (`remove` runs `confirmDelete` when present, otherwise deletes at
|
|
71
|
+
once). The bound `Conversation` wires the composable, forwards
|
|
72
|
+
`canEditMessage` and `confirmDelete`, and emits the four events.
|
|
73
|
+
- Adapter additions: `ConvoKitUiClient.editMessage?(messageId, { text,
|
|
74
|
+
revision })` and `deleteMessage?(messageId)` are optional; the default
|
|
75
|
+
adapter implements both. Without them the rows render no actions and
|
|
76
|
+
`saveEdit` / `deleteMessage` reject. (The Flutter and Android UIs make the
|
|
77
|
+
same members required on their `ConvoKitUiClient`: a breaking change for
|
|
78
|
+
custom implementers there, like `listInbox` in 0.6.0.) Custom adapters must
|
|
79
|
+
reject a stale revision with `code: 'REVISION_CONFLICT'` and a gone message
|
|
80
|
+
with `code: 'MESSAGE_NOT_FOUND'`.
|
|
81
|
+
- Deleting a message removes it and its attachments from the conversation for
|
|
82
|
+
every member and cannot be undone; files already received or downloaded
|
|
83
|
+
cannot be retracted, and stored files are reclaimed by the existing user or
|
|
84
|
+
app deletion cleanup.
|
|
85
|
+
- Mixed fleet: 0.7 clients ignore `revision`; against a 0.7 backend the author
|
|
86
|
+
routes answer an uncoded 404 (`HTTP_ERROR`) that keeps the row and edit
|
|
87
|
+
mode, every row parses with revision 0 and nothing reads `Edited`.
|
|
88
|
+
|
|
89
|
+
```vue
|
|
90
|
+
<script setup lang="ts">
|
|
91
|
+
import { ref } from 'vue'
|
|
92
|
+
import type { Message } from '@convokitapp/sdk'
|
|
93
|
+
import { ConversationView } from '@convokitapp/vue-ui'
|
|
94
|
+
|
|
95
|
+
const editing = ref<Message | null>(null)
|
|
96
|
+
const rows = ref<Message[]>([])
|
|
97
|
+
async function saveEdit(message: Message, text: string) {
|
|
98
|
+
// Send message.revision; on 409 REVISION_CONFLICT reload the row and let the user retry.
|
|
99
|
+
const saved = await api.editMessage(message.id, { text: text || null, revision: message.revision })
|
|
100
|
+
rows.value = rows.value.map((row) => (row.id === saved.id ? saved : row))
|
|
101
|
+
editing.value = null
|
|
102
|
+
}
|
|
103
|
+
const confirmDelete = (message: Message) => window.confirm(`Delete "${message.text ?? 'this message'}"?`)
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<template>
|
|
107
|
+
<ConversationView
|
|
108
|
+
v-bind="conversationProps"
|
|
109
|
+
:messages="rows"
|
|
110
|
+
:editing-message="editing"
|
|
111
|
+
@edit-message="(message) => (editing = message)"
|
|
112
|
+
@save-edit="saveEdit"
|
|
113
|
+
@cancel-edit="editing = null"
|
|
114
|
+
@delete-message="(message) => api.deleteMessage(message.id)"
|
|
115
|
+
:confirm-delete="confirmDelete"
|
|
116
|
+
/>
|
|
117
|
+
</template>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 0.7.0 private mark unread
|
|
121
|
+
|
|
122
|
+
This release requires JavaScript SDK 0.7.x (`markConversationUnread`,
|
|
123
|
+
`clearConversationUnread`, `Conversation.membership`, the `InboxSummary`
|
|
124
|
+
members `isUnread`, `unreadMarkedAt` and `privateStateVersion`) and the
|
|
125
|
+
coordinated backend (`POST`/`DELETE /api/v1/conversations/:id/unread`, the
|
|
126
|
+
`privateStateVersion` acknowledgement field, the `membership` sibling on
|
|
127
|
+
`GET /api/v1/conversations/:id`).
|
|
128
|
+
|
|
129
|
+
- The marker is private: other members, webhooks and `read` events never see
|
|
130
|
+
it. `useConversationList` and the `ConversationList` controller gain
|
|
131
|
+
`markUnread(conversationId)` and `clearUnread(conversationId, { ifVersion? })`.
|
|
132
|
+
Wire them through `#conversation-item` (a row action or menu) or
|
|
133
|
+
`@controller-change`; the default row adds no affordance. `clearUnread`
|
|
134
|
+
resolves to the response's `cleared` ("this request removed the marker",
|
|
135
|
+
not "the room is read"). Both reject from a disposed or session-ended
|
|
136
|
+
controller without sending anything.
|
|
137
|
+
- Summaries carry `isUnread`
|
|
138
|
+
(`unreadCount > 0 || unreadCountCapped || unreadMarkedAt !== null`),
|
|
139
|
+
`unreadMarkedAt` and `privateStateVersion`. After a mark or a clear the
|
|
140
|
+
row's summary takes the response only while it is not older than what the
|
|
141
|
+
row already holds; a delayed response never resurrects a marker a newer
|
|
142
|
+
action removed. Other devices refresh through `inbox_activity`, which the
|
|
143
|
+
backend also sends for your own marker changes; `activityRefreshWindowMs`
|
|
144
|
+
applies as before.
|
|
145
|
+
- Dot rule: a count (or a capped count) keeps the numeric badge (`5`, `99+`,
|
|
146
|
+
accessible name `<count> unread`); `isUnread` without one renders a
|
|
147
|
+
numberless dot with the accessible name `Unread`, never `0 unread`
|
|
148
|
+
(`.ckui-unread-badge.ckui-unread-badge--dot`, `badge` colour). The title is
|
|
149
|
+
bold and `data-unread` is set in both cases; rows without a summary are
|
|
150
|
+
unchanged.
|
|
151
|
+
- Capture at open: the room store captures
|
|
152
|
+
`conversation.membership.privateStateVersion` once per open (the first time
|
|
153
|
+
its `conversation` goes from null to a DTO: on load, or on the reconcile
|
|
154
|
+
after a transient first-load failure) and sends it with every targeted
|
|
155
|
+
acknowledgement of that open. A refresh never recaptures, so a mark made
|
|
156
|
+
elsewhere while the room is open survives its acknowledgements until the
|
|
157
|
+
room is reopened; a DTO without `membership` (0.6 backend) sends no version.
|
|
158
|
+
An automatic acknowledgement for a row that arrives before the DTO (during
|
|
159
|
+
the load, or while a transient first-load failure stands) waits for the
|
|
160
|
+
capture, like it waits while hidden, so it too carries the version.
|
|
161
|
+
- Empty rooms: when the opened membership carries a marker and nothing
|
|
162
|
+
non-pending is rendered, the store calls
|
|
163
|
+
`clearConversationUnread(conversationId, { ifVersion })` once per open under
|
|
164
|
+
the same triggers and visibility gating as the load acknowledgement (and on
|
|
165
|
+
an explicit `markRead()`); once a row is rendered its acknowledgement clears
|
|
166
|
+
the marker instead. The UI still never sends an acknowledgement without a
|
|
167
|
+
target. `cleared: false` is not an error.
|
|
168
|
+
- Adapter additions: `ConvoKitUiClient.markConversationUnread?(conversationId)`
|
|
169
|
+
and `clearConversationUnread?(conversationId, { ifVersion? })` are optional;
|
|
170
|
+
the default adapter implements both. Custom adapters without them make
|
|
171
|
+
`markUnread` / `clearUnread` reject and leave the marker in empty rooms.
|
|
172
|
+
(The Flutter and Android UIs make the same members, and the
|
|
173
|
+
`privateStateVersion` acknowledgement parameter, required on their
|
|
174
|
+
`ConvoKitUiClient`: a breaking change for custom implementers there, like
|
|
175
|
+
`listInbox` in 0.6.0.)
|
|
176
|
+
`markConversationRead` options now carry `privateStateVersion`; forward the
|
|
177
|
+
options object unchanged, or acknowledgements advance the position but never
|
|
178
|
+
clear the marker.
|
|
179
|
+
- Mixed fleet: 0.6 lists ignore `isUnread`; against a 0.6 backend the new
|
|
180
|
+
adapter members fail with a 404 (`HTTP_ERROR`) and no dots render.
|
|
181
|
+
|
|
14
182
|
## 0.6.0 inbox previews and unread counts
|
|
15
183
|
|
|
16
184
|
This release requires JavaScript SDK 0.6.x (`listInbox`, `onInboxActivity`)
|
|
@@ -230,15 +398,20 @@ state directly. Every important surface has a named slot:
|
|
|
230
398
|
<template #header="{ conversation }">
|
|
231
399
|
<SupportHeader :title="conversation.displayTitle" />
|
|
232
400
|
</template>
|
|
233
|
-
<template #message="{ message, isCurrentUser, readerIds }">
|
|
234
|
-
<SupportBubble :message="message" :mine="isCurrentUser" :read-by="readerIds" />
|
|
401
|
+
<template #message="{ message, isCurrentUser, readerIds, isEdited, edit, remove }">
|
|
402
|
+
<SupportBubble :message="message" :mine="isCurrentUser" :read-by="readerIds" :edited="isEdited" @edit="edit" @delete="remove" />
|
|
235
403
|
</template>
|
|
236
|
-
<template #composer="{ value, setValue, send }">
|
|
237
|
-
<BrandComposer :model-value="value" @update:model-value="setValue" @send="send" />
|
|
404
|
+
<template #composer="{ value, setValue, send, editing, cancelEdit }">
|
|
405
|
+
<BrandComposer :model-value="value" :editing="editing" @update:model-value="setValue" @send="send" @cancel="cancelEdit" />
|
|
238
406
|
</template>
|
|
239
407
|
</ConversationView>
|
|
240
408
|
```
|
|
241
409
|
|
|
410
|
+
`#message` slots receive `isEdited`, `canEdit`, `canDelete` and, while the row
|
|
411
|
+
is eligible, `edit()` and `remove()`; `#composer` slots receive `editing` and
|
|
412
|
+
`cancelEdit` while the view is in edit mode, and their `send` saves the edit
|
|
413
|
+
(0.8.0).
|
|
414
|
+
|
|
242
415
|
Available slots include `conversation-item`, `separator`, `header`, `message`,
|
|
243
416
|
`media`, `read-receipt`, `composer`, `typing-indicator`, `loading`, `empty`,
|
|
244
417
|
`error`, `load-more`, `loading-older`, and `message-error`.
|
|
@@ -248,11 +421,16 @@ Available slots include `conversation-item`, `separator`, `header`, `message`,
|
|
|
248
421
|
Use `useConversationList` and `useConversation` when you want ConvoKit's
|
|
249
422
|
pagination, de-duplication, realtime, typing, and read state without the
|
|
250
423
|
default UI. Both return readonly Vue refs plus actions and a `dispose()` method.
|
|
251
|
-
`useConversationList` also exposes `summaries` and `currentUserId
|
|
252
|
-
`activityRefreshWindowMs
|
|
424
|
+
`useConversationList` also exposes `summaries` and `currentUserId`, accepts
|
|
425
|
+
`activityRefreshWindowMs` (`ConversationList` forwards the same option), and
|
|
426
|
+
offers `markUnread(conversationId)` / `clearUnread(conversationId, options?)`
|
|
427
|
+
for a host-built row action.
|
|
253
428
|
`useConversation` also exposes `readPositionByUserId`, `readerIdsFor(message)`,
|
|
254
429
|
`markRead()` and `setVisible(visible)`; call `setVisible(false)` while your own
|
|
255
430
|
surface hides the room so automatic acknowledgements wait until it is shown.
|
|
431
|
+
For the viewer's own messages it exposes `editingMessage`, `canEditMessages`,
|
|
432
|
+
`canDeleteMessages`, `startEditing(messageId)`, `cancelEditing()`,
|
|
433
|
+
`saveEdit(text)` and `deleteMessage(messageId)` (0.8.0).
|
|
256
434
|
|
|
257
435
|
Optimistic rows display `Sending…` until acknowledgement. The final server
|
|
258
436
|
timestamp is formatted in the viewer's local timezone. Custom message slots can
|
|
@@ -261,7 +439,7 @@ use `isConvoKitPendingMessage(message)` to present the same state.
|
|
|
261
439
|
## Appearance
|
|
262
440
|
|
|
263
441
|
Wrap any subtree with `ConvoKitThemeProvider` (tokens include `badge` for the
|
|
264
|
-
unread counter), set `density="compact"`, or use
|
|
442
|
+
unread counter and dot), set `density="compact"`, or use
|
|
265
443
|
the per-part `classNames` and `styles` maps. `unstyled` removes package classes
|
|
266
444
|
from the configurable parts for a fully host-owned presentation.
|
|
267
445
|
|