Merge pull request #3698 from directus/markdown

New markdown interface
This commit is contained in:
Rijk van Zanten
2021-01-14 17:38:08 -07:00
committed by GitHub
11 changed files with 602 additions and 394 deletions

View File

@@ -4,6 +4,7 @@ import VAvatar from './v-avatar/';
import VBadge from './v-badge/';
import VBreadcrumb from './v-breadcrumb';
import VButton from './v-button/';
import VButtonGroup from './v-button-group/';
import VCard, { VCardActions, VCardTitle, VCardSubtitle, VCardText } from './v-card';
import VCheckbox from './v-checkbox/';
import VChip from './v-chip/';
@@ -43,6 +44,7 @@ Vue.component('v-avatar', VAvatar);
Vue.component('v-badge', VBadge);
Vue.component('v-breadcrumb', VBreadcrumb);
Vue.component('v-button', VButton);
Vue.component('v-button-group', VButtonGroup);
Vue.component('v-card-actions', VCardActions);
Vue.component('v-card-subtitle', VCardSubtitle);
Vue.component('v-card-text', VCardText);

View File

@@ -85,21 +85,21 @@ body {
}
&.tile .v-item-group ::v-deep .v-button {
&:first-child {
&:first-child .button {
--border-radius: 0px;
}
&:last-child {
&:last-child .button {
--border-radius: 0px;
}
}
&.rounded:not(.tile) .v-item-group ::v-deep .v-button {
&:first-child {
&:first-child .button {
--border-radius: var(--v-button-height) 0px 0px var(--v-button-height);
}
&:last-child {
&:last-child .button {
--border-radius: 0px var(--v-button-height) var(--v-button-height) 0px;
}
}

View File

@@ -209,12 +209,6 @@ body {
border-color: var(--v-button-background-color-hover);
}
&.activated {
color: var(--v-button-color);
background-color: var(--v-button-background-color);
border-color: var(--v-button-background-color);
}
&.align-left {
justify-content: flex-start;
}
@@ -267,9 +261,9 @@ body {
--v-button-font-size: 12px;
--v-button-font-weight: 600;
--v-button-min-width: 60px;
--border-radius: 4px;
padding: 0 12px;
border-radius: 4px;
}
&.small {
@@ -336,7 +330,8 @@ body {
}
}
&.activated {
&.activated,
&.active {
--v-button-color: var(--v-button-color-activated) !important;
--v-button-background-color: var(--v-button-background-color-activated) !important;
--v-button-background-color-hover: var(--v-button-background-color-activated) !important;

View File

@@ -300,6 +300,7 @@ export default defineComponent({
cursor: pointer;
transition: color var(--fast) var(--transition-out);
user-select: none;
&:hover {
color: var(--primary-125);
transition: none;

View File

@@ -0,0 +1,234 @@
import { Ref } from '@vue/composition-api';
import { Position } from 'codemirror';
import { cloneDeep } from 'lodash';
type Alteration =
| 'bold'
| 'italic'
| 'strikethrough'
| 'listBulleted'
| 'listNumbered'
| 'heading'
| 'blockquote'
| 'code'
| 'link';
type AlterationFunctions = Record<
Alteration,
(
selections: string,
cursors: { cursorHead: Position; cursorFrom: Position; cursorTo: Position }
) => { newSelection: string; newCursor: Position; highlight?: { from: Position; to: Position } }
>;
export function useEdit(codemirror: Ref<CodeMirror.EditorFromTextArea | null>) {
const alterations: AlterationFunctions = {
heading(selection, { cursorTo }) {
let newSelection = selection;
let newCursor = cursorTo;
if (selection.startsWith('# ')) {
newSelection = selection.substring(2);
} else {
newSelection = `# ${selection}`;
newCursor.ch = newCursor.ch + 2;
}
return { newSelection, newCursor };
},
bold(selection, { cursorTo }) {
let newSelection = selection;
let newCursor = cursorTo;
if (selection.startsWith('**') && selection.endsWith('**')) {
newSelection = selection.substring(2, selection.length - 2);
} else {
newSelection = `**${selection}**`;
newCursor.ch = newCursor.ch + 2;
}
return { newSelection, newCursor };
},
italic(selection, { cursorTo }) {
let newSelection = selection;
let newCursor = cursorTo;
if (selection.startsWith('*') && selection.endsWith('*')) {
newSelection = selection.substring(1, selection.length - 1);
} else {
newSelection = `*${selection}*`;
newCursor.ch = newCursor.ch + 1;
}
return { newSelection, newCursor };
},
strikethrough(selection, { cursorTo }) {
let newSelection = selection;
let newCursor = cursorTo;
if (selection.startsWith('~~') && selection.endsWith('~~')) {
newSelection = selection.substring(2, selection.length - 2);
} else {
newSelection = `~~${selection}~~`;
newCursor.ch = newCursor.ch + 2;
}
return { newSelection, newCursor };
},
listBulleted(selection, { cursorTo }) {
let newSelection = selection;
let newCursor = cursorTo;
const lines = selection.split('\n');
const isList = lines.every((line) => line.startsWith('- '));
if (isList) {
newSelection = lines.map((line) => line.substring(2)).join('\n');
} else {
newSelection = lines.map((line) => `- ${line}`).join('\n');
}
if (!selection) {
newCursor.ch = newCursor.ch + 2;
}
return { newSelection, newCursor };
},
listNumbered(selection, { cursorTo }) {
let newSelection = selection;
let newCursor = cursorTo;
const lines = selection.split('\n');
const isList = lines.every((line, index) => line.startsWith(`${index + 1}.`));
if (isList) {
newSelection = lines.map((line) => line.substring(3)).join('\n');
} else {
newSelection = lines.map((line, index) => `${index + 1}. ${line}`).join('\n');
}
if (!selection) {
newCursor.ch = newCursor.ch + 2;
}
return { newSelection, newCursor };
},
blockquote(selection, { cursorTo }) {
let newSelection = selection;
let newCursor = cursorTo;
const lines = selection.split('\n');
const isList = lines.every((line) => line.startsWith('> '));
if (isList) {
newSelection = lines.map((line) => line.substring(2)).join('\n');
} else {
newSelection = lines.map((line) => `> ${line}`).join('\n');
}
if (!selection) {
newCursor.ch = newCursor.ch + 2;
}
return { newSelection, newCursor };
},
code(selection, { cursorTo }) {
if (selection.includes('\n')) {
// Multiline
let newSelection = selection;
let newCursor = cursorTo;
if (selection.startsWith('```') && selection.endsWith('```')) {
newSelection = selection.substring(3, selection.length - 3);
} else {
newSelection = '```\n' + newSelection + '\n```';
newCursor.line = newCursor.line + 1;
}
return { newSelection, newCursor };
} else {
// Inline
let newSelection = selection;
let newCursor = cursorTo;
if (selection.startsWith('`') && selection.endsWith('`')) {
newSelection = selection.substring(1, selection.length - 1);
} else {
newSelection = `\`${selection}\``;
newCursor.ch = newCursor.ch + 1;
}
return { newSelection, newCursor };
}
},
link(selection, { cursorFrom, cursorTo }) {
let newSelection = selection;
let newCursor = cursorTo;
let highlight;
if (selection.endsWith('](url)')) {
newSelection = selection.substring(1, selection.length - 6);
} else if (selection.startsWith('http')) {
newSelection = `[](${selection})`;
newCursor.ch = cursorFrom.ch + 1;
} else {
newSelection = `[${selection}](url)`;
if (selection) {
highlight = {
from: {
...cloneDeep(newCursor),
ch: newCursor.ch + 3,
},
to: {
...cloneDeep(newCursor),
ch: newCursor.ch + 6,
},
};
} else {
newCursor.ch = cursorFrom.ch + 1;
}
}
return { newSelection, newCursor, highlight };
},
};
return { edit };
function edit(type: Alteration) {
if (codemirror.value) {
const cursor = codemirror.value.getCursor('head');
const cursorFrom = codemirror.value.getCursor('from');
const cursorTo = codemirror.value.getCursor('to');
const wordRange = codemirror.value.findWordAt(cursor);
const word = codemirror.value.getRange(wordRange.anchor, wordRange.head).trim();
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,
});
if (word && !selection) {
codemirror.value.replaceRange(newSelection, wordRange.anchor, wordRange.head);
} else {
codemirror.value.replaceSelection(newSelection);
}
codemirror.value.setCursor(newCursor);
if (highlight) {
codemirror.value.setSelection(highlight.from, highlight.to);
}
codemirror.value.focus();
}
}
}

View File

@@ -21,20 +21,5 @@ export default defineInterface(({ i18n }) => ({
},
},
},
{
field: 'tabbed',
name: i18n.t('interfaces.markdown.tabbed'),
type: 'boolean',
meta: {
width: 'half',
interface: 'toggle',
options: {
label: i18n.t('interfaces.markdown.tabbed_label'),
},
},
schema: {
default_value: false,
},
},
],
}));

View File

@@ -1,51 +0,0 @@
import { withKnobs, boolean, text, optionsKnob } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import Vue from 'vue';
import InterfaceMarkdown from './markdown.vue';
import markdown from './readme.md';
import withPadding from '../../../.storybook/decorators/with-padding';
import { defineComponent, ref } from '@vue/composition-api';
import RawValue from '../../../.storybook/raw-value.vue';
import i18n from '@/lang';
Vue.component('interface-markdown', InterfaceMarkdown);
export default {
title: 'Interfaces / Markdown',
decorators: [withKnobs, withPadding],
parameters: {
notes: markdown,
},
};
export const basic = () =>
defineComponent({
components: { RawValue },
i18n,
props: {
disabled: {
default: boolean('Disabled', false, 'Options'),
},
placeholder: {
default: text('Placeholder', 'Enter a value...', 'Options'),
},
tabbed: {
default: boolean('Tabbed', false, 'Options'),
},
},
setup() {
const value = ref('');
const onInput = action('input');
return { onInput, value };
},
template: `
<div>
<interface-markdown
v-model="value"
v-bind="{ placeholder, tabbed, disabled }"
@input="onInput"
/>
<raw-value>{{ value }}</raw-value>
</div>
`,
});

View File

@@ -1,33 +1,39 @@
<template>
<div class="interface-markdown" :class="{ tabbed }">
<div v-if="tabbed" class="toolbar">
<v-tabs v-model="currentTab">
<v-tab>
<v-icon name="code" left />
{{ $t('interfaces.markdown.edit') }}
</v-tab>
<v-tab>
<v-icon name="visibility" outline left />
{{ $t('interfaces.markdown.preview') }}
</v-tab>
</v-tabs>
</div>
<v-textarea
v-show="showEdit"
:placeholder="placeholder"
:value="value"
:disabled="disabled"
@input="$listeners.input"
/>
<div v-show="showPreview" class="preview-container">
<div class="preview" v-html="html"></div>
<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>
<div class="spacer"></div>
<v-button-group class="view" mandatory v-model="view" rounded>
<v-button x-small value="editor">Editor</v-button>
<v-button x-small value="preview">Preview</v-button>
</v-button-group>
</div>
<textarea ref="codemirrorEl" :value="value || ''" />
<div v-show="view[0] === 'preview'" class="preview-box" v-html="html"></div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, ref, onMounted, onUnmounted, watch } from '@vue/composition-api';
import { sanitize } from 'dompurify';
import marked from 'marked';
import { defineComponent, computed, ref } from '@vue/composition-api';
import CodeMirror from 'codemirror';
import 'codemirror/mode/markdown/markdown';
import { useEdit } from './composables/use-edit';
export default defineComponent({
props: {
@@ -43,310 +49,288 @@ export default defineComponent({
type: String,
default: null,
},
tabbed: {
type: Boolean,
default: true,
},
},
setup(props) {
const currentTab = ref([0]);
setup(props, { emit }) {
const codemirrorEl = ref<HTMLTextAreaElement | null>(null);
const codemirror = ref<CodeMirror.EditorFromTextArea | null>(null);
const html = computed(() => (props.value ? marked(props.value) : ''));
const showEdit = computed(() => !props.tabbed || currentTab.value[0] === 0);
const showPreview = computed(() => !props.tabbed || currentTab.value[0] !== 0);
const view = ref(['editor']);
return { html, currentTab, showEdit, showPreview };
onMounted(async () => {
if (codemirrorEl.value) {
codemirror.value = CodeMirror.fromTextArea(codemirrorEl.value, {
mode: 'markdown',
configureMouse: () => ({ addNew: false }),
});
codemirror.value.setValue(props.value || '');
codemirror.value.on('change', (cm, { origin }) => {
if (origin === 'setValue') return;
const content = cm.getValue();
emit('input', content);
});
}
});
onUnmounted(() => {
codemirror.value?.toTextArea();
});
watch(
() => props.value,
(newValue) => {
if (codemirror.value?.getValue() !== newValue) {
codemirror.value?.setValue(props.value || '');
}
}
);
const { edit } = useEdit(codemirror);
const html = computed(() => {
const html = marked(props.value || '');
const htmlSanitized = sanitize(html);
return htmlSanitized;
});
return { codemirrorEl, edit, view, html };
},
});
</script>
<style lang="scss" scoped>
.interface-markdown {
--v-textarea-min-height: var(--input-height-tall);
--v-textarea-max-height: 400px;
--v-tab-background-color: var(--background-subdued);
--v-tab-background-color-active: var(--background-subdued);
--v-button-background-color: transparent;
--v-button-color: var(--foreground-normal);
--v-button-background-color-hover: var(--border-normal);
--v-button-color-hover: var(--foreground-normal);
min-height: 300px;
overflow: hidden;
border: 2px solid var(--border-normal);
border-radius: var(--border-radius);
}
textarea {
display: none;
}
.interface-markdown ::v-deep .CodeMirror {
border: none;
border-radius: 0;
.CodeMirror-lines {
padding: 0 20px;
&:first-of-type {
margin-top: 20px;
}
&:last-of-type {
margin-bottom: 20px;
}
}
.CodeMirror-scroll {
min-height: 300px - 40px;
}
}
.interface-markdown.preview {
::v-deep .CodeMirror {
display: none;
}
}
.toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
height: 40px;
padding: 0 4px;
background-color: var(--background-subdued);
border-bottom: 2px solid var(--border-normal);
.toolbar {
width: 100%;
height: 42px;
background-color: var(--background-subdued);
border: var(--border-width) solid var(--border-normal);
border-radius: var(--border-radius) var(--border-radius) 0 0;
.v-button + .v-button {
margin-left: 2px;
}
.v-textarea {
height: unset;
min-height: var(--input-height-tall);
border-radius: var(--border-radius) 0 0 var(--border-radius);
}
.preview-container {
min-height: var(--v-textarea-min-height);
max-height: var(--v-textarea-max-height);
padding: var(--input-padding);
overflow-y: auto;
border: var(--border-width) solid var(--border-normal);
border-radius: 0 var(--border-radius) var(--border-radius) 0;
}
.v-textarea,
.preview-container {
flex-basis: 100px;
.spacer {
flex-grow: 1;
}
&:not(.tabbed) .preview-container {
border-left: none;
.view {
--v-button-background-color: var(--border-subdued);
--v-button-color: var(--foreground-subdued);
--v-button-background-color-hover: var(--border-normal);
--v-button-color-hover: var(--foreground-normal);
--v-button-background-color-activated: var(--border-normal);
--v-button-color-activated: var(--foreground-normal);
}
}
&.tabbed .v-textarea {
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
&.tabbed .preview-container {
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
&.tabbed .toolbar {
border-bottom: none;
}
&.tabbed .expand {
right: 0;
}
&.tabbed.hasScrollbar .expand {
right: 14px;
}
.preview-box {
padding: 20px;
::v-deep {
.preview {
font-weight: 500;
font-size: 14px;
& > *:first-child {
margin-top: 0;
}
& > *:last-child {
margin-bottom: 0;
}
a {
text-decoration: underline;
}
h1,
h2,
h3,
h4,
h5,
h6 {
position: relative;
margin: 20px 0 10px;
padding: 0;
font-weight: 600;
cursor: text;
}
pre {
padding: 6px 10px;
overflow: auto;
font-size: 13px;
line-height: 19px;
background-color: var(--background-page);
border: 1px solid var(--background-normal);
border-radius: var(--border-radius);
}
code,
tt {
margin: 0 2px;
padding: 0 5px;
white-space: nowrap;
background-color: var(--background-page);
border: 1px solid var(--background-normal);
border-radius: var(--border-radius);
}
pre code {
margin: 0;
padding: 0;
white-space: pre;
background: transparent;
border: none;
}
pre code,
pre tt {
background-color: transparent;
border: none;
}
h1 tt,
h1 code {
font-size: inherit;
}
h2 tt,
h2 code {
font-size: inherit;
}
h3 tt,
h3 code {
font-size: inherit;
}
h4 tt,
h4 code {
font-size: inherit;
}
h5 tt,
h5 code {
font-size: inherit;
}
h6 tt,
h6 code {
font-size: inherit;
}
h1 {
font-size: 28px;
}
h2 {
font-size: 24px;
}
h3 {
font-size: 18px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
color: var(--foreground-normal);
font-size: 14px;
}
p,
blockquote,
ul,
ol,
dl,
li,
table,
pre {
margin: 15px 0;
}
& > h2:first-child {
margin-top: 0;
padding-top: 0;
}
& > h1:first-child {
margin-top: 0;
padding-top: 0;
}
& > h3:first-child,
& > h4:first-child,
& > h5:first-child,
& > h6:first-child {
margin-top: 0;
padding-top: 0;
}
a:first-child h1,
a:first-child h2,
a:first-child h3,
a:first-child h4,
a:first-child h5,
a:first-child h6 {
margin-top: 0;
padding-top: 0;
}
h1 p,
h2 p,
h3 p,
h4 p,
h5 p,
h6 p {
margin-top: 0;
}
li p.first {
display: inline-block;
}
ul,
ol {
padding-left: 30px;
li {
margin: 0;
}
}
ul :first-child,
ol :first-child {
margin-top: 0;
}
ul :last-child,
ol :last-child {
margin-bottom: 0;
}
h1 {
margin-bottom: 0;
font-weight: 300;
font-size: 44px;
font-family: var(--font-serif), serif;
line-height: 52px;
}
h2 {
margin-top: 60px;
margin-bottom: 0;
font-weight: 600;
font-size: 34px;
line-height: 38px;
}
h3 {
margin-top: 40px;
margin-bottom: 0;
font-weight: 600;
font-size: 26px;
line-height: 31px;
}
h4 {
margin-top: 40px;
margin-bottom: 0;
font-weight: 600;
font-size: 22px;
line-height: 28px;
}
h5 {
margin-top: 40px;
margin-bottom: 0;
font-weight: 600;
font-size: 18px;
line-height: 26px;
}
h6 {
margin-top: 40px;
margin-bottom: 0;
font-weight: 600;
font-size: 16px;
line-height: 24px;
}
p {
margin-top: 20px;
margin-bottom: 20px;
font-size: 16px;
font-family: var(--font-serif), serif;
line-height: 32px;
}
a {
color: #546e7a;
}
ul,
ol {
margin: 24px 0;
font-size: 18px;
font-family: var(--font-serif), serif;
line-height: 34px;
}
ul ul,
ol ol,
ul ol,
ol ul {
margin: 0;
}
b,
strong {
font-weight: 600;
}
code {
padding: 2px 4px;
font-size: 18px;
font-family: var(--family-monospace), monospace;
line-height: 34px;
overflow-wrap: break-word;
background-color: #eceff1;
border-radius: 4px;
}
pre {
padding: 20px;
overflow: auto;
font-size: 18px;
font-family: var(--family-monospace), monospace;
line-height: 24px;
background-color: #eceff1;
border-radius: 4px;
}
blockquote {
margin-left: -10px;
padding-left: 10px;
font-size: 18px;
font-family: var(--font-serif), serif;
font-style: italic;
line-height: 34px;
border-left: 2px solid #546e7a;
blockquote {
padding: 0 15px;
color: var(--foreground-normal);
border-left: 4px solid var(--background-normal);
}
blockquote > :first-child {
margin-top: 0;
}
blockquote > :last-child {
margin-bottom: 0;
}
table {
padding: 0;
border-collapse: collapse;
border-spacing: 0;
}
table tr {
margin: 0;
padding: 0;
background-color: white;
border-top: 1px solid var(--background-normal);
}
table tr:nth-child(2n) {
background-color: var(--background-page);
}
table tr th {
margin: 0;
padding: 6px 13px;
font-weight: bold;
text-align: left;
border: 1px solid var(--background-normal);
}
table tr td {
margin: 0;
padding: 6px 13px;
text-align: left;
border: 1px solid var(--background-normal);
}
table tr th :first-child,
table tr td :first-child {
margin-top: 0;
}
table tr th :last-child,
table tr td :last-child {
margin-bottom: 0;
}
img {
max-width: 100%;
}
.highlight pre {
padding: 6px 10px;
overflow: auto;
font-size: 13px;
line-height: 19px;
background-color: var(--background-page);
border: 1px solid var(--background-normal);
border-radius: var(--border-radius);
}
hr {
margin: 20px auto;
border: none;
border-top: 1px solid var(--background-normal);
}
b,
strong {
font-weight: 600;
margin-left: 10px;
}
}
video,
iframe,
img {
max-width: 100%;
height: auto;
border-radius: 4px;
}
hr {
margin-top: 52px;
margin-bottom: 56px;
text-align: center;
border: 0;
}
hr::after {
font-size: 28px;
line-height: 0;
letter-spacing: 16px;
content: '...';
}
table {
border-collapse: collapse;
}
table th,
table td {
padding: 0.4rem;
border: 1px solid #cfd8dc;
}
figure {
display: table;
margin: 1rem auto;
}
figure figcaption {
display: block;
margin-top: 0.25rem;
color: #999;
text-align: center;
}
}
}
</style>

View File

@@ -5,7 +5,9 @@
}
.tox .tox-tbtn {
margin: 2px 2px 4px 0;
width: 36px;
height: 36px;
margin: 0;
color: var(--foreground-normal);
}
@@ -108,8 +110,9 @@
.tox .tox-toolbar,
.tox .tox-toolbar__primary,
.tox .tox-toolbar__overflow {
background: url("data:image/svg+xml;charset=utf8,%3Csvg height='40px' viewBox='0 0 40 40px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='2' fill='%23cfd8dc'/%3E%3C/svg%3E")
left 0 top 0 var(--background-subdued);
height: 40px;
background: var(--background-subdued);
border-bottom: 2px solid var(--border-normal);
}
.tox .tox-pop__dialog .tox-toolbar {
@@ -149,6 +152,10 @@ body.dark .tox .tox-toolbar__overflow {
background: var(--border-normal);
}
.tox .tox-tbtn + .tox .tox-tbtn {
margin-left: 2px;
}
.tox .tox-swatches__picker-btn:hover {
background: transparent;
border: none;

65
package-lock.json generated
View File

@@ -14,7 +14,8 @@
"@directus/sdk-js": "file:packages/sdk-js",
"@directus/specs": "file:packages/specs",
"create-directus-project": "file:packages/create-directus-project",
"directus": "file:api"
"directus": "file:api",
"dompurify": "^2.2.6"
},
"devDependencies": {
"@apidevtools/swagger-cli": "^4.0.4",
@@ -42,13 +43,14 @@
"@types/bytes": "^3.1.0",
"@types/chai": "^4.2.14",
"@types/clear": "^0.1.0",
"@types/codemirror": "^0.0.98",
"@types/codemirror": "^0.0.106",
"@types/color": "^3.0.1",
"@types/color-string": "^1.5.0",
"@types/cookie-parser": "^1.4.2",
"@types/cors": "^2.8.7",
"@types/debug": "^4.1.5",
"@types/diff": "^4.0.2",
"@types/dompurify": "^2.2.1",
"@types/express": "^4.17.9",
"@types/express-pino-logger": "^4.0.2",
"@types/express-session": "^1.17.2",
@@ -182,6 +184,7 @@
}
},
"api": {
"name": "directus",
"version": "9.0.0-rc.28",
"license": "GPL-3.0-only",
"dependencies": {
@@ -448,6 +451,7 @@
}
},
"app": {
"name": "@directus/app",
"version": "9.0.0-rc.28",
"dependencies": {
"@directus/format-title": "file:../packages/format-title"
@@ -575,6 +579,7 @@
}
},
"docs": {
"name": "@directus/docs",
"version": "9.0.0-rc.28",
"license": "ISC",
"devDependencies": {
@@ -7465,9 +7470,9 @@
"dev": true
},
"node_modules/@types/codemirror": {
"version": "0.0.98",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.98.tgz",
"integrity": "sha512-cbty5LPayy2vNSeuUdjNA9tggG+go5vAxmnLDRWpiZI5a+RDBi9dlozy4/jW/7P/gletbBWbQREEa7A81YxstA==",
"version": "0.0.106",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.106.tgz",
"integrity": "sha512-o2bJWaI56+J1IuzUyb1KKNLs0Tm1sqdosGeSneicQZpg9s59++8Nz70KOD6IACiZWI6b+H0H+UE+JRkgTUO3Ww==",
"dev": true,
"dependencies": {
"@types/tern": "*"
@@ -7549,6 +7554,15 @@
"integrity": "sha512-mIenTfsIe586/yzsyfql69KRnA75S8SVXQbTLpDejRrjH0QSJcpu3AUOi/Vjnt9IOsXKxPhJfGpQUNMueIU1fQ==",
"dev": true
},
"node_modules/@types/dompurify": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.2.1.tgz",
"integrity": "sha512-3JwbEeRVQ3n6+JgBW/hCdkydRk9/vWT+UEglcXEJqLJEcUganDH37zlfLznxPKTZZfDqA9K229l1qN458ubcOQ==",
"dev": true,
"dependencies": {
"@types/trusted-types": "*"
}
},
"node_modules/@types/eslint-visitor-keys": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
@@ -8061,6 +8075,12 @@
"@types/node": "*"
}
},
"node_modules/@types/trusted-types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.0.tgz",
"integrity": "sha512-I8MnZqNXsOLHsU111oHbn3khtvKMi5Bn4qVFsIWSJcCP1KKDiXX5AEw8UPk0nSopeC+Hvxt6yAy1/a5PailFqg==",
"dev": true
},
"node_modules/@types/tunnel": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.0.tgz",
@@ -18039,6 +18059,11 @@
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/dompurify": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.2.6.tgz",
"integrity": "sha512-7b7ZArhhH0SP6W2R9cqK6RjaU82FZ2UPM7RO8qN1b1wyvC/NY1FNWcX1Pu00fFOAnzEORtwXe4bPaClg6pUybQ=="
},
"node_modules/domutils": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.4.tgz",
@@ -44738,6 +44763,7 @@
}
},
"packages/format-title": {
"name": "@directus/format-title",
"version": "9.0.0-rc.28",
"license": "MIT",
"devDependencies": {
@@ -44763,6 +44789,7 @@
}
},
"packages/schema": {
"name": "@directus/schema",
"version": "9.0.0-rc.28",
"license": "GPL-3.0",
"devDependencies": {
@@ -44774,6 +44801,7 @@
}
},
"packages/sdk-js": {
"name": "@directus/sdk-js",
"version": "9.0.0-rc.28",
"license": "MIT",
"dependencies": {
@@ -44782,6 +44810,7 @@
}
},
"packages/specs": {
"name": "@directus/specs",
"version": "9.0.0-rc.28",
"license": "GPL-3.0",
"devDependencies": {
@@ -50621,9 +50650,9 @@
"dev": true
},
"@types/codemirror": {
"version": "0.0.98",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.98.tgz",
"integrity": "sha512-cbty5LPayy2vNSeuUdjNA9tggG+go5vAxmnLDRWpiZI5a+RDBi9dlozy4/jW/7P/gletbBWbQREEa7A81YxstA==",
"version": "0.0.106",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.106.tgz",
"integrity": "sha512-o2bJWaI56+J1IuzUyb1KKNLs0Tm1sqdosGeSneicQZpg9s59++8Nz70KOD6IACiZWI6b+H0H+UE+JRkgTUO3Ww==",
"dev": true,
"requires": {
"@types/tern": "*"
@@ -50705,6 +50734,15 @@
"integrity": "sha512-mIenTfsIe586/yzsyfql69KRnA75S8SVXQbTLpDejRrjH0QSJcpu3AUOi/Vjnt9IOsXKxPhJfGpQUNMueIU1fQ==",
"dev": true
},
"@types/dompurify": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.2.1.tgz",
"integrity": "sha512-3JwbEeRVQ3n6+JgBW/hCdkydRk9/vWT+UEglcXEJqLJEcUganDH37zlfLznxPKTZZfDqA9K229l1qN458ubcOQ==",
"dev": true,
"requires": {
"@types/trusted-types": "*"
}
},
"@types/eslint-visitor-keys": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
@@ -51219,6 +51257,12 @@
"@types/node": "*"
}
},
"@types/trusted-types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.0.tgz",
"integrity": "sha512-I8MnZqNXsOLHsU111oHbn3khtvKMi5Bn4qVFsIWSJcCP1KKDiXX5AEw8UPk0nSopeC+Hvxt6yAy1/a5PailFqg==",
"dev": true
},
"@types/tunnel": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.0.tgz",
@@ -59480,6 +59524,11 @@
"domelementtype": "^2.0.1"
}
},
"dompurify": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.2.6.tgz",
"integrity": "sha512-7b7ZArhhH0SP6W2R9cqK6RjaU82FZ2UPM7RO8qN1b1wyvC/NY1FNWcX1Pu00fFOAnzEORtwXe4bPaClg6pUybQ=="
},
"domutils": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.4.tgz",

View File

@@ -45,13 +45,14 @@
"@types/bytes": "^3.1.0",
"@types/chai": "^4.2.14",
"@types/clear": "^0.1.0",
"@types/codemirror": "^0.0.98",
"@types/codemirror": "^0.0.106",
"@types/color": "^3.0.1",
"@types/color-string": "^1.5.0",
"@types/cookie-parser": "^1.4.2",
"@types/cors": "^2.8.7",
"@types/debug": "^4.1.5",
"@types/diff": "^4.0.2",
"@types/dompurify": "^2.2.1",
"@types/express": "^4.17.9",
"@types/express-pino-logger": "^4.0.2",
"@types/express-session": "^1.17.2",
@@ -190,7 +191,8 @@
"@directus/sdk-js": "file:packages/sdk-js",
"@directus/specs": "file:packages/specs",
"create-directus-project": "file:packages/create-directus-project",
"directus": "file:api"
"directus": "file:api",
"dompurify": "^2.2.6"
},
"husky": {
"hooks": {