...

Markdown редактор

Тема в разделе "Примеры решений и дополнительных модулей", создана пользователем Valentin Lysenko, 29 дек 2024.

  1. Valentin Lysenko

    Valentin Lysenko Участник

    В этой статье предоставляю расширенный функционал Markdown редактора на основе существующего модуля от Алексея Пушкарёва.

    Обновление позволяет регулировать тип отображения виджета в зависимости от параметра - просмотр или редактирование.

    Код с виджет кода перенёс в сценарий. В контексте появилась переменная 'widget_read_only' (Только для чтения), отвечающая за тип отображения.

    Спойлер: Клиентский сценарий
    Код:
    
    declare const documentany
    declare const consoleany

    async 
    function onInit():Promise <void> {
        
    Context.data.editor_id "pac-markdown-editor-" getRandomIntString();  
       
    }

    function 
    getRandomIntString(maxnumber Number.MAX_SAFE_INTEGERminnumber 0): string {
        
    min Math.ceil(min);
        
    max Math.floor(max);
        const 
    randomValue Math.floor(Math.random() * (max min 1)) + min;
        return 
    randomValue.toString();
    }

    function 
    getChangedMarkdownText(textstring) {
        
    Context.data.property_value text;
    }


    async function onRender(): Promise<void> {
        
    toastuiLoadScript(callbackFunction);
        
    makeLinksClickable()
    }

    function 
    toastuiLoadScript(callback?: () => void): void {

        const 
    link document.createElement('link');
        
    link.type 'text/css';
        
    link.rel 'stylesheet';
        
    link.href 'https://uicdn.toast.com/editor/latest/toastui-editor.min.css';
        
    document.head.appendChild(link);
       
        const 
    script document.createElement('script');
        
    script.src 'https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js';

        
    script.onload = () => {
            const 
    selectorString = `#${Context.data.editor_id}`;
            const 
    markdownEditors document.querySelectorAll(selectorString);
            
    markdownEditors.forEach((divItemany) => {
                
    //@ts-ignore
                
    const markdownEditor = new toastui.Editor({
                    
    eldivItem,
                    
    heightContext.data.height,
                    
    initialEditType'wysiwyg',
                    
    initialValueContext.data.property_value,
                    
    events: {
                        
    change: (e1any) => {
                            
    getChangedMarkdownText(markdownEditor.getMarkdown());
                        }
                    }
                });
            });

           
            
    callback?.();
        };
       
        
    document.head.appendChild(script);
    }


    function 
    callbackFunction(): void {
        
    makeReadOnly()
        
    changeHeight()
       
    }

    function 
    makeReadOnly(): void {
        
    //Удаляем элементы из дерева если доступ тольько на чтение
        
    if(Context.data.widget_read_only){
            
    deleteToolbar();
            
    deleteFooter();
            
    setContentEditableFalse();
        }
       
       
    }

    function 
    deleteToolbar(): void {
        const 
    selector = `#${Context.data.editor_id} > div > div.toastui-editor-toolbar`;
        const 
    toolbarElement document.querySelector(selector);
       
        if (
    toolbarElement) {
            
    toolbarElement.remove(); // Удаляем элементы из дерева
            
    console.log('Markdown editor: Toolbar has been deleted.');
        } else {
            
    console.log('Markdown editor: Toolbar element not found.');
            
    console.log(selector);
        }        
    }

    function 
    deleteFooter(): void {
        
    // document.
        
    const selector = `#${Context.data.editor_id} > div > div.toastui-editor-mode-switch`;
        const 
    footerElement document.querySelector(selector);

        if (
    footerElement) {
            
    footerElement.remove(); // Удаляем элементы из дерева
            
    console.log('Markdown editor: Footer has been deleted.');
        } else {
            
    console.log('Markdown editor: Footer element not found.');
            
    console.log(selector);
        }
    }

    function 
    setContentEditableFalse(): void {
        const 
    selector = `#${Context.data.editor_id} > div > div.toastui-editor-main.toastui-editor-ww-mode > div > div.toastui-editor-ww-container > div > div`;
        const 
    editableDiv document.querySelector(selector);

       
        if (
    editableDiv) {
            
    // делаем поле нередактируемым
            
    editableDiv.setAttribute('contenteditable''false');
        } else {
            
    console.log('Markdown editor: Textbox not found');
        }
    }

    function 
    makeLinksClickable() {
        if (
    Context.data.property_value && Context.data.widget_read_only) {
            
    // Регулярное выражение для поиска ссылок
            
    const urlRegex = /https?:\/\/[^\s]+/g;

            
    // Заменяем найденные ссылки на те же ссылки, но с кавычками по краям
            
    Context.data.property_value Context.data.property_value.replace(urlRegex, (url) => `<${url}>`);
        }
    }

    function 
    changeHeight() {
        const 
    selector = `#${Context.data.editor_id} > div > div.toastui-editor-main.toastui-editor-ww-mode > div > div.toastui-editor-ww-container > div `;
        const 
    editableDiv document.querySelector(selector);
        
    console.log('editableDiv')
        
    console.log(editableDiv)
        if (
    editableDiv) {
            
    editableDiv.style.minHeight 'fit-content'
        
    } else {
            
    console.log('editableDiv/changeheight not found');
        }
    }

    Что делает код:
    Вырезает из DOM-дерева тулбар, отвечающий за форматирование текста и вставку картинок. Также меняет параметр редактируемости текста на false

    В настройках виджета можно указать как статические параметры Да/Нет, так и зависимость от контекста.
    [​IMG]
    [​IMG]
    Update: В клиентском сценарии добавил callback функцию, в которой вынес функции, вызываемые после отрисовки виджета.
    Также во второй версии присутствует адаптация размеров виджета под окно формы. Ширина = 100% (Виджет код). Высота - fit-content (Сделано отдельной функцией).

    Вложения:

    Последнее редактирование: 2 апр 2025
  2. Valentin Lysenko

    Valentin Lysenko Участник

    Если нужно сделать виджет более адаптивным, то необходимо определить ширину и переопределить высоту.

    Ширина меняется в " виджет код"

    Код:
    
    <app-dynamic-form-row>
        <
    elma-form-row class="elma-form-row ng-untouched ng-pristine ng-valid">
            <
    elma-form-label class="elma-form-label">
                <
    span class="labelName"><%- Context.data.property_name %></span>
            </
    elma-form-label>
            <
    elma-form-control class="elma-form-control text-base">
                <
    div id="<%- Context.data.editor_id %>" class="multiline-container ng-star-inserted" style='width: 100%'></div>
            </
    elma-form-control>
        </
    elma-form-row>
    </
    app-dynamic-form-row>
    Высоту мы меняем в функции onRender()
    Код:
    
    async function onRender(): Promise<void> {
        
    toastuiLoadScript(makeReadOnly);
        
    makeLinksClickable()
        
    document.setTimeout(changeHeight(), 5000);//Здесь новая функция
    }
    Код:
    
    function changeHeight() {
        const 
    selector = `#${Context.data.editor_id} > div > div.toastui-editor-main.toastui-editor-ww-mode > div > div.toastui-editor-ww-container > div `;
        const 
    editableDiv document.querySelector(selector);
        if (
    editableDiv) {
            
    editableDiv.style.minHeight 'fit-content'
        
    } else {
            
    console.log('editableDiv/changeheight not found');
        }
    }
    Последнее редактирование: 2 апр 2025