diff --git a/frontend/package.json b/frontend/package.json
index 6022e94..3cc421e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,6 +12,7 @@
"axios": "^0.19.2",
"buefy": "^0.8.20",
"c3": "^0.7.18",
+ "codeflask": "^1.4.1",
"core-js": "^3.6.5",
"dayjs": "^1.8.28",
"humps": "^2.0.1",
diff --git a/frontend/src/assets/style.scss b/frontend/src/assets/style.scss
index aaecd00..6a733d7 100644
--- a/frontend/src/assets/style.scss
+++ b/frontend/src/assets/style.scss
@@ -126,6 +126,16 @@ section {
}
}
+/* HTML code editor */
+.html-editor {
+ position: relative;
+ width: 100%;
+ min-height: 250px;
+ height: 65vh;
+ border: 1px solid $grey-lighter;
+ border-radius: 2px;
+}
+
/* Table colors and padding */
.main table {
thead th {
diff --git a/frontend/src/components/Editor.vue b/frontend/src/components/Editor.vue
index 144b33e..0eac32e 100644
--- a/frontend/src/components/Editor.vue
+++ b/frontend/src/components/Editor.vue
@@ -33,10 +33,8 @@
/>
-
-
+
import 'quill/dist/quill.snow.css';
import 'quill/dist/quill.core.css';
+
import { quillEditor } from 'vue-quill-editor';
+import CodeFlask from 'codeflask';
+
import CampaignPreview from './CampaignPreview.vue';
import Media from '../views/Media.vue';
@@ -155,6 +156,31 @@ export default {
this.$emit('input', { contentType: this.form.format, body: this.form.body });
},
+ initHTMLEditor() {
+ // CodeFlask editor is rendered in a shadow DOM tree to keep its styles
+ // sandboxed away from the global styles.
+ const el = document.createElement('code-flask');
+ el.attachShadow({ mode: 'open' });
+ el.shadowRoot.innerHTML = `
+
+
+ `;
+ this.$refs.htmlEditor.appendChild(el);
+
+ const flask = new CodeFlask(el.shadowRoot.getElementById('area'), {
+ language: 'html',
+ lineNumbers: true,
+ styleParent: el.shadowRoot,
+ readonly: this.disabled,
+ });
+
+ flask.updateCode(this.form.body);
+ flask.onUpdate((b) => {
+ this.form.body = b;
+ this.$emit('input', { contentType: this.form.format, body: this.form.body });
+ });
+ },
+
togglePreview() {
this.isPreviewing = !this.isPreviewing;
},
@@ -168,6 +194,12 @@ export default {
},
},
+ computed: {
+ htmlFormat() {
+ return this.form.format;
+ },
+ },
+
watch: {
// Capture contentType and body passed from the parent as props.
contentType(f) {
@@ -182,6 +214,19 @@ export default {
body(b) {
this.form.body = b;
},
+
+ htmlFormat(f) {
+ if (f !== 'html') {
+ return;
+ }
+
+ this.$nextTick(() => {
+ this.initHTMLEditor();
+ });
+ },
+ },
+
+ mounted() {
},
};
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 246eee0..f2f54cf 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -923,6 +923,11 @@
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
+"@types/prismjs@^1.9.1":
+ version "1.16.1"
+ resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.16.1.tgz#50b82947207847db6abcbcd14caa89e3b897c259"
+ integrity sha512-RNgcK3FEc1GpeOkamGDq42EYkb6yZW5OWQwTS56NJIB8WL0QGISQglA7En7NUx9RGP8AC52DOe+squqbAckXlA==
+
"@types/q@^1.5.1":
version "1.5.4"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
@@ -2306,6 +2311,15 @@ cli-width@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==
+clipboard@^2.0.0:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.6.tgz#52921296eec0fdf77ead1749421b21c968647376"
+ integrity sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==
+ dependencies:
+ good-listener "^1.2.2"
+ select "^1.1.2"
+ tiny-emitter "^2.0.0"
+
clipboardy@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-2.3.0.tgz#3c2903650c68e46a91b388985bc2774287dba290"
@@ -2366,6 +2380,14 @@ code-point-at@^1.0.0:
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
+codeflask@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/codeflask/-/codeflask-1.4.1.tgz#c5229854e3f648377922a75f1145f7316030d3db"
+ integrity sha512-4vb2IbE/iwvP0Uubhd2ixVeysm3KNC2pl7SoDaisxq1juhZzvap3qbaX7B2CtpQVvv5V9sjcQK8hO0eTcY0V9Q==
+ dependencies:
+ "@types/prismjs" "^1.9.1"
+ prismjs "^1.14.0"
+
collection-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
@@ -3264,6 +3286,11 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
+delegate@^3.1.2:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
+ integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==
+
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@@ -4444,6 +4471,13 @@ globule@^1.0.0:
lodash "~4.17.12"
minimatch "~3.0.2"
+good-listener@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
+ integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=
+ dependencies:
+ delegate "^3.1.2"
+
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.2:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
@@ -7095,6 +7129,13 @@ pretty-error@^2.0.2:
renderkid "^2.0.1"
utila "~0.4"
+prismjs@^1.14.0:
+ version "1.20.0"
+ resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.20.0.tgz#9b685fc480a3514ee7198eac6a3bf5024319ff03"
+ integrity sha512-AEDjSrVNkynnw6A+B1DsFkd6AVdTnp+/WoUixFRULlCLZVRZlVQMVWio/16jv7G1FscUxQxOQhWwApgbnxr6kQ==
+ optionalDependencies:
+ clipboard "^2.0.0"
+
private@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
@@ -7743,6 +7784,11 @@ select-hose@^2.0.0:
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=
+select@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
+ integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=
+
selfsigned@^1.10.7:
version "1.10.7"
resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b"
@@ -8531,6 +8577,11 @@ timsort@^0.3.0:
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
+tiny-emitter@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
+ integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
+
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"