From 3316af11d38c51e65d4951562b926636e0967c19 Mon Sep 17 00:00:00 2001 From: Alexandre Date: Mon, 17 Jun 2024 11:53:20 -0300 Subject: [PATCH] Addded more block options. keep the focus on content and allow choose a block with key up and down. --- demo/assets/bundle.js | 248 ++++++++++- demo/assets/img/11zon_cropped (1).jpeg | Bin 0 -> 5374 bytes demo/assets/img/11zon_cropped.jpeg | Bin 0 -> 5169 bytes ...6 - 1172px-Venus_botticelli_detail (1).jpg | Bin 0 -> 4037 bytes demo/assets/img/icons8-google-maps-96.png | Bin 0 -> 2361 bytes demo/assets/{ => img}/kepler.ico | Bin demo/assets/img/youtube.jpeg | Bin 0 -> 4264 bytes demo/assets/style.css | 265 +++++++++--- demo/index.html | 395 +++++++++++++++++- src/add-block.css | 23 + src/add-block.js | 13 + src/create-block-hit-enter.js | 30 ++ src/delete-block-hit-backspace.js | 33 ++ src/drag-and-drop.js | 61 +++ src/element-farm.js | 59 +++ src/focus-on-p.js | 15 + src/helper.js | 9 + src/index.js | 14 +- src/johannes.js | 346 +-------------- src/keyboard-navigation.js | 68 +++ src/shortcut.js | 0 src/style.css | 113 ++--- src/switch-block.css | 139 ++++++ src/switch-block.js | 287 +++++++++++++ src/text-blocks-from-newlines.js | 43 ++ 25 files changed, 1665 insertions(+), 496 deletions(-) create mode 100644 demo/assets/img/11zon_cropped (1).jpeg create mode 100644 demo/assets/img/11zon_cropped.jpeg create mode 100644 demo/assets/img/ginal96 - 1172px-Venus_botticelli_detail (1).jpg create mode 100644 demo/assets/img/icons8-google-maps-96.png rename demo/assets/{ => img}/kepler.ico (100%) create mode 100644 demo/assets/img/youtube.jpeg create mode 100644 src/add-block.css create mode 100644 src/add-block.js create mode 100644 src/create-block-hit-enter.js create mode 100644 src/delete-block-hit-backspace.js create mode 100644 src/drag-and-drop.js create mode 100644 src/element-farm.js create mode 100644 src/focus-on-p.js create mode 100644 src/helper.js create mode 100644 src/keyboard-navigation.js create mode 100644 src/shortcut.js create mode 100644 src/switch-block.css create mode 100644 src/switch-block.js create mode 100644 src/text-blocks-from-newlines.js diff --git a/demo/assets/bundle.js b/demo/assets/bundle.js index fc3c33a..da87926 100644 --- a/demo/assets/bundle.js +++ b/demo/assets/bundle.js @@ -1 +1,247 @@ -(()=>{var e={570:()=>{function e(){let e=document.createElement("p");return e.classList.add("johannes-content-element"),e.contentEditable=!0,e.setAttribute("data-placeholder","Write something or type / (slash) to choose a block..."),e}function t(){let t=document.createElement("div"),n=e(),o=document.createElement("button");return o.innerHTML='',t.appendChild(o),t.appendChild(n),t.classList.add("draggable-block"),o.classList.add("drag-handler"),o.classList.add("button-reset"),o.draggable=!0,t}document.addEventListener("DOMContentLoaded",(function(){const e=document.querySelector(".johannes-editor");let n=null,o=document.createElement("div");o.classList.add("drop-line"),o.style.height="2px",o.style.display="none",e.addEventListener("dragstart",(e=>{e.target.classList.contains("drag-handler")&&(n=e.target.closest(".draggable-block"),n.setAttribute("draggable","true"),setTimeout((()=>{n.style.opacity="0.5"}),0))})),e.addEventListener("dragend",(()=>{setTimeout((()=>{n&&(n.style.opacity="",n.removeAttribute("draggable"),n=null),o.remove()}),0)})),e.addEventListener("dragover",(e=>{e.preventDefault();let t=e.target.closest(".draggable-block");if(t&&t!==n){let n=t.getBoundingClientRect(),r=n.y+n.height/2;e.clientY>r?t.nextElementSibling!==o&&t.insertAdjacentElement("afterend",o):t.previousElementSibling!==o&&t.insertAdjacentElement("beforebegin",o)}o.style.display="block"})),e.addEventListener("drop",(e=>{e.preventDefault(),o.parentElement&&(o.parentElement.insertBefore(n,o),o.remove())})),document.addEventListener("keydown",(function(e){if("Enter"===e.key&&e.target.isContentEditable){e.preventDefault();const n=t(),o=e.target.closest(".draggable-block");o&&(o.nextSibling?o.parentNode.insertBefore(n,o.nextSibling):o.parentNode.appendChild(n)),setTimeout((()=>{let e=n.querySelector(".johannes-content-element");e&&e.focus()}),0)}})),document.querySelector(".add-block").addEventListener("click",(function(){let e=t();document.querySelector(".johannes-editor > .content").appendChild(e)}))})),document.addEventListener("DOMContentLoaded",(()=>{document.querySelector(".content").addEventListener("input",(function(e){const n=e.target;if("P"===n.tagName&&n.isContentEditable){const o=n.innerText.split("\n");if(o.length>1){e.preventDefault(),n.innerText=o[0];let r=n;for(let e=1;e{document.querySelector(".content").addEventListener("keydown",(function(e){if("Backspace"===e.key){const t=document.activeElement;if("H1"!==t.tagName&&t.isContentEditable){const n=t.getAttribute("data-placeholder"),o=t.textContent.trim();if(""===o||o===n){e.preventDefault();let n=t.closest(".draggable-block").previousElementSibling;t.remove();let o=n.querySelector(".johannes-content-element");o.focus();let r=document.createRange(),a=window.getSelection();r.selectNodeContents(o),r.collapse(!1),a.removeAllRanges(),a.addRange(r)}}}}))})),document.addEventListener("DOMContentLoaded",(function(){document.body.addEventListener("paste",(function(e){if("true"===e.target.getAttribute("contenteditable")){e.preventDefault();const t=(e.clipboardData||window.clipboardData).getData("text/plain");document.execCommand("insertText",!1,t)}}))})),document.addEventListener("DOMContentLoaded",(e=>{document.querySelector(".content").addEventListener("keydown",(function(e){"Escape"===e.key&&(document.querySelector(".block-options").style.display="none")}))})),document.addEventListener("DOMContentLoaded",(()=>{const t=document.querySelector(".johannes-editor");let n=null;t.addEventListener("keydown",(function(e){"/"===e.key&&setTimeout((()=>{const t=window.getSelection().rangeCount>0?window.getSelection().getRangeAt(0):null;t||alert("Erro fatal!!!");const o=e.target;if(o.closest(".draggable-block")){e.preventDefault(),n=o.closest(".draggable-block");const r=t.getBoundingClientRect(),a=document.querySelector(".block-options");a.style.left=`${r.left}px`,a.style.top=`${r.bottom+window.scrollY}px`,a.style.display="block"}}),0)})),document.querySelectorAll(".block-options .option").forEach((t=>{t.addEventListener("click",(function(){const t=this.getAttribute("data-type");n&&function(t,n){let o,r=t.querySelector(".johannes-content-element"),a=r.innerText;switch(a.endsWith("/")&&(a=a.slice(0,-1)),n){case"p":o=e(),o.innerText=a;break;case"h2":o=function(){let e=document.createElement("h2");return e.classList.add("johannes-content-element"),e.contentEditable=!0,e.setAttribute("data-placeholder","Heading 2"),e}(),o.innerText=a;break;case"code":o=document.createElement("pre");const t=document.createElement("code");t.innerText=a,o.appendChild(t);break;case"image":o=document.createElement("img"),o.src=a,o.alt="Descriptive text";break;case"quote":o=document.createElement("blockquote"),o.innerText=a;break;case"list":o=document.createElement("ul"),a.split("\n").forEach((e=>{const t=document.createElement("li");t.innerText=e,o.appendChild(t)}));break;default:return void console.error("Unsupported type")}t.replaceChild(o,r),o.focus()}(n,t),document.querySelector(".block-options").style.display="none"}))}))}))}},t={};function n(o){var r=t[o];if(void 0!==r)return r.exports;var a=t[o]={exports:{}};return e[o](a,a.exports,n),a.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var o in t)n.o(t,o)&&!n.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{"use strict";n(570)})()})(); \ No newline at end of file +/* + * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). + * This devtool is neither made for production nor for readable output files. + * It uses "eval()" calls to create a separate source file in the browser devtools. + * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) + * or disable the default devtool with "devtool: false". + * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). + */ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ "./src/add-block.css": +/*!***************************!*\ + !*** ./src/add-block.css ***! + \***************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n// extracted by mini-css-extract-plugin\n\n\n//# sourceURL=webpack://johannes/./src/add-block.css?"); + +/***/ }), + +/***/ "./src/style.css": +/*!***********************!*\ + !*** ./src/style.css ***! + \***********************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n// extracted by mini-css-extract-plugin\n\n\n//# sourceURL=webpack://johannes/./src/style.css?"); + +/***/ }), + +/***/ "./src/switch-block.css": +/*!******************************!*\ + !*** ./src/switch-block.css ***! + \******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n// extracted by mini-css-extract-plugin\n\n\n//# sourceURL=webpack://johannes/./src/switch-block.css?"); + +/***/ }), + +/***/ "./src/add-block.js": +/*!**************************!*\ + !*** ./src/add-block.js ***! + \**************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _element_farm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./element-farm */ \"./src/element-farm.js\");\n/* harmony import */ var _add_block_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./add-block.css */ \"./src/add-block.css\");\n\r\n\r\n\r\n\r\ndocument.addEventListener('DOMContentLoaded', function () {\r\n document.querySelector('.add-block').addEventListener('click', function () {\r\n const newElement = (0,_element_farm__WEBPACK_IMPORTED_MODULE_0__.createNewDraggableParagraphElement)();\r\n document.querySelector('.johannes-editor > .content').appendChild(newElement);\r\n\r\n const newContentElement = newElement.querySelector('.johannes-content-element');\r\n newContentElement.focus();\r\n });\r\n});\n\n//# sourceURL=webpack://johannes/./src/add-block.js?"); + +/***/ }), + +/***/ "./src/create-block-hit-enter.js": +/*!***************************************!*\ + !*** ./src/create-block-hit-enter.js ***! + \***************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _element_farm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./element-farm */ \"./src/element-farm.js\");\n\r\n\r\ndocument.addEventListener('DOMContentLoaded', function () {\r\n document.addEventListener('keydown', function (e) {\r\n if (e.key === 'Enter' && e.target.isContentEditable) {\r\n e.preventDefault();\r\n\r\n const newP = (0,_element_farm__WEBPACK_IMPORTED_MODULE_0__.createNewDraggableParagraphElement)();\r\n\r\n const draggableBlock = e.target.closest('.draggable-block');\r\n\r\n if (draggableBlock) {\r\n if (draggableBlock.nextSibling) {\r\n draggableBlock.parentNode.insertBefore(newP, draggableBlock.nextSibling);\r\n } else {\r\n draggableBlock.parentNode.appendChild(newP);\r\n }\r\n }\r\n\r\n setTimeout(() => {\r\n\r\n let focusable = newP.querySelector('.johannes-content-element');\r\n\r\n if (focusable) {\r\n focusable.focus();\r\n }\r\n }, 0);\r\n }\r\n });\r\n});\n\n//# sourceURL=webpack://johannes/./src/create-block-hit-enter.js?"); + +/***/ }), + +/***/ "./src/delete-block-hit-backspace.js": +/*!*******************************************!*\ + !*** ./src/delete-block-hit-backspace.js ***! + \*******************************************/ +/***/ (() => { + +eval("document.addEventListener('DOMContentLoaded', (event) => {\r\n const content = document.querySelector('.content');\r\n\r\n content.addEventListener('keydown', function (event) {\r\n if (event.key === 'Backspace') {\r\n const activeElement = document.activeElement;\r\n if (activeElement.isContentEditable) {\r\n const placeholder = activeElement.getAttribute('data-placeholder');\r\n const textContent = activeElement.textContent.trim();\r\n\r\n if (textContent === '' || textContent === placeholder) {\r\n event.preventDefault();\r\n\r\n let actualDraggableBlock = activeElement.closest('.draggable-block');\r\n let previousDraggableBlockSibling = actualDraggableBlock.previousElementSibling;\r\n\r\n actualDraggableBlock.remove();\r\n\r\n let previousSiblingContentElement = previousDraggableBlockSibling.querySelector('.johannes-content-element');\r\n\r\n previousSiblingContentElement.focus();\r\n\r\n let range = document.createRange();\r\n let selection = window.getSelection();\r\n range.selectNodeContents(previousSiblingContentElement);\r\n range.collapse(false);\r\n selection.removeAllRanges();\r\n selection.addRange(range);\r\n }\r\n }\r\n }\r\n });\r\n});\n\n//# sourceURL=webpack://johannes/./src/delete-block-hit-backspace.js?"); + +/***/ }), + +/***/ "./src/drag-and-drop.js": +/*!******************************!*\ + !*** ./src/drag-and-drop.js ***! + \******************************/ +/***/ (() => { + +eval("document.addEventListener('DOMContentLoaded', function () {\r\n const content = document.querySelector('.johannes-editor');\r\n\r\n let draggedItem = null;\r\n\r\n let dropLine = document.createElement('div');\r\n dropLine.classList.add('drop-line');\r\n dropLine.style.height = '2px';\r\n dropLine.style.display = 'none';\r\n\r\n content.addEventListener('dragstart', (e) => {\r\n if (e.target.classList.contains('drag-handler')) {\r\n draggedItem = e.target.closest('.draggable-block');\r\n draggedItem.setAttribute('draggable', 'true');\r\n setTimeout(() => {\r\n draggedItem.style.opacity = '0.5';\r\n }, 0);\r\n }\r\n });\r\n\r\n content.addEventListener('dragend', () => {\r\n setTimeout(() => {\r\n if (draggedItem) {\r\n draggedItem.style.opacity = '';\r\n draggedItem.removeAttribute('draggable');\r\n draggedItem = null;\r\n }\r\n dropLine.remove();\r\n }, 0);\r\n });\r\n\r\n content.addEventListener('dragover', (e) => {\r\n e.preventDefault();\r\n let target = e.target.closest('.draggable-block');\r\n\r\n if (target && target !== draggedItem) {\r\n let bounding = target.getBoundingClientRect();\r\n let offset = bounding.y + bounding.height / 2;\r\n\r\n if (e.clientY > offset) {\r\n if (target.nextElementSibling !== dropLine) {\r\n target.insertAdjacentElement('afterend', dropLine);\r\n }\r\n } else {\r\n if (target.previousElementSibling !== dropLine) {\r\n target.insertAdjacentElement('beforebegin', dropLine);\r\n }\r\n }\r\n }\r\n\r\n dropLine.style.display = 'block';\r\n });\r\n\r\n content.addEventListener('drop', (e) => {\r\n e.preventDefault();\r\n if (dropLine.parentElement) {\r\n dropLine.parentElement.insertBefore(draggedItem, dropLine);\r\n dropLine.remove();\r\n }\r\n });\r\n});\n\n//# sourceURL=webpack://johannes/./src/drag-and-drop.js?"); + +/***/ }), + +/***/ "./src/element-farm.js": +/*!*****************************!*\ + !*** ./src/element-farm.js ***! + \*****************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ createNewDraggableParagraphElement: () => (/* binding */ createNewDraggableParagraphElement),\n/* harmony export */ createNewH2Element: () => (/* binding */ createNewH2Element),\n/* harmony export */ createNewHeadingElement: () => (/* binding */ createNewHeadingElement),\n/* harmony export */ createNewParagraphElement: () => (/* binding */ createNewParagraphElement)\n/* harmony export */ });\nfunction createNewH2Element() {\r\n\r\n let newElement = document.createElement('h2');\r\n newElement.classList.add('johannes-content-element');\r\n\r\n newElement.contentEditable = true;\r\n\r\n newElement.setAttribute('data-placeholder', 'Heading 2');\r\n\r\n return newElement;\r\n}\r\n\r\nfunction createNewHeadingElement(number = 2) {\r\n\r\n if(number < 1 || number > 6){\r\n throw new Error(\"Invalid heading number\");\r\n }\r\n\r\n let newElement = document.createElement(`h${number}`);\r\n newElement.classList.add('johannes-content-element');\r\n\r\n newElement.contentEditable = true;\r\n\r\n newElement.setAttribute('data-placeholder', `Heading ${number}`);\r\n\r\n return newElement;\r\n}\r\n\r\n\r\nfunction createNewParagraphElement() {\r\n\r\n let newElement = document.createElement('p');\r\n newElement.classList.add('johannes-content-element');\r\n\r\n newElement.contentEditable = true;\r\n\r\n newElement.setAttribute('data-placeholder', 'Write something or type / (slash) to choose a block...');\r\n\r\n return newElement;\r\n}\r\n\r\nfunction createNewDraggableParagraphElement() {\r\n\r\n let newDiv = document.createElement('div');\r\n let newElement = createNewParagraphElement();\r\n\r\n let newButton = document.createElement('button');\r\n newButton.innerHTML = '';\r\n\r\n newDiv.appendChild(newButton);\r\n newDiv.appendChild(newElement);\r\n\r\n newDiv.classList.add('draggable-block');\r\n newButton.classList.add('drag-handler');\r\n newButton.classList.add('button-reset');\r\n newButton.draggable = true;\r\n\r\n return newDiv;\r\n}\n\n//# sourceURL=webpack://johannes/./src/element-farm.js?"); + +/***/ }), + +/***/ "./src/focus-on-p.js": +/*!***************************!*\ + !*** ./src/focus-on-p.js ***! + \***************************/ +/***/ (() => { + +eval("document.addEventListener('DOMContentLoaded', function(){\r\n const editor = document.querySelector('.johannes-editor');\r\n\r\n if(editor){\r\n let blocks = editor.querySelectorAll('.draggable-block');\r\n\r\n if(blocks.length == 1){\r\n\r\n const p = blocks[0].querySelector('.johannes-content-element'); \r\n if(p.innerText == ''){\r\n p.focus();\r\n }\r\n }\r\n }\r\n});\n\n//# sourceURL=webpack://johannes/./src/focus-on-p.js?"); + +/***/ }), + +/***/ "./src/helper.js": +/*!***********************!*\ + !*** ./src/helper.js ***! + \***********************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ focusOnTheEndOfTheText: () => (/* binding */ focusOnTheEndOfTheText)\n/* harmony export */ });\nfunction focusOnTheEndOfTheText(contentBlock) {\r\n const range = document.createRange();\r\n const selection = window.getSelection();\r\n\r\n range.selectNodeContents(contentBlock);\r\n range.collapse(false);\r\n selection.removeAllRanges();\r\n selection.addRange(range);\r\n}\r\n\n\n//# sourceURL=webpack://johannes/./src/helper.js?"); + +/***/ }), + +/***/ "./src/index.js": +/*!**********************!*\ + !*** ./src/index.js ***! + \**********************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _style_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./style.css */ \"./src/style.css\");\n/* harmony import */ var _add_block_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./add-block.js */ \"./src/add-block.js\");\n/* harmony import */ var _drag_and_drop_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./drag-and-drop.js */ \"./src/drag-and-drop.js\");\n/* harmony import */ var _drag_and_drop_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_drag_and_drop_js__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _switch_block_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./switch-block.js */ \"./src/switch-block.js\");\n/* harmony import */ var _create_block_hit_enter_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./create-block-hit-enter.js */ \"./src/create-block-hit-enter.js\");\n/* harmony import */ var _delete_block_hit_backspace_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./delete-block-hit-backspace.js */ \"./src/delete-block-hit-backspace.js\");\n/* harmony import */ var _delete_block_hit_backspace_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_delete_block_hit_backspace_js__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var _keyboard_navigation_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./keyboard-navigation.js */ \"./src/keyboard-navigation.js\");\n/* harmony import */ var _keyboard_navigation_js__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_keyboard_navigation_js__WEBPACK_IMPORTED_MODULE_6__);\n/* harmony import */ var _text_blocks_from_newlines_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./text-blocks-from-newlines.js */ \"./src/text-blocks-from-newlines.js\");\n/* harmony import */ var _focus_on_p_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./focus-on-p.js */ \"./src/focus-on-p.js\");\n/* harmony import */ var _focus_on_p_js__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(_focus_on_p_js__WEBPACK_IMPORTED_MODULE_8__);\n/* harmony import */ var _johannes_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./johannes.js */ \"./src/johannes.js\");\n/* harmony import */ var _johannes_js__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(_johannes_js__WEBPACK_IMPORTED_MODULE_9__);\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\n\n//# sourceURL=webpack://johannes/./src/index.js?"); + +/***/ }), + +/***/ "./src/johannes.js": +/*!*************************!*\ + !*** ./src/johannes.js ***! + \*************************/ +/***/ (() => { + +eval("let uniqueIdCounter = 0;\r\n\r\n\r\n// Remove pest text style\r\ndocument.addEventListener('DOMContentLoaded', function () {\r\n \r\n document.body.addEventListener('paste', function (e) {\r\n if (e.target.getAttribute('contenteditable') === 'true') {\r\n e.preventDefault();\r\n const text = (e.clipboardData || window.clipboardData).getData('text/plain');\r\n document.execCommand('insertText', false, text); // Inset text without style\r\n }\r\n });\r\n});\r\n// End remove pest text style\n\n//# sourceURL=webpack://johannes/./src/johannes.js?"); + +/***/ }), + +/***/ "./src/keyboard-navigation.js": +/*!************************************!*\ + !*** ./src/keyboard-navigation.js ***! + \************************************/ +/***/ (() => { + +eval("// document.addEventListener('DOMContentLoaded', function (event) {\r\n// const blockWrapper = document.querySelector('.block-options-wrapper');\r\n\r\n// if (blockWrapper) {\r\n\r\n// const first = this.querySelector('.option');\r\n// let current = null;\r\n\r\n// blockWrapper.addEventListener('keydown', function (event) {\r\n\r\n// current = current || first;\r\n\r\n// if (event.key === 'ArrowDown') {\r\n// event.preventDefault();\r\n// current = moveToNextOption(current);\r\n// } else if (event.key === 'ArrowUp') {\r\n// event.preventDefault();\r\n// current = moveToPreviousOption(current);\r\n// }\r\n// });\r\n// }\r\n// });\r\n\r\n\r\n// function moveToNextOption(current) {\r\n\r\n// let next = current.nextElementSibling;\r\n\r\n// if (!(next && next.classList.contains('option'))) {\r\n\r\n// let currentSection = current.closest('section');\r\n// let siblingSection = currentSection.nextElementSibling;\r\n\r\n// if (siblingSection) {\r\n// next = siblingSection.querySelector('.option');\r\n// } else {\r\n// next = document.querySelector('.block-options-wrapper .option');\r\n// }\r\n\r\n// }\r\n\r\n// next.focus();\r\n\r\n// return next;\r\n// }\r\n\r\n// function moveToPreviousOption(current) {\r\n\r\n// let previous = current.previousElementSibling;\r\n\r\n// if (!(previous && previous.classList.contains('option'))) {\r\n\r\n// let currentSection = current.closest('section');\r\n// let siblingSection = currentSection.previousElementSibling;\r\n\r\n// if (siblingSection) {\r\n// let options = siblingSection.querySelectorAll('.option');\r\n// previous = options[options.length - 1];\r\n// } else {\r\n// let options = document.querySelectorAll('.block-options-wrapper .option');\r\n// previous = options[options.length - 1];\r\n// }\r\n// }\r\n\r\n// previous.focus();\r\n\r\n// return previous;\r\n// }\n\n//# sourceURL=webpack://johannes/./src/keyboard-navigation.js?"); + +/***/ }), + +/***/ "./src/switch-block.js": +/*!*****************************!*\ + !*** ./src/switch-block.js ***! + \*****************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _element_farm_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./element-farm.js */ \"./src/element-farm.js\");\n/* harmony import */ var _switch_block_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./switch-block.css */ \"./src/switch-block.css\");\n/* harmony import */ var _helper_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./helper.js */ \"./src/helper.js\");\n\r\n\r\n\r\n\r\n\r\nfunction showBlockOptions(x, y) {\r\n\r\n const blockOptionsWrapper = document.querySelector('.block-options-wrapper');\r\n\r\n blockOptionsWrapper.style.display = 'block';\r\n blockOptionsWrapper.style.left = `${x}px`;\r\n blockOptionsWrapper.style.top = `${y}px`;\r\n\r\n blockOptionsWrapper.focus();\r\n\r\n const firstOption = blockOptionsWrapper.querySelector('.option');\r\n if (firstOption) {\r\n firstOption.focus();\r\n }\r\n}\r\n\r\n\r\n\r\n\r\n//Show oo\r\ndocument.addEventListener('DOMContentLoaded', () => {\r\n const content = document.querySelector('.johannes-editor > .content');\r\n\r\n content.addEventListener('contextmenu', function (event) {\r\n if (event.target.classList.contains('drag-handler')) {\r\n event.preventDefault();\r\n\r\n showBlockOptions(event.pageX, event.pageY);\r\n\r\n return false;\r\n }\r\n });\r\n});\r\n\r\n\r\n\r\n//Close the options wrapper\r\ndocument.addEventListener('DOMContentLoaded', (event) => {\r\n const editor = document.querySelector('.johannes-editor');\r\n\r\n editor.addEventListener('keydown', function (event) {\r\n if (event.key === 'Escape') {\r\n document.querySelector('.block-options-wrapper').style.display = 'none';\r\n }\r\n });\r\n\r\n // Adicionando o evento de mouse para fechar ao clicar fora\r\n document.addEventListener('mousedown', function (event) {\r\n const optionsWrapper = document.querySelector('.block-options-wrapper');\r\n\r\n if (optionsWrapper) {\r\n const isClickInsideOptions = optionsWrapper.contains(event.target);\r\n const mainMouseButton = event.button === 0;\r\n\r\n if (!isClickInsideOptions && mainMouseButton) {\r\n optionsWrapper.style.display = 'none';\r\n }\r\n }\r\n });\r\n});\r\n\r\n\r\ndocument.addEventListener('DOMContentLoaded', () => {\r\n\r\n const editor = document.querySelector('.johannes-editor');\r\n const blockOptions = document.querySelector('.johannes-editor > .block-options-wrapper');\r\n\r\n let triggerElement = null;\r\n let currentDraggableBlock = null;\r\n\r\n let currentFocusedOption = null;\r\n\r\n\r\n editor.addEventListener('keydown', function (event) {\r\n if (event.key === '/') {\r\n\r\n setTimeout(() => {\r\n\r\n const range = window.getSelection().rangeCount > 0 ? window.getSelection().getRangeAt(0) : null;\r\n\r\n if (!range) {\r\n\r\n alert('Erro fatal!!!');\r\n }\r\n\r\n const target = event.target;\r\n if (target.closest('.draggable-block')) {\r\n event.preventDefault(); // Avoid / be inserted\r\n currentDraggableBlock = target.closest('.draggable-block');\r\n triggerElement = target;\r\n\r\n // Take the element cursor position\r\n const cursorPos = range.getBoundingClientRect();\r\n\r\n showBlockOptions(cursorPos.left, cursorPos.bottom + window.scrollY);\r\n\r\n let firstOption = blockOptions.querySelector('.option');\r\n currentFocusedOption = firstOption;\r\n\r\n currentFocusedOption.focus();\r\n currentFocusedOption.classList.add('block-options-focused');\r\n triggerElement.focus();\r\n }\r\n\r\n\r\n }, 0);\r\n } else if (event.key === 'Escape'\r\n // && document.activeElement === blockOptions\r\n ) {\r\n event.preventDefault();\r\n if (triggerElement) {\r\n triggerElement.focus();\r\n\r\n currentFocusedOption.classList.remove('block-options-focused');\r\n }\r\n } else if (event.key === 'Enter' && blockOptions.style.display !== 'none') {\r\n\r\n event.preventDefault();\r\n\r\n // let option = document.activeElement.closest('.option');\r\n currentFocusedOption.classList.remove('block-options-focused');\r\n let option = currentFocusedOption;\r\n\r\n if (option) {\r\n const asasas = triggerElement.closest('.draggable-block');\r\n const blockType = option.getAttribute('data-type');\r\n transformBlock(asasas, blockType);\r\n\r\n }\r\n\r\n } else if (event.key === 'ArrowDown' && blockOptions.style.display !== 'none') {\r\n event.preventDefault();\r\n currentFocusedOption = moveToNextOption(currentFocusedOption, triggerElement);\r\n\r\n } else if (event.key === 'ArrowUp' && blockOptions.style.display !== 'none') {\r\n event.preventDefault();\r\n currentFocusedOption = moveToPreviousOption(currentFocusedOption, triggerElement);\r\n }\r\n });\r\n\r\n\r\n // Added listeners in options\r\n document.querySelectorAll('.block-options .option').forEach(option => {\r\n option.addEventListener('click', function () {\r\n const type = this.getAttribute('data-type');\r\n if (currentDraggableBlock) {\r\n transformBlock(currentDraggableBlock, type);\r\n }\r\n });\r\n });\r\n});\r\n\r\n\r\nfunction transformBlock(blockElement, type) {\r\n\r\n let contentElement = blockElement.querySelector('.johannes-content-element');\r\n let content = contentElement.innerText;\r\n\r\n if (content.endsWith('/')) {\r\n content = content.slice(0, -1); // Remove the last '/'\r\n }\r\n\r\n let newContentBlock;\r\n\r\n switch (type) {\r\n case 'p':\r\n newContentBlock = _element_farm_js__WEBPACK_IMPORTED_MODULE_0__.createNewParagraphElement();\r\n newContentBlock.innerText = content;\r\n break;\r\n case 'h1':\r\n newContentBlock = _element_farm_js__WEBPACK_IMPORTED_MODULE_0__.createNewHeadingElement(1);\r\n newContentBlock.innerText = content;\r\n break;\r\n case 'h2':\r\n newContentBlock = _element_farm_js__WEBPACK_IMPORTED_MODULE_0__.createNewHeadingElement(2);\r\n newContentBlock.innerText = content;\r\n break;\r\n case 'h3':\r\n newContentBlock = _element_farm_js__WEBPACK_IMPORTED_MODULE_0__.createNewHeadingElement(3);\r\n newContentBlock.innerText = content;\r\n break;\r\n case 'h4':\r\n newContentBlock = _element_farm_js__WEBPACK_IMPORTED_MODULE_0__.createNewHeadingElement(4);\r\n newContentBlock.innerText = content;\r\n break;\r\n case 'h5':\r\n newContentBlock = _element_farm_js__WEBPACK_IMPORTED_MODULE_0__.createNewHeadingElement(5);\r\n newContentBlock.innerText = content;\r\n break;\r\n case 'h6':\r\n newContentBlock = _element_farm_js__WEBPACK_IMPORTED_MODULE_0__.createNewHeadingElement(6);\r\n newContentBlock.innerText = content;\r\n break;\r\n case 'code':\r\n newContentBlock = document.createElement('pre');\r\n const code = document.createElement('code');\r\n code.innerText = content;\r\n newContentBlock.appendChild(code);\r\n break;\r\n case 'image':\r\n newContentBlock = document.createElement('img');\r\n newContentBlock.src = content;\r\n newContentBlock.alt = \"Descriptive text\";\r\n break;\r\n case 'quote':\r\n newContentBlock = document.createElement('blockquote');\r\n newContentBlock.innerText = content;\r\n break;\r\n case 'list':\r\n newContentBlock = document.createElement('ul');\r\n const items = content.split('\\n');\r\n items.forEach(item => {\r\n const listItem = document.createElement('li');\r\n listItem.innerText = item;\r\n newContentBlock.appendChild(listItem);\r\n });\r\n break;\r\n default:\r\n console.error('Unsupported type');\r\n return;\r\n }\r\n\r\n blockElement.replaceChild(newContentBlock, contentElement);\r\n\r\n (0,_helper_js__WEBPACK_IMPORTED_MODULE_2__.focusOnTheEndOfTheText)(newContentBlock);\r\n\r\n document.querySelector('.block-options-wrapper').style.display = 'none';\r\n}\r\n\r\n\r\nfunction moveToNextOption(current, keepFocused) {\r\n\r\n let next = current.nextElementSibling;\r\n\r\n if (!(next && next.classList.contains('option'))) {\r\n\r\n let currentSection = current.closest('section');\r\n let siblingSection = currentSection.nextElementSibling;\r\n\r\n if (siblingSection) {\r\n next = siblingSection.querySelector('.option');\r\n } else {\r\n next = document.querySelector('.block-options-wrapper .option');\r\n }\r\n\r\n }\r\n\r\n next.focus();\r\n current.classList.remove('block-options-focused');\r\n next.classList.add('block-options-focused');\r\n\r\n keepFocused.focus();\r\n\r\n return next;\r\n}\r\n\r\nfunction moveToPreviousOption(current, keepFocused) {\r\n\r\n let previous = current.previousElementSibling;\r\n\r\n if (!(previous && previous.classList.contains('option'))) {\r\n\r\n let currentSection = current.closest('section');\r\n let siblingSection = currentSection.previousElementSibling;\r\n\r\n if (siblingSection) {\r\n let options = siblingSection.querySelectorAll('.option');\r\n previous = options[options.length - 1];\r\n } else {\r\n let options = document.querySelectorAll('.block-options-wrapper .option');\r\n previous = options[options.length - 1];\r\n }\r\n }\r\n\r\n previous.focus();\r\n current.classList.remove('block-options-focused');\r\n previous.classList.add('block-options-focused');\r\n\r\n keepFocused.focus();\r\n\r\n return previous;\r\n}\n\n//# sourceURL=webpack://johannes/./src/switch-block.js?"); + +/***/ }), + +/***/ "./src/text-blocks-from-newlines.js": +/*!******************************************!*\ + !*** ./src/text-blocks-from-newlines.js ***! + \******************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _element_farm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./element-farm */ \"./src/element-farm.js\");\n/* harmony import */ var _helper__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./helper */ \"./src/helper.js\");\n\r\n\r\n\r\ndocument.addEventListener('DOMContentLoaded', () => {\r\n\r\n const content = document.querySelector('.johannes-editor > .content');\r\n\r\n content.addEventListener('input', function (event) {\r\n\r\n const target = event.target;\r\n\r\n if (target.tagName === 'P' && target.isContentEditable) {\r\n\r\n const blocks = target.innerText.split('\\n');\r\n\r\n if (blocks.length > 1) {\r\n\r\n event.preventDefault();\r\n\r\n // Remove original text to avoid duplication\r\n target.innerText = blocks[0]; // Keep the first actual line paragraph \r\n\r\n let currentTarget = target.closest('.draggable-block');\r\n let lastContentBlock = null;\r\n\r\n // Each new line, create a new P below the actual\r\n for (let i = 1; i < blocks.length; i++) {\r\n\r\n const newParagraph = (0,_element_farm__WEBPACK_IMPORTED_MODULE_0__.createNewDraggableParagraphElement)();\r\n\r\n //works? I dont't know\r\n lastContentBlock = newParagraph.querySelector('.johannes-content-element');\r\n\r\n lastContentBlock.innerText = blocks[i];\r\n currentTarget.insertAdjacentElement('afterend', newParagraph);\r\n currentTarget = newParagraph;\r\n }\r\n\r\n (0,_helper__WEBPACK_IMPORTED_MODULE_1__.focusOnTheEndOfTheText)(lastContentBlock);\r\n }\r\n }\r\n });\r\n});\n\n//# sourceURL=webpack://johannes/./src/text-blocks-from-newlines.js?"); + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat get default export */ +/******/ (() => { +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = (module) => { +/******/ var getter = module && module.__esModule ? +/******/ () => (module['default']) : +/******/ () => (module); +/******/ __webpack_require__.d(getter, { a: getter }); +/******/ return getter; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ (() => { +/******/ // define getter functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ (() => { +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ })(); +/******/ +/************************************************************************/ +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module can't be inlined because the eval devtool is used. +/******/ var __webpack_exports__ = __webpack_require__("./src/index.js"); +/******/ +/******/ })() +; \ No newline at end of file diff --git a/demo/assets/img/11zon_cropped (1).jpeg b/demo/assets/img/11zon_cropped (1).jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d8d46cc40ad9183f40855e08f9c82d2cc8ea5db4 GIT binary patch literal 5374 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 DF~VtK literal 0 HcmV?d00001 diff --git a/demo/assets/img/icons8-google-maps-96.png b/demo/assets/img/icons8-google-maps-96.png new file mode 100644 index 0000000000000000000000000000000000000000..1eab783972b98f57e6baf89a45bd0af712feb8a3 GIT binary patch literal 2361 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 UyEPt(D)ne!oc-{J$i?)306^MY>;M1& literal 0 HcmV?d00001 diff --git a/demo/assets/style.css b/demo/assets/style.css index 8b41edb..f2b398f 100644 --- a/demo/assets/style.css +++ b/demo/assets/style.css @@ -1,8 +1,42 @@ +/*!*****************************************************************!*\ + !*** css ./node_modules/css-loader/dist/cjs.js!./src/style.css ***! + \*****************************************************************/ +/** CSS reset */ + +.button-reset { + border: none; + margin: 0; + padding: 0; + width: auto; + overflow: visible; + background: transparent; + color: inherit; + font: inherit; + line-height: normal; + -webkit-font-smoothing: inherit; + -moz-osx-font-smoothing: inherit; + -webkit-appearance: none; +} + + + + .drag-handler { color: #84888d; visibility: hidden; cursor: grab; align-self: start; + + /* background-color: pink; */ + padding-top: 0.33rem; +} + +.drag-handler svg { + /* background-color: red; */ +} + +.drag-handler svg use { + /* background-color: blue; */ } .drag-handler:active { @@ -13,6 +47,9 @@ display: flex; flex-direction: row; margin-bottom: 1rem; + + + /* background-color: purple; */ } .draggable-block:hover .drag-handler { @@ -28,31 +65,8 @@ } - - -.add-block { - margin: 0; - padding: 0; - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - border: 1px solid #000; - background-color: white; - transition: all 0.3s ease; - box-sizing: border-box; -} - -.add-block-wrapper { - width: 100%; - height: 3.125rem; - -} - -.add-block-wrapper:hover .add-block { - display: flex; - +.title h1{ + margin-left: 1.3rem; } h1[data-placeholder]:empty:before { @@ -85,10 +99,6 @@ h1 { font-weight: bolder !important; } -.block-options { - width: 480px; -} - .johannes-editor { width: 100%; @@ -109,7 +119,7 @@ pre { outline: none; } -.content-area { +body { height: 2000px; background-color: #f0f0f1 !important; @@ -118,73 +128,200 @@ pre { .editor { + display: flex; + margin-top: 80px; +} + +p { + font-size: 20px !important; + color: #242424; +} + + +.draggable-block, +.draggable-block * { + user-select: text; +} + + +.johannes-content-element { + width: 100%; + padding: 0; + margin: 0; + /* background-color: green; */ +} + + +.editor-wrapper { + width: 60%; + margin-left: auto; + margin-right: auto; +} +/*!*********************************************************************!*\ + !*** css ./node_modules/css-loader/dist/cjs.js!./src/add-block.css ***! + \*********************************************************************/ +.add-block { + margin: 0; + padding: 0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid #000; + background-color: white; + transition: all 0.3s ease; + box-sizing: border-box; + margin-left: 1.3rem; +} + +.add-block-wrapper { + width: 100%; + height: 3.125rem; +} +.add-block-wrapper:hover .add-block { display: flex; +} +/*!************************************************************************!*\ + !*** css ./node_modules/css-loader/dist/cjs.js!./src/switch-block.css ***! + \************************************************************************/ +.block-options-wrapper { + z-index: 999999; + /* box-shadow: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 3px 6px, rgba(15, 15, 15, 0.2) 0px 9px 24px; */ + position: absolute; + display: none; + /* border: 2px solid #ccc; */ - margin-top: 80px; +} + +/* ::-webkit-scrollbar { + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; + width: 6px; +} */ + +.block-options { + /* width: 324px; 20.25rem */ + width: 20.25rem; + /* height: 384px; */ + height: 340px; + + overflow-y: scroll; + /* border-radius: 10px; */ } .block-options { padding: 10px; background: #f9f9f9; - border: 1px solid #ccc; + border: 2px solid #ccc; margin-top: 10px; - position: absolute; - display: none; + /* position: absolute; */ +} + +.block-options section h2 { + font-size: small ; + padding-left: 0.625rem; + color: #37352fa6; } .block-options .option { - padding: 5px 10px; - border: 1px solid #ddd; - margin: 5px; + padding: 0.3125rem 0.625rem; + margin: 0.3125rem 0; cursor: pointer; - display: inline-block; + display: flex; + flex-direction: row; + align-items: center; + justify-content: left; + gap: 0.6rem; + border-radius: 3px; } -.block-options .option:hover { +.block-options .option:focus { + outline: none; + background-color: #e9e9e9; + border: inherit; +} + +.block-options-focused { + outline: none; background-color: #e9e9e9; + border: inherit; } -p { - font-size: 20px !important; - color: #242424; + + +.block-options .option:focus-visible{ + outline: none; + background-color: #e9e9e9; + border: inherit; } +.block-options .option:hover { + background-color: #e9e9e9; +} -.draggable-block, -.draggable-block * { - user-select: text; + +.option[focused="true"] { + background-color: #bde4ff; /* estilo de foco personalizado */ } -.johannes-content-element { - width: 100%; - padding: 0; - margin: 0; + + + +.option-image { + width: 2.875rem; + height: 2.875rem; + border-radius: 3px; + /* background-color: red; */ + border: 1px solid #ccc; + color: #4f4f4f; + + /* color: #84888d; */ + /* color: #242424; */ + display: flex; + flex-direction: row; + justify-content: center; + vertical-align: middle; + align-items: center; + background-color: #ffff; + box-sizing: border-box; } +.option-image img { + /* max-width: 46px; 2,875rem */ + max-width: 2.875rem; + max-height: 2.875rem; +} -.editor-wrapper { - width: 80%; - margin-left: auto; - margin-right: auto; +.option-image svg { + /* padding: 10px; 0.625rem */ + margin: 0; + padding: 0; + padding: 0.625rem; + box-sizing: border-box; + } +.option-text { + /* background-color: yellow; */ + /* font-size: 0.5rem; */ +} -.button-reset { - border: none; +.option-text * { margin: 0; padding: 0; - width: auto; - overflow: visible; - background: transparent; - color: inherit; - font: inherit; - line-height: normal; - -webkit-font-smoothing: inherit; - -moz-osx-font-smoothing: inherit; - -webkit-appearance: none; + } +.option-text .block-title { + font-size: small !important; + font-weight: 500; +} + +.option-text .block-description { + font-size: small !important; +} diff --git a/demo/index.html b/demo/index.html index 5c0748b..0ad965d 100644 --- a/demo/index.html +++ b/demo/index.html @@ -6,11 +6,14 @@ Johannes Editor - + + + @@ -34,7 +37,7 @@

