Van 6 anos atrás
pai
commit
5b9063d0a9

+ 2 - 1
CHANGELOG.md

@@ -34,8 +34,9 @@
 * [121](https://github.com/b3log/vditor/issues/121) 所见即所得 `feature`
 * [161](https://github.com/b3log/vditor/issues/161) 在工具栏上添加语音转文字功能 `feature`
 
-### v1.9.5 / 2019-11-08
+### v1.9.6 / 2019-11-09
 
+* [163](https://github.com/b3log/vditor/issues/163) 为标题添加锚点 `feature`
 * [162](https://github.com/b3log/vditor/issues/162) 没有使用后端渲染时,编辑器卡顿 `bug`
 * [160](https://github.com/b3log/vditor/issues/160) 添加 `speechRender` 方法 `feature`
 * [159](https://github.com/b3log/vditor/issues/159) Vditor.preview方法在页面中渲染textarea内markdown代码无效 `invalid`

+ 2 - 1
demo/index-preview.js

@@ -3,7 +3,7 @@ import '../src/assets/scss/classic.scss'
 
 VditorPreview.preview(document.getElementById('preview'),
   document.getElementById('markdownText').textContent, {
-    className: 'preview vditor-reset',
+    className: 'preview vditor-reset vditor-reset--anchor',
     customEmoji: {
       'sd': '💔',
       'j': 'https://unpkg.com/[email protected]/dist/images/emoji/j.png',
@@ -11,4 +11,5 @@ VditorPreview.preview(document.getElementById('preview'),
     speech: {
       enable: true,
     },
+    anchor: true
   })

+ 2 - 1
demo/static-preview.html

@@ -326,7 +326,7 @@ https://v.qq.com/x/cover/zf2z0xpqcculhcz/y0016tj0qvh.html
 <script>
   Vditor.preview(document.getElementById('preview'),
     document.getElementById('markdownText').textContent, {
-      className: 'preview vditor-reset',
+      className: 'preview vditor-reset vditor-reset--anchor',
       customEmoji: {
         'sd': '💔',
         'j': 'https://unpkg.com/[email protected]/dist/images/emoji/j.png',
@@ -334,6 +334,7 @@ https://v.qq.com/x/cover/zf2z0xpqcculhcz/y0016tj0qvh.html
       speech: {
         enable: true,
       },
+      anchor: true
     })
 </script>
 </body>

+ 28 - 0
src/assets/scss/_reset.scss

@@ -1,5 +1,9 @@
 .vditor {
   &-reset {
+    &--anchor {
+      padding-left: 20px;
+    }
+
     font-variant-ligatures: no-common-ligatures;
     font-family: $font-family-base;
     word-wrap: break-word;
@@ -64,6 +68,10 @@
       margin-bottom: 16px;
       font-weight: 600;
       line-height: 1.25;
+
+      &:hover .vditor-anchor svg {
+        visibility: visible;
+      }
     }
 
     h1 {
@@ -332,6 +340,26 @@
     }
   }
 
+  &-anchor {
+    float: left;
+    padding-right: 4px;
+    margin-left: -20px;
+
+    svg {
+      visibility: hidden;
+    }
+
+    &:hover {
+      svg {
+        visibility: visible;
+      }
+    }
+
+    &:focus {
+      outline: none;
+    }
+  }
+
   &--error {
     color: $errorColor;
     font-size: 12px;

+ 16 - 0
src/ts/markdown/anchorRender.ts

@@ -0,0 +1,16 @@
+export const anchorRender = () => {
+    document.querySelectorAll('.vditor-anchor').forEach((anchor: HTMLLinkElement) => {
+        anchor.onclick = () => {
+            const id = anchor.getAttribute('href').substr(1)
+            const top = document.getElementById('vditorAnchor-' + id).offsetTop
+            document.querySelector("html").scrollTop = top
+        }
+    })
+
+    window.onhashchange = () => {
+        const element = document.getElementById('vditorAnchor-' + decodeURIComponent(window.location.hash.substr(1)))
+        if (element) {
+            document.querySelector("html").scrollTop = element.offsetTop
+        }
+    }
+};

+ 3 - 3
src/ts/markdown/md2html.ts

@@ -5,9 +5,8 @@ declare const Lute: ILute;
 export const loadLuteJs = (vditor?: IVditor) => {
     const scriptElement = document.createElement("script");
     scriptElement.type = "text/javascript";
-    scriptElement.src = `${CDN_PATH}/vditor@${VDITOR_VERSION}/dist/js/lute/lute.min.js`;
-    // scriptElement.src = `http://192.168.0.107:9090/lute.min.js?${new Date().getTime()}`;
-    // scriptElement.src = `js/lute/lute.min1.js?${new Date().getTime()}`;
+    // scriptElement.src = `${CDN_PATH}/vditor@${VDITOR_VERSION}/dist/js/lute/lute.min.js`;
+    scriptElement.src = `http://192.168.0.107:9090/lute.min.js?${new Date().getTime()}`;
     document.getElementsByTagName("head")[0].appendChild(scriptElement);
 
     return new Promise((resolve) => {
@@ -34,6 +33,7 @@ export const md2htmlByPreview = async (mdText: string, options?: IPreviewOptions
     const lute: ILute = Lute.New();
     lute.PutEmojis(options.customEmoji);
     lute.SetEmojiSite(options.emojiPath);
+    lute.SetHeadingAnchor(options.anchor);
     const md = lute.MarkdownStr("", mdText);
 
     return md[0] || md[1];

+ 6 - 1
src/ts/markdown/previewRender.ts

@@ -8,10 +8,11 @@ import {md2htmlByPreview} from "./md2html";
 import {mediaRender} from "./mediaRender";
 import {mermaidRender} from "./mermaidRender";
 import {speechRender} from "./speechRender";
+import {anchorRender} from "./anchorRender";
 
 export const previewRender = async (previewElement: HTMLDivElement, markdown: string, options?: IPreviewOptions) => {
     const defaultOption = {
-        className: "vditor-reset",
+        className: options.anchor ? "vditor-reset vditor-reset--anchor" : "vditor-reset",
         customEmoji: {},
         emojiPath: `${CDN_PATH}/vditor/dist/images/emoji`,
         hljs: {
@@ -23,6 +24,7 @@ export const previewRender = async (previewElement: HTMLDivElement, markdown: st
         speech: {
             enable: false,
         },
+        anchor: false,
     };
     options = Object.assign(defaultOption, options);
     if (options.hljs) {
@@ -48,4 +50,7 @@ export const previewRender = async (previewElement: HTMLDivElement, markdown: st
     if (options.speech.enable) {
         speechRender(previewElement,  options.lang);
     }
+    if (options.anchor) {
+        anchorRender()
+    }
 };

+ 14 - 14
src/ts/types/index.d.ts

@@ -28,6 +28,8 @@ interface ILute {
 
     SetEmojiSite(emojiSite: string): void;
 
+    SetHeadingAnchor(enable: boolean): void;
+
     PutEmojis(emojis: { [key: string]: string }): void;
 
     MarkdownStr(error: string, text: string): string[];
@@ -41,8 +43,6 @@ interface ILute {
     RenderVditorDOM(text: string, start: number, end: number): string[];
 
     VditorOperation(text: string, startOffset: number, endOffset: number, operation: string): string[];
-
-    // SpinVditorDOM(html: string): string[];
 }
 
 declare var webkitAudioContext: {
@@ -120,10 +120,21 @@ interface IPreview {
     mode?: keyof IPreviewMode;
     url?: string;
     hljs?: IHljs;
-
     parse?(element: HTMLElement): void;
 }
 
+interface IPreviewOptions {
+    className?: string;
+    customEmoji?: { [key: string]: string };
+    lang?: (keyof II18nLang);
+    emojiPath?: string;
+    hljs?: IHljs;
+    speech?: {
+        enable?: boolean,
+    };
+    anchor?:boolean;
+}
+
 interface IHintData {
     html: string;
     value: string;
@@ -145,17 +156,6 @@ interface IResize {
     after?(height: number): void;
 }
 
-interface IPreviewOptions {
-    className?: string;
-    customEmoji?: { [key: string]: string };
-    lang?: (keyof II18nLang);
-    emojiPath?: string;
-    hljs?: IHljs;
-    speech?: {
-        enable?: boolean,
-    };
-}
-
 interface IOptions {
     after?: () => void;
     typewriterMode?: boolean;