SOURCE

console 命令行工具 X clear

                    
>
console
import pretty from 'https://cdn.jsdelivr.net/npm/pretty@2.0.0/+esm'
const { log, dir, table, clear, warn, error } = console; clear();

const btnSave = document.querySelector('.btnSave');
const pop = document.querySelector('.popup');

// 初始化 CodeMirror
const coder = CodeMirror.fromTextArea(document.querySelector("#coder"), {
    lineNumbers: true, // 显示行号
    lineWrapping: false, // 代码不要自动换行,否则看起来很费劲
    indentWithTabs: true, // 缩进替换tab为空格
    mode: "htmlmixed", // html语法高亮
    gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], // 显示行号和折叠区域
    lint: true, // 自动纠错
    tabSize: 2, // 设置Tab键的缩进大小
    theme: "default", // 主题
    cursorHeight: 1, // 光标高度 最大值1
    autoCloseTags: true, // 自动关闭标签,好像没什么用
});

// 初始化 TinyMCE
const editor = tinymce.init({
    selector: "#editor",
    language:'zh_CN',
    promotion: false,
    branding: false,
    license_key: 'gpl',
    plugins: 'code',
    toolbar: 'coder | fontsize bold italic alignleft aligncenter alignright alignjustify lineheight | forecolor backcolor',
    // menubar: 'edit insert format',
    menubar: '', /* 直接隐藏菜单栏了 */

    width: '100%',
    height: '100%',
    valid_children : '+body[style]',
    valid_elements: '+*[*]',
    inline_styles: true,
    keep_styles: true,
    extended_valid_elements: '+*[*]',
    custom_elements: '*',
    invalid_elements: '',
    verify_html: false,

	setup: function (editor) {

        // 初始化侦听
        editor.on('init', function (e) {
            // 把数据库的html写到tiny
            // 这个示例会直接从textarea拿到数据,所以注视到下面这行
            // editor.setContent( getData() );

            // 保存按钮侦听
            btnSave.addEventListener('click', (e) => {
                // 将coder的内容传到tiny
                editor.setContent( coder.getValue() );
                
                // 关闭侧栏,恢复tiny可编辑状态
                pop.classList.add('hide');
                editor.mode.set('design');
                editor.getBody().style.opacity = 1;
            });

            // 监听代码:代码变化
            coder.on('change', function (instance, changeObj) {
                // 将coder的内容传到tiny
                // 这步会重置tiny光标到开头,所以不要在富文本编辑时启用,要额外判断一下
                if (!pop.classList.contains('hide')) {
                    editor.setContent( coder.getValue() );
                }
            });
        });

        // editor.on('BeforeSetContent', (e) => {
        //     log(e.content.split('\n').map(line=>line.substring(4)).join('\n'))
        //     if (e.format === 'raw') {
        //         e.content = e.content.trim();
        //     }
        // });

        // 注册`编辑源码`按钮
        editor.ui.registry.addButton('coder', {
            icon: 'sourcecode',
            text: '编辑源码',
            onAction: function () {
                // 将tiny的代码传到coder
                coder.setValue( getHtmlFromTinyMCE(editor) );
                
                // 打开侧栏,tiny设为只读状态
                pop.classList.remove('hide');
                editor.mode.set('readonly');
                editor.getBody().style.opacity = .5;
            }
        });

        // 监听富文本:键盘输入
        editor.on('input', function () {
            // 将tiny的代码传到coder
            coder.setValue( getHtmlFromTinyMCE(editor) );
        });

        // 监听富文本:内容变化
        editor.on('change', function () {
            // 将tiny的代码传到coder
            coder.setValue( getHtmlFromTinyMCE(editor) );
        });
	}
});

// 格式化html代码
function getHtmlFromTinyMCE(editor) {
    const html = editor.getContent()
    return pretty(html);
}
<!-- CodeMirror 样式 -->
<link rel="stylesheet" href="https://cdn.staticfile.org/codemirror/6.65.7/codemirror.min.css">
<link rel="stylesheet" href="https://cdn.staticfile.org/codemirror/6.65.7/theme/idea.min.css">
<link rel="stylesheet" href="https://cdn.staticfile.org/codemirror/6.65.7/addon/hint/show-hint.css">

<!-- CodeMirror 脚本 -->
<script src="https://cdn.staticfile.org/codemirror/6.65.7/codemirror.min.js"></script>
<script src="https://cdn.staticfile.org/codemirror/6.65.7/mode/xml/xml.min.js"></script>
<script src="https://cdn.staticfile.org/codemirror/6.65.7/mode/css/css.min.js"></script>
<script src="https://cdn.staticfile.org/codemirror/6.65.7/mode/javascript/javascript.min.js"></script>
<script src="https://cdn.staticfile.org/codemirror/6.65.7/mode/htmlmixed/htmlmixed.min.js"></script>

<!-- js-beautify 脚本(不再用这个了) -->
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.15.1/beautify.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.15.1/beautify-css.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.15.1/beautify-html.min.js"></script> -->

<textarea id="editor" name="content">
    <h3 v-scope @vue:mounted="mounted">TinyMCE源码格式化-最小验证模型</h3>
    <ul v-effect="test($el)">
        <li>解决了编辑源码时会自动清除掉`空格缩进`导致日后维护活动页源码很麻烦的问题</li>
        <li>解决了编辑源码时会自动清除掉`script`和`style`标签的问题</li>
        <li><s>现在 TinyMCE 和 CodeMirror 修改的内容会保持同步变化</s></li>
        <li>解决 TinyMCE 编辑时,编辑器光标位置不断重置到最前面的问题</li>
        <li>解决 TinyMCE 源码编辑器不支持在标签中加入特殊字符的问题,如"@vue:mounted"</li>
    </ul>

    <blockquote>实际开发时别再引用这里的CDN地址,国内某些地区有风墙!!!</blockquote>

    <a href="https://github.com/tinymce/tinymce" target="_blank">TinyMCE</a>
    <span>&nbsp;|&nbsp;</span>
    <a href="https://github.com/codemirror/dev" target="_blank">CodeMirror</a>
    <span>&nbsp;|&nbsp;</span>
    <a href="https://github.com/jonschlinkert/pretty" target="_blank">Pretty</a>

    <style>button{background:#0cf;}</style>
    <script>PetiteVue.createApp({mounted(){}}).mount()</script>
</textarea>

<div class="popup hide">
    <h3>编辑源码</h3>
    <textarea id="coder"></textarea>
    <button class="btnSave" style="margin-top:25px;">保存 & 关闭</button>
</div>
:root {
    --tiny-width: 70%;
}
html, body {
    height: 100%;
    margin: 0;
    padding: 10px;
    background: #f2f5f7;
}
.hide {
    visibility: hidden;
    pointer-events: none;
    right: calc(-1 * var(--tiny-width)) !important;
}
.CodeMirror {
    flex: 1;
}
.popup {
    display: flex;
    flex-direction: column;
    position: absolute;
    width: var(--tiny-width);
    min-width: 300px;
    height: 100%;
    top: 0;
    right: 0px;
    padding: 20px;
    z-index: 99999;
    background: #ffffffdd;
    backdrop-filter: blur(5px);
    transition: .3s;
    box-shadow: 0 0 8px #00000033;
}

本项目引用的自定义外部资源