diff --git a/App.vue b/App.vue index 451c12a..43fc6ac 100755 --- a/App.vue +++ b/App.vue @@ -6,7 +6,7 @@ @on-copy="onCopy" @on-upload-image="onUpladImage" @on-save="onSave" - :height="500" + :height="fullHeight" /> @@ -21,11 +21,33 @@ components: { Markdown }, - data: function () { - return { - val: '' + mounted() { + const that = this + window.onresize = () => { + return (() => { + window.fullHeight = document.documentElement.clientHeight + that.fullHeight = window.fullHeight + })() } }, + data: function () { + return { + val: '', + fullHeight: document.documentElement.clientHeight + } + }, + watch: { + fullHeight(val) { + if (!this.timer) { + this.fullHeight = val + this.timer = true + let that = this + setTimeout(function() { + that.timer = false + }, 400) + } + } + }, methods: { onReady(data) { console.log(data) diff --git a/README.md b/README.md index a677da3..950daab 100755 --- a/README.md +++ b/README.md @@ -342,7 +342,15 @@ export default index.setOptions({ 预览区域和文档预览组件暂不支持自动生成目录,实现自动生成目录思路目前想到的大致有 - 重写`renderer.heading` 方法,为生成的标题添加id,输入特定快捷键,如`[TOC]`时,查找预览区域内的的所有标题标签,分析等级关系,生成目录标签 - +属性 | 说明| 类型| 默认值 +-|-|-|- +affix |固定模式| Boolean |true +offset-top |距离窗口顶部达到指定偏移量后触发 |Number| 0 +offset-bottom |距离窗口底部达到指定偏移量后触发| Number| - +bounds| 锚点区域边界,单位:px| Number| 5 +scroll-offset| 点击滚动的额外距离| Number| 0 +container| 指定滚动的容器 |String | HTMLElement| - +show-ink| 是否显示小圆点| Boolean |false ### icon替换 项目内所有的icon和命名参考`/assets/font/index.html`,替换时需注意,预览区域的checkbox为icon,注意一并替换, 修改`/assets/css/index.less`内的`input[type="checkbox"]`的`:after`样式。 diff --git a/img/截图.jpg b/img/截图.jpg new file mode 100644 index 0000000..0fcec28 Binary files /dev/null and b/img/截图.jpg differ diff --git a/src/assets/css/anchor.less b/src/assets/css/anchor.less new file mode 100644 index 0000000..40e10a6 --- /dev/null +++ b/src/assets/css/anchor.less @@ -0,0 +1,81 @@ +// Anchor +@primary-color : #2d8cf0; +@anchor-border-width: 2px; +@border-color-split : #e8eaec; // inside +@body-background : #fff; +@transition-time : .2s; +@text-color : #515a6e; +.anchor{ + &-wrapper{ + overflow: auto; + padding-left: 4px; + margin-left: -4px; + } + + &{ + position: relative; + padding-left: @anchor-border-width; + + &-ink { + position: absolute; + height: 100%; + left: 0; + top: 0; + &:before { + content: ' '; + position: relative; + width: @anchor-border-width; + height: 100%; + display: block; + background-color: @border-color-split; + margin: 0 auto; + } + &-ball { + display: inline-block; + position: absolute; + width: 8px; + height: 8px; + border-radius: 50%; + border: 2px solid @primary-color; + background-color: @body-background; + left: 50%; + transition: top @transition-time ease-in-out; + transform: translate(-50%, 2px); + } + } + + &.fixed &-ink &-ink-ball { + display: none; + } + } + + &-link { + padding: 8px 0 6px 16px !important; + line-height: 1; + + &-title { + font-size: 12px; + text-decoration:none; + display: block; + position: relative; + transition: all .3s; + color: @text-color; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 8px; + &:only-child { + margin-bottom: 0; + } + } + + &-active > &-title { + color: @primary-color; + } + } + + &-link &-link { + padding-top: 6px; + padding-bottom: 6px; + } +} diff --git a/src/assets/css/index.less b/src/assets/css/index.less index 1e22a4a..0c2ea8e 100644 --- a/src/assets/css/index.less +++ b/src/assets/css/index.less @@ -1,3 +1,4 @@ +@import './anchor.less'; @margin: 8px 0; @line-height: 22px; diff --git a/src/assets/js/marked/createToc.js b/src/assets/js/marked/createToc.js new file mode 100644 index 0000000..d32f069 --- /dev/null +++ b/src/assets/js/marked/createToc.js @@ -0,0 +1,19 @@ +export default { + add(text, level) { + const anchor = `toc${level}${++this.index}`; + const item = { anchor, level, text }; + const items = this.tocItems; + + if (item.level <= 3) { + items.push(item); + } + + return anchor; + }, + reset: function() { + this.tocItems = []; + this.index = 0; + }, + tocItems: [], + index: 0 +}; diff --git a/src/assets/js/marked/index.js b/src/assets/js/marked/index.js index ff90515..1be469c 100644 --- a/src/assets/js/marked/index.js +++ b/src/assets/js/marked/index.js @@ -1,3 +1,4 @@ +import tocObj from './createToc'; var block = { newline: /^\n+/, code: /^( {4}[^\n]+\n*)+/, @@ -947,20 +948,22 @@ Renderer.prototype.html = function (html) { }; Renderer.prototype.heading = function (text, level, raw, slugger) { - if (this.options.headerIds) { - return '' - + text - + '\n'; - } - // ignore IDs - return '' + text + '\n'; + // if (this.options.headerIds) { + // return '' + // + text + // + '\n'; + // } + // // ignore IDs + // return '' + text + '\n'; + let anchor = tocObj.add(text, level); + return `${text}\n`; }; Renderer.prototype.hr = function () { diff --git a/src/components/Anchor/affix.vue b/src/components/Anchor/affix.vue new file mode 100644 index 0000000..5fc9702 --- /dev/null +++ b/src/components/Anchor/affix.vue @@ -0,0 +1,155 @@ + + + \ No newline at end of file diff --git a/src/components/Anchor/anchor.less b/src/components/Anchor/anchor.less new file mode 100644 index 0000000..04d374e --- /dev/null +++ b/src/components/Anchor/anchor.less @@ -0,0 +1,81 @@ +// Anchor +@primary-color : #2d8cf0; +@anchor-border-width: 2px; +@border-color-split : #e8eaec; // inside +@body-background : #fff; +@transition-time : .2s; +@text-color : #515a6e; +.anchor{ + &-wrapper{ + overflow: auto; + padding-left: 4px; + margin-left: -4px; + } + + &{ + position: relative; + padding-left: @anchor-border-width; + + &-ink { + position: absolute; + height: 100%; + left: 0; + top: 0; + &:before { + content: ' '; + position: relative; + width: @anchor-border-width; + height: 100%; + display: block; + background-color: @border-color-split; + margin: 0 auto; + } + &-ball { + display: inline-block; + position: absolute; + width: 8px; + height: 8px; + border-radius: 50%; + border: 2px solid @primary-color; + background-color: @body-background; + left: 50%; + transition: top @transition-time ease-in-out; + transform: translate(-50%, 2px); + } + } + + &.fixed &-ink &-ink-ball { + display: none; + } + } + + &-link { + padding: 8px 0 6px 16px; + line-height: 1; + + &-title { + font-size: 12px; + text-decoration:none; + display: block; + position: relative; + transition: all .3s; + color: @text-color; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 8px; + &:only-child { + margin-bottom: 0; + } + } + + &-active > &-title { + color: @primary-color; + } + } + + &-link &-link { + padding-top: 6px; + padding-bottom: 6px; + } +} diff --git a/src/components/Anchor/anchor.vue b/src/components/Anchor/anchor.vue new file mode 100644 index 0000000..e8a4c41 --- /dev/null +++ b/src/components/Anchor/anchor.vue @@ -0,0 +1,207 @@ + + + diff --git a/src/components/Anchor/anchorLink.vue b/src/components/Anchor/anchorLink.vue new file mode 100644 index 0000000..08ba847 --- /dev/null +++ b/src/components/Anchor/anchorLink.vue @@ -0,0 +1,62 @@ + + + \ No newline at end of file diff --git a/src/components/Anchor/anchorLinks.vue b/src/components/Anchor/anchorLinks.vue new file mode 100644 index 0000000..c7cf80a --- /dev/null +++ b/src/components/Anchor/anchorLinks.vue @@ -0,0 +1,36 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/Anchor/util.js b/src/components/Anchor/util.js new file mode 100644 index 0000000..97a0f91 --- /dev/null +++ b/src/components/Anchor/util.js @@ -0,0 +1,92 @@ +import Vue from "vue"; +const isServer = Vue.prototype.$isServer; +// Find components downward + +export function findComponentsDownward(context, componentName) { + return context.$children.reduce((components, child) => { + if (child.$options.name === componentName) components.push(child); + const foundChilds = findComponentsDownward(child, componentName); + return components.concat(foundChilds); + }, []); +} + +// scrollTop animation +export function scrollTop(el, from = 0, to, duration = 500, endCallback) { + if (!window.requestAnimationFrame) { + window.requestAnimationFrame = + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(callback, 1000 / 60); + }; + } + const difference = Math.abs(from - to); + const step = Math.ceil((difference / duration) * 50); + + function scroll(start, end, step) { + if (start === end) { + endCallback && endCallback(); + return; + } + + let d = start + step > end ? end : start + step; + if (start > end) { + d = start - step < end ? end : start - step; + } + + if (el === window) { + window.scrollTo(d, d); + } else { + el.scrollTop = d; + } + window.requestAnimationFrame(() => scroll(d, end, step)); + } + scroll(from, to, step); +} + +export const sharpMatcherRegx = /#([^#]+)$/; + +export const dimensionMap = { + xs: "480px", + sm: "576px", + md: "768px", + lg: "992px", + xl: "1200px", + xxl: "1600px" +}; + + +/* istanbul ignore next */ +export const on = (function() { + if (!isServer && document.addEventListener) { + return function(element, event, handler, useCapture = false) { + if (element && event && handler) { + element.addEventListener(event, handler, useCapture); + } + }; + } else { + return function(element, event, handler) { + if (element && event && handler) { + element.attachEvent("on" + event, handler); + } + }; + } +})(); + +/* istanbul ignore next */ +export const off = (function() { + if (!isServer && document.removeEventListener) { + return function(element, event, handler, useCapture = false) { + if (element && event) { + element.removeEventListener(event, handler, useCapture); + } + }; + } else { + return function(element, event, handler) { + if (element && event) { + element.detachEvent("on" + event, handler); + } + }; + } +})(); diff --git a/src/components/pro/index.vue b/src/components/pro/index.vue index c74ea36..8c40f00 100644 --- a/src/components/pro/index.vue +++ b/src/components/pro/index.vue @@ -193,6 +193,12 @@ >
+ + + +
diff --git a/src/components/pro/pro.js b/src/components/pro/pro.js index 1e9e135..87dba13 100644 --- a/src/components/pro/pro.js +++ b/src/components/pro/pro.js @@ -7,7 +7,7 @@ import codemirrorConfig from '../../assets/js/codemirror/config'; import '../../assets/js/codemirror/styles/codemirror.css'; import common from '../../mixins/common'; import marked from '../../config/marked'; - +import tocObj from "../../assets/js/marked/createToc"; export default { name: 'markdown-pro', mixins: [common], @@ -345,6 +345,10 @@ export default { html = html.replace(/
/g, '
' + this.copyBtnText + '
').replace(/<\/pre>/g, '
') } this.html = html; + //toc + this.toc = tocObj.tocItems; + tocObj.reset() + this.addImageClickListener(); this.addCopyListener(); this.$emit('input', currentValue); diff --git a/src/mixins/common.js b/src/mixins/common.js index 66ca350..6aa24c8 100644 --- a/src/mixins/common.js +++ b/src/mixins/common.js @@ -1,8 +1,11 @@ import {saveFile} from '../utils'; import defaultTools from '../config/tools'; +import AnchorLink from '../components/Anchor/anchorLink' +import Anchor from '../components/Anchor/anchor' export default { name: 'markdown', + components: {Anchor,AnchorLink}, props: { value: { type: [String, Number],