马黑黑 发表于 2026-2-16 08:51

Markdown在线编辑器第三版

<style>
        .mnBox { margin: 20px; left: calc(50% - 81px); transform: translateX(-50%); padding: 0 15px; width: clamp(600px, 90vw, 1400px); box-shadow: 1px 1px 6px gray; background: linear-gradient(45deg, beige, aliceblue, beige); z-index: 1000; position: relative; }
        .mnBox * { box-sizing: border-box; }
        .barDiv { display: flex; gap: 6px; justify-content: flex-start; align-items: center; height: 50px; }
        .mainarea { display: flex; gap: 15px; width: 100%; height: 75vh; }
        .barDiv span:hover { box-shadow: 1px 1px 2px gray; color: red; }
        .barDiv span { height: 26px; min-width: 72px; padding: 0 10px; line-height: 26px; font-size: 14px; text-align: center; border-radius: 10px; background: none; border: none; cursor: pointer; display: inline-block; }
        #menus { left: 10px; top: 10px; background: aliceblue; padding: 12px 8px; border-radius: 8px; box-shadow: 1px 1px 3px gray; display: none; z-index: 9; position: absolute; }
        #txtEditor, #showDiv { width: 50%; height: 100%; padding: 18px; background: #fff; }
        #showDiv { font-size: 16px; overflow: auto; position: relative; }
        #showDiv:empty::before { content: '效果预览'; position: absolute; color: #aaa; }
        #showDiv p { margin: 0 10px; }
        #showDiv code, #showDiv pre { background: #afeeee; padding: 2px 4px; tab-size: 4; }
        #showDiv pre { white-space: pre-wrap; word-wrap: break-word; }
        #showDiv blockquote { border-left: 3px solid #f0f8ff; }
        #showDiv table { border-collapse: collapse; white-space: pre-wrap; box-sizing: border-box; }
        #showDiv th, #showDiv td { padding: 8px 10px; border: 1px solid #999; }
        #txtEditor { font-size: 18px; resize: none; tab-size: 4; }
        .grow2 { flex-grow: 2; pointer-events: none; }
        .tMid { text-align: center; }
</style>

<h1 class="tMid">Markdown在线编辑器</h1>
<div class="mnBox">
        <div class="barDiv">
                <span id="showMenuBox">快捷键</span>
                <div id="menus"></div>
        </div>

        <div class="mainarea">
                <textarea id="txtEditor" placeholder="输入Markdown文本"></textarea>
                <div id="showDiv"></div>
        </div>

        <div class="barDiv">
                <span id="copyMDContent">复制Markdown</span>
                <span id="copyHtml">复制HTML</span>
                <span class="grow2"></span>
                <span id="clearContent" class="floatRight">重置</span>
        </div>
</div>