+ data-placeholder="Write something or type / (slash) to choose a block..." tabindex="0">

@@ -47,13 +50,255 @@

- @@ -70,7 +315,139 @@

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/add-block.css b/src/add-block.css new file mode 100644 index 0000000..831f96f --- /dev/null +++ b/src/add-block.css @@ -0,0 +1,23 @@ +.add-block { + margin: 0; + padding: 0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid #000; + background-color: white; + transition: all 0.3s ease; + box-sizing: border-box; + margin-left: 1.3rem; +} + +.add-block-wrapper { + width: 100%; + height: 3.125rem; +} + +.add-block-wrapper:hover .add-block { + display: flex; +} \ No newline at end of file diff --git a/src/add-block.js b/src/add-block.js new file mode 100644 index 0000000..f9bbc6c --- /dev/null +++ b/src/add-block.js @@ -0,0 +1,13 @@ +import {createNewDraggableParagraphElement} from './element-farm'; + +import './add-block.css'; + +document.addEventListener('DOMContentLoaded', function () { + document.querySelector('.add-block').addEventListener('click', function () { + const newElement = createNewDraggableParagraphElement(); + document.querySelector('.johannes-editor > .content').appendChild(newElement); + + const newContentElement = newElement.querySelector('.johannes-content-element'); + newContentElement.focus(); + }); +}); \ No newline at end of file diff --git a/src/create-block-hit-enter.js b/src/create-block-hit-enter.js new file mode 100644 index 0000000..3509a57 --- /dev/null +++ b/src/create-block-hit-enter.js @@ -0,0 +1,30 @@ +import {createNewDraggableParagraphElement} from './element-farm'; + +document.addEventListener('DOMContentLoaded', function () { + document.addEventListener('keydown', function (e) { + if (e.key === 'Enter' && e.target.isContentEditable) { + e.preventDefault(); + + const newP = createNewDraggableParagraphElement(); + + const draggableBlock = e.target.closest('.draggable-block'); + + if (draggableBlock) { + if (draggableBlock.nextSibling) { + draggableBlock.parentNode.insertBefore(newP, draggableBlock.nextSibling); + } else { + draggableBlock.parentNode.appendChild(newP); + } + } + + setTimeout(() => { + + let focusable = newP.querySelector('.johannes-content-element'); + + if (focusable) { + focusable.focus(); + } + }, 0); + } + }); +}); \ No newline at end of file diff --git a/src/delete-block-hit-backspace.js b/src/delete-block-hit-backspace.js new file mode 100644 index 0000000..811b628 --- /dev/null +++ b/src/delete-block-hit-backspace.js @@ -0,0 +1,33 @@ +document.addEventListener('DOMContentLoaded', (event) => { + const content = document.querySelector('.content'); + + content.addEventListener('keydown', function (event) { + if (event.key === 'Backspace') { + const activeElement = document.activeElement; + if (activeElement.isContentEditable) { + const placeholder = activeElement.getAttribute('data-placeholder'); + const textContent = activeElement.textContent.trim(); + + if (textContent === '' || textContent === placeholder) { + event.preventDefault(); + + let actualDraggableBlock = activeElement.closest('.draggable-block'); + let previousDraggableBlockSibling = actualDraggableBlock.previousElementSibling; + + actualDraggableBlock.remove(); + + let previousSiblingContentElement = previousDraggableBlockSibling.querySelector('.johannes-content-element'); + + previousSiblingContentElement.focus(); + + let range = document.createRange(); + let selection = window.getSelection(); + range.selectNodeContents(previousSiblingContentElement); + range.collapse(false); + selection.removeAllRanges(); + selection.addRange(range); + } + } + } + }); +}); \ No newline at end of file diff --git a/src/drag-and-drop.js b/src/drag-and-drop.js new file mode 100644 index 0000000..3cb0eb5 --- /dev/null +++ b/src/drag-and-drop.js @@ -0,0 +1,61 @@ +document.addEventListener('DOMContentLoaded', function () { + const content = document.querySelector('.johannes-editor'); + + let draggedItem = null; + + let dropLine = document.createElement('div'); + dropLine.classList.add('drop-line'); + dropLine.style.height = '2px'; + dropLine.style.display = 'none'; + + content.addEventListener('dragstart', (e) => { + if (e.target.classList.contains('drag-handler')) { + draggedItem = e.target.closest('.draggable-block'); + draggedItem.setAttribute('draggable', 'true'); + setTimeout(() => { + draggedItem.style.opacity = '0.5'; + }, 0); + } + }); + + content.addEventListener('dragend', () => { + setTimeout(() => { + if (draggedItem) { + draggedItem.style.opacity = ''; + draggedItem.removeAttribute('draggable'); + draggedItem = null; + } + dropLine.remove(); + }, 0); + }); + + content.addEventListener('dragover', (e) => { + e.preventDefault(); + let target = e.target.closest('.draggable-block'); + + if (target && target !== draggedItem) { + let bounding = target.getBoundingClientRect(); + let offset = bounding.y + bounding.height / 2; + + if (e.clientY > offset) { + if (target.nextElementSibling !== dropLine) { + target.insertAdjacentElement('afterend', dropLine); + } + } else { + if (target.previousElementSibling !== dropLine) { + target.insertAdjacentElement('beforebegin', dropLine); + } + } + } + + dropLine.style.display = 'block'; + }); + + content.addEventListener('drop', (e) => { + e.preventDefault(); + if (dropLine.parentElement) { + dropLine.parentElement.insertBefore(draggedItem, dropLine); + dropLine.remove(); + } + }); +}); \ No newline at end of file diff --git a/src/element-farm.js b/src/element-farm.js new file mode 100644 index 0000000..8b6e532 --- /dev/null +++ b/src/element-farm.js @@ -0,0 +1,59 @@ +export function createNewH2Element() { + + let newElement = document.createElement('h2'); + newElement.classList.add('johannes-content-element'); + + newElement.contentEditable = true; + + newElement.setAttribute('data-placeholder', 'Heading 2'); + + return newElement; +} + +export function createNewHeadingElement(number = 2) { + + if(number < 1 || number > 6){ + throw new Error("Invalid heading number"); + } + + let newElement = document.createElement(`h${number}`); + newElement.classList.add('johannes-content-element'); + + newElement.contentEditable = true; + + newElement.setAttribute('data-placeholder', `Heading ${number}`); + + return newElement; +} + + +export function createNewParagraphElement() { + + let newElement = document.createElement('p'); + newElement.classList.add('johannes-content-element'); + + newElement.contentEditable = true; + + newElement.setAttribute('data-placeholder', 'Write something or type / (slash) to choose a block...'); + + return newElement; +} + +export function createNewDraggableParagraphElement() { + + let newDiv = document.createElement('div'); + let newElement = createNewParagraphElement(); + + let newButton = document.createElement('button'); + newButton.innerHTML = ''; + + newDiv.appendChild(newButton); + newDiv.appendChild(newElement); + + newDiv.classList.add('draggable-block'); + newButton.classList.add('drag-handler'); + newButton.classList.add('button-reset'); + newButton.draggable = true; + + return newDiv; +} \ No newline at end of file diff --git a/src/focus-on-p.js b/src/focus-on-p.js new file mode 100644 index 0000000..d6c958d --- /dev/null +++ b/src/focus-on-p.js @@ -0,0 +1,15 @@ +document.addEventListener('DOMContentLoaded', function(){ + const editor = document.querySelector('.johannes-editor'); + + if(editor){ + let blocks = editor.querySelectorAll('.draggable-block'); + + if(blocks.length == 1){ + + const p = blocks[0].querySelector('.johannes-content-element'); + if(p.innerText == ''){ + p.focus(); + } + } + } +}); \ No newline at end of file diff --git a/src/helper.js b/src/helper.js new file mode 100644 index 0000000..3ade4e2 --- /dev/null +++ b/src/helper.js @@ -0,0 +1,9 @@ +export function focusOnTheEndOfTheText(contentBlock) { + const range = document.createRange(); + const selection = window.getSelection(); + + range.selectNodeContents(contentBlock); + range.collapse(false); + selection.removeAllRanges(); + selection.addRange(range); +} diff --git a/src/index.js b/src/index.js index 2ffd091..810ef6a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,12 @@ -// import './script1.js'; -// import './script2.js'; -// import './script3.js'; - import './style.css'; + +import './add-block.js'; +import './drag-and-drop.js'; +import './switch-block.js'; +import './create-block-hit-enter.js'; +import './delete-block-hit-backspace.js'; +import './keyboard-navigation.js'; +import './text-blocks-from-newlines.js'; +import './focus-on-p.js'; + import './johannes.js'; \ No newline at end of file diff --git a/src/johannes.js b/src/johannes.js index 24b7518..1371488 100644 --- a/src/johannes.js +++ b/src/johannes.js @@ -1,190 +1,6 @@ let uniqueIdCounter = 0; -document.addEventListener('DOMContentLoaded', function () { - - //Text selection - - //end text selection - - - // Drag element - const content = document.querySelector('.johannes-editor'); - let draggedItem = null; - let dropLine = document.createElement('div'); - dropLine.classList.add('drop-line'); - dropLine.style.height = '2px'; - dropLine.style.display = 'none'; - - content.addEventListener('dragstart', (e) => { - if (e.target.classList.contains('drag-handler')) { - draggedItem = e.target.closest('.draggable-block'); - draggedItem.setAttribute('draggable', 'true'); - setTimeout(() => { - draggedItem.style.opacity = '0.5'; - }, 0); - } - }); - - content.addEventListener('dragend', () => { - setTimeout(() => { - if (draggedItem) { - draggedItem.style.opacity = ''; - draggedItem.removeAttribute('draggable'); - draggedItem = null; - } - dropLine.remove(); - }, 0); - }); - - content.addEventListener('dragover', (e) => { - e.preventDefault(); - let target = e.target.closest('.draggable-block'); - - if (target && target !== draggedItem) { - let bounding = target.getBoundingClientRect(); - let offset = bounding.y + bounding.height / 2; - - if (e.clientY > offset) { - if (target.nextElementSibling !== dropLine) { - target.insertAdjacentElement('afterend', dropLine); - } - } else { - if (target.previousElementSibling !== dropLine) { - target.insertAdjacentElement('beforebegin', dropLine); - } - } - } - - dropLine.style.display = 'block'; - }); - - content.addEventListener('drop', (e) => { - e.preventDefault(); - if (dropLine.parentElement) { - dropLine.parentElement.insertBefore(draggedItem, dropLine); - dropLine.remove(); - } - }); - // End drag element - - - // Creates a new P when hit Enter - document.addEventListener('keydown', function (e) { - if (e.key === 'Enter' && e.target.isContentEditable) { - e.preventDefault(); - - const newP = createNewDraggableParagraphElement(); - - const draggableBlock = e.target.closest('.draggable-block'); - - if (draggableBlock) { - if (draggableBlock.nextSibling) { - draggableBlock.parentNode.insertBefore(newP, draggableBlock.nextSibling); - } else { - draggableBlock.parentNode.appendChild(newP); - } - } - - setTimeout(() => { - - let focusable = newP.querySelector('.johannes-content-element'); - - if (focusable) { - focusable.focus(); - } - }, 0); - } - }); - // End create a new p when hit Enter - - - - - - // Create a new block when clicked add - document.querySelector('.add-block').addEventListener('click', function () { - let newElement = createNewDraggableParagraphElement(); - document.querySelector('.johannes-editor > .content').appendChild(newElement); - }); - // End create a new block when clicked add - - - - -});//End DomContentLoaded - - -// New paragraph when find a \n -document.addEventListener('DOMContentLoaded', () => { - - const content = document.querySelector('.content'); - - content.addEventListener('input', function (event) { - const target = event.target; - if (target.tagName === 'P' && target.isContentEditable) { - const lines = target.innerText.split('\n'); - if (lines.length > 1) { - event.preventDefault(); // Prevent insert directly - // Remove original text to avoid duplication - target.innerText = lines[0]; // Keep the first actual line paragraph - - let currentTarget = target; - // Each new line, create a new P below the actual - for (let i = 1; i < lines.length; i++) { - - const newParagraph = createNewDraggableParagraphElement(); - - //works? I dont't know - newParagraph.innerText = lines[i]; - currentTarget.insertAdjacentElement('afterend', newParagraph); - currentTarget = newParagraph; - } - - currentTarget.focus(); - } - } - }); -}); -// End new paragraph when find a \n - - -// Remove the block when hit backspace -document.addEventListener('DOMContentLoaded', (event) => { - const content = document.querySelector('.content'); - - content.addEventListener('keydown', function (event) { - if (event.key === 'Backspace') { - const activeElement = document.activeElement; - if (activeElement.tagName !== 'H1' && activeElement.isContentEditable) { - const placeholder = activeElement.getAttribute('data-placeholder'); - const textContent = activeElement.textContent.trim(); - - if (textContent === '' || textContent === placeholder) { - event.preventDefault(); - - let sibling = activeElement.closest('.draggable-block').previousElementSibling; - activeElement.remove(); - - let focusableElement = sibling.querySelector('.johannes-content-element'); - - focusableElement.focus(); - - let range = document.createRange(); - let selection = window.getSelection(); - range.selectNodeContents(focusableElement); - range.collapse(false); - selection.removeAllRanges(); - selection.addRange(range); - - } - } - } - }); -}); -// End remove the block when hit backspace - - // Remove pest text style document.addEventListener('DOMContentLoaded', function () { @@ -196,164 +12,4 @@ document.addEventListener('DOMContentLoaded', function () { } }); }); -// End remove pest text style - - -document.addEventListener('DOMContentLoaded', (event) => { - const content = document.querySelector('.content'); - - content.addEventListener('keydown', function (event) { - if (event.key === 'Escape') { - document.querySelector('.block-options').style.display = 'none'; - } - }); -}); - - - -document.addEventListener('DOMContentLoaded', () => { - - const editor = document.querySelector('.johannes-editor'); - let currentBlock = null; - - editor.addEventListener('keydown', function (event) { - if (event.key === '/') { - - setTimeout(() => { - const range = window.getSelection().rangeCount > 0 ? window.getSelection().getRangeAt(0) : null; - if (!range) { - - alert('Erro fatal!!!'); - } - - const target = event.target; - if (target.closest('.draggable-block')) { - event.preventDefault(); // Avoid / be inserted - currentBlock = target.closest('.draggable-block'); - - // Take the element cursor position - const cursorPos = range.getBoundingClientRect(); - const blockOptions = document.querySelector('.block-options'); - - // Set menu position and show the block selector - blockOptions.style.left = `${cursorPos.left}px`; - blockOptions.style.top = `${cursorPos.bottom + window.scrollY}px`; // The scroll - blockOptions.style.display = 'block'; - } - - - }, 0); - } - }); - - - // Added listeners in options - document.querySelectorAll('.block-options .option').forEach(option => { - option.addEventListener('click', function () { - const type = this.getAttribute('data-type'); - if (currentBlock) { - transformBlock(currentBlock, type); - } - document.querySelector('.block-options').style.display = 'none'; - }); - }); -}); - - -function transformBlock(blockElement, type) { - - let contentElement = blockElement.querySelector('.johannes-content-element'); - let content = contentElement.innerText; - - if (content.endsWith('/')) { - content = content.slice(0, -1); // Remove the last '/' - } - - let newBlock; - - switch (type) { - case 'p': - newBlock = createNewParagraphElement(); - newBlock.innerText = content; - break; - case 'h2': - newBlock = createNewH2Element(); - newBlock.innerText = content; - break; - case 'code': - newBlock = document.createElement('pre'); - const code = document.createElement('code'); - code.innerText = content; - newBlock.appendChild(code); - break; - case 'image': - newBlock = document.createElement('img'); - newBlock.src = content; - newBlock.alt = "Descriptive text"; - break; - case 'quote': - newBlock = document.createElement('blockquote'); - newBlock.innerText = content; - break; - case 'list': - newBlock = document.createElement('ul'); - const items = content.split('\n'); - items.forEach(item => { - const listItem = document.createElement('li'); - listItem.innerText = item; - newBlock.appendChild(listItem); - }); - break; - default: - console.error('Unsupported type'); - return; - } - - - blockElement.replaceChild(newBlock, contentElement); - newBlock.focus(); -} - -function createNewH2Element() { - - let newElement = document.createElement('h2'); - newElement.classList.add('johannes-content-element'); - - newElement.contentEditable = true; - - newElement.setAttribute('data-placeholder', 'Heading 2'); - - return newElement; -} - - -function createNewParagraphElement() { - - let newElement = document.createElement('p'); - newElement.classList.add('johannes-content-element'); - - newElement.contentEditable = true; - - newElement.setAttribute('data-placeholder', 'Write something or type / (slash) to choose a block...'); - - return newElement; -} - -function createNewDraggableParagraphElement() { - - let newDiv = document.createElement('div'); - let newElement = createNewParagraphElement(); - - let newButton = document.createElement('button'); - newButton.innerHTML = ''; - - newDiv.appendChild(newButton); - newDiv.appendChild(newElement); - - newDiv.classList.add('draggable-block'); - newButton.classList.add('drag-handler'); - newButton.classList.add('button-reset'); - newButton.draggable = true; - - return newDiv; -} \ No newline at end of file +// End remove pest text style \ No newline at end of file diff --git a/src/keyboard-navigation.js b/src/keyboard-navigation.js new file mode 100644 index 0000000..cffb315 --- /dev/null +++ b/src/keyboard-navigation.js @@ -0,0 +1,68 @@ +// document.addEventListener('DOMContentLoaded', function (event) { +// const blockWrapper = document.querySelector('.block-options-wrapper'); + +// if (blockWrapper) { + +// const first = this.querySelector('.option'); +// let current = null; + +// blockWrapper.addEventListener('keydown', function (event) { + +// current = current || first; + +// if (event.key === 'ArrowDown') { +// event.preventDefault(); +// current = moveToNextOption(current); +// } else if (event.key === 'ArrowUp') { +// event.preventDefault(); +// current = moveToPreviousOption(current); +// } +// }); +// } +// }); + + +// function moveToNextOption(current) { + +// let next = current.nextElementSibling; + +// if (!(next && next.classList.contains('option'))) { + +// let currentSection = current.closest('section'); +// let siblingSection = currentSection.nextElementSibling; + +// if (siblingSection) { +// next = siblingSection.querySelector('.option'); +// } else { +// next = document.querySelector('.block-options-wrapper .option'); +// } + +// } + +// next.focus(); + +// return next; +// } + +// function moveToPreviousOption(current) { + +// let previous = current.previousElementSibling; + +// if (!(previous && previous.classList.contains('option'))) { + +// let currentSection = current.closest('section'); +// let siblingSection = currentSection.previousElementSibling; + +// if (siblingSection) { +// let options = siblingSection.querySelectorAll('.option'); +// previous = options[options.length - 1]; +// } else { +// let options = document.querySelectorAll('.block-options-wrapper .option'); +// previous = options[options.length - 1]; +// } +// } + +// previous.focus(); + +// return previous; +// } \ No newline at end of file diff --git a/src/shortcut.js b/src/shortcut.js new file mode 100644 index 0000000..e69de29 diff --git a/src/style.css b/src/style.css index 0309347..ab28f7a 100644 --- a/src/style.css +++ b/src/style.css @@ -1,8 +1,39 @@ +/** CSS reset */ + +.button-reset { + border: none; + margin: 0; + padding: 0; + width: auto; + overflow: visible; + background: transparent; + color: inherit; + font: inherit; + line-height: normal; + -webkit-font-smoothing: inherit; + -moz-osx-font-smoothing: inherit; + -webkit-appearance: none; +} + + + + .drag-handler { color: #84888d; visibility: hidden; cursor: grab; align-self: start; + + /* background-color: pink; */ + padding-top: 0.33rem; +} + +.drag-handler svg { + /* background-color: red; */ +} + +.drag-handler svg use { + /* background-color: blue; */ } .drag-handler:active { @@ -13,6 +44,9 @@ display: flex; flex-direction: row; margin-bottom: 1rem; + + + /* background-color: purple; */ } .draggable-block:hover .drag-handler { @@ -28,31 +62,8 @@ } - - -.add-block { - margin: 0; - padding: 0; - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - border: 1px solid #000; - background-color: white; - transition: all 0.3s ease; - box-sizing: border-box; -} - -.add-block-wrapper { - width: 100%; - height: 3.125rem; - -} - -.add-block-wrapper:hover .add-block { - display: flex; - +.title h1{ + margin-left: 1.3rem; } h1[data-placeholder]:empty:before { @@ -85,10 +96,6 @@ h1 { font-weight: bolder !important; } -.block-options { - width: 480px; -} - .johannes-editor { width: 100%; @@ -109,7 +116,7 @@ pre { outline: none; } -.content-area { +body { height: 2000px; background-color: #f0f0f1 !important; @@ -118,34 +125,10 @@ pre { .editor { - display: flex; - margin-top: 80px; } -.block-options { - padding: 10px; - background: #f9f9f9; - border: 1px solid #ccc; - margin-top: 10px; - position: absolute; - display: none; -} - -.block-options .option { - padding: 5px 10px; - border: 1px solid #ddd; - margin: 5px; - cursor: pointer; - display: inline-block; -} - -.block-options .option:hover { - background-color: #e9e9e9; -} - - p { font-size: 20px !important; color: #242424; @@ -162,28 +145,12 @@ p { width: 100%; padding: 0; margin: 0; + /* background-color: green; */ } .editor-wrapper { - width: 80%; + width: 60%; margin-left: auto; margin-right: auto; -} - - - -.button-reset { - border: none; - margin: 0; - padding: 0; - width: auto; - overflow: visible; - background: transparent; - color: inherit; - font: inherit; - line-height: normal; - -webkit-font-smoothing: inherit; - -moz-osx-font-smoothing: inherit; - -webkit-appearance: none; -} +} \ No newline at end of file diff --git a/src/switch-block.css b/src/switch-block.css new file mode 100644 index 0000000..0de9222 --- /dev/null +++ b/src/switch-block.css @@ -0,0 +1,139 @@ +.block-options-wrapper { + z-index: 999999; + /* box-shadow: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 3px 6px, rgba(15, 15, 15, 0.2) 0px 9px 24px; */ + position: absolute; + display: none; + /* border: 2px solid #ccc; */ + +} + +/* ::-webkit-scrollbar { + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; + width: 6px; +} */ + +.block-options { + /* width: 324px; 20.25rem */ + width: 20.25rem; + /* height: 384px; */ + height: 340px; + + overflow-y: scroll; + /* border-radius: 10px; */ +} + +.block-options { + padding: 10px; + background: #f9f9f9; + border: 2px solid #ccc; + margin-top: 10px; + /* position: absolute; */ +} + +.block-options section h2 { + font-size: small ; + padding-left: 0.625rem; + color: #37352fa6; +} + +.block-options .option { + padding: 0.3125rem 0.625rem; + margin: 0.3125rem 0; + cursor: pointer; + display: flex; + flex-direction: row; + align-items: center; + justify-content: left; + gap: 0.6rem; + border-radius: 3px; +} + +.block-options .option:focus { + outline: none; + background-color: #e9e9e9; + border: inherit; +} + +.block-options-focused { + outline: none; + background-color: #e9e9e9; + border: inherit; +} + + + + +.block-options .option:focus-visible{ + outline: none; + background-color: #e9e9e9; + border: inherit; +} + +.block-options .option:hover { + background-color: #e9e9e9; +} + + +.option[focused="true"] { + background-color: #bde4ff; /* estilo de foco personalizado */ +} + + + + + +.option-image { + width: 2.875rem; + height: 2.875rem; + border-radius: 3px; + /* background-color: red; */ + border: 1px solid #ccc; + color: #4f4f4f; + + /* color: #84888d; */ + /* color: #242424; */ + display: flex; + flex-direction: row; + justify-content: center; + vertical-align: middle; + align-items: center; + background-color: #ffff; + box-sizing: border-box; +} + +.option-image img { + /* max-width: 46px; 2,875rem */ + max-width: 2.875rem; + max-height: 2.875rem; +} + +.option-image svg { + /* padding: 10px; 0.625rem */ + margin: 0; + padding: 0; + padding: 0.625rem; + box-sizing: border-box; + +} + +.option-text { + /* background-color: yellow; */ + /* font-size: 0.5rem; */ + +} + +.option-text * { + margin: 0; + padding: 0; + +} + +.option-text .block-title { + font-size: small !important; + font-weight: 500; +} + +.option-text .block-description { + font-size: small !important; +} \ No newline at end of file diff --git a/src/switch-block.js b/src/switch-block.js new file mode 100644 index 0000000..aee9bda --- /dev/null +++ b/src/switch-block.js @@ -0,0 +1,287 @@ +import * as farm from './element-farm.js'; +import './switch-block.css'; +import { focusOnTheEndOfTheText } from './helper.js'; + + +function showBlockOptions(x, y) { + + const blockOptionsWrapper = document.querySelector('.block-options-wrapper'); + + blockOptionsWrapper.style.display = 'block'; + blockOptionsWrapper.style.left = `${x}px`; + blockOptionsWrapper.style.top = `${y}px`; + + blockOptionsWrapper.focus(); + + const firstOption = blockOptionsWrapper.querySelector('.option'); + if (firstOption) { + firstOption.focus(); + } +} + + + + +//Show oo +document.addEventListener('DOMContentLoaded', () => { + const content = document.querySelector('.johannes-editor > .content'); + + content.addEventListener('contextmenu', function (event) { + if (event.target.classList.contains('drag-handler')) { + event.preventDefault(); + + showBlockOptions(event.pageX, event.pageY); + + return false; + } + }); +}); + + + +//Close the options wrapper +document.addEventListener('DOMContentLoaded', (event) => { + const editor = document.querySelector('.johannes-editor'); + + editor.addEventListener('keydown', function (event) { + if (event.key === 'Escape') { + document.querySelector('.block-options-wrapper').style.display = 'none'; + } + }); + + // Adicionando o evento de mouse para fechar ao clicar fora + document.addEventListener('mousedown', function (event) { + const optionsWrapper = document.querySelector('.block-options-wrapper'); + + if (optionsWrapper) { + const isClickInsideOptions = optionsWrapper.contains(event.target); + const mainMouseButton = event.button === 0; + + if (!isClickInsideOptions && mainMouseButton) { + optionsWrapper.style.display = 'none'; + } + } + }); +}); + + +document.addEventListener('DOMContentLoaded', () => { + + const editor = document.querySelector('.johannes-editor'); + const blockOptions = document.querySelector('.johannes-editor > .block-options-wrapper'); + + let triggerElement = null; + let currentDraggableBlock = null; + + let currentFocusedOption = null; + + + editor.addEventListener('keydown', function (event) { + if (event.key === '/') { + + setTimeout(() => { + + const range = window.getSelection().rangeCount > 0 ? window.getSelection().getRangeAt(0) : null; + + if (!range) { + + alert('Erro fatal!!!'); + } + + const target = event.target; + if (target.closest('.draggable-block')) { + event.preventDefault(); // Avoid / be inserted + currentDraggableBlock = target.closest('.draggable-block'); + triggerElement = target; + + // Take the element cursor position + const cursorPos = range.getBoundingClientRect(); + + showBlockOptions(cursorPos.left, cursorPos.bottom + window.scrollY); + + let firstOption = blockOptions.querySelector('.option'); + currentFocusedOption = firstOption; + + currentFocusedOption.focus(); + currentFocusedOption.classList.add('block-options-focused'); + triggerElement.focus(); + } + + + }, 0); + } else if (event.key === 'Escape' + // && document.activeElement === blockOptions + ) { + event.preventDefault(); + if (triggerElement) { + triggerElement.focus(); + + currentFocusedOption.classList.remove('block-options-focused'); + } + } else if (event.key === 'Enter' && blockOptions.style.display !== 'none') { + + event.preventDefault(); + + // let option = document.activeElement.closest('.option'); + currentFocusedOption.classList.remove('block-options-focused'); + let option = currentFocusedOption; + + if (option) { + const asasas = triggerElement.closest('.draggable-block'); + const blockType = option.getAttribute('data-type'); + transformBlock(asasas, blockType); + + } + + } else if (event.key === 'ArrowDown' && blockOptions.style.display !== 'none') { + event.preventDefault(); + currentFocusedOption = moveToNextOption(currentFocusedOption, triggerElement); + + } else if (event.key === 'ArrowUp' && blockOptions.style.display !== 'none') { + event.preventDefault(); + currentFocusedOption = moveToPreviousOption(currentFocusedOption, triggerElement); + } + }); + + + // Added listeners in options + document.querySelectorAll('.block-options .option').forEach(option => { + option.addEventListener('click', function () { + const type = this.getAttribute('data-type'); + if (currentDraggableBlock) { + transformBlock(currentDraggableBlock, type); + } + }); + }); +}); + + +function transformBlock(blockElement, type) { + + let contentElement = blockElement.querySelector('.johannes-content-element'); + let content = contentElement.innerText; + + if (content.endsWith('/')) { + content = content.slice(0, -1); // Remove the last '/' + } + + let newContentBlock; + + switch (type) { + case 'p': + newContentBlock = farm.createNewParagraphElement(); + newContentBlock.innerText = content; + break; + case 'h1': + newContentBlock = farm.createNewHeadingElement(1); + newContentBlock.innerText = content; + break; + case 'h2': + newContentBlock = farm.createNewHeadingElement(2); + newContentBlock.innerText = content; + break; + case 'h3': + newContentBlock = farm.createNewHeadingElement(3); + newContentBlock.innerText = content; + break; + case 'h4': + newContentBlock = farm.createNewHeadingElement(4); + newContentBlock.innerText = content; + break; + case 'h5': + newContentBlock = farm.createNewHeadingElement(5); + newContentBlock.innerText = content; + break; + case 'h6': + newContentBlock = farm.createNewHeadingElement(6); + newContentBlock.innerText = content; + break; + case 'code': + newContentBlock = document.createElement('pre'); + const code = document.createElement('code'); + code.innerText = content; + newContentBlock.appendChild(code); + break; + case 'image': + newContentBlock = document.createElement('img'); + newContentBlock.src = content; + newContentBlock.alt = "Descriptive text"; + break; + case 'quote': + newContentBlock = document.createElement('blockquote'); + newContentBlock.innerText = content; + break; + case 'list': + newContentBlock = document.createElement('ul'); + const items = content.split('\n'); + items.forEach(item => { + const listItem = document.createElement('li'); + listItem.innerText = item; + newContentBlock.appendChild(listItem); + }); + break; + default: + console.error('Unsupported type'); + return; + } + + blockElement.replaceChild(newContentBlock, contentElement); + + focusOnTheEndOfTheText(newContentBlock); + + document.querySelector('.block-options-wrapper').style.display = 'none'; +} + + +function moveToNextOption(current, keepFocused) { + + let next = current.nextElementSibling; + + if (!(next && next.classList.contains('option'))) { + + let currentSection = current.closest('section'); + let siblingSection = currentSection.nextElementSibling; + + if (siblingSection) { + next = siblingSection.querySelector('.option'); + } else { + next = document.querySelector('.block-options-wrapper .option'); + } + + } + + next.focus(); + current.classList.remove('block-options-focused'); + next.classList.add('block-options-focused'); + + keepFocused.focus(); + + return next; +} + +function moveToPreviousOption(current, keepFocused) { + + let previous = current.previousElementSibling; + + if (!(previous && previous.classList.contains('option'))) { + + let currentSection = current.closest('section'); + let siblingSection = currentSection.previousElementSibling; + + if (siblingSection) { + let options = siblingSection.querySelectorAll('.option'); + previous = options[options.length - 1]; + } else { + let options = document.querySelectorAll('.block-options-wrapper .option'); + previous = options[options.length - 1]; + } + } + + previous.focus(); + current.classList.remove('block-options-focused'); + previous.classList.add('block-options-focused'); + + keepFocused.focus(); + + return previous; +} \ No newline at end of file diff --git a/src/text-blocks-from-newlines.js b/src/text-blocks-from-newlines.js new file mode 100644 index 0000000..4755aa4 --- /dev/null +++ b/src/text-blocks-from-newlines.js @@ -0,0 +1,43 @@ +import { createNewDraggableParagraphElement } from './element-farm'; +import { focusOnTheEndOfTheText } from './helper'; + +document.addEventListener('DOMContentLoaded', () => { + + const content = document.querySelector('.johannes-editor > .content'); + + content.addEventListener('input', function (event) { + + const target = event.target; + + if (target.tagName === 'P' && target.isContentEditable) { + + const blocks = target.innerText.split('\n'); + + if (blocks.length > 1) { + + event.preventDefault(); + + // Remove original text to avoid duplication + target.innerText = blocks[0]; // Keep the first actual line paragraph + + let currentTarget = target.closest('.draggable-block'); + let lastContentBlock = null; + + // Each new line, create a new P below the actual + for (let i = 1; i < blocks.length; i++) { + + const newParagraph = createNewDraggableParagraphElement(); + + //works? I dont't know + lastContentBlock = newParagraph.querySelector('.johannes-content-element'); + + lastContentBlock.innerText = blocks[i]; + currentTarget.insertAdjacentElement('afterend', newParagraph); + currentTarget = newParagraph; + } + + focusOnTheEndOfTheText(lastContentBlock); + } + } + }); +}); \ No newline at end of file