Various markdown improvements

This commit is contained in:
rijkvanzanten
2021-01-15 11:07:55 -05:00
parent f494e3b1ec
commit 596230107a
4 changed files with 149 additions and 28 deletions

View File

@@ -36,7 +36,15 @@
>
<div class="arrow" :class="{ active: showArrow && isActive }" :style="arrowStyles" data-popper-arrow />
<div class="v-menu-content" @click.stop="onContentClick">
<slot :active="isActive" />
<slot
:active="isActive"
v-bind="{
toggle: toggle,
active: isActive,
activate: activate,
deactivate: deactivate,
}"
/>
</div>
</div>
</portal>
@@ -124,8 +132,7 @@ export default defineComponent({
watch(isActive, (newActive) => {
if (newActive === true) {
reference.value =
((activator.value as HTMLElement)?.childNodes[0] as HTMLElement) || virtualReference.value;
reference.value = ((activator.value as HTMLElement)?.childNodes[0] as HTMLElement) || virtualReference.value;
Vue.nextTick(() => {
popper.value = document.getElementById(id.value);

View File

@@ -11,27 +11,33 @@ type Alteration =
| 'heading'
| 'blockquote'
| 'code'
| 'link';
| 'link'
| 'table';
type AlterationFunctions = Record<
Alteration,
(
selections: string,
cursors: { cursorHead: Position; cursorFrom: Position; cursorTo: Position }
cursors: { cursorHead: Position; cursorFrom: Position; cursorTo: Position },
options?: Record<string, any>
) => { newSelection: string; newCursor: Position; highlight?: { from: Position; to: Position } }
>;
export function useEdit(codemirror: Ref<CodeMirror.EditorFromTextArea | null>) {
const alterations: AlterationFunctions = {
heading(selection, { cursorTo }) {
heading(selection, { cursorTo }, options) {
const level = options?.level || 3;
let newSelection = selection;
let newCursor = cursorTo;
if (selection.startsWith('# ')) {
newSelection = selection.substring(2);
const prefix = '#'.repeat(level) + ' ';
if (selection.startsWith(prefix)) {
newSelection = selection.substring(prefix.length);
} else {
newSelection = `# ${selection}`;
newCursor.ch = newCursor.ch + 2;
newSelection = `${prefix}${selection}`;
newCursor.ch = newCursor.ch + prefix.length;
}
return { newSelection, newCursor };
@@ -195,11 +201,28 @@ export function useEdit(codemirror: Ref<CodeMirror.EditorFromTextArea | null>) {
return { newSelection, newCursor, highlight };
},
table(selection, cursors, options) {
if (!options) return { newSelection: selection, newCursor: cursors.cursorFrom };
let table: string = '';
// Headers
const headers = [];
for (let i = 0; i < options.columns; i++) headers.push('Header');
table += `| ${headers.join(' | ')} |`;
table += `\n| ${headers.map(() => '------').join(' | ')} |`;
for (let i = 0; i < options.rows; i++) {
table += `\n| ${headers.map(() => 'Cell').join(' | ')} |`;
}
return { newSelection: table, newCursor: cursors.cursorFrom };
},
};
return { edit };
function edit(type: Alteration) {
function edit(type: Alteration, options?: Record<string, any>) {
if (codemirror.value) {
const cursor = codemirror.value.getCursor('head');
const cursorFrom = codemirror.value.getCursor('from');
@@ -210,11 +233,15 @@ export function useEdit(codemirror: Ref<CodeMirror.EditorFromTextArea | null>) {
const selection = codemirror.value.getSelection();
const { newSelection, newCursor, highlight } = alterations[type](selection || word, {
cursorFrom: cloneDeep(selection ? cursorFrom : wordRange.anchor),
cursorTo: cloneDeep(selection ? cursorTo : wordRange.head),
cursorHead: cursor,
});
const { newSelection, newCursor, highlight } = alterations[type](
selection || word,
{
cursorFrom: cloneDeep(selection ? cursorFrom : wordRange.anchor),
cursorTo: cloneDeep(selection ? cursorTo : wordRange.head),
cursorHead: cursor,
},
options
);
if (word && !selection) {
codemirror.value.replaceRange(newSelection, wordRange.anchor, wordRange.head);

View File

@@ -1,15 +1,77 @@
<template>
<div class="interface-markdown" :class="view[0]">
<div class="toolbar">
<v-button small icon @click="edit('heading')"><v-icon name="title" /></v-button>
<v-button small icon @click="edit('bold')"><v-icon name="format_bold" /></v-button>
<v-button small icon @click="edit('italic')"><v-icon name="format_italic" /></v-button>
<v-button small icon @click="edit('strikethrough')"><v-icon name="format_strikethrough" /></v-button>
<v-button small icon @click="edit('listBulleted')"><v-icon name="format_list_bulleted" /></v-button>
<v-button small icon @click="edit('listNumbered')"><v-icon name="format_list_numbered" /></v-button>
<v-button small icon @click="edit('blockquote')"><v-icon name="format_quote" /></v-button>
<v-button small icon @click="edit('code')"><v-icon name="code" /></v-button>
<v-button small icon @click="edit('link')"><v-icon name="insert_link" /></v-button>
<v-menu show-arrow placement="bottom-start">
<template #activator="{ toggle }">
<v-button small icon @click="toggle" v-tooltip="$t('wysiwyg_options.heading')">
<v-icon name="title" />
</v-button>
</template>
<v-list>
<v-list-item v-for="n in 5" :key="n" @click="edit('heading', { level: n })">
<v-list-item-text>{{ $t(`wysiwyg_options.h${n}`) }}</v-list-item-text>
</v-list-item>
</v-list>
</v-menu>
<v-button small icon @click="edit('bold')" v-tooltip="$t('wysiwyg_options.bold')">
<v-icon name="format_bold" />
</v-button>
<v-button small icon @click="edit('italic')" v-tooltip="$t('wysiwyg_options.italic')">
<v-icon name="format_italic" />
</v-button>
<v-button small icon @click="edit('strikethrough')" v-tooltip="$t('wysiwyg_options.strikethrough')">
<v-icon name="format_strikethrough" />
</v-button>
<v-button small icon @click="edit('listBulleted')" v-tooltip="$t('wysiwyg_options.bullist')">
<v-icon name="format_list_bulleted" />
</v-button>
<v-button small icon @click="edit('listNumbered')" v-tooltip="$t('wysiwyg_options.numlist')">
<v-icon name="format_list_numbered" />
</v-button>
<v-button small icon @click="edit('blockquote')" v-tooltip="$t('wysiwyg_options.blockquote')">
<v-icon name="format_quote" />
</v-button>
<v-button small icon @click="edit('code')" v-tooltip="$t('wysiwyg_options.codeblock')">
<v-icon name="code" />
</v-button>
<v-button small icon @click="edit('link')" v-tooltip="$t('wysiwyg_options.link')">
<v-icon name="insert_link" />
</v-button>
<v-menu show-arrow :close-on-content-click="false">
<template #activator="{ toggle }">
<v-button small icon @click="toggle" v-tooltip="$t('wysiwyg_options.table')">
<v-icon name="table_chart" />
</v-button>
</template>
<template #default="{ deactivate }">
<div class="table-options">
<div class="field half">
<p class="type-label">{{ $t('rows') }}</p>
<v-input :min="1" type="number" v-model="table.rows" />
</div>
<div class="field half">
<p class="type-label">{{ $t('columns') }}</p>
<v-input :min="1" type="number" v-model="table.columns" />
</div>
<div class="field full">
<v-button
full-width
@click="
() => {
edit('table', table);
deactivate();
}
"
>
Create
</v-button>
</div>
</div>
</template>
</v-menu>
<div class="spacer"></div>
@@ -26,7 +88,7 @@
</template>
<script lang="ts">
import { defineComponent, computed, ref, onMounted, onUnmounted, watch } from '@vue/composition-api';
import { defineComponent, computed, ref, onMounted, onUnmounted, watch, reactive } from '@vue/composition-api';
import { sanitize } from 'dompurify';
import marked from 'marked';
@@ -61,6 +123,7 @@ export default defineComponent({
codemirror.value = CodeMirror.fromTextArea(codemirrorEl.value, {
mode: 'markdown',
configureMouse: () => ({ addNew: false }),
lineWrapping: true,
});
codemirror.value.setValue(props.value || '');
@@ -95,12 +158,19 @@ export default defineComponent({
return htmlSanitized;
});
return { codemirrorEl, edit, view, html };
const table = reactive({
rows: 4,
columns: 4,
});
return { codemirrorEl, edit, view, html, table };
},
});
</script>
<style lang="scss" scoped>
@import '../../styles/mixins/form-grid';
.interface-markdown {
--v-button-background-color: transparent;
--v-button-color: var(--foreground-normal);
@@ -170,6 +240,19 @@ textarea {
}
}
.table-options {
@include form-grid;
--form-vertical-gap: 12px;
--form-horizontal-gap: 12px;
padding: 12px;
.v-input {
min-width: 100px;
}
}
.preview-box {
padding: 20px;

View File

@@ -462,6 +462,8 @@ upload_pending: Upload Pending
drag_file_here: Drag & Drop a File Here
click_to_browse: Click to Browse
layout_options: Layout Options
rows: Rows
columns: Columns
collection_setup: Collection Setup
optional_system_fields: Optional System Fields
value_unique: Value has to be unique
@@ -512,17 +514,19 @@ wysiwyg_options:
strikethrough: Strikethrough
subscript: Subscript
superscript: Superscript
codeblock: Code
blockquote: Blockquote
bullist: Bullet List
numlist: Numbered List
hr: Horizontal Rule
link: Add Link
link: Link
unlink: Remove Link
media: Add Media
image: Add Image
copy: Copy
cut: Cut
paste: Paste
heading: Heading
h1: Heading 1
h2: Heading 2
h3: Heading 3