Full Code of zkqiang/wechat-mdeditor for AI

master 7c9f5de5f498 cached
14 files
44.2 KB
13.9k tokens
9 symbols
1 requests
Download .txt
Repository: zkqiang/wechat-mdeditor
Branch: master
Commit: 7c9f5de5f498
Files: 14
Total size: 44.2 KB

Directory structure:
gitextract_hh_tb0ei/

├── .gitignore
├── README.md
└── src/
    ├── assets/
    │   ├── css/
    │   │   ├── app.css
    │   │   └── loading.css
    │   ├── default-content.md
    │   └── scripts/
    │       ├── editor.js
    │       ├── loading.js
    │       ├── renderers/
    │       │   └── wx-renderer.js
    │       └── themes/
    │           ├── default.js
    │           ├── lupeng.js
    │           └── lyric.js
    ├── index.html
    └── libs/
        ├── FuriganaMD.js
        └── sync-scroll.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Created by .ignore support plugin (hsz.mobi)
### Java template
# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

### JetBrains template
.idea

### Android template
# Built application files
*.apk
*.ap_
*.aab

# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/
release/

# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml

# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild

# Google Services (e.g. APIs or Firebase)
# google-services.json

# Freeline
freeline.py
freeline/
freeline_project_description.json

# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md

# Version control
vcs.xml

# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/

### macOS template
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

.idea/markdown-navigator/
.idea/misc.xml


================================================
FILE: README.md
================================================
# 微信公众号 Markdown 编辑器

这款编辑器可以将 Markdown 转换成微信公众号编辑器的样式,可以直接复制到公众号后台。

这让你在公众号创作时,把更多的时间专注于文章本身,而不是繁琐地调整文章样式。

