@aparte/core 0.12.1 → 0.13.1
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/dist/client/aparte-client.d.ts +25 -0
- package/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts +117 -0
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/chat/aparte-chat.d.ts +70 -15
- package/dist/components/chat/aparte-chat.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-action.d.ts +48 -7
- package/dist/components/composer/aparte-composer-action.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-add-attachment.d.ts +37 -4
- package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-attachments.d.ts +35 -3
- package/dist/components/composer/aparte-composer-attachments.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-cancel.d.ts +25 -2
- package/dist/components/composer/aparte-composer-cancel.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-input.d.ts +54 -5
- package/dist/components/composer/aparte-composer-input.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-send.d.ts +47 -4
- package/dist/components/composer/aparte-composer-send.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-toolbar.d.ts +15 -4
- package/dist/components/composer/aparte-composer-toolbar.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer.d.ts +139 -7
- package/dist/components/composer/aparte-composer.d.ts.map +1 -1
- package/dist/components/composer/index.d.ts +1 -1
- package/dist/components/composer/index.d.ts.map +1 -1
- package/dist/components/conversation-list/aparte-conversation-list.d.ts +50 -8
- package/dist/components/conversation-list/aparte-conversation-list.d.ts.map +1 -1
- package/dist/components/elicitation/aparte-elicitation.d.ts +45 -0
- package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/status/aparte-chat-status.d.ts +47 -5
- package/dist/components/status/aparte-chat-status.d.ts.map +1 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts +80 -8
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/config/aparte-config.d.ts +22 -0
- package/dist/config/aparte-config.d.ts.map +1 -1
- package/dist/config/icon-provider.d.ts +17 -15
- package/dist/config/icon-provider.d.ts.map +1 -1
- package/dist/custom-elements.json +7372 -5219
- package/dist/elicitation/approval-panel.d.ts.map +1 -1
- package/dist/elicitation/panel.d.ts +23 -2
- package/dist/elicitation/panel.d.ts.map +1 -1
- package/dist/generated/element-attributes.d.ts +21 -15
- package/dist/generated/element-attributes.d.ts.map +1 -1
- package/dist/glyphs-QXfbL3n_.js +81 -0
- package/dist/glyphs-QXfbL3n_.js.map +1 -0
- package/dist/icons/extended.d.ts +153 -0
- package/dist/icons/extended.d.ts.map +1 -0
- package/dist/icons/glyphs.d.ts +98 -0
- package/dist/icons/glyphs.d.ts.map +1 -0
- package/dist/icons/index.d.ts +24 -0
- package/dist/icons/index.d.ts.map +1 -0
- package/dist/icons.d.ts +14 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +157 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.css +5109 -2121
- package/dist/index.d.ts +37 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +184 -68
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +2 -2
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +1 -1
- package/dist/index.node.js.map +1 -1
- package/dist/{is-awaiting-reply-BWTDvQ__.js → is-awaiting-reply-CWp1ffHh.js} +347 -171
- package/dist/is-awaiting-reply-CWp1ffHh.js.map +1 -0
- package/dist/primitives/icon/aparte-icon.d.ts +49 -0
- package/dist/primitives/icon/aparte-icon.d.ts.map +1 -0
- package/dist/primitives/icon/index.d.ts +2 -0
- package/dist/primitives/icon/index.d.ts.map +1 -0
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/progress-spinner/aparte-progress-spinner.d.ts +20 -5
- package/dist/primitives/progress-spinner/aparte-progress-spinner.d.ts.map +1 -1
- package/dist/primitives/select/aparte-optgroup.d.ts +30 -7
- package/dist/primitives/select/aparte-optgroup.d.ts.map +1 -1
- package/dist/primitives/select/aparte-option.d.ts +29 -3
- package/dist/primitives/select/aparte-option.d.ts.map +1 -1
- package/dist/primitives/select/aparte-select.d.ts +36 -11
- package/dist/primitives/select/aparte-select.d.ts.map +1 -1
- package/dist/renderers/segment-renderers.d.ts +18 -0
- package/dist/renderers/segment-renderers.d.ts.map +1 -1
- package/dist/renderers/segments/artifact/card.d.ts.map +1 -1
- package/dist/renderers/segments/thinking.d.ts.map +1 -1
- package/dist/renderers/segments/tool-call.d.ts.map +1 -1
- package/dist/types/element-map.d.ts +2 -0
- package/dist/types/element-map.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/segments.d.ts +92 -7
- package/dist/types/segments.d.ts.map +1 -1
- package/dist/types/tools.d.ts +2 -0
- package/dist/types/tools.d.ts.map +1 -1
- package/package.json +7 -1
- package/dist/is-awaiting-reply-BWTDvQ__.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, q, r, t, u, v, w, z, B, C, D, E, G, H, I, J, M, N, R, S, T, U, V, X, Y, Z, _, a2, a3, a5 } from "./is-awaiting-reply-
|
|
1
|
+
import { $ as resolveConfig, a4 as subscribeConfigChange, a1 as runWithConfig, p as AparteElicitationAbortError, x as buildApprovalPanel, y as buildElicitationPanel, K as escapeAttr, F as cssEscape, a7 as mergeSegmentUpdate, L as escapeHtml, a8 as writeStreamedMarkdown, O as getSegmentRenderer, P as installDefaultRenderersOnce, s as AparteMessageRepository, a9 as stampSegmentActivity, aa as stampSegmentOnInsert, ab as stampSegmentOnUpdate, ac as renumberSegments, ad as openSegmentIds, ae as isTerminalStatus, af as adoptMessageSegments, Q as isAwaitingReply, W as populateBubbleFromMessage, a6 as uuid, a0 as revokeAttachmentUrls } from "./is-awaiting-reply-CWp1ffHh.js";
|
|
2
|
+
import { A, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, q, r, t, u, v, w, z, B, C, D, E, G, H, I, J, M, N, R, S, T, U, V, X, Y, Z, _, a2, a3, a5 } from "./is-awaiting-reply-CWp1ffHh.js";
|
|
3
3
|
class AparteOption extends HTMLElement {
|
|
4
4
|
static get observedAttributes() {
|
|
5
5
|
return ["value", "disabled", "selected", "data-status"];
|
|
@@ -132,6 +132,7 @@ class AparteOptgroup extends HTMLElement {
|
|
|
132
132
|
if (this.collapsible) {
|
|
133
133
|
const chevron = document.createElement("span");
|
|
134
134
|
chevron.className = "aparte-optgroup-chevron";
|
|
135
|
+
chevron.innerHTML = resolveConfig(this).getIcon("expand");
|
|
135
136
|
header.appendChild(chevron);
|
|
136
137
|
header.style.cursor = "pointer";
|
|
137
138
|
header.addEventListener("click", (e2) => {
|
|
@@ -204,8 +205,7 @@ class AparteSelect extends HTMLElement {
|
|
|
204
205
|
* document-level handlers and could not touch this one.
|
|
205
206
|
*
|
|
206
207
|
* This is verbatim the bug class `aparte-chat-viewport` documents having fixed
|
|
207
|
-
* for its own listeners
|
|
208
|
-
* listener on a persistent node.
|
|
208
|
+
* for its own listeners.
|
|
209
209
|
*/
|
|
210
210
|
_boundHandleOptionClick = this._handleOptionClick.bind(this);
|
|
211
211
|
_boundHandleDocumentClick = this._handleDocumentClick.bind(this);
|
|
@@ -289,7 +289,7 @@ class AparteSelect extends HTMLElement {
|
|
|
289
289
|
labelSpan.textContent = placeholder;
|
|
290
290
|
const chevronSpan = document.createElement("span");
|
|
291
291
|
chevronSpan.className = "aparte-select-chevron";
|
|
292
|
-
chevronSpan.innerHTML =
|
|
292
|
+
chevronSpan.innerHTML = resolveConfig(this).getIcon("expand");
|
|
293
293
|
trigger.append(labelSpan, chevronSpan);
|
|
294
294
|
const dropdown = document.createElement("div");
|
|
295
295
|
dropdown.className = "aparte-select-dropdown";
|
|
@@ -631,6 +631,32 @@ class AparteProgressSpinner extends HTMLElement {
|
|
|
631
631
|
if (!customElements.get("aparte-progress-spinner")) {
|
|
632
632
|
customElements.define("aparte-progress-spinner", AparteProgressSpinner);
|
|
633
633
|
}
|
|
634
|
+
class AparteIcon extends HTMLElement {
|
|
635
|
+
static get observedAttributes() {
|
|
636
|
+
return ["name"];
|
|
637
|
+
}
|
|
638
|
+
_unsubscribe = null;
|
|
639
|
+
connectedCallback() {
|
|
640
|
+
this._render();
|
|
641
|
+
this._unsubscribe = subscribeConfigChange(this, () => this._render());
|
|
642
|
+
}
|
|
643
|
+
disconnectedCallback() {
|
|
644
|
+
this._unsubscribe?.();
|
|
645
|
+
this._unsubscribe = null;
|
|
646
|
+
}
|
|
647
|
+
attributeChangedCallback() {
|
|
648
|
+
if (this.isConnected) this._render();
|
|
649
|
+
}
|
|
650
|
+
_render() {
|
|
651
|
+
const name = this.getAttribute("name");
|
|
652
|
+
const glyph = name ? resolveConfig(this).getIconProvider()[name]?.() : null;
|
|
653
|
+
this.innerHTML = glyph ?? "";
|
|
654
|
+
this.firstElementChild?.setAttribute("aria-hidden", "true");
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (!customElements.get("aparte-icon")) {
|
|
658
|
+
customElements.define("aparte-icon", AparteIcon);
|
|
659
|
+
}
|
|
634
660
|
class AparteElicitation extends HTMLElement {
|
|
635
661
|
_pending = null;
|
|
636
662
|
/**
|
|
@@ -705,11 +731,17 @@ class AparteElicitation extends HTMLElement {
|
|
|
705
731
|
return new Promise((resolve, reject) => {
|
|
706
732
|
let done = false;
|
|
707
733
|
const slot = {};
|
|
734
|
+
const focusedBefore = document.activeElement;
|
|
708
735
|
const close = () => {
|
|
709
736
|
if (done) return false;
|
|
710
737
|
done = true;
|
|
711
738
|
this._pending = null;
|
|
739
|
+
const active = document.activeElement;
|
|
740
|
+
const focusWasInPanel = active instanceof Node && composer.contains(active);
|
|
712
741
|
composer.hidePanel(slot.token);
|
|
742
|
+
if (focusWasInPanel && focusedBefore instanceof HTMLElement && focusedBefore.isConnected) {
|
|
743
|
+
focusedBefore.focus();
|
|
744
|
+
}
|
|
713
745
|
return true;
|
|
714
746
|
};
|
|
715
747
|
const settle = (result) => {
|
|
@@ -727,17 +759,15 @@ class AparteElicitation extends HTMLElement {
|
|
|
727
759
|
}
|
|
728
760
|
const cfg = resolveConfig(this);
|
|
729
761
|
if (request.kind === "approval") {
|
|
762
|
+
const approvalMode = () => panel2.isComplete() ? "submit" : "none";
|
|
730
763
|
const panel2 = runWithConfig(cfg, () => buildApprovalPanel(request.message, request.options ?? [], () => {
|
|
731
|
-
composer.setPanelSubmitEnabled(panel2.isComplete(),
|
|
764
|
+
composer.setPanelSubmitEnabled(panel2.isComplete(), approvalMode());
|
|
732
765
|
}));
|
|
733
766
|
panel2.onSettle((answer) => settle({ action: "accept", content: answer }));
|
|
734
767
|
this._pending = { abort: () => fail(), composer, relabel: () => panel2.relabel() };
|
|
735
768
|
slot.token = composer.showPanel(panel2.el, {
|
|
736
769
|
submitEnabled: panel2.isComplete(),
|
|
737
|
-
mode:
|
|
738
|
-
// The composer's button carries the INSTRUCTION, which is written
|
|
739
|
-
// text — exactly the act that button already means. The options
|
|
740
|
-
// never route through it: a decision is its own click.
|
|
770
|
+
mode: approvalMode(),
|
|
741
771
|
onSubmit: () => {
|
|
742
772
|
if (panel2.isComplete()) settle({ action: "accept", content: panel2.getContent() });
|
|
743
773
|
},
|
|
@@ -752,10 +782,11 @@ class AparteElicitation extends HTMLElement {
|
|
|
752
782
|
}));
|
|
753
783
|
const skip = document.createElement("button");
|
|
754
784
|
skip.type = "button";
|
|
755
|
-
skip.className = "aparte-elic-skip";
|
|
785
|
+
skip.className = "aparte-btn aparte-elic-skip";
|
|
756
786
|
skip.textContent = cfg.t("elicitationSkip");
|
|
757
787
|
skip.addEventListener("click", () => settle({ action: "decline" }));
|
|
758
788
|
panel.dismiss.appendChild(skip);
|
|
789
|
+
panel.onSettle((content) => settle({ action: "accept", content }));
|
|
759
790
|
this._pending = {
|
|
760
791
|
abort: () => fail(),
|
|
761
792
|
composer,
|
|
@@ -968,6 +999,14 @@ function warnMissingRenderer(type) {
|
|
|
968
999
|
_warnedNoRenderer = true;
|
|
969
1000
|
console.warn(`[aparte] No renderer for segment "${type}". Register one with registerSegmentRenderer({ type: '${type}', render }) from @aparte/core — see https://apartejs.dev/guides/customization/#custom-segment-types`);
|
|
970
1001
|
}
|
|
1002
|
+
function unrenderedSegment(segment) {
|
|
1003
|
+
const fallback = typeof segment.fallback === "string" && segment.fallback.trim() ? segment.fallback : null;
|
|
1004
|
+
if (!fallback) warnMissingRenderer(segment.type);
|
|
1005
|
+
const el = document.createElement("div");
|
|
1006
|
+
el.className = fallback ? "aparte-segment aparte-segment-fallback" : "aparte-segment aparte-segment-unknown";
|
|
1007
|
+
el.textContent = fallback ?? `[Unknown segment type: ${segment.type}]`;
|
|
1008
|
+
return el;
|
|
1009
|
+
}
|
|
971
1010
|
function resolveSegmentRenderer(type, config) {
|
|
972
1011
|
const renderer = getSegmentRenderer(type, config);
|
|
973
1012
|
if (renderer) return renderer;
|
|
@@ -980,7 +1019,6 @@ function segmentRenderResultToElement(result) {
|
|
|
980
1019
|
wrapper.innerHTML = result;
|
|
981
1020
|
return wrapper.firstElementChild;
|
|
982
1021
|
}
|
|
983
|
-
const INFO_ICON_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>';
|
|
984
1022
|
class AparteChatBubble extends HTMLElement {
|
|
985
1023
|
_contentEl = null;
|
|
986
1024
|
_segmentsEl = null;
|
|
@@ -1106,6 +1144,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1106
1144
|
break;
|
|
1107
1145
|
case "content":
|
|
1108
1146
|
this._content = newValue || "";
|
|
1147
|
+
this._resetMarkdownStream();
|
|
1109
1148
|
this._updateContent();
|
|
1110
1149
|
break;
|
|
1111
1150
|
case "timestamp":
|
|
@@ -1131,8 +1170,20 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1131
1170
|
setContent(content) {
|
|
1132
1171
|
this._content = content;
|
|
1133
1172
|
this.setAttribute("content", content);
|
|
1173
|
+
this._resetMarkdownStream();
|
|
1134
1174
|
this._updateContent();
|
|
1135
1175
|
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Drop the incremental Markdown parser's state.
|
|
1178
|
+
*
|
|
1179
|
+
* Only needed where `_content` is REPLACED rather than grown. `appendToken` grows it, so
|
|
1180
|
+
* the parser's cursor stays valid there — which is the whole point of the seam.
|
|
1181
|
+
*/
|
|
1182
|
+
_resetMarkdownStream() {
|
|
1183
|
+
const host = this;
|
|
1184
|
+
if (host._aparteSmd) host._aparteSmd.renderer.end();
|
|
1185
|
+
host._aparteSmd = void 0;
|
|
1186
|
+
}
|
|
1136
1187
|
/** Get current content */
|
|
1137
1188
|
getContent() {
|
|
1138
1189
|
return this._content;
|
|
@@ -1267,11 +1318,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1267
1318
|
runWithConfig(this._cfg, () => renderer.setup?.(el, segment));
|
|
1268
1319
|
}
|
|
1269
1320
|
} else {
|
|
1270
|
-
|
|
1271
|
-
const fallback = document.createElement("div");
|
|
1272
|
-
fallback.className = "aparte-segment aparte-segment-unknown";
|
|
1273
|
-
fallback.textContent = `[Unknown segment type: ${segment.type}]`;
|
|
1274
|
-
this._segmentsEl.appendChild(fallback);
|
|
1321
|
+
this._segmentsEl.appendChild(unrenderedSegment(segment));
|
|
1275
1322
|
}
|
|
1276
1323
|
if (this._contentEl) this._contentEl.style.display = "none";
|
|
1277
1324
|
this._reflectError();
|
|
@@ -1350,9 +1397,16 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1350
1397
|
</div>
|
|
1351
1398
|
<div class="aparte-footer">
|
|
1352
1399
|
<div class="aparte-branch-picker" hidden>
|
|
1353
|
-
<button class="aparte-branch-prev" aria-label="${escapeAttr(this._cfg.getLocale().previousResponse ?? "Previous response")}">‹</button>
|
|
1400
|
+
<button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-prev" aria-label="${escapeAttr(this._cfg.getLocale().previousResponse ?? "Previous response")}">‹</button>
|
|
1354
1401
|
<span class="aparte-branch-label">1 / 1</span>
|
|
1355
|
-
|
|
1402
|
+
<!-- The move has to be ANNOUNCED. Pressing the arrows deliberately does not
|
|
1403
|
+
take focus, so without a live region a screen-reader user gets the new
|
|
1404
|
+
branch and no indication anything changed. The visible label cannot be
|
|
1405
|
+
the region itself: a custom sibling-nav renderer may replace it with
|
|
1406
|
+
dots, which reads as nothing. No new locale key — the position is
|
|
1407
|
+
digits, and the buttons beside it already carry translated labels. -->
|
|
1408
|
+
<span class="aparte-sr-only aparte-branch-status" aria-live="polite"></span>
|
|
1409
|
+
<button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-next" aria-label="${escapeAttr(this._cfg.getLocale().nextResponse ?? "Next response")}">›</button>
|
|
1356
1410
|
</div>
|
|
1357
1411
|
<div class="aparte-action-bar" role="toolbar" aria-label="${escapeAttr(this._cfg.getLocale().messageActions ?? "Message actions")}"></div>
|
|
1358
1412
|
</div>
|
|
@@ -1453,7 +1507,10 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1453
1507
|
return;
|
|
1454
1508
|
}
|
|
1455
1509
|
this._contentEl.style.display = "";
|
|
1456
|
-
|
|
1510
|
+
runWithConfig(
|
|
1511
|
+
this._cfg,
|
|
1512
|
+
() => writeStreamedMarkdown(this, this._contentEl, this._content, this._streaming)
|
|
1513
|
+
);
|
|
1457
1514
|
this._updateWaiting();
|
|
1458
1515
|
if (!this._streaming) this._highlightContentCode();
|
|
1459
1516
|
}
|
|
@@ -1496,11 +1553,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1496
1553
|
runWithConfig(this._cfg, () => renderer.setup?.(el, segment));
|
|
1497
1554
|
}
|
|
1498
1555
|
} else {
|
|
1499
|
-
|
|
1500
|
-
const fallback = document.createElement("div");
|
|
1501
|
-
fallback.className = "aparte-segment aparte-segment-unknown";
|
|
1502
|
-
fallback.textContent = `[Unknown segment type: ${segment.type}]`;
|
|
1503
|
-
this._segmentsEl.appendChild(fallback);
|
|
1556
|
+
this._segmentsEl.appendChild(unrenderedSegment(segment));
|
|
1504
1557
|
}
|
|
1505
1558
|
}
|
|
1506
1559
|
if (this._contentEl) {
|
|
@@ -1649,6 +1702,11 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1649
1702
|
label.textContent = `${this._siblingIndex + 1} / ${this._siblingCount}`;
|
|
1650
1703
|
}
|
|
1651
1704
|
}
|
|
1705
|
+
const status = this._branchPickerEl.querySelector(".aparte-branch-status");
|
|
1706
|
+
if (status) {
|
|
1707
|
+
const position = `${this._siblingIndex + 1} / ${this._siblingCount}`;
|
|
1708
|
+
if (status.textContent !== position) status.textContent = position;
|
|
1709
|
+
}
|
|
1652
1710
|
const prevBtn = this._branchPickerEl.querySelector(".aparte-branch-prev");
|
|
1653
1711
|
const nextBtn = this._branchPickerEl.querySelector(".aparte-branch-next");
|
|
1654
1712
|
if (prevBtn) prevBtn.disabled = this._siblingIndex === 0;
|
|
@@ -1715,7 +1773,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1715
1773
|
const roles = a4.bubble?.roles ?? ["user", "assistant"];
|
|
1716
1774
|
if (!roles.includes(this._role)) continue;
|
|
1717
1775
|
const btn = document.createElement("button");
|
|
1718
|
-
btn.className = "aparte-action-btn aparte-action-custom";
|
|
1776
|
+
btn.className = "aparte-btn aparte-btn--icon aparte-action-btn aparte-action-custom";
|
|
1719
1777
|
btn.dataset["action"] = `custom:${a4.id}`;
|
|
1720
1778
|
btn.setAttribute("aria-label", a4.label);
|
|
1721
1779
|
btn.setAttribute("title", a4.label);
|
|
@@ -1729,28 +1787,28 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1729
1787
|
switch (action) {
|
|
1730
1788
|
case "copy": {
|
|
1731
1789
|
const l2 = locale.copy ?? "Copy";
|
|
1732
|
-
return `<button class="aparte-action-btn aparte-action-copy" data-action="copy" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.copy()}</button>`;
|
|
1790
|
+
return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-copy" data-action="copy" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.copy()}</button>`;
|
|
1733
1791
|
}
|
|
1734
1792
|
case "edit": {
|
|
1735
1793
|
const l2 = locale.edit ?? "Edit message";
|
|
1736
|
-
return `<button class="aparte-action-btn aparte-action-edit" data-action="edit" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.edit()}</button>`;
|
|
1794
|
+
return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-edit" data-action="edit" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.edit()}</button>`;
|
|
1737
1795
|
}
|
|
1738
1796
|
case "retry": {
|
|
1739
1797
|
const l2 = locale.retry ?? "Retry";
|
|
1740
|
-
return `<button class="aparte-action-btn aparte-action-retry" data-action="retry" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.retry()}</button>`;
|
|
1798
|
+
return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-retry" data-action="retry" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.retry()}</button>`;
|
|
1741
1799
|
}
|
|
1742
1800
|
case "thumbUp": {
|
|
1743
1801
|
const l2 = locale.feedbackPositive ?? "Good response";
|
|
1744
|
-
return `<button class="aparte-action-btn aparte-action-feedback-pos" data-action="feedback-positive" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.thumbUp()}</button>`;
|
|
1802
|
+
return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-feedback-pos" data-action="feedback-positive" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.thumbUp()}</button>`;
|
|
1745
1803
|
}
|
|
1746
1804
|
case "thumbDown": {
|
|
1747
1805
|
const l2 = locale.feedbackNegative ?? "Bad response";
|
|
1748
|
-
return `<button class="aparte-action-btn aparte-action-feedback-neg" data-action="feedback-negative" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.thumbDown()}</button>`;
|
|
1806
|
+
return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-feedback-neg" data-action="feedback-negative" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.thumbDown()}</button>`;
|
|
1749
1807
|
}
|
|
1750
1808
|
case "info": {
|
|
1751
1809
|
if (!this._usage) return "";
|
|
1752
1810
|
const l2 = locale.messageInfo ?? "Details";
|
|
1753
|
-
return `<button class="aparte-action-btn aparte-action-info" data-action="info" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${
|
|
1811
|
+
return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-info" data-action="info" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${this._cfg.getIcon("info")}</button>`;
|
|
1754
1812
|
}
|
|
1755
1813
|
default:
|
|
1756
1814
|
return "";
|
|
@@ -1762,7 +1820,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1762
1820
|
const locale = this._cfg.getLocale();
|
|
1763
1821
|
const saveLabel = locale.editConfirm ?? "Save";
|
|
1764
1822
|
const cancelLabel = locale.editCancel ?? "Cancel";
|
|
1765
|
-
this._actionBarEl.innerHTML = `<button class="aparte-action-btn aparte-action-edit-save" data-action="edit-save" aria-label="${escapeAttr(saveLabel)}" title="${escapeAttr(saveLabel)}">${this._cfg.getIcon("check")}</button><button class="aparte-action-btn aparte-action-edit-cancel" data-action="edit-cancel" aria-label="${escapeAttr(cancelLabel)}" title="${escapeAttr(cancelLabel)}">${this._cfg.getIcon("close")}</button>`;
|
|
1823
|
+
this._actionBarEl.innerHTML = `<button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--success aparte-action-btn aparte-action-edit-save" data-action="edit-save" aria-label="${escapeAttr(saveLabel)}" title="${escapeAttr(saveLabel)}">${this._cfg.getIcon("check")}</button><button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--danger aparte-action-btn aparte-action-edit-cancel" data-action="edit-cancel" aria-label="${escapeAttr(cancelLabel)}" title="${escapeAttr(cancelLabel)}">${this._cfg.getIcon("close")}</button>`;
|
|
1766
1824
|
this._actionBarEl.querySelectorAll(".aparte-action-btn").forEach((btn) => {
|
|
1767
1825
|
btn.addEventListener("click", (e2) => this._handleActionClick(e2));
|
|
1768
1826
|
});
|
|
@@ -2100,6 +2158,8 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2100
2158
|
_layoutTransitionMs = 0;
|
|
2101
2159
|
_repo = new AparteMessageRepository();
|
|
2102
2160
|
_isAutoScrollEnabled = true;
|
|
2161
|
+
/** The last scroll position we saw, so growth can be told from a gesture. */
|
|
2162
|
+
_lastScrollTop = 0;
|
|
2103
2163
|
_scrollThreshold = 50;
|
|
2104
2164
|
/** When true, the next _autoScroll() call uses smooth instead of instant, then resets. */
|
|
2105
2165
|
_smoothScrollOnce = false;
|
|
@@ -2572,6 +2632,11 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2572
2632
|
getMessage(messageId) {
|
|
2573
2633
|
return this._repo.getMessageById(messageId);
|
|
2574
2634
|
}
|
|
2635
|
+
/**
|
|
2636
|
+
* The messages on the currently ACTIVE path, root → head — not the whole tree.
|
|
2637
|
+
* A message that was retried contributes only the branch currently selected;
|
|
2638
|
+
* `exportTree()` is what returns every sibling.
|
|
2639
|
+
*/
|
|
2575
2640
|
getMessages() {
|
|
2576
2641
|
return this._repo.getMessages();
|
|
2577
2642
|
}
|
|
@@ -2836,11 +2901,11 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2836
2901
|
this.appendChild(container);
|
|
2837
2902
|
this._container = container;
|
|
2838
2903
|
this._scrollBtn = document.createElement("button");
|
|
2839
|
-
this._scrollBtn.className = "aparte-scroll-btn aparte-scroll-btn--hidden";
|
|
2904
|
+
this._scrollBtn.className = "aparte-btn aparte-btn--surface aparte-btn--circle aparte-btn--lg aparte-scroll-btn aparte-scroll-btn--hidden";
|
|
2840
2905
|
this._scrollBtn.setAttribute("type", "button");
|
|
2841
2906
|
this._scrollBtn.setAttribute("aria-label", "Scroll to bottom");
|
|
2842
2907
|
const scrollIcon = resolveConfig(this).getIcon("scrollDown");
|
|
2843
|
-
this._scrollBtn.innerHTML = scrollIcon
|
|
2908
|
+
this._scrollBtn.innerHTML = scrollIcon;
|
|
2844
2909
|
this.appendChild(this._scrollBtn);
|
|
2845
2910
|
} else {
|
|
2846
2911
|
this._container = this.querySelector(".aparte-viewport-container");
|
|
@@ -2869,11 +2934,11 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2869
2934
|
}
|
|
2870
2935
|
this.classList.add("aparte-viewport--framework");
|
|
2871
2936
|
const scrollBtn = document.createElement("button");
|
|
2872
|
-
scrollBtn.className = "aparte-scroll-btn aparte-scroll-btn--hidden";
|
|
2937
|
+
scrollBtn.className = "aparte-btn aparte-btn--surface aparte-btn--circle aparte-btn--lg aparte-scroll-btn aparte-scroll-btn--hidden";
|
|
2873
2938
|
scrollBtn.setAttribute("type", "button");
|
|
2874
2939
|
scrollBtn.setAttribute("aria-label", "Scroll to bottom");
|
|
2875
2940
|
const scrollIcon = resolveConfig(this).getIcon("scrollDown");
|
|
2876
|
-
scrollBtn.innerHTML = scrollIcon
|
|
2941
|
+
scrollBtn.innerHTML = scrollIcon;
|
|
2877
2942
|
this.appendChild(scrollBtn);
|
|
2878
2943
|
this._scrollBtn = scrollBtn;
|
|
2879
2944
|
}
|
|
@@ -2932,6 +2997,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2932
2997
|
this._scrollToBottom();
|
|
2933
2998
|
}
|
|
2934
2999
|
this._recalculateSpacer();
|
|
3000
|
+
this._updateScrollButton();
|
|
2935
3001
|
});
|
|
2936
3002
|
const wrapper = this.querySelector(".aparte-messages-wrapper");
|
|
2937
3003
|
if (this._container) {
|
|
@@ -2964,7 +3030,14 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2964
3030
|
}
|
|
2965
3031
|
_handleScroll() {
|
|
2966
3032
|
if (!this._container) return;
|
|
2967
|
-
|
|
3033
|
+
const top = this._container.scrollTop;
|
|
3034
|
+
const readerWentUp = top < this._lastScrollTop - 1;
|
|
3035
|
+
this._lastScrollTop = top;
|
|
3036
|
+
if (this._isAtBottom()) {
|
|
3037
|
+
this._isAutoScrollEnabled = true;
|
|
3038
|
+
} else if (readerWentUp) {
|
|
3039
|
+
this._isAutoScrollEnabled = false;
|
|
3040
|
+
}
|
|
2968
3041
|
this._updateScrollButton();
|
|
2969
3042
|
}
|
|
2970
3043
|
_scrollToBottom() {
|
|
@@ -3293,18 +3366,31 @@ class AparteComposer extends HTMLElement {
|
|
|
3293
3366
|
submitEnabled: this._panelSubmitEnabled
|
|
3294
3367
|
};
|
|
3295
3368
|
}
|
|
3369
|
+
/**
|
|
3370
|
+
* Set the composer's value — both what a send will submit and what the editor
|
|
3371
|
+
* shows. `<aparte-composer-input>` writes through any value it does not already
|
|
3372
|
+
* hold, so this prefills the visible field (a template button, a restored draft)
|
|
3373
|
+
* as readily as it stages text for an immediate `submit()`.
|
|
3374
|
+
*/
|
|
3296
3375
|
setValue(value) {
|
|
3297
3376
|
this._value = value;
|
|
3298
3377
|
this._emit("value-change", { value });
|
|
3299
3378
|
}
|
|
3379
|
+
/** Append files to the pending attachments and notify. Does not de-duplicate. */
|
|
3300
3380
|
addAttachments(files) {
|
|
3301
3381
|
this._attachments = [...this._attachments, ...Array.from(files)];
|
|
3302
3382
|
this._emit("attachments-change", { attachments: this._attachments });
|
|
3303
3383
|
}
|
|
3384
|
+
/**
|
|
3385
|
+
* Drop one pending attachment and notify. Matched by IDENTITY — pass the same
|
|
3386
|
+
* `File` object the composer handed you, not an equal one; two picks of the same
|
|
3387
|
+
* file on disk are two distinct objects.
|
|
3388
|
+
*/
|
|
3304
3389
|
removeAttachment(file) {
|
|
3305
3390
|
this._attachments = this._attachments.filter((f2) => f2 !== file);
|
|
3306
3391
|
this._emit("attachments-change", { attachments: this._attachments });
|
|
3307
3392
|
}
|
|
3393
|
+
/** Drop every pending attachment and notify. */
|
|
3308
3394
|
clearAttachments() {
|
|
3309
3395
|
this._attachments = [];
|
|
3310
3396
|
this._emit("attachments-change", { attachments: [] });
|
|
@@ -3323,6 +3409,15 @@ class AparteComposer extends HTMLElement {
|
|
|
3323
3409
|
* are the user's state and not an action to offer — hiding them would look like
|
|
3324
3410
|
* losing them; and the toolbar, because switching model still does something.
|
|
3325
3411
|
*
|
|
3412
|
+
* The send button is the part the PANEL decides, through `mode`. `'submit'` and
|
|
3413
|
+
* `'advance'` keep it; `'none'` says this panel has no act for it and it is not
|
|
3414
|
+
* drawn. That third value is what a panel whose options settle on the first click
|
|
3415
|
+
* needs — a single-choice question, an approval — and until it existed such a
|
|
3416
|
+
* panel left a permanently disabled button beside options that never routed
|
|
3417
|
+
* through it. Flip between them at any time with {@link setPanelSubmitEnabled},
|
|
3418
|
+
* which is how a panel that grows an act (an "Other…" field, a written
|
|
3419
|
+
* instruction) turns the button back on.
|
|
3420
|
+
*
|
|
3326
3421
|
* Declared with an attribute + CSS rather than the inline `style.display` this
|
|
3327
3422
|
* used to set on a child: an attribute is themeable, is visible to a consumer's
|
|
3328
3423
|
* own rules, and does not clobber a `display` the consumer had set (the restore
|
|
@@ -3346,6 +3441,7 @@ class AparteComposer extends HTMLElement {
|
|
|
3346
3441
|
this._panelActive = true;
|
|
3347
3442
|
this._panelSubmitEnabled = options?.submitEnabled ?? false;
|
|
3348
3443
|
this._panelMode = options?.mode ?? "submit";
|
|
3444
|
+
this._reflectPanelMode();
|
|
3349
3445
|
this._panelOnSubmit = options?.onSubmit ?? null;
|
|
3350
3446
|
this._panelOnEvict = options?.onEvict ?? null;
|
|
3351
3447
|
const token = Symbol("aparte-composer-panel");
|
|
@@ -3405,6 +3501,7 @@ class AparteComposer extends HTMLElement {
|
|
|
3405
3501
|
this._panelActive = false;
|
|
3406
3502
|
this._panelSubmitEnabled = false;
|
|
3407
3503
|
this._panelMode = "submit";
|
|
3504
|
+
this._reflectPanelMode();
|
|
3408
3505
|
this._panelOnSubmit = null;
|
|
3409
3506
|
this._panelOnEvict = null;
|
|
3410
3507
|
this._panelToken = null;
|
|
@@ -3423,8 +3520,21 @@ class AparteComposer extends HTMLElement {
|
|
|
3423
3520
|
if (!this._panelActive) return;
|
|
3424
3521
|
this._panelSubmitEnabled = enabled;
|
|
3425
3522
|
if (mode) this._panelMode = mode;
|
|
3523
|
+
this._reflectPanelMode();
|
|
3426
3524
|
this._emit("panel-change", { active: true, submitEnabled: enabled, mode: this._panelMode });
|
|
3427
3525
|
}
|
|
3526
|
+
/**
|
|
3527
|
+
* Publish the panel mode as an attribute, so CSS can act on it.
|
|
3528
|
+
*
|
|
3529
|
+
* The same reasoning `data-panel-active` is set for and the same one that took
|
|
3530
|
+
* the old `style.display` off a child: an attribute is themeable, is visible to
|
|
3531
|
+
* a consumer's own rules, and does not clobber what the consumer set. It is what
|
|
3532
|
+
* lets `'none'` remove the send button without this component reaching into it.
|
|
3533
|
+
*/
|
|
3534
|
+
_reflectPanelMode() {
|
|
3535
|
+
if (this._panelActive) this.setAttribute("data-panel-mode", this._panelMode);
|
|
3536
|
+
else this.removeAttribute("data-panel-mode");
|
|
3537
|
+
}
|
|
3428
3538
|
get panelActive() {
|
|
3429
3539
|
return this._panelActive;
|
|
3430
3540
|
}
|
|
@@ -3443,7 +3553,7 @@ class AparteComposer extends HTMLElement {
|
|
|
3443
3553
|
/** Submit the current value. Called by aparte-composer-send or programmatically. */
|
|
3444
3554
|
submit() {
|
|
3445
3555
|
if (this._panelActive) {
|
|
3446
|
-
if (this._panelSubmitEnabled) this._panelOnSubmit?.();
|
|
3556
|
+
if (this._panelMode !== "none" && this._panelSubmitEnabled) this._panelOnSubmit?.();
|
|
3447
3557
|
return;
|
|
3448
3558
|
}
|
|
3449
3559
|
if (this._streaming) {
|
|
@@ -3596,6 +3706,10 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3596
3706
|
if (name === "disabled") this._updateDisabled(value !== null);
|
|
3597
3707
|
}
|
|
3598
3708
|
// ── Public API ──────────────────────────────────────────────────────────
|
|
3709
|
+
/**
|
|
3710
|
+
* The editor's text, with `<br>` serialized back to newlines — `textContent`
|
|
3711
|
+
* alone would collapse a multi-line draft onto a single line.
|
|
3712
|
+
*/
|
|
3599
3713
|
getValue() {
|
|
3600
3714
|
if (!this._editor) return "";
|
|
3601
3715
|
let out = "";
|
|
@@ -3609,6 +3723,7 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3609
3723
|
walk(this._editor);
|
|
3610
3724
|
return out.trim();
|
|
3611
3725
|
}
|
|
3726
|
+
/** Replace the editor's content and mirror the value onto the parent composer. */
|
|
3612
3727
|
setValue(value) {
|
|
3613
3728
|
if (!this._editor) return;
|
|
3614
3729
|
this._editor.textContent = value;
|
|
@@ -3616,6 +3731,7 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3616
3731
|
this._adjustHeight();
|
|
3617
3732
|
this._getRoot()?.setValue(value);
|
|
3618
3733
|
}
|
|
3734
|
+
/** Empty the editor and mirror the empty value onto the parent composer. */
|
|
3619
3735
|
clear() {
|
|
3620
3736
|
if (!this._editor) return;
|
|
3621
3737
|
this._editor.innerHTML = "";
|
|
@@ -3623,9 +3739,11 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3623
3739
|
this._adjustHeight();
|
|
3624
3740
|
this._getRoot()?.setValue("");
|
|
3625
3741
|
}
|
|
3742
|
+
/** Focus the inner contenteditable rather than the host element. */
|
|
3626
3743
|
focus() {
|
|
3627
3744
|
this._editor?.focus();
|
|
3628
3745
|
}
|
|
3746
|
+
/** Blur the inner contenteditable rather than the host element. */
|
|
3629
3747
|
blur() {
|
|
3630
3748
|
this._editor?.blur();
|
|
3631
3749
|
}
|
|
@@ -3678,7 +3796,9 @@ class AparteComposerInput extends HTMLElement {
|
|
|
3678
3796
|
);
|
|
3679
3797
|
this._unsubscribes.push(
|
|
3680
3798
|
root._on("value-change", ({ value }) => {
|
|
3681
|
-
if (
|
|
3799
|
+
if (this.getValue() === value.trim()) return;
|
|
3800
|
+
if (value === "") this.clear();
|
|
3801
|
+
else this.setValue(value);
|
|
3682
3802
|
})
|
|
3683
3803
|
);
|
|
3684
3804
|
this._unsubscribes.push(
|
|
@@ -3814,7 +3934,7 @@ class AparteComposerSend extends HTMLElement {
|
|
|
3814
3934
|
const root = this._getRoot();
|
|
3815
3935
|
const disabled = !root || root.disabled || root.value.trim() === "";
|
|
3816
3936
|
this.innerHTML = `<button
|
|
3817
|
-
class="aparte-cs-button aparte-send-button"
|
|
3937
|
+
class="aparte-btn aparte-btn--primary aparte-btn--solid aparte-btn--icon aparte-cs-button aparte-send-button"
|
|
3818
3938
|
aria-label="${escapeAttr(label)}"
|
|
3819
3939
|
title="${escapeAttr(label)}"
|
|
3820
3940
|
${disabled ? "disabled" : ""}
|
|
@@ -3884,6 +4004,10 @@ class AparteComposerSend extends HTMLElement {
|
|
|
3884
4004
|
const panel = this._panel;
|
|
3885
4005
|
if (!this._button || !panel?.active) return;
|
|
3886
4006
|
const cfg = resolveConfig(this);
|
|
4007
|
+
if (panel.mode === "none") {
|
|
4008
|
+
this._button.disabled = true;
|
|
4009
|
+
return;
|
|
4010
|
+
}
|
|
3887
4011
|
const advancing = panel.mode === "advance";
|
|
3888
4012
|
this._button.disabled = !panel.submitEnabled;
|
|
3889
4013
|
this._button.innerHTML = advancing ? cfg.getIcon("nextBranch") : this._getSubmitIcon();
|
|
@@ -3962,7 +4086,7 @@ class AparteComposerCancel extends HTMLElement {
|
|
|
3962
4086
|
const label = resolveConfig(this).t("stopButton") || "Stop";
|
|
3963
4087
|
const icon = this._getStopIcon();
|
|
3964
4088
|
this.innerHTML = `<button
|
|
3965
|
-
class="aparte-cc-button"
|
|
4089
|
+
class="aparte-btn aparte-btn--icon aparte-cc-button"
|
|
3966
4090
|
aria-label="${escapeAttr(label)}"
|
|
3967
4091
|
title="${escapeAttr(label)}"
|
|
3968
4092
|
hidden
|
|
@@ -4005,7 +4129,6 @@ class AparteComposerCancel extends HTMLElement {
|
|
|
4005
4129
|
if (!customElements.get("aparte-composer-cancel")) {
|
|
4006
4130
|
customElements.define("aparte-composer-cancel", AparteComposerCancel);
|
|
4007
4131
|
}
|
|
4008
|
-
const REMOVE_ICON = '<svg width="10" height="10" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><path d="M9.5 3.205 8.795 2.5 6 5.295 3.205 2.5 2.5 3.205 5.295 6 2.5 8.795l.705.705L6 6.705l2.795 2.795.705-.705L6.705 6z"/></svg>';
|
|
4009
4132
|
class AparteComposerAttachments extends HTMLElement {
|
|
4010
4133
|
_unsubscribes = [];
|
|
4011
4134
|
/** Object URLs minted for image previews — revoked on re-render/disconnect. */
|
|
@@ -4041,13 +4164,13 @@ class AparteComposerAttachments extends HTMLElement {
|
|
|
4041
4164
|
this._revokeUrls();
|
|
4042
4165
|
this.innerHTML = files.map((file) => {
|
|
4043
4166
|
const name = this._escape(file.name);
|
|
4044
|
-
const remove = `<button class="aparte-thumb__remove" type="button" aria-label="Remove ${name}">${
|
|
4167
|
+
const remove = `<button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-thumb__remove" type="button" aria-label="Remove ${name}">${resolveConfig(this).getIcon("close")}</button>`;
|
|
4045
4168
|
if (file.type.startsWith("image/")) {
|
|
4046
4169
|
const url = URL.createObjectURL(file);
|
|
4047
4170
|
this._objectUrls.push(url);
|
|
4048
|
-
return `<div class="aparte-thumb aparte-thumb--image" title="${escapeAttr(name)}"><img class="aparte-thumb__img" src="${escapeAttr(url)}" alt="${escapeAttr(name)}" /><span class="aparte-thumb__name">${name}</span>${remove}</div>`;
|
|
4171
|
+
return `<div class="aparte-thumbnail aparte-thumb aparte-thumb--image" title="${escapeAttr(name)}"><img class="aparte-thumb__img" src="${escapeAttr(url)}" alt="${escapeAttr(name)}" /><span class="aparte-thumb__name">${name}</span>${remove}</div>`;
|
|
4049
4172
|
}
|
|
4050
|
-
return `<div class="aparte-thumb aparte-thumb--file" title="${escapeAttr(name)}"><span class="aparte-thumb__ext">${this._escape(this._ext(file.name))}</span><span class="aparte-thumb__name">${name}</span>${remove}</div>`;
|
|
4173
|
+
return `<div class="aparte-thumbnail aparte-thumb aparte-thumb--file" title="${escapeAttr(name)}"><span class="aparte-thumb__ext">${this._escape(this._ext(file.name))}</span><span class="aparte-thumb__name">${name}</span>${remove}</div>`;
|
|
4051
4174
|
}).join("");
|
|
4052
4175
|
this.querySelectorAll(".aparte-thumb__remove").forEach((btn, i2) => {
|
|
4053
4176
|
btn.addEventListener("click", (e2) => {
|
|
@@ -4136,15 +4259,15 @@ class AparteComposerAddAttachment extends HTMLElement {
|
|
|
4136
4259
|
const label = cfg.t("actionUpload") || "Attach file";
|
|
4137
4260
|
this._button.setAttribute("aria-label", label);
|
|
4138
4261
|
this._button.setAttribute("title", label);
|
|
4139
|
-
this._button.innerHTML = cfg.getIcon("paperclip")
|
|
4262
|
+
this._button.innerHTML = cfg.getIcon("paperclip");
|
|
4140
4263
|
}
|
|
4141
4264
|
_render() {
|
|
4142
4265
|
if (this.querySelector(".aparte-caa-button")) return;
|
|
4143
4266
|
const label = resolveConfig(this).t("actionUpload") || "Attach file";
|
|
4144
|
-
const icon = resolveConfig(this).getIcon("paperclip")
|
|
4267
|
+
const icon = resolveConfig(this).getIcon("paperclip");
|
|
4145
4268
|
const disabled = this.hasAttribute("disabled") || this._getRoot()?.disabled || false;
|
|
4146
4269
|
this.innerHTML = `<button
|
|
4147
|
-
class="aparte-caa-button aparte-action-button"
|
|
4270
|
+
class="aparte-btn aparte-btn--icon aparte-caa-button aparte-action-button"
|
|
4148
4271
|
aria-label="${escapeAttr(label)}"
|
|
4149
4272
|
title="${escapeAttr(label)}"
|
|
4150
4273
|
type="button"
|
|
@@ -4213,9 +4336,6 @@ class AparteComposerAddAttachment extends HTMLElement {
|
|
|
4213
4336
|
root.removeEventListener("drop", onDrop);
|
|
4214
4337
|
};
|
|
4215
4338
|
}
|
|
4216
|
-
_defaultIcon() {
|
|
4217
|
-
return `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>`;
|
|
4218
|
-
}
|
|
4219
4339
|
}
|
|
4220
4340
|
if (!customElements.get("aparte-composer-add-attachment")) {
|
|
4221
4341
|
customElements.define("aparte-composer-add-attachment", AparteComposerAddAttachment);
|
|
@@ -4263,7 +4383,7 @@ class AparteComposerAction extends HTMLElement {
|
|
|
4263
4383
|
const icon = this._resolveIcon(this.getAttribute("icon") ?? "");
|
|
4264
4384
|
const disabled = this.hasAttribute("disabled") || this._getRoot()?.disabled || false;
|
|
4265
4385
|
this.innerHTML = `<button
|
|
4266
|
-
class="aparte-cact-button aparte-action-button"
|
|
4386
|
+
class="aparte-btn aparte-btn--icon aparte-cact-button aparte-action-button"
|
|
4267
4387
|
aria-label="${label}"
|
|
4268
4388
|
title="${label}"
|
|
4269
4389
|
type="button"
|
|
@@ -4298,7 +4418,6 @@ class AparteComposerAction extends HTMLElement {
|
|
|
4298
4418
|
if (icon.trimStart().startsWith("<")) return icon;
|
|
4299
4419
|
return resolveConfig(this).getIcon(icon) ?? icon;
|
|
4300
4420
|
}
|
|
4301
|
-
/** Escape a value before it lands in a double-quoted HTML attribute. */
|
|
4302
4421
|
}
|
|
4303
4422
|
if (!customElements.get("aparte-composer-action")) {
|
|
4304
4423
|
customElements.define("aparte-composer-action", AparteComposerAction);
|
|
@@ -4400,10 +4519,10 @@ class AparteConversationList extends HTMLElement {
|
|
|
4400
4519
|
const unarchiveLabel = this._esc(locale["unarchiveConversation"] ?? "Unarchive conversation");
|
|
4401
4520
|
const archiveAction = isArchived ? "unarchive" : "archive";
|
|
4402
4521
|
const archiveAriaLabel = isArchived ? unarchiveLabel : archiveLabel;
|
|
4403
|
-
const
|
|
4522
|
+
const archiveGlyph = resolveConfig(this).getIcon(isArchived ? "unarchive" : "archive");
|
|
4404
4523
|
return `
|
|
4405
4524
|
<div
|
|
4406
|
-
class="aparte-conv-item${activeClass}${archivedClass}"
|
|
4525
|
+
class="aparte-menu__item aparte-conv-item${activeClass}${archivedClass}"
|
|
4407
4526
|
role="button"
|
|
4408
4527
|
tabindex="0"
|
|
4409
4528
|
data-conv-id="${escapedId}"
|
|
@@ -4411,24 +4530,21 @@ class AparteConversationList extends HTMLElement {
|
|
|
4411
4530
|
>
|
|
4412
4531
|
<span class="aparte-conv-item__title">${escapedTitle}</span>
|
|
4413
4532
|
<button
|
|
4414
|
-
class="aparte-conv-item__archive"
|
|
4533
|
+
class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-conv-item__archive"
|
|
4415
4534
|
type="button"
|
|
4416
4535
|
data-archive-id="${escapedId}"
|
|
4417
4536
|
data-archive-action="${escapeAttr(archiveAction)}"
|
|
4418
4537
|
aria-label="${escapeAttr(archiveAriaLabel)}"
|
|
4419
4538
|
tabindex="0"
|
|
4420
|
-
>${
|
|
4539
|
+
>${archiveGlyph}</button>
|
|
4421
4540
|
<button
|
|
4422
|
-
class="aparte-conv-item__delete"
|
|
4541
|
+
class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-conv-item__delete"
|
|
4423
4542
|
type="button"
|
|
4424
4543
|
data-delete-id="${escapedId}"
|
|
4425
4544
|
aria-label="${deleteLabel}"
|
|
4426
4545
|
tabindex="0"
|
|
4427
4546
|
>
|
|
4428
|
-
|
|
4429
|
-
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
4430
|
-
<path d="M18 6L6 18M6 6l12 12"/>
|
|
4431
|
-
</svg>
|
|
4547
|
+
${resolveConfig(this).getIcon("close")}
|
|
4432
4548
|
</button>
|
|
4433
4549
|
</div>`;
|
|
4434
4550
|
}
|
|
@@ -4471,11 +4587,10 @@ class AparteConversationList extends HTMLElement {
|
|
|
4471
4587
|
};
|
|
4472
4588
|
_onKeydown = (e2) => {
|
|
4473
4589
|
if (e2.key !== "Enter" && e2.key !== " ") return;
|
|
4474
|
-
const
|
|
4475
|
-
if (
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
}
|
|
4590
|
+
const target = e2.target;
|
|
4591
|
+
if (!target.matches("[data-conv-id]")) return;
|
|
4592
|
+
e2.preventDefault();
|
|
4593
|
+
target.click();
|
|
4479
4594
|
};
|
|
4480
4595
|
/** Update active class without full re-render (perf optimisation). */
|
|
4481
4596
|
_updateActiveState() {
|
|
@@ -4535,6 +4650,7 @@ export {
|
|
|
4535
4650
|
AparteElicitationAbortError,
|
|
4536
4651
|
q as AparteError,
|
|
4537
4652
|
r as AparteErrorCode,
|
|
4653
|
+
AparteIcon,
|
|
4538
4654
|
AparteMessageRepository,
|
|
4539
4655
|
AparteOptgroup,
|
|
4540
4656
|
AparteOption,
|