mirror of
https://github.com/directus/directus.git
synced 2026-04-03 03:00:39 -04:00
Add wysiwyg interface (#460)
* Install tinymce * Add wysiwyg interface * Mock window.matchMedia * Fix wysiwyg not reading data on value change * Render skeleton loader on top of wysiwyg * Add hideLoader support to interfaces * Sneak this little bugfix in unnoticed It's now passing on the interface / options to the display rendering in render-template
This commit is contained in:
15
.jest/before-all.js
Normal file
15
.jest/before-all.js
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation(query => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(), // deprecated
|
||||
removeListener: jest.fn(), // deprecated
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ module.exports = {
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
coveragePathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.jest/'],
|
||||
transformIgnorePatterns: [`/node_modules/(?!${esModules})`],
|
||||
setupFilesAfterEnv: ['<rootDir>/.jest/before-all.js'],
|
||||
restoreMocks: true,
|
||||
clearMocks: true,
|
||||
resetMocks: true,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"@directus/format-title": "^3.1.1",
|
||||
"@popperjs/core": "^2.3.3",
|
||||
"@sindresorhus/slugify": "^0.11.0",
|
||||
"@tinymce/tinymce-vue": "^3.2.0",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/lodash": "^4.14.150",
|
||||
"@vue/composition-api": "^0.5.0",
|
||||
@@ -34,6 +35,7 @@
|
||||
"resize-observer": "^1.0.0",
|
||||
"semver": "^7.3.2",
|
||||
"stylelint-config-prettier": "^8.0.1",
|
||||
"tinymce": "^5.2.2",
|
||||
"vue": "^2.6.11",
|
||||
"vue-i18n": "^8.17.3",
|
||||
"vue-router": "^3.1.5",
|
||||
|
||||
@@ -5,4 +5,5 @@ export type FormField = Partial<Field> & {
|
||||
field: string;
|
||||
name: string | TranslateResult;
|
||||
hideLabel?: boolean;
|
||||
hideLoader?: boolean;
|
||||
};
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
subdued: batchMode && batchActiveFields.includes(field.field) === false,
|
||||
}"
|
||||
>
|
||||
<v-skeleton-loader v-if="loading" />
|
||||
<v-skeleton-loader v-if="loading && field.hideLoader !== true" />
|
||||
<component
|
||||
:is="`interface-${field.interface}`"
|
||||
v-bind="field.options"
|
||||
@@ -226,6 +226,10 @@ export default defineComponent({
|
||||
(field as FormField).hideLabel = true;
|
||||
}
|
||||
|
||||
if (interfaceUsed?.hideLoader === true) {
|
||||
(field as FormField).hideLoader = true;
|
||||
}
|
||||
|
||||
return field;
|
||||
});
|
||||
|
||||
@@ -349,6 +353,7 @@ export default defineComponent({
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export default defineInterface(({ i18n }) => ({
|
||||
icon: 'remove',
|
||||
component: InterfaceDivider,
|
||||
hideLabel: true,
|
||||
hideLoader: true,
|
||||
options: [
|
||||
{
|
||||
field: 'color',
|
||||
|
||||
@@ -4,6 +4,7 @@ import InterfaceDivider from './divider/';
|
||||
import InterfaceNumeric from './numeric/';
|
||||
import InterfaceSlider from './slider/';
|
||||
import InterfaceToggle from './toggle/';
|
||||
import InterfaceWYSIWYG from './wysiwyg/';
|
||||
|
||||
export const interfaces = [
|
||||
InterfaceTextInput,
|
||||
@@ -12,6 +13,7 @@ export const interfaces = [
|
||||
InterfaceSlider,
|
||||
InterfaceDivider,
|
||||
InterfaceToggle,
|
||||
InterfaceWYSIWYG,
|
||||
];
|
||||
|
||||
export default interfaces;
|
||||
|
||||
@@ -9,6 +9,7 @@ export type InterfaceConfig = {
|
||||
component: Component;
|
||||
options: Partial<Field>[] | Component;
|
||||
hideLabel?: boolean;
|
||||
hideLoader?: boolean;
|
||||
};
|
||||
|
||||
export type InterfaceContext = { i18n: VueI18n };
|
||||
|
||||
147
src/interfaces/wysiwyg/get-editor-styles.ts
Normal file
147
src/interfaces/wysiwyg/get-editor-styles.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
function cssVar(name: string) {
|
||||
return getComputedStyle(document.body).getPropertyValue(name);
|
||||
}
|
||||
|
||||
export default function getEditorStyles(font: 'sans-serif' | 'serif' | 'monospace') {
|
||||
return `
|
||||
body {
|
||||
color: ${cssVar('--foreground-normal')};
|
||||
background-color: ${cssVar('--background-page')};
|
||||
margin: 20px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
h1 {
|
||||
font-family: ${cssVar(`--font-${font}`)}, serif;
|
||||
font-size: 44px;
|
||||
line-height: 52px;
|
||||
font-weight: 300;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h2 {
|
||||
font-size: 34px;
|
||||
line-height: 38px;
|
||||
font-weight: 600;
|
||||
margin-top: 60px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h3 {
|
||||
font-size: 26px;
|
||||
line-height: 31px;
|
||||
font-weight: 600;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h4 {
|
||||
font-size: 22px;
|
||||
line-height: 28px;
|
||||
font-weight: 600;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h5 {
|
||||
font-size: 18px;
|
||||
line-height: 26px;
|
||||
font-weight: 600;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h6 {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 600;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
p {
|
||||
font-family: ${cssVar(`--font-${font}`)}, serif;
|
||||
font-size: 16px;
|
||||
line-height: 32px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
a {
|
||||
color: #546e7a;
|
||||
}
|
||||
ul, ol {
|
||||
font-family: ${cssVar(`--font-${font}`)}, serif;
|
||||
font-size: 18px;
|
||||
line-height: 34px;
|
||||
margin: 24px 0;
|
||||
}
|
||||
ul ul,
|
||||
ol ol,
|
||||
ul ol,
|
||||
ol ul {
|
||||
margin: 0;
|
||||
}
|
||||
b, strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
code {
|
||||
font-size: 18px;
|
||||
line-height: 34px;
|
||||
padding: 2px 4px;
|
||||
font-family: ${cssVar('--family-monospace')}, monospace;
|
||||
background-color: #eceff1;
|
||||
border-radius: 3px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
pre {
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
padding: 20px;
|
||||
font-family: ${cssVar('--family-monospace')}, monospace;
|
||||
background-color: #eceff1;
|
||||
border-radius: 3px;
|
||||
overflow: auto;
|
||||
}
|
||||
blockquote {
|
||||
font-family: ${cssVar(`--font-${font}`)}, serif;
|
||||
font-size: 18px;
|
||||
line-height: 34px;
|
||||
border-left: 2px solid #546e7a;
|
||||
padding-left: 10px;
|
||||
margin-left: -10px;
|
||||
font-style: italic;
|
||||
}
|
||||
video,
|
||||
iframe,
|
||||
img {
|
||||
max-width: 100 %;
|
||||
border-radius: 3px;
|
||||
height: auto;
|
||||
}
|
||||
hr {
|
||||
border: 0;
|
||||
margin-top: 52px;
|
||||
margin-bottom: 56px;
|
||||
text-align: center;
|
||||
}
|
||||
hr: after {
|
||||
content: "...";
|
||||
font-size: 28px;
|
||||
letter-spacing: 16px;
|
||||
line-height: 0;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table th,
|
||||
table td {
|
||||
border: 1px solid #cfd8dc;
|
||||
padding: 0.4rem;
|
||||
}
|
||||
figure {
|
||||
display: table;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
figure figcaption {
|
||||
color: #999;
|
||||
display: block;
|
||||
margin-top: 0.25rem;
|
||||
text-align: center;
|
||||
}`;
|
||||
}
|
||||
120
src/interfaces/wysiwyg/index.ts
Normal file
120
src/interfaces/wysiwyg/index.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import InterfaceWYSIWYG from './wysiwyg.vue';
|
||||
import { defineInterface } from '@/interfaces/define';
|
||||
|
||||
export default defineInterface(({ i18n }) => ({
|
||||
id: 'wysiwyg',
|
||||
name: i18n.t('wysiwyg'),
|
||||
icon: 'format_quote',
|
||||
component: InterfaceWYSIWYG,
|
||||
options: [
|
||||
{
|
||||
field: 'toolbar',
|
||||
name: i18n.t('toolbar'),
|
||||
width: 'full',
|
||||
interface: 'checkboxes',
|
||||
default_value: [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'removeformat',
|
||||
'link',
|
||||
'bullist',
|
||||
'numlist',
|
||||
'blockquote',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'image',
|
||||
'media',
|
||||
'hr',
|
||||
'code',
|
||||
'fullscreen',
|
||||
],
|
||||
options: {
|
||||
choices: {
|
||||
aligncenter: i18n.t('wysiwyg_options.aligncenter'),
|
||||
alignjustify: i18n.t('wysiwyg_options.alignjustify'),
|
||||
alignleft: i18n.t('wysiwyg_options.alignleft'),
|
||||
alignnone: i18n.t('wysiwyg_options.alignnone'),
|
||||
alignright: i18n.t('wysiwyg_options.alignright'),
|
||||
forecolor: i18n.t('wysiwyg_options.forecolor'),
|
||||
backcolor: i18n.t('wysiwyg_options.backcolor'),
|
||||
bold: i18n.t('wysiwyg_options.bold'),
|
||||
italic: i18n.t('wysiwyg_options.italic'),
|
||||
underline: i18n.t('wysiwyg_options.underline'),
|
||||
strikethrough: i18n.t('wysiwyg_options.strikethrough'),
|
||||
subscript: i18n.t('wysiwyg_options.subscript'),
|
||||
superscript: i18n.t('wysiwyg_options.superscript'),
|
||||
blockquote: i18n.t('wysiwyg_options.blockquote'),
|
||||
bullist: i18n.t('wysiwyg_options.bullist'),
|
||||
numlist: i18n.t('wysiwyg_options.numlist'),
|
||||
hr: i18n.t('wysiwyg_options.hr'),
|
||||
link: i18n.t('wysiwyg_options.link'),
|
||||
unlink: i18n.t('wysiwyg_options.unlink'),
|
||||
media: i18n.t('wysiwyg_options.media'),
|
||||
image: i18n.t('wysiwyg_options.image'),
|
||||
copy: i18n.t('wysiwyg_options.copy'),
|
||||
cut: i18n.t('wysiwyg_options.cut'),
|
||||
paste: i18n.t('wysiwyg_options.paste'),
|
||||
h1: i18n.t('wysiwyg_options.h1'),
|
||||
h2: i18n.t('wysiwyg_options.h2'),
|
||||
h3: i18n.t('wysiwyg_options.h3'),
|
||||
h4: i18n.t('wysiwyg_options.h4'),
|
||||
h5: i18n.t('wysiwyg_options.h5'),
|
||||
h6: i18n.t('wysiwyg_options.h6'),
|
||||
fontselect: i18n.t('wysiwyg_options.fontselect'),
|
||||
fontsizeselect: i18n.t('wysiwyg_options.fontsizeselect'),
|
||||
indent: i18n.t('wysiwyg_options.indent'),
|
||||
outdent: i18n.t('wysiwyg_options.outdent'),
|
||||
undo: i18n.t('wysiwyg_options.undo'),
|
||||
redo: i18n.t('wysiwyg_options.redo'),
|
||||
remove: i18n.t('wysiwyg_options.remove'),
|
||||
removeformat: i18n.t('wysiwyg_options.removeformat'),
|
||||
selectall: i18n.t('wysiwyg_options.selectall'),
|
||||
table: i18n.t('wysiwyg_options.table'),
|
||||
visualaid: i18n.t('wysiwyg_options.visualaid'),
|
||||
code: i18n.t('wysiwyg_options.code'),
|
||||
fullscreen: i18n.t('wysiwyg_options.fullscreen'),
|
||||
'ltr rtl': i18n.t('wysiwyg_options.directionality'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'font',
|
||||
name: i18n.t('font'),
|
||||
width: 'half',
|
||||
interface: 'dropdown',
|
||||
default_value: 'serif',
|
||||
options: {
|
||||
items: [
|
||||
{ itemText: i18n.t('sans_serif'), itemValue: 'sans-serif' },
|
||||
{ itemText: i18n.t('monospace'), itemValue: 'monospace' },
|
||||
{ itemText: i18n.t('serif'), itemValue: 'serif' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'customFormats',
|
||||
name: i18n.t('custom_formats'),
|
||||
interface: 'code',
|
||||
options: {
|
||||
type: 'application/json',
|
||||
template: {
|
||||
title: 'My Custom Format',
|
||||
inline: 'span',
|
||||
classes: 'custom-wrapper',
|
||||
styles: { color: '#00ff00', 'font-size': '20px' },
|
||||
attributes: { title: 'My Custom Wrapper' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'tinymceOverrides',
|
||||
name: i18n.t('tinymce_options_override'),
|
||||
interface: 'code',
|
||||
options: {
|
||||
type: 'application/json',
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
33
src/interfaces/wysiwyg/readme.md
Normal file
33
src/interfaces/wysiwyg/readme.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# WYSIWYG
|
||||
|
||||
Rich Text editor based on TineMCE.
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|---------------------|--------------------------------------------------------------------------------------------------|--------------|
|
||||
| `toolbar` | What toolbar options to show | _See below_ |
|
||||
| `custom-formats` | What custom html blocks to show in the editor | `null` |
|
||||
| `font` | Font to render the value in (`sans-serif`, `serif`, or `monospace`) | `serif` |
|
||||
| `tinymce-overrides` | Override any of the [init options](https://www.tiny.cloud/docs/configure/integration-and-setup/) | `null` |
|
||||
|
||||
### Toolbar defaults
|
||||
|
||||
```
|
||||
bold,
|
||||
italic,
|
||||
underline,
|
||||
removeformat,
|
||||
link,
|
||||
bullist,
|
||||
numlist,
|
||||
blockquote,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
image,
|
||||
media,
|
||||
hr,
|
||||
code,
|
||||
fullscreen
|
||||
```
|
||||
239
src/interfaces/wysiwyg/tinymce-overrides.css
Normal file
239
src/interfaces/wysiwyg/tinymce-overrides.css
Normal file
@@ -0,0 +1,239 @@
|
||||
/* stylelint-disable font-family-no-missing-generic-family-keyword */
|
||||
|
||||
.tox .tox-tbtn svg {
|
||||
fill: var(--foreground-normal);
|
||||
}
|
||||
|
||||
.tox-tbtn[aria-label='Bold'] .tox-icon svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tox-tbtn[aria-label='Bold'] .tox-icon::after {
|
||||
display: inline-block;
|
||||
margin-top: 4px;
|
||||
color: var(--foreground-normal);
|
||||
font-size: 24px;
|
||||
font-family: 'Material Icons';
|
||||
content: 'format_bold';
|
||||
font-feature-settings: 'liga';
|
||||
}
|
||||
|
||||
.tox-tbtn[aria-label='Italic'] .tox-icon svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tox-tbtn[aria-label='Italic'] .tox-icon::after {
|
||||
display: inline-block;
|
||||
margin-top: 4px;
|
||||
color: var(--foreground-normal);
|
||||
font-size: 24px;
|
||||
font-family: 'Material Icons';
|
||||
content: 'format_italic';
|
||||
font-feature-settings: 'liga';
|
||||
}
|
||||
|
||||
.tox-tbtn[aria-label='Underline'] .tox-icon svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tox-tbtn[aria-label='Underline'] .tox-icon::after {
|
||||
display: inline-block;
|
||||
margin-top: 4px;
|
||||
color: var(--foreground-normal);
|
||||
font-size: 24px;
|
||||
font-family: 'Material Icons';
|
||||
content: 'format_underlined';
|
||||
font-feature-settings: 'liga';
|
||||
}
|
||||
|
||||
.tox-tbtn[aria-label='Insert/edit link'] .tox-icon svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tox-tbtn[aria-label='Insert/edit link'] .tox-icon::after {
|
||||
display: inline-block;
|
||||
margin-top: 4px;
|
||||
color: var(--foreground-normal);
|
||||
font-size: 24px;
|
||||
font-family: 'Material Icons';
|
||||
content: 'insert_link';
|
||||
font-feature-settings: 'liga';
|
||||
}
|
||||
|
||||
.tox .tox-edit-area__iframe {
|
||||
background-color: var(--background-page);
|
||||
}
|
||||
|
||||
.tox-tinymce {
|
||||
min-height: 300px;
|
||||
color: var(--foreground-normal);
|
||||
border: 2px solid var(--border-normal);
|
||||
border-radius: var(--border-radius);
|
||||
transition: var(--fast) var(--transition);
|
||||
transition-property: color, border-color;
|
||||
}
|
||||
|
||||
.tox-tinymce:hover {
|
||||
border-color: var(--border-normal-alt);
|
||||
}
|
||||
|
||||
.tox-tinymce:focus {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.tox .tox-toolbar,
|
||||
.tox .tox-toolbar__primary,
|
||||
.tox .tox-toolbar__overflow {
|
||||
background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='37px' width='100' height='2' fill='%23cfd8dc'/%3E%3C/svg%3E")
|
||||
left 0 top 0 var(--background-subdued);
|
||||
}
|
||||
|
||||
body.dark .tox .tox-toolbar,
|
||||
body.dark .tox .tox-toolbar__primary,
|
||||
body.dark .tox .tox-toolbar__overflow {
|
||||
background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='37px' width='100' height='2' fill='%23455a64'/%3E%3C/svg%3E")
|
||||
left 0 top 0 var(--background-subdued);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body.auto .tox .tox-toolbar,
|
||||
body.auto .tox .tox-toolbar__primary,
|
||||
body.auto .tox .tox-toolbar__overflow {
|
||||
background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='37px' width='100' height='2' fill='%23455a64'/%3E%3C/svg%3E")
|
||||
left 0 top 0 var(--background-subdued);
|
||||
}
|
||||
}
|
||||
|
||||
.tox .tox-tbtn {
|
||||
margin-right: 2px;
|
||||
color: var(--foreground-normal);
|
||||
}
|
||||
|
||||
.tox .tox-tbtn--enabled,
|
||||
.tox .tox-tbtn--enabled:hover,
|
||||
.tox .tox-tbtn:hover {
|
||||
color: var(--foreground-normal);
|
||||
background: var(--border-normal);
|
||||
}
|
||||
|
||||
.mce-content-body {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
|
||||
.tox .tox-dialog-wrap__backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
.tox .tox-dialog {
|
||||
color: var(--foreground-normal);
|
||||
background-color: var(--background-page);
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tox .tox-dialog--width-lg {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.tox .tox-dialog__header {
|
||||
padding: 16px 24px 0 24px;
|
||||
color: var(--foreground-normal);
|
||||
background-color: var(--background-page);
|
||||
}
|
||||
|
||||
.tox .tox-dialog__body-content {
|
||||
padding: 24px 24px;
|
||||
|
||||
}
|
||||
|
||||
.tox .tox-dialog__footer {
|
||||
padding: 0 24px 24px;
|
||||
color: var(--foreground-normal);
|
||||
background-color: var(--background-page);
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.tox .tox-textfield,
|
||||
.tox .tox-toolbar-textfield,
|
||||
.tox .tox-selectfield select,
|
||||
.tox .tox-textarea {
|
||||
padding: 12px;
|
||||
color: var(--foreground-normal);
|
||||
font-family: monospace;
|
||||
background-color: var(--background-page);
|
||||
border: 2px solid var(--border-normal);
|
||||
border-radius: var(--border-radius);
|
||||
transition: var(--fast) var(--transition);
|
||||
}
|
||||
|
||||
.tox .tox-textfield:focus,
|
||||
.tox .tox-selectfield select:focus,
|
||||
.tox .tox-textarea:focus {
|
||||
border-color: var(--foreground-subdued);
|
||||
}
|
||||
|
||||
.tox .tox-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 136px;
|
||||
height: 44px;
|
||||
padding: 0 20px 1px;
|
||||
color: var(--white);
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 19px;
|
||||
background-color: var(--primary);
|
||||
border: 2px solid var(--primary);
|
||||
border: none;
|
||||
border-color: var(--primary);
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
-webkit-transition: var(--fast) var(--transition);
|
||||
transition: var(--fast) var(--transition);
|
||||
transition-property: border-color, background-color, color;
|
||||
}
|
||||
|
||||
.tox .tox-button:hover:not(:disabled) {
|
||||
background-color: var(--primary);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.tox .tox-button--secondary {
|
||||
color: var(--foreground-normal);
|
||||
background-color: var(--background-normal);
|
||||
border-color: var(--background-normal);
|
||||
}
|
||||
|
||||
.tox .tox-button--secondary:hover:not(:disabled) {
|
||||
color: var(--foreground-normal);
|
||||
background-color: var(--background-normal);
|
||||
border-color: var(--background-normal);
|
||||
}
|
||||
|
||||
.tox .tox-button--naked {
|
||||
min-width: auto;
|
||||
height: auto;
|
||||
color: var(--foreground-subdued);
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.tox .tox-button--naked.tox-button--icon:hover:not(:disabled) {
|
||||
color: var(--foreground-normal);
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.tox .tox-form__group {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.tox .tox-label,
|
||||
.tox .tox-toolbar-label {
|
||||
margin-bottom: 10px;
|
||||
color: var(--foreground-normal);
|
||||
font-size: 14px;
|
||||
}
|
||||
36
src/interfaces/wysiwyg/wysiwyg.story.ts
Normal file
36
src/interfaces/wysiwyg/wysiwyg.story.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { withKnobs, select } from '@storybook/addon-knobs';
|
||||
import readme from './readme.md';
|
||||
import withPadding from '../../../.storybook/decorators/with-padding';
|
||||
import { defineComponent, ref } from '@vue/composition-api';
|
||||
import RawValue from '../../../.storybook/raw-value.vue';
|
||||
|
||||
export default {
|
||||
title: 'Interfaces / WYSIWYG',
|
||||
decorators: [withKnobs, withPadding],
|
||||
parameters: {
|
||||
notes: readme,
|
||||
},
|
||||
};
|
||||
|
||||
export const basic = () =>
|
||||
defineComponent({
|
||||
components: { RawValue },
|
||||
props: {
|
||||
font: {
|
||||
default: select('Font', ['sans-serif', 'serif', 'monospace'], 'serif'),
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const value = ref(false);
|
||||
return { value };
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<interface-wysiwyg
|
||||
v-model="value"
|
||||
:font="font"
|
||||
/>
|
||||
<raw-value>{{ value }}</raw-value>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
144
src/interfaces/wysiwyg/wysiwyg.vue
Normal file
144
src/interfaces/wysiwyg/wysiwyg.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<Editor ref="editorElement" :init="editorOptions" v-model="_value" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref, computed, watch } from '@vue/composition-api';
|
||||
|
||||
import 'tinymce/tinymce';
|
||||
import 'tinymce/themes/silver';
|
||||
import 'tinymce/plugins/media/plugin';
|
||||
import 'tinymce/plugins/table/plugin';
|
||||
import 'tinymce/plugins/hr/plugin';
|
||||
import 'tinymce/plugins/lists/plugin';
|
||||
import 'tinymce/plugins/image/plugin';
|
||||
import 'tinymce/plugins/imagetools/plugin';
|
||||
import 'tinymce/plugins/link/plugin';
|
||||
import 'tinymce/plugins/pagebreak/plugin';
|
||||
import 'tinymce/plugins/code/plugin';
|
||||
import 'tinymce/plugins/insertdatetime/plugin';
|
||||
import 'tinymce/plugins/autoresize/plugin';
|
||||
import 'tinymce/plugins/paste/plugin';
|
||||
import 'tinymce/plugins/preview/plugin';
|
||||
import 'tinymce/plugins/fullscreen/plugin';
|
||||
import 'tinymce/plugins/directionality/plugin';
|
||||
|
||||
import Editor from '@tinymce/tinymce-vue';
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import getEditorStyles from './get-editor-styles';
|
||||
|
||||
type CustomFormat = {
|
||||
title: string;
|
||||
inline: string;
|
||||
classes: string;
|
||||
styles: Record<string, string>;
|
||||
attributes: Record<string, string>;
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
components: { Editor },
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
toolbar: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'removeformat',
|
||||
'link',
|
||||
'bullist',
|
||||
'numlist',
|
||||
'blockquote',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'image',
|
||||
'media',
|
||||
'hr',
|
||||
'code',
|
||||
'fullscreen',
|
||||
],
|
||||
},
|
||||
font: {
|
||||
type: String as PropType<'sans-serif' | 'serif' | 'monospace'>,
|
||||
default: 'serif',
|
||||
},
|
||||
customFormats: {
|
||||
type: Array as PropType<CustomFormat[]>,
|
||||
default: () => [],
|
||||
},
|
||||
tinymceOverrides: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const editorElement = ref<any>(null);
|
||||
|
||||
const _value = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
set(newValue: string) {
|
||||
emit('input', newValue);
|
||||
},
|
||||
});
|
||||
|
||||
const editorOptions = computed(() => {
|
||||
let styleFormats = null;
|
||||
|
||||
if (Array.isArray(props.customFormats) && props.customFormats.length > 0) {
|
||||
styleFormats = props.customFormats;
|
||||
}
|
||||
|
||||
let toolbarString = props.toolbar.join(' ');
|
||||
|
||||
if (styleFormats) {
|
||||
toolbarString += ' styleselect';
|
||||
}
|
||||
|
||||
return {
|
||||
skin: false,
|
||||
skin_url: false,
|
||||
content_css: false,
|
||||
content_style: getEditorStyles(props.font as 'sans-serif' | 'serif' | 'monospace'),
|
||||
plugins:
|
||||
'media table hr lists image link pagebreak code insertdatetime autoresize paste preview fullscreen directionality',
|
||||
branding: false,
|
||||
max_height: 1000,
|
||||
elementpath: false,
|
||||
statusbar: false,
|
||||
menubar: false,
|
||||
convert_urls: false,
|
||||
readonly: props.disabled,
|
||||
extended_valid_elements: 'audio[loop],source',
|
||||
toolbar: toolbarString,
|
||||
style_formats: styleFormats,
|
||||
...(props.tinymceOverrides || {}),
|
||||
};
|
||||
});
|
||||
|
||||
return { editorElement, editorOptions, _value };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
@import '~tinymce/skins/ui/oxide/skin.css';
|
||||
@import './tinymce-overrides.css';
|
||||
</style>
|
||||
@@ -284,6 +284,56 @@
|
||||
"icon_on": "Icon (On)",
|
||||
"icon_off": "Icon (Off)",
|
||||
"label": "Label",
|
||||
"wysiwyg": "WYSIWYG",
|
||||
"toolbar": "Toolbar",
|
||||
"wysiwyg_options": {
|
||||
"aligncenter": "Align Center",
|
||||
"alignjustify": "Align Justify",
|
||||
"alignleft": "Align Left",
|
||||
"alignnone": "Align None",
|
||||
"alignright": "Align Right",
|
||||
"forecolor": "Foreground Color",
|
||||
"backcolor": "Background Color",
|
||||
"bold": "Bold",
|
||||
"italic": "Italic",
|
||||
"underline": "Underline",
|
||||
"strikethrough": "Strikethrough",
|
||||
"subscript": "Subscript",
|
||||
"superscript": "Superscript",
|
||||
"blockquote": "Blockquote",
|
||||
"bullist": "Bullet List",
|
||||
"numlist": "Numbered List",
|
||||
"hr": "Horizontal Rule",
|
||||
"link": "Add Link",
|
||||
"unlink": "Remove Link",
|
||||
"media": "Add Media",
|
||||
"image": "Add Image",
|
||||
"copy": "Copy",
|
||||
"cut": "Cut",
|
||||
"paste": "Paste",
|
||||
"h1": "Heading 1",
|
||||
"h2": "Heading 2",
|
||||
"h3": "Heading 3",
|
||||
"h4": "Heading 4",
|
||||
"h5": "Heading 5",
|
||||
"h6": "Heading 6",
|
||||
"fontselect": "Select Font",
|
||||
"fontsizeselect": "Select Font Size",
|
||||
"indent": "Indent",
|
||||
"outdent": "Outdent",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo",
|
||||
"remove": "Remove",
|
||||
"removeformat": "Remove Format",
|
||||
"selectall": "Select All",
|
||||
"table": "Table",
|
||||
"visualaid": "View invisible elements",
|
||||
"code": "View Source",
|
||||
"fullscreen": "Full Screen",
|
||||
"directionality": "Directionality"
|
||||
},
|
||||
"custom_formats": "Custom Formats",
|
||||
"tinymce_options_override": "TinyMCE Options Override",
|
||||
|
||||
"about_directus": "About Directus",
|
||||
"activity_log": "Activity Log",
|
||||
|
||||
@@ -52,7 +52,7 @@ export const useNotificationsStore = createStore({
|
||||
}
|
||||
return notification;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
lastFour(state) {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
:is="`display-${part.component}`"
|
||||
:key="index"
|
||||
:value="part.value"
|
||||
:interface="part.interface"
|
||||
:interface-options="part.interfaceOptions"
|
||||
v-bind="part.options"
|
||||
/>
|
||||
<template v-else>{{ part }}</template>
|
||||
@@ -74,6 +76,8 @@ export default defineComponent({
|
||||
component: field.display,
|
||||
options: field.display_options,
|
||||
value: value,
|
||||
interface: field.interface,
|
||||
interfaceOptions: field.options,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
10
yarn.lock
10
yarn.lock
@@ -1852,6 +1852,11 @@
|
||||
dependencies:
|
||||
defer-to-connect "^1.0.1"
|
||||
|
||||
"@tinymce/tinymce-vue@^3.2.0":
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@tinymce/tinymce-vue/-/tinymce-vue-3.2.0.tgz#abadab7fd6e143688e756df779fc3b81e586427a"
|
||||
integrity sha512-7mrexFcL23TdsHL35EHrhUN1HhRW5DbHeraai+idOqARhUhbATDxp4RBBUBGfuCjsEPnkNdu9+CcJl9xETSH/g==
|
||||
|
||||
"@types/babel__core@^7.1.0":
|
||||
version "7.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.6.tgz#16ff42a5ae203c9af1c6e190ed1f30f83207b610"
|
||||
@@ -14260,6 +14265,11 @@ tinycolor2@^1.4.1:
|
||||
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8"
|
||||
integrity sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=
|
||||
|
||||
tinymce@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-5.2.2.tgz#1ac77cce1565b54932b4e612d2fd9c07b687f238"
|
||||
integrity sha512-G1KZOxHIrokeP/rhJuvwctmeAuHDAeH8AI1ubnVcdMZtmC6mUh3SfESqFJrFWoiF143OUMC61GkVhi920pIP0A==
|
||||
|
||||
tmp@^0.0.33:
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||
|
||||
Reference in New Issue
Block a user