diff --git a/README.md b/README.md
index cf7bc0dc..57be2d13 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,20 @@ In order to utilize the `edit`, `split`, and `preview` modes, this `editable` pr
> [!NOTE]
> If the `editable` prop is set to `false`, it will override the `mode` and force into read-only mode.
+#### `downloadable`
+
+- type: `Boolean`
+- default: `false`
+
+Is the user allowed to download the document. Defaults to `false`.
+
+#### `filename`
+
+- type: `String`
+- default: `'document'`
+
+The markdown document filename used when downloaded.
+
#### `mode`
- type: `'read' | 'edit' | 'split' | 'preview'`
@@ -184,17 +198,23 @@ A slot for providing a custom element (i.e. `button`) that enables the `Edit` mo
When the `edit` button (native, or custom) is clicked, the component will automatically determine whether to enable `edit` or `split` mode based on the browser's viewport width. On larger screens, the editor will launch in `split` mode.
+#### `download`
+
+A slot for providing a custom element (i.e. `button`) that triggers the `Download` functionality within the component. The slot exposes the `download` method to trigger the built-in function.
+
+When the `download` button (native, or custom) is clicked, the component will download the document to the user's computer.
+
> [!NOTE]
-> The `editable` prop must be set to `true` to enable this slot.
+> The `downloadable` prop must be set to `true` to enable this slot.
```html
-
-
-
+
+
+
```
diff --git a/sandbox/App.vue b/sandbox/App.vue
index 2569fa97..0c7d91a1 100644
--- a/sandbox/App.vue
+++ b/sandbox/App.vue
@@ -6,6 +6,7 @@
Mollitia aspernatur itaque mollitia suscipit adipisci consectetur error quis. Pariatur et magni mollitia quia. Ut sit quos.
-
-
+
+
+
+
+ Download
+
+
+
+
-
-
- Edit
-
-
+
+
+ Edit
+
+
+
,
@@ -287,13 +323,13 @@ const insertTemplate = (template: MarkdownTemplate): void => {
const htmlPreview = ref(false)
// If the htmlPreview is enabled, pass the generated HTML through the markdown renderer and output the syntax-highlighted result
-watchEffect(() => {
+watchEffect((): void => {
if (htmlPreview.value) {
markdownPreviewHtml.value = md.value?.render('```html' + NEW_LINE_CHARACTER + formatHtml(markdownHtml.value, ' '.repeat(tabSize.value)) + NEW_LINE_CHARACTER + '```')
}
})
-const updateMermaid = async () => {
+const updateMermaid = async (): Promise => {
if (props.mermaid) {
// Scope the query selector to this instance of the markdown component (unique container id)
const mermaidNodes = `#${componentContainerId.value} .markdown-content-container .mermaid`
@@ -306,6 +342,33 @@ const updateMermaid = async () => {
}
}
+const download = (): void => {
+ try {
+ const blob = new Blob([rawMarkdown.value], { type: 'text/markdown;charset=utf-8' })
+ const data = URL.createObjectURL(blob)
+ const link = document.createElement('a')
+ link.href = data
+ link.download = `${props.filename.replace(/(\.md)+$/g, '')}.md`
+
+ // link.click() doesn't work in Firefox
+ link.dispatchEvent(
+ new MouseEvent('click', {
+ bubbles: true,
+ cancelable: true,
+ view: window,
+ }),
+ )
+ // For Firefox it is necessary to delay revoking the ObjectURL
+ setTimeout(() => {
+ window.URL.revokeObjectURL(data)
+ link.remove()
+ }, 100)
+
+ } catch (err) {
+ console.warn('download', err)
+ }
+}
+
// When the textarea `input` event is triggered, or "faked" by other editor methods, update the Vue refs and rendered markdown
const onContentEdit = (event: TextAreaInputEvent, emitEvent = true): void => {
// Update the ref immediately
@@ -601,10 +664,14 @@ const markdownPanesMaxHeight = computed((): string => `${props.maxHeight}px`)
box-sizing: border-box; // Ensure the padding is calculated in the element's width
position: relative;
- .edit-button {
+ .content-buttons {
+ align-items: center;
+ display: flex;
+ gap: var(--kui-space-20, $kui-space-20);
+ justify-content: flex-end;
position: absolute;
- right: 4px;
- top: 4px;
+ right: 6px;
+ top: 6px;
}
}