diff --git a/App.vue b/App.vue index 5f4cbf5..43fc6ac 100755 --- a/App.vue +++ b/App.vue @@ -4,9 +4,9 @@ v-model="val" @on-ready="onReady" @on-copy="onCopy" - @on-paste-image="onPasteImage" + @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) @@ -33,7 +55,7 @@ onCopy(text) { console.log(text); }, - onPasteImage(file) { + onUpladImage(file) { console.log(file) }, onSave(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/docs/changelog.md b/docs/changelog.md index 446f5a2..64914e4 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,8 @@ +### 2.1.0 +- onSave 事件内返回结果增加html字段 +- 头部按钮增加本地图片上传,默认关闭 +- 增加on-upload-image事件,废除on-paste-image事件 + ### 2.0.0 - 新增专业版编辑器 - 新增预览组件 diff --git a/docs/events.md b/docs/events.md index c6b4930..d56016f 100644 --- a/docs/events.md +++ b/docs/events.md @@ -5,9 +5,13 @@ 编辑器保存事件,自动保存或者手动保存时触发,支持`ctrl+s`或`command+s`触发保存,返回值类型为`Object`,包含当前输入值`value`和选择的代码块主题`theme`。 -### on-paste-image +### on-paste-image [已废除,可改用on-upload-image] 监听编辑器粘贴图片事件,在编辑区域内手动粘贴图片时触发,可用于支持粘贴插入图片文件,返回`file`文件,上传文件后可结合`on-ready`事件内返回的`insertContent`插入图片。 ### on-copy 复制代码块内容,触发时返回当前代码块的text,copyCode开启时才有效。 + +### on-upload-image + +图片上传事件,用户自定义上传图片,复制粘贴图片截图、文件和点开菜单栏上传按钮时式触发,返回`file`文件,上传文件后可结合`on-ready`事件内返回的`insertContent`插入图片。 diff --git a/docs/props.md b/docs/props.md index 29019bb..df57e0c 100644 --- a/docs/props.md +++ b/docs/props.md @@ -47,6 +47,7 @@ ol|有序列表|是 code |代码块|是 link |链接|是 image|image|是 +uploadImage|本地图片|否 table |表格|是 checked|已完成列表|是 notChecked |未完成列表|是 diff --git a/package.json b/package.json index d4c1957..97becb0 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vue-meditor", "description": "一款使用marked和highlight.js开发的一款markdown编辑器", - "version": "2.0.3", + "version": "2.1.0", "author": "zhaoxuhui<1258835133@qq.com>", "license": "MIT", "private": false, 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/font/iconfont.css b/src/assets/font/iconfont.css index 807b0ca..44b6509 100644 --- a/src/assets/font/iconfont.css +++ b/src/assets/font/iconfont.css @@ -1,10 +1,10 @@ @font-face {font-family: "iconfont"; - src: url('iconfont.eot?t=1572423800887'); /* IE9 */ - src: url('iconfont.eot?t=1572423800887#iefix') format('embedded-opentype'), /* IE6-IE8 */ - url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), - url('iconfont.woff?t=1572423800887') format('woff'), - url('iconfont.ttf?t=1572423800887') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ - url('iconfont.svg?t=1572423800887#iconfont') format('svg'); /* iOS 4.1- */ + src: url('iconfont.eot?t=1576231776697'); /* IE9 */ + src: url('iconfont.eot?t=1576231776697#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), + url('iconfont.woff?t=1576231776697') format('woff'), + url('iconfont.ttf?t=1576231776697') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ + url('iconfont.svg?t=1576231776697#iconfont') format('svg'); /* iOS 4.1- */ } .iconfont { @@ -15,10 +15,6 @@ -moz-osx-font-smoothing: grayscale; } -.icon-fujian:before { - content: "\e61b"; -} - .icon-md:before { content: "\e604"; } @@ -71,10 +67,6 @@ content: "\e65a"; } -.icon-checked1:before { - content: "\eaa0"; -} - .icon-close:before { content: "\e690"; } @@ -135,7 +127,15 @@ content: "\e6f0"; } +.icon-upload-img:before { + content: "\e679"; +} + .icon-save:before { content: "\e648"; } +.icon-check-box:before { + content: "\ec58"; +} + diff --git a/src/assets/font/iconfont.eot b/src/assets/font/iconfont.eot index 0c43c73..8b157bc 100644 Binary files a/src/assets/font/iconfont.eot and b/src/assets/font/iconfont.eot differ diff --git a/src/assets/font/iconfont.svg b/src/assets/font/iconfont.svg index 8191335..5e0b2a6 100644 --- a/src/assets/font/iconfont.svg +++ b/src/assets/font/iconfont.svg @@ -20,9 +20,6 @@ Created by iconfont /> - - - @@ -62,9 +59,6 @@ Created by iconfont - - - @@ -110,9 +104,15 @@ Created by iconfont + + + + + + diff --git a/src/assets/font/iconfont.ttf b/src/assets/font/iconfont.ttf index 1ae0037..0a4e6d2 100644 Binary files a/src/assets/font/iconfont.ttf and b/src/assets/font/iconfont.ttf differ diff --git a/src/assets/font/iconfont.woff b/src/assets/font/iconfont.woff index b0f60a7..904c17f 100644 Binary files a/src/assets/font/iconfont.woff and b/src/assets/font/iconfont.woff differ diff --git a/src/assets/font/iconfont.woff2 b/src/assets/font/iconfont.woff2 index 6caf963..2d9eac6 100644 Binary files a/src/assets/font/iconfont.woff2 and b/src/assets/font/iconfont.woff2 differ 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 d23188d..8c40f00 100644 --- a/src/components/pro/index.vue +++ b/src/components/pro/index.vue @@ -87,6 +87,9 @@
  • +
  • + +
  • @@ -190,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/components/simple/index.vue b/src/components/simple/index.vue index 9e6bb68..6dac969 100644 --- a/src/components/simple/index.vue +++ b/src/components/simple/index.vue @@ -85,6 +85,9 @@
  • +
  • + +
  • diff --git a/src/config/tools.js b/src/config/tools.js index 50a0441..34a9b84 100644 --- a/src/config/tools.js +++ b/src/config/tools.js @@ -18,6 +18,7 @@ export default { code: true, link: true, image: true, + uploadImage:false, table: true, checked: true, notChecked: true, diff --git a/src/mixins/common.js b/src/mixins/common.js index a2ba894..6aa24c8 100644 --- a/src/mixins/common.js +++ b/src/mixins/common.js @@ -1,8 +1,11 @@ -import { saveFile } from '../utils'; +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], @@ -48,7 +51,7 @@ export default { return {}; } }, - copyCode:{// 复制代码 + copyCode: {// 复制代码 type: Boolean, default: true }, @@ -75,6 +78,7 @@ export default { timerId: null, // 定时器id themeName: '', themeSlideDown: false, + imgSlideDown: false, imgs: [], scrolling: true, // 同步滚动 editorScrollHeight: 0, @@ -85,7 +89,7 @@ export default { }, computed: { tools() { - const { toolbars = {} } = this; + const {toolbars = {}} = this; return { ...defaultTools, ...toolbars @@ -105,7 +109,7 @@ export default { ); }, handleSave() {// 保存操作 - const { currentValue, themeName,html } = this; + const {currentValue, themeName, html} = this; this.$emit('on-save', { theme: themeName, value: currentValue, @@ -128,8 +132,8 @@ export default { if (!file) { return; } - const { type } = file; - if (!['text/markdown','text/src'].includes(type)) { + const {type} = file; + if (!['text/markdown', 'text/src'].includes(type)) { return; } const reader = new FileReader(); @@ -139,17 +143,17 @@ export default { reader.onload = () => { this.currentValue = reader.result; e.target.value = ''; - if(this.pro){// 专业版,手动set value + if (this.pro) {// 专业版,手动set value this.editor.setOption('value', this.currentValue); } }; - reader.onerror = err=>{ + reader.onerror = err => { console.error(err); } }, handlePaste(_, e) {// 粘贴图片 - const { clipboardData = {} } = e; - const { types = [], items } = clipboardData; + const {clipboardData = {}} = e; + const {types = [], items} = clipboardData; let item = null; for (let i = 0; i < types.length; i++) { if (types[i] === 'Files') { @@ -160,7 +164,7 @@ export default { if (item) { const file = item.getAsFile(); if (/image/gi.test(file.type)) { - this.$emit('on-paste-image', file); + this.$emit('on-upload-image', file); e.preventDefault(); } } @@ -169,7 +173,7 @@ export default { this.scrollSide = side; }, addImageClickListener() {// 监听查看大图 - const { imgs = [] } = this; + const {imgs = []} = this; if (imgs.length > 0) { for (let i = 0, len = imgs.length; i < len; i++) { imgs[i].onclick = null; @@ -200,6 +204,19 @@ export default { this.previewImgModal = true; }; }, + chooseImage() {// 选择图片 + const input = document.createElement('input'); + input.type = 'file'; + input.accept = 'image/*'; + input.onchange = ()=>{ + const files = input.files; + if(files[0]){ + this.$emit('on-upload-image', files[0]); + input.value = ''; + } + } + input.click(); + }, addCopyListener() {// 监听复制操作 setTimeout(() => { const btns = document.querySelectorAll(