提交 b318ab87 编写于 作者: 璃白.'s avatar 璃白. 🌻

feat:链接转卡片

上级 a108af2e
无法预览此类型文件
......@@ -15,7 +15,7 @@
}
.text {
width: 100%;
height: 200px;
/* height: 200px; */
border: 1px solid red;
overflow-y: auto;
padding: 10px;
......@@ -81,15 +81,22 @@ void main()
// aaa.remove()
// })
// `,
value: "## edswgdfgdfgdfg\n**dfgdfgdfg**\n_ergdfgdfg_\n> ergergdfg\n```\nwefgdfsfdgdf\n```\n- efwefsdfsdf\n\n\nsdgfdfgdfgdfg\n\n\nedrfgdfgdfg\n\n\n\nergergergergerg\nergergergerg",
value:
"## edswgdfgdfgdfg\n**dfgdfgdfg**\n_ergdfgdfg_\n> ergergdfg\n```\nwefgdfsfdgdf\n```\n- efwefsdfsdf\n\n\nsdgfdfgdfgdfg\n\n\nedrfgdfgdfg\n\n\n\nergergergergerg\nergergergerg\n\n\nedrfgdfgdfg\n\n\n\nergergergergerg\nergergergerg\n\n\nedrfgdfgdfg\n\n\n\nergergergergerg\nergergergerg",
value:
"![img](https://img2.baidu.com/it/u=4025475678,645544065&fm=26&fmt=auto&gp=0.jpg)",
value: "http://www.baidu.com",
disabled: false,
themeOptions: {
dark: false,
borderColorActive: "#409eff",
textColor: "#303030",
textColorActive: "#000",
// textColorActive: "#000",
codeTheme: "atom-one-dark"
},
height: 400,
// height: 400,
rows: 6,
height: 40,
toolsOptions: {
format: true,
bold: true,
......@@ -102,10 +109,11 @@ void main()
task: true,
table: true,
file: true,
// help: false,
fullScreen: true
},
zIndex: 7000,
maxLength: 20000,
// maxLength: 20000,
showWordLimit: true,
rows: "10",
// height: "600",
......@@ -113,7 +121,7 @@ void main()
filePathRule: /^https:\/\/ss2\.bdstatic\.com/,
canPreview: true,
// canAttachFile: true,
placeholder: "请输入内容",
placeholder: "当前问题已结题,不再开放新的回答。",
// throttle: 1000,
// onFocus: function(res) {
// console.log(res);
......@@ -145,13 +153,29 @@ void main()
callback(reader.result);
};
});
},
renderLinks: function(val, callback) {
const newLinks = val.map(item => {
item.title = "接口返回的标题";
item.desc =
"接口返回的描述接口返回的描述接口返回的描述接口返回的描述接口返回的描述接口返回的描述接口返回的描述接口返回的描述接口返回的描述接口返回的描述接口返回的描述接口返回的描述接口返回的描述接口返回的描述接口返回的描述接口返回的描述";
return item;
});
setTimeout(() => {
callback(newLinks);
}, 2000);
}
});
ee.focus();
document.querySelector("#a").onclick = function() {};
// ee.focus();
document.querySelector("#a").onclick = function() {
// ee.disable();
ee.setValue(
"![img](https://img0.baidu.com/it/u=1562265942,1137696507&fm=253&fmt=auto&app=120&f=JPEG?w=525&h=500)"
);
};
document.querySelector("#b").onclick = function() {
ee.setValue("");
ee.enable();
};
</script>
</body>
......
此差异已折叠。
文件已添加
{
"name": "markdown-editor",
"description": " A open source markdown editor of csdn codechina team contributed",
"version": "0.6.2",
"version": "0.6.3",
"publisher": "guoweijia",
"scripts": {
"start": "webpack serve --mode=development",
......
......@@ -14,11 +14,13 @@
:toolsOptions="toolsOptions"
:zIndex="zIndex"
:tabSize="tabSize"
:disabled="disabled"
:fullScreen.sync="fullScreen"
:themeOptions="themeOptions"
@upload="handleUpload"
@getFormatType="formatType = $event"
@updateShowHelp="showHelp = $event"
@renderLinks="$emit('renderLinks', $event)"
/>
<input
ref="mdUploadFile"
......@@ -38,6 +40,7 @@
:htmlMinHeight="htmlMinHeight"
v-if="showPreview"
/>
<markdown-editor
:selectionInfo.sync="selectionInfo"
:text.sync="text"
......@@ -53,6 +56,7 @@
:height="textareaHeight"
:html.sync="html"
:id="textareaId"
:disabled="disabled"
:show-help="showHelp"
:formatType="formatType"
:ref="'md_textarea' + id"
......@@ -61,6 +65,7 @@
@enter="handleEnter"
@getFilteredTags="filteredTags = $event"
@updateShowHelp="showHelp = $event"
@renderLinksHtml="renderLinksHtml"
v-else
/>
<div v-if="maxLength && showWordLimit && !showPreview" class="word_limit">
......@@ -125,6 +130,10 @@ export default {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
setFullScreen: {
// type: Boolean,
default: ""
......@@ -229,6 +238,7 @@ export default {
handler: function(val) {
setTimeout(() => {
const textEl = document.getElementById(this.textareaId);
if (!textEl) return;
if (val) {
textEl.focus();
} else {
......@@ -266,6 +276,7 @@ export default {
html: {
immediate: false,
handler: function(val) {
console.log("html update");
const emitContent = {
text: this.text,
html: this.html
......@@ -342,6 +353,30 @@ export default {
text: this.text,
html: this.html
});
},
renderLinksHtml({ vDom, links }) {
new Promise((resolve, reject) => {
this.$emit("renderLinks", {
links,
callback: function(list) {
resolve(list);
}
});
}).then(res => {
console.log("返回的列表", res);
res.forEach(item => {
const linkEl = vDom.querySelector("#" + item.id);
linkEl.className = "md_link_card";
linkEl.setAttribute("target", "_blank");
linkEl.innerHTML = `
<span class="md_link_title">${item.title}</span>
<span class="md_link_desc">${item.desc}</span>
<span class="md_link_url">${item.url}</span>
`;
});
// return vDom.innerHTML;
this.html = vDom.innerHTML;
});
}
}
};
......@@ -371,6 +406,9 @@ export default {
&.active {
border: 1px solid var(--md-editor-border-color-active);
}
&.disabled {
background: var(--md-editor-frame-bg-color-disabled);
}
.word_limit {
position: absolute;
right: 10px;
......
import Vue from "vue";
let eventBus = new Vue();
export default eventBus;
......@@ -303,3 +303,30 @@ export function copyFormatRules(selection) {
return formatType;
}
export function addLanguageClass(html) {
const virtualDom = document.createElement("div");
virtualDom.innerHTML = html;
virtualDom.querySelectorAll("code").forEach(item => {
if (!/language-/.test(item.className)) {
item.className = "language-javascript";
}
});
return virtualDom;
}
export function getLinkTags(id, html) {
const virtualDom = document.createElement("div");
virtualDom.innerHTML = html;
const links = Array.from(virtualDom.querySelectorAll("a")).map(
(item, index) => {
item.id = id + "_" + index;
return {
id: item.id,
title: item.innerText,
url: item.href
};
}
);
return { vDom: virtualDom, links };
}
.md_link_card {
display: block;
padding: 10px 16px;
box-sizing: border-box;
border-radius: 4px;
border: 1px solid var(--md-editor-border-color);
background: #f5f6f7;
transition: border 0.3s;
&:hover {
border: 1px solid var(--md-editor-border-color-active);
}
span {
color: var(--md-editor-text-color-active);
display: block;
}
.md_link_title {
font-size: 16px;
font-weight: 500;
}
.md_link_desc {
font-size: 14px;
color: var(--md-editor-text-color);
margin: 6px 0;
}
.md_link_url {
font-size: 12px;
color: var(--md-editor-border-color-active);
}
}
@import "./variable.less";
@import "./iconfont.less";
@import "./markdown.less";
@import './card.less';
* {
padding: 0;
......@@ -46,4 +47,4 @@ textarea {
// img {
// display: block;
// margin: 0;
// }
// }
\ No newline at end of file
......@@ -5,6 +5,8 @@
--md-editor-text-color-active: #333;
--md-editor-frame-bg-color: #fff;
--md-editor-content-bg-color: #fff;
--md-editor-frame-bg-color-disabled: #f5f7fa;
--md-editor-content-bg-color-disabled: #f5f7fa;
--md-editor-helpdoc-color: #666666;
--md-editor-code-bg-color: #f3f4f5;
--md-editor-fullScrren-zIndex: 2000;
......
<template>
<div class="md_link_card">
<div class="title">图像识别从入门到放弃</div>
<div class="desc">此文章来自</div>
<div class="link"></div>
</div>
</template>
<script>
export default {
data() {
return {};
}
};
</script>
<style lang="less" scoped>
.md_link_card {
}
</style>
<template>
<div :class="['md_textarea', { fullScreen, isFocus }]">
<textarea
spellcheck="false"
:id="id"
@change="$emit('update:text', textContent)"
@input="input"
......@@ -15,10 +16,13 @@
:placeholder="placeholder"
:maxlength="maxLength"
:rows="rows"
:disabled="disabled"
:style="{
height: editorHeight,
overflow: editorOverFlow,
cursor: formatType
cursor: disabled
? 'not-allowed'
: formatType
? `url(https://codechina.csdn.net/codechina/operation-work/uploads/a1b7c2a995b2320dca911e2f2ecb9b88/format.png),text`
: 'text'
}"
......@@ -38,10 +42,14 @@ import {
getSelectionInfo,
getPosition,
getFilteredTags,
getLinkTags,
addLanguageClass,
throttle as throttleFn
} from "@/assets/js/utils";
import eventBus from "@/assets/js/eventBus";
import marked from "marked";
import helpDoc from "./help-doc";
import helpDoc from "./components/help-doc";
import DOMPurify from "dompurify";
export default {
components: { helpDoc },
props: {
......@@ -68,6 +76,10 @@ export default {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: false
......@@ -129,7 +141,6 @@ export default {
text: {
immediate: true,
handler: function(val) {
const cursorPoint = getPosition(this.id);
this.textContent = val;
this.transferMarkdown(val);
}
......@@ -161,10 +172,6 @@ export default {
document.removeEventListener("mouseup", this.checkSelection);
},
computed: {
formatIcon() {
return import("@/assets/img/icon-format.png");
return require("@/assets/img/icon-format.png");
},
emitText() {
// return throttleFn(() => {}, this.throttleTime);
return () => {
......@@ -195,25 +202,18 @@ export default {
}
});
const str = val + "";
// if (!str.trim()) return;
const html = marked(str);
const virtualDom = document.createElement("div");
virtualDom.innerHTML = html;
virtualDom.querySelectorAll("code").forEach(item => {
if (!/language-/.test(item.className)) {
item.className = "language-javascript";
}
});
const DOMPurify = require("dompurify");
const html = marked(str); // 解析markdown
const virtualDom = addLanguageClass(html); // 如果没指定语言,添加默认语言
const cleanHtml = DOMPurify.sanitize(virtualDom.innerHTML, {
FORBID_TAGS: ["style", "script"]
});
// console.log(html.length);
// console.log(cleanHtml.length);
}); // 去除标签
const filteredTags = getFilteredTags(html, cleanHtml); // 计算是否有标签被过滤
// 链接转换为卡片
const { vDom, links } = getLinkTags(this.id, cleanHtml);
const filteredTags = getFilteredTags(html, cleanHtml);
this.$emit("getFilteredTags", filteredTags);
this.$emit("update:html", cleanHtml);
this.$emit("renderLinksHtml", { vDom, links });
},
input() {
this.$emit("update:textLength", this.textContent.length);
......@@ -311,6 +311,9 @@ export default {
overflow-y: auto;
}
}
&.disabled {
background: var(--md-editor-content-bg-color-disabled);
}
textarea {
display: block;
......@@ -327,6 +330,9 @@ export default {
&::placeholder {
color: var(--md-editor-text-color);
}
// &:disabled {
// background: var(--md-editor-content-bg-color-disabled);
// }
}
.icon {
position: absolute;
......
......@@ -3,7 +3,7 @@
<ul>
<li
@click="$emit('select', item)"
v-for="(item, index) in list"
v-for="(item, index) in sortList"
:key="index"
>
{{ item }}
......@@ -49,6 +49,13 @@ export default {
"XML"
]
};
},
computed: {
sortList() {
return this.list.sort((a, b) => {
return a.charCodeAt(0) - b.charCodeAt(0);
});
}
}
};
</script>
......@@ -80,8 +87,6 @@ ul {
&::-webkit-scrollbar {
display: none;
width: 2px;
height: 2px;
}
&::-webkit-scrollbar-thumb {
border-radius: 1em;
......
<template>
<div class="tips">
<div class="md_doc_tips">
<div class="name">{{ name }}</div>
<div class="doc" v-if="doc">Markdown: {{ doc }}</div>
<!-- <div class="doc" v-if="doc">Markdown: {{ doc }}</div> -->
</div>
</template>
<script>
......@@ -22,7 +22,7 @@ export default {
};
</script>
<style lang="less" scoped>
.tips {
.md_doc_tips {
text-align: center;
.doc {
color: var(--md-editor-text-color-active);
......
<template>
<div :class="['md_header', { active: isFocus }]">
<div class="header_tabs">
<div v-if="!disabled" class="header_tabs">
<div
:class="['tab_item', { active: canPreview && !showPreview }]"
@click="setShowPreview(false)"
......@@ -17,7 +17,7 @@
<span>预览</span>
</div>
</div>
<div class="header_tools" v-if="!showPreview">
<div class="header_tools" v-if="!disabled && !showPreview">
<tool-button
:ref="item.name"
:ulNum.sync="ulNum"
......@@ -73,6 +73,10 @@ export default {
type: Boolean,
default: true
},
disabled: {
type: Boolean,
default: false
},
themeOptions: {
type: Object,
default: () => {}
......@@ -151,6 +155,7 @@ export default {
},
formatType: "", // 格式刷类型
lock: false,
scrollTop: 0,
ulNum: 1,
fullScreenBtn: {
name: "fullScreen",
......@@ -254,6 +259,7 @@ export default {
]
};
},
created() {},
methods: {
resetUlNum() {
this.ulNum = 1;
......@@ -305,6 +311,18 @@ export default {
},
setShowPreview(val) {
this.$emit("update:showPreview", val);
if (val) {
const textEl = document.getElementById(this.id);
this.scrollTop = textEl.scrollTop;
} else {
this.$nextTick(() => {
const textEl = document.getElementById(this.id);
if (!textEl) return;
setTimeout(() => {
textEl.scrollTop = this.scrollTop;
}, 0);
});
}
},
handleUpdateText({ startStr, endStr, type, copy }) {
......
......@@ -22,8 +22,10 @@ function initMdEditor(obj) {
onBlur = () => {},
onInput = () => {},
onSubmit = () => {},
renderLinks = () => {},
placeholder,
value,
disabled,
zIndex = 2000,
filePathRule,
rows = 6,
......@@ -56,6 +58,7 @@ function initMdEditor(obj) {
placeholder,
maxLength,
zIndex,
disabled,
tabSize,
setPreview: false,
setFullScreen: false,
......@@ -105,6 +108,11 @@ function initMdEditor(obj) {
onUpload(val, function(res) {
callback(res);
});
},
renderLinks({ links, callback }) {
renderLinks(links, function(res) {
callback(res);
});
}
};
this.vEl = new Vue({
......@@ -125,6 +133,13 @@ function initMdEditor(obj) {
this.setValue = function(val) {
props.value = (val || "") + "";
this.vEl.$forceUpdate();
if (!props.setPreview) return;
setTimeout(() => {
this.toggleTab();
});
setTimeout(() => {
this.toggleTab();
});
};
this.focus = function() {
......@@ -137,9 +152,19 @@ function initMdEditor(obj) {
this.vEl.$forceUpdate();
};
this.disable = function() {
props.disabled = true;
this.vEl.$forceUpdate();
};
this.enable = function() {
props.disabled = false;
this.vEl.$forceUpdate();
};
this.toggleTab = function(setPreview) {
if (setPreview !== "edit" && setPreview !== "preview") {
props.setPreview = !props.preview;
props.setPreview = !props.setPreview;
} else {
props.setPreview = setPreview === "preview";
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册