<script type="module">
        import { marked } from 'https://638183.freep.cn/638183/web/mod/marked.js';
        import { copy } from 'https://638183.freep.cn/638183/web/mod/copy.js';
        import { formatHTML } from 'https://638183.freep.cn/638183/web/mod/formathtml.js';

        let scheduled = false;
        let lastValue = '';

        // 快捷键数组
        const shortcutKeys = [
                ['1', '一级标题'],
                ['2', '二级标题'],
                ['3', '三级标题'],
                ['4', '四级标题'],
                ['5', '五级标题'],
                ['6', '六级标题'],
                ['`', '代码'],
                ['7', '多行代码'],
                ['8', '斜体'],
                ['9', '粗体'],
                ['0', '粗斜体'],
                ['-', '删除线'],
                ['=', '块引用'],
                ['h', '分隔线'],
                ['l', '无序列表'],
                ['u', '下划线'],
                ['t', '表格'],
                ['a', '链接'],
                ['p', '图片']
        ];

        // 输出的样式
        const cssCode = `\<style>
    #showDiv { font-size: 18px; }
    #showDiv p { margin: 10px 0; }
    #showDiv code, #showDiv pre { background: #afeeee; padding: 2px 4px; tab-size: 4; }
    #showDiv pre { white-space: pre-wrap; word-wrap: break-word; }
    #showDiv blockquote { border-left: 3px solid #f0f8ff; }
    #showDiv table { border-collapse: collapse; white-space: pre-wrap; box-sizing: border-box; }
    #showDiv th, #showDiv td { padding: 8px 10px; border: 1px solid #999; }
    #showDiv tr:firstChild { text-align: center; }
\</style>\n\n`;

        addshortcutMenu(shortcutKeys, menus);

        showMenuBox.onmouseenter = () => menus.style.display = 'block';
        menus.onmouseleave = () => menus.style.display = 'none';
       
        txtEditor.oninput = () => {
                const value = txtEditor.value;
               
                if (!scheduled && value !== lastValue) {
                        scheduled = true;
                        requestAnimationFrame(() => {
                                showDiv.innerHTML = marked.parse(value);
                                lastValue = value;
                                scheduled = false;
                        });
                }
        };

        txtEditor.addEventListener('keydown', (e) => {
                const keys = shortcutKeys.map(key => key);
                if ((e.altKey && keys.includes(e.key)) || e.key === 'Tab') {
                        e.preventDefault();
                        handleKeydown(e.target, e.altKey, e.key);
                        showDiv.innerHTML = marked.parse(txtEditor.value);
                }
        });

        clearContent.onclick = () => txtEditor.value = showDiv.innerHTML = '';
        copyMDContent.onclick = () => copy(txtEditor.value);
        copyHtml.onclick = () => copy(cssCode + formatHTML(showDiv.outerHTML));

        // 快捷键菜单(keys数组结构 : )
        function addshortcutMenu(keys, target) {
                const fm = document.createDocumentFragment();
                keys.forEach(key => {
                        const span = document.createElement('span');
                        span.textContent = key + `(Alt + ${key})`;
                        span.onclick = () => {
                                handleKeydown(txtEditor, true, key);
                                menus.style.display = 'none';
                                showDiv.innerHTML = marked(txtEditor.value);
                                txtEditor.focus();
                        };
                        fm.appendChild(span);
                        const br = document.createElement('br');
                        fm.appendChild(br);
                });
                target.appendChild(fm);
        }

        // 语法输入
        function handleKeydown(elm, alt, key) {
                if (alt) {
                        switch(key) {
                                case '1': addTitle(elm, 1); break;
                                case '2': addTitle(elm, 2); break;
                                case '3': addTitle(elm, 3); break;
                                case '4': addTitle(elm, 4); break;
                                case '5': addTitle(elm, 5); break;
                                case '6': addTitle(elm, 6); break;
                                case '7': addInlineElm(elm, '\n```\n', '\n```\n'); break;
                                case '8': addInlineElm(elm, '*', '*'); break;
                                case '9': addInlineElm(elm, '**', '**'); break;
                                case '0': addInlineElm(elm, '***', '***'); break;
                                case '`': addInlineElm(elm, '`', '`'); break;
                                case '-': addInlineElm(elm, '~~', '~~'); break;
                                case '=': addInlineElm(elm, '>', ''); break;
                                case 'a': addMedia(elm, 0); break;
                                case 'p': addMedia(elm, 1); break;
                                case 't': addMedia(elm, 2); break;
                                case 'h': addHr(elm); break;
                                case 'l': addInlineElm(elm, '- ', ''); break;
                                case 'u': addInlineElm(elm, '<u>', '</u>'); break;
                                default: return;
                        }
                } else {
                        if (key === 'Tab') {
                                elm.setRangeText('    ');
                                elm.selectionStart += 4;
                        }
                }
        }

        // 标题语法
        function addTitle(elm, num) {
                const selectedText = window.getSelection().toString();
                const lineStart = isLineStart(elm);
                let begin = elm.selectionStart;
                let title = selectedText.length > 0 ? selectedText.trim() : '标题' + num;
                let insertStr = (lineStart ? '' : '\n') + '#'.repeat(num) + ' ' +title;
                elm.setRangeText(insertStr);
                begin += num;
                elm.selectionStart = begin + (lineStart ? 1 : 2);
                elm.selectionEnd =begin + title.length + (lineStart ? 1 : 2);
        }

        // 行内语法
        function addInlineElm(elm, strStart, strEnd) {
                const selectedText = window.getSelection().toString();
                let begin = elm.selectionStart + strStart.length;
                let insertStr = strStart + (selectedText.length > 0 ? selectedText.trim() : '文本') + strEnd;
                elm.setRangeText(insertStr);
                elm.selectionStart = begin;
                elm.selectionEnd = begin + insertStr.length - strStart.length - strEnd.length;
        }

        // 分隔线语法
        function addHr(elm) {
                const hr = '\n---\n';
                elm.setRangeText(hr);
                elm.selectionStart += hr.length;
        }

        // 表格和媒体语法
        function addMedia(elm, idx) {
                const mediaAr = [
                        '[锚](地址 "说明")',
                        '\n![图alt](地址 "说明")\n',
                        `\n| 序号| 项目一 | 项目二 | 项目三 |\n| :---: | :---   | ---:   | ---    |\n| 1   | 内容   | 内容   | 内容   |\n\n`,
                ];
                elm.setRangeText(mediaAr);
                elm.selectionStart += mediaAr.length;
        }

        // 判断是否为行首
        function isLineStart(textarea) {
                const start = textarea.selectionStart;
                const value = textarea.value;
                const textBeforeSelection = value.substring(0, start);
                const lastNewlineIndex = textBeforeSelection.lastIndexOf('\n');
                return (lastNewlineIndex === -1 && start === 0) || (lastNewlineIndex + 1 === start);
        }
