diff --git a/app/views/ublog/form.scala b/app/views/ublog/form.scala index 663d404be0..3a6f0112a1 100644 --- a/app/views/ublog/form.scala +++ b/app/views/ublog/form.scala @@ -17,7 +17,7 @@ object form { def create(user: User, f: Form[UblogPostData], captcha: Captcha)(implicit ctx: Context) = views.html.base.layout( - moreCss = cssTag("ublog"), + moreCss = cssTag("ublog.form"), moreJs = captchaTag, title = s"${trans.ublog.xBlog.txt(user.username)} • ${trans.ublog.newPost()}" ) { @@ -30,7 +30,7 @@ object form { def edit(user: User, post: UblogPost, f: Form[UblogPostData])(implicit ctx: Context) = views.html.base.layout( - moreCss = cssTag("ublog"), + moreCss = cssTag("ublog.form"), moreJs = jsModule("ublog"), title = s"${trans.ublog.xBlog.txt(user.username)} blog • ${post.title}" ) { @@ -89,8 +89,13 @@ object form { form3.group( form("markdown"), trans.ublog.postBody(), - help = frag(markdownAvailable, br, trans.embedsAvailable()).some - )(form3.textarea(_)(rows := 30)), + help = frag(trans.embedsAvailable()).some + ) { field => + frag( + form3.textarea(field)(), + div(id := "markdown-editor") + ) + }, captcha.fold(views.html.base.captcha.hiddenEmpty(form)) { c => views.html.base.captcha(form, c) }, diff --git a/public/vendor/toastui/toastui-editor-dark.css b/public/vendor/toastui/toastui-editor-dark.css new file mode 120000 index 0000000000..5073513507 --- /dev/null +++ b/public/vendor/toastui/toastui-editor-dark.css @@ -0,0 +1 @@ +../../../node_modules/@toast-ui/editor/dist/theme/toastui-editor-dark.css \ No newline at end of file diff --git a/public/vendor/toastui/toastui-editor-only.css b/public/vendor/toastui/toastui-editor-only.css new file mode 120000 index 0000000000..753dd2d2f5 --- /dev/null +++ b/public/vendor/toastui/toastui-editor-only.css @@ -0,0 +1 @@ +../../../node_modules/@toast-ui/editor/dist/toastui-editor-only.css \ No newline at end of file diff --git a/public/vendor/toastui/toastui-editor.css b/public/vendor/toastui/toastui-editor.css new file mode 120000 index 0000000000..e4f72a9dc3 --- /dev/null +++ b/public/vendor/toastui/toastui-editor.css @@ -0,0 +1 @@ +../../../node_modules/@toast-ui/editor/dist/toastui-editor.css \ No newline at end of file diff --git a/ui/site/css/build/_ublog.form.scss b/ui/site/css/build/_ublog.form.scss new file mode 100644 index 0000000000..85ed605ea0 --- /dev/null +++ b/ui/site/css/build/_ublog.form.scss @@ -0,0 +1,6 @@ +@import '../../../common/css/plugin'; +@import '../../../common/css/form/form3'; +@import '../../../common/css/form/captcha'; +@import '../../../../public/vendor/toastui/toastui-editor'; +@import '../../../../public/vendor/toastui/toastui-editor-dark'; +@import '../ublog/form'; diff --git a/ui/site/css/build/_ublog.scss b/ui/site/css/build/_ublog.scss index d1085b8ea6..de8094e7cb 100644 --- a/ui/site/css/build/_ublog.scss +++ b/ui/site/css/build/_ublog.scss @@ -1,5 +1,3 @@ @import '../../../common/css/plugin'; @import '../../../common/css/component/slist'; -@import '../../../common/css/form/form3'; -@import '../../../common/css/form/captcha'; @import '../ublog/ublog'; diff --git a/ui/site/css/build/ublog.form.dark.scss b/ui/site/css/build/ublog.form.dark.scss new file mode 100644 index 0000000000..4c26e0ae12 --- /dev/null +++ b/ui/site/css/build/ublog.form.dark.scss @@ -0,0 +1,2 @@ +@import '../../../common/css/theme/dark'; +@import 'ublog.form'; diff --git a/ui/site/css/build/ublog.form.light.scss b/ui/site/css/build/ublog.form.light.scss new file mode 100644 index 0000000000..3e74f92d83 --- /dev/null +++ b/ui/site/css/build/ublog.form.light.scss @@ -0,0 +1,2 @@ +@import '../../../common/css/theme/light'; +@import 'ublog.form'; diff --git a/ui/site/css/build/ublog.form.transp.scss b/ui/site/css/build/ublog.form.transp.scss new file mode 100644 index 0000000000..e0015425a3 --- /dev/null +++ b/ui/site/css/build/ublog.form.transp.scss @@ -0,0 +1,2 @@ +@import '../../../common/css/theme/transp'; +@import 'ublog.form'; diff --git a/ui/site/css/ublog/_form.scss b/ui/site/css/ublog/_form.scss index ec393a9c66..472d16224d 100644 --- a/ui/site/css/ublog/_form.scss +++ b/ui/site/css/ublog/_form.scss @@ -24,3 +24,16 @@ max-width: 80ch; } } +#form3-markdown { + display: none; +} +#markdown-editor { + height: 80vh; + .toastui-editor-popup-body li[data-type='Heading'] { + &[data-level='1'], + &[data-level='5'], + &[data-level='6'] { + display: none; + } + } +} diff --git a/ui/site/css/ublog/_ublog.scss b/ui/site/css/ublog/_ublog.scss index 8c3ba61e1a..e4a17fcb24 100644 --- a/ui/site/css/ublog/_ublog.scss +++ b/ui/site/css/ublog/_ublog.scss @@ -1,5 +1,4 @@ @import 'card'; -@import 'form'; @import 'markup'; .ublog { diff --git a/ui/site/package.json b/ui/site/package.json index 5faaa3938a..eb591114b6 100644 --- a/ui/site/package.json +++ b/ui/site/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "@fnando/sparkline": "^0.3.10", + "@toast-ui/editor": "^3.0.3", "@yaireo/tagify": "^4.0.5", "apexcharts": "^3.27.1", "chat": "2.0.0", diff --git a/ui/site/src/ublog.ts b/ui/site/src/ublog.ts index fb71e4399d..8bfe90719a 100644 --- a/ui/site/src/ublog.ts +++ b/ui/site/src/ublog.ts @@ -1,5 +1,6 @@ import * as xhr from 'common/xhr'; import spinner from './component/spinner'; +import Editor from '@toast-ui/editor'; lichess.load.then(() => { $('.ublog-post-form__image').each(function (this: HTMLFormElement) { @@ -17,4 +18,28 @@ lichess.load.then(() => { }); }); $('.flash').addClass('fade'); + $('#markdown-editor').each(function (this: HTMLTextAreaElement) { + const editor = new Editor({ + el: this, + usageStatistics: false, + height: '70vh', + theme: $('body').data('theme') == 'light' ? 'light' : 'dark', + initialValue: $('#form3-markdown').val() as string, + initialEditType: 'wysiwyg', + language: $('html').attr('lang') as string, + toolbarItems: [ + ['heading', 'bold', 'italic', 'strike'], + ['hr', 'quote'], + ['ul', 'ol'], + ['table', 'image', 'link'], + ['code', 'codeblock'], + ['scrollSync'], + ], + events: { + change() { + $('#form3-markdown').val(editor.getMarkdown()); + }, + }, + }); + }); }); diff --git a/yarn.lock b/yarn.lock index 62949fcb19..dd1bfadff2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -159,6 +159,19 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@toast-ui/editor@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@toast-ui/editor/-/editor-3.0.3.tgz#8423e27ce92b6806c7dbdab1ea67f065afa91a81" + integrity sha512-uU8FhUKsbW6nYSS+NrwqE7d/63JvezfmQ9xsRwIV9crLLyZ3K6a9/kT8XPIDcep2yKviybIDLeTEbXxuwBWvFQ== + dependencies: + prosemirror-commands "^1.1.9" + prosemirror-history "^1.1.3" + prosemirror-inputrules "^1.1.3" + prosemirror-keymap "^1.1.4" + prosemirror-model "^1.14.1" + prosemirror-state "^1.3.4" + prosemirror-view "^1.18.7" + "@types/chart.js@^2.9.29": version "2.9.33" resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.9.33.tgz#59c6de80b8134379156f4ba7b96c0928a721c1ab" @@ -3888,6 +3901,11 @@ ordered-read-streams@^1.0.0: dependencies: readable-stream "^2.0.1" +orderedmap@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-1.1.1.tgz#c618e77611b3b21d0fe3edc92586265e0059c789" + integrity sha512-3Ux8um0zXbVacKUkcytc0u3HgC0b0bBLT+I60r2J/En72cI0nZffqrA7Xtf2Hqs27j1g82llR5Mhbd0Z1XW4AQ== + os-locale@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" @@ -4158,6 +4176,71 @@ prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" +prosemirror-commands@^1.1.9: + version "1.1.10" + resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.1.10.tgz#406a6589966e6cd80809cea2d801fb998639b37d" + integrity sha512-IWyBBXNAd44RM6NnBPljwq+/CM2oYCQJkF+YhKEAZNwzW0uFdGf4qComhjbKZzqFdu6Iub2ZhNsXgwPibA0lCQ== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.0.0" + +prosemirror-history@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.2.0.tgz#04cc4df8d2f7b2a46651a2780de191ada6d465ea" + integrity sha512-B9v9xtf4fYbKxQwIr+3wtTDNLDZcmMMmGiI3TAPShnUzvo+Rmv1GiUrsQChY1meetHl7rhML2cppF3FTs7f7UQ== + dependencies: + prosemirror-state "^1.2.2" + prosemirror-transform "^1.0.0" + rope-sequence "^1.3.0" + +prosemirror-inputrules@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.1.3.tgz#93f9199ca02473259c30d7e352e4c14022d54638" + integrity sha512-ZaHCLyBtvbyIHv0f5p6boQTIJjlD6o2NPZiEaZWT2DA+j591zS29QQEMT4lBqwcLW3qRSf7ZvoKNbf05YrsStw== + dependencies: + prosemirror-state "^1.0.0" + prosemirror-transform "^1.0.0" + +prosemirror-keymap@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.1.4.tgz#8b481bf8389a5ac40d38dbd67ec3da2c7eac6a6d" + integrity sha512-Al8cVUOnDFL4gcI5IDlG6xbZ0aOD/i3B17VT+1JbHWDguCgt/lBHVTHUBcKvvbSg6+q/W4Nj1Fu6bwZSca3xjg== + dependencies: + prosemirror-state "^1.0.0" + w3c-keyname "^2.2.0" + +prosemirror-model@^1.0.0, prosemirror-model@^1.14.1, prosemirror-model@^1.14.3: + version "1.14.3" + resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.14.3.tgz#a9c250d3c4023ddf10ecb41a0a7a130e9741d37e" + integrity sha512-yzZlBaSxfUPIIP6U5Edh5zKxJPZ5f7bwZRhiCuH3UYkWhj+P3d8swHsbuAMOu/iDatDc5J/Qs5Mb3++mZf+CvQ== + dependencies: + orderedmap "^1.1.0" + +prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.3.4.tgz#4c6b52628216e753fc901c6d2bfd84ce109e8952" + integrity sha512-Xkkrpd1y/TQ6HKzN3agsQIGRcLckUMA9u3j207L04mt8ToRgpGeyhbVv0HI7omDORIBHjR29b7AwlATFFf2GLA== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-transform "^1.0.0" + +prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.3.2.tgz#5620ebe7379e6fae4f34ecc881886cb22ce96579" + integrity sha512-/G6d/u9Mf6Bv3H1XR8VxhpjmUO75LYmnvj+s3ZfZpakU1hnQbsvCEybml1B3f2IWUAAQRFkbO1PnsbFhLZsYsw== + dependencies: + prosemirror-model "^1.0.0" + +prosemirror-view@^1.18.7: + version "1.20.0" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.20.0.tgz#64198845f0d112c14a5594732c46a96ac3d9d828" + integrity sha512-OqU/bHUIiJhpyb2ytX4fLalYAJJOyZ0k5H0AibP/WPsdHq9CqmJFU676gO+N8WWhR5tVz1NxsqMZgEBy5Lc6GQ== + dependencies: + prosemirror-model "^1.14.3" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.1.0" + psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -4523,6 +4606,11 @@ rollup@^2.56.2: optionalDependencies: fsevents "~2.3.2" +rope-sequence@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.2.tgz#a19e02d72991ca71feb6b5f8a91154e48e3c098b" + integrity sha512-ku6MFrwEVSVmXLvy3dYph3LAMNS0890K7fabn+0YIRQ2T96T9F4gkFf0vf0WW0JUraNWwGRtInEpH7yO4tbQZg== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -5595,6 +5683,11 @@ vinyl@^2.0.0: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" +w3c-keyname@^2.2.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.4.tgz#4ade6916f6290224cdbd1db8ac49eab03d0eef6b" + integrity sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw== + which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"