mirror of
https://github.com/directus/directus.git
synced 2026-01-25 09:37:58 -05:00
Various markdown improvements
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user