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 0000000..d8d46cc Binary files /dev/null and b/demo/assets/img/11zon_cropped (1).jpeg differ diff --git a/demo/assets/img/11zon_cropped.jpeg b/demo/assets/img/11zon_cropped.jpeg new file mode 100644 index 0000000..f002ab8 Binary files /dev/null and b/demo/assets/img/11zon_cropped.jpeg differ diff --git a/demo/assets/img/ginal96 - 1172px-Venus_botticelli_detail (1).jpg b/demo/assets/img/ginal96 - 1172px-Venus_botticelli_detail (1).jpg new file mode 100644 index 0000000..22624c3 Binary files /dev/null and b/demo/assets/img/ginal96 - 1172px-Venus_botticelli_detail (1).jpg differ 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 0000000..1eab783 Binary files /dev/null and b/demo/assets/img/icons8-google-maps-96.png differ diff --git a/demo/assets/kepler.ico b/demo/assets/img/kepler.ico similarity index 100% rename from demo/assets/kepler.ico rename to demo/assets/img/kepler.ico diff --git a/demo/assets/img/youtube.jpeg b/demo/assets/img/youtube.jpeg new file mode 100644 index 0000000..2af55f8 Binary files /dev/null and b/demo/assets/img/youtube.jpeg differ 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