</script>

马黑黑 发表于 2026-2-16 08:52

<style>
    .showDiv { font-size: 18px; }
    .showDiv p { margin: 10px 0; }
    .showDiv code, .showDiv pre { background: #afeeee; padding: 2px 4px; tab-size: 4; }
    .showDiv pre { white-space: pre-wrap; word-wrap: break-word; }
    .showDiv blockquote { border-left: 3px solid #f0f8ff; }
    .showDiv table { border-collapse: collapse; white-space: pre-wrap; box-sizing: border-box; }
    .showDiv th, .showDiv td { padding: 8px 10px; border: 1px solid #999; }
    .showDiv tr:firstChild { text-align: center; }
</style>

<div class="showDiv">
    <h1>Markdown在线编辑器第二版简单说明</h1>
    <h2>主要更新内容</h2>
    <ul>
      <li>界面细节调整</li>
      <li>功能按钮增删</li>
      <li>新增辅助快捷菜单和组合键语法输入</li>
    </ul>
    <h3>关于语法辅助输入</h3>
    <ol>
      <li>展开“快捷键”菜单,点击相应子项目;</li>
      <li>直接使用快捷组合键(“快捷键”菜单有提示)</li>
    </ol>
    <blockquote>
      <p>
            【说明一】有序列表未加入菜单子项目、也不提供快捷输入方式,请使用直接输入的方法,语法格式为:阿拉伯数字.空格+内容【例】
            <code>1. 项目一</code>
            <br>
            【说明二】表格预设四列,可用管道符号
            <code>|</code>
            添加更多的列,每一行的管道符号应当一致。辅助插入的表格源码中,第二行是定义单元格居中方式,略作分析便可理解其规则。
            <br>
            【说明三】快捷组合键的安排,为避免与系统功能键的冲突,使用了 alt 做引导键,即按
            <code>alt + 预设键</code>
            即可上屏对应语法的语句,使用时注意
            <strong>确保编辑框获得焦点</strong>
            ,上屏的语句代码中预设文本(若有)应改为自己的内容。
      </p>
    </blockquote>
    <h2>其它</h2>
    <p>Markdown在线编辑器成熟的产品很多,我这个功能比较弱小,主要对标论坛简单HTML帖子制作。工具所生成的HTML代码没有第三方依赖,直接可用。</p>
    <p>复制HTML源码附带的CSS代码应根据帖子实际内容增删、修改,例如,假设内容中并没有表格,为避免冗余,表格相关的CSS属性可以删掉,当然,留着也没事。</p>
</div>

红影 发表于 2026-2-16 09:25

过年了,黑黑还在出最新版本的Markdown在线编辑器,辛苦了。
努力学习,同时祝福黑黑过年好,新春快乐,万事如意{:4_187:}

马黑黑 发表于 2026-2-16 13:03

红影 发表于 2026-2-16 09:25
过年了,黑黑还在出最新版本的Markdown在线编辑器,辛苦了。
努力学习,同时祝福黑黑过年好,新春快乐,万 ...

居家办公,没事玩玩

杨帆 发表于 2026-2-16 14:59

祝贺Markdown在线编辑器第三版闪亮登场{:4_199:}

界面更加美观,功能更加完善,使用更加便捷{:4_172:}

祝福马老师除夕快乐,新春快乐,马年大吉{:4_176:}

马黑黑 发表于 2026-2-16 17:53

杨帆 发表于 2026-2-16 14:59
祝贺Markdown在线编辑器第三版闪亮登场

界面更加美观,功能更加完善,使用更加便捷


新年好

红影 发表于 2026-2-18 14:06

马黑黑 发表于 2026-2-16 13:03
居家办公,没事玩玩

居家办公,很流行的工作方式,真好{:4_187:}

马黑黑 发表于 2026-2-18 19:51

红影 发表于 2026-2-18 14:06
居家办公,很流行的工作方式,真好

假期哦

红影 发表于 2026-2-18 20:16

马黑黑 发表于 2026-2-18 19:51
假期哦

过大年呢,放假是必须的{:4_204:}

马黑黑 发表于 2026-2-18 20:18

红影 发表于 2026-2-18 20:16
过大年呢,放假是必须的

但是俺得靠产业养家糊口,俺的产业不能有假期

红影 发表于 2026-2-18 20:43

马黑黑 发表于 2026-2-18 20:18
但是俺得靠产业养家糊口,俺的产业不能有假期

你居家办公,有没有假期你可以自己决定呢{:4_204:}

马黑黑 发表于 2026-2-18 20:46

红影 发表于 2026-2-18 20:43
你居家办公,有没有假期你可以自己决定呢

对,俺自己批自己的假

红影 发表于 2026-2-20 21:51

马黑黑 发表于 2026-2-18 20:46
对,俺自己批自己的假

这样多好,多自由啊。{:4_187:}

马黑黑 发表于 2026-2-20 23:07

红影 发表于 2026-2-20 21:51
这样多好,多自由啊。
是的

红影 发表于 2026-2-21 09:32

马黑黑 发表于 2026-2-20 23:07
是的

这才叫自由职业呢,从事这样的职业也才是自由的人。

马黑黑 发表于 2026-2-21 11:38

红影 发表于 2026-2-21 09:32
这才叫自由职业呢,从事这样的职业也才是自由的人。

也成自谋职业

红影 发表于 2026-2-22 09:24

马黑黑 发表于 2026-2-21 11:38
也成自谋职业

不管怎么说,也是个自由的人。

马黑黑 发表于 2026-2-22 13:02

红影 发表于 2026-2-22 09:24
不管怎么说,也是个自由的人。

自由自在无拘无束

红影 发表于 2026-2-22 21:33

马黑黑 发表于 2026-2-22 13:02
自由自在无拘无束

真好,令人羡慕{:4_204:}

马黑黑 发表于 2026-2-22 21:47

红影 发表于 2026-2-22 21:33
真好,令人羡慕

就是吃不饱穿不暖
页: [1] 2
查看完整版本: Markdown在线编辑器第三版