quasar-ui-danx 0.5.0 → 0.5.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/danx.es.js +12797 -8181
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +192 -120
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +8 -1
- package/src/components/Utility/Code/CodeViewer.vue +31 -14
- package/src/components/Utility/Code/CodeViewerCollapsed.vue +2 -0
- package/src/components/Utility/Code/CodeViewerFooter.vue +1 -1
- package/src/components/Utility/Code/LanguageBadge.vue +278 -5
- package/src/components/Utility/Code/index.ts +3 -0
- package/src/components/Utility/Markdown/ContextMenu.vue +314 -0
- package/src/components/Utility/Markdown/HotkeyHelpPopover.vue +259 -0
- package/src/components/Utility/Markdown/LineTypeMenu.vue +226 -0
- package/src/components/Utility/Markdown/LinkPopover.vue +331 -0
- package/src/components/Utility/Markdown/MarkdownEditor.vue +228 -0
- package/src/components/Utility/Markdown/MarkdownEditorContent.vue +235 -0
- package/src/components/Utility/Markdown/MarkdownEditorFooter.vue +50 -0
- package/src/components/Utility/Markdown/TablePopover.vue +420 -0
- package/src/components/Utility/Markdown/index.ts +11 -0
- package/src/components/Utility/Markdown/types.ts +27 -0
- package/src/components/Utility/index.ts +1 -0
- package/src/composables/index.ts +1 -0
- package/src/composables/markdown/features/useBlockquotes.spec.ts +428 -0
- package/src/composables/markdown/features/useBlockquotes.ts +248 -0
- package/src/composables/markdown/features/useCodeBlockManager.ts +369 -0
- package/src/composables/markdown/features/useCodeBlocks.spec.ts +779 -0
- package/src/composables/markdown/features/useCodeBlocks.ts +774 -0
- package/src/composables/markdown/features/useContextMenu.ts +444 -0
- package/src/composables/markdown/features/useFocusTracking.ts +116 -0
- package/src/composables/markdown/features/useHeadings.spec.ts +834 -0
- package/src/composables/markdown/features/useHeadings.ts +290 -0
- package/src/composables/markdown/features/useInlineFormatting.spec.ts +705 -0
- package/src/composables/markdown/features/useInlineFormatting.ts +402 -0
- package/src/composables/markdown/features/useLineTypeMenu.ts +285 -0
- package/src/composables/markdown/features/useLinks.spec.ts +369 -0
- package/src/composables/markdown/features/useLinks.ts +374 -0
- package/src/composables/markdown/features/useLists.spec.ts +834 -0
- package/src/composables/markdown/features/useLists.ts +747 -0
- package/src/composables/markdown/features/usePopoverManager.ts +181 -0
- package/src/composables/markdown/features/useTables.spec.ts +1601 -0
- package/src/composables/markdown/features/useTables.ts +1107 -0
- package/src/composables/markdown/index.ts +16 -0
- package/src/composables/markdown/useMarkdownEditor.spec.ts +332 -0
- package/src/composables/markdown/useMarkdownEditor.ts +1068 -0
- package/src/composables/markdown/useMarkdownHotkeys.spec.ts +791 -0
- package/src/composables/markdown/useMarkdownHotkeys.ts +266 -0
- package/src/composables/markdown/useMarkdownSelection.ts +219 -0
- package/src/composables/markdown/useMarkdownSync.ts +549 -0
- package/src/composables/useCodeViewerEditor.spec.ts +655 -0
- package/src/composables/useCodeViewerEditor.ts +174 -20
- package/src/helpers/formats/markdown/htmlToMarkdown/convertHeadings.ts +41 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/index.spec.ts +489 -0
- package/src/helpers/formats/markdown/htmlToMarkdown/index.ts +412 -0
- package/src/helpers/formats/markdown/index.ts +7 -0
- package/src/helpers/formats/markdown/linePatterns.spec.ts +495 -0
- package/src/helpers/formats/markdown/linePatterns.ts +172 -0
- package/src/test/helpers/editorTestUtils.spec.ts +296 -0
- package/src/test/helpers/editorTestUtils.ts +253 -0
- package/src/test/helpers/index.ts +1 -0
- package/src/test/setup.test.ts +12 -0
- package/src/test/setup.ts +12 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="dx-markdown-editor" :class="{ 'is-readonly': readonly }">
|
|
3
|
+
<div class="dx-markdown-editor-body" @contextmenu="contextMenu.show">
|
|
4
|
+
<!-- Floating line type menu positioned next to current block -->
|
|
5
|
+
<div
|
|
6
|
+
ref="menuContainerRef"
|
|
7
|
+
class="dx-line-type-menu-container"
|
|
8
|
+
:style="lineTypeMenu.menuStyle.value"
|
|
9
|
+
>
|
|
10
|
+
<LineTypeMenu
|
|
11
|
+
:current-type="lineTypeMenu.currentLineType.value"
|
|
12
|
+
@change="lineTypeMenu.onLineTypeChange"
|
|
13
|
+
/>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<MarkdownEditorContent
|
|
17
|
+
ref="contentRef"
|
|
18
|
+
:html="editor.renderedHtml.value"
|
|
19
|
+
:readonly="readonly"
|
|
20
|
+
:placeholder="placeholder"
|
|
21
|
+
@input="editor.onInput"
|
|
22
|
+
@keydown="editor.onKeyDown"
|
|
23
|
+
@blur="editor.onBlur"
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<MarkdownEditorFooter
|
|
28
|
+
:char-count="editor.charCount.value"
|
|
29
|
+
@show-hotkeys="editor.showHotkeyHelp"
|
|
30
|
+
/>
|
|
31
|
+
|
|
32
|
+
<HotkeyHelpPopover
|
|
33
|
+
v-if="editor.isShowingHotkeyHelp.value"
|
|
34
|
+
:hotkeys="editor.hotkeyDefinitions.value"
|
|
35
|
+
@close="editor.hideHotkeyHelp"
|
|
36
|
+
/>
|
|
37
|
+
|
|
38
|
+
<LinkPopover
|
|
39
|
+
v-if="linkPopover.isVisible.value"
|
|
40
|
+
:position="linkPopover.position.value"
|
|
41
|
+
:existing-url="linkPopover.existingUrl.value"
|
|
42
|
+
:selected-text="linkPopover.selectedText.value"
|
|
43
|
+
@submit="linkPopover.submit"
|
|
44
|
+
@cancel="linkPopover.cancel"
|
|
45
|
+
/>
|
|
46
|
+
|
|
47
|
+
<TablePopover
|
|
48
|
+
v-if="tablePopover.isVisible.value"
|
|
49
|
+
:position="tablePopover.position.value"
|
|
50
|
+
@submit="tablePopover.submit"
|
|
51
|
+
@cancel="tablePopover.cancel"
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
<ContextMenu
|
|
55
|
+
v-if="contextMenu.isVisible.value"
|
|
56
|
+
:position="contextMenu.position.value"
|
|
57
|
+
:items="contextMenu.items.value"
|
|
58
|
+
@close="contextMenu.hide"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<script setup lang="ts">
|
|
64
|
+
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
|
65
|
+
import { useContextMenu } from "../../../composables/markdown/features/useContextMenu";
|
|
66
|
+
import { useFocusTracking } from "../../../composables/markdown/features/useFocusTracking";
|
|
67
|
+
import { useLineTypeMenu } from "../../../composables/markdown/features/useLineTypeMenu";
|
|
68
|
+
import { useLinkPopover, useTablePopover } from "../../../composables/markdown/features/usePopoverManager";
|
|
69
|
+
import { useMarkdownEditor } from "../../../composables/markdown/useMarkdownEditor";
|
|
70
|
+
import ContextMenu from "./ContextMenu.vue";
|
|
71
|
+
import HotkeyHelpPopover from "./HotkeyHelpPopover.vue";
|
|
72
|
+
import LineTypeMenu from "./LineTypeMenu.vue";
|
|
73
|
+
import LinkPopover from "./LinkPopover.vue";
|
|
74
|
+
import MarkdownEditorContent from "./MarkdownEditorContent.vue";
|
|
75
|
+
import MarkdownEditorFooter from "./MarkdownEditorFooter.vue";
|
|
76
|
+
import TablePopover from "./TablePopover.vue";
|
|
77
|
+
|
|
78
|
+
export interface MarkdownEditorProps {
|
|
79
|
+
modelValue?: string;
|
|
80
|
+
placeholder?: string;
|
|
81
|
+
readonly?: boolean;
|
|
82
|
+
minHeight?: string;
|
|
83
|
+
maxHeight?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const props = withDefaults(defineProps<MarkdownEditorProps>(), {
|
|
87
|
+
modelValue: "",
|
|
88
|
+
placeholder: "Start typing...",
|
|
89
|
+
readonly: false,
|
|
90
|
+
minHeight: "100px",
|
|
91
|
+
maxHeight: "none"
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const emit = defineEmits<{
|
|
95
|
+
"update:modelValue": [value: string];
|
|
96
|
+
}>();
|
|
97
|
+
|
|
98
|
+
// Reference to the content component
|
|
99
|
+
const contentRef = ref<InstanceType<typeof MarkdownEditorContent> | null>(null);
|
|
100
|
+
|
|
101
|
+
// Reference to the menu container for focus handling
|
|
102
|
+
const menuContainerRef = ref<HTMLElement | null>(null);
|
|
103
|
+
|
|
104
|
+
// Get the actual HTMLElement from the content component
|
|
105
|
+
const contentElementRef = computed(() => contentRef.value?.containerRef || null);
|
|
106
|
+
|
|
107
|
+
// Initialize popover managers
|
|
108
|
+
const linkPopover = useLinkPopover();
|
|
109
|
+
const tablePopover = useTablePopover();
|
|
110
|
+
|
|
111
|
+
// Initialize the markdown editor composable
|
|
112
|
+
const editor = useMarkdownEditor({
|
|
113
|
+
contentRef: contentElementRef,
|
|
114
|
+
initialValue: props.modelValue,
|
|
115
|
+
onEmitValue: (markdown: string) => {
|
|
116
|
+
emit("update:modelValue", markdown);
|
|
117
|
+
},
|
|
118
|
+
onShowLinkPopover: linkPopover.show,
|
|
119
|
+
onShowTablePopover: tablePopover.show
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Initialize focus tracking
|
|
123
|
+
const focusTracking = useFocusTracking({
|
|
124
|
+
contentRef: contentElementRef,
|
|
125
|
+
menuContainerRef,
|
|
126
|
+
onSelectionChange: () => lineTypeMenu.updatePositionAndState()
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Initialize line type menu
|
|
130
|
+
const lineTypeMenu = useLineTypeMenu({
|
|
131
|
+
contentRef: contentElementRef,
|
|
132
|
+
editor,
|
|
133
|
+
isEditorFocused: focusTracking.isEditorFocused
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Initialize context menu
|
|
137
|
+
const contextMenu = useContextMenu({
|
|
138
|
+
editor,
|
|
139
|
+
readonly: computed(() => props.readonly)
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Setup line type menu listeners on mount
|
|
143
|
+
onMounted(() => {
|
|
144
|
+
lineTypeMenu.setupListeners();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Cleanup line type menu listeners on unmount
|
|
148
|
+
onUnmounted(() => {
|
|
149
|
+
lineTypeMenu.cleanupListeners();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Watch for external value changes
|
|
153
|
+
watch(
|
|
154
|
+
() => props.modelValue,
|
|
155
|
+
(newValue) => {
|
|
156
|
+
// Skip if this change originated from the editor itself (internal update)
|
|
157
|
+
// This prevents cursor jumping when the watch triggers after typing
|
|
158
|
+
if (editor.isInternalUpdate.value) {
|
|
159
|
+
editor.isInternalUpdate.value = false;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Only update if the value is different from current
|
|
164
|
+
if (newValue !== undefined) {
|
|
165
|
+
editor.setMarkdown(newValue);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// NOTE: Content is already initialized in useMarkdownEditor with initialValue.
|
|
171
|
+
// The v-html binding renders it, and the MutationObserver mounts CodeViewers.
|
|
172
|
+
// Calling setMarkdown again here would replace the DOM and cause race conditions
|
|
173
|
+
// with CodeViewer mounting. Only call setMarkdown for external value changes.
|
|
174
|
+
|
|
175
|
+
// Expose the editor for parent components that may need access
|
|
176
|
+
defineExpose({
|
|
177
|
+
editor,
|
|
178
|
+
setMarkdown: editor.setMarkdown
|
|
179
|
+
});
|
|
180
|
+
</script>
|
|
181
|
+
|
|
182
|
+
<style lang="scss">
|
|
183
|
+
.dx-markdown-editor {
|
|
184
|
+
display: flex;
|
|
185
|
+
flex-direction: column;
|
|
186
|
+
width: 100%;
|
|
187
|
+
border-radius: 0.375rem;
|
|
188
|
+
overflow: hidden;
|
|
189
|
+
|
|
190
|
+
&.is-readonly {
|
|
191
|
+
.dx-markdown-editor-content {
|
|
192
|
+
cursor: default;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.dx-line-type-menu-container {
|
|
196
|
+
display: none;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Body container with floating menu and content side by side
|
|
201
|
+
.dx-markdown-editor-body {
|
|
202
|
+
display: flex;
|
|
203
|
+
position: relative;
|
|
204
|
+
flex: 1;
|
|
205
|
+
overflow: visible;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Floating line type menu container - positioned outside editor bounds
|
|
209
|
+
.dx-line-type-menu-container {
|
|
210
|
+
position: absolute;
|
|
211
|
+
left: -1.75rem;
|
|
212
|
+
z-index: 10;
|
|
213
|
+
width: 1.75rem;
|
|
214
|
+
display: flex;
|
|
215
|
+
align-items: flex-start;
|
|
216
|
+
justify-content: center;
|
|
217
|
+
padding-top: 0.25rem;
|
|
218
|
+
transition: top 0.1s ease-out, opacity 0.15s ease;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Apply min/max height to content area (no left margin needed - menu is outside)
|
|
222
|
+
.dx-markdown-editor-content {
|
|
223
|
+
flex: 1;
|
|
224
|
+
min-height: v-bind(minHeight);
|
|
225
|
+
max-height: v-bind(maxHeight);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
</style>
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
ref="containerRef"
|
|
4
|
+
class="dx-markdown-editor-content dx-markdown-content"
|
|
5
|
+
:class="{ 'is-readonly': readonly, 'is-empty': isEmpty }"
|
|
6
|
+
:contenteditable="!readonly"
|
|
7
|
+
:data-placeholder="placeholder"
|
|
8
|
+
@input="$emit('input')"
|
|
9
|
+
@keydown="$emit('keydown', $event)"
|
|
10
|
+
@blur="$emit('blur')"
|
|
11
|
+
@click="handleClick"
|
|
12
|
+
v-html="html"
|
|
13
|
+
/>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import { computed, ref } from "vue";
|
|
18
|
+
|
|
19
|
+
export interface MarkdownEditorContentProps {
|
|
20
|
+
html: string;
|
|
21
|
+
readonly?: boolean;
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const props = withDefaults(defineProps<MarkdownEditorContentProps>(), {
|
|
26
|
+
readonly: false,
|
|
27
|
+
placeholder: "Start typing..."
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
defineEmits<{
|
|
31
|
+
input: [];
|
|
32
|
+
keydown: [event: KeyboardEvent];
|
|
33
|
+
blur: [];
|
|
34
|
+
}>();
|
|
35
|
+
|
|
36
|
+
const containerRef = ref<HTMLElement | null>(null);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Find the anchor element if the click target is inside one
|
|
40
|
+
*/
|
|
41
|
+
function findLinkAncestor(node: Node | null): HTMLAnchorElement | null {
|
|
42
|
+
if (!node || !containerRef.value) return null;
|
|
43
|
+
|
|
44
|
+
let current: Node | null = node;
|
|
45
|
+
while (current && current !== containerRef.value) {
|
|
46
|
+
if (current.nodeType === Node.ELEMENT_NODE && (current as Element).tagName === "A") {
|
|
47
|
+
return current as HTMLAnchorElement;
|
|
48
|
+
}
|
|
49
|
+
current = current.parentNode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Handle clicks in the editor content.
|
|
57
|
+
* Ctrl+Click (or Cmd+Click on Mac) opens links in a new tab.
|
|
58
|
+
*/
|
|
59
|
+
function handleClick(event: MouseEvent): void {
|
|
60
|
+
// Check if Ctrl (Windows/Linux) or Cmd (Mac) is held
|
|
61
|
+
const isModifierHeld = event.ctrlKey || event.metaKey;
|
|
62
|
+
if (!isModifierHeld) return;
|
|
63
|
+
|
|
64
|
+
// Find if the click was on or inside a link
|
|
65
|
+
const link = findLinkAncestor(event.target as Node);
|
|
66
|
+
if (!link) return;
|
|
67
|
+
|
|
68
|
+
const href = link.getAttribute("href");
|
|
69
|
+
if (!href) return;
|
|
70
|
+
|
|
71
|
+
// Prevent default behavior and open the link in a new tab
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
event.stopPropagation();
|
|
74
|
+
window.open(href, "_blank", "noopener,noreferrer");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const isEmpty = computed(() => {
|
|
78
|
+
return !props.html || props.html === "<p></p>" || props.html === "<p><br></p>";
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Expose containerRef for parent component
|
|
82
|
+
defineExpose({ containerRef });
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<style lang="scss">
|
|
86
|
+
.dx-markdown-editor-content {
|
|
87
|
+
min-height: 100px;
|
|
88
|
+
outline: none;
|
|
89
|
+
cursor: text;
|
|
90
|
+
border: 2px solid transparent;
|
|
91
|
+
border-radius: 0.375rem 0.375rem 0 0;
|
|
92
|
+
padding: 1rem;
|
|
93
|
+
background-color: #1e1e1e;
|
|
94
|
+
color: #d4d4d4;
|
|
95
|
+
transition: border-color 0.2s ease;
|
|
96
|
+
overflow: auto;
|
|
97
|
+
|
|
98
|
+
&:focus {
|
|
99
|
+
border-color: rgba(86, 156, 214, 0.6);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
&:hover:not(:focus):not(.is-readonly) {
|
|
103
|
+
border-color: rgba(86, 156, 214, 0.3);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
&.is-readonly {
|
|
107
|
+
cursor: default;
|
|
108
|
+
border-color: transparent;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Placeholder styling when empty
|
|
112
|
+
&.is-empty::before {
|
|
113
|
+
content: attr(data-placeholder);
|
|
114
|
+
color: #6b7280;
|
|
115
|
+
pointer-events: none;
|
|
116
|
+
position: absolute;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
&.is-empty {
|
|
120
|
+
position: relative;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Caret color
|
|
124
|
+
caret-color: #d4d4d4;
|
|
125
|
+
|
|
126
|
+
// Link styling - show pointer cursor and hint for Ctrl+Click
|
|
127
|
+
a {
|
|
128
|
+
cursor: pointer;
|
|
129
|
+
position: relative;
|
|
130
|
+
|
|
131
|
+
&:hover::after {
|
|
132
|
+
content: "Ctrl+Click to open";
|
|
133
|
+
position: absolute;
|
|
134
|
+
bottom: 100%;
|
|
135
|
+
left: 50%;
|
|
136
|
+
transform: translateX(-50%);
|
|
137
|
+
background: #374151;
|
|
138
|
+
color: #d1d5db;
|
|
139
|
+
padding: 0.25rem 0.5rem;
|
|
140
|
+
border-radius: 0.25rem;
|
|
141
|
+
font-size: 0.75rem;
|
|
142
|
+
white-space: nowrap;
|
|
143
|
+
z-index: 10;
|
|
144
|
+
pointer-events: none;
|
|
145
|
+
opacity: 0;
|
|
146
|
+
animation: fadeIn 0.2s ease 0.5s forwards;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@keyframes fadeIn {
|
|
151
|
+
to {
|
|
152
|
+
opacity: 1;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Alternating styles for nested ordered lists
|
|
157
|
+
// Level 1: decimal (1, 2, 3)
|
|
158
|
+
ol {
|
|
159
|
+
list-style-type: decimal;
|
|
160
|
+
|
|
161
|
+
// Level 2: lower-roman (i, ii, iii)
|
|
162
|
+
ol {
|
|
163
|
+
list-style-type: lower-roman;
|
|
164
|
+
|
|
165
|
+
// Level 3: lower-alpha (a, b, c)
|
|
166
|
+
ol {
|
|
167
|
+
list-style-type: lower-alpha;
|
|
168
|
+
|
|
169
|
+
// Level 4+: cycle back to decimal
|
|
170
|
+
ol {
|
|
171
|
+
list-style-type: decimal;
|
|
172
|
+
|
|
173
|
+
ol {
|
|
174
|
+
list-style-type: lower-roman;
|
|
175
|
+
|
|
176
|
+
ol {
|
|
177
|
+
list-style-type: lower-alpha;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Alternating styles for nested unordered lists
|
|
186
|
+
// Level 1: disc
|
|
187
|
+
ul {
|
|
188
|
+
list-style-type: disc;
|
|
189
|
+
|
|
190
|
+
// Level 2: circle
|
|
191
|
+
ul {
|
|
192
|
+
list-style-type: circle;
|
|
193
|
+
|
|
194
|
+
// Level 3: square
|
|
195
|
+
ul {
|
|
196
|
+
list-style-type: square;
|
|
197
|
+
|
|
198
|
+
// Level 4+: cycle back to disc
|
|
199
|
+
ul {
|
|
200
|
+
list-style-type: disc;
|
|
201
|
+
|
|
202
|
+
ul {
|
|
203
|
+
list-style-type: circle;
|
|
204
|
+
|
|
205
|
+
ul {
|
|
206
|
+
list-style-type: square;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Code block wrapper styling - distinct background to separate from editor content
|
|
215
|
+
.code-block-wrapper {
|
|
216
|
+
background: #0d1117;
|
|
217
|
+
border-radius: 0.375rem;
|
|
218
|
+
margin: 0.5rem 0;
|
|
219
|
+
border: 1px solid #30363d;
|
|
220
|
+
|
|
221
|
+
// Override CodeViewer backgrounds to be transparent so wrapper controls it
|
|
222
|
+
.dx-code-viewer {
|
|
223
|
+
.code-content {
|
|
224
|
+
background: transparent;
|
|
225
|
+
border-radius: 0.375rem 0.375rem 0 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.code-footer {
|
|
229
|
+
background: #161b22;
|
|
230
|
+
border-radius: 0 0 0.375rem 0.375rem;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
</style>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="dx-markdown-editor-footer flex items-center justify-between px-2 py-1">
|
|
3
|
+
<span class="char-count text-xs text-gray-500">
|
|
4
|
+
{{ charCount.toLocaleString() }} chars
|
|
5
|
+
</span>
|
|
6
|
+
<button
|
|
7
|
+
class="hotkey-help-btn text-gray-500 hover:text-gray-300 transition-colors p-1 rounded"
|
|
8
|
+
title="Keyboard shortcuts (Ctrl+?)"
|
|
9
|
+
type="button"
|
|
10
|
+
@click="$emit('show-hotkeys')"
|
|
11
|
+
>
|
|
12
|
+
<KeyboardIcon class="w-4 h-4" />
|
|
13
|
+
</button>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import { FaSolidKeyboard as KeyboardIcon } from "danx-icon";
|
|
19
|
+
|
|
20
|
+
export interface MarkdownEditorFooterProps {
|
|
21
|
+
charCount: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
defineProps<MarkdownEditorFooterProps>();
|
|
25
|
+
|
|
26
|
+
defineEmits<{
|
|
27
|
+
"show-hotkeys": [];
|
|
28
|
+
}>();
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<style lang="scss">
|
|
32
|
+
.dx-markdown-editor-footer {
|
|
33
|
+
background-color: #252526;
|
|
34
|
+
border-radius: 0 0 0.375rem 0.375rem;
|
|
35
|
+
flex-shrink: 0;
|
|
36
|
+
|
|
37
|
+
.hotkey-help-btn {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
background: transparent;
|
|
42
|
+
border: none;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
|
|
45
|
+
&:hover {
|
|
46
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
</style>
|