[在线使用](http://prod.zkqiang.cn/wxeditor)

## 功能

- 支持序号列表和圆点列表,解决了样式会被重置的问题
- 外链会自动转换为参考文献索引,并且附在文章末尾
- 支持多种字体和样式
- 支持日语注音假名、汉语拼音样式
- 支持不同于微信的代码配色方案

## 关于

本仓库 Fork 自 [yricat/wechat-format](https://github.com/lyricat/wechat-format),并根据自用需求进行修改开发。

感谢他的创意和贡献!


================================================
FILE: src/assets/css/app.css
================================================
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

input, button, textarea {
  font-family: inherit;
}

h1, h2, h3, h4, h5, h6 {
  font-weight: normal;
}

em {
  font-style: normal !important;
}

html, body {
  height: 100%;
  font-family: 'PingFang SC', BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif;
}

.copy-button {
  text-decoration: none;
  color: #ff3502
}

.el-message__icon {
  display: none
}

.container {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.top {
  height: 60px;
  padding: 10px 20px;
  display: flex;
  align-items: center;
}

.top {
  margin-right: 20px;
}

.web-title {
  margin: 0 15px 0 5px;
}

.web-icon {
  width: auto;
  height: 1.5rem;
  vertical-align: middle;
}

#editor {
  height: 100%;
  display: block;
  border: none;
  width: 100%;
  padding: 10px;
}

section {
  height: 100%;
}

.main-body {
  display: flex;
  flex-direction: column;
  padding-top: 0;
  padding-bottom: 10px;
}

.ctrl {
  flex-basis: 60px;
  flex-grow: 1;
  flex-shrink: 1;
  display: flex;
  align-items: center;
}

.preview-wrapper {
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
  padding: 0;
  align-items: center;
  justify-content: center;
  display: flex;
  /* height: 100%; */
  overflow: scroll;
}

.main-section {
  display: flex;
  height: 100%;
}

.hint {
  opacity: 0.6;
  margin: 20px 0;
}

.preview {
  margin: 0 -20px;
  width: 375px;
  padding: 20px;
  font-size: 14px;
  outline: none;
  box-shadow: 0 0 60px rgba(0, 0, 0, 0.1);
}

.preview table {
  margin-bottom: 10px;
  border-collapse: collapse;
  display: table;
  width: 100% !important;
}

/*.preview ul, .preview ol {*/
/*  padding-left: 40px !important;*/
/*}*/

.select-item-left {
  float: left;
}

.select-item-right {
  float: right;
  color: #8492a6;
  font-size: 13px;
}

.CodeMirror {
  height: 100%;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
  font-size: 14px;
  padding: 20px;
  width: 100%;
  font-family: 'PingFang SC', BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif;
}

/* ele ui */
.el-form-item {
  margin-bottom: 0 !important;
}

/*wechat code block*/
.rich_media_content .code-snippet *, .rich_media_content .code-snippet__fix * {
  max-width: 1000% !important;
}

.code-snippet__fix {
  word-wrap: break-word !important;
  ont-size: 14px;
  margin: 10px 0;
  color: #333;
  position: relative;
  background-color: rgba(0, 0, 0, 0.03);
  border: 1px solid #f0f0f0;
  border-radius: 2px;
  display: flex;
  line-height: 26px;
}

.code-snippet__fix .code-snippet__line-index {
  counter-reset: line;
  flex-shrink: 0;
  height: 100%;
  padding: 1em;
  list-style-type: none;
}

.code-snippet__fix .code-snippet__line-index li {
  list-style-type: none;
  text-align: right;
}

.code-snippet__fix .code-snippet__line-index li::before {
  min-width: 1.5em;
  text-align: right;
  left: -2.5em;
  counter-increment: line;
  content: counter(line);
  display: inline;
  color: rgba(0, 0, 0, 0.15);
}

.code-snippet__fix pre {
  overflow-x: auto;
  padding: 1em 1em 1em 1em;
  white-space: normal;
  flex: 1;
  -webkit-overflow-scrolling: touch;
}

.code-snippet__fix code {
  text-align: left;
  font-size: 14px;
  white-space: pre;
  display: flex;
  position: relative;
  font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
}


================================================
FILE: src/assets/css/loading.css
================================================
.loading {
  text-align: center;
  position: fixed;
  width: 100%;
  height: 100%;
  overflow: hidden;
  z-index: 99999;
  background-color: #f2f2f2;
}

.loading-wrapper {
  position: fixed;
  top: 50%;
  left: 50%;
  -webkit-transform: translateX(-50%) translateY(-50%);
  -moz-transform: translateX(-50%) translateY(-50%);
  -ms-transform: translateX(-50%) translateY(-50%);
  transform: translateX(-50%) translateY(-50%);
}

.loading-text {
  line-height: 1.4;
  font-size: 1.2rem;
  font-weight: bold;
  margin-bottom: 1rem;
}

.loading-anim {
  width: 35px;
  height: 35px;
  border: 5px solid rgba(189, 189, 189, 0.25);
  border-left-color: rgba(3, 155, 229, 1);
  border-top-color: rgba(3, 155, 229, 1);
  border-radius: 50%;
  display: inline-block;
  animation: rotate 600ms infinite linear;
}

@keyframes rotate {
  to {
    transform: rotate(1turn)
  }
}


================================================
FILE: src/assets/default-content.md
================================================
# 公众号 Markdown 编辑器

### 简介

这款编辑器可以将 Markdown 转换成微信公众号编辑器的样式,只需将 MD 文档复制到左侧栏,再在右侧栏顶部"点击复制",右侧预览内容就可被复制到公众号后台。

这让你在公众号创作时,把更多的时间专注于文章本身,而不是繁琐地调整文章样式。


### 功能

- 支持序号列表和圆点列表,解决了样式会被重置的问题
- 外链会自动转换为参考文献索引,并且附在文章末尾
- 支持多种字体和样式
- 支持日语注音假名、汉语拼音样式
- 支持不同于微信的代码配色方案
- 支持编辑内容自动保存、预览同步滚动等常见功能

### 关于 Markdown

1. Markdown 是一种轻量级标记语言,能将文本换成有效的 XHTML(或者HTML) 文档
2. Markdown 强大之处,在于可以用一套格式,在所有支持 Markdown 的编辑器中转换成发布样式,做到最大化兼容,不需要担心复制到不同编辑器中样式被破坏
3. 正如你右侧看到的这样,Markdown 被转换成了微信支持的样式,同样你可以在一字不改的情况下,在 Github 等平台上转换类似的样式
4. 学习 Markdown 的语法,可以查看 [Markdown 语法入门手册](https://www.w3cschool.cn/markdownyfsm/markdownyfsm-odm6256r.html)

## 更多样式

### 注音符号

[注音符号 W3C 定义](http://www.w3.org/TR/ruby/)。

支持日语注音假名、汉语拼音。

用法有以下几种:

* 世界{せかい}
* 小夜時雨{さ・よ・しぐれ}
* 食べる{たべる}
* 丧心病狂{gàn・de・piào・liang}

### 图片

接下来是一张图片。你可以用自己图床,也可以上传到微信媒体库再把图片 URL
粘贴回来,或者编辑好以后,在公众号里插入图片。

![这里可以写图片描述](https://static.zkqiang.cn/images/20191019181145.JPG-slim)

如果使用图床链接的话,有可能复制后图片不能被上传,需要手动在微信重新上传替换。

### 代码块

代码高亮使用了 Github 配色方案,后续会加入更多配色。

**注意:由于微信编辑器限制,复制后若在微信编辑器中点击代码块,会被微信自动重置后它的配色,只能重新再复制**

```cpp
#include <stdio.h>

const int MAX = 10;
int cache[MAX] = {0};

int fib(int x) {
  if (x == 1) return 1;
  if (x == 0) return 0;
  if (cache[x] == 0) {
    int ret = fib(x - 1) + fib(x - 2);
    cache[x] = ret;
  }
  return cache[x];
}

int main() {
    int i;
    printf("fibonacci series:\n");
    for (i = 0; i < MAX; ++i) {
        printf("%d ", fib(i));
    }
    return 0;
}
```

### 内联代码

inline code `{code: 0}`

### 表格

表格无法使用自定义样式,暂时没找到解决途径

| Header 1 | Header 2 |
| --- | --- |
| Key 1 | Value 1 |
| Key 2 | Value 2 |
| Key 3 | Value 3 |

### 超链接

如果是公众号文章的超链接,是可以点击打开的,但其他链接都无法点击,所以这里使用类似于文献的底部引用。

例如:

[这是一篇公众号文章](https://mp.weixin.qq.com/s/ahpV7Poj5wHmtUP6vqy3gg)

[这是我的博客地址](http://zkqiang.cn)

[通过引号设置引用名](http://prod.zkqiang.cn/wxeditor "这是自定义的引用名")

[本项目是 Fork 自 Lyric 原项目后的二次开发,感谢他的贡献!](https://github.com/lyricat/wechat-format "原项目代码库")


================================================
FILE: src/assets/scripts/editor.js
================================================
let app = new Vue({
  el: '#app',
  data: function () {
    let d = {
      aboutOutput: '',
      output: '',
      source: '',
      editorThemes: [
        { label: 'base16-light', value: 'base16-light' },
        { label: 'duotone-light', value: 'duotone-light' },
        { label: 'monokai', value: 'monokai' }
      ],
      editor: null,
      builtinFonts: [
        {
          label: '无衬线',
          value: "-apple-system-font,BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB , Microsoft YaHei UI , Microsoft YaHei ,Arial,sans-serif"
        },
        {
          label: '衬线',
          value: "Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, 'PingFang SC', Cambria, Cochin, Georgia, Times, 'Times New Roman', serif"
        }
      ],
      sizeOption: [
        { label: '14px', value: '14px', desc: '稍小' },
        { label: '15px', value: '15px', desc: '默认' },
        { label: '16px', value: '16px', desc: '稍大' },
        { label: '17px', value: '17px', desc: '很大' },
      ],
      themeOption: [
        { label: 'default', value: 'default', author: '张凯强' },
        { label: 'lyric', value: 'lyric', author: 'Lyric' },
        { label: 'lupeng', value: 'lupeng', author: '鲁鹏' }
      ],
      styleThemes: {
        default: defaultTheme,
        lyric: lyricTheme,
        lupeng: lupengTheme
      },
      aboutDialogVisible: false
    };
    d.currentEditorTheme = d.editorThemes[0].value;
    d.currentFont = d.builtinFonts[0].value;
    d.currentSize = d.sizeOption[1].value;
    d.currentTheme = d.themeOption[0].value;
    return d;
  },
  mounted() {
    let self = this;
    this.editor = CodeMirror.fromTextArea(
      document.getElementById('editor'),
      {
        lineNumbers: false,
        lineWrapping: true,
        styleActiveLine: true,
        theme: this.currentEditorTheme,
        mode: 'text/x-markdown',
      }
    );
    this.editor.on("change", function (cm, change) {
      self.refresh();
      self.saveEditorContent();
    });
    this.wxRenderer = new WxRenderer({
      theme: this.styleThemes.default,
      fonts: this.currentFont,
      size: this.currentSize
    });
    // 如果有编辑内容被保存则读取,否则加载默认文档
    if (localStorage.getItem("__editor_content")) {
      this.editor.setValue(localStorage.getItem("__editor_content"));
    } else {
      axios({
        method: 'get',
        url: './assets/default-content.md',
      }).then(function (resp) {
        self.editor.setValue(resp.data)
      })
    }
  },
  methods: {
    renderWeChat: function (source) {
      let output = marked(source, { renderer: this.wxRenderer.getRenderer() });
      if (this.wxRenderer.hasFootnotes()) {
        // 去除第一行的 margin-top
        output = output.replace(/(style=".*?)"/, '$1;margin-top: 0"');
        // 引用注脚
        output += this.wxRenderer.buildFootnotes();
        // 附加的一些 style
        output += this.wxRenderer.buildAddition();
      }
      return output
    },
    editorThemeChanged: function (editorTheme) {
      this.editor.setOption('theme', editorTheme)
    },
    fontChanged: function (fonts) {
      this.wxRenderer.setOptions({
        fonts: fonts
      });
      this.refresh()
    },
    sizeChanged: function (size) {
      this.wxRenderer.setOptions({
        size: size
      });
      this.refresh()
    },
    themeChanged: function (themeName) {
      let themeObject = this.styleThemes[themeName];
      this.wxRenderer.setOptions({
        theme: themeObject
      });
      this.refresh()
    },
    // 刷新右侧预览
    refresh: function () {
      this.output = this.renderWeChat(this.editor.getValue(0))
    },
    // 将左侧编辑器内容保存到 LocalStorage
    saveEditorContent: function () {
      let content = this.editor.getValue(0);
      if (content){
        localStorage.setItem("__editor_content", content);
      } else {
        localStorage.removeItem("__editor_content");
      }
    },
    copy: function () {
      let clipboardDiv = document.getElementById('output');
      clipboardDiv.focus();
      window.getSelection().removeAllRanges();
      let range = document.createRange();
      range.setStartBefore(clipboardDiv.firstChild);
      range.setEndAfter(clipboardDiv.lastChild);
      window.getSelection().addRange(range);

      try {
        if (document.execCommand('copy')) {
          this.$message({
            message: '已复制到剪贴板', type: 'success'
          })
        } else {
          this.$message({
            message: '未能复制到剪贴板,请全选后右键复制', type: 'warning'
          })
        }
      } catch (err) {
        this.$message({
          message: '未能复制到剪贴板,请全选后右键复制', type: 'warning'
        })
      }
    },
    openWindow: function (url) {
      window.open(url);
    }
  },
  updated: function () {
    this.$nextTick(function () {
      prettyPrint()
    })
  }
});


================================================
FILE: src/assets/scripts/loading.js
================================================
// 加载完成隐藏 loading 界面
window.onload = () => {
  $('#loading').hide();
};


================================================
FILE: src/assets/scripts/renderers/wx-renderer.js
================================================
let WxRenderer = function (opts) {
  this.opts = opts;
  let ENV_USE_REFERENCES = true;
  let ENV_STRETCH_IMAGE = true;

  let footnotes = [];
  let footnoteIndex = 0;
  let styleMapping = null;

  let CODE_FONT_FAMILY = "Menlo, Operator Mono, Consolas, Monaco, monospace";

  let merge = function (base, extend) {
    return Object.assign({}, base, extend)
  };

  this.buildTheme = function (themeTpl) {
    let mapping = {};
    let base = merge(themeTpl.BASE, {
      'font-family': this.opts.fonts,
      'font-size': this.opts.size
    });
    let base_block = merge(base, {});
    for (let ele in themeTpl.inline) {
      if (themeTpl.inline.hasOwnProperty(ele)) {
        let style = themeTpl.inline[ele];
        if (ele === 'codespan') {
          style['font-family'] = CODE_FONT_FAMILY;
          style['white-space'] = 'normal';
        }
        mapping[ele] = merge(base, style)
      }
    }
    for (let ele in themeTpl.block) {
      if (themeTpl.block.hasOwnProperty(ele)) {
        let style = themeTpl.block[ele];
        if (ele === 'code') {
          style['font-family'] = CODE_FONT_FAMILY
        }
        mapping[ele] = merge(base_block, style)
      }
    }
    return mapping
  };

  let getStyles = function (tokenName, addition) {
    let arr = [];
    let dict = styleMapping[tokenName];
    if (!dict) return '';
    for (const key in dict) {
      arr.push(key + ':' + dict[key])
    }
    return `style="${ arr.join(';') + (addition || '') }"`
  };

  let addFootnote = function (title, link) {
    footnoteIndex += 1;
    footnotes.push([footnoteIndex, title, link]);
    return footnoteIndex
  };

  this.buildFootnotes = function () {
    let footnoteArray = footnotes.map(function (x) {
      if (x[1] === x[2]) {
        return `<code style="font-size: 90%; opacity: 0.6;">[${ x[0] }]</code>: <i>${ x[1] }</i><br/>`
      }
      return `<code style="font-size: 90%; opacity: 0.6;">[${ x[0] }]</code> ${ x[1] }: <i>${ x[2] }</i><br/>`
    });
    return `<h3 ${ getStyles('h3') }>References</h3><p ${ getStyles('footnotes') }>${ footnoteArray.join('\n') }</p>`
  };

  this.buildAddition = function () {
    return '<style>.preview-wrapper pre::before{' +
      'font-family:"SourceSansPro","HelveticaNeue",Arial,sans-serif;' +
      'position:absolute;' +
      'top:0;' +
      'right:0;' +
      'color:#ccc;' +
      'text-align:right;' +
      'font-size:0.8em;' +
      'padding:5px10px0;' +
      'line-height:15px;' +
      'height:15px;' +
      'font-weight:600;' +
      '}</style>'
  };

  this.setOptions = function (newOpts) {
    this.opts = merge(this.opts, newOpts)
  };

  this.hasFootnotes = function () {
    return footnotes.length !== 0
  };

  this.getRenderer = function () {
    footnotes = [];
    footnoteIndex = 0;

    styleMapping = this.buildTheme(this.opts.theme);
    let renderer = new marked.Renderer();
    FuriganaMD.register(renderer);

    renderer.heading = function (text, level) {
      switch (level) {
        case 1:
          return `<h1 ${ getStyles('h1') }>${ text }</h1>`;
        case 2:
          return `<h2 ${ getStyles('h2') }>${ text }</h2>`;
        case 3:
          return `<h3 ${ getStyles('h3') }>${ text }</h3>`;
        default:
          return `<h4 ${ getStyles('h4') }>${ text }</h4>`;
      }
    };
    renderer.paragraph = function (text) {
      return `<p ${ getStyles('p') }>${ text }</p>`
    };
    renderer.blockquote = function (text) {
      text = text.replace(/<p.*?>/, `<p ${ getStyles('blockquote_p') }>`);
      return `<blockquote ${ getStyles('blockquote') }>${ text }</blockquote>`
    };
    renderer.code = function (text, infoString) {
      text = text.replace(/</g, "&lt;");
      text = text.replace(/>/g, "&gt;");

      let lines = text.split('\n');
      let codeLines = [];
      let numbers = [];
      for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        codeLines.push(`<code class="prettyprint"><span class="code-snippet_outer">${ (line || '<br>') }</span></code>`);
        numbers.push('<li></li>')
      }
      let lang = infoString || '';
      return `<section class="code-snippet__fix code-snippet__js">`
        + `<ul class="code-snippet__line-index code-snippet__js">${ numbers.join('') }</ul>`
        + `<pre class="code-snippet__js" data-lang="${ lang }">`
        + codeLines.join('')
        + `</pre></section>`
    };
    renderer.codespan = function (text, infoString) {
      return `<code ${ getStyles('codespan') }>${ text }</code>`
    };
    renderer.listitem = function (text) {
      return `<span ${ getStyles('listitem') }><span style="margin-right: 10px;"><%s/></span>${ text }</span>`;
    };
    renderer.list = function (text, ordered, start) {
      text = text.replace(/<\/*p.*?>/g, '');
      let segments = text.split(`<%s/>`);
      if (!ordered) {
        text = segments.join('•');
        return `<p ${ getStyles('ul') }>${ text }</p>`;
      }
      text = segments[0];
      for (let i = 1; i < segments.length; i++) {
        text = text + i + '.' + segments[i];
      }
      return `<p ${ getStyles('ol') }>${ text }</p>`;
    };
    renderer.image = function (href, title, text) {
      let subText = '';
      if (text) {
        subText = `<figcaption ${ getStyles('figcaption') }>${ text }</figcaption>`
      }
      let figureStyles = getStyles('figure');
      let imgStyles = getStyles(ENV_STRETCH_IMAGE ? 'image' : 'image_org');
      return `<figure ${ figureStyles }><img ${ imgStyles } src="${ href }" title="${ title }" alt="${ text }"/>${ subText }</figure>`
    };
    renderer.link = function (href, title, text) {
      if (href.indexOf('https://mp.weixin.qq.com') === 0) {
        return `<a href="${ href }" title="${ (title || text) }" ${ getStyles('wx_link') }>${ text }</a>`;
      } else if (href === text) {
        return text;
      } else {
        if (ENV_USE_REFERENCES) {
          let ref = addFootnote(title || text, href);
          return `<span ${ getStyles('link') }>${ text }<sup>[${ ref }]</sup></span>`;
        } else {
          return `<a href="${ href }" title="${ (title || text) }" ${ getStyles('link') }>${ text }</a>`;
        }
      }
    };
    renderer.strong = function (text) {
      return `<strong ${ getStyles('strong') }>${ text }</strong>`;
    };
    renderer.em = function (text) {
      return `<p ${ getStyles('p', ';font-style: italic;')}>${ text }</p>`
    };
      renderer.table = function (header, body) {
      return `<table class="preview-table"><thead ${ getStyles('thead') }>${ header }</thead><tbody>${ body }</tbody></table>`;
    };
    renderer.tablecell = function (text, flags) {
      return `<td ${ getStyles('td') }>${ text }</td>`;
    };
    renderer.hr = function () {
      return `<hr style="border-style: solid;border-width: 1px 0 0;border-color: rgba(0,0,0,0.1);-webkit-transform-origin: 0 0;-webkit-transform: scale(1, 0.5);transform-origin: 0 0;transform: scale(1, 0.5);">`;
    };
    return renderer
  }
};


================================================
FILE: src/assets/scripts/themes/default.js
================================================
let defaultTheme = {
  BASE: {
    'text-align': 'left',
    'color': '#3f3f3f',
    'line-height': '1.75',
  },
  BASE_BLOCK: {
    'margin': '1em 8px'
  },
  // block element
  block: {
    h1: {
      'font-size': '1.2em',
      'text-align': 'center',
      'font-weight': 'bold',
      'display': 'table',
      'margin': '2em auto 1em auto',
      'padding': '0 1em',
      'border-bottom': '1px solid rgb(248,57,41)'
    },
    h2: {
      'font-size': '1.2em',
      'text-align': 'center',
      'font-weight': 'bold',
      'display': 'table',
      'margin': '4em auto 2em auto',
      'padding': '0 1em',
      'border-bottom': '1px solid rgb(248,57,41)'
    },
    h3: {
      'font-weight': 'bold',
      'font-size': '1.1em',
      'margin': '2em 8px 0.75em 0',
      'padding-bottom': '.1em',
      // 'border-bottom': '1px solid #eaecef',
      'padding-left': '8px',
      'border-left': '4px solid rgb(248,57,41)'
    },
    h4: {
      'font-weight': 'bold',
      'font-size': '1em',
      'margin': '2em 8px 0.5em 8px',
    },
    p: {
      'margin': '1.5em 8px',
      'letter-spacing': '0.1em'
    },
    blockquote: {
      'font-style': 'normal',
      'border-left': 'none',
      'padding': '1em',
      'border-radius': '4px',
      'color': '#FEEEED',
      'background': 'rgba(27,31,35,.05)',
      'margin': '2em 8px'
    },
    blockquote_p: {
      'letter-spacing': '0.1em',
      'color': 'rgb(80, 80, 80)',
      'font-family': 'PingFangSC-light, PingFangTC-light, Open Sans, Helvetica Neue, sans-serif',
      'font-size': '1em',
      'display': 'inline',
    },
    code: {
      'font-size': '80%',
      'overflow': 'auto',
      'color': '#333',
      'background': 'rgb(247, 247, 247)',
      'border-radius': '2px',
      'padding': '10px',
      'line-height': '1.5',
      'border': '1px solid rgb(236,236,236)',
      'margin': '20px 0',
    },
    image: {
      'border-radius': '4px',
      'display': 'block',
      'margin': '0.5em auto',
      'width': '100%'
    },
    image_org: {
      'border-radius': '4px',
      'display': 'block'
    },
    ol: {
      'margin-left': '0',
      'padding-left': '1em'
    },
    ul: {
      'margin-left': '0',
      'padding-left': '1em',
      'list-style': 'circle'
    },
    footnotes: {
      'margin': '0.5em 8px',
      'font-size': '80%'
    },
    figure: {
      'margin': '1.5em 8px',
    }
  },
  inline: {
    // inline element
    listitem: {
      'text-indent': '-1em',
      'display': 'block',
      'margin': '0.5em 8px'
    },
    codespan: {
      'font-size': '90%',
      'color': '#d14',
      'background': 'rgba(27,31,35,.05)',
      'padding': '3px 5px',
      'border-radius': '4px',
    },
    link: {
      'color': '#009926'
    },
    wx_link: {
      'color': '#0080ff',
      'text-decoration': 'none',
      'border-bottom': '1px solid #d1e9ff'
    },
    strong: {
      'color': '#ff5f2e',
      'font-weight': 'bold',
    },
    table: {
      'border-collapse': 'collapse',
      'text-align': 'center',
      'margin': '1em 8px'
    },
    thead: {
      'background': 'rgba(0, 0, 0, 0.05)'
    },
    td: {
      'font-size': '80%',
      'border': '1px solid #dfdfdf',
      'padding': '0.25em 0.5em'
    },
    footnote: {
      'font-size': '12px'
    },
    figcaption: {
      'text-align': 'center',
      'color': '#888',
      'font-size': '0.8em'
    }
  }
};


================================================
FILE: src/assets/scripts/themes/lupeng.js
================================================
let lupengTheme = {
  BASE: {
    'text-align': 'left',
    'color': '#595959',
    'line-height': '1.55em',
    'letter-spacing': '0.06em'
  },
  BASE_BLOCK: {
    'margin': '20px 10px'
  },
  block: {
    h1: {
      'font-size': '140%',
      'text-align': 'center',
      'font-weight': 'normal',
      'margin': '80px 10px 40px 10px'
    },
    h2: {
      'font-size': '140%',
      'text-align': 'center',
      'font-weight': 'normal',
      'margin': '80px 10px 40px 10px'
    },
    h3: {
      'font-weight': 'bold',
      'font-size': '120%',
      'margin': '40px 10px 20px 10px'
    },
    h4: {
      'font-weight': 'bold',
      'font-size': '100%',
      'margin': '20px 10px 10px 10px'
    },
    p: {
      'margin': '10px 10px',
      'line-height': '1.6'
    },
    blockquote: {
      'color': '#9a9a9a',
      'padding-left': '10px',
      // 'padding-top': '0.05px',
      'background-color': '#fefefe',
      'line-height': '1.6',
      'border-left': '3px solid #dbdbdb',
      'font-size': '15px',
      'margin': '1em 0'
    },
    code: {
      'font-size': '80%',
      'overflow': 'auto',
      'color': '#333',
      'background': 'rgb(247, 247, 247)',
      'border-radius': '2px',
      'padding': '10px',
      'line-height': '1.3',
      'border': '1px solid rgb(236,236,236)',
      'margin': '20px 0',
    },
    image: {
      'border-radius': '4px',
      'display': 'block',
      'margin': '20px auto',
      'width': '100%',
    },
    image_org: {
      'border-radius': '4px',
      'display': 'block',
    },
    ol: {
      'margin-left': '0',
      'padding-left': '20px'
    },
    ul: {
      'margin-left': '0',
      'padding-left': '20px',
      'list-style': 'circle',
    },
    footnotes: {
      'margin': '10px 10px',
      'font-size': '14px'
    }
  },
  inline: {
    // inline element
    listitem: {
      'text-indent': '-20px',
      'display': 'block',
      'margin': '10px 10px',
    },
    codespan: {
      'font-size': '0.8em',
      'color': '#d14',
      'background': '#fefefe',
      'padding': '3px 5px 0px',
      'margin': '0px 2px',
      'border': '1px solid #ddd',
      'border-radius': '3px',
    },
    link: {
      'color': '#ff3502'
    },
    wx_link: {
      'color': '#576b95',
      'text-decoration': 'none'
    },
    strong: {
      'font-weight': 'bold',
    },
    table: {
      'border-collapse': 'collapse',
      'margin': '20px 0',
    },
    thead: {
      'background': 'rgba(0,0,0,0.05)',
    },
    td: {
      'font-size': '80%',
      'border': '1px solid #dfdfdf',
      'padding': '4px 8px',
    },
    footnote: {
      'font-size': '12px',
    }
  }
};


================================================
FILE: src/assets/scripts/themes/lyric.js
================================================
let lyricTheme = {
  BASE: {
    'text-align': 'left',
    'color': '#3f3f3f',
    'line-height': '1.5'
  },
  BASE_BLOCK: {
    'margin': '20px 10px'
  },
  // block element
  block: {
    h1: {
      'font-size': '140%',
      'text-align': 'center',
      'font-weight': 'normal',
      'margin': '80px 10px 40px 10px'
    },
    h2: {
      'font-size': '140%',
      'text-align': 'center',
      'font-weight': 'normal',
      'margin': '80px 10px 40px 10px'
    },
    h3: {
      'font-weight': 'bold',
      'font-size': '120%',
      'margin': '40px 10px 20px 10px'
    },
    h4: {
      'font-weight': 'bold',
      'font-size': '100%',
      'margin': '20px 10px 10px 10px'
    },
    p: {
      'margin': '10px 10px',
      'line-height': '1.6'
    },
    blockquote: {
      'color': 'rgb(91, 91, 91)',
      'padding': '1px 0 1px 10px',
      'background': 'rgba(158, 158, 158, 0.1)',
      'border-left': '3px solid rgb(158,158,158)',
    },
    code: {
      'font-size': '80%',
      'overflow': 'auto',
      'color': '#333',
      'background': 'rgb(247, 247, 247)',
      'border-radius': '2px',
      'padding': '10px',
      'line-height': '1.3',
      'border': '1px solid rgb(236,236,236)',
      'margin': '20px 0',
    },
    image: {
      'border-radius': '4px',
      'display': 'block',
      'margin': '20px auto',
      'width': '100%',
    },
    image_org: {
      'border-radius': '4px',
      'display': 'block',
    },
    ol: {
      'margin-left': '0',
      'padding-left': '20px'
    },
    ul: {
      'margin-left': '0',
      'padding-left': '20px',
      'list-style': 'circle',
    },
    footnotes: {
      'margin': '10px 10px',
      'font-size': '14px'
    }
  },
  inline: {
    // inline element
    listitem: {
      'text-indent': '-20px',
      'display': 'block',
      'margin': '10px 10px',
    },
    codespan: {
      'font-size': '90%',
      // 'font-family': FONT_FAMILY_MONO,
      'color': '#ff3502',
      'background': '#f8f5ec',
      'padding': '3px 5px',
      'border-radius': '2px',
    },
    link: {
      'color': '#ff3502'
    },
    strong: {
      'color': '#ff3502'
    },
    table: {
      'border-collapse': 'collapse',
      'margin': '20px 0',
    },
    thead: {
      'background': 'rgba(0,0,0,0.05)',
    },
    td: {
      'font-size': '80%',
      'border': '1px solid #dfdfdf',
      'padding': '4px 8px',
    },
    footnote: {
      'font-size': '12px',
    }
  }
};


================================================
FILE: src/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>微信公众号 Markdown 编辑器</title>
  <link rel="shortcut icon" href="assets/images/favicon.png">
  <link rel="apple-touch-icon-precomposed" href="assets/images/favicon.png">

  <link rel="stylesheet" href="assets/css/loading.css">

  <link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.11.1/theme-chalk/index.css">
  <link rel="stylesheet" href="https://cdn.staticfile.org/codemirror/5.48.4/codemirror.min.css">
  <link rel="stylesheet" href="https://cdn.staticfile.org/codemirror/5.48.4/theme/base16-light.min.css">
  <link rel="stylesheet" href="https://cdn.staticfile.org/codemirror/5.48.4/theme/duotone-light.min.css">
  <link rel="stylesheet" href="https://cdn.staticfile.org/codemirror/5.48.4/theme/monokai.min.css">

  <link rel="stylesheet" href="libs/prettify/color-themes/github-v2.min.css">

  <link rel="stylesheet" href="assets/css/app.css">
</head>
<body>
  <!--loading 界面-->
  <div class="loading" id="loading">
    <div class="loading-wrapper">
      <div class="loading-text">Loading...</div>
      <div class="loading-anim"></div>
    </div>
  </div>

  <!--应用主体-->
  <div id="app" class="container">
    <el-container>
      <el-header class="top">
        <div><img src="assets/images/favicon.png" class="web-icon" alt="icon"> <span
            class="web-title">公众号 Markdown 编辑器 </span></div>
        <el-form size="mini" class="ctrl" :inline=true>
          <el-form-item label="Editor Themes">
            <el-select v-model="currentEditorTheme" size="mini" placeholder="选择字体" @change="editorThemeChanged">
              <el-option v-for="editorTheme in editorThemes" :key="editorTheme.value" :label="editorTheme.label"
                         :value="editorTheme.value">
              </el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="Fonts">
            <el-select v-model="currentFont" size="mini" placeholder="选择字体" @change="fontChanged">
              <el-option v-for="font in builtinFonts" :style="{fontFamily: font.value}"
                         :key="font.value"
                         :label="font.label"
                         :value="font.value">
                <span class="select-item-left">{{ font.label }}</span>
                <span class="select-item-right">Abc</span>
              </el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="Font Size">
            <el-select v-model="currentSize" size="mini" placeholder="选择段落字体大小" @change="sizeChanged">
              <el-option v-for="size in sizeOption"
                         :key="size.value"
                         :label="size.label"
                         :value="size.value">
                <span class="select-item-left">{{ size.label }}</span>
                <span class="select-item-right">{{ size.desc }}</span>
              </el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="Themes">
            <el-select v-model="currentTheme" size="mini" placeholder="选择主题样式" @change="themeChanged">
              <el-option v-for="theme in themeOption" :key="theme.value" :label="theme.label" :value="theme.value">
                <span class="select-item-left">{{ theme.label }}</span>
                <span class="select-item-right">{{ theme.author }}</span>
              </el-option>
            </el-select>
          </el-form-item>
        </el-form>
        <el-button class="about" @click="aboutDialogVisible = true">关于</el-button>
      </el-header>
      <el-main class="main-body">
        <el-row :gutter="10" class="main-section">
          <el-col :span="12">
              <textarea
                  id="editor"
                  type="textarea"
                  placeholder="Your markdown here."
                  v-model="source">
              </textarea>
          </el-col>
          <el-col :span="12" class="preview-wrapper" id="preview">
            <section>
              <div class="hint">全选复制或<a href="#" @click="copy" class="copy-button">点此复制</a>,然后在公众号编辑器粘贴</div>
              <div class="preview" contenteditable="true">
                <div id="output" v-html="output">
                </div>
              </div>
            </section>
          </el-col>
        </el-row>
      </el-main>
    </el-container>

    <el-dialog title="关于" :visible.sync="aboutDialogVisible" width="30%" center>
      <div>
        <p>一款可以将 Markdown 转换为微信公众号文章的在线编辑器,</p>
        <p>这让你在公众号创作时,摆脱繁琐地排版样式,</p>
        <p>可以把更多的时间专注于文章本身。</p>
        <p>除了常规 Markdown 格式化,还增加了外链引用、注音样式等。</p>
      </div>
      <div style="text-align: center;">
        <img src="https://static.zkqiang.cn/images/20191019181436.JPG-slim" style="max-width: 300px">
      </div>
      <span slot="footer" class="dialog-footer">
          <el-button type="primary"
                     @click="openWindow('https://github.com/zkqiang/wechat-format')">查看 GitHub 仓库</el-button>
        </span>
    </el-dialog>
  </div>

  <script src="https://cdn.staticfile.org/vue/2.6.10/vue.min.js"></script>
  <script src="https://cdn.staticfile.org/axios/0.19.0-beta.1/axios.min.js"></script>
  <script src="https://cdn.staticfile.org/marked/0.7.0/marked.min.js"></script>
  <script src="https://cdn.staticfile.org/codemirror/5.48.4/codemirror.min.js"></script>
  <script src="https://cdn.staticfile.org/codemirror/5.48.4/mode/markdown/markdown.min.js"></script>
  <script src="https://cdn.staticfile.org/prettify/r298/prettify.min.js"></script>
  <script src="https://cdn.staticfile.org/element-ui/2.11.1/index.js"></script>
  <script src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js"></script>

  <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

  <script src="libs/sync-scroll.js"></script>
  <script src="libs/FuriganaMD.js"></script>

  <script src="assets/scripts/themes/default.js"></script>
  <script src="assets/scripts/themes/lyric.js"></script>
  <script src="assets/scripts/themes/lupeng.js"></script>
  <script src="assets/scripts/renderers/wx-renderer.js"></script>
  <script src="assets/scripts/editor.js"></script>
  <script src="assets/scripts/loading.js"></script>

</body>
</html>


================================================
FILE: src/libs/FuriganaMD.js
================================================
// 注音功能来自于
// https://github.com/amclees/furigana-markdown
// 详见上述文档

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
      (global.FuriganaMD = factory());
}(this, (function () {
  'use strict';

// This function escapes special characters for use in a regex constructor.
  function escapeForRegex(string) {
    return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
  }

  function emptyStringFilter(block) {
    return block !== '';
  }

  const kanjiRange = '\\u4e00-\\u9faf';
  const kanjiBlockRegex = new RegExp(`[${kanjiRange}]+`, 'g');
  const nonKanjiBlockRegex = new RegExp(`[^${kanjiRange}]+`, 'g');
  const kanaWithAnnotations = '\\u3041-\\u3095\\u3099-\\u309c\\u3081-\\u30fa\\u30fc';
  const furiganaSeperators = '..。・';
  const seperatorRegex = new RegExp(`[${furiganaSeperators}]`, 'g');

  const singleKanjiRegex = new RegExp(`^[${kanjiRange}]$`);

  function isKanji(character) {
    return character.match(singleKanjiRegex);
  }

  const innerRegexString = '(?:[^\\u0000-\\u007F]|\\w)+';

  let regexList = [];
  let previousFuriganaForms = '';

  function updateRegexList(furiganaForms) {
    previousFuriganaForms = furiganaForms;
    let formArray = furiganaForms.split('|');
    if (formArray.length === 0) {
      formArray = ['[]:^:()'];
    }
    regexList = formArray.map(form => {
      let furiganaComponents = form.split(':');
      if (furiganaComponents.length !== 3) {
        furiganaComponents = ['[]', '^', '()'];
      }
      const mainBrackets = furiganaComponents[0];
      const seperator = furiganaComponents[1];
      const furiganaBrackets = furiganaComponents[2];
      return new RegExp(
        escapeForRegex(mainBrackets[0]) +
        '(' + innerRegexString + ')' +
        escapeForRegex(mainBrackets[1]) +
        escapeForRegex(seperator) +
        escapeForRegex(furiganaBrackets[0]) +
        '(' + innerRegexString + ')' +
        escapeForRegex(furiganaBrackets[1]),
        'g'
      );
    });
  }

  let autoRegexList = [];
  let previousAutoBracketSets = '';

  function updateAutoRegexList(autoBracketSets) {
    previousAutoBracketSets = autoBracketSets;
    autoRegexList = autoBracketSets.split('|').map(brackets => {
      /*
        Sample built regex:
        /(^|[^\u4e00-\u9faf]|)([\u4e00-\u9faf]+)([\u3041-\u3095\u3099-\u309c\u3081-\u30fa\u30fc]*)【((?:[^【】\u4e00-\u9faf]|w)+)】/g
      */
      return new RegExp(
        `(^|[^${kanjiRange}]|)` +
        `([${kanjiRange}]+)` +
        `([${kanaWithAnnotations}]*)` +
        escapeForRegex(brackets[0]) +
        `((?:[^${escapeForRegex(brackets)}\\u0000-\\u007F]|\\w|[${furiganaSeperators}])+)` +
        escapeForRegex(brackets[1]),
        'g'
      );
    });
  }

  let replacementTemplate = '';
  let replacementBrackets = '';

  function updateReplacementTemplate(furiganaFallbackBrackets) {
    if (furiganaFallbackBrackets.length !== 2) {
      furiganaFallbackBrackets = '【】';
    }
    replacementBrackets = furiganaFallbackBrackets;
    replacementTemplate = `<ruby>$1<rp>${furiganaFallbackBrackets[0]}</rp><rt style="line-height:1;font-size:10px;">$2</rt><rp>${furiganaFallbackBrackets[1]}</rp></ruby>`;
  }

  updateReplacementTemplate('【】');

  function addFurigana(text, options) {
    if (options.furiganaForms !== previousFuriganaForms) {
      updateRegexList(options.furiganaForms);
    }
    if (options.furiganaFallbackBrackets !== replacementBrackets) {
      updateReplacementTemplate(options.furiganaFallbackBrackets);
    }
    regexList.forEach(regex => {
      text = text.replace(regex, (match, wordText, furiganaText, offset, mainText) => {
        if (match.indexOf('\\') === -1 && mainText[offset - 1] !== '\\') {
          if ((!options.furiganaPatternMatching) || wordText.search(kanjiBlockRegex) === -1 || wordText[0].search(kanjiBlockRegex) === -1) {
            return replacementTemplate.replace('$1', wordText).replace('$2', furiganaText);
          } else {
            let originalFuriganaText = (' ' + furiganaText).slice(1);
            let nonKanji = wordText.split(kanjiBlockRegex).filter(emptyStringFilter);
            let kanji = wordText.split(nonKanjiBlockRegex).filter(emptyStringFilter);
            let replacementText = '';
            let lastUsedKanjiIndex = 0;
            if (nonKanji.length === 0) {
              return replacementTemplate.replace('$1', wordText).replace('$2', furiganaText);
            }

            nonKanji.forEach((currentNonKanji, index) => {
              if (furiganaText === undefined) {
                if (index < kanji.length) {
                  replacementText += kanji[index];
                }

                replacementText += currentNonKanji;
                return;
              }
              let splitFurigana = furiganaText.split(new RegExp(escapeForRegex(currentNonKanji) + '(.*)')).filter(emptyStringFilter);

              lastUsedKanjiIndex = index;
              replacementText += replacementTemplate.replace('$1', kanji[index]).replace('$2', splitFurigana[0]);
              replacementText += currentNonKanji;

              furiganaText = splitFurigana[1];
            });
            if (furiganaText !== undefined && lastUsedKanjiIndex + 1 < kanji.length) {
              replacementText += replacementTemplate.replace('$1', kanji[lastUsedKanjiIndex + 1]).replace('$2', furiganaText);
            } else if (furiganaText !== undefined) {
              return replacementTemplate.replace('$1', wordText).replace('$2', originalFuriganaText);
            } else if (lastUsedKanjiIndex + 1 < kanji.length) {
              replacementText += kanji[lastUsedKanjiIndex + 1];
            }
            return replacementText;
          }
        } else {
          return match;
        }
      });
    });

    if (!options.furiganaStrictMode) {
      if (options.furiganaAutoBracketSets !== previousAutoBracketSets) {
        updateAutoRegexList(options.furiganaAutoBracketSets);
      }
      autoRegexList.forEach(regex => {
        text = text.replace(regex, (match, preWordTerminator, wordKanji, wordKanaSuffix, furiganaText, offset, mainText) => {
          if (match.indexOf('\\') === -1) {
            if (options.furiganaPatternMatching) {
              let rubies = [];

              let furigana = furiganaText;

              let stem = (' ' + wordKanaSuffix).slice(1);
              for (let i = furiganaText.length - 1; i >= 0; i--) {
                if (wordKanaSuffix.length === 0) {
                  furigana = furiganaText.substring(0, i + 1);
                  break;
                }
                if (furiganaText[i] !== wordKanaSuffix.slice(-1)) {
                  furigana = furiganaText.substring(0, i + 1);
                  break;
                }
                wordKanaSuffix = wordKanaSuffix.slice(0, -1);
              }

              if (furiganaSeperators.split('').reduce(
                (noSeperator, seperator) => {
                  return noSeperator && (furigana.indexOf(seperator) === -1);
                },
                true
              )) {
                rubies = [replacementTemplate.replace('$1', wordKanji).replace('$2', furigana)];
              } else {
                let kanaParts = furigana.split(seperatorRegex);
                let kanji = wordKanji.split('');
                if (kanaParts.length === 0 || kanaParts.length > kanji.length) {
                  rubies = [replacementTemplate.replace('$1', wordKanji).replace('$2', furigana)];
                } else {
                  for (let i = 0; i < kanaParts.length - 1; i++) {
                    if (kanji.length === 0) {
                      break;
                    }
                    rubies.push(replacementTemplate.replace('$1', kanji.shift()).replace('$2', kanaParts[i]));
                  }
                  let lastKanaPart = kanaParts.pop();
                  rubies.push(replacementTemplate.replace('$1', kanji.join('')).replace('$2', lastKanaPart));
                }
              }

              return preWordTerminator + rubies.join('') + stem;
            } else {
              return preWordTerminator + replacementTemplate.replace('$1', wordKanji).replace('$2', furiganaText) + wordKanaSuffix;
            }
          } else {
            return match;
          }
        });
      });
    }
    return text;
  }

  function handleEscapedSpecialBrackets(text) {
    // By default 【 and 】 cannot be escaped in markdown, this will remove backslashes from in front of them to give that effect.
    return text.replace(/\\([【】])/g, '$1');
  }

  let FuriganaMD = {};
  FuriganaMD.register = function (renderer) {
    renderer.text = function (text) {
      let options = {
        furigana: true,
        furiganaForms: "()::{}",
        furiganaFallbackBrackets: "{}",
        furiganaStrictMode: false,
        furiganaAutoBracketSets: "{}",
        furiganaPatternMatching: true,
      };
      // console.log('override text render',text);
      // console.log('after add',addFurigana(text, options));
      return handleEscapedSpecialBrackets(addFurigana(text, options));
    };
  };

  return FuriganaMD;

})));


================================================
FILE: src/libs/sync-scroll.js
================================================
// 左右栏同步滚动

$(document).ready(function () {

  let timeout;

  $('div.CodeMirror-scroll, #preview').on("scroll", function callback() {
    clearTimeout(timeout);

    let source = $(this),
      target = $(source.is("#preview") ? 'div.CodeMirror-scroll' : '#preview');

    target.off("scroll");

    let source0 = source[0];
    let target0 = target[0];

    let percentage = source0.scrollTop / (source0.scrollHeight - source0.offsetHeight);
    let height = percentage * (target0.scrollHeight - target0.offsetHeight);
    target0.scrollTo(0, height);

    timeout = setTimeout(function () {
      target.on("scroll", callback);
    }, 100);
  });

});
Download .txt
gitextract_hh_tb0ei/

├── .gitignore
├── README.md
└── src/
    ├── assets/
    │   ├── css/
    │   │   ├── app.css
    │   │   └── loading.css
    │   ├── default-content.md
    │   └── scripts/
    │       ├── editor.js
    │       ├── loading.js
    │       ├── renderers/
    │       │   └── wx-renderer.js
    │       └── themes/
    │           ├── default.js
    │           ├── lupeng.js
    │           └── lyric.js
    ├── index.html
    └── libs/
        ├── FuriganaMD.js
        └── sync-scroll.js
Download .txt
SYMBOL INDEX (9 symbols across 2 files)

FILE: src/assets/scripts/editor.js
  method mounted (line 48) | mounted() {

FILE: src/libs/FuriganaMD.js
  function escapeForRegex (line 13) | function escapeForRegex(string) {
  function emptyStringFilter (line 17) | function emptyStringFilter(block) {
  function isKanji (line 30) | function isKanji(character) {
  function updateRegexList (line 39) | function updateRegexList(furiganaForms) {
  function updateAutoRegexList (line 69) | function updateAutoRegexList(autoBracketSets) {
  function updateReplacementTemplate (line 91) | function updateReplacementTemplate(furiganaFallbackBrackets) {
  function addFurigana (line 101) | function addFurigana(text, options) {
  function handleEscapedSpecialBrackets (line 217) | function handleEscapedSpecialBrackets(text) {
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (51K chars).
[
  {
    "path": ".gitignore",
    "chars": 2170,
    "preview": "# Created by .ignore support plugin (hsz.mobi)\n### Java template\n# Compiled class file\n*.class\n\n# Log file\n*.log\n\n# Blue"
  },
  {
    "path": "README.md",
    "chars": 369,
    "preview": "# 微信公众号 Markdown 编辑器\n\n这款编辑器可以将 Markdown 转换成微信公众号编辑器的样式,可以直接复制到公众号后台。\n\n这让你在公众号创作时,把更多的时间专注于文章本身,而不是繁琐地调整文章样式。\n\n[在线使用](htt"
  },
  {
    "path": "src/assets/css/app.css",
    "chars": 3301,
    "preview": "* {\n  box-sizing: border-box;\n  margin: 0;\n  padding: 0;\n}\n\ninput, button, textarea {\n  font-family: inherit;\n}\n\nh1, h2,"
  },
  {
    "path": "src/assets/css/loading.css",
    "chars": 866,
    "preview": ".loading {\n  text-align: center;\n  position: fixed;\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n  z-index: 99999;"
  },
  {
    "path": "src/assets/default-content.md",
    "chars": 1917,
    "preview": "# 公众号 Markdown 编辑器\n\n### 简介\n\n这款编辑器可以将 Markdown 转换成微信公众号编辑器的样式,只需将 MD 文档复制到左侧栏,再在右侧栏顶部\"点击复制\",右侧预览内容就可被复制到公众号后台。\n\n这让你在公众号创作"
  },
  {
    "path": "src/assets/scripts/editor.js",
    "chars": 4796,
    "preview": "let app = new Vue({\n  el: '#app',\n  data: function () {\n    let d = {\n      aboutOutput: '',\n      output: '',\n      sou"
  },
  {
    "path": "src/assets/scripts/loading.js",
    "chars": 72,
    "preview": "// 加载完成隐藏 loading 界面\nwindow.onload = () => {\n  $('#loading').hide();\n};\n"
  },
  {
    "path": "src/assets/scripts/renderers/wx-renderer.js",
    "chars": 7009,
    "preview": "let WxRenderer = function (opts) {\n  this.opts = opts;\n  let ENV_USE_REFERENCES = true;\n  let ENV_STRETCH_IMAGE = true;\n"
  },
  {
    "path": "src/assets/scripts/themes/default.js",
    "chars": 3408,
    "preview": "let defaultTheme = {\n  BASE: {\n    'text-align': 'left',\n    'color': '#3f3f3f',\n    'line-height': '1.75',\n  },\n  BASE_"
  },
  {
    "path": "src/assets/scripts/themes/lupeng.js",
    "chars": 2664,
    "preview": "let lupengTheme = {\n  BASE: {\n    'text-align': 'left',\n    'color': '#595959',\n    'line-height': '1.55em',\n    'letter"
  },
  {
    "path": "src/assets/scripts/themes/lyric.js",
    "chars": 2461,
    "preview": "let lyricTheme = {\n  BASE: {\n    'text-align': 'left',\n    'color': '#3f3f3f',\n    'line-height': '1.5'\n  },\n  BASE_BLOC"
  },
  {
    "path": "src/index.html",
    "chars": 6381,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, in"
  },
  {
    "path": "src/libs/FuriganaMD.js",
    "chars": 9241,
    "preview": "// 注音功能来自于\n// https://github.com/amclees/furigana-markdown\n// 详见上述文档\n\n(function (global, factory) {\n  typeof exports ==="
  },
  {
    "path": "src/libs/sync-scroll.js",
    "chars": 655,
    "preview": "// 左右栏同步滚动\n\n$(document).ready(function () {\n\n  let timeout;\n\n  $('div.CodeMirror-scroll, #preview').on(\"scroll\", functio"
  }
]

About this extraction

This page contains the full source code of the zkqiang/wechat-mdeditor GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (44.2 KB), approximately 13.9k tokens, and a symbol index with 9 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!