diff --git a/CHANGELOG.md b/CHANGELOG.md index a50eb436599300354f5b26ec853f3940a313ac0a..c295a5fc637112645189759f3267859385514518 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 See `build.properties.d/00_default.properties` for more information * Start editing an entity/creating a new record directly by adding an `#edit` or `#new_record` URI fragment, respectively. +* Optional WYSIWYG editor with markdown output for text properties. Controled by + the `BUILD_MODULE_EXT_EDITMODE_WYSIWYG_TEXT` build variable which is set do + `DISABLED` by default. ### Changed (for changes in existing functionality) diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index c6043d09d049b91cfc0a03560970537feb8bbc06..f3e1d1706dd8dd9bdbdab16377c2c75ed0299f7f 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -24,5 +24,12 @@ * proj4js-2.5.0 * Proj4Leaflet-1.0.1 +## For CKEditor (WYSIWYG editor in edit mode) + +* we're using a custom-built ckeditor 31.0.0 from + https://ckeditor.com/ckeditor-5/online-builder/ with a customized set of + editor plugins. Please refer to the `package.json` within + `libs/ckeditor...zip` for a full list of said plugins. + ## For testing * qunit-2.9.2 diff --git a/Makefile b/Makefile index c820cfb159d9b63518dff445883615e67fbd9289..5cca686319c617006007e4884cfb269796ac7af4 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ LIBS_DIR = $(abspath libs) TEST_CORE_DIR = $(abspath test/core/) TEST_EXT_DIR = $(abspath test/ext) TEST_SSS_DIR =$(abspath test/server_side_scripting) -LIBS = fonts css/fonts css/bootstrap.css css/bootstrap-icons.css js/state-machine.js js/jquery.js js/showdown.js js/dropzone.js css/dropzone.css js/loglevel.js js/leaflet.js css/leaflet.css css/images js/leaflet-latlng-graticule.js js/proj4.js js/proj4leaflet.js js/leaflet-coordinates.js css/leaflet-coordinates.css js/leaflet-graticule.js js/bootstrap-select.js css/bootstrap-select.css js/bootstrap-autocomplete.min.js js/plotly.js js/pako.js js/utif.js js/bootstrap.js js/qrcode.js +LIBS = fonts css/fonts css/bootstrap.css css/bootstrap-icons.css js/state-machine.js js/jquery.js js/showdown.js js/dropzone.js css/dropzone.css js/loglevel.js js/leaflet.js css/leaflet.css css/images js/leaflet-latlng-graticule.js js/proj4.js js/proj4leaflet.js js/leaflet-coordinates.js css/leaflet-coordinates.css js/leaflet-graticule.js js/bootstrap-select.js css/bootstrap-select.css js/bootstrap-autocomplete.min.js js/plotly.js js/pako.js js/utif.js js/bootstrap.js js/qrcode.js js/ckeditor.js TEST_LIBS = $(LIBS) js/qunit.js css/qunit.css $(subst $(TEST_CORE_DIR)/,,$(shell find $(TEST_CORE_DIR)/)) @@ -305,6 +305,9 @@ $(LIBS_DIR)/js/utif.js: unzip $(LIBS_DIR)/js $(LIBS_DIR)/js/qrcode.js: unzip $(LIBS_DIR)/js ln -s $(LIBS_DIR)/qrcode-1.4.4/qrcode.min.js $@ +$(LIBS_DIR)/js/ckeditor.js: unzip $(LIBS_DIR)/js + ln -s $(LIBS_DIR)/ckeditor5-31.0.0-k356w86hp13l/build/ckeditor.js $@ + $(addprefix $(LIBS_DIR)/, js css): mkdir $@ || true diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties index db01885d1494ffd3f07eecfa2e6f2d26f56e3705..535a6c846a5c39c48937dee43f087501f43285ae 100644 --- a/build.properties.d/00_default.properties +++ b/build.properties.d/00_default.properties @@ -60,6 +60,8 @@ BUILD_MODULE_USER_MANAGEMENT_CHANGE_OWN_PASSWORD_REALM=CaosDB BUILD_MODULE_EXT_RESOLVE_REFERENCES=ENABLED BUILD_EXT_REFERENCES_CUSTOM_RESOLVER=person_reference +BUILD_MODULE_EXT_EDITMODE_WYSIWYG_TEXT=DISABLED + ############################################################################## # Navbar properties ############################################################################## @@ -173,4 +175,6 @@ MODULE_DEPENDENCIES=( qrcode.js ext_qrcode.js form_panel.js + ckeditor.js + ext_editmode_wysiwyg_text.js ) diff --git a/libs/ckeditor5-31.0.0-k356w86hp13l.zip b/libs/ckeditor5-31.0.0-k356w86hp13l.zip new file mode 100644 index 0000000000000000000000000000000000000000..71523482a2bdfa7c0a9776eeed7aa7be010e053b Binary files /dev/null and b/libs/ckeditor5-31.0.0-k356w86hp13l.zip differ diff --git a/src/core/js/ext_editmode_wysiwyg_text.js b/src/core/js/ext_editmode_wysiwyg_text.js new file mode 100644 index 0000000000000000000000000000000000000000..a95b9911e37bfd2cff7022a624b64942b10655d8 --- /dev/null +++ b/src/core/js/ext_editmode_wysiwyg_text.js @@ -0,0 +1,118 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2021 Florian Spreckelsen <f.spreckelsen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +"use strict"; + +/** + * Replaces textareas in the edit mode by a wysiwyg editor + * + * @module ext_editmode_wysiwyg_text + * @version 0.1 + * + * @param jQuery - well-known library. + * @param log - singleton from loglevel library or javascript console. + * @param {class} ClassicEditor - ClassicEditor class from ckEditor + * @param {module} edit_mode - caosdb's edit-mode module + * @param {function} getPropertyElements - caosdb's function to extract the + * property HTML elements from an entity HTML element + * @param {function} getPropertyDatatype - caosdb's function to extract the + * data type from a property HTML element + * @param {function} getPropertyName - caosdb's function to extract the + * name from a property HTML element + */ +var ext_editmode_wysiwyg_text = function ($, logger, ClassicEditor, edit_mode, getPropertyElements, getPropertyDatatype, getPropertyName) { + + var insertEditorInProperty = async function (prop) { + if (!(getPropertyDatatype(prop) === 'TEXT')) { + // Ignore anything that isn't a list property, even LIST<TEXT> + return; + } + + try{ + const editor = await ClassicEditor + .create(prop.querySelector('textarea'), { + // use all plugins since we built the editor dependency to + // contain only those we need. + plugins: ClassicEditor.builtinPlugins, + // Markdown needs a header row so enforce this + table: { + defaultHeadings: { + rows: 1, + columns: 0 + }, + }, + }) + console.log('Initialized editor for ' + getPropertyName(prop)); + // Manually implement saving the data since edit mode is not + // a form to be submitted. + editor.model.document.on("change:data", (e) => { + editor.updateSourceElement(); + }); + } catch(error) { + console.error(error.stack); + } + } + + var replaceTextAreas = function (entity) { + const properties = getPropertyElements(entity); + for (let prop of properties) { + // TODO(fspreck): This will be replaced by a whitelist of properties + // in the future. + if (getPropertyName(prop)) { + insertEditorInProperty(prop); + } + } + } + + var init = function () { + + // Insert an editor into all TEXT properties of the entity which is + // being edited. + document.body.addEventListener(edit_mode.start_edit.type, (e) => { + console.log('Replacing text areas ...'); + ext_editmode_wysiwyg_text.replaceTextAreas(e.target); + }, true); + + // Listen to added properties and replace the textarea if necessary + document.body.addEventListener(edit_mode.property_added.type, (e) => { + console.log('Replacing textarea in ' + getPropertyName(e.target)); + ext_editmode_wysiwyg_text.insertEditorInProperty(e.target); + }, true) + + // Listen to properties, the data type of which has changed. Mainly + // because of change from list to scalar and vice versa. + document.body.addEventListener(edit_mode.property_data_type_changed.type, (e) => { + console.log('Re-rendering ' + getPropertyName(e.target)); + ext_editmode_wysiwyg_text.insertEditorInProperty(e.target); + }, true); + }; + + return { + init: init, + replaceTextAreas: replaceTextAreas, + insertEditorInProperty: insertEditorInProperty, + }; +}($, log.getLogger("ext_editmode_wysiwyg_text"), ClassicEditor, edit_mode, getPropertyElements, getPropertyDatatype, getPropertyName); + +$(document).ready(() => { + if ("${BUILD_MODULE_EXT_EDITMODE_WYSIWYG_TEXT}" == "ENABLED") { + caosdb_modules.register(ext_editmode_wysiwyg_text); + } +});