Skip to content

Commit

Permalink
feat: download document
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdehaven committed Jan 7, 2024
1 parent 1527951 commit 934651e
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 32 deletions.
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'`
Expand Down Expand Up @@ -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
<MarkdownUi
v-model="content"
editable
downloadable
>
<template #edit="{ edit }">
<!-- Call the provided `edit` method when custom button is clicked -->
<button @click="edit">Custom edit button</button>
<template #download="{ download }">
<!-- Call the provided `download` method when custom button is clicked -->
<button @click="download">Download the doc</button>
</template>
</MarkdownUi>
```
Expand Down
1 change: 1 addition & 0 deletions sandbox/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<p>Mollitia aspernatur itaque mollitia suscipit adipisci consectetur error quis. Pariatur et magni mollitia quia. Ut sit quos.</p>
<MarkdownUi
v-model="content"
downloadable
:editable="editable"
mode="split"
@cancel="cancelEdit"
Expand Down
121 changes: 94 additions & 27 deletions src/components/MarkdownUi.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,29 +80,55 @@
class="markdown-content-container"
data-testid="markdown-content-container"
>
<div
v-if="editable && currentMode === 'read'"
class="edit-button"
>
<slot
:edit="edit"
name="edit"
<div class="content-buttons">
<div
v-if="currentMode === 'read' && downloadable"
class="download-button"
>
<slot
:download="download"
name="download"
>
<ToolbarButton
appearance="primary"
aria-label="Download markdown document"
data-testid="download"
:icon="false"
:tabindex="0"
@click="download"
>
<ArrowDownIcon
decorative
:size="KUI_ICON_SIZE_30"
/>
Download
</ToolbarButton>
</slot>
</div>
<div
v-if="currentMode === 'read' && editable"
class="edit-button"
>
<ToolbarButton
appearance="primary"
aria-label="Edit markdown document"
data-testid="edit"
:icon="false"
:tabindex="0"
@click="edit"
<slot
:edit="edit"
name="edit"
>
<EditIcon
decorative
:size="KUI_ICON_SIZE_30"
/>
Edit
</ToolbarButton>
</slot>
<ToolbarButton
appearance="primary"
aria-label="Edit markdown document"
data-testid="edit"
:icon="false"
:tabindex="0"
@click="edit"
>
<EditIcon
decorative
:size="KUI_ICON_SIZE_30"
/>
Edit
</ToolbarButton>
</slot>
</div>
</div>
<MarkdownContent
:class="{ 'html-preview': htmlPreview }"
Expand All @@ -128,7 +154,7 @@ import { v4 as uuidv4 } from 'uuid'
import type { MarkdownMode, InlineFormat, MarkdownTemplate, TextAreaInputEvent, Theme } from '@/types'
import formatHtml from 'html-format'
import { KUI_FONT_FAMILY_TEXT, KUI_FONT_FAMILY_CODE, KUI_SPACE_60, KUI_BREAKPOINT_PHABLET, KUI_ICON_SIZE_30 } from '@kong/design-tokens'
import { EditIcon } from '@kong/icons'
import { EditIcon, ArrowDownIcon } from '@kong/icons'
import MermaidJs from 'mermaid'
const props = defineProps({
Expand All @@ -142,6 +168,16 @@ const props = defineProps({
type: Boolean,
default: false,
},
/** Is the markdown document able to be edited by the user. Defaults to `false`. */
downloadable: {
type: Boolean,
default: false,
},
/** The markdown document filename used when downloaded. */
filename: {
type: String,
default: 'document',
},
/** The mode used when the component initializes, one of 'read', 'edit', 'split', 'preview' */
mode: {
type: String as PropType<MarkdownMode>,
Expand Down Expand Up @@ -287,13 +323,13 @@ const insertTemplate = (template: MarkdownTemplate): void => {
const htmlPreview = ref<boolean>(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<void> => {
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`
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
}
Expand Down

0 comments on commit 934651e

Please sign in to comment.