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

fix:优化@用户样式

上级 e0e776dc
......@@ -68,6 +68,7 @@
@updateShowHelp="showHelp = $event"
@renderLinksHtml="renderLinksHtml"
@queryUserList="queryUserList"
@callUserList="callUserList = $event"
v-else
/>
<div v-if="maxLength && showWordLimit && !showPreview" class="word_limit">
......@@ -88,7 +89,12 @@ import markdownHeader from "./components/header/md-header";
import markdownFooter from "./components/footer/md-footer";
import markdownEditor from "./components/content/md-textarea";
import markdownPreview from "./components/content/md-preview";
import { formatText, checktUrl, getLinkTitle } from "@/assets/js/utils";
import {
formatText,
checktUrl,
getLinkTitle,
renderLinkCard
} from "@/assets/js/utils";
export default {
components: {
markdownHeader,
......@@ -248,7 +254,8 @@ export default {
htmlMinHeight: 150,
showHelp: false,
textLength: "",
userList: [],
userList: false,
callUserList: [],
selectionInfo: {
selectorId: "",
selectionStart: "",
......@@ -315,6 +322,9 @@ export default {
const checkResult = checktUrl(val, this.filePathRule);
emitContent.invalidList = checkResult;
}
if (this.callUserList.length) {
emitContent.callUserList = this.callUserList;
}
emitContent.filteredTags = this.filteredTags;
this.$emit("change", emitContent);
this.$emit("input", emitContent);
......@@ -395,6 +405,7 @@ export default {
}).then(res => {
console.log("返回的列表", res);
res.forEach(item => {
item.csdn = true;
const linkEl = vDom.querySelector("#" + item.id);
const url = item.csdn
? "https://link.csdn.net/?target=" + item.url
......@@ -403,16 +414,7 @@ export default {
linkEl.setAttribute("target", "_blank");
linkEl.setAttribute("href", url);
const title = getLinkTitle(linkEl);
linkEl.innerHTML = `
<span class="md_link_title">${title || item.title}</span>
<span class="md_link_desc">${item.desc}</span>
<span class="md_flex_card">
<img class="md_link_img" src="${item.img}" />
<span class="flex-1">
<span class="md_link_url">${item.url}</span>
</span>
</span>
`;
linkEl.innerHTML = renderLinkCard(title, item);
});
// return vDom.innerHTML;
this.html = vDom.innerHTML;
......
......@@ -315,11 +315,24 @@ export function addLanguageClass(html) {
});
return virtualDom;
}
export function addLinkTarget(html) {
const virtualDom = document.createElement("div");
virtualDom.innerHTML = html;
const userList = [];
Array.from(virtualDom.querySelectorAll("a[href]")).forEach(item => {
item.setAttribute("target", "_blank");
if (item.getAttribute("type") === "user") {
userList.push(item.dataset.user);
}
});
const list = Array.from(new Set(userList)); // 去重
return { callUserList: list, userHtml: virtualDom.innerHTML };
}
export function getLinkTags(id, html) {
const virtualDom = document.createElement("div");
virtualDom.innerHTML = html;
const links = Array.from(
virtualDom.querySelectorAll("a:not([download])")
virtualDom.querySelectorAll("a:not([download],[type=user])")
).map((item, index) => {
item.id = id + "_" + index;
return {
......@@ -336,6 +349,22 @@ export function getLinkTitle(linkEl) {
return /^http/.test(title) ? "" : title;
}
export function renderLinkCard(title, item) {
return `
<span class="md_link_title">${title || item.title || ""}</span>
${item.desc ? `<span class="md_link_desc">${item.desc}</span>` : ""}
<span class="md_flex_card">
${
item.img
? `<img class="md_link_img" src="${item.img}" />`
: "<span class='md_link_img icon iconfont icon-lianjie'></span>"
}
<span class="flex-1">
<span class="md_link_url">${item.url}</span>
</span>
</span>`;
}
export function preventDefault(id) {
const textEl = document.getElementById(id);
textEl.blur();
......
.md_link_card {
display: block;
padding: 10px 16px;
padding: 6px 16px;
box-sizing: border-box;
border-radius: 4px;
border: 1px solid var(--md-editor-border-color);
background: #f5f7fa;
transition: border 0.3s;
max-width: 640px;
margin: 4px 0;
// max-width: 640px;
@media (any-hover: hover) {
&:hover {
border: 1px solid var(--md-editor-border-color-active);
......@@ -16,8 +17,9 @@
color: var(--md-editor-text-color-active);
}
.md_link_img {
height: 20px;
margin-right: 12px;
height: 14px !important;
line-height: 14px !important;
margin-right: 10px;
border-radius: 2px;
}
.md_link_title {
......
......@@ -65,7 +65,13 @@ textarea {
}
}
.md_call_user {
a.md_call_user {
color: var(--md-editor-border-color-active);
cursor: pointer;
@media (any-hover: hover) {
&:hover {
color: var(--md-editor-border-color-active);
opacity: 0.8;
}
}
}
......@@ -13,7 +13,7 @@
>
<img class="md_select_user_avatar" :src="item.avatar" />
<div class="md_select_user_info">
<div class="md_select_user_name">{{ item.name }}</div>
<div class="md_select_user_name">{{ item.nickname }}</div>
<div class="md_select_user_desc">最近回答过类似问题</div>
</div>
</li>
......@@ -37,8 +37,8 @@ export default {
}
},
userList: {
type: Array,
default: () => []
type: [Array, Boolean],
default: false
},
activeUserIndex: {
type: Number,
......
......@@ -128,8 +128,8 @@ export default {
default: false
},
userList: {
type: Array,
default: () => []
type: [Boolean, Array],
default: false
}
},
......
import {
getFilteredTags,
getLinkTags,
addLanguageClass
addLanguageClass,
addLinkTarget
} from "@/assets/js/utils";
import marked from "marked";
import DOMPurify from "dompurify";
......@@ -36,12 +37,14 @@ export default {
const filteredTags = getFilteredTags(html, cleanHtml); // 计算是否有标签被过滤
// 链接转换为卡片
const { vDom, links } = getLinkTags(this.id, cleanHtml);
const { callUserList, userHtml } = addLinkTarget(cleanHtml);
this.$emit("callUserList", callUserList);
this.$emit("getFilteredTags", filteredTags);
this.$emit("update:html", cleanHtml);
this.$emit("update:html", userHtml);
if (links.length) this.$emit("renderLinksHtml", { vDom, links });
},
rerender() {
const _this = this;
const renderer = {
image(href, title, text) {
if (href === null) {
......@@ -56,7 +59,7 @@ export default {
<span class="md_file_desc">16.6KB</span>
</span>
<span class="md_file_controls">
<a href="${href}" download target="_blank" class="md_file_download icon iconfont icon-xiazai"></a>
<a href="${href}" type="file" download class="md_file_download icon iconfont icon-xiazai"></a>
</span>
</div>`;
}
......@@ -69,8 +72,11 @@ export default {
return out;
},
text(text) {
const newText = text.replace(/(@.+? )/g, `<span class="md_call_user">$1</span>`);
const newText = text.replace(/(@.+? )/g, function(val) {
const user = _this.getUserByName(val.slice(1).trim());
return `<a type="user" data-user="${user &&
user.username}" href="${user && user.url}" class="md_call_user">${val}</a>`;
});
return newText;
}
};
......
......@@ -5,7 +5,7 @@ export default {
const originalText = this.textContent;
const queryInfo = this.queryInfo;
const cursorPosition = getPosition(this.id);
const username = user.name + " ";
const username = user.nickname + " ";
const newText =
originalText.slice(0, queryInfo.startPosition) +
username +
......@@ -79,20 +79,29 @@ export default {
pEl.offsetLeft < frameWidth * (2 / 3)
? pEl.offsetLeft
: pEl.offsetLeft - 200,
top: pEl.offsetTop - textEl.scrollTop
tozp: pEl.offsetTop - textEl.scrollTop
// left: pEl.getBoundingClientRect().left,
// top: pEl.getBoundingClientRect().top
};
textEl.parentNode.removeChild(hideEl);
this.showSelectUser = true;
this.queryInfo.startPosition = getPosition(this.id) + 1;
this.queryInfo.endPosition = getPosition(this.id) + 1;
this.$emit("queryUserList", this.queryInfo.keyWord);
this.$nextTick(() => {
const list = textEl.parentNode.querySelector(".md_select_user");
if (list) list.scrollTo(0, 0);
const userList = this.userList;
if (userList === false) return;
this.showSelectUser = true;
this.$nextTick(() => {
const list = textEl.parentNode.querySelector(".md_select_user");
if (list) list.scrollTo(0, 0);
});
});
});
},
getUserByName(name) {
const userList = this.userList;
if (!userList.length) return "";
return userList.find(item => item.nickname === name);
}
}
};
......@@ -230,13 +230,13 @@ export default {
startStr: "",
endStr: ""
},
{
name: "file",
icon: "wenjian",
tip: "上传附件",
startStr: "",
endStr: ""
},
// {
// name: "file",
// icon: "wenjian",
// tip: "上传附件",
// startStr: "",
// endStr: ""
// },
{
name: "task",
icon: "renwu",
......
......@@ -22,8 +22,8 @@ function initMdEditor(obj) {
onBlur = () => {},
onInput = () => {},
onSubmit = () => {},
renderLinks = () => {},
queryUserList = () => {},
renderLinks,
queryUserList,
placeholder,
value,
disabled,
......@@ -116,71 +116,72 @@ function initMdEditor(obj) {
});
},
renderLinks({ links, callback }) {
if (!renderLinks) return callback(links);
renderLinks(links, function(res) {
callback(res);
});
},
queryUserList({ keyWord, callback }) {
// queryUserList(keyWord, function(res) {
const list = [
{
id: 1,
name: "藤原拓海",
avatar:
"https://img2.baidu.com/it/u=2380211986,3979961921&fm=26&fmt=auto&gp=0.jpg"
},
{
id: 2,
name: "高桥凉介",
avatar:
"https://img0.baidu.com/it/u=777620324,2343967729&fm=26&fmt=auto&gp=0.jpg"
},
{
id: 3,
name: "马奎斯",
avatar:
"https://img2.baidu.com/it/u=1297316011,1869565258&fm=26&fmt=auto&gp=0.jpg"
},
{
id: 4,
name: "王一博",
url: "https://weibo.com/u/5492443184",
avatar:
"https://img2.baidu.com/it/u=298051053,3773223854&fm=26&fmt=auto&gp=0.jpg"
},
{
id: 5,
name: "王俊凯",
url: "https://weibo.com/tfwangjunkai",
avatar:
"https://img1.baidu.com/it/u=2378425879,2273515018&fm=26&fmt=auto&gp=0.jpg"
},
{
id: 6,
name: "易烊千玺",
url: "https://weibo.com/tfyiyangqianxi",
avatar:
"https://img0.baidu.com/it/u=2227200088,1939721201&fm=26&fmt=auto&gp=0.jpg"
},
{
id: 7,
name: "白敬亭",
url: "https://weibo.com/u/2112496475",
avatar:
"https://img1.baidu.com/it/u=3265411836,2089649447&fm=11&fmt=auto&gp=0.jpg"
}
];
if (!queryUserList) return callback(false); // 返回false则不触发@弹窗
queryUserList(keyWord, function(res) {
// const list = [
// {
// id: 1,
// name: "藤原拓海",
// avatar:
// "https://img2.baidu.com/it/u=2380211986,3979961921&fm=26&fmt=auto&gp=0.jpg"
// },
// {
// id: 2,
// name: "高桥凉介",
// avatar:
// "https://img0.baidu.com/it/u=777620324,2343967729&fm=26&fmt=auto&gp=0.jpg"
// },
// {
// id: 3,
// name: "马奎斯",
// avatar:
// "https://img2.baidu.com/it/u=1297316011,1869565258&fm=26&fmt=auto&gp=0.jpg"
// },
// {
// id: 4,
// name: "王一博",
// url: "https://weibo.com/u/5492443184",
// avatar:
// "https://img2.baidu.com/it/u=298051053,3773223854&fm=26&fmt=auto&gp=0.jpg"
// },
// {
// id: 5,
// name: "王俊凯",
// url: "https://weibo.com/tfwangjunkai",
// avatar:
// "https://img1.baidu.com/it/u=2378425879,2273515018&fm=26&fmt=auto&gp=0.jpg"
// },
// {
// id: 6,
// name: "易烊千玺",
// url: "https://weibo.com/tfyiyangqianxi",
// avatar:
// "https://img0.baidu.com/it/u=2227200088,1939721201&fm=26&fmt=auto&gp=0.jpg"
// },
// {
// id: 7,
// name: "白敬亭",
// url: "https://weibo.com/u/2112496475",
// avatar:
// "https://img1.baidu.com/it/u=3265411836,2089649447&fm=11&fmt=auto&gp=0.jpg"
// }
// ];
// callback(res);
if (!keyWord) {
callback(list);
return;
}
callback(
list.filter(item => {
return item.name.includes(keyWord);
})
);
// });
const list = res;
if (!keyWord) return callback(list);
callback(
list.filter(item => {
return item.nickname.includes(keyWord);
})
);
});
}
};
this.vEl = new Vue({
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册