diff --git a/assets/css/code.css b/assets/css/code.css index c11cf39..0a6872d 100644 --- a/assets/css/code.css +++ b/assets/css/code.css @@ -9,20 +9,32 @@ Code block wrapper ------------------------------------------------------------------------- */ +/* ------------------------------------------------------------------------- + Code block wrapper +------------------------------------------------------------------------- */ + .ztfr-code { position: relative; + background-color: var(--footer-color); color: var(--light-color); - padding: 1.1rem 1.2rem; + + padding: 1rem 1.2rem; margin: 1rem 0; - border-radius: 6px; + + border-radius: 0; + border: 1px solid rgba(248, 248, 242, 0.08); + border-left: 4px solid var(--hover-color); + max-width: 100%; overflow-x: auto; + font-family: var(--secondary-font); - font-size: 0.870rem; + font-size: 0.85rem; line-height: 1.6; font-weight: 400; - border: 1px solid rgba(248, 248, 242, 0.08); + + box-shadow: none; } /* ------------------------------------------------------------------------- diff --git a/assets/js/editor.js b/assets/js/editor.js index d96a8b1..64c1e98 100644 --- a/assets/js/editor.js +++ b/assets/js/editor.js @@ -1,6 +1,21 @@ (function () { 'use strict'; + /** + * Escape HTML before saving it into the code block markup. + * + * @param {string} text Raw code. + * @returns {string} Escaped code. + */ + function escapeHtml(text) { + return String(text) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + /** * Gutenberg block */ @@ -16,10 +31,114 @@ const element = window.wp.element; const i18n = window.wp.i18n; const blockEditor = window.wp.blockEditor; + const components = window.wp.components; const el = element.createElement; const __ = i18n.__; const PlainText = blockEditor.PlainText; + const Button = components.Button; + const Modal = components.Modal; + const TextareaControl = components.TextareaControl; + const useState = element.useState; + const useEffect = element.useEffect; + const Fragment = element.Fragment; + + function EditCodeBlock(props) { + const content = props.attributes.content || ''; + const setAttributes = props.setAttributes; + const state = useState(false); + const isDialogOpen = state[0]; + const setDialogOpen = state[1]; + + function updateCode(value) { + setAttributes({ content: value }); + } + + useEffect(function () { + if (!isDialogOpen) { + return; + } + + window.setTimeout(function () { + const textarea = document.querySelector('.ztfr-code__modal-textarea textarea'); + + if (textarea) { + textarea.focus(); + textarea.setSelectionRange(textarea.value.length, textarea.value.length); + } + }, 50); + }, [isDialogOpen]); + + return el( + Fragment, + null, + el( + 'div', + { + className: 'ztfr-code is-editor-preview', + 'data-language': 'yaml' + }, + el( + 'div', + { className: 'ztfr-code__editor-actions' }, + el( + Button, + { + variant: 'secondary', + onClick: function () { + setDialogOpen(true); + } + }, + __('Edit code', 'zeitfresser') + ) + ), + el( + 'pre', + { className: 'language-yaml' }, + el(PlainText, { + tagName: 'code', + className: 'language-yaml', + value: content, + placeholder: __('Write or paste YAML code here…', 'zeitfresser'), + onChange: updateCode + }) + ) + ), + isDialogOpen && + el( + Modal, + { + title: __('Edit code block', 'zeitfresser'), + onRequestClose: function () { + setDialogOpen(false); + }, + className: 'ztfr-code__modal' + }, + el(TextareaControl, { + label: __('Code', 'zeitfresser'), + value: content, + onChange: updateCode, + help: __('Paste your YAML code here. Indentation and line breaks are preserved.', 'zeitfresser'), + rows: 18, + className: 'ztfr-code__modal-textarea' + }), + el( + 'div', + { className: 'ztfr-code__modal-actions' }, + el( + Button, + { + variant: 'primary', + onClick: function () { + setDialogOpen(false); + } + }, + __('Done', 'zeitfresser') + ) + ) + ) + ); + } blocks.registerBlockType('ztfr/code-block', { title: __('Code', 'zeitfresser'), @@ -36,30 +155,7 @@ } }, - edit: function (props) { - const content = props.attributes.content || ''; - - return el( - 'div', - { - className: 'ztfr-code is-editor-preview', - 'data-language': 'yaml' - }, - el( - 'pre', - { className: 'language-yaml' }, - el(PlainText, { - tagName: 'code', - className: 'language-yaml', - value: content, - placeholder: __('Write or paste YAML code here…', 'zeitfresser'), - onChange: function (value) { - props.setAttributes({ content: value }); - } - }) - ) - ); - }, + edit: EditCodeBlock, save: function (props) { const content = props.attributes.content || ''; @@ -81,32 +177,42 @@ * Classic Editor TinyMCE button */ if (window.tinymce && window.tinymce.PluginManager) { - function escapeHtml(text) { - return String(text) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - } - window.tinymce.PluginManager.add('ztfr_code_block', function (editor) { - function insertCodeBlock() { - const selectedText = editor.selection.getContent({ format: 'text' }); - const code = selectedText || 'your_key: your_value'; - const safeCode = escapeHtml(code); + function insertCodeBlockFromDialog() { + editor.windowManager.open({ + title: 'Insert code block', + body: [ + { + type: 'textbox', + name: 'code', + label: 'Code', + multiline: true, + minWidth: 700, + minHeight: 350, + value: '' + } + ], + onsubmit: function (event) { + const code = event.data.code || 'your_key: your_value'; + const safeCode = escapeHtml(code); - editor.insertContent( - '
' +
-          safeCode +
-          '

' - ); + editor.insertContent( + '
' +
+              safeCode +
+              '

' + ); + + editor.focus(); + + return true; + } + }); } editor.addButton('ztfr_code_block', { text: 'Code', icon: false, - onclick: insertCodeBlock + onclick: insertCodeBlockFromDialog }); }); } diff --git a/inc/tools/code-block.php b/inc/tools/code-block.php index dd227ed..9e8da36 100644 --- a/inc/tools/code-block.php +++ b/inc/tools/code-block.php @@ -16,6 +16,34 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } +/** + * Check if current post content contains code blocks. + * + * @return bool + */ +function ztfr_has_code_block() { + + if ( is_admin() ) { + return false; + } + + if ( ! is_singular() ) { + return false; + } + + global $post; + + if ( ! isset( $post ) || empty( $post->post_content ) ) { + return false; + } + + if ( has_block( 'ztfr/code-block', $post ) ) { + return true; + } + + return false !== strpos( $post->post_content, '