@jant/core 0.3.36 → 0.3.37
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/bin/commands/export.js +1 -1
- package/bin/commands/import-site.js +529 -0
- package/bin/commands/reset-password.js +3 -2
- package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
- package/dist/client/assets/url-FWFqPJPb.js +1 -0
- package/dist/client/client.css +1 -1
- package/dist/client/client.js +4012 -3276
- package/dist/index.js +10285 -5809
- package/package.json +11 -3
- package/src/__tests__/helpers/app.ts +9 -9
- package/src/__tests__/helpers/db.ts +91 -93
- package/src/app.tsx +157 -27
- package/src/auth.ts +20 -2
- package/src/client/archive-nav.js +187 -0
- package/src/client/audio-player.ts +478 -0
- package/src/client/audio-processor.ts +84 -0
- package/src/client/avatar-upload.ts +3 -2
- package/src/client/components/__tests__/jant-compose-dialog.test.ts +645 -49
- package/src/client/components/__tests__/jant-compose-editor.test.ts +208 -16
- package/src/client/components/__tests__/jant-post-form.test.ts +19 -9
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
- package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
- package/src/client/components/collection-sidebar-types.ts +7 -9
- package/src/client/components/compose-types.ts +101 -4
- package/src/client/components/jant-collection-form.ts +43 -7
- package/src/client/components/jant-collection-sidebar.ts +88 -84
- package/src/client/components/jant-compose-dialog.ts +1655 -219
- package/src/client/components/jant-compose-editor.ts +732 -168
- package/src/client/components/jant-compose-fullscreen.ts +23 -78
- package/src/client/components/jant-media-lightbox.ts +2 -0
- package/src/client/components/jant-nav-manager.ts +24 -284
- package/src/client/components/jant-post-form.ts +89 -9
- package/src/client/components/jant-post-menu.ts +1019 -0
- package/src/client/components/jant-settings-avatar.ts +3 -3
- package/src/client/components/jant-settings-general.ts +38 -4
- package/src/client/components/jant-text-preview.ts +232 -0
- package/src/client/components/nav-manager-types.ts +4 -19
- package/src/client/components/post-form-template.ts +107 -12
- package/src/client/components/post-form-types.ts +11 -4
- package/src/client/compose-bridge.ts +410 -109
- package/src/client/image-processor.ts +26 -8
- package/src/client/media-metadata.ts +247 -0
- package/src/client/multipart-upload.ts +160 -0
- package/src/client/post-form-bridge.ts +52 -1
- package/src/client/settings-bridge.ts +0 -12
- package/src/client/thread-context.ts +140 -0
- package/src/client/tiptap/create-editor.ts +46 -0
- package/src/client/tiptap/extensions.ts +5 -0
- package/src/client/tiptap/image-node.ts +2 -8
- package/src/client/tiptap/paste-image.ts +2 -13
- package/src/client/tiptap/slash-commands.ts +173 -63
- package/src/client/toast.ts +101 -3
- package/src/client/types/sortablejs.d.ts +15 -0
- package/src/client/upload-with-metadata.ts +54 -0
- package/src/client/video-processor.ts +207 -0
- package/src/client.ts +5 -2
- package/src/db/__tests__/migrations.test.ts +118 -0
- package/src/db/index.ts +52 -0
- package/src/db/migrations/0000_baseline.sql +269 -0
- package/src/db/migrations/0001_fts_setup.sql +31 -0
- package/src/db/migrations/meta/0000_snapshot.json +703 -119
- package/src/db/migrations/meta/0001_snapshot.json +1337 -0
- package/src/db/migrations/meta/_journal.json +4 -39
- package/src/db/schema.ts +409 -145
- package/src/i18n/__tests__/detect.test.ts +115 -0
- package/src/i18n/context.tsx +2 -2
- package/src/i18n/detect.ts +85 -1
- package/src/i18n/i18n.ts +1 -1
- package/src/i18n/index.ts +2 -1
- package/src/i18n/locales/en.po +487 -1217
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +613 -996
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +624 -1007
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +6 -0
- package/src/index.ts +5 -7
- package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
- package/src/lib/__tests__/constants.test.ts +0 -1
- package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
- package/src/lib/__tests__/nanoid.test.ts +26 -0
- package/src/lib/__tests__/schemas.test.ts +181 -63
- package/src/lib/__tests__/slug.test.ts +126 -0
- package/src/lib/__tests__/sse.test.ts +6 -6
- package/src/lib/__tests__/summary.test.ts +264 -0
- package/src/lib/__tests__/theme.test.ts +1 -1
- package/src/lib/__tests__/timeline.test.ts +33 -30
- package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
- package/src/lib/__tests__/view.test.ts +141 -66
- package/src/lib/blurhash-placeholder.ts +102 -0
- package/src/lib/constants.ts +3 -1
- package/src/lib/emoji-catalog.ts +885 -68
- package/src/lib/errors.ts +11 -8
- package/src/lib/feed.ts +78 -32
- package/src/lib/html.ts +2 -1
- package/src/lib/icon-catalog.ts +5033 -1
- package/src/lib/icons.ts +3 -2
- package/src/lib/index.ts +0 -1
- package/src/lib/markdown-to-tiptap.ts +286 -0
- package/src/lib/media-helpers.ts +12 -3
- package/src/lib/nanoid.ts +29 -0
- package/src/lib/navigation.ts +1 -1
- package/src/lib/render.tsx +20 -2
- package/src/lib/resolve-config.ts +6 -2
- package/src/lib/schemas.ts +224 -55
- package/src/lib/search-snippet.ts +34 -0
- package/src/lib/slug.ts +96 -0
- package/src/lib/sse.ts +6 -6
- package/src/lib/storage.ts +115 -7
- package/src/lib/summary.ts +66 -0
- package/src/lib/theme.ts +11 -8
- package/src/lib/timeline.ts +74 -34
- package/src/lib/tiptap-render.ts +5 -10
- package/src/lib/tiptap-to-markdown.ts +305 -0
- package/src/lib/upload.ts +190 -29
- package/src/lib/url.ts +31 -0
- package/src/lib/view.ts +204 -37
- package/src/middleware/__tests__/auth.test.ts +191 -11
- package/src/middleware/__tests__/onboarding.test.ts +12 -10
- package/src/middleware/auth.ts +63 -9
- package/src/middleware/onboarding.ts +1 -1
- package/src/middleware/secure-headers.ts +40 -0
- package/src/preset.css +45 -2
- package/src/routes/__tests__/compose.test.ts +17 -24
- package/src/routes/api/__tests__/collections.test.ts +109 -61
- package/src/routes/api/__tests__/nav-items.test.ts +46 -29
- package/src/routes/api/__tests__/posts.test.ts +132 -68
- package/src/routes/api/__tests__/search.test.ts +15 -2
- package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
- package/src/routes/api/collections.ts +51 -42
- package/src/routes/api/custom-urls.ts +80 -0
- package/src/routes/api/export.ts +31 -0
- package/src/routes/api/nav-items.ts +23 -19
- package/src/routes/api/posts.ts +43 -39
- package/src/routes/api/search.ts +3 -4
- package/src/routes/api/upload-multipart.ts +245 -0
- package/src/routes/api/upload.ts +85 -19
- package/src/routes/auth/__tests__/setup.test.ts +20 -60
- package/src/routes/auth/setup.tsx +26 -33
- package/src/routes/auth/signin.tsx +3 -7
- package/src/routes/compose.tsx +10 -55
- package/src/routes/dash/__tests__/settings-avatar.test.ts +1 -1
- package/src/routes/dash/custom-urls.tsx +414 -0
- package/src/routes/dash/settings.tsx +304 -232
- package/src/routes/feed/__tests__/rss.test.ts +27 -28
- package/src/routes/feed/rss.ts +6 -4
- package/src/routes/feed/sitemap.ts +2 -12
- package/src/routes/pages/__tests__/collections.test.ts +5 -6
- package/src/routes/pages/__tests__/featured.test.ts +41 -22
- package/src/routes/pages/archive.tsx +175 -39
- package/src/routes/pages/collection.tsx +22 -10
- package/src/routes/pages/collections.tsx +3 -3
- package/src/routes/pages/featured.tsx +28 -4
- package/src/routes/pages/home.tsx +16 -15
- package/src/routes/pages/latest.tsx +1 -11
- package/src/routes/pages/new.tsx +39 -0
- package/src/routes/pages/page.tsx +95 -49
- package/src/routes/pages/search.tsx +1 -1
- package/src/services/__tests__/api-token.test.ts +135 -0
- package/src/services/__tests__/collection.test.ts +275 -227
- package/src/services/__tests__/custom-url.test.ts +213 -0
- package/src/services/__tests__/media.test.ts +162 -22
- package/src/services/__tests__/navigation.test.ts +109 -68
- package/src/services/__tests__/post-timeline.test.ts +205 -32
- package/src/services/__tests__/post.test.ts +713 -234
- package/src/services/__tests__/search.test.ts +67 -10
- package/src/services/api-token.ts +166 -0
- package/src/services/auth.ts +17 -2
- package/src/services/collection.ts +397 -131
- package/src/services/custom-url.ts +188 -0
- package/src/services/export.ts +802 -0
- package/src/services/index.ts +26 -19
- package/src/services/media.ts +100 -22
- package/src/services/navigation.ts +158 -47
- package/src/services/path.ts +339 -0
- package/src/services/post.ts +687 -154
- package/src/services/search.ts +160 -75
- package/src/styles/components.css +58 -7
- package/src/styles/tokens.css +84 -6
- package/src/styles/ui.css +2964 -457
- package/src/types/bindings.ts +4 -1
- package/src/types/config.ts +12 -4
- package/src/types/constants.ts +15 -3
- package/src/types/entities.ts +74 -35
- package/src/types/operations.ts +11 -24
- package/src/types/props.ts +51 -16
- package/src/types/views.ts +45 -22
- package/src/ui/color-themes.ts +133 -23
- package/src/ui/compose/ComposeDialog.tsx +239 -17
- package/src/ui/compose/ComposePrompt.tsx +1 -1
- package/src/ui/dash/CrudPageHeader.tsx +1 -1
- package/src/ui/dash/ListItemRow.tsx +1 -1
- package/src/ui/dash/StatusBadge.tsx +3 -1
- package/src/ui/dash/appearance/AdvancedContent.tsx +22 -1
- package/src/ui/dash/appearance/ColorThemeContent.tsx +22 -2
- package/src/ui/dash/appearance/FontThemeContent.tsx +1 -1
- package/src/ui/dash/appearance/NavigationContent.tsx +5 -45
- package/src/ui/dash/index.ts +0 -3
- package/src/ui/dash/settings/AccountContent.tsx +3 -57
- package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
- package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
- package/src/ui/dash/settings/AvatarContent.tsx +8 -0
- package/src/ui/dash/settings/SessionsContent.tsx +159 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +45 -15
- package/src/ui/feed/LinkCard.tsx +89 -40
- package/src/ui/feed/NoteCard.tsx +39 -25
- package/src/ui/feed/PostStatusBadges.tsx +67 -0
- package/src/ui/feed/QuoteCard.tsx +38 -23
- package/src/ui/feed/ThreadPreview.tsx +90 -26
- package/src/ui/feed/TimelineFeed.tsx +3 -2
- package/src/ui/feed/TimelineItem.tsx +15 -6
- package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
- package/src/ui/feed/thread-preview-state.ts +61 -0
- package/src/ui/font-themes.ts +2 -2
- package/src/ui/layouts/BaseLayout.tsx +2 -2
- package/src/ui/layouts/SiteLayout.tsx +105 -92
- package/src/ui/pages/ArchivePage.tsx +923 -98
- package/src/ui/pages/ComposePage.tsx +54 -0
- package/src/ui/pages/PostPage.tsx +30 -45
- package/src/ui/pages/SearchPage.tsx +181 -37
- package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
- package/src/ui/shared/CollectionsSidebar.tsx +47 -37
- package/src/ui/shared/MediaGallery.tsx +445 -149
- package/src/ui/shared/PostFooter.tsx +204 -0
- package/src/ui/shared/StarRating.tsx +27 -0
- package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
- package/src/ui/shared/index.ts +0 -1
- package/dist/client/assets/url-8Dj-5CLW.js +0 -1
- package/src/client/media-upload.ts +0 -161
- package/src/client/page-slug-bridge.ts +0 -42
- package/src/db/migrations/0000_square_wallflower.sql +0 -118
- package/src/db/migrations/0001_add_search_fts.sql +0 -34
- package/src/db/migrations/0002_add_media_attachments.sql +0 -3
- package/src/db/migrations/0003_add_navigation_links.sql +0 -8
- package/src/db/migrations/0004_add_storage_provider.sql +0 -3
- package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
- package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
- package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
- package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
- package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
- package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
- package/src/db/migrations/0011_add_path_registry.sql +0 -23
- package/src/db/migrations/0012_add_tiptap_columns.sql +0 -2
- package/src/db/migrations/0013_replace_featured_with_visibility.sql +0 -8
- package/src/db/migrations/meta/0003_snapshot.json +0 -821
- package/src/lib/__tests__/sqid.test.ts +0 -65
- package/src/lib/sqid.ts +0 -79
- package/src/routes/api/__tests__/pages.test.ts +0 -218
- package/src/routes/api/pages.ts +0 -73
- package/src/routes/dash/__tests__/pages.test.ts +0 -226
- package/src/routes/dash/index.tsx +0 -109
- package/src/routes/dash/media.tsx +0 -135
- package/src/routes/dash/pages.tsx +0 -245
- package/src/routes/dash/posts.tsx +0 -338
- package/src/routes/dash/redirects.tsx +0 -263
- package/src/routes/pages/post.tsx +0 -59
- package/src/services/__tests__/page.test.ts +0 -298
- package/src/services/__tests__/path-registry.test.ts +0 -165
- package/src/services/__tests__/redirect.test.ts +0 -159
- package/src/services/page.ts +0 -216
- package/src/services/path-registry.ts +0 -160
- package/src/services/redirect.ts +0 -97
- package/src/ui/dash/PageForm.tsx +0 -187
- package/src/ui/dash/PostList.tsx +0 -95
- package/src/ui/dash/media/MediaListContent.tsx +0 -206
- package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
- package/src/ui/dash/pages/PagesContent.tsx +0 -75
- package/src/ui/dash/posts/PostForm.tsx +0 -260
- package/src/ui/layouts/DashLayout.tsx +0 -247
- package/src/ui/pages/SinglePage.tsx +0 -23
- package/src/ui/shared/ThreadView.tsx +0 -136
|
@@ -10,11 +10,14 @@
|
|
|
10
10
|
|
|
11
11
|
import { LitElement, html, nothing } from "lit";
|
|
12
12
|
import { classMap } from "lit/directives/class-map.js";
|
|
13
|
+
import { unsafeSVG } from "lit/directives/unsafe-svg.js";
|
|
13
14
|
import type { Editor, JSONContent } from "@tiptap/core";
|
|
15
|
+
import Sortable from "sortablejs";
|
|
14
16
|
import type {
|
|
15
17
|
ComposeFormat,
|
|
16
18
|
ComposeLabels,
|
|
17
19
|
ComposeAttachment,
|
|
20
|
+
AttachedTextItem,
|
|
18
21
|
} from "./compose-types.js";
|
|
19
22
|
import {
|
|
20
23
|
UPLOAD_ACCEPT,
|
|
@@ -38,9 +41,9 @@ export class JantComposeEditor extends LitElement {
|
|
|
38
41
|
_rating: { state: true },
|
|
39
42
|
_showTitle: { state: true },
|
|
40
43
|
_showRating: { state: true },
|
|
41
|
-
|
|
42
|
-
_showAttachedText: { state: true },
|
|
44
|
+
_attachedTexts: { state: true },
|
|
43
45
|
_attachments: { state: true },
|
|
46
|
+
_attachmentOrder: { state: true },
|
|
44
47
|
_showAltPanel: { state: true },
|
|
45
48
|
_altPanelIndex: { state: true },
|
|
46
49
|
_showEmojiPicker: { state: true },
|
|
@@ -57,9 +60,9 @@ export class JantComposeEditor extends LitElement {
|
|
|
57
60
|
declare _rating: number;
|
|
58
61
|
declare _showTitle: boolean;
|
|
59
62
|
declare _showRating: boolean;
|
|
60
|
-
declare
|
|
61
|
-
declare _showAttachedText: boolean;
|
|
63
|
+
declare _attachedTexts: AttachedTextItem[];
|
|
62
64
|
declare _attachments: ComposeAttachment[];
|
|
65
|
+
declare _attachmentOrder: string[];
|
|
63
66
|
declare _showAltPanel: boolean;
|
|
64
67
|
declare _altPanelIndex: number;
|
|
65
68
|
declare _showEmojiPicker: boolean;
|
|
@@ -71,6 +74,10 @@ export class JantComposeEditor extends LitElement {
|
|
|
71
74
|
private _emojiPickerEl: HTMLElement | null = null;
|
|
72
75
|
private _emojiContainer: HTMLElement | null = null;
|
|
73
76
|
private _onDocClickBound = this._onDocumentClick.bind(this);
|
|
77
|
+
private _scrollBufferApplied = false;
|
|
78
|
+
private _suppressAttachedTextOpenUntil = 0;
|
|
79
|
+
#sortable: { destroy(): void } | null = null;
|
|
80
|
+
#revertNextSibling: globalThis.Node | null = null;
|
|
74
81
|
|
|
75
82
|
createRenderRoot() {
|
|
76
83
|
return this;
|
|
@@ -89,9 +96,9 @@ export class JantComposeEditor extends LitElement {
|
|
|
89
96
|
this._rating = 0;
|
|
90
97
|
this._showTitle = false;
|
|
91
98
|
this._showRating = false;
|
|
92
|
-
this.
|
|
93
|
-
this._showAttachedText = false;
|
|
99
|
+
this._attachedTexts = [];
|
|
94
100
|
this._attachments = [];
|
|
101
|
+
this._attachmentOrder = [];
|
|
95
102
|
this._showAltPanel = false;
|
|
96
103
|
this._altPanelIndex = 0;
|
|
97
104
|
this._showEmojiPicker = false;
|
|
@@ -106,6 +113,8 @@ export class JantComposeEditor extends LitElement {
|
|
|
106
113
|
super.disconnectedCallback();
|
|
107
114
|
this._editor?.destroy();
|
|
108
115
|
this._editor = null;
|
|
116
|
+
this.#sortable?.destroy();
|
|
117
|
+
this.#sortable = null;
|
|
109
118
|
document.removeEventListener("jant:slash-image", this._onSlashImage);
|
|
110
119
|
document.removeEventListener("click", this._onDocClickBound, true);
|
|
111
120
|
this._emojiContainer?.remove();
|
|
@@ -191,12 +200,25 @@ export class JantComposeEditor extends LitElement {
|
|
|
191
200
|
}
|
|
192
201
|
}
|
|
193
202
|
|
|
203
|
+
private _isEmptyDoc(json: JSONContent): boolean {
|
|
204
|
+
if (!json.content || json.content.length === 0) return true;
|
|
205
|
+
return json.content.every(
|
|
206
|
+
(node) =>
|
|
207
|
+
node.type === "paragraph" &&
|
|
208
|
+
(!node.content || node.content.length === 0),
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
194
212
|
getData() {
|
|
195
|
-
const body =
|
|
213
|
+
const body =
|
|
214
|
+
this._bodyJson && !this._isEmptyDoc(this._bodyJson)
|
|
215
|
+
? JSON.stringify(this._bodyJson)
|
|
216
|
+
: "";
|
|
196
217
|
const shared = {
|
|
197
218
|
rating: this._rating,
|
|
198
|
-
|
|
219
|
+
attachedTexts: this._attachedTexts,
|
|
199
220
|
attachments: this._attachments,
|
|
221
|
+
attachmentOrder: this._attachmentOrder,
|
|
200
222
|
};
|
|
201
223
|
|
|
202
224
|
switch (this.format) {
|
|
@@ -240,13 +262,13 @@ export class JantComposeEditor extends LitElement {
|
|
|
240
262
|
this._rating = 0;
|
|
241
263
|
this._showTitle = false;
|
|
242
264
|
this._showRating = false;
|
|
243
|
-
this.
|
|
244
|
-
this._showAttachedText = false;
|
|
265
|
+
this._attachedTexts = [];
|
|
245
266
|
// Revoke preview URLs before clearing
|
|
246
267
|
for (const a of this._attachments) {
|
|
247
268
|
URL.revokeObjectURL(a.previewUrl);
|
|
248
269
|
}
|
|
249
270
|
this._attachments = [];
|
|
271
|
+
this._attachmentOrder = [];
|
|
250
272
|
this._showAltPanel = false;
|
|
251
273
|
this._altPanelIndex = 0;
|
|
252
274
|
this.closeEmojiPicker();
|
|
@@ -263,6 +285,20 @@ export class JantComposeEditor extends LitElement {
|
|
|
263
285
|
);
|
|
264
286
|
}
|
|
265
287
|
|
|
288
|
+
updateAttachmentPreview(clientId: string, file: File) {
|
|
289
|
+
this._attachments = this._attachments.map((a) => {
|
|
290
|
+
if (a.clientId !== clientId) return a;
|
|
291
|
+
URL.revokeObjectURL(a.previewUrl);
|
|
292
|
+
return { ...a, file, previewUrl: URL.createObjectURL(file) };
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
updateAttachmentProgress(clientId: string, progress: number) {
|
|
297
|
+
this._attachments = this._attachments.map((a) =>
|
|
298
|
+
a.clientId === clientId ? { ...a, progress } : a,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
266
302
|
focusInput() {
|
|
267
303
|
if (this.format === "link") {
|
|
268
304
|
this.querySelector<HTMLElement>('.compose-input[type="url"]')?.focus();
|
|
@@ -286,11 +322,38 @@ export class JantComposeEditor extends LitElement {
|
|
|
286
322
|
content: this._bodyJson,
|
|
287
323
|
onUpdate: (json) => {
|
|
288
324
|
this._bodyJson = json;
|
|
325
|
+
this._ensureScrollBuffer();
|
|
289
326
|
},
|
|
290
327
|
onFocus: () => {
|
|
291
328
|
this._lastFocusedField = null;
|
|
292
329
|
},
|
|
293
330
|
});
|
|
331
|
+
|
|
332
|
+
// Lock editor min-height once so new lines fill existing space
|
|
333
|
+
// instead of growing the dialog line-by-line.
|
|
334
|
+
this._scrollBufferApplied = false;
|
|
335
|
+
const dom = this._editor.view.dom as HTMLElement;
|
|
336
|
+
const last = dom.lastElementChild as HTMLElement | null;
|
|
337
|
+
const contentH = last ? last.offsetTop + last.offsetHeight : 0;
|
|
338
|
+
const buffer = this.format !== "note" ? 60 : 120;
|
|
339
|
+
dom.style.minHeight = `${contentH + buffer}px`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* One-time: adds bottom padding for scroll buffer once the
|
|
344
|
+
* compose-body starts scrolling. Since the dialog is already at
|
|
345
|
+
* max-height by that point, the extra padding doesn't grow it.
|
|
346
|
+
*/
|
|
347
|
+
private _ensureScrollBuffer() {
|
|
348
|
+
if (this._scrollBufferApplied) return;
|
|
349
|
+
const dom = this._editor?.view?.dom as HTMLElement | undefined;
|
|
350
|
+
if (!dom) return;
|
|
351
|
+
const body = this.querySelector(".compose-body") as HTMLElement | null;
|
|
352
|
+
if (!body) return;
|
|
353
|
+
if (body.scrollHeight > body.clientHeight + 20) {
|
|
354
|
+
dom.style.paddingBottom = "80px";
|
|
355
|
+
this._scrollBufferApplied = true;
|
|
356
|
+
}
|
|
294
357
|
}
|
|
295
358
|
|
|
296
359
|
private _destroyEditor() {
|
|
@@ -298,6 +361,20 @@ export class JantComposeEditor extends LitElement {
|
|
|
298
361
|
this._editor = null;
|
|
299
362
|
}
|
|
300
363
|
|
|
364
|
+
/** Content-relevant properties that trigger a change event for draft auto-save */
|
|
365
|
+
private static _CONTENT_PROPS = new Set([
|
|
366
|
+
"_title",
|
|
367
|
+
"_bodyJson",
|
|
368
|
+
"_url",
|
|
369
|
+
"_quoteText",
|
|
370
|
+
"_quoteAuthor",
|
|
371
|
+
"_rating",
|
|
372
|
+
"_showTitle",
|
|
373
|
+
"_showRating",
|
|
374
|
+
"_attachedTexts",
|
|
375
|
+
"_attachmentOrder",
|
|
376
|
+
]);
|
|
377
|
+
|
|
301
378
|
protected updated(changed: Map<string, unknown>) {
|
|
302
379
|
super.updated(changed);
|
|
303
380
|
|
|
@@ -312,6 +389,29 @@ export class JantComposeEditor extends LitElement {
|
|
|
312
389
|
// Schedule init after Lit re-renders the new template
|
|
313
390
|
this.updateComplete.then(() => this._initEditor());
|
|
314
391
|
}
|
|
392
|
+
|
|
393
|
+
if (
|
|
394
|
+
changed.has("_attachmentOrder") ||
|
|
395
|
+
changed.has("_attachments") ||
|
|
396
|
+
changed.has("_attachedTexts")
|
|
397
|
+
) {
|
|
398
|
+
if (this._attachmentOrder.length > 1) {
|
|
399
|
+
this.#initSortable();
|
|
400
|
+
} else {
|
|
401
|
+
this.#sortable?.destroy();
|
|
402
|
+
this.#sortable = null;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Notify parent dialog of content changes for draft auto-save
|
|
407
|
+
for (const key of changed.keys()) {
|
|
408
|
+
if (JantComposeEditor._CONTENT_PROPS.has(key as string)) {
|
|
409
|
+
this.dispatchEvent(
|
|
410
|
+
new Event("jant:compose-content-changed", { bubbles: true }),
|
|
411
|
+
);
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
315
415
|
}
|
|
316
416
|
|
|
317
417
|
/** Returns Tiptap editor content and title for fullscreen handoff */
|
|
@@ -323,6 +423,124 @@ export class JantComposeEditor extends LitElement {
|
|
|
323
423
|
};
|
|
324
424
|
}
|
|
325
425
|
|
|
426
|
+
/** Pre-fill all fields for edit mode or draft restore */
|
|
427
|
+
populate(data: {
|
|
428
|
+
format: string;
|
|
429
|
+
title?: string;
|
|
430
|
+
bodyJson?: string;
|
|
431
|
+
url?: string;
|
|
432
|
+
quoteText?: string;
|
|
433
|
+
quoteAuthor?: string;
|
|
434
|
+
rating?: number;
|
|
435
|
+
showTitle?: boolean;
|
|
436
|
+
showRating?: boolean;
|
|
437
|
+
media?: Array<{
|
|
438
|
+
id: string;
|
|
439
|
+
previewUrl: string;
|
|
440
|
+
alt?: string;
|
|
441
|
+
mimeType: string;
|
|
442
|
+
originalName?: string;
|
|
443
|
+
summary?: string;
|
|
444
|
+
chars?: number;
|
|
445
|
+
}>;
|
|
446
|
+
textAttachments?: Array<{
|
|
447
|
+
clientId?: string;
|
|
448
|
+
bodyJson: string;
|
|
449
|
+
bodyHtml?: string;
|
|
450
|
+
summary: string;
|
|
451
|
+
mediaId?: string;
|
|
452
|
+
}>;
|
|
453
|
+
attachmentOrder?: string[];
|
|
454
|
+
}) {
|
|
455
|
+
if (data.title) this._title = data.title;
|
|
456
|
+
if (data.url) this._url = data.url;
|
|
457
|
+
if (data.quoteText) this._quoteText = data.quoteText;
|
|
458
|
+
if (data.quoteAuthor) this._quoteAuthor = data.quoteAuthor;
|
|
459
|
+
if (data.rating && data.rating > 0) {
|
|
460
|
+
this._rating = data.rating;
|
|
461
|
+
this._showRating = true;
|
|
462
|
+
}
|
|
463
|
+
if (data.showTitle !== undefined) this._showTitle = data.showTitle;
|
|
464
|
+
else if (data.title && data.format === "note") this._showTitle = true;
|
|
465
|
+
if (data.showRating !== undefined) this._showRating = data.showRating;
|
|
466
|
+
|
|
467
|
+
// Parse body JSON and set editor content
|
|
468
|
+
if (data.bodyJson) {
|
|
469
|
+
try {
|
|
470
|
+
const parsed = JSON.parse(data.bodyJson) as JSONContent;
|
|
471
|
+
this._bodyJson = parsed;
|
|
472
|
+
if (this._editor) {
|
|
473
|
+
this._editor.commands.setContent(parsed);
|
|
474
|
+
}
|
|
475
|
+
} catch {
|
|
476
|
+
// Body is not valid JSON — ignore
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Convert media attachments to ComposeAttachment[] with status "done"
|
|
481
|
+
if (data.media?.length) {
|
|
482
|
+
const attachments = data.media.map((m) => ({
|
|
483
|
+
clientId: crypto.randomUUID(),
|
|
484
|
+
file: new File([], m.originalName ?? "existing", { type: m.mimeType }),
|
|
485
|
+
previewUrl: m.previewUrl,
|
|
486
|
+
status: "done" as const,
|
|
487
|
+
progress: null,
|
|
488
|
+
mediaId: m.id,
|
|
489
|
+
alt: m.alt ?? "",
|
|
490
|
+
error: null,
|
|
491
|
+
summary: m.summary ?? null,
|
|
492
|
+
chars: m.chars ?? null,
|
|
493
|
+
}));
|
|
494
|
+
this._attachments = attachments;
|
|
495
|
+
this._attachmentOrder = attachments.map((a) => a.clientId);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Restore attached texts from server data
|
|
499
|
+
if (data.textAttachments?.length) {
|
|
500
|
+
const texts: AttachedTextItem[] = data.textAttachments.map((t) => {
|
|
501
|
+
let parsed: JSONContent | null = null;
|
|
502
|
+
try {
|
|
503
|
+
parsed = JSON.parse(t.bodyJson) as JSONContent;
|
|
504
|
+
} catch {
|
|
505
|
+
// Invalid JSON — leave as null
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
clientId: t.clientId ?? crypto.randomUUID(),
|
|
509
|
+
bodyJson: parsed,
|
|
510
|
+
bodyHtml: t.bodyHtml ?? "",
|
|
511
|
+
summary: t.summary,
|
|
512
|
+
mediaId: t.mediaId,
|
|
513
|
+
};
|
|
514
|
+
});
|
|
515
|
+
this._attachedTexts = texts;
|
|
516
|
+
this._attachmentOrder = [
|
|
517
|
+
...this._attachmentOrder,
|
|
518
|
+
...texts.map((t) => t.clientId),
|
|
519
|
+
];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (data.attachmentOrder?.length) {
|
|
523
|
+
const orderedClientIds = data.attachmentOrder
|
|
524
|
+
.map((attachmentId) => {
|
|
525
|
+
const mediaClientId = this._attachments.find(
|
|
526
|
+
(item) =>
|
|
527
|
+
item.mediaId === attachmentId || item.clientId === attachmentId,
|
|
528
|
+
)?.clientId;
|
|
529
|
+
if (mediaClientId) return mediaClientId;
|
|
530
|
+
return this._attachedTexts.find(
|
|
531
|
+
(item) =>
|
|
532
|
+
item.mediaId === attachmentId || item.clientId === attachmentId,
|
|
533
|
+
)?.clientId;
|
|
534
|
+
})
|
|
535
|
+
.filter((clientId): clientId is string => clientId !== undefined);
|
|
536
|
+
|
|
537
|
+
const remainingClientIds = this._attachmentOrder.filter(
|
|
538
|
+
(clientId) => !orderedClientIds.includes(clientId),
|
|
539
|
+
);
|
|
540
|
+
this._attachmentOrder = [...orderedClientIds, ...remainingClientIds];
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
326
544
|
/** Updates editor content and title from fullscreen close */
|
|
327
545
|
setEditorState(json: JSONContent | null, title: string) {
|
|
328
546
|
this._bodyJson = json;
|
|
@@ -336,28 +554,226 @@ export class JantComposeEditor extends LitElement {
|
|
|
336
554
|
}
|
|
337
555
|
}
|
|
338
556
|
|
|
557
|
+
private static SUMMARY_LENGTH = 100;
|
|
558
|
+
|
|
559
|
+
private _computeSummary(text: string): string {
|
|
560
|
+
const plain = text.replace(/\s+/g, " ").trim();
|
|
561
|
+
if (plain.length <= JantComposeEditor.SUMMARY_LENGTH) return plain;
|
|
562
|
+
return plain.slice(0, JantComposeEditor.SUMMARY_LENGTH) + "…";
|
|
563
|
+
}
|
|
564
|
+
|
|
339
565
|
private _openAttachedText() {
|
|
340
|
-
|
|
566
|
+
const item: AttachedTextItem = {
|
|
567
|
+
clientId: crypto.randomUUID(),
|
|
568
|
+
bodyJson: null,
|
|
569
|
+
bodyHtml: "",
|
|
570
|
+
summary: "",
|
|
571
|
+
};
|
|
572
|
+
this._attachedTexts = [...this._attachedTexts, item];
|
|
573
|
+
this._attachmentOrder = [...this._attachmentOrder, item.clientId];
|
|
574
|
+
const index = this._attachedTexts.length - 1;
|
|
341
575
|
this.dispatchEvent(
|
|
342
|
-
new CustomEvent("jant:attached-panel-open", {
|
|
576
|
+
new CustomEvent("jant:attached-panel-open", {
|
|
577
|
+
bubbles: true,
|
|
578
|
+
detail: { index },
|
|
579
|
+
}),
|
|
343
580
|
);
|
|
344
581
|
}
|
|
345
582
|
|
|
346
|
-
|
|
347
|
-
|
|
583
|
+
private _moveAttachment(clientId: string, direction: -1 | 1) {
|
|
584
|
+
const index = this._attachmentOrder.indexOf(clientId);
|
|
585
|
+
const nextIndex = index + direction;
|
|
586
|
+
if (
|
|
587
|
+
index === -1 ||
|
|
588
|
+
nextIndex < 0 ||
|
|
589
|
+
nextIndex >= this._attachmentOrder.length
|
|
590
|
+
) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const nextOrder = [...this._attachmentOrder];
|
|
595
|
+
const [item] = nextOrder.splice(index, 1);
|
|
596
|
+
if (!item) return;
|
|
597
|
+
nextOrder.splice(nextIndex, 0, item);
|
|
598
|
+
this._attachmentOrder = nextOrder;
|
|
599
|
+
this.#scrollAttachmentIntoView(clientId);
|
|
348
600
|
}
|
|
349
601
|
|
|
350
|
-
|
|
351
|
-
|
|
602
|
+
private _handleAttachmentKeydown(
|
|
603
|
+
clientId: string,
|
|
604
|
+
e: globalThis.KeyboardEvent,
|
|
605
|
+
onActivate?: () => void,
|
|
606
|
+
) {
|
|
607
|
+
if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
608
|
+
e.preventDefault();
|
|
609
|
+
this._moveAttachment(clientId, -1);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
614
|
+
e.preventDefault();
|
|
615
|
+
this._moveAttachment(clientId, 1);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (onActivate && (e.key === "Enter" || e.key === " ")) {
|
|
620
|
+
e.preventDefault();
|
|
621
|
+
onActivate();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
#initSortable() {
|
|
626
|
+
const list = this.querySelector<HTMLElement>("[data-attachment-list]");
|
|
627
|
+
if (!list || this.#sortable || this._attachmentOrder.length <= 1) return;
|
|
628
|
+
|
|
629
|
+
this.#sortable = Sortable.create(list, {
|
|
630
|
+
animation: 180,
|
|
631
|
+
bubbleScroll: false,
|
|
632
|
+
chosenClass: "compose-attachment-chosen",
|
|
633
|
+
direction: "horizontal",
|
|
634
|
+
dragClass: "compose-attachment-drag",
|
|
635
|
+
fallbackTolerance: 4,
|
|
636
|
+
filter:
|
|
637
|
+
"button, a, input, textarea, select, option, [contenteditable='true']",
|
|
638
|
+
forceAutoScrollFallback: true,
|
|
639
|
+
ghostClass: "compose-attachment-ghost",
|
|
640
|
+
handle: "[data-attachment-sortable]",
|
|
641
|
+
preventOnFilter: false,
|
|
642
|
+
scroll: list,
|
|
643
|
+
scrollSensitivity: 56,
|
|
644
|
+
scrollSpeed: 18,
|
|
645
|
+
onChoose: () => {
|
|
646
|
+
list.dataset.dragging = "true";
|
|
647
|
+
},
|
|
648
|
+
onStart: (evt) => {
|
|
649
|
+
this.#revertNextSibling = evt.item.nextSibling;
|
|
650
|
+
},
|
|
651
|
+
onUnchoose: () => {
|
|
652
|
+
delete list.dataset.dragging;
|
|
653
|
+
},
|
|
654
|
+
onEnd: (evt) => {
|
|
655
|
+
const els = [
|
|
656
|
+
...list.querySelectorAll<HTMLElement>("[data-attachment-id]"),
|
|
657
|
+
];
|
|
658
|
+
const orderedIds = els
|
|
659
|
+
.map((el) => el.dataset.attachmentId)
|
|
660
|
+
.filter((id): id is string => id !== undefined);
|
|
661
|
+
|
|
662
|
+
const { item, oldIndex, newIndex } = evt;
|
|
663
|
+
if (oldIndex != null && newIndex != null && oldIndex !== newIndex) {
|
|
664
|
+
item.parentNode?.removeChild(item);
|
|
665
|
+
if (this.#revertNextSibling) {
|
|
666
|
+
list.insertBefore(item, this.#revertNextSibling);
|
|
667
|
+
} else {
|
|
668
|
+
list.appendChild(item);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
this.#revertNextSibling = null;
|
|
672
|
+
delete list.dataset.dragging;
|
|
673
|
+
|
|
674
|
+
this.#sortable?.destroy();
|
|
675
|
+
this.#sortable = null;
|
|
676
|
+
|
|
677
|
+
if (orderedIds.length === this._attachmentOrder.length) {
|
|
678
|
+
this._attachmentOrder = orderedIds;
|
|
679
|
+
const movedId =
|
|
680
|
+
evt.newIndex != null ? orderedIds[evt.newIndex] : undefined;
|
|
681
|
+
if (movedId) {
|
|
682
|
+
this._suppressAttachedTextOpenUntil = Date.now() + 250;
|
|
683
|
+
this.#scrollAttachmentIntoView(movedId);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
#scrollAttachmentIntoView(clientId: string) {
|
|
691
|
+
void this.updateComplete.then(() => {
|
|
692
|
+
const target = this.querySelector<HTMLElement>(
|
|
693
|
+
`[data-attachment-id="${clientId}"]`,
|
|
694
|
+
);
|
|
695
|
+
target?.scrollIntoView({
|
|
696
|
+
behavior: "smooth",
|
|
697
|
+
block: "nearest",
|
|
698
|
+
inline: "nearest",
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
private _maybeEditAttachedText(index: number) {
|
|
704
|
+
if (Date.now() < this._suppressAttachedTextOpenUntil) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
this._editAttachedText(index);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
private _editAttachedText(index: number) {
|
|
711
|
+
this.dispatchEvent(
|
|
712
|
+
new CustomEvent("jant:attached-panel-open", {
|
|
713
|
+
bubbles: true,
|
|
714
|
+
detail: { index },
|
|
715
|
+
}),
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
private _removeAttachedText(index: number) {
|
|
720
|
+
const removed = this._attachedTexts[index];
|
|
721
|
+
this._attachedTexts = this._attachedTexts.filter((_, i) => i !== index);
|
|
722
|
+
if (removed) {
|
|
723
|
+
this._attachmentOrder = this._attachmentOrder.filter(
|
|
724
|
+
(id) => id !== removed.clientId,
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
updateAttachedText(
|
|
730
|
+
index: number,
|
|
731
|
+
bodyJson: JSONContent | null,
|
|
732
|
+
bodyHtml?: string,
|
|
733
|
+
) {
|
|
734
|
+
const plainText = this._extractPlainText(bodyJson);
|
|
735
|
+
this._attachedTexts = this._attachedTexts.map((item, i) =>
|
|
736
|
+
i === index
|
|
737
|
+
? {
|
|
738
|
+
...item,
|
|
739
|
+
bodyJson,
|
|
740
|
+
bodyHtml: bodyHtml ?? "",
|
|
741
|
+
summary: this._computeSummary(plainText),
|
|
742
|
+
}
|
|
743
|
+
: item,
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
closeAttachedPanel(index: number) {
|
|
748
|
+
const item = this._attachedTexts[index];
|
|
749
|
+
if (item && !this._hasAttachedTextContent(item.bodyJson)) {
|
|
750
|
+
this._attachedTexts = this._attachedTexts.filter((_, i) => i !== index);
|
|
751
|
+
this._attachmentOrder = this._attachmentOrder.filter(
|
|
752
|
+
(id) => id !== item.clientId,
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
private _hasAttachedTextContent(bodyJson: JSONContent | null): boolean {
|
|
758
|
+
if (!bodyJson) return false;
|
|
759
|
+
return this._extractPlainText(bodyJson).trim().length > 0;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
private _extractPlainText(json: JSONContent | null): string {
|
|
763
|
+
if (!json) return "";
|
|
764
|
+
let text = "";
|
|
765
|
+
const walk = (node: JSONContent) => {
|
|
766
|
+
if (node.text) text += node.text;
|
|
767
|
+
if (node.content) node.content.forEach(walk);
|
|
768
|
+
};
|
|
769
|
+
walk(json);
|
|
770
|
+
return text;
|
|
352
771
|
}
|
|
353
772
|
|
|
354
773
|
private _onInput(field: string, e: Event) {
|
|
355
774
|
const target = e.target as HTMLInputElement | HTMLTextAreaElement;
|
|
356
775
|
(this as Record<string, unknown>)[field] = target.value;
|
|
357
|
-
if (
|
|
358
|
-
target.tagName === "TEXTAREA" &&
|
|
359
|
-
!target.classList.contains("compose-attached-textarea")
|
|
360
|
-
) {
|
|
776
|
+
if (target.tagName === "TEXTAREA") {
|
|
361
777
|
this._autoResize(target as HTMLElement);
|
|
362
778
|
}
|
|
363
779
|
}
|
|
@@ -410,9 +826,12 @@ export class JantComposeEditor extends LitElement {
|
|
|
410
826
|
file,
|
|
411
827
|
previewUrl,
|
|
412
828
|
status: "pending",
|
|
829
|
+
progress: null,
|
|
413
830
|
mediaId: null,
|
|
414
831
|
alt: "",
|
|
415
832
|
error: null,
|
|
833
|
+
summary: null,
|
|
834
|
+
chars: null,
|
|
416
835
|
});
|
|
417
836
|
files.push({ file, clientId });
|
|
418
837
|
}
|
|
@@ -420,6 +839,24 @@ export class JantComposeEditor extends LitElement {
|
|
|
420
839
|
if (newAttachments.length === 0) return;
|
|
421
840
|
|
|
422
841
|
this._attachments = [...this._attachments, ...newAttachments];
|
|
842
|
+
this._attachmentOrder = [
|
|
843
|
+
...this._attachmentOrder,
|
|
844
|
+
...newAttachments.map((a) => a.clientId),
|
|
845
|
+
];
|
|
846
|
+
|
|
847
|
+
// Extract summaries and char counts for text-category files asynchronously
|
|
848
|
+
for (const att of newAttachments) {
|
|
849
|
+
const category = getMediaCategory(att.file.type);
|
|
850
|
+
if (category === "text") {
|
|
851
|
+
att.file.text().then((content) => {
|
|
852
|
+
const summary = this._computeSummary(content);
|
|
853
|
+
const chars = content.length;
|
|
854
|
+
this._attachments = this._attachments.map((a) =>
|
|
855
|
+
a.clientId === att.clientId ? { ...a, summary, chars } : a,
|
|
856
|
+
);
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|
|
423
860
|
|
|
424
861
|
this.dispatchEvent(
|
|
425
862
|
new CustomEvent("jant:files-selected", {
|
|
@@ -429,6 +866,11 @@ export class JantComposeEditor extends LitElement {
|
|
|
429
866
|
);
|
|
430
867
|
}
|
|
431
868
|
|
|
869
|
+
removeAttachment(clientId: string) {
|
|
870
|
+
const index = this._attachments.findIndex((a) => a.clientId === clientId);
|
|
871
|
+
if (index !== -1) this._removeAttachment(index);
|
|
872
|
+
}
|
|
873
|
+
|
|
432
874
|
private _removeAttachment(index: number) {
|
|
433
875
|
const attachment = this._attachments[index];
|
|
434
876
|
if (attachment) {
|
|
@@ -443,6 +885,11 @@ export class JantComposeEditor extends LitElement {
|
|
|
443
885
|
}),
|
|
444
886
|
);
|
|
445
887
|
}
|
|
888
|
+
if (attachment) {
|
|
889
|
+
this._attachmentOrder = this._attachmentOrder.filter(
|
|
890
|
+
(id) => id !== attachment.clientId,
|
|
891
|
+
);
|
|
892
|
+
}
|
|
446
893
|
this._attachments = this._attachments.filter((_, i) => i !== index);
|
|
447
894
|
// Close alt panel if it was showing the removed item
|
|
448
895
|
if (this._showAltPanel && this._altPanelIndex === index) {
|
|
@@ -462,7 +909,7 @@ export class JantComposeEditor extends LitElement {
|
|
|
462
909
|
// Reset failed attachments to pending
|
|
463
910
|
this._attachments = this._attachments.map((a) =>
|
|
464
911
|
a.status === "error"
|
|
465
|
-
? { ...a, status: "pending" as const, error: null }
|
|
912
|
+
? { ...a, status: "pending" as const, progress: null, error: null }
|
|
466
913
|
: a,
|
|
467
914
|
);
|
|
468
915
|
|
|
@@ -619,7 +1066,7 @@ export class JantComposeEditor extends LitElement {
|
|
|
619
1066
|
|
|
620
1067
|
// ── Helpers ──────────────────────────────────────────────────────
|
|
621
1068
|
|
|
622
|
-
private _getCategory(a: ComposeAttachment): MediaCategory
|
|
1069
|
+
private _getCategory(a: ComposeAttachment): MediaCategory {
|
|
623
1070
|
return getMediaCategory(a.file.type);
|
|
624
1071
|
}
|
|
625
1072
|
|
|
@@ -629,6 +1076,47 @@ export class JantComposeEditor extends LitElement {
|
|
|
629
1076
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
630
1077
|
}
|
|
631
1078
|
|
|
1079
|
+
private _formatChars(count: number): string {
|
|
1080
|
+
if (count < 1000) return `${count} chars`;
|
|
1081
|
+
if (count < 1_000_000) {
|
|
1082
|
+
return `${parseFloat((count / 1000).toFixed(1))}k chars`;
|
|
1083
|
+
}
|
|
1084
|
+
return `${parseFloat((count / 1_000_000).toFixed(1))}M chars`;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
private _renderFileIcon(mimeType: string, size: number) {
|
|
1088
|
+
const doc = `<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>`;
|
|
1089
|
+
|
|
1090
|
+
let inner: string;
|
|
1091
|
+
if (mimeType === "application/pdf") {
|
|
1092
|
+
inner = `<text x="12" y="16.5" text-anchor="middle" fill="currentColor" stroke="none" font-size="6" font-weight="700" font-family="system-ui, sans-serif">PDF</text>`;
|
|
1093
|
+
} else if (mimeType === "text/markdown") {
|
|
1094
|
+
inner = `<text x="12" y="16.5" text-anchor="middle" fill="currentColor" stroke="none" font-size="10" font-weight="700" font-family="system-ui, sans-serif">#</text>`;
|
|
1095
|
+
} else if (mimeType === "text/csv") {
|
|
1096
|
+
inner = `<line x1="8" y1="12" x2="16" y2="12"/><line x1="8" y1="15" x2="16" y2="15"/><line x1="8" y1="18" x2="16" y2="18"/><line x1="10.7" y1="12" x2="10.7" y2="18"/><line x1="13.3" y1="12" x2="13.3" y2="18"/>`;
|
|
1097
|
+
} else if (getMediaCategory(mimeType) === "archive") {
|
|
1098
|
+
inner = `<line x1="12" y1="10" x2="12" y2="11.5"/><line x1="12" y1="13" x2="12" y2="14.5"/><line x1="12" y1="16" x2="12" y2="17.5"/>`;
|
|
1099
|
+
} else if (mimeType === "text/x-tiptap+json") {
|
|
1100
|
+
inner = `<line x1="16" y1="11" x2="8" y2="11"/><line x1="16" y1="14" x2="8" y2="14"/><line x1="12" y1="17" x2="8" y2="17"/>`;
|
|
1101
|
+
} else {
|
|
1102
|
+
// Plain text default — 3 text lines
|
|
1103
|
+
inner = `<line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><line x1="10" y1="9" x2="8" y2="9"/>`;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
return html`<svg
|
|
1107
|
+
width="${size}"
|
|
1108
|
+
height="${size}"
|
|
1109
|
+
viewBox="0 0 24 24"
|
|
1110
|
+
fill="none"
|
|
1111
|
+
stroke="currentColor"
|
|
1112
|
+
stroke-width="1.5"
|
|
1113
|
+
stroke-linecap="round"
|
|
1114
|
+
stroke-linejoin="round"
|
|
1115
|
+
>
|
|
1116
|
+
${unsafeSVG(doc + inner)}
|
|
1117
|
+
</svg>`;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
632
1120
|
// ── Render helpers ────────────────────────────────────────────────
|
|
633
1121
|
|
|
634
1122
|
private _renderNoteFields() {
|
|
@@ -763,48 +1251,6 @@ export class JantComposeEditor extends LitElement {
|
|
|
763
1251
|
`;
|
|
764
1252
|
}
|
|
765
1253
|
|
|
766
|
-
private _renderAttachedBadge() {
|
|
767
|
-
if (this._attachedText.trim().length === 0 || this._showAttachedText)
|
|
768
|
-
return nothing;
|
|
769
|
-
return html`
|
|
770
|
-
<div
|
|
771
|
-
class="compose-attached-badge"
|
|
772
|
-
@click=${() => this._openAttachedText()}
|
|
773
|
-
>
|
|
774
|
-
<svg
|
|
775
|
-
width="14"
|
|
776
|
-
height="14"
|
|
777
|
-
viewBox="0 0 18 18"
|
|
778
|
-
fill="none"
|
|
779
|
-
stroke="currentColor"
|
|
780
|
-
stroke-width="1.3"
|
|
781
|
-
stroke-linecap="round"
|
|
782
|
-
class="text-muted-foreground icon-fine"
|
|
783
|
-
>
|
|
784
|
-
<rect x="3" y="2" width="12" height="14" rx="2" />
|
|
785
|
-
<line x1="6" y1="6" x2="12" y2="6" />
|
|
786
|
-
<line x1="6" y1="9" x2="12" y2="9" />
|
|
787
|
-
<line x1="6" y1="12" x2="9.5" y2="12" />
|
|
788
|
-
</svg>
|
|
789
|
-
<span class="text-xs font-medium">${this.labels.attachedText}</span>
|
|
790
|
-
<span class="text-xs text-muted-foreground"
|
|
791
|
-
>· ${this._attachedText.length.toLocaleString()} chars</span
|
|
792
|
-
>
|
|
793
|
-
<div class="flex-1"></div>
|
|
794
|
-
<button
|
|
795
|
-
type="button"
|
|
796
|
-
class="compose-attached-badge-dismiss"
|
|
797
|
-
@click=${(e: Event) => {
|
|
798
|
-
e.stopPropagation();
|
|
799
|
-
this._attachedText = "";
|
|
800
|
-
}}
|
|
801
|
-
>
|
|
802
|
-
✕
|
|
803
|
-
</button>
|
|
804
|
-
</div>
|
|
805
|
-
`;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
1254
|
private _renderAttachmentPreview(a: ComposeAttachment) {
|
|
809
1255
|
const category = this._getCategory(a);
|
|
810
1256
|
|
|
@@ -846,6 +1292,9 @@ export class JantComposeEditor extends LitElement {
|
|
|
846
1292
|
</svg>
|
|
847
1293
|
</div>
|
|
848
1294
|
<span class="compose-attachment-file-name">${a.file.name}</span>
|
|
1295
|
+
<span class="compose-attachment-file-size"
|
|
1296
|
+
>${this._formatSize(a.file.size)}</span
|
|
1297
|
+
>
|
|
849
1298
|
</div>
|
|
850
1299
|
`;
|
|
851
1300
|
}
|
|
@@ -854,23 +1303,43 @@ export class JantComposeEditor extends LitElement {
|
|
|
854
1303
|
return html`
|
|
855
1304
|
<div class="compose-attachment-file-card">
|
|
856
1305
|
<div class="compose-attachment-file-icon">
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1306
|
+
${this._renderFileIcon(a.file.type, 20)}
|
|
1307
|
+
</div>
|
|
1308
|
+
<span class="compose-attachment-file-name">${a.file.name}</span>
|
|
1309
|
+
<span class="compose-attachment-file-size"
|
|
1310
|
+
>${this._formatSize(a.file.size)}</span
|
|
1311
|
+
>
|
|
1312
|
+
</div>
|
|
1313
|
+
`;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
if (category === "text") {
|
|
1317
|
+
return html`
|
|
1318
|
+
<div class="compose-attachment-file-card">
|
|
1319
|
+
<div class="compose-attachment-file-icon">
|
|
1320
|
+
${this._renderFileIcon(a.file.type, 20)}
|
|
1321
|
+
</div>
|
|
1322
|
+
<span class="compose-attachment-file-name">${a.file.name}</span>
|
|
1323
|
+
${a.summary
|
|
1324
|
+
? html`<span class="compose-attachment-text-summary"
|
|
1325
|
+
>${a.summary}</span
|
|
1326
|
+
>`
|
|
1327
|
+
: nothing}
|
|
1328
|
+
${typeof a.chars === "number" && a.chars > 0
|
|
1329
|
+
? html`<span class="compose-attachment-file-size"
|
|
1330
|
+
>${this._formatChars(a.chars)}</span
|
|
1331
|
+
>`
|
|
1332
|
+
: nothing}
|
|
1333
|
+
</div>
|
|
1334
|
+
`;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// Default for non-visual types: generic file card (archive, office, font, 3d, code, etc.)
|
|
1338
|
+
if (category !== "image") {
|
|
1339
|
+
return html`
|
|
1340
|
+
<div class="compose-attachment-file-card">
|
|
1341
|
+
<div class="compose-attachment-file-icon">
|
|
1342
|
+
${this._renderFileIcon(a.file.type, 20)}
|
|
874
1343
|
</div>
|
|
875
1344
|
<span class="compose-attachment-file-name">${a.file.name}</span>
|
|
876
1345
|
<span class="compose-attachment-file-size"
|
|
@@ -880,7 +1349,7 @@ export class JantComposeEditor extends LitElement {
|
|
|
880
1349
|
`;
|
|
881
1350
|
}
|
|
882
1351
|
|
|
883
|
-
//
|
|
1352
|
+
// Image
|
|
884
1353
|
return html`
|
|
885
1354
|
<div class="compose-attachment-thumb">
|
|
886
1355
|
<img src=${a.previewUrl} alt="" class="compose-attachment-img" />
|
|
@@ -890,48 +1359,38 @@ export class JantComposeEditor extends LitElement {
|
|
|
890
1359
|
|
|
891
1360
|
private _renderAttachmentOverlay(a: ComposeAttachment, index: number) {
|
|
892
1361
|
return html`
|
|
893
|
-
${a.status === "pending" || a.status === "uploading"
|
|
894
|
-
? html`
|
|
895
|
-
<div class="compose-attachment-overlay">
|
|
896
|
-
<svg
|
|
897
|
-
class="animate-spin size-4"
|
|
898
|
-
viewBox="0 0 24 24"
|
|
899
|
-
fill="none"
|
|
900
|
-
stroke="currentColor"
|
|
901
|
-
style="stroke-width: 2.5"
|
|
902
|
-
stroke-linecap="round"
|
|
903
|
-
>
|
|
904
|
-
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
905
|
-
</svg>
|
|
906
|
-
</div>
|
|
907
|
-
`
|
|
908
|
-
: nothing}
|
|
909
1362
|
${a.status === "error"
|
|
910
1363
|
? html`
|
|
911
1364
|
<button
|
|
912
1365
|
type="button"
|
|
913
1366
|
class="compose-attachment-overlay compose-attachment-retry"
|
|
914
|
-
title="${a.error ?? "Upload failed"}. ${this.labels.retryAll}"
|
|
915
1367
|
@click=${(e: Event) => {
|
|
916
1368
|
e.stopPropagation();
|
|
917
1369
|
this._retryAllFailed();
|
|
918
1370
|
}}
|
|
919
1371
|
>
|
|
920
|
-
<
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1372
|
+
<span class="compose-retry-content">
|
|
1373
|
+
<svg
|
|
1374
|
+
width="20"
|
|
1375
|
+
height="20"
|
|
1376
|
+
viewBox="0 0 24 24"
|
|
1377
|
+
fill="none"
|
|
1378
|
+
stroke="currentColor"
|
|
1379
|
+
stroke-width="2"
|
|
1380
|
+
stroke-linecap="round"
|
|
1381
|
+
stroke-linejoin="round"
|
|
1382
|
+
>
|
|
1383
|
+
<path
|
|
1384
|
+
d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"
|
|
1385
|
+
/>
|
|
1386
|
+
<path d="M3 3v5h5" />
|
|
1387
|
+
<path
|
|
1388
|
+
d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"
|
|
1389
|
+
/>
|
|
1390
|
+
<path d="M16 16h5v5" />
|
|
1391
|
+
</svg>
|
|
1392
|
+
<span class="compose-retry-label">${this.labels.retryAll}</span>
|
|
1393
|
+
</span>
|
|
935
1394
|
</button>
|
|
936
1395
|
`
|
|
937
1396
|
: nothing}
|
|
@@ -945,55 +1404,126 @@ export class JantComposeEditor extends LitElement {
|
|
|
945
1404
|
`;
|
|
946
1405
|
}
|
|
947
1406
|
|
|
948
|
-
private
|
|
949
|
-
|
|
1407
|
+
private _renderAttachedTextCard(item: AttachedTextItem, index: number) {
|
|
1408
|
+
return html`
|
|
1409
|
+
<div class="compose-attachment" data-attachment-id=${item.clientId}>
|
|
1410
|
+
<div
|
|
1411
|
+
class="compose-attachment-thumb compose-attachment-sortable"
|
|
1412
|
+
data-attachment-sortable
|
|
1413
|
+
tabindex="0"
|
|
1414
|
+
@click=${() => this._maybeEditAttachedText(index)}
|
|
1415
|
+
@keydown=${(e: globalThis.KeyboardEvent) =>
|
|
1416
|
+
this._handleAttachmentKeydown(item.clientId, e, () =>
|
|
1417
|
+
this._maybeEditAttachedText(index),
|
|
1418
|
+
)}
|
|
1419
|
+
>
|
|
1420
|
+
<div class="compose-attachment-text-card">
|
|
1421
|
+
<div class="compose-attachment-file-icon">
|
|
1422
|
+
${this._renderFileIcon("text/x-tiptap+json", 20)}
|
|
1423
|
+
</div>
|
|
1424
|
+
<span class="compose-attachment-text-summary">${item.summary}</span>
|
|
1425
|
+
${item.bodyJson
|
|
1426
|
+
? html`<span class="compose-attachment-file-size"
|
|
1427
|
+
>${this._formatChars(
|
|
1428
|
+
this._extractPlainText(item.bodyJson).length,
|
|
1429
|
+
)}</span
|
|
1430
|
+
>`
|
|
1431
|
+
: nothing}
|
|
1432
|
+
</div>
|
|
1433
|
+
<button
|
|
1434
|
+
type="button"
|
|
1435
|
+
class="compose-attachment-remove"
|
|
1436
|
+
@click=${(e: Event) => {
|
|
1437
|
+
e.stopPropagation();
|
|
1438
|
+
this._removeAttachedText(index);
|
|
1439
|
+
}}
|
|
1440
|
+
>
|
|
1441
|
+
✕
|
|
1442
|
+
</button>
|
|
1443
|
+
</div>
|
|
1444
|
+
</div>
|
|
1445
|
+
`;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
private _renderMediaAttachment(a: ComposeAttachment, i: number) {
|
|
1449
|
+
const category = this._getCategory(a);
|
|
1450
|
+
const isFileCard = category !== "image" && category !== "video";
|
|
950
1451
|
|
|
951
1452
|
return html`
|
|
952
|
-
<div class="compose-
|
|
953
|
-
${
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
/>
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1453
|
+
<div class="compose-attachment" data-attachment-id=${a.clientId}>
|
|
1454
|
+
${isFileCard
|
|
1455
|
+
? html`
|
|
1456
|
+
<div class="compose-attachment-thumb">
|
|
1457
|
+
<div
|
|
1458
|
+
class="compose-attachment-sortable"
|
|
1459
|
+
data-attachment-sortable
|
|
1460
|
+
tabindex="0"
|
|
1461
|
+
@keydown=${(e: globalThis.KeyboardEvent) =>
|
|
1462
|
+
this._handleAttachmentKeydown(a.clientId, e)}
|
|
1463
|
+
>
|
|
1464
|
+
${this._renderAttachmentPreview(a)}
|
|
1465
|
+
</div>
|
|
1466
|
+
${this._renderAttachmentOverlay(a, i)}
|
|
1467
|
+
</div>
|
|
1468
|
+
`
|
|
1469
|
+
: html`
|
|
1470
|
+
<div class="compose-attachment-thumb">
|
|
1471
|
+
<div
|
|
1472
|
+
class="compose-attachment-sortable"
|
|
1473
|
+
data-attachment-sortable
|
|
1474
|
+
tabindex="0"
|
|
1475
|
+
@keydown=${(e: globalThis.KeyboardEvent) =>
|
|
1476
|
+
this._handleAttachmentKeydown(a.clientId, e)}
|
|
1477
|
+
>
|
|
1478
|
+
${category === "video"
|
|
1479
|
+
? html`
|
|
1480
|
+
<video
|
|
1481
|
+
src=${a.previewUrl}
|
|
1482
|
+
class="compose-attachment-img"
|
|
1483
|
+
preload="metadata"
|
|
1484
|
+
muted
|
|
1485
|
+
></video>
|
|
1486
|
+
<div class="compose-attachment-play-icon">
|
|
1487
|
+
<svg
|
|
1488
|
+
width="24"
|
|
1489
|
+
height="24"
|
|
1490
|
+
viewBox="0 0 24 24"
|
|
1491
|
+
fill="white"
|
|
1492
|
+
>
|
|
1493
|
+
<path d="M8 5v14l11-7z" />
|
|
1494
|
+
</svg>
|
|
1495
|
+
</div>
|
|
1496
|
+
`
|
|
1497
|
+
: a.status === "processing"
|
|
1498
|
+
? html`
|
|
1499
|
+
<div class="compose-attachment-processing">
|
|
1500
|
+
<svg
|
|
1501
|
+
class="animate-spin size-5"
|
|
1502
|
+
viewBox="0 0 24 24"
|
|
1503
|
+
fill="none"
|
|
1504
|
+
stroke="currentColor"
|
|
1505
|
+
stroke-width="2"
|
|
1506
|
+
>
|
|
1507
|
+
<path
|
|
1508
|
+
d="M12 2a10 10 0 1 0 10 10"
|
|
1509
|
+
stroke-linecap="round"
|
|
1510
|
+
/>
|
|
1511
|
+
</svg>
|
|
1512
|
+
</div>
|
|
1513
|
+
`
|
|
1514
|
+
: html`
|
|
1515
|
+
<img
|
|
1516
|
+
src=${a.previewUrl}
|
|
1517
|
+
alt=""
|
|
1518
|
+
class="compose-attachment-img"
|
|
1519
|
+
/>
|
|
1520
|
+
`}
|
|
1521
|
+
</div>
|
|
1522
|
+
${this._renderAttachmentOverlay(a, i)}
|
|
1523
|
+
</div>
|
|
1524
|
+
`}
|
|
1525
|
+
${category === "image"
|
|
1526
|
+
? html`
|
|
997
1527
|
<button
|
|
998
1528
|
type="button"
|
|
999
1529
|
class=${classMap({
|
|
@@ -1004,15 +1534,45 @@ export class JantComposeEditor extends LitElement {
|
|
|
1004
1534
|
>
|
|
1005
1535
|
${a.alt.length > 0 ? "ALT" : "+ ALT"}
|
|
1006
1536
|
</button>
|
|
1007
|
-
|
|
1008
|
-
|
|
1537
|
+
`
|
|
1538
|
+
: nothing}
|
|
1539
|
+
</div>
|
|
1540
|
+
`;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
private _renderAttachments() {
|
|
1544
|
+
if (this._attachments.length === 0 && this._attachedTexts.length === 0)
|
|
1545
|
+
return nothing;
|
|
1546
|
+
|
|
1547
|
+
return html`
|
|
1548
|
+
<div class="compose-attachments" data-attachment-list>
|
|
1549
|
+
${this._attachmentOrder.map((clientId) => {
|
|
1550
|
+
const mediaIndex = this._attachments.findIndex(
|
|
1551
|
+
(a) => a.clientId === clientId,
|
|
1552
|
+
);
|
|
1553
|
+
if (mediaIndex !== -1) {
|
|
1554
|
+
return this._renderMediaAttachment(
|
|
1555
|
+
this._attachments[mediaIndex],
|
|
1556
|
+
mediaIndex,
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
const textIndex = this._attachedTexts.findIndex(
|
|
1560
|
+
(t) => t.clientId === clientId,
|
|
1561
|
+
);
|
|
1562
|
+
if (textIndex !== -1) {
|
|
1563
|
+
return this._renderAttachedTextCard(
|
|
1564
|
+
this._attachedTexts[textIndex],
|
|
1565
|
+
textIndex,
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
return nothing;
|
|
1009
1569
|
})}
|
|
1010
1570
|
</div>
|
|
1011
1571
|
`;
|
|
1012
1572
|
}
|
|
1013
1573
|
|
|
1014
1574
|
private _renderToolsRow() {
|
|
1015
|
-
const hasAttached = this.
|
|
1575
|
+
const hasAttached = this._attachedTexts.length > 0;
|
|
1016
1576
|
return html`
|
|
1017
1577
|
<div class="compose-tools-row">
|
|
1018
1578
|
<!-- Media / Add -->
|
|
@@ -1053,9 +1613,9 @@ export class JantComposeEditor extends LitElement {
|
|
|
1053
1613
|
type="button"
|
|
1054
1614
|
class=${classMap({
|
|
1055
1615
|
"compose-tool-btn": true,
|
|
1056
|
-
"compose-tool-btn-
|
|
1616
|
+
"compose-tool-btn-add": hasAttached,
|
|
1057
1617
|
})}
|
|
1058
|
-
title=${this.labels.attachedText}
|
|
1618
|
+
title=${hasAttached ? "" : this.labels.attachedText}
|
|
1059
1619
|
@click=${() => this._openAttachedText()}
|
|
1060
1620
|
>
|
|
1061
1621
|
<svg
|
|
@@ -1073,6 +1633,11 @@ export class JantComposeEditor extends LitElement {
|
|
|
1073
1633
|
<line x1="6" y1="9" x2="12" y2="9" />
|
|
1074
1634
|
<line x1="6" y1="12" x2="9.5" y2="12" />
|
|
1075
1635
|
</svg>
|
|
1636
|
+
${hasAttached
|
|
1637
|
+
? html`<span class="compose-tool-label"
|
|
1638
|
+
>${this.labels.addMore}</span
|
|
1639
|
+
>`
|
|
1640
|
+
: nothing}
|
|
1076
1641
|
</button>
|
|
1077
1642
|
|
|
1078
1643
|
<!-- Rate -->
|
|
@@ -1238,8 +1803,7 @@ export class JantComposeEditor extends LitElement {
|
|
|
1238
1803
|
: this.format === "link"
|
|
1239
1804
|
? this._renderLinkFields()
|
|
1240
1805
|
: this._renderQuoteFields()}
|
|
1241
|
-
${this.
|
|
1242
|
-
${this._renderAttachments()}
|
|
1806
|
+
${this._renderAttachments()} ${this._renderStarRating()}
|
|
1243
1807
|
</section>
|
|
1244
1808
|
${this._renderToolsRow()}
|
|
1245
1809
|
`;
|