@autumnsgrove/groveengine 0.3.3 → 0.4.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/auth/index.d.ts +2 -0
- package/dist/auth/index.js +5 -0
- package/dist/components/admin/GutterManager.svelte +4 -4
- package/dist/components/admin/MarkdownEditor.svelte +381 -1311
- package/dist/components/admin/MarkdownEditor.svelte.d.ts +2 -8
- package/dist/components/admin/composables/index.d.ts +7 -0
- package/dist/components/admin/composables/index.js +12 -0
- package/dist/components/admin/composables/useAmbientSounds.svelte.d.ts +53 -0
- package/dist/components/admin/composables/useAmbientSounds.svelte.js +192 -0
- package/dist/components/admin/composables/useCommandPalette.svelte.d.ts +17 -0
- package/dist/components/admin/composables/useCommandPalette.svelte.js +118 -0
- package/dist/components/admin/composables/useDraftManager.svelte.d.ts +17 -0
- package/dist/components/admin/composables/useDraftManager.svelte.js +154 -0
- package/dist/components/admin/composables/useEditorTheme.svelte.d.ts +195 -0
- package/dist/components/admin/composables/useEditorTheme.svelte.js +182 -0
- package/dist/components/admin/composables/useSlashCommands.svelte.d.ts +32 -0
- package/dist/components/admin/composables/useSlashCommands.svelte.js +166 -0
- package/dist/components/admin/composables/useSnippets.svelte.d.ts +5 -0
- package/dist/components/admin/composables/useSnippets.svelte.js +122 -0
- package/dist/components/admin/composables/useWritingSession.svelte.d.ts +13 -0
- package/dist/components/admin/composables/useWritingSession.svelte.js +100 -0
- package/dist/components/custom/ContentWithGutter.svelte +14 -7
- package/dist/components/custom/GutterItem.svelte +2 -2
- package/dist/config/ai-models.d.ts +25 -0
- package/dist/config/ai-models.js +50 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +4 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +6 -6
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +4 -0
- package/dist/ui/components/charts/ActivityOverview.svelte +293 -0
- package/dist/ui/components/charts/ActivityOverview.svelte.d.ts +12 -0
- package/dist/ui/components/charts/LOCBar.svelte +129 -0
- package/dist/ui/components/charts/LOCBar.svelte.d.ts +21 -0
- package/dist/ui/components/charts/RepoBreakdown.svelte +136 -0
- package/dist/ui/components/charts/RepoBreakdown.svelte.d.ts +16 -0
- package/dist/ui/components/charts/Sparkline.svelte +139 -0
- package/dist/ui/components/charts/Sparkline.svelte.d.ts +6 -0
- package/dist/ui/components/charts/index.d.ts +5 -0
- package/dist/ui/components/charts/index.js +11 -0
- package/dist/ui/components/content/PlanCard.svelte +91 -0
- package/dist/ui/components/content/PlanCard.svelte.d.ts +13 -0
- package/dist/ui/components/content/ProductCard.svelte +125 -0
- package/dist/ui/components/content/ProductCard.svelte.d.ts +14 -0
- package/dist/ui/components/content/SearchCard.svelte +60 -0
- package/dist/ui/components/content/SearchCard.svelte.d.ts +10 -0
- package/dist/ui/components/content/index.d.ts +4 -0
- package/dist/ui/components/content/index.js +10 -0
- package/dist/ui/components/forms/SearchInput.svelte +89 -0
- package/dist/ui/components/forms/SearchInput.svelte.d.ts +11 -0
- package/dist/ui/components/forms/index.d.ts +2 -0
- package/dist/ui/components/forms/index.js +8 -0
- package/dist/ui/components/gallery/index.d.ts +5 -0
- package/dist/ui/components/gallery/index.js +13 -0
- package/dist/ui/components/icons/IconLegend.svelte +83 -0
- package/dist/ui/components/icons/IconLegend.svelte.d.ts +11 -0
- package/dist/ui/components/icons/Icons.svelte +115 -0
- package/dist/ui/components/icons/Icons.svelte.d.ts +8 -0
- package/dist/ui/components/icons/index.d.ts +3 -0
- package/dist/ui/components/icons/index.js +9 -0
- package/dist/ui/components/indicators/CreditBalance.svelte +67 -0
- package/dist/ui/components/indicators/CreditBalance.svelte.d.ts +9 -0
- package/dist/ui/components/indicators/ScoreBar.svelte +63 -0
- package/dist/ui/components/indicators/ScoreBar.svelte.d.ts +9 -0
- package/dist/ui/components/indicators/StatusBadge.svelte +46 -0
- package/dist/ui/components/indicators/StatusBadge.svelte.d.ts +7 -0
- package/dist/ui/components/indicators/index.d.ts +4 -0
- package/dist/ui/components/indicators/index.js +10 -0
- package/dist/{components/ui → ui/components/primitives}/accordion/accordion-content.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/accordion/accordion-item.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/accordion/accordion-trigger.svelte +1 -1
- package/dist/ui/components/primitives/badge/badge.svelte +50 -0
- package/dist/ui/components/primitives/badge/badge.svelte.d.ts +60 -0
- package/dist/ui/components/primitives/badge/index.d.ts +2 -0
- package/dist/ui/components/primitives/badge/index.js +2 -0
- package/dist/ui/components/primitives/button/button.svelte +82 -0
- package/dist/ui/components/primitives/button/button.svelte.d.ts +132 -0
- package/dist/ui/components/primitives/button/index.d.ts +2 -0
- package/dist/ui/components/primitives/button/index.js +4 -0
- package/dist/ui/components/primitives/card/card-content.svelte +16 -0
- package/dist/ui/components/primitives/card/card-content.svelte.d.ts +5 -0
- package/dist/ui/components/primitives/card/card-description.svelte +16 -0
- package/dist/ui/components/primitives/card/card-description.svelte.d.ts +5 -0
- package/dist/ui/components/primitives/card/card-footer.svelte +16 -0
- package/dist/ui/components/primitives/card/card-footer.svelte.d.ts +5 -0
- package/dist/ui/components/primitives/card/card-header.svelte +16 -0
- package/dist/ui/components/primitives/card/card-header.svelte.d.ts +5 -0
- package/dist/ui/components/primitives/card/card-title.svelte +25 -0
- package/dist/ui/components/primitives/card/card-title.svelte.d.ts +8 -0
- package/dist/ui/components/primitives/card/card.svelte +20 -0
- package/dist/ui/components/primitives/card/card.svelte.d.ts +5 -0
- package/dist/ui/components/primitives/card/index.d.ts +7 -0
- package/dist/ui/components/primitives/card/index.js +9 -0
- package/dist/{components/ui → ui/components/primitives}/dialog/dialog-content.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/dialog/dialog-description.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/dialog/dialog-footer.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/dialog/dialog-header.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/dialog/dialog-overlay.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/dialog/dialog-title.svelte +1 -1
- package/dist/ui/components/primitives/input/index.d.ts +2 -0
- package/dist/ui/components/primitives/input/index.js +4 -0
- package/dist/ui/components/primitives/input/input.svelte +46 -0
- package/dist/ui/components/primitives/input/input.svelte.d.ts +13 -0
- package/dist/{components/ui → ui/components/primitives}/select/select-content.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/select/select-group-heading.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/select/select-item.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/select/select-scroll-down-button.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/select/select-scroll-up-button.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/select/select-separator.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/select/select-trigger.svelte +1 -1
- package/dist/ui/components/primitives/separator/index.d.ts +2 -0
- package/dist/ui/components/primitives/separator/index.js +4 -0
- package/dist/ui/components/primitives/separator/separator.svelte +22 -0
- package/dist/ui/components/primitives/separator/separator.svelte.d.ts +4 -0
- package/dist/{components/ui → ui/components/primitives}/sheet/sheet-content.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/sheet/sheet-description.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/sheet/sheet-footer.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/sheet/sheet-header.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/sheet/sheet-overlay.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/sheet/sheet-title.svelte +1 -1
- package/dist/ui/components/primitives/skeleton/index.d.ts +2 -0
- package/dist/ui/components/primitives/skeleton/index.js +4 -0
- package/dist/ui/components/primitives/skeleton/skeleton.svelte +17 -0
- package/dist/ui/components/primitives/skeleton/skeleton.svelte.d.ts +5 -0
- package/dist/{components/ui → ui/components/primitives}/table/table-body.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/table/table-caption.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/table/table-cell.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/table/table-footer.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/table/table-head.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/table/table-header.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/table/table-row.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/table/table.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/tabs/tabs-content.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/tabs/tabs-list.svelte +1 -1
- package/dist/{components/ui → ui/components/primitives}/tabs/tabs-trigger.svelte +1 -1
- package/dist/ui/components/primitives/textarea/index.d.ts +2 -0
- package/dist/ui/components/primitives/textarea/index.js +4 -0
- package/dist/ui/components/primitives/textarea/textarea.svelte +24 -0
- package/dist/ui/components/primitives/textarea/textarea.svelte.d.ts +6 -0
- package/dist/ui/components/states/EmptyState.svelte +28 -0
- package/dist/ui/components/states/EmptyState.svelte.d.ts +10 -0
- package/dist/ui/components/states/Loading.svelte +62 -0
- package/dist/ui/components/states/Loading.svelte.d.ts +7 -0
- package/dist/ui/components/states/LoadingSkeleton.svelte +46 -0
- package/dist/ui/components/states/LoadingSkeleton.svelte.d.ts +8 -0
- package/dist/ui/components/states/ThemeToggle.svelte +138 -0
- package/dist/ui/components/states/ThemeToggle.svelte.d.ts +6 -0
- package/dist/ui/components/states/index.d.ts +5 -0
- package/dist/ui/components/states/index.js +11 -0
- package/dist/{components → ui/components}/ui/Accordion.svelte +1 -1
- package/dist/ui/components/ui/Badge.svelte +52 -0
- package/dist/ui/components/ui/Badge.svelte.d.ts +28 -0
- package/dist/ui/components/ui/Button.svelte +77 -0
- package/dist/ui/components/ui/Button.svelte.d.ts +34 -0
- package/dist/ui/components/ui/Card.svelte +102 -0
- package/dist/ui/components/ui/Card.svelte.d.ts +46 -0
- package/dist/ui/components/ui/CollapsibleSection.svelte +65 -0
- package/dist/ui/components/ui/CollapsibleSection.svelte.d.ts +10 -0
- package/dist/{components → ui/components}/ui/Dialog.svelte +1 -1
- package/dist/ui/components/ui/Input.svelte +81 -0
- package/dist/ui/components/ui/Input.svelte.d.ts +35 -0
- package/dist/{components → ui/components}/ui/Select.svelte +1 -1
- package/dist/{components → ui/components}/ui/Sheet.svelte +1 -1
- package/dist/ui/components/ui/Skeleton.svelte +31 -0
- package/dist/ui/components/ui/Skeleton.svelte.d.ts +26 -0
- package/dist/ui/components/ui/Spinner.svelte +45 -0
- package/dist/ui/components/ui/Spinner.svelte.d.ts +15 -0
- package/dist/{components → ui/components}/ui/Table.svelte +2 -2
- package/dist/{components → ui/components}/ui/Table.svelte.d.ts +1 -1
- package/dist/{components → ui/components}/ui/Tabs.svelte +2 -2
- package/dist/ui/components/ui/Textarea.svelte +81 -0
- package/dist/ui/components/ui/Textarea.svelte.d.ts +35 -0
- package/dist/ui/components/ui/index.d.ts +18 -0
- package/dist/ui/components/ui/index.js +28 -0
- package/dist/{components → ui/components}/ui/toast.d.ts +1 -1
- package/dist/{components → ui/components}/ui/toast.js +1 -1
- package/dist/ui/index.d.ts +10 -0
- package/dist/ui/index.js +22 -0
- package/dist/ui/stores/theme.d.ts +12 -0
- package/dist/ui/stores/theme.js +52 -0
- package/dist/ui/styles/content.css +514 -0
- package/dist/ui/styles/grove.css +715 -0
- package/dist/ui/styles/tokens.css +429 -0
- package/dist/ui/tailwind.preset.d.ts +340 -0
- package/dist/ui/tailwind.preset.js +441 -0
- package/dist/ui/tokens/animation.d.ts +417 -0
- package/dist/ui/tokens/animation.js +139 -0
- package/dist/ui/tokens/colors.d.ts +183 -0
- package/dist/ui/tokens/colors.js +97 -0
- package/dist/ui/tokens/effects.d.ts +111 -0
- package/dist/ui/tokens/effects.js +61 -0
- package/dist/ui/tokens/index.d.ts +6 -0
- package/dist/ui/tokens/index.js +19 -0
- package/dist/ui/tokens/spacing.d.ts +89 -0
- package/dist/ui/tokens/spacing.js +49 -0
- package/dist/ui/tokens/typography.d.ts +85 -0
- package/dist/ui/tokens/typography.js +68 -0
- package/dist/ui/utils/cn.d.ts +13 -0
- package/dist/ui/utils/cn.js +24 -0
- package/dist/ui/utils/index.d.ts +2 -0
- package/dist/ui/utils/index.js +8 -0
- package/dist/utils/index.d.ts +11 -0
- package/dist/utils/index.js +14 -0
- package/dist/utils/markdown.d.ts +11 -0
- package/dist/utils/markdown.js +25 -0
- package/dist/utils/sanitize.js +2 -3
- package/package.json +92 -31
- package/dist/components/ui/index.d.ts +0 -14
- package/dist/components/ui/index.js +0 -18
- /package/dist/{components → ui/components}/gallery/ImageGallery.svelte +0 -0
- /package/dist/{components → ui/components}/gallery/ImageGallery.svelte.d.ts +0 -0
- /package/dist/{components → ui/components}/gallery/Lightbox.svelte +0 -0
- /package/dist/{components → ui/components}/gallery/Lightbox.svelte.d.ts +0 -0
- /package/dist/{components → ui/components}/gallery/LightboxCaption.svelte +0 -0
- /package/dist/{components → ui/components}/gallery/LightboxCaption.svelte.d.ts +0 -0
- /package/dist/{components → ui/components}/gallery/ZoomableImage.svelte +0 -0
- /package/dist/{components → ui/components}/gallery/ZoomableImage.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/accordion/accordion-content.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/accordion/accordion-item.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/accordion/accordion-trigger.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/accordion/index.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/accordion/index.js +0 -0
- /package/dist/{components/ui → ui/components/primitives}/dialog/dialog-content.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/dialog/dialog-description.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/dialog/dialog-footer.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/dialog/dialog-header.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/dialog/dialog-overlay.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/dialog/dialog-title.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/dialog/index.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/dialog/index.js +0 -0
- /package/dist/{components/ui → ui/components/primitives}/select/index.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/select/index.js +0 -0
- /package/dist/{components/ui → ui/components/primitives}/select/select-content.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/select/select-group-heading.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/select/select-item.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/select/select-scroll-down-button.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/select/select-scroll-up-button.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/select/select-separator.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/select/select-trigger.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/sheet/index.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/sheet/index.js +0 -0
- /package/dist/{components/ui → ui/components/primitives}/sheet/sheet-content.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/sheet/sheet-description.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/sheet/sheet-footer.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/sheet/sheet-header.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/sheet/sheet-overlay.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/sheet/sheet-title.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/table/index.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/table/index.js +0 -0
- /package/dist/{components/ui → ui/components/primitives}/table/table-body.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/table/table-caption.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/table/table-cell.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/table/table-footer.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/table/table-head.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/table/table-header.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/table/table-row.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/table/table.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/tabs/index.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/tabs/index.js +0 -0
- /package/dist/{components/ui → ui/components/primitives}/tabs/tabs-content.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/tabs/tabs-list.svelte.d.ts +0 -0
- /package/dist/{components/ui → ui/components/primitives}/tabs/tabs-trigger.svelte.d.ts +0 -0
- /package/dist/{components → ui/components}/ui/Accordion.svelte.d.ts +0 -0
- /package/dist/{components → ui/components}/ui/Dialog.svelte.d.ts +0 -0
- /package/dist/{components → ui/components}/ui/Select.svelte.d.ts +0 -0
- /package/dist/{components → ui/components}/ui/Sheet.svelte.d.ts +0 -0
- /package/dist/{components → ui/components}/ui/Tabs.svelte.d.ts +0 -0
- /package/dist/{components → ui/components}/ui/Toast.svelte +0 -0
- /package/dist/{components → ui/components}/ui/Toast.svelte.d.ts +0 -0
|
@@ -3,8 +3,21 @@
|
|
|
3
3
|
import { onMount, tick } from "svelte";
|
|
4
4
|
import { sanitizeMarkdown } from "../../utils/sanitize.js";
|
|
5
5
|
import "../../styles/content.css";
|
|
6
|
-
import { Button, Input } from '
|
|
7
|
-
import Dialog from "
|
|
6
|
+
import { Button, Input } from '../../ui';
|
|
7
|
+
import Dialog from "../../ui/components/ui/Dialog.svelte";
|
|
8
|
+
|
|
9
|
+
// Import composables
|
|
10
|
+
import {
|
|
11
|
+
useAmbientSounds,
|
|
12
|
+
soundLibrary,
|
|
13
|
+
useEditorTheme,
|
|
14
|
+
themes,
|
|
15
|
+
useSnippets,
|
|
16
|
+
useDraftManager,
|
|
17
|
+
useWritingSession,
|
|
18
|
+
useSlashCommands,
|
|
19
|
+
useCommandPalette,
|
|
20
|
+
} from "./composables/index.js";
|
|
8
21
|
|
|
9
22
|
// Props
|
|
10
23
|
let {
|
|
@@ -12,19 +25,18 @@
|
|
|
12
25
|
onSave = () => {},
|
|
13
26
|
saving = false,
|
|
14
27
|
readonly = false,
|
|
15
|
-
draftKey = null,
|
|
16
|
-
onDraftRestored = () => {},
|
|
17
|
-
// Optional metadata for full preview mode
|
|
28
|
+
draftKey = null,
|
|
29
|
+
onDraftRestored = () => {},
|
|
18
30
|
previewTitle = "",
|
|
19
31
|
previewDate = "",
|
|
20
32
|
previewTags = [],
|
|
21
33
|
} = $props();
|
|
22
34
|
|
|
23
|
-
//
|
|
35
|
+
// Core refs and state
|
|
24
36
|
let textareaRef = $state(null);
|
|
25
37
|
let previewRef = $state(null);
|
|
38
|
+
let lineNumbersRef = $state(null);
|
|
26
39
|
let showPreview = $state(true);
|
|
27
|
-
let lineNumbers = $state([]);
|
|
28
40
|
let cursorLine = $state(1);
|
|
29
41
|
let cursorCol = $state(1);
|
|
30
42
|
|
|
@@ -34,18 +46,10 @@
|
|
|
34
46
|
let uploadProgress = $state("");
|
|
35
47
|
let uploadError = $state(null);
|
|
36
48
|
|
|
37
|
-
//
|
|
38
|
-
let lastSavedContent = $state("");
|
|
39
|
-
let draftSaveTimer = $state(null);
|
|
40
|
-
let hasDraft = $state(false);
|
|
41
|
-
let draftRestorePrompt = $state(false);
|
|
42
|
-
let storedDraft = $state(null);
|
|
43
|
-
const AUTO_SAVE_DELAY = 2000; // 2 seconds
|
|
44
|
-
|
|
45
|
-
// Full preview mode state
|
|
49
|
+
// Full preview mode
|
|
46
50
|
let showFullPreview = $state(false);
|
|
47
51
|
|
|
48
|
-
// Editor settings
|
|
52
|
+
// Editor settings
|
|
49
53
|
let editorSettings = $state({
|
|
50
54
|
typewriterMode: false,
|
|
51
55
|
zenMode: false,
|
|
@@ -53,262 +57,87 @@
|
|
|
53
57
|
wordWrap: true,
|
|
54
58
|
});
|
|
55
59
|
|
|
56
|
-
// Zen mode
|
|
60
|
+
// Zen mode
|
|
57
61
|
let isZenMode = $state(false);
|
|
58
62
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
targetMinutes: 25,
|
|
64
|
-
startWordCount: 0,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// Writing goals
|
|
68
|
-
let writingGoal = $state({
|
|
69
|
-
enabled: false,
|
|
70
|
-
targetWords: 500,
|
|
71
|
-
sessionWords: 0,
|
|
72
|
-
});
|
|
63
|
+
// Initialize composables
|
|
64
|
+
const ambientSounds = useAmbientSounds();
|
|
65
|
+
const editorTheme = useEditorTheme();
|
|
66
|
+
const snippetsManager = useSnippets();
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
open: false,
|
|
77
|
-
query: "",
|
|
78
|
-
position: { x: 0, y: 0 },
|
|
79
|
-
selectedIndex: 0,
|
|
68
|
+
const writingSession = useWritingSession({
|
|
69
|
+
getWordCount: () => wordCount,
|
|
80
70
|
});
|
|
81
71
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
72
|
+
const draftManager = useDraftManager({
|
|
73
|
+
draftKey,
|
|
74
|
+
getContent: () => content,
|
|
75
|
+
setContent: (c) => (content = c),
|
|
76
|
+
onDraftRestored,
|
|
77
|
+
readonly,
|
|
87
78
|
});
|
|
88
79
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
80
|
+
const slashCommands = useSlashCommands({
|
|
81
|
+
getTextareaRef: () => textareaRef,
|
|
82
|
+
getContent: () => content,
|
|
83
|
+
setContent: (c) => (content = c),
|
|
84
|
+
getSnippets: () => snippetsManager.snippets,
|
|
85
|
+
onOpenSnippetsModal: () => snippetsManager.openModal(),
|
|
95
86
|
});
|
|
96
87
|
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
88
|
+
// Command palette actions
|
|
89
|
+
const basePaletteActions = [
|
|
90
|
+
{ id: "save", label: "Save", shortcut: "⌘S", action: () => onSave() },
|
|
91
|
+
{ id: "preview", label: "Toggle Preview", shortcut: "", action: () => (showPreview = !showPreview) },
|
|
92
|
+
{ id: "fullPreview", label: "Full Preview", shortcut: "", action: () => (showFullPreview = true) },
|
|
93
|
+
{ id: "zen", label: "Toggle Zen Mode", shortcut: "⌘⇧↵", action: () => toggleZenMode() },
|
|
94
|
+
{ id: "campfire", label: "Start Campfire Session", shortcut: "", action: () => writingSession.startCampfire() },
|
|
95
|
+
{ id: "bold", label: "Bold", shortcut: "⌘B", action: () => wrapSelection("**", "**") },
|
|
96
|
+
{ id: "italic", label: "Italic", shortcut: "⌘I", action: () => wrapSelection("_", "_") },
|
|
97
|
+
{ id: "code", label: "Insert Code Block", shortcut: "", action: () => insertCodeBlock() },
|
|
98
|
+
{ id: "link", label: "Insert Link", shortcut: "", action: () => insertLink() },
|
|
99
|
+
{ id: "image", label: "Insert Image", shortcut: "", action: () => insertImage() },
|
|
100
|
+
{ id: "goal", label: "Set Writing Goal", shortcut: "", action: () => writingSession.promptWritingGoal() },
|
|
101
|
+
{ id: "snippets", label: "Manage Snippets", shortcut: "", action: () => snippetsManager.openModal() },
|
|
102
|
+
{ id: "newSnippet", label: "Create New Snippet", shortcut: "", action: () => snippetsManager.openModal() },
|
|
103
|
+
{ id: "sounds", label: "Toggle Ambient Sounds", shortcut: "", action: () => ambientSounds.toggle() },
|
|
104
|
+
{ id: "soundPanel", label: "Sound Settings", shortcut: "", action: () => ambientSounds.togglePanel() },
|
|
105
|
+
];
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
volume: 0.3,
|
|
112
|
-
showPanel: false,
|
|
107
|
+
const commandPalette = useCommandPalette({
|
|
108
|
+
getActions: () => basePaletteActions,
|
|
109
|
+
getThemes: () => themes,
|
|
110
|
+
getCurrentTheme: () => editorTheme.currentTheme,
|
|
113
111
|
});
|
|
114
|
-
let audioElement = $state(null);
|
|
115
|
-
|
|
116
|
-
// Theme system
|
|
117
|
-
const themes = {
|
|
118
|
-
grove: {
|
|
119
|
-
name: "grove",
|
|
120
|
-
label: "Grove",
|
|
121
|
-
desc: "forest green",
|
|
122
|
-
accent: "#8bc48b",
|
|
123
|
-
accentDim: "#7a9a7a",
|
|
124
|
-
accentBright: "#a8dca8",
|
|
125
|
-
accentGlow: "#c8f0c8",
|
|
126
|
-
bg: "#1e1e1e",
|
|
127
|
-
bgSecondary: "#252526",
|
|
128
|
-
bgTertiary: "#1a1a1a",
|
|
129
|
-
border: "#3a3a3a",
|
|
130
|
-
borderAccent: "#4a7c4a",
|
|
131
|
-
text: "#d4d4d4",
|
|
132
|
-
textDim: "#9d9d9d",
|
|
133
|
-
statusBg: "#2d4a2d",
|
|
134
|
-
statusBorder: "#3d5a3d",
|
|
135
|
-
},
|
|
136
|
-
amber: {
|
|
137
|
-
name: "amber",
|
|
138
|
-
label: "Amber",
|
|
139
|
-
desc: "classic terminal",
|
|
140
|
-
accent: "#ffb000",
|
|
141
|
-
accentDim: "#c98b00",
|
|
142
|
-
accentBright: "#ffc940",
|
|
143
|
-
accentGlow: "#ffe080",
|
|
144
|
-
bg: "#1a1400",
|
|
145
|
-
bgSecondary: "#241c00",
|
|
146
|
-
bgTertiary: "#140e00",
|
|
147
|
-
border: "#3a3000",
|
|
148
|
-
borderAccent: "#5a4800",
|
|
149
|
-
text: "#ffcc66",
|
|
150
|
-
textDim: "#aa8844",
|
|
151
|
-
statusBg: "#2a2000",
|
|
152
|
-
statusBorder: "#3a3000",
|
|
153
|
-
},
|
|
154
|
-
matrix: {
|
|
155
|
-
name: "matrix",
|
|
156
|
-
label: "Matrix",
|
|
157
|
-
desc: "digital rain",
|
|
158
|
-
accent: "#00ff00",
|
|
159
|
-
accentDim: "#00aa00",
|
|
160
|
-
accentBright: "#44ff44",
|
|
161
|
-
accentGlow: "#88ff88",
|
|
162
|
-
bg: "#0a0a0a",
|
|
163
|
-
bgSecondary: "#111111",
|
|
164
|
-
bgTertiary: "#050505",
|
|
165
|
-
border: "#1a3a1a",
|
|
166
|
-
borderAccent: "#00aa00",
|
|
167
|
-
text: "#00dd00",
|
|
168
|
-
textDim: "#008800",
|
|
169
|
-
statusBg: "#0a1a0a",
|
|
170
|
-
statusBorder: "#1a3a1a",
|
|
171
|
-
},
|
|
172
|
-
dracula: {
|
|
173
|
-
name: "dracula",
|
|
174
|
-
label: "Dracula",
|
|
175
|
-
desc: "purple night",
|
|
176
|
-
accent: "#bd93f9",
|
|
177
|
-
accentDim: "#9580c9",
|
|
178
|
-
accentBright: "#d4b0ff",
|
|
179
|
-
accentGlow: "#e8d0ff",
|
|
180
|
-
bg: "#282a36",
|
|
181
|
-
bgSecondary: "#343746",
|
|
182
|
-
bgTertiary: "#21222c",
|
|
183
|
-
border: "#44475a",
|
|
184
|
-
borderAccent: "#6272a4",
|
|
185
|
-
text: "#f8f8f2",
|
|
186
|
-
textDim: "#a0a0a0",
|
|
187
|
-
statusBg: "#3a3c4e",
|
|
188
|
-
statusBorder: "#44475a",
|
|
189
|
-
},
|
|
190
|
-
nord: {
|
|
191
|
-
name: "nord",
|
|
192
|
-
label: "Nord",
|
|
193
|
-
desc: "arctic frost",
|
|
194
|
-
accent: "#88c0d0",
|
|
195
|
-
accentDim: "#6a9aa8",
|
|
196
|
-
accentBright: "#a3d4e2",
|
|
197
|
-
accentGlow: "#c0e8f0",
|
|
198
|
-
bg: "#2e3440",
|
|
199
|
-
bgSecondary: "#3b4252",
|
|
200
|
-
bgTertiary: "#272c36",
|
|
201
|
-
border: "#434c5e",
|
|
202
|
-
borderAccent: "#5e81ac",
|
|
203
|
-
text: "#eceff4",
|
|
204
|
-
textDim: "#a0a8b0",
|
|
205
|
-
statusBg: "#3b4252",
|
|
206
|
-
statusBorder: "#434c5e",
|
|
207
|
-
},
|
|
208
|
-
rose: {
|
|
209
|
-
name: "rose",
|
|
210
|
-
label: "Rose",
|
|
211
|
-
desc: "soft pink",
|
|
212
|
-
accent: "#f5a9b8",
|
|
213
|
-
accentDim: "#c98a96",
|
|
214
|
-
accentBright: "#ffccd5",
|
|
215
|
-
accentGlow: "#ffe0e6",
|
|
216
|
-
bg: "#1f1a1b",
|
|
217
|
-
bgSecondary: "#2a2224",
|
|
218
|
-
bgTertiary: "#171314",
|
|
219
|
-
border: "#3a3234",
|
|
220
|
-
borderAccent: "#5a4a4e",
|
|
221
|
-
text: "#e8d8dc",
|
|
222
|
-
textDim: "#a09498",
|
|
223
|
-
statusBg: "#2a2224",
|
|
224
|
-
statusBorder: "#3a3234",
|
|
225
|
-
},
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
let currentTheme = $state("grove");
|
|
229
|
-
const THEME_STORAGE_KEY = "grove-editor-theme";
|
|
230
|
-
|
|
231
|
-
// Sound definitions with free ambient loops
|
|
232
|
-
const soundLibrary = {
|
|
233
|
-
forest: {
|
|
234
|
-
name: "forest",
|
|
235
|
-
key: "f",
|
|
236
|
-
// Using freesound.org URLs for ambient sounds (CC0 licensed)
|
|
237
|
-
// These are placeholder paths - user can provide their own audio files
|
|
238
|
-
url: "/sounds/forest-ambience.mp3",
|
|
239
|
-
description: "birds, wind",
|
|
240
|
-
},
|
|
241
|
-
rain: {
|
|
242
|
-
name: "rain",
|
|
243
|
-
key: "r",
|
|
244
|
-
url: "/sounds/rain-ambience.mp3",
|
|
245
|
-
description: "gentle rainfall",
|
|
246
|
-
},
|
|
247
|
-
campfire: {
|
|
248
|
-
name: "fire",
|
|
249
|
-
key: "i",
|
|
250
|
-
url: "/sounds/campfire-ambience.mp3",
|
|
251
|
-
description: "crackling embers",
|
|
252
|
-
},
|
|
253
|
-
night: {
|
|
254
|
-
name: "night",
|
|
255
|
-
key: "n",
|
|
256
|
-
url: "/sounds/night-ambience.mp3",
|
|
257
|
-
description: "crickets, breeze",
|
|
258
|
-
},
|
|
259
|
-
cafe: {
|
|
260
|
-
name: "cafe",
|
|
261
|
-
key: "a",
|
|
262
|
-
url: "/sounds/cafe-ambience.mp3",
|
|
263
|
-
description: "soft murmurs",
|
|
264
|
-
},
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
// Line numbers container ref for scroll sync
|
|
268
|
-
let lineNumbersRef = $state(null);
|
|
269
112
|
|
|
270
113
|
// Computed values
|
|
271
|
-
let wordCount = $derived(
|
|
272
|
-
content.trim() ? content.trim().split(/\s+/).length : 0
|
|
273
|
-
);
|
|
114
|
+
let wordCount = $derived(content.trim() ? content.trim().split(/\s+/).length : 0);
|
|
274
115
|
let charCount = $derived(content.length);
|
|
275
116
|
let lineCount = $derived(content.split("\n").length);
|
|
276
|
-
|
|
277
117
|
let previewHtml = $derived(content ? sanitizeMarkdown(marked.parse(content)) : "");
|
|
278
118
|
|
|
279
|
-
|
|
280
|
-
let readingTime = $derived(() => {
|
|
119
|
+
let readingTime = $derived.by(() => {
|
|
281
120
|
const minutes = Math.ceil(wordCount / 200);
|
|
282
121
|
return minutes < 1 ? "< 1 min" : `~${minutes} min read`;
|
|
283
122
|
});
|
|
284
123
|
|
|
285
|
-
|
|
286
|
-
let goalProgress = $derived(() => {
|
|
287
|
-
if (!writingGoal.enabled) return 0;
|
|
288
|
-
const wordsWritten = wordCount - writingGoal.sessionWords;
|
|
289
|
-
return Math.min(100, Math.round((wordsWritten / writingGoal.targetWords) * 100));
|
|
290
|
-
});
|
|
124
|
+
let goalProgress = $derived.by(() => writingSession.getGoalProgress(wordCount));
|
|
291
125
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
const mins = Math.floor(elapsed / 60);
|
|
298
|
-
const secs = elapsed % 60;
|
|
299
|
-
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
126
|
+
let campfireElapsed = $derived.by(() => writingSession.getCampfireElapsed());
|
|
127
|
+
|
|
128
|
+
let lineNumbers = $derived.by(() => {
|
|
129
|
+
const count = content.split("\n").length;
|
|
130
|
+
return Array.from({ length: count }, (_, i) => i + 1);
|
|
300
131
|
});
|
|
301
132
|
|
|
302
|
-
// Extract available anchors from content
|
|
133
|
+
// Extract available anchors from content
|
|
303
134
|
let availableAnchors = $derived.by(() => {
|
|
304
135
|
const anchors = [];
|
|
305
|
-
// Extract headings
|
|
306
136
|
const headingRegex = /^(#{1,6})\s+(.+)$/gm;
|
|
307
137
|
let match;
|
|
308
138
|
while ((match = headingRegex.exec(content)) !== null) {
|
|
309
139
|
anchors.push(match[0].trim());
|
|
310
140
|
}
|
|
311
|
-
// Extract custom anchors
|
|
312
141
|
const anchorRegex = /<!--\s*anchor:([\w-]+)\s*-->/g;
|
|
313
142
|
while ((match = anchorRegex.exec(content)) !== null) {
|
|
314
143
|
anchors.push(`anchor:${match[1]}`);
|
|
@@ -316,26 +145,46 @@
|
|
|
316
145
|
return anchors;
|
|
317
146
|
});
|
|
318
147
|
|
|
319
|
-
//
|
|
148
|
+
// Filtered commands for UI
|
|
149
|
+
let filteredSlashCommands = $derived.by(() => slashCommands.getFilteredCommands());
|
|
150
|
+
let filteredPaletteCommands = $derived.by(() => {
|
|
151
|
+
// Include theme actions that actually set the theme
|
|
152
|
+
const actions = basePaletteActions.filter((cmd) =>
|
|
153
|
+
cmd.label.toLowerCase().includes(commandPalette.query.toLowerCase())
|
|
154
|
+
);
|
|
155
|
+
const themeCommands = Object.entries(themes)
|
|
156
|
+
.filter(([key, theme]) =>
|
|
157
|
+
`Theme: ${theme.label} (${theme.desc})`.toLowerCase().includes(commandPalette.query.toLowerCase())
|
|
158
|
+
)
|
|
159
|
+
.map(([key, theme]) => ({
|
|
160
|
+
id: `theme-${key}`,
|
|
161
|
+
label: `Theme: ${theme.label} (${theme.desc})`,
|
|
162
|
+
shortcut: editorTheme.currentTheme === key ? "●" : "",
|
|
163
|
+
action: () => editorTheme.setTheme(key),
|
|
164
|
+
}));
|
|
165
|
+
return [...actions, ...themeCommands];
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Public exports
|
|
320
169
|
export function getAvailableAnchors() {
|
|
321
170
|
return availableAnchors;
|
|
322
171
|
}
|
|
323
172
|
|
|
324
|
-
// Public function to insert an anchor at cursor position
|
|
325
173
|
export function insertAnchor(name) {
|
|
326
174
|
insertAtCursor(`<!-- anchor:${name} -->\n`);
|
|
327
175
|
}
|
|
328
176
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
lineNumbers = Array.from({ length: lines }, (_, i) => i + 1);
|
|
333
|
-
});
|
|
177
|
+
export function clearDraft() {
|
|
178
|
+
draftManager.clearDraft();
|
|
179
|
+
}
|
|
334
180
|
|
|
335
|
-
|
|
181
|
+
export function getDraftStatus() {
|
|
182
|
+
return draftManager.getStatus();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Cursor position tracking
|
|
336
186
|
function updateCursorPosition() {
|
|
337
187
|
if (!textareaRef) return;
|
|
338
|
-
|
|
339
188
|
const pos = textareaRef.selectionStart;
|
|
340
189
|
const textBefore = content.substring(0, pos);
|
|
341
190
|
const lines = textBefore.split("\n");
|
|
@@ -343,16 +192,16 @@
|
|
|
343
192
|
cursorCol = lines[lines.length - 1].length + 1;
|
|
344
193
|
}
|
|
345
194
|
|
|
346
|
-
//
|
|
195
|
+
// Keyboard handlers
|
|
347
196
|
function handleKeydown(e) {
|
|
348
197
|
// Escape key handling
|
|
349
198
|
if (e.key === "Escape") {
|
|
350
|
-
if (
|
|
351
|
-
|
|
199
|
+
if (slashCommands.isOpen) {
|
|
200
|
+
slashCommands.close();
|
|
352
201
|
return;
|
|
353
202
|
}
|
|
354
|
-
if (commandPalette.
|
|
355
|
-
commandPalette.
|
|
203
|
+
if (commandPalette.isOpen) {
|
|
204
|
+
commandPalette.close();
|
|
356
205
|
return;
|
|
357
206
|
}
|
|
358
207
|
if (isZenMode) {
|
|
@@ -362,46 +211,38 @@
|
|
|
362
211
|
}
|
|
363
212
|
|
|
364
213
|
// Slash commands trigger
|
|
365
|
-
if (e.key === "/" && !
|
|
214
|
+
if (e.key === "/" && !slashCommands.isOpen) {
|
|
366
215
|
const pos = textareaRef.selectionStart;
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (pos === 0 || /\s$/.test(textBefore)) {
|
|
370
|
-
// Don't prevent default yet - let the slash be typed
|
|
371
|
-
setTimeout(() => {
|
|
372
|
-
openSlashMenu();
|
|
373
|
-
}, 0);
|
|
216
|
+
if (slashCommands.shouldTrigger("/", pos, content)) {
|
|
217
|
+
setTimeout(() => slashCommands.open(), 0);
|
|
374
218
|
}
|
|
375
219
|
}
|
|
376
220
|
|
|
377
|
-
// Close slash menu on space or enter
|
|
378
|
-
if (
|
|
221
|
+
// Close slash menu on space or enter
|
|
222
|
+
if (slashCommands.isOpen && (e.key === " " || e.key === "Enter")) {
|
|
379
223
|
if (e.key === "Enter") {
|
|
380
224
|
e.preventDefault();
|
|
381
|
-
|
|
225
|
+
slashCommands.execute(slashCommands.menu.selectedIndex);
|
|
382
226
|
}
|
|
383
|
-
|
|
227
|
+
slashCommands.close();
|
|
384
228
|
}
|
|
385
229
|
|
|
386
230
|
// Navigate slash menu
|
|
387
|
-
if (
|
|
388
|
-
const cmdCount = filteredSlashCommands.length;
|
|
231
|
+
if (slashCommands.isOpen) {
|
|
389
232
|
if (e.key === "ArrowDown") {
|
|
390
233
|
e.preventDefault();
|
|
391
|
-
|
|
234
|
+
slashCommands.navigate("down");
|
|
392
235
|
}
|
|
393
236
|
if (e.key === "ArrowUp") {
|
|
394
237
|
e.preventDefault();
|
|
395
|
-
|
|
238
|
+
slashCommands.navigate("up");
|
|
396
239
|
}
|
|
397
240
|
}
|
|
398
241
|
|
|
399
242
|
// Command palette: Cmd+K
|
|
400
243
|
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
|
401
244
|
e.preventDefault();
|
|
402
|
-
commandPalette.
|
|
403
|
-
commandPalette.query = "";
|
|
404
|
-
commandPalette.selectedIndex = 0;
|
|
245
|
+
commandPalette.toggle();
|
|
405
246
|
}
|
|
406
247
|
|
|
407
248
|
// Zen mode: Cmd+Shift+Enter
|
|
@@ -410,15 +251,12 @@
|
|
|
410
251
|
toggleZenMode();
|
|
411
252
|
}
|
|
412
253
|
|
|
254
|
+
// Tab for indentation
|
|
413
255
|
if (e.key === "Tab") {
|
|
414
256
|
e.preventDefault();
|
|
415
257
|
const start = textareaRef.selectionStart;
|
|
416
258
|
const end = textareaRef.selectionEnd;
|
|
417
|
-
|
|
418
|
-
// Insert 2 spaces
|
|
419
259
|
content = content.substring(0, start) + " " + content.substring(end);
|
|
420
|
-
|
|
421
|
-
// Move cursor
|
|
422
260
|
setTimeout(() => {
|
|
423
261
|
textareaRef.selectionStart = textareaRef.selectionEnd = start + 2;
|
|
424
262
|
}, 0);
|
|
@@ -443,16 +281,15 @@
|
|
|
443
281
|
}
|
|
444
282
|
}
|
|
445
283
|
|
|
446
|
-
// Global keyboard handler for modals
|
|
447
284
|
function handleGlobalKeydown(e) {
|
|
448
285
|
if (e.key === "Escape") {
|
|
449
286
|
if (ambientSounds.showPanel) {
|
|
450
|
-
ambientSounds.
|
|
287
|
+
ambientSounds.closePanel();
|
|
451
288
|
e.preventDefault();
|
|
452
289
|
return;
|
|
453
290
|
}
|
|
454
|
-
if (
|
|
455
|
-
|
|
291
|
+
if (snippetsManager.modal.open) {
|
|
292
|
+
snippetsManager.closeModal();
|
|
456
293
|
e.preventDefault();
|
|
457
294
|
return;
|
|
458
295
|
}
|
|
@@ -463,126 +300,6 @@
|
|
|
463
300
|
}
|
|
464
301
|
}
|
|
465
302
|
|
|
466
|
-
// Slash commands definition
|
|
467
|
-
const slashCommands = [
|
|
468
|
-
{ id: "heading1", label: "Heading 1", insert: "# " },
|
|
469
|
-
{ id: "heading2", label: "Heading 2", insert: "## " },
|
|
470
|
-
{ id: "heading3", label: "Heading 3", insert: "### " },
|
|
471
|
-
{ id: "code", label: "Code Block", insert: "```\n\n```", cursorOffset: 4 },
|
|
472
|
-
{ id: "quote", label: "Quote", insert: "> " },
|
|
473
|
-
{ id: "list", label: "Bullet List", insert: "- " },
|
|
474
|
-
{ id: "numbered", label: "Numbered List", insert: "1. " },
|
|
475
|
-
{ id: "link", label: "Link", insert: "[](url)", cursorOffset: 1 },
|
|
476
|
-
{ id: "image", label: "Image", insert: "", cursorOffset: 2 },
|
|
477
|
-
{ id: "divider", label: "Divider", insert: "\n---\n" },
|
|
478
|
-
{ id: "anchor", label: "Custom Anchor", insert: "<!-- anchor:name -->\n", cursorOffset: 14 },
|
|
479
|
-
{ id: "newSnippet", label: "Create New Snippet...", insert: "", isAction: true, action: () => openSnippetsModal() },
|
|
480
|
-
];
|
|
481
|
-
|
|
482
|
-
// Dynamic slash commands including user snippets
|
|
483
|
-
let allSlashCommands = $derived(() => {
|
|
484
|
-
const snippetCommands = snippets.map(s => ({
|
|
485
|
-
id: s.id,
|
|
486
|
-
label: `> ${s.name}`,
|
|
487
|
-
insert: s.content,
|
|
488
|
-
isSnippet: true,
|
|
489
|
-
}));
|
|
490
|
-
return [...slashCommands, ...snippetCommands];
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
// Filtered slash commands based on query
|
|
494
|
-
let filteredSlashCommands = $derived(
|
|
495
|
-
allSlashCommands().filter(cmd =>
|
|
496
|
-
cmd.label.toLowerCase().includes(slashMenu.query.toLowerCase())
|
|
497
|
-
)
|
|
498
|
-
);
|
|
499
|
-
|
|
500
|
-
function openSlashMenu() {
|
|
501
|
-
slashMenu.open = true;
|
|
502
|
-
slashMenu.query = "";
|
|
503
|
-
slashMenu.selectedIndex = 0;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function executeSlashCommand(index) {
|
|
507
|
-
const cmd = filteredSlashCommands[index];
|
|
508
|
-
if (!cmd) return;
|
|
509
|
-
|
|
510
|
-
// Handle action commands (like "Create New Snippet...")
|
|
511
|
-
if (cmd.isAction && cmd.action) {
|
|
512
|
-
// Remove the slash that triggered the menu
|
|
513
|
-
const pos = textareaRef.selectionStart;
|
|
514
|
-
const textBefore = content.substring(0, pos);
|
|
515
|
-
const lastSlashIndex = textBefore.lastIndexOf("/");
|
|
516
|
-
if (lastSlashIndex >= 0) {
|
|
517
|
-
content = content.substring(0, lastSlashIndex) + content.substring(pos);
|
|
518
|
-
}
|
|
519
|
-
slashMenu.open = false;
|
|
520
|
-
cmd.action();
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// Remove the slash that triggered the menu
|
|
525
|
-
const pos = textareaRef.selectionStart;
|
|
526
|
-
const textBefore = content.substring(0, pos);
|
|
527
|
-
const lastSlashIndex = textBefore.lastIndexOf("/");
|
|
528
|
-
|
|
529
|
-
if (lastSlashIndex >= 0) {
|
|
530
|
-
content = content.substring(0, lastSlashIndex) + cmd.insert + content.substring(pos);
|
|
531
|
-
|
|
532
|
-
setTimeout(() => {
|
|
533
|
-
const newPos = lastSlashIndex + (cmd.cursorOffset || cmd.insert.length);
|
|
534
|
-
textareaRef.selectionStart = textareaRef.selectionEnd = newPos;
|
|
535
|
-
textareaRef.focus();
|
|
536
|
-
}, 0);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
slashMenu.open = false;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Command palette actions
|
|
543
|
-
const basePaletteCommands = [
|
|
544
|
-
{ id: "save", label: "Save", shortcut: "⌘S", action: () => onSave() },
|
|
545
|
-
{ id: "preview", label: "Toggle Preview", shortcut: "", action: () => showPreview = !showPreview },
|
|
546
|
-
{ id: "fullPreview", label: "Full Preview", shortcut: "", action: () => showFullPreview = true },
|
|
547
|
-
{ id: "zen", label: "Toggle Zen Mode", shortcut: "⌘⇧↵", action: () => toggleZenMode() },
|
|
548
|
-
{ id: "campfire", label: "Start Campfire Session", shortcut: "", action: () => startCampfireSession() },
|
|
549
|
-
{ id: "bold", label: "Bold", shortcut: "⌘B", action: () => wrapSelection("**", "**") },
|
|
550
|
-
{ id: "italic", label: "Italic", shortcut: "⌘I", action: () => wrapSelection("_", "_") },
|
|
551
|
-
{ id: "code", label: "Insert Code Block", shortcut: "", action: () => insertCodeBlock() },
|
|
552
|
-
{ id: "link", label: "Insert Link", shortcut: "", action: () => insertLink() },
|
|
553
|
-
{ id: "image", label: "Insert Image", shortcut: "", action: () => insertImage() },
|
|
554
|
-
{ id: "goal", label: "Set Writing Goal", shortcut: "", action: () => promptWritingGoal() },
|
|
555
|
-
{ id: "snippets", label: "Manage Snippets", shortcut: "", action: () => openSnippetsModal() },
|
|
556
|
-
{ id: "newSnippet", label: "Create New Snippet", shortcut: "", action: () => openSnippetsModal() },
|
|
557
|
-
{ id: "sounds", label: "Toggle Ambient Sounds", shortcut: "", action: () => toggleAmbientSound() },
|
|
558
|
-
{ id: "soundPanel", label: "Sound Settings", shortcut: "", action: () => toggleSoundPanel() },
|
|
559
|
-
];
|
|
560
|
-
|
|
561
|
-
// Add theme commands dynamically
|
|
562
|
-
let paletteCommands = $derived(() => {
|
|
563
|
-
const themeCommands = Object.entries(themes).map(([key, theme]) => ({
|
|
564
|
-
id: `theme-${key}`,
|
|
565
|
-
label: `Theme: ${theme.label} (${theme.desc})`,
|
|
566
|
-
shortcut: currentTheme === key ? "●" : "",
|
|
567
|
-
action: () => setTheme(key),
|
|
568
|
-
}));
|
|
569
|
-
return [...basePaletteCommands, ...themeCommands];
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
let filteredPaletteCommands = $derived(
|
|
573
|
-
paletteCommands().filter(cmd =>
|
|
574
|
-
cmd.label.toLowerCase().includes(commandPalette.query.toLowerCase())
|
|
575
|
-
)
|
|
576
|
-
);
|
|
577
|
-
|
|
578
|
-
function executePaletteCommand(index) {
|
|
579
|
-
const cmd = filteredPaletteCommands[index];
|
|
580
|
-
if (cmd) {
|
|
581
|
-
cmd.action();
|
|
582
|
-
commandPalette.open = false;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
303
|
// Zen mode toggle
|
|
587
304
|
function toggleZenMode() {
|
|
588
305
|
isZenMode = !isZenMode;
|
|
@@ -591,306 +308,29 @@
|
|
|
591
308
|
}
|
|
592
309
|
}
|
|
593
310
|
|
|
594
|
-
//
|
|
595
|
-
function startCampfireSession() {
|
|
596
|
-
campfireSession.active = true;
|
|
597
|
-
campfireSession.startTime = Date.now();
|
|
598
|
-
campfireSession.startWordCount = wordCount;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
function endCampfireSession() {
|
|
602
|
-
const wordsWritten = wordCount - campfireSession.startWordCount;
|
|
603
|
-
const elapsed = campfireSession.startTime ? Math.floor((Date.now() - campfireSession.startTime) / 1000) : 0;
|
|
604
|
-
|
|
605
|
-
// Could show a summary modal here
|
|
606
|
-
campfireSession.active = false;
|
|
607
|
-
campfireSession.startTime = null;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// Writing goal prompt
|
|
611
|
-
function promptWritingGoal() {
|
|
612
|
-
const target = prompt("Set your word goal for this session:", "500");
|
|
613
|
-
if (target && !isNaN(parseInt(target))) {
|
|
614
|
-
writingGoal.enabled = true;
|
|
615
|
-
writingGoal.targetWords = parseInt(target);
|
|
616
|
-
writingGoal.sessionWords = wordCount;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// Snippet management
|
|
621
|
-
const SNIPPETS_STORAGE_KEY = "grove-editor-snippets";
|
|
622
|
-
|
|
623
|
-
function loadSnippets() {
|
|
624
|
-
try {
|
|
625
|
-
const stored = localStorage.getItem(SNIPPETS_STORAGE_KEY);
|
|
626
|
-
if (stored) {
|
|
627
|
-
snippets = JSON.parse(stored);
|
|
628
|
-
}
|
|
629
|
-
} catch (e) {
|
|
630
|
-
console.warn("Failed to load snippets:", e);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
function saveSnippets() {
|
|
635
|
-
try {
|
|
636
|
-
localStorage.setItem(SNIPPETS_STORAGE_KEY, JSON.stringify(snippets));
|
|
637
|
-
} catch (e) {
|
|
638
|
-
console.warn("Failed to save snippets:", e);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
function openSnippetsModal(editId = null) {
|
|
643
|
-
if (editId) {
|
|
644
|
-
const snippet = snippets.find(s => s.id === editId);
|
|
645
|
-
if (snippet) {
|
|
646
|
-
snippetsModal.editingId = editId;
|
|
647
|
-
snippetsModal.name = snippet.name;
|
|
648
|
-
snippetsModal.content = snippet.content;
|
|
649
|
-
snippetsModal.trigger = snippet.trigger || "";
|
|
650
|
-
}
|
|
651
|
-
} else {
|
|
652
|
-
snippetsModal.editingId = null;
|
|
653
|
-
snippetsModal.name = "";
|
|
654
|
-
snippetsModal.content = "";
|
|
655
|
-
snippetsModal.trigger = "";
|
|
656
|
-
}
|
|
657
|
-
snippetsModal.open = true;
|
|
658
|
-
commandPalette.open = false;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
function closeSnippetsModal() {
|
|
662
|
-
snippetsModal.open = false;
|
|
663
|
-
snippetsModal.editingId = null;
|
|
664
|
-
snippetsModal.name = "";
|
|
665
|
-
snippetsModal.content = "";
|
|
666
|
-
snippetsModal.trigger = "";
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
function saveSnippet() {
|
|
670
|
-
if (!snippetsModal.name.trim() || !snippetsModal.content.trim()) return;
|
|
671
|
-
|
|
672
|
-
if (snippetsModal.editingId) {
|
|
673
|
-
// Update existing snippet
|
|
674
|
-
snippets = snippets.map(s =>
|
|
675
|
-
s.id === snippetsModal.editingId
|
|
676
|
-
? {
|
|
677
|
-
...s,
|
|
678
|
-
name: snippetsModal.name.trim(),
|
|
679
|
-
content: snippetsModal.content,
|
|
680
|
-
trigger: snippetsModal.trigger.trim() || null,
|
|
681
|
-
}
|
|
682
|
-
: s
|
|
683
|
-
);
|
|
684
|
-
} else {
|
|
685
|
-
// Create new snippet
|
|
686
|
-
const newSnippet = {
|
|
687
|
-
id: `snippet-${Date.now()}`,
|
|
688
|
-
name: snippetsModal.name.trim(),
|
|
689
|
-
content: snippetsModal.content,
|
|
690
|
-
trigger: snippetsModal.trigger.trim() || null,
|
|
691
|
-
createdAt: new Date().toISOString(),
|
|
692
|
-
};
|
|
693
|
-
snippets = [...snippets, newSnippet];
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
saveSnippets();
|
|
697
|
-
closeSnippetsModal();
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
function deleteSnippet(id) {
|
|
701
|
-
if (confirm("Delete this snippet?")) {
|
|
702
|
-
snippets = snippets.filter(s => s.id !== id);
|
|
703
|
-
saveSnippets();
|
|
704
|
-
if (snippetsModal.editingId === id) {
|
|
705
|
-
closeSnippetsModal();
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
function insertSnippet(snippet) {
|
|
711
|
-
insertAtCursor(snippet.content);
|
|
712
|
-
slashMenu.open = false;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// Ambient sound controls
|
|
716
|
-
const SOUNDS_STORAGE_KEY = "grove-editor-sounds";
|
|
717
|
-
|
|
718
|
-
function loadSoundSettings() {
|
|
719
|
-
try {
|
|
720
|
-
const stored = localStorage.getItem(SOUNDS_STORAGE_KEY);
|
|
721
|
-
if (stored) {
|
|
722
|
-
const settings = JSON.parse(stored);
|
|
723
|
-
ambientSounds.currentSound = settings.currentSound || "forest";
|
|
724
|
-
ambientSounds.volume = settings.volume ?? 0.3;
|
|
725
|
-
// Don't auto-enable on load - user must click to start
|
|
726
|
-
}
|
|
727
|
-
} catch (e) {
|
|
728
|
-
console.warn("Failed to load sound settings:", e);
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
function saveSoundSettings() {
|
|
733
|
-
try {
|
|
734
|
-
localStorage.setItem(SOUNDS_STORAGE_KEY, JSON.stringify({
|
|
735
|
-
currentSound: ambientSounds.currentSound,
|
|
736
|
-
volume: ambientSounds.volume,
|
|
737
|
-
}));
|
|
738
|
-
} catch (e) {
|
|
739
|
-
console.warn("Failed to save sound settings:", e);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
function toggleAmbientSound() {
|
|
744
|
-
if (ambientSounds.enabled) {
|
|
745
|
-
stopSound();
|
|
746
|
-
} else {
|
|
747
|
-
playSound(ambientSounds.currentSound);
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
function playSound(soundKey) {
|
|
752
|
-
const sound = soundLibrary[soundKey];
|
|
753
|
-
if (!sound) return;
|
|
754
|
-
|
|
755
|
-
// Stop current sound if playing
|
|
756
|
-
if (audioElement) {
|
|
757
|
-
audioElement.pause();
|
|
758
|
-
audioElement = null;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
// Create new audio element
|
|
762
|
-
audioElement = new Audio(sound.url);
|
|
763
|
-
audioElement.loop = true;
|
|
764
|
-
audioElement.volume = ambientSounds.volume;
|
|
765
|
-
|
|
766
|
-
// Handle playback errors gracefully
|
|
767
|
-
audioElement.onerror = () => {
|
|
768
|
-
console.warn(`Sound file not found: ${sound.url}`);
|
|
769
|
-
ambientSounds.enabled = false;
|
|
770
|
-
};
|
|
771
|
-
|
|
772
|
-
audioElement.play().then(() => {
|
|
773
|
-
ambientSounds.enabled = true;
|
|
774
|
-
ambientSounds.currentSound = soundKey;
|
|
775
|
-
saveSoundSettings();
|
|
776
|
-
}).catch((e) => {
|
|
777
|
-
console.warn("Failed to play sound:", e);
|
|
778
|
-
ambientSounds.enabled = false;
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
function stopSound() {
|
|
783
|
-
if (audioElement) {
|
|
784
|
-
audioElement.pause();
|
|
785
|
-
audioElement = null;
|
|
786
|
-
}
|
|
787
|
-
ambientSounds.enabled = false;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
function setVolume(newVolume) {
|
|
791
|
-
ambientSounds.volume = newVolume;
|
|
792
|
-
if (audioElement) {
|
|
793
|
-
audioElement.volume = newVolume;
|
|
794
|
-
}
|
|
795
|
-
saveSoundSettings();
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
function selectSound(soundKey) {
|
|
799
|
-
if (ambientSounds.enabled) {
|
|
800
|
-
playSound(soundKey);
|
|
801
|
-
} else {
|
|
802
|
-
ambientSounds.currentSound = soundKey;
|
|
803
|
-
saveSoundSettings();
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
function toggleSoundPanel() {
|
|
808
|
-
ambientSounds.showPanel = !ambientSounds.showPanel;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
// Theme controls
|
|
812
|
-
function loadTheme() {
|
|
813
|
-
try {
|
|
814
|
-
const stored = localStorage.getItem(THEME_STORAGE_KEY);
|
|
815
|
-
if (stored && themes[stored]) {
|
|
816
|
-
currentTheme = stored;
|
|
817
|
-
applyTheme(stored);
|
|
818
|
-
}
|
|
819
|
-
} catch (e) {
|
|
820
|
-
console.warn("Failed to load theme:", e);
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
function saveTheme(themeName) {
|
|
825
|
-
try {
|
|
826
|
-
localStorage.setItem(THEME_STORAGE_KEY, themeName);
|
|
827
|
-
} catch (e) {
|
|
828
|
-
console.warn("Failed to save theme:", e);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
function applyTheme(themeName) {
|
|
833
|
-
const theme = themes[themeName];
|
|
834
|
-
if (!theme) return;
|
|
835
|
-
|
|
836
|
-
const root = document.documentElement;
|
|
837
|
-
root.style.setProperty("--editor-accent", theme.accent);
|
|
838
|
-
root.style.setProperty("--editor-accent-dim", theme.accentDim);
|
|
839
|
-
root.style.setProperty("--editor-accent-bright", theme.accentBright);
|
|
840
|
-
root.style.setProperty("--editor-accent-glow", theme.accentGlow);
|
|
841
|
-
root.style.setProperty("--editor-bg", theme.bg);
|
|
842
|
-
root.style.setProperty("--editor-bg-secondary", theme.bgSecondary);
|
|
843
|
-
root.style.setProperty("--editor-bg-tertiary", theme.bgTertiary);
|
|
844
|
-
root.style.setProperty("--editor-border", theme.border);
|
|
845
|
-
root.style.setProperty("--editor-border-accent", theme.borderAccent);
|
|
846
|
-
root.style.setProperty("--editor-text", theme.text);
|
|
847
|
-
root.style.setProperty("--editor-text-dim", theme.textDim);
|
|
848
|
-
root.style.setProperty("--editor-status-bg", theme.statusBg);
|
|
849
|
-
root.style.setProperty("--editor-status-border", theme.statusBorder);
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
function setTheme(themeName) {
|
|
853
|
-
if (!themes[themeName]) return;
|
|
854
|
-
currentTheme = themeName;
|
|
855
|
-
applyTheme(themeName);
|
|
856
|
-
saveTheme(themeName);
|
|
857
|
-
commandPalette.open = false;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// Typewriter scrolling - keep cursor line centered
|
|
311
|
+
// Typewriter scrolling
|
|
861
312
|
function applyTypewriterScroll() {
|
|
862
313
|
if (!textareaRef || !editorSettings.typewriterMode) return;
|
|
863
|
-
|
|
864
314
|
const lineHeight = parseFloat(getComputedStyle(textareaRef).lineHeight) || 24;
|
|
865
315
|
const viewportHeight = textareaRef.clientHeight;
|
|
866
316
|
const centerOffset = viewportHeight / 2;
|
|
867
317
|
const targetScroll = (cursorLine - 1) * lineHeight - centerOffset + lineHeight / 2;
|
|
868
|
-
|
|
869
318
|
textareaRef.scrollTop = Math.max(0, targetScroll);
|
|
870
319
|
}
|
|
871
320
|
|
|
872
|
-
// Sync line numbers scroll with textarea
|
|
873
321
|
function syncLineNumbersScroll() {
|
|
874
322
|
if (lineNumbersRef && textareaRef) {
|
|
875
323
|
lineNumbersRef.scrollTop = textareaRef.scrollTop;
|
|
876
324
|
}
|
|
877
325
|
}
|
|
878
326
|
|
|
879
|
-
//
|
|
327
|
+
// Text manipulation helpers
|
|
880
328
|
function wrapSelection(before, after) {
|
|
881
329
|
if (!textareaRef) return;
|
|
882
|
-
|
|
883
330
|
const start = textareaRef.selectionStart;
|
|
884
331
|
const end = textareaRef.selectionEnd;
|
|
885
332
|
const selectedText = content.substring(start, end);
|
|
886
|
-
|
|
887
|
-
content =
|
|
888
|
-
content.substring(0, start) +
|
|
889
|
-
before +
|
|
890
|
-
selectedText +
|
|
891
|
-
after +
|
|
892
|
-
content.substring(end);
|
|
893
|
-
|
|
333
|
+
content = content.substring(0, start) + before + selectedText + after + content.substring(end);
|
|
894
334
|
setTimeout(() => {
|
|
895
335
|
textareaRef.selectionStart = start + before.length;
|
|
896
336
|
textareaRef.selectionEnd = end + before.length;
|
|
@@ -898,24 +338,19 @@
|
|
|
898
338
|
}, 0);
|
|
899
339
|
}
|
|
900
340
|
|
|
901
|
-
// Insert text at cursor
|
|
902
341
|
function insertAtCursor(text) {
|
|
903
342
|
if (!textareaRef) return;
|
|
904
|
-
|
|
905
343
|
const start = textareaRef.selectionStart;
|
|
906
344
|
content = content.substring(0, start) + text + content.substring(start);
|
|
907
|
-
|
|
908
345
|
setTimeout(() => {
|
|
909
|
-
textareaRef.selectionStart = textareaRef.selectionEnd =
|
|
910
|
-
start + text.length;
|
|
346
|
+
textareaRef.selectionStart = textareaRef.selectionEnd = start + text.length;
|
|
911
347
|
textareaRef.focus();
|
|
912
348
|
}, 0);
|
|
913
349
|
}
|
|
914
350
|
|
|
915
351
|
// Toolbar actions
|
|
916
352
|
function insertHeading(level) {
|
|
917
|
-
|
|
918
|
-
insertAtCursor(prefix);
|
|
353
|
+
insertAtCursor("#".repeat(level) + " ");
|
|
919
354
|
}
|
|
920
355
|
|
|
921
356
|
function insertLink() {
|
|
@@ -928,15 +363,9 @@
|
|
|
928
363
|
|
|
929
364
|
function insertCodeBlock() {
|
|
930
365
|
const start = textareaRef.selectionStart;
|
|
931
|
-
const selectedText = content.substring(
|
|
932
|
-
start,
|
|
933
|
-
textareaRef.selectionEnd
|
|
934
|
-
);
|
|
366
|
+
const selectedText = content.substring(start, textareaRef.selectionEnd);
|
|
935
367
|
const codeBlock = "```\n" + (selectedText || "code here") + "\n```";
|
|
936
|
-
content =
|
|
937
|
-
content.substring(0, start) +
|
|
938
|
-
codeBlock +
|
|
939
|
-
content.substring(textareaRef.selectionEnd);
|
|
368
|
+
content = content.substring(0, start) + codeBlock + content.substring(textareaRef.selectionEnd);
|
|
940
369
|
}
|
|
941
370
|
|
|
942
371
|
function insertList() {
|
|
@@ -947,18 +376,12 @@
|
|
|
947
376
|
insertAtCursor("> ");
|
|
948
377
|
}
|
|
949
378
|
|
|
950
|
-
//
|
|
379
|
+
// Scroll sync
|
|
951
380
|
function handleScroll() {
|
|
952
|
-
// Sync line numbers
|
|
953
381
|
syncLineNumbersScroll();
|
|
954
|
-
|
|
955
|
-
// Sync preview
|
|
956
382
|
if (textareaRef && previewRef && showPreview) {
|
|
957
|
-
const scrollRatio =
|
|
958
|
-
|
|
959
|
-
(textareaRef.scrollHeight - textareaRef.clientHeight);
|
|
960
|
-
previewRef.scrollTop =
|
|
961
|
-
scrollRatio * (previewRef.scrollHeight - previewRef.clientHeight);
|
|
383
|
+
const scrollRatio = textareaRef.scrollTop / (textareaRef.scrollHeight - textareaRef.clientHeight);
|
|
384
|
+
previewRef.scrollTop = scrollRatio * (previewRef.scrollHeight - previewRef.clientHeight);
|
|
962
385
|
}
|
|
963
386
|
}
|
|
964
387
|
|
|
@@ -969,12 +392,17 @@
|
|
|
969
392
|
}
|
|
970
393
|
});
|
|
971
394
|
|
|
972
|
-
//
|
|
395
|
+
// Auto-save draft effect
|
|
396
|
+
$effect(() => {
|
|
397
|
+
if (draftKey && !readonly) {
|
|
398
|
+
draftManager.scheduleSave(content);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Drag and drop handlers
|
|
973
403
|
function handleDragEnter(e) {
|
|
974
404
|
e.preventDefault();
|
|
975
405
|
if (readonly) return;
|
|
976
|
-
|
|
977
|
-
// Check if dragging files
|
|
978
406
|
if (e.dataTransfer?.types?.includes("Files")) {
|
|
979
407
|
isDragging = true;
|
|
980
408
|
}
|
|
@@ -983,7 +411,6 @@
|
|
|
983
411
|
function handleDragOver(e) {
|
|
984
412
|
e.preventDefault();
|
|
985
413
|
if (readonly) return;
|
|
986
|
-
|
|
987
414
|
if (e.dataTransfer?.types?.includes("Files")) {
|
|
988
415
|
e.dataTransfer.dropEffect = "copy";
|
|
989
416
|
isDragging = true;
|
|
@@ -992,7 +419,6 @@
|
|
|
992
419
|
|
|
993
420
|
function handleDragLeave(e) {
|
|
994
421
|
e.preventDefault();
|
|
995
|
-
// Only set to false if leaving the container entirely
|
|
996
422
|
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
997
423
|
isDragging = false;
|
|
998
424
|
}
|
|
@@ -1012,7 +438,6 @@
|
|
|
1012
438
|
return;
|
|
1013
439
|
}
|
|
1014
440
|
|
|
1015
|
-
// Upload each image
|
|
1016
441
|
for (const file of imageFiles) {
|
|
1017
442
|
await uploadImage(file);
|
|
1018
443
|
}
|
|
@@ -1039,7 +464,6 @@
|
|
|
1039
464
|
throw new Error(result.message || "Upload failed");
|
|
1040
465
|
}
|
|
1041
466
|
|
|
1042
|
-
// Insert markdown image at cursor
|
|
1043
467
|
const altText = file.name.replace(/\.[^/.]+$/, "").replace(/[-_]/g, " ");
|
|
1044
468
|
const imageMarkdown = `\n`;
|
|
1045
469
|
insertAtCursor(imageMarkdown);
|
|
@@ -1054,7 +478,6 @@
|
|
|
1054
478
|
}
|
|
1055
479
|
}
|
|
1056
480
|
|
|
1057
|
-
// Handle paste for images
|
|
1058
481
|
function handlePaste(e) {
|
|
1059
482
|
if (readonly) return;
|
|
1060
483
|
|
|
@@ -1065,7 +488,6 @@
|
|
|
1065
488
|
e.preventDefault();
|
|
1066
489
|
const file = imageItem.getAsFile();
|
|
1067
490
|
if (file) {
|
|
1068
|
-
// Generate a filename for pasted images
|
|
1069
491
|
const timestamp = Date.now();
|
|
1070
492
|
const extension = file.type.split("/")[1] || "png";
|
|
1071
493
|
const renamedFile = new File([file], `pasted-${timestamp}.${extension}`, {
|
|
@@ -1076,114 +498,25 @@
|
|
|
1076
498
|
}
|
|
1077
499
|
}
|
|
1078
500
|
|
|
1079
|
-
//
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
clearTimeout(draftSaveTimer);
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
// Don't save if content hasn't changed from last saved version
|
|
1089
|
-
if (content === lastSavedContent) return;
|
|
1090
|
-
|
|
1091
|
-
// Schedule a draft save
|
|
1092
|
-
draftSaveTimer = setTimeout(() => {
|
|
1093
|
-
saveDraft();
|
|
1094
|
-
}, AUTO_SAVE_DELAY);
|
|
1095
|
-
|
|
1096
|
-
return () => {
|
|
1097
|
-
if (draftSaveTimer) {
|
|
1098
|
-
clearTimeout(draftSaveTimer);
|
|
1099
|
-
}
|
|
1100
|
-
};
|
|
1101
|
-
});
|
|
1102
|
-
|
|
1103
|
-
function saveDraft() {
|
|
1104
|
-
if (!draftKey || readonly) return;
|
|
1105
|
-
|
|
1106
|
-
try {
|
|
1107
|
-
const draft = {
|
|
1108
|
-
content,
|
|
1109
|
-
savedAt: new Date().toISOString(),
|
|
1110
|
-
};
|
|
1111
|
-
localStorage.setItem(`draft:${draftKey}`, JSON.stringify(draft));
|
|
1112
|
-
lastSavedContent = content;
|
|
1113
|
-
hasDraft = true;
|
|
1114
|
-
} catch (e) {
|
|
1115
|
-
console.warn("Failed to save draft:", e);
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
function loadDraft() {
|
|
1120
|
-
if (!draftKey) return null;
|
|
1121
|
-
|
|
1122
|
-
try {
|
|
1123
|
-
const stored = localStorage.getItem(`draft:${draftKey}`);
|
|
1124
|
-
if (stored) {
|
|
1125
|
-
return JSON.parse(stored);
|
|
1126
|
-
}
|
|
1127
|
-
} catch (e) {
|
|
1128
|
-
console.warn("Failed to load draft:", e);
|
|
1129
|
-
}
|
|
1130
|
-
return null;
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
export function clearDraft() {
|
|
1134
|
-
if (!draftKey) return;
|
|
1135
|
-
|
|
1136
|
-
try {
|
|
1137
|
-
localStorage.removeItem(`draft:${draftKey}`);
|
|
1138
|
-
hasDraft = false;
|
|
1139
|
-
storedDraft = null;
|
|
1140
|
-
draftRestorePrompt = false;
|
|
1141
|
-
} catch (e) {
|
|
1142
|
-
console.warn("Failed to clear draft:", e);
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
export function getDraftStatus() {
|
|
1147
|
-
return { hasDraft, storedDraft };
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
function restoreDraft() {
|
|
1151
|
-
if (storedDraft) {
|
|
1152
|
-
content = storedDraft.content;
|
|
1153
|
-
lastSavedContent = storedDraft.content;
|
|
1154
|
-
onDraftRestored(storedDraft);
|
|
501
|
+
// Command palette execution
|
|
502
|
+
function executePaletteCommand(index) {
|
|
503
|
+
const cmd = filteredPaletteCommands[index];
|
|
504
|
+
if (cmd && cmd.action) {
|
|
505
|
+
cmd.action();
|
|
506
|
+
commandPalette.close();
|
|
1155
507
|
}
|
|
1156
|
-
draftRestorePrompt = false;
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
function discardDraft() {
|
|
1160
|
-
clearDraft();
|
|
1161
|
-
lastSavedContent = content;
|
|
1162
508
|
}
|
|
1163
509
|
|
|
1164
510
|
onMount(() => {
|
|
1165
511
|
updateCursorPosition();
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
loadTheme();
|
|
1169
|
-
|
|
1170
|
-
// Check for existing draft on mount
|
|
1171
|
-
if (draftKey) {
|
|
1172
|
-
const draft = loadDraft();
|
|
1173
|
-
if (draft && draft.content !== content) {
|
|
1174
|
-
storedDraft = draft;
|
|
1175
|
-
draftRestorePrompt = true;
|
|
1176
|
-
} else {
|
|
1177
|
-
lastSavedContent = content;
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
512
|
+
snippetsManager.load();
|
|
513
|
+
ambientSounds.loadSettings();
|
|
514
|
+
editorTheme.loadTheme();
|
|
515
|
+
draftManager.init(content);
|
|
1180
516
|
|
|
1181
|
-
// Cleanup audio on unmount
|
|
1182
517
|
return () => {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
audioElement = null;
|
|
1186
|
-
}
|
|
518
|
+
ambientSounds.cleanup();
|
|
519
|
+
draftManager.cleanup();
|
|
1187
520
|
};
|
|
1188
521
|
});
|
|
1189
522
|
</script>
|
|
@@ -1194,7 +527,7 @@
|
|
|
1194
527
|
class="editor-container"
|
|
1195
528
|
class:dragging={isDragging}
|
|
1196
529
|
class:zen-mode={isZenMode}
|
|
1197
|
-
class:campfire-mode={
|
|
530
|
+
class:campfire-mode={writingSession.isCampfireActive}
|
|
1198
531
|
aria-label="Markdown editor with live preview"
|
|
1199
532
|
ondragenter={handleDragEnter}
|
|
1200
533
|
ondragover={handleDragOver}
|
|
@@ -1225,21 +558,21 @@
|
|
|
1225
558
|
{/if}
|
|
1226
559
|
|
|
1227
560
|
<!-- Draft restore prompt -->
|
|
1228
|
-
{#if draftRestorePrompt && storedDraft}
|
|
561
|
+
{#if draftManager.draftRestorePrompt && draftManager.storedDraft}
|
|
1229
562
|
<div class="draft-prompt">
|
|
1230
563
|
<div class="draft-prompt-content">
|
|
1231
564
|
<span class="draft-icon">~</span>
|
|
1232
565
|
<div class="draft-message">
|
|
1233
566
|
<strong>Unsaved draft found</strong>
|
|
1234
567
|
<span class="draft-time">
|
|
1235
|
-
Saved {new Date(storedDraft.savedAt).toLocaleString()}
|
|
568
|
+
Saved {new Date(draftManager.storedDraft.savedAt).toLocaleString()}
|
|
1236
569
|
</span>
|
|
1237
570
|
</div>
|
|
1238
571
|
<div class="draft-actions">
|
|
1239
|
-
<button type="button" class="draft-btn restore" onclick={restoreDraft}>
|
|
572
|
+
<button type="button" class="draft-btn restore" onclick={() => draftManager.restoreDraft()}>
|
|
1240
573
|
[<span class="key">r</span>estore]
|
|
1241
574
|
</button>
|
|
1242
|
-
<button type="button" class="draft-btn discard" onclick={discardDraft}>
|
|
575
|
+
<button type="button" class="draft-btn discard" onclick={() => draftManager.discardDraft()}>
|
|
1243
576
|
[<span class="key">d</span>iscard]
|
|
1244
577
|
</button>
|
|
1245
578
|
</div>
|
|
@@ -1250,98 +583,54 @@
|
|
|
1250
583
|
<!-- Toolbar -->
|
|
1251
584
|
<div class="toolbar">
|
|
1252
585
|
<div class="toolbar-group">
|
|
1253
|
-
<button
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
class="toolbar-btn"
|
|
1263
|
-
onclick={() => insertHeading(2)}
|
|
1264
|
-
title="Heading 2"
|
|
1265
|
-
disabled={readonly}
|
|
1266
|
-
>[h<span class="key">2</span>]</button>
|
|
1267
|
-
<button
|
|
1268
|
-
type="button"
|
|
1269
|
-
class="toolbar-btn"
|
|
1270
|
-
onclick={() => insertHeading(3)}
|
|
1271
|
-
title="Heading 3"
|
|
1272
|
-
disabled={readonly}
|
|
1273
|
-
>[h<span class="key">3</span>]</button>
|
|
586
|
+
<button type="button" class="toolbar-btn" onclick={() => insertHeading(1)} title="Heading 1" disabled={readonly}>
|
|
587
|
+
[h<span class="key">1</span>]
|
|
588
|
+
</button>
|
|
589
|
+
<button type="button" class="toolbar-btn" onclick={() => insertHeading(2)} title="Heading 2" disabled={readonly}>
|
|
590
|
+
[h<span class="key">2</span>]
|
|
591
|
+
</button>
|
|
592
|
+
<button type="button" class="toolbar-btn" onclick={() => insertHeading(3)} title="Heading 3" disabled={readonly}>
|
|
593
|
+
[h<span class="key">3</span>]
|
|
594
|
+
</button>
|
|
1274
595
|
</div>
|
|
1275
596
|
|
|
1276
597
|
<div class="toolbar-divider">|</div>
|
|
1277
598
|
|
|
1278
599
|
<div class="toolbar-group">
|
|
1279
|
-
<button
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
class="toolbar-btn"
|
|
1289
|
-
onclick={() => wrapSelection("_", "_")}
|
|
1290
|
-
title="Italic (Cmd+I)"
|
|
1291
|
-
disabled={readonly}
|
|
1292
|
-
>[<span class="key">i</span>talic]</button>
|
|
1293
|
-
<button
|
|
1294
|
-
type="button"
|
|
1295
|
-
class="toolbar-btn"
|
|
1296
|
-
onclick={() => wrapSelection("`", "`")}
|
|
1297
|
-
title="Inline Code"
|
|
1298
|
-
disabled={readonly}
|
|
1299
|
-
>[<span class="key">c</span>ode]</button>
|
|
600
|
+
<button type="button" class="toolbar-btn" onclick={() => wrapSelection("**", "**")} title="Bold (Cmd+B)" disabled={readonly}>
|
|
601
|
+
[<span class="key">b</span>old]
|
|
602
|
+
</button>
|
|
603
|
+
<button type="button" class="toolbar-btn" onclick={() => wrapSelection("_", "_")} title="Italic (Cmd+I)" disabled={readonly}>
|
|
604
|
+
[<span class="key">i</span>talic]
|
|
605
|
+
</button>
|
|
606
|
+
<button type="button" class="toolbar-btn" onclick={() => wrapSelection("`", "`")} title="Inline Code" disabled={readonly}>
|
|
607
|
+
[<span class="key">c</span>ode]
|
|
608
|
+
</button>
|
|
1300
609
|
</div>
|
|
1301
610
|
|
|
1302
611
|
<div class="toolbar-divider">|</div>
|
|
1303
612
|
|
|
1304
613
|
<div class="toolbar-group">
|
|
1305
|
-
<button
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
class="toolbar-btn"
|
|
1315
|
-
onclick={insertImage}
|
|
1316
|
-
title="Image"
|
|
1317
|
-
disabled={readonly}
|
|
1318
|
-
>[i<span class="key">m</span>g]</button>
|
|
1319
|
-
<button
|
|
1320
|
-
type="button"
|
|
1321
|
-
class="toolbar-btn"
|
|
1322
|
-
onclick={insertCodeBlock}
|
|
1323
|
-
title="Code Block"
|
|
1324
|
-
disabled={readonly}
|
|
1325
|
-
>[bloc<span class="key">k</span>]</button>
|
|
614
|
+
<button type="button" class="toolbar-btn" onclick={insertLink} title="Link" disabled={readonly}>
|
|
615
|
+
[<span class="key">l</span>ink]
|
|
616
|
+
</button>
|
|
617
|
+
<button type="button" class="toolbar-btn" onclick={insertImage} title="Image" disabled={readonly}>
|
|
618
|
+
[i<span class="key">m</span>g]
|
|
619
|
+
</button>
|
|
620
|
+
<button type="button" class="toolbar-btn" onclick={insertCodeBlock} title="Code Block" disabled={readonly}>
|
|
621
|
+
[bloc<span class="key">k</span>]
|
|
622
|
+
</button>
|
|
1326
623
|
</div>
|
|
1327
624
|
|
|
1328
625
|
<div class="toolbar-divider">|</div>
|
|
1329
626
|
|
|
1330
627
|
<div class="toolbar-group">
|
|
1331
|
-
<button
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
>[lis<span class="key">t</span>]</button>
|
|
1338
|
-
<button
|
|
1339
|
-
type="button"
|
|
1340
|
-
class="toolbar-btn"
|
|
1341
|
-
onclick={insertQuote}
|
|
1342
|
-
title="Quote"
|
|
1343
|
-
disabled={readonly}
|
|
1344
|
-
>[<span class="key">q</span>uote]</button>
|
|
628
|
+
<button type="button" class="toolbar-btn" onclick={insertList} title="List" disabled={readonly}>
|
|
629
|
+
[lis<span class="key">t</span>]
|
|
630
|
+
</button>
|
|
631
|
+
<button type="button" class="toolbar-btn" onclick={insertQuote} title="Quote" disabled={readonly}>
|
|
632
|
+
[<span class="key">q</span>uote]
|
|
633
|
+
</button>
|
|
1345
634
|
</div>
|
|
1346
635
|
|
|
1347
636
|
<div class="toolbar-spacer"></div>
|
|
@@ -1353,13 +642,17 @@
|
|
|
1353
642
|
class:active={showPreview}
|
|
1354
643
|
onclick={() => (showPreview = !showPreview)}
|
|
1355
644
|
title="Toggle Preview"
|
|
1356
|
-
>
|
|
645
|
+
>
|
|
646
|
+
{#if showPreview}[hide <span class="key">p</span>review]{:else}[show <span class="key">p</span>review]{/if}
|
|
647
|
+
</button>
|
|
1357
648
|
<button
|
|
1358
649
|
type="button"
|
|
1359
650
|
class="toolbar-btn full-preview-btn"
|
|
1360
651
|
onclick={() => (showFullPreview = true)}
|
|
1361
652
|
title="Open Full Preview (site styling)"
|
|
1362
|
-
>
|
|
653
|
+
>
|
|
654
|
+
[<span class="key">f</span>ull]
|
|
655
|
+
</button>
|
|
1363
656
|
</div>
|
|
1364
657
|
</div>
|
|
1365
658
|
|
|
@@ -1412,26 +705,20 @@
|
|
|
1412
705
|
<!-- Status Bar -->
|
|
1413
706
|
<div class="status-bar">
|
|
1414
707
|
<div class="status-left">
|
|
1415
|
-
<span class="status-item">
|
|
1416
|
-
Ln {cursorLine}, Col {cursorCol}
|
|
1417
|
-
</span>
|
|
708
|
+
<span class="status-item">Ln {cursorLine}, Col {cursorCol}</span>
|
|
1418
709
|
<span class="status-divider">|</span>
|
|
1419
710
|
<span class="status-item">{lineCount} lines</span>
|
|
1420
711
|
<span class="status-divider">|</span>
|
|
1421
712
|
<span class="status-item">{wordCount} words</span>
|
|
1422
713
|
<span class="status-divider">|</span>
|
|
1423
|
-
<span class="status-item">{readingTime
|
|
1424
|
-
{#if
|
|
714
|
+
<span class="status-item">{readingTime}</span>
|
|
715
|
+
{#if writingSession.isGoalEnabled}
|
|
1425
716
|
<span class="status-divider">|</span>
|
|
1426
|
-
<span class="status-goal">
|
|
1427
|
-
Goal: {goalProgress()}%
|
|
1428
|
-
</span>
|
|
717
|
+
<span class="status-goal">Goal: {goalProgress}%</span>
|
|
1429
718
|
{/if}
|
|
1430
|
-
{#if
|
|
719
|
+
{#if writingSession.isCampfireActive}
|
|
1431
720
|
<span class="status-divider">|</span>
|
|
1432
|
-
<span class="status-campfire">
|
|
1433
|
-
~ {campfireElapsed()}
|
|
1434
|
-
</span>
|
|
721
|
+
<span class="status-campfire">~ {campfireElapsed}</span>
|
|
1435
722
|
{/if}
|
|
1436
723
|
</div>
|
|
1437
724
|
<div class="status-right">
|
|
@@ -1439,7 +726,7 @@
|
|
|
1439
726
|
type="button"
|
|
1440
727
|
class="status-sound-btn"
|
|
1441
728
|
class:playing={ambientSounds.enabled}
|
|
1442
|
-
onclick={
|
|
729
|
+
onclick={() => ambientSounds.togglePanel()}
|
|
1443
730
|
title="Ambient sounds"
|
|
1444
731
|
>
|
|
1445
732
|
[{soundLibrary[ambientSounds.currentSound]?.name || "snd"}]{#if ambientSounds.enabled}<span class="sound-wave">~</span>{/if}
|
|
@@ -1451,7 +738,7 @@
|
|
|
1451
738
|
{/if}
|
|
1452
739
|
{#if saving}
|
|
1453
740
|
<span class="status-saving">Saving...</span>
|
|
1454
|
-
{:else if draftKey && content
|
|
741
|
+
{:else if draftKey && draftManager.hasUnsavedChanges(content)}
|
|
1455
742
|
<span class="status-draft">Draft saving...</span>
|
|
1456
743
|
{:else}
|
|
1457
744
|
<span class="status-item">Markdown</span>
|
|
@@ -1461,15 +748,15 @@
|
|
|
1461
748
|
</div>
|
|
1462
749
|
|
|
1463
750
|
<!-- Slash Commands Menu -->
|
|
1464
|
-
{#if
|
|
751
|
+
{#if slashCommands.isOpen}
|
|
1465
752
|
<div class="slash-menu">
|
|
1466
753
|
<div class="slash-menu-header">:: commands</div>
|
|
1467
754
|
{#each filteredSlashCommands as cmd, i}
|
|
1468
755
|
<button
|
|
1469
756
|
type="button"
|
|
1470
757
|
class="slash-menu-item"
|
|
1471
|
-
class:selected={i ===
|
|
1472
|
-
onclick={() =>
|
|
758
|
+
class:selected={i === slashCommands.menu.selectedIndex}
|
|
759
|
+
onclick={() => slashCommands.execute(i)}
|
|
1473
760
|
>
|
|
1474
761
|
<span class="slash-cmd-label">{cmd.label}</span>
|
|
1475
762
|
</button>
|
|
@@ -1481,29 +768,30 @@
|
|
|
1481
768
|
{/if}
|
|
1482
769
|
|
|
1483
770
|
<!-- Command Palette -->
|
|
1484
|
-
{#if commandPalette.
|
|
1485
|
-
<div class="command-palette-overlay" onclick={() => commandPalette.
|
|
771
|
+
{#if commandPalette.isOpen}
|
|
772
|
+
<div class="command-palette-overlay" onclick={() => commandPalette.close()}>
|
|
1486
773
|
<div class="command-palette" onclick={(e) => e.stopPropagation()}>
|
|
1487
774
|
<input
|
|
1488
775
|
type="text"
|
|
1489
776
|
class="command-palette-input"
|
|
1490
777
|
placeholder="> type a command..."
|
|
1491
|
-
|
|
778
|
+
value={commandPalette.query}
|
|
779
|
+
oninput={(e) => commandPalette.setQuery(e.target.value)}
|
|
1492
780
|
onkeydown={(e) => {
|
|
1493
781
|
if (e.key === "ArrowDown") {
|
|
1494
782
|
e.preventDefault();
|
|
1495
|
-
commandPalette.
|
|
783
|
+
commandPalette.navigate("down");
|
|
1496
784
|
}
|
|
1497
785
|
if (e.key === "ArrowUp") {
|
|
1498
786
|
e.preventDefault();
|
|
1499
|
-
commandPalette.
|
|
787
|
+
commandPalette.navigate("up");
|
|
1500
788
|
}
|
|
1501
789
|
if (e.key === "Enter") {
|
|
1502
790
|
e.preventDefault();
|
|
1503
791
|
executePaletteCommand(commandPalette.selectedIndex);
|
|
1504
792
|
}
|
|
1505
793
|
if (e.key === "Escape") {
|
|
1506
|
-
commandPalette.
|
|
794
|
+
commandPalette.close();
|
|
1507
795
|
}
|
|
1508
796
|
}}
|
|
1509
797
|
/>
|
|
@@ -1526,23 +814,23 @@
|
|
|
1526
814
|
</div>
|
|
1527
815
|
{/if}
|
|
1528
816
|
|
|
1529
|
-
<!-- Campfire Session Controls
|
|
1530
|
-
{#if
|
|
817
|
+
<!-- Campfire Session Controls -->
|
|
818
|
+
{#if writingSession.isCampfireActive}
|
|
1531
819
|
<div class="campfire-controls">
|
|
1532
820
|
<div class="campfire-ember"></div>
|
|
1533
821
|
<div class="campfire-stats">
|
|
1534
|
-
<span class="campfire-time">{campfireElapsed
|
|
1535
|
-
<span class="campfire-words">+{wordCount
|
|
822
|
+
<span class="campfire-time">{campfireElapsed}</span>
|
|
823
|
+
<span class="campfire-words">+{writingSession.getCampfireWords(wordCount)} words</span>
|
|
1536
824
|
</div>
|
|
1537
|
-
<button type="button" class="campfire-end" onclick={
|
|
825
|
+
<button type="button" class="campfire-end" onclick={() => writingSession.endCampfire()}>
|
|
1538
826
|
[<span class="key">e</span>nd]
|
|
1539
827
|
</button>
|
|
1540
828
|
</div>
|
|
1541
829
|
{/if}
|
|
1542
830
|
|
|
1543
831
|
<!-- Snippets Modal -->
|
|
1544
|
-
<Dialog bind:open={
|
|
1545
|
-
<h3 slot="title">:: {
|
|
832
|
+
<Dialog bind:open={snippetsManager.modal.open}>
|
|
833
|
+
<h3 slot="title">:: {snippetsManager.modal.editingId ? "edit snippet" : "new snippet"}</h3>
|
|
1546
834
|
|
|
1547
835
|
<div class="snippets-modal-body">
|
|
1548
836
|
<div class="snippets-form">
|
|
@@ -1551,7 +839,7 @@
|
|
|
1551
839
|
<Input
|
|
1552
840
|
id="snippet-name"
|
|
1553
841
|
type="text"
|
|
1554
|
-
bind:value={
|
|
842
|
+
bind:value={snippetsManager.modal.name}
|
|
1555
843
|
placeholder="e.g., Blog signature"
|
|
1556
844
|
/>
|
|
1557
845
|
</div>
|
|
@@ -1561,55 +849,52 @@
|
|
|
1561
849
|
<Input
|
|
1562
850
|
id="snippet-trigger"
|
|
1563
851
|
type="text"
|
|
1564
|
-
bind:value={
|
|
852
|
+
bind:value={snippetsManager.modal.trigger}
|
|
1565
853
|
placeholder="e.g., sig"
|
|
1566
854
|
/>
|
|
1567
855
|
<span class="field-hint">Type /trigger to quickly insert</span>
|
|
1568
856
|
</div>
|
|
1569
857
|
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
858
|
+
<div class="snippet-field">
|
|
859
|
+
<label for="snippet-content">Content</label>
|
|
860
|
+
<textarea
|
|
861
|
+
id="snippet-content"
|
|
862
|
+
bind:value={snippetsManager.modal.content}
|
|
863
|
+
placeholder="Enter your markdown snippet..."
|
|
864
|
+
rows="6"
|
|
865
|
+
></textarea>
|
|
866
|
+
</div>
|
|
1579
867
|
|
|
1580
868
|
<div class="snippet-actions">
|
|
1581
|
-
{#if
|
|
1582
|
-
<Button
|
|
1583
|
-
variant="danger"
|
|
1584
|
-
onclick={() => deleteSnippet(snippetsModal.editingId)}
|
|
1585
|
-
>
|
|
869
|
+
{#if snippetsManager.modal.editingId}
|
|
870
|
+
<Button variant="danger" onclick={() => snippetsManager.deleteSnippet(snippetsManager.modal.editingId)}>
|
|
1586
871
|
[<span class="key">d</span>elete]
|
|
1587
872
|
</Button>
|
|
1588
873
|
{/if}
|
|
1589
874
|
<div class="snippet-actions-right">
|
|
1590
|
-
<Button variant="outline" onclick={
|
|
875
|
+
<Button variant="outline" onclick={() => snippetsManager.closeModal()}>
|
|
1591
876
|
[<span class="key">c</span>ancel]
|
|
1592
877
|
</Button>
|
|
1593
878
|
<Button
|
|
1594
|
-
onclick={saveSnippet}
|
|
1595
|
-
disabled={!
|
|
879
|
+
onclick={() => snippetsManager.saveSnippet()}
|
|
880
|
+
disabled={!snippetsManager.modal.name.trim() || !snippetsManager.modal.content.trim()}
|
|
1596
881
|
>
|
|
1597
|
-
{#if
|
|
882
|
+
{#if snippetsManager.modal.editingId}[<span class="key">u</span>pdate]{:else}[<span class="key">s</span>ave]{/if}
|
|
1598
883
|
</Button>
|
|
1599
884
|
</div>
|
|
1600
885
|
</div>
|
|
1601
886
|
</div>
|
|
1602
887
|
|
|
1603
|
-
{#if snippets.length > 0 && !
|
|
888
|
+
{#if snippetsManager.snippets.length > 0 && !snippetsManager.modal.editingId}
|
|
1604
889
|
<div class="snippets-list-divider">
|
|
1605
890
|
<span>:: your snippets</span>
|
|
1606
891
|
</div>
|
|
1607
892
|
<div class="snippets-list">
|
|
1608
|
-
{#each snippets as snippet}
|
|
893
|
+
{#each snippetsManager.snippets as snippet}
|
|
1609
894
|
<button
|
|
1610
895
|
type="button"
|
|
1611
896
|
class="snippet-list-item"
|
|
1612
|
-
onclick={() =>
|
|
897
|
+
onclick={() => snippetsManager.openModal(snippet.id)}
|
|
1613
898
|
>
|
|
1614
899
|
<span class="snippet-name">{snippet.name}</span>
|
|
1615
900
|
{#if snippet.trigger}
|
|
@@ -1627,11 +912,9 @@
|
|
|
1627
912
|
<div class="sound-panel">
|
|
1628
913
|
<div class="sound-panel-header">
|
|
1629
914
|
<span class="sound-panel-title">:: ambient sounds</span>
|
|
1630
|
-
<button
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
onclick={() => ambientSounds.showPanel = false}
|
|
1634
|
-
>[x]</button>
|
|
915
|
+
<button type="button" class="sound-panel-close" onclick={() => ambientSounds.closePanel()}>
|
|
916
|
+
[x]
|
|
917
|
+
</button>
|
|
1635
918
|
</div>
|
|
1636
919
|
|
|
1637
920
|
<div class="sound-options">
|
|
@@ -1641,7 +924,7 @@
|
|
|
1641
924
|
class="sound-option"
|
|
1642
925
|
class:active={ambientSounds.currentSound === key}
|
|
1643
926
|
class:playing={ambientSounds.enabled && ambientSounds.currentSound === key}
|
|
1644
|
-
onclick={() => selectSound(key)}
|
|
927
|
+
onclick={() => ambientSounds.selectSound(key)}
|
|
1645
928
|
>
|
|
1646
929
|
[<span class="key">{sound.key}</span>] {sound.name}
|
|
1647
930
|
</button>
|
|
@@ -1657,7 +940,7 @@
|
|
|
1657
940
|
max="1"
|
|
1658
941
|
step="0.05"
|
|
1659
942
|
value={ambientSounds.volume}
|
|
1660
|
-
oninput={(e) => setVolume(parseFloat(e.target.value))}
|
|
943
|
+
oninput={(e) => ambientSounds.setVolume(parseFloat(e.target.value))}
|
|
1661
944
|
class="volume-slider"
|
|
1662
945
|
/>
|
|
1663
946
|
</label>
|
|
@@ -1666,7 +949,7 @@
|
|
|
1666
949
|
type="button"
|
|
1667
950
|
class="sound-play-btn"
|
|
1668
951
|
class:playing={ambientSounds.enabled}
|
|
1669
|
-
onclick={
|
|
952
|
+
onclick={() => ambientSounds.toggle()}
|
|
1670
953
|
>
|
|
1671
954
|
{#if ambientSounds.enabled}[<span class="key">s</span>top]{:else}[<span class="key">p</span>lay]{/if}
|
|
1672
955
|
</button>
|
|
@@ -1686,18 +969,13 @@
|
|
|
1686
969
|
<header class="full-preview-header">
|
|
1687
970
|
<h2>:: full preview</h2>
|
|
1688
971
|
<div class="full-preview-actions">
|
|
1689
|
-
<button
|
|
1690
|
-
type="button"
|
|
1691
|
-
class="full-preview-close"
|
|
1692
|
-
onclick={() => (showFullPreview = false)}
|
|
1693
|
-
>
|
|
972
|
+
<button type="button" class="full-preview-close" onclick={() => (showFullPreview = false)}>
|
|
1694
973
|
[<span class="key">c</span>lose]
|
|
1695
974
|
</button>
|
|
1696
975
|
</div>
|
|
1697
976
|
</header>
|
|
1698
977
|
<div class="full-preview-scroll">
|
|
1699
978
|
<article class="full-preview-article">
|
|
1700
|
-
<!-- Post Header -->
|
|
1701
979
|
{#if previewTitle || previewDate || previewTags.length > 0}
|
|
1702
980
|
<header class="content-header">
|
|
1703
981
|
{#if previewTitle}
|
|
@@ -1726,7 +1004,6 @@
|
|
|
1726
1004
|
</header>
|
|
1727
1005
|
{/if}
|
|
1728
1006
|
|
|
1729
|
-
<!-- Rendered Content -->
|
|
1730
1007
|
<div class="content-body">
|
|
1731
1008
|
{#if previewHtml}
|
|
1732
1009
|
{@html previewHtml}
|
|
@@ -1752,12 +1029,12 @@
|
|
|
1752
1029
|
overflow: hidden;
|
|
1753
1030
|
font-family: "JetBrains Mono", "Fira Code", "SF Mono", Consolas, monospace;
|
|
1754
1031
|
position: relative;
|
|
1032
|
+
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
|
1755
1033
|
}
|
|
1756
1034
|
.editor-container.dragging {
|
|
1757
1035
|
border-color: var(--editor-accent, #8bc48b);
|
|
1758
1036
|
box-shadow: 0 0 0 2px color-mix(in srgb, var(--editor-accent, #8bc48b) 30%, transparent);
|
|
1759
1037
|
}
|
|
1760
|
-
/* Drag overlay */
|
|
1761
1038
|
.drag-overlay {
|
|
1762
1039
|
position: absolute;
|
|
1763
1040
|
inset: 0;
|
|
@@ -1791,7 +1068,6 @@
|
|
|
1791
1068
|
font-size: 1.1rem;
|
|
1792
1069
|
font-weight: 500;
|
|
1793
1070
|
}
|
|
1794
|
-
/* Upload status */
|
|
1795
1071
|
.upload-status {
|
|
1796
1072
|
position: absolute;
|
|
1797
1073
|
top: 50%;
|
|
@@ -1835,11 +1111,8 @@
|
|
|
1835
1111
|
font-weight: bold;
|
|
1836
1112
|
}
|
|
1837
1113
|
@keyframes spin {
|
|
1838
|
-
to {
|
|
1839
|
-
transform: rotate(360deg);
|
|
1840
|
-
}
|
|
1114
|
+
to { transform: rotate(360deg); }
|
|
1841
1115
|
}
|
|
1842
|
-
/* Draft prompt */
|
|
1843
1116
|
.draft-prompt {
|
|
1844
1117
|
position: absolute;
|
|
1845
1118
|
top: 0;
|
|
@@ -1901,13 +1174,11 @@
|
|
|
1901
1174
|
.draft-btn.discard:hover {
|
|
1902
1175
|
color: #d4d4d4;
|
|
1903
1176
|
}
|
|
1904
|
-
/* Terminal Key Highlight */
|
|
1905
1177
|
.key {
|
|
1906
1178
|
color: var(--editor-accent, #8bc48b);
|
|
1907
1179
|
font-weight: bold;
|
|
1908
1180
|
text-decoration: underline;
|
|
1909
1181
|
}
|
|
1910
|
-
/* Toolbar */
|
|
1911
1182
|
.toolbar {
|
|
1912
1183
|
display: flex;
|
|
1913
1184
|
align-items: center;
|
|
@@ -1917,6 +1188,7 @@
|
|
|
1917
1188
|
border-bottom: 1px solid var(--editor-border, var(--light-border-primary));
|
|
1918
1189
|
flex-wrap: wrap;
|
|
1919
1190
|
font-family: "JetBrains Mono", "Fira Code", monospace;
|
|
1191
|
+
transition: opacity 0.3s ease;
|
|
1920
1192
|
}
|
|
1921
1193
|
.toolbar-group {
|
|
1922
1194
|
display: flex;
|
|
@@ -1972,7 +1244,6 @@
|
|
|
1972
1244
|
.toolbar-spacer {
|
|
1973
1245
|
flex: 1;
|
|
1974
1246
|
}
|
|
1975
|
-
/* Editor Area */
|
|
1976
1247
|
.editor-area {
|
|
1977
1248
|
display: flex;
|
|
1978
1249
|
flex: 1;
|
|
@@ -1996,7 +1267,6 @@
|
|
|
1996
1267
|
min-height: 0;
|
|
1997
1268
|
overflow: hidden;
|
|
1998
1269
|
}
|
|
1999
|
-
/* Line Numbers */
|
|
2000
1270
|
.line-numbers {
|
|
2001
1271
|
display: flex;
|
|
2002
1272
|
flex-direction: column;
|
|
@@ -2021,7 +1291,6 @@
|
|
|
2021
1291
|
color: var(--editor-accent, #8bc48b);
|
|
2022
1292
|
background: color-mix(in srgb, var(--editor-accent, #8bc48b) 10%, transparent);
|
|
2023
1293
|
}
|
|
2024
|
-
/* Editor Textarea */
|
|
2025
1294
|
.editor-textarea {
|
|
2026
1295
|
flex: 1;
|
|
2027
1296
|
padding: 1rem;
|
|
@@ -2047,7 +1316,6 @@
|
|
|
2047
1316
|
opacity: 0.7;
|
|
2048
1317
|
cursor: not-allowed;
|
|
2049
1318
|
}
|
|
2050
|
-
/* Preview Panel */
|
|
2051
1319
|
.preview-panel {
|
|
2052
1320
|
width: 50%;
|
|
2053
1321
|
display: flex;
|
|
@@ -2070,12 +1338,7 @@
|
|
|
2070
1338
|
padding: 1rem;
|
|
2071
1339
|
overflow-y: auto;
|
|
2072
1340
|
color: #d4d4d4;
|
|
2073
|
-
font-family:
|
|
2074
|
-
-apple-system,
|
|
2075
|
-
BlinkMacSystemFont,
|
|
2076
|
-
"Segoe UI",
|
|
2077
|
-
Roboto,
|
|
2078
|
-
sans-serif;
|
|
1341
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
2079
1342
|
font-size: 0.95rem;
|
|
2080
1343
|
line-height: 1.7;
|
|
2081
1344
|
}
|
|
@@ -2083,7 +1346,6 @@
|
|
|
2083
1346
|
color: #5a5a5a;
|
|
2084
1347
|
font-style: italic;
|
|
2085
1348
|
}
|
|
2086
|
-
/* Preview content styles */
|
|
2087
1349
|
.preview-content :global(h1),
|
|
2088
1350
|
.preview-content :global(h2),
|
|
2089
1351
|
.preview-content :global(h3),
|
|
@@ -2157,7 +1419,6 @@
|
|
|
2157
1419
|
max-width: 100%;
|
|
2158
1420
|
border-radius: 4px;
|
|
2159
1421
|
}
|
|
2160
|
-
/* Status Bar */
|
|
2161
1422
|
.status-bar {
|
|
2162
1423
|
display: flex;
|
|
2163
1424
|
justify-content: space-between;
|
|
@@ -2167,6 +1428,7 @@
|
|
|
2167
1428
|
border-top: 1px solid var(--editor-status-border, var(--light-border-secondary));
|
|
2168
1429
|
font-size: 0.75rem;
|
|
2169
1430
|
color: var(--editor-accent-bright, #a8dca8);
|
|
1431
|
+
transition: opacity 0.3s ease;
|
|
2170
1432
|
}
|
|
2171
1433
|
.status-left,
|
|
2172
1434
|
.status-right {
|
|
@@ -2188,16 +1450,21 @@
|
|
|
2188
1450
|
color: #7a9a7a;
|
|
2189
1451
|
font-style: italic;
|
|
2190
1452
|
}
|
|
1453
|
+
.status-goal {
|
|
1454
|
+
color: var(--editor-accent, #8bc48b);
|
|
1455
|
+
font-weight: 500;
|
|
1456
|
+
}
|
|
1457
|
+
.status-campfire {
|
|
1458
|
+
color: #f0a060;
|
|
1459
|
+
}
|
|
1460
|
+
.status-mode {
|
|
1461
|
+
color: #7ab3ff;
|
|
1462
|
+
font-size: 0.75rem;
|
|
1463
|
+
}
|
|
2191
1464
|
@keyframes pulse {
|
|
2192
|
-
0%,
|
|
2193
|
-
|
|
2194
|
-
opacity: 1;
|
|
2195
|
-
}
|
|
2196
|
-
50% {
|
|
2197
|
-
opacity: 0.5;
|
|
2198
|
-
}
|
|
1465
|
+
0%, 100% { opacity: 1; }
|
|
1466
|
+
50% { opacity: 0.5; }
|
|
2199
1467
|
}
|
|
2200
|
-
/* Responsive */
|
|
2201
1468
|
@media (max-width: 768px) {
|
|
2202
1469
|
.editor-area.split {
|
|
2203
1470
|
flex-direction: column;
|
|
@@ -2220,136 +1487,6 @@
|
|
|
2220
1487
|
font-size: 0.75rem;
|
|
2221
1488
|
}
|
|
2222
1489
|
}
|
|
2223
|
-
/* Full Preview Button */
|
|
2224
|
-
.full-preview-btn {
|
|
2225
|
-
background: #2d3a4d;
|
|
2226
|
-
color: #7ab3ff;
|
|
2227
|
-
border-color: #3d4a5d;
|
|
2228
|
-
}
|
|
2229
|
-
.full-preview-btn:hover {
|
|
2230
|
-
background: #3d4a5d;
|
|
2231
|
-
color: #9ac5ff;
|
|
2232
|
-
}
|
|
2233
|
-
/* Full Preview Modal */
|
|
2234
|
-
.full-preview-modal {
|
|
2235
|
-
position: fixed;
|
|
2236
|
-
inset: 0;
|
|
2237
|
-
z-index: 1000;
|
|
2238
|
-
display: flex;
|
|
2239
|
-
align-items: center;
|
|
2240
|
-
justify-content: center;
|
|
2241
|
-
}
|
|
2242
|
-
.full-preview-backdrop {
|
|
2243
|
-
position: absolute;
|
|
2244
|
-
inset: 0;
|
|
2245
|
-
background: rgba(0, 0, 0, 0.7);
|
|
2246
|
-
}
|
|
2247
|
-
.full-preview-container {
|
|
2248
|
-
position: relative;
|
|
2249
|
-
width: 90%;
|
|
2250
|
-
max-width: 900px;
|
|
2251
|
-
height: 90vh;
|
|
2252
|
-
background: var(--color-bg, var(--light-bg-primary));
|
|
2253
|
-
border-radius: 12px;
|
|
2254
|
-
display: flex;
|
|
2255
|
-
flex-direction: column;
|
|
2256
|
-
overflow: hidden;
|
|
2257
|
-
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
|
|
2258
|
-
}
|
|
2259
|
-
:global(.dark) .full-preview-container {
|
|
2260
|
-
background: var(--color-bg-dark, #0d1117);
|
|
2261
|
-
}
|
|
2262
|
-
.full-preview-header {
|
|
2263
|
-
display: flex;
|
|
2264
|
-
justify-content: space-between;
|
|
2265
|
-
align-items: center;
|
|
2266
|
-
padding: 1rem 1.5rem;
|
|
2267
|
-
background: var(--color-bg-secondary, var(--light-bg-tertiary));
|
|
2268
|
-
border-bottom: 1px solid var(--color-border, var(--light-border-primary));
|
|
2269
|
-
flex-shrink: 0;
|
|
2270
|
-
}
|
|
2271
|
-
:global(.dark) .full-preview-header {
|
|
2272
|
-
background: var(--color-bg-secondary-dark, var(--light-bg-primary));
|
|
2273
|
-
border-color: var(--color-border-dark, var(--light-border-secondary));
|
|
2274
|
-
}
|
|
2275
|
-
.full-preview-header h2 {
|
|
2276
|
-
margin: 0;
|
|
2277
|
-
font-size: 0.9rem;
|
|
2278
|
-
font-weight: 500;
|
|
2279
|
-
font-family: "JetBrains Mono", "Fira Code", monospace;
|
|
2280
|
-
color: #8bc48b;
|
|
2281
|
-
}
|
|
2282
|
-
:global(.dark) .full-preview-header h2 {
|
|
2283
|
-
color: #8bc48b;
|
|
2284
|
-
}
|
|
2285
|
-
.full-preview-close {
|
|
2286
|
-
padding: 0.3rem 0.5rem;
|
|
2287
|
-
background: transparent;
|
|
2288
|
-
color: #7a9a7a;
|
|
2289
|
-
border: none;
|
|
2290
|
-
font-size: 0.85rem;
|
|
2291
|
-
font-family: "JetBrains Mono", "Fira Code", monospace;
|
|
2292
|
-
cursor: pointer;
|
|
2293
|
-
transition: color 0.1s ease;
|
|
2294
|
-
}
|
|
2295
|
-
.full-preview-close:hover {
|
|
2296
|
-
color: #a8dca8;
|
|
2297
|
-
}
|
|
2298
|
-
.full-preview-scroll {
|
|
2299
|
-
flex: 1;
|
|
2300
|
-
overflow-y: auto;
|
|
2301
|
-
padding: 2rem;
|
|
2302
|
-
}
|
|
2303
|
-
.full-preview-article {
|
|
2304
|
-
max-width: 800px;
|
|
2305
|
-
margin: 0 auto;
|
|
2306
|
-
}
|
|
2307
|
-
/* Post meta styling in full preview */
|
|
2308
|
-
.full-preview-article .post-meta {
|
|
2309
|
-
display: flex;
|
|
2310
|
-
align-items: center;
|
|
2311
|
-
gap: 1rem;
|
|
2312
|
-
flex-wrap: wrap;
|
|
2313
|
-
margin-top: 1rem;
|
|
2314
|
-
}
|
|
2315
|
-
.full-preview-article time {
|
|
2316
|
-
color: var(--light-text-light);
|
|
2317
|
-
font-size: 1rem;
|
|
2318
|
-
transition: color 0.3s ease;
|
|
2319
|
-
}
|
|
2320
|
-
:global(.dark) .full-preview-article time {
|
|
2321
|
-
color: var(--color-text-subtle-dark, #666);
|
|
2322
|
-
}
|
|
2323
|
-
.full-preview-article .tags {
|
|
2324
|
-
display: flex;
|
|
2325
|
-
gap: 0.5rem;
|
|
2326
|
-
flex-wrap: wrap;
|
|
2327
|
-
}
|
|
2328
|
-
.full-preview-article .tag {
|
|
2329
|
-
padding: 0.25rem 0.75rem;
|
|
2330
|
-
background: var(--tag-bg, #2c5f2d);
|
|
2331
|
-
color: white;
|
|
2332
|
-
border-radius: 12px;
|
|
2333
|
-
font-size: 0.8rem;
|
|
2334
|
-
font-weight: 500;
|
|
2335
|
-
}
|
|
2336
|
-
/* Line numbers scroll sync */
|
|
2337
|
-
.line-numbers {
|
|
2338
|
-
overflow: hidden;
|
|
2339
|
-
}
|
|
2340
|
-
/* Status bar enhancements */
|
|
2341
|
-
.status-goal {
|
|
2342
|
-
color: var(--editor-accent, #8bc48b);
|
|
2343
|
-
font-weight: 500;
|
|
2344
|
-
}
|
|
2345
|
-
.status-campfire {
|
|
2346
|
-
color: #f0a060;
|
|
2347
|
-
}
|
|
2348
|
-
.status-mode {
|
|
2349
|
-
color: #7ab3ff;
|
|
2350
|
-
font-size: 0.75rem;
|
|
2351
|
-
}
|
|
2352
|
-
/* Zen Mode Styles */
|
|
2353
1490
|
.editor-container.zen-mode {
|
|
2354
1491
|
position: fixed;
|
|
2355
1492
|
inset: 0;
|
|
@@ -2359,14 +1496,12 @@
|
|
|
2359
1496
|
}
|
|
2360
1497
|
.editor-container.zen-mode .toolbar {
|
|
2361
1498
|
opacity: 0.3;
|
|
2362
|
-
transition: opacity 0.3s ease;
|
|
2363
1499
|
}
|
|
2364
1500
|
.editor-container.zen-mode .toolbar:hover {
|
|
2365
1501
|
opacity: 1;
|
|
2366
1502
|
}
|
|
2367
1503
|
.editor-container.zen-mode .status-bar {
|
|
2368
1504
|
opacity: 0.5;
|
|
2369
|
-
transition: opacity 0.3s ease;
|
|
2370
1505
|
}
|
|
2371
1506
|
.editor-container.zen-mode .status-bar:hover {
|
|
2372
1507
|
opacity: 1;
|
|
@@ -2374,7 +1509,6 @@
|
|
|
2374
1509
|
.editor-container.zen-mode .editor-area {
|
|
2375
1510
|
height: calc(100vh - 80px);
|
|
2376
1511
|
}
|
|
2377
|
-
/* Campfire Mode Styles */
|
|
2378
1512
|
.editor-container.campfire-mode {
|
|
2379
1513
|
border-color: #8b5a2b;
|
|
2380
1514
|
box-shadow: 0 0 30px rgba(240, 160, 96, 0.15);
|
|
@@ -2393,6 +1527,7 @@
|
|
|
2393
1527
|
color: #f0d0a0;
|
|
2394
1528
|
z-index: 1000;
|
|
2395
1529
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
|
|
1530
|
+
animation: fade-in 0.3s ease;
|
|
2396
1531
|
}
|
|
2397
1532
|
.campfire-ember {
|
|
2398
1533
|
width: 12px;
|
|
@@ -2402,12 +1537,8 @@
|
|
|
2402
1537
|
animation: ember-glow 2s ease-in-out infinite;
|
|
2403
1538
|
}
|
|
2404
1539
|
@keyframes ember-glow {
|
|
2405
|
-
0%, 100% {
|
|
2406
|
-
|
|
2407
|
-
}
|
|
2408
|
-
50% {
|
|
2409
|
-
box-shadow: 0 0 12px #f0a060, 0 0 24px rgba(240, 160, 96, 0.6);
|
|
2410
|
-
}
|
|
1540
|
+
0%, 100% { box-shadow: 0 0 8px #ff6b35, 0 0 16px rgba(240, 107, 53, 0.5); }
|
|
1541
|
+
50% { box-shadow: 0 0 12px #f0a060, 0 0 24px rgba(240, 160, 96, 0.6); }
|
|
2411
1542
|
}
|
|
2412
1543
|
.campfire-stats {
|
|
2413
1544
|
display: flex;
|
|
@@ -2436,7 +1567,6 @@
|
|
|
2436
1567
|
.campfire-end:hover {
|
|
2437
1568
|
color: #f0d0a0;
|
|
2438
1569
|
}
|
|
2439
|
-
/* Slash Commands Menu */
|
|
2440
1570
|
.slash-menu {
|
|
2441
1571
|
position: fixed;
|
|
2442
1572
|
top: 50%;
|
|
@@ -2450,6 +1580,7 @@
|
|
|
2450
1580
|
border-radius: 8px;
|
|
2451
1581
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
|
2452
1582
|
z-index: 1001;
|
|
1583
|
+
animation: scale-in 0.15s ease;
|
|
2453
1584
|
}
|
|
2454
1585
|
.slash-menu-header {
|
|
2455
1586
|
padding: 0.5rem 0.75rem;
|
|
@@ -2486,7 +1617,6 @@
|
|
|
2486
1617
|
font-size: 0.8rem;
|
|
2487
1618
|
text-align: center;
|
|
2488
1619
|
}
|
|
2489
|
-
/* Command Palette */
|
|
2490
1620
|
.command-palette-overlay {
|
|
2491
1621
|
position: fixed;
|
|
2492
1622
|
inset: 0;
|
|
@@ -2505,6 +1635,7 @@
|
|
|
2505
1635
|
border-radius: 8px;
|
|
2506
1636
|
box-shadow: 0 16px 64px rgba(0, 0, 0, 0.6);
|
|
2507
1637
|
overflow: hidden;
|
|
1638
|
+
animation: slide-down 0.2s ease;
|
|
2508
1639
|
}
|
|
2509
1640
|
.command-palette-input {
|
|
2510
1641
|
width: 100%;
|
|
@@ -2554,107 +1685,17 @@
|
|
|
2554
1685
|
color: #6a6a6a;
|
|
2555
1686
|
font-family: "JetBrains Mono", monospace;
|
|
2556
1687
|
}
|
|
2557
|
-
/* Mode Transitions */
|
|
2558
|
-
.editor-container {
|
|
2559
|
-
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
|
2560
|
-
}
|
|
2561
|
-
.toolbar,
|
|
2562
|
-
.status-bar {
|
|
2563
|
-
transition: opacity 0.3s ease;
|
|
2564
|
-
}
|
|
2565
|
-
.campfire-controls {
|
|
2566
|
-
animation: fade-in 0.3s ease;
|
|
2567
|
-
}
|
|
2568
1688
|
@keyframes fade-in {
|
|
2569
|
-
from {
|
|
2570
|
-
|
|
2571
|
-
transform: translateY(10px);
|
|
2572
|
-
}
|
|
2573
|
-
to {
|
|
2574
|
-
opacity: 1;
|
|
2575
|
-
transform: translateY(0);
|
|
2576
|
-
}
|
|
2577
|
-
}
|
|
2578
|
-
.slash-menu,
|
|
2579
|
-
.command-palette {
|
|
2580
|
-
animation: scale-in 0.15s ease;
|
|
1689
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
1690
|
+
to { opacity: 1; transform: translateY(0); }
|
|
2581
1691
|
}
|
|
2582
1692
|
@keyframes scale-in {
|
|
2583
|
-
from {
|
|
2584
|
-
|
|
2585
|
-
transform: translate(-50%, -50%) scale(0.95);
|
|
2586
|
-
}
|
|
2587
|
-
to {
|
|
2588
|
-
opacity: 1;
|
|
2589
|
-
transform: translate(-50%, -50%) scale(1);
|
|
2590
|
-
}
|
|
2591
|
-
}
|
|
2592
|
-
.command-palette {
|
|
2593
|
-
animation: slide-down 0.2s ease;
|
|
1693
|
+
from { opacity: 0; transform: translate(-50%, -50%) scale(0.95); }
|
|
1694
|
+
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
2594
1695
|
}
|
|
2595
1696
|
@keyframes slide-down {
|
|
2596
|
-
from {
|
|
2597
|
-
|
|
2598
|
-
transform: translateY(-10px);
|
|
2599
|
-
}
|
|
2600
|
-
to {
|
|
2601
|
-
opacity: 1;
|
|
2602
|
-
transform: translateY(0);
|
|
2603
|
-
}
|
|
2604
|
-
}
|
|
2605
|
-
/* Snippets Modal */
|
|
2606
|
-
.snippets-modal-overlay {
|
|
2607
|
-
position: fixed;
|
|
2608
|
-
inset: 0;
|
|
2609
|
-
background: rgba(0, 0, 0, 0.6);
|
|
2610
|
-
display: flex;
|
|
2611
|
-
align-items: center;
|
|
2612
|
-
justify-content: center;
|
|
2613
|
-
z-index: 1003;
|
|
2614
|
-
animation: fade-in 0.2s ease;
|
|
2615
|
-
}
|
|
2616
|
-
.snippets-modal {
|
|
2617
|
-
width: 90%;
|
|
2618
|
-
max-width: 500px;
|
|
2619
|
-
max-height: 80vh;
|
|
2620
|
-
background: var(--light-bg-primary);
|
|
2621
|
-
border: 1px solid var(--light-border-primary);
|
|
2622
|
-
border-radius: 12px;
|
|
2623
|
-
display: flex;
|
|
2624
|
-
flex-direction: column;
|
|
2625
|
-
overflow: hidden;
|
|
2626
|
-
box-shadow: 0 16px 64px rgba(0, 0, 0, 0.5);
|
|
2627
|
-
animation: scale-in 0.2s ease;
|
|
2628
|
-
}
|
|
2629
|
-
.snippets-modal-header {
|
|
2630
|
-
display: flex;
|
|
2631
|
-
justify-content: space-between;
|
|
2632
|
-
align-items: center;
|
|
2633
|
-
padding: 1rem 1.25rem;
|
|
2634
|
-
background: #252526;
|
|
2635
|
-
border-bottom: 1px solid var(--light-border-primary);
|
|
2636
|
-
}
|
|
2637
|
-
.snippets-modal-header h3 {
|
|
2638
|
-
margin: 0;
|
|
2639
|
-
font-size: 0.9rem;
|
|
2640
|
-
font-weight: 500;
|
|
2641
|
-
font-family: "JetBrains Mono", "Fira Code", monospace;
|
|
2642
|
-
color: #8bc48b;
|
|
2643
|
-
}
|
|
2644
|
-
.snippets-modal-close {
|
|
2645
|
-
display: flex;
|
|
2646
|
-
align-items: center;
|
|
2647
|
-
justify-content: center;
|
|
2648
|
-
background: transparent;
|
|
2649
|
-
border: none;
|
|
2650
|
-
color: #7a9a7a;
|
|
2651
|
-
font-size: 0.85rem;
|
|
2652
|
-
font-family: "JetBrains Mono", "Fira Code", monospace;
|
|
2653
|
-
cursor: pointer;
|
|
2654
|
-
transition: color 0.1s ease;
|
|
2655
|
-
}
|
|
2656
|
-
.snippets-modal-close:hover {
|
|
2657
|
-
color: #a8dca8;
|
|
1697
|
+
from { opacity: 0; transform: translateY(-10px); }
|
|
1698
|
+
to { opacity: 1; transform: translateY(0); }
|
|
2658
1699
|
}
|
|
2659
1700
|
.snippets-modal-body {
|
|
2660
1701
|
padding: 1.25rem;
|
|
@@ -2675,28 +1716,23 @@
|
|
|
2675
1716
|
font-weight: 500;
|
|
2676
1717
|
color: #a8dca8;
|
|
2677
1718
|
}
|
|
2678
|
-
.snippet-field input,
|
|
2679
1719
|
.snippet-field textarea {
|
|
2680
1720
|
padding: 0.6rem 0.75rem;
|
|
2681
1721
|
background: #252526;
|
|
2682
1722
|
border: 1px solid var(--light-border-primary);
|
|
2683
1723
|
border-radius: 6px;
|
|
2684
1724
|
color: #d4d4d4;
|
|
2685
|
-
font-family:
|
|
1725
|
+
font-family: "JetBrains Mono", "Fira Code", monospace;
|
|
2686
1726
|
font-size: 0.9rem;
|
|
1727
|
+
line-height: 1.5;
|
|
1728
|
+
resize: vertical;
|
|
1729
|
+
min-height: 100px;
|
|
2687
1730
|
transition: border-color 0.2s ease;
|
|
2688
1731
|
}
|
|
2689
|
-
.snippet-field input:focus,
|
|
2690
1732
|
.snippet-field textarea:focus {
|
|
2691
1733
|
outline: none;
|
|
2692
1734
|
border-color: #4a7c4a;
|
|
2693
1735
|
}
|
|
2694
|
-
.snippet-field textarea {
|
|
2695
|
-
resize: vertical;
|
|
2696
|
-
min-height: 100px;
|
|
2697
|
-
font-family: "JetBrains Mono", "Fira Code", monospace;
|
|
2698
|
-
line-height: 1.5;
|
|
2699
|
-
}
|
|
2700
1736
|
.field-hint {
|
|
2701
1737
|
font-size: 0.75rem;
|
|
2702
1738
|
color: #6a6a6a;
|
|
@@ -2715,38 +1751,6 @@
|
|
|
2715
1751
|
gap: 0.5rem;
|
|
2716
1752
|
margin-left: auto;
|
|
2717
1753
|
}
|
|
2718
|
-
.snippet-btn {
|
|
2719
|
-
padding: 0.3rem 0.5rem;
|
|
2720
|
-
border-radius: 0;
|
|
2721
|
-
font-size: 0.85rem;
|
|
2722
|
-
font-family: "JetBrains Mono", "Fira Code", monospace;
|
|
2723
|
-
cursor: pointer;
|
|
2724
|
-
transition: color 0.1s ease;
|
|
2725
|
-
background: transparent;
|
|
2726
|
-
border: none;
|
|
2727
|
-
}
|
|
2728
|
-
.snippet-btn.save {
|
|
2729
|
-
color: #8bc48b;
|
|
2730
|
-
}
|
|
2731
|
-
.snippet-btn.save:hover:not(:disabled) {
|
|
2732
|
-
color: #c8f0c8;
|
|
2733
|
-
}
|
|
2734
|
-
.snippet-btn.save:disabled {
|
|
2735
|
-
opacity: 0.4;
|
|
2736
|
-
cursor: not-allowed;
|
|
2737
|
-
}
|
|
2738
|
-
.snippet-btn.cancel {
|
|
2739
|
-
color: #9d9d9d;
|
|
2740
|
-
}
|
|
2741
|
-
.snippet-btn.cancel:hover {
|
|
2742
|
-
color: #d4d4d4;
|
|
2743
|
-
}
|
|
2744
|
-
.snippet-btn.delete {
|
|
2745
|
-
color: #e08080;
|
|
2746
|
-
}
|
|
2747
|
-
.snippet-btn.delete:hover {
|
|
2748
|
-
color: #ff9090;
|
|
2749
|
-
}
|
|
2750
1754
|
.snippets-list-divider {
|
|
2751
1755
|
display: flex;
|
|
2752
1756
|
align-items: center;
|
|
@@ -2800,7 +1804,6 @@
|
|
|
2800
1804
|
padding: 0.15rem 0.4rem;
|
|
2801
1805
|
border-radius: 3px;
|
|
2802
1806
|
}
|
|
2803
|
-
/* Status Bar Sound Button */
|
|
2804
1807
|
.status-sound-btn {
|
|
2805
1808
|
display: flex;
|
|
2806
1809
|
align-items: center;
|
|
@@ -2830,16 +1833,9 @@
|
|
|
2830
1833
|
animation: sound-pulse 1.5s ease-in-out infinite;
|
|
2831
1834
|
}
|
|
2832
1835
|
@keyframes sound-pulse {
|
|
2833
|
-
0%, 100% {
|
|
2834
|
-
|
|
2835
|
-
transform: scale(0.8);
|
|
2836
|
-
}
|
|
2837
|
-
50% {
|
|
2838
|
-
opacity: 1;
|
|
2839
|
-
transform: scale(1);
|
|
2840
|
-
}
|
|
1836
|
+
0%, 100% { opacity: 0.4; transform: scale(0.8); }
|
|
1837
|
+
50% { opacity: 1; transform: scale(1); }
|
|
2841
1838
|
}
|
|
2842
|
-
/* Sound Panel */
|
|
2843
1839
|
.sound-panel {
|
|
2844
1840
|
position: fixed;
|
|
2845
1841
|
bottom: 3.5rem;
|
|
@@ -2853,14 +1849,8 @@
|
|
|
2853
1849
|
animation: slide-up 0.2s ease;
|
|
2854
1850
|
}
|
|
2855
1851
|
@keyframes slide-up {
|
|
2856
|
-
from {
|
|
2857
|
-
|
|
2858
|
-
transform: translateY(10px);
|
|
2859
|
-
}
|
|
2860
|
-
to {
|
|
2861
|
-
opacity: 1;
|
|
2862
|
-
transform: translateY(0);
|
|
2863
|
-
}
|
|
1852
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
1853
|
+
to { opacity: 1; transform: translateY(0); }
|
|
2864
1854
|
}
|
|
2865
1855
|
.sound-panel-header {
|
|
2866
1856
|
display: flex;
|
|
@@ -2907,6 +1897,8 @@
|
|
|
2907
1897
|
border-radius: 8px;
|
|
2908
1898
|
cursor: pointer;
|
|
2909
1899
|
transition: all 0.15s ease;
|
|
1900
|
+
font-size: 0.65rem;
|
|
1901
|
+
color: #9d9d9d;
|
|
2910
1902
|
}
|
|
2911
1903
|
.sound-option:hover {
|
|
2912
1904
|
background: var(--light-bg-tertiary);
|
|
@@ -2915,22 +1907,12 @@
|
|
|
2915
1907
|
.sound-option.active {
|
|
2916
1908
|
background: var(--light-border-secondary);
|
|
2917
1909
|
border-color: #4a7c4a;
|
|
1910
|
+
color: #a8dca8;
|
|
2918
1911
|
}
|
|
2919
1912
|
.sound-option.playing {
|
|
2920
1913
|
border-color: #8bc48b;
|
|
2921
1914
|
box-shadow: 0 0 8px rgba(139, 196, 139, 0.3);
|
|
2922
1915
|
}
|
|
2923
|
-
.sound-icon {
|
|
2924
|
-
font-size: 1.25rem;
|
|
2925
|
-
}
|
|
2926
|
-
.sound-name {
|
|
2927
|
-
font-size: 0.65rem;
|
|
2928
|
-
color: #9d9d9d;
|
|
2929
|
-
text-align: center;
|
|
2930
|
-
}
|
|
2931
|
-
.sound-option.active .sound-name {
|
|
2932
|
-
color: #a8dca8;
|
|
2933
|
-
}
|
|
2934
1916
|
.sound-controls {
|
|
2935
1917
|
display: flex;
|
|
2936
1918
|
align-items: center;
|
|
@@ -3011,14 +1993,102 @@
|
|
|
3011
1993
|
font-size: 0.7rem;
|
|
3012
1994
|
color: #6a6a6a;
|
|
3013
1995
|
}
|
|
3014
|
-
.
|
|
1996
|
+
.full-preview-modal {
|
|
1997
|
+
position: fixed;
|
|
1998
|
+
inset: 0;
|
|
1999
|
+
z-index: 1000;
|
|
2000
|
+
display: flex;
|
|
2001
|
+
align-items: center;
|
|
2002
|
+
justify-content: center;
|
|
2003
|
+
}
|
|
2004
|
+
.full-preview-backdrop {
|
|
2005
|
+
position: absolute;
|
|
2006
|
+
inset: 0;
|
|
2007
|
+
background: rgba(0, 0, 0, 0.7);
|
|
2008
|
+
}
|
|
2009
|
+
.full-preview-container {
|
|
2010
|
+
position: relative;
|
|
2011
|
+
width: 90%;
|
|
2012
|
+
max-width: 900px;
|
|
2013
|
+
height: 90vh;
|
|
2014
|
+
background: var(--color-bg, var(--light-bg-primary));
|
|
2015
|
+
border-radius: 12px;
|
|
2016
|
+
display: flex;
|
|
2017
|
+
flex-direction: column;
|
|
2018
|
+
overflow: hidden;
|
|
2019
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
|
|
2020
|
+
}
|
|
2021
|
+
:global(.dark) .full-preview-container {
|
|
2022
|
+
background: var(--color-bg-dark, #0d1117);
|
|
2023
|
+
}
|
|
2024
|
+
.full-preview-header {
|
|
2025
|
+
display: flex;
|
|
2026
|
+
justify-content: space-between;
|
|
2027
|
+
align-items: center;
|
|
2028
|
+
padding: 1rem 1.5rem;
|
|
2029
|
+
background: var(--color-bg-secondary, var(--light-bg-tertiary));
|
|
2030
|
+
border-bottom: 1px solid var(--color-border, var(--light-border-primary));
|
|
2031
|
+
flex-shrink: 0;
|
|
2032
|
+
}
|
|
2033
|
+
:global(.dark) .full-preview-header {
|
|
2034
|
+
background: var(--color-bg-secondary-dark, var(--light-bg-primary));
|
|
2035
|
+
border-color: var(--color-border-dark, var(--light-border-secondary));
|
|
2036
|
+
}
|
|
2037
|
+
.full-preview-header h2 {
|
|
2038
|
+
margin: 0;
|
|
2039
|
+
font-size: 0.9rem;
|
|
2040
|
+
font-weight: 500;
|
|
2041
|
+
font-family: "JetBrains Mono", "Fira Code", monospace;
|
|
2042
|
+
color: #8bc48b;
|
|
2043
|
+
}
|
|
2044
|
+
.full-preview-close {
|
|
2045
|
+
padding: 0.3rem 0.5rem;
|
|
2046
|
+
background: transparent;
|
|
2047
|
+
color: #7a9a7a;
|
|
2048
|
+
border: none;
|
|
3015
2049
|
font-size: 0.85rem;
|
|
2050
|
+
font-family: "JetBrains Mono", "Fira Code", monospace;
|
|
2051
|
+
cursor: pointer;
|
|
2052
|
+
transition: color 0.1s ease;
|
|
3016
2053
|
}
|
|
3017
|
-
.
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
2054
|
+
.full-preview-close:hover {
|
|
2055
|
+
color: #a8dca8;
|
|
2056
|
+
}
|
|
2057
|
+
.full-preview-scroll {
|
|
2058
|
+
flex: 1;
|
|
2059
|
+
overflow-y: auto;
|
|
2060
|
+
padding: 2rem;
|
|
2061
|
+
}
|
|
2062
|
+
.full-preview-article {
|
|
2063
|
+
max-width: 800px;
|
|
2064
|
+
margin: 0 auto;
|
|
2065
|
+
}
|
|
2066
|
+
.full-preview-article .post-meta {
|
|
2067
|
+
display: flex;
|
|
2068
|
+
align-items: center;
|
|
2069
|
+
gap: 1rem;
|
|
2070
|
+
flex-wrap: wrap;
|
|
2071
|
+
margin-top: 1rem;
|
|
2072
|
+
}
|
|
2073
|
+
.full-preview-article time {
|
|
2074
|
+
color: var(--light-text-light);
|
|
2075
|
+
font-size: 1rem;
|
|
2076
|
+
transition: color 0.3s ease;
|
|
2077
|
+
}
|
|
2078
|
+
:global(.dark) .full-preview-article time {
|
|
2079
|
+
color: var(--color-text-subtle-dark, #666);
|
|
2080
|
+
}
|
|
2081
|
+
.full-preview-article .tags {
|
|
2082
|
+
display: flex;
|
|
2083
|
+
gap: 0.5rem;
|
|
2084
|
+
flex-wrap: wrap;
|
|
2085
|
+
}
|
|
2086
|
+
.full-preview-article .tag {
|
|
2087
|
+
padding: 0.25rem 0.75rem;
|
|
2088
|
+
background: var(--tag-bg, #2c5f2d);
|
|
2089
|
+
color: white;
|
|
2090
|
+
border-radius: 12px;
|
|
2091
|
+
font-size: 0.8rem;
|
|
2092
|
+
font-weight: 500;
|
|
3023
2093
|
}
|
|
3024
2094
|
</style>
|