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

fix:优化@用户逻辑

上级 a6571f9f
...@@ -145,7 +145,7 @@ void main() ...@@ -145,7 +145,7 @@ void main()
new Promise((res, rej) => { new Promise((res, rej) => {
setTimeout(() => { setTimeout(() => {
res(file); res(file);
}, 2000); }, 1000);
}).then(res => { }).then(res => {
var reader = new FileReader(); var reader = new FileReader();
reader.readAsDataURL(res); reader.readAsDataURL(res);
...@@ -156,6 +156,7 @@ void main() ...@@ -156,6 +156,7 @@ void main()
}, },
renderLinks: function(val, callback) { renderLinks: function(val, callback) {
const newLinks = val.map(item => { const newLinks = val.map(item => {
item.csdn = true;
item.title = item.title =
"接口返回的标题接口返回的标题接口返回的标题接口返回的标题接口返回的标题接口返回的标题接口返回的标题接口返回的标题接口返回的标题"; "接口返回的标题接口返回的标题接口返回的标题接口返回的标题接口返回的标题接口返回的标题接口返回的标题接口返回的标题接口返回的标题";
item.img = item.img =
...@@ -166,8 +167,8 @@ void main() ...@@ -166,8 +167,8 @@ void main()
}); });
setTimeout(() => { setTimeout(() => {
callback(newLinks); callback(newLinks);
}, 2000); }, 1000);
}, }
// queryUserList: function(val, callback) { // queryUserList: function(val, callback) {
// const list = [ // const list = [
// { // {
......
此差异已折叠。
...@@ -396,8 +396,12 @@ export default { ...@@ -396,8 +396,12 @@ export default {
console.log("返回的列表", res); console.log("返回的列表", res);
res.forEach(item => { res.forEach(item => {
const linkEl = vDom.querySelector("#" + item.id); const linkEl = vDom.querySelector("#" + item.id);
const url = item.csdn
? "https://link.csdn.net/?target=" + item.url
: item.url;
linkEl.className = "md_link_card"; linkEl.className = "md_link_card";
linkEl.setAttribute("target", "_blank"); linkEl.setAttribute("target", "_blank");
linkEl.setAttribute("href", url);
const title = getLinkTitle(linkEl); const title = getLinkTitle(linkEl);
linkEl.innerHTML = ` linkEl.innerHTML = `
<span class="md_link_title">${title || item.title}</span> <span class="md_link_title">${title || item.title}</span>
......
...@@ -29,7 +29,7 @@ export const throttle = function(fn, wait) { ...@@ -29,7 +29,7 @@ export const throttle = function(fn, wait) {
} }
}; };
}; };
// 获取光标的位置
export const getPosition = function(selectorId) { export const getPosition = function(selectorId) {
const element = document.getElementById(selectorId); const element = document.getElementById(selectorId);
if (!element) return 0; if (!element) return 0;
...@@ -336,33 +336,11 @@ export function getLinkTitle(linkEl) { ...@@ -336,33 +336,11 @@ export function getLinkTitle(linkEl) {
return /^http/.test(title) ? "" : title; return /^http/.test(title) ? "" : title;
} }
export function rerender() { export function preventDefault(id) {
const renderer = { const textEl = document.getElementById(id);
image(href, title, text) { textEl.blur();
if (href === null) { setTimeout(() => {
return text; textEl.focus();
} }, 0);
// ![file](...)渲染文件,只可以下载 return;
if (text === "file") {
return `<a href="${href}" class="md_file_card md_flex_card" download target="_blank">
<span class="md_file_img icon iconfont icon-doc"></span>
<span class="flex-1">
<span class="md_file_title">${title}</span>
<span class="md_file_desc">16.6KB</span>
</span>
<span class="md_file_controls">
<span class="md_file_download icon iconfont icon-xiazai"></span>
</span>
</a>`;
}
// ![img](...)渲染图片
let out = '<img src="' + href + '" alt="' + text + '"';
if (title) {
out += ' title="' + title + '"';
}
out += "/>";
return out;
}
};
marked.use({ renderer });
} }
...@@ -6,9 +6,11 @@ ...@@ -6,9 +6,11 @@
border: 1px solid var(--md-editor-border-color); border: 1px solid var(--md-editor-border-color);
background: #f5f7fa; background: #f5f7fa;
transition: border 0.3s; transition: border 0.3s;
max-width: 800px; max-width: 600px;
&:hover { @media (any-hover: hover) {
border: 1px solid var(--md-editor-border-color-active); &:hover {
border: 1px solid var(--md-editor-border-color-active);
}
} }
span { span {
color: var(--md-editor-text-color-active); color: var(--md-editor-text-color-active);
...@@ -55,28 +57,35 @@ ...@@ -55,28 +57,35 @@
border: 1px solid var(--md-editor-border-color); border: 1px solid var(--md-editor-border-color);
background: #f5f7fa; background: #f5f7fa;
transition: border 0.3s; transition: border 0.3s;
max-width: 600px; max-width: 400px;
&:hover { margin: 4px 0;
border: 1px solid var(--md-editor-border-color-active); @media (any-hover: hover) {
&:hover {
border: 1px solid var(--md-editor-border-color-active);
}
} }
span { span {
color: var(--md-editor-text-color-active); color: var(--md-editor-text-color-active);
} }
.md_file_img { .md_file_img {
font-size: 36px; font-size: 34px;
margin-right: 16px; margin-right: 16px;
color: var(--md-editor-text-color); color: var(--md-editor-border-color-active);
} }
.md_file_download { .md_file_download {
font-size: 16px; font-size: 14px;
padding: 8px; padding: 8px;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid var(--md-editor-border-color); border: 1px solid var(--md-editor-border-color);
border-radius: 50%; border-radius: 50%;
cursor: pointer; cursor: pointer;
transition: color 0.3s; transition: color 0.3s;
&:hover { line-height: 1 !important;
color: var(--md-editor-border-color-active); color: var(--md-editor-border-color);
@media (any-hover: hover) {
&:hover {
color: var(--md-editor-border-color-active);
}
} }
} }
.md_file_title { .md_file_title {
......
...@@ -112,7 +112,7 @@ ...@@ -112,7 +112,7 @@
} }
.icon-doc:before { .icon-doc:before {
content: "\e642"; content: "\e689";
} }
.icon-xiazai:before { .icon-xiazai:before {
......
...@@ -3,11 +3,12 @@ ...@@ -3,11 +3,12 @@
class="md_select_container" class="md_select_container"
:style="{ left: this.left + 'px', top: this.top + 'px' }" :style="{ left: this.left + 'px', top: this.top + 'px' }"
> >
<ul v-show="userList.length" class="md_select_user"> <ul v-show="list.length" class="md_select_user">
<li <li
@click="selectUser(item)" @click="selectUser(item)"
v-for="(item, index) in userList" v-for="(item, index) in list"
:key="index" :key="index"
:class="[{ active: isActive(index) }]"
> >
<img class="md_select_user_avatar" :src="item.avatar" /> <img class="md_select_user_avatar" :src="item.avatar" />
<div class="md_select_user_info"> <div class="md_select_user_info">
...@@ -16,8 +17,8 @@ ...@@ -16,8 +17,8 @@
</div> </div>
</li> </li>
</ul> </ul>
<div v-show="userList.length" class="after"></div> <div v-show="list.length" class="after"></div>
<div v-show="!userList.length" class="md_select_no_data"> <div v-show="!list.length" class="md_select_no_data">
轻敲空格完成输入 轻敲空格完成输入
</div> </div>
</div> </div>
...@@ -37,6 +38,24 @@ export default { ...@@ -37,6 +38,24 @@ export default {
userList: { userList: {
type: Array, type: Array,
default: () => [] default: () => []
},
activeUserIndex: {
type: Number,
default: 0
}
},
computed: {
list() {
const list = this.userList;
if (!list.length) return [];
return list.map((item, index) => {
if (index === 0) {
item.active = true;
} else {
item.active = false;
}
return item;
});
} }
}, },
watch: { watch: {
...@@ -57,6 +76,9 @@ export default { ...@@ -57,6 +76,9 @@ export default {
methods: { methods: {
selectUser(user) { selectUser(user) {
this.$emit("selectUser", user); this.$emit("selectUser", user);
},
isActive(index) {
return index === this.activeUserIndex;
} }
} }
}; };
...@@ -86,12 +108,6 @@ export default { ...@@ -86,12 +108,6 @@ export default {
.md_select_user { .md_select_user {
max-height: 214px; max-height: 214px;
width: 180px; width: 180px;
// background: #fff;
// box-shadow: 0 1px 6px rgb(0 0 0 / 10%);
// border: 1px solid var(--md-editor-border-color);
// border-radius: 4px;
// z-index: var(--md-editor-fullScrren-zIndex);
// margin: 0;
padding: 6px 0; padding: 6px 0;
box-sizing: border-box; box-sizing: border-box;
overflow-y: auto; overflow-y: auto;
...@@ -115,7 +131,8 @@ export default { ...@@ -115,7 +131,8 @@ export default {
box-sizing: border-box; box-sizing: border-box;
padding: 6px 8px; padding: 6px 8px;
cursor: pointer; cursor: pointer;
&:hover { &:hover,
&.active {
background: #f5f7fa; background: #f5f7fa;
} }
// & + li { // & + li {
......
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
@focus="setFocus(true)" @focus="setFocus(true)"
@blur="setFocus(false)" @blur="setFocus(false)"
@paste="pasteFile" @paste="pasteFile"
@keydown.stop.50="keyup" @keydown.stop.50="handleCallUser"
@keydown.stop.up.prevent="changeActiveUserIndex('up')"
@keydown.stop.down.prevent="changeActiveUserIndex('down')"
@keydown.enter="handleEnter" @keydown.enter="handleEnter"
@keydown.meta.enter.exact="submit" @keydown.meta.enter.exact="submit"
@keydown.ctrl.enter.exact="submit" @keydown.ctrl.enter.exact="submit"
...@@ -39,9 +41,10 @@ ...@@ -39,9 +41,10 @@
<transition name="slideup-fade"> <transition name="slideup-fade">
<selectUser <selectUser
:userList="userList" :userList="userList"
:activeUserIndex.sync="activeUserIndex"
v-show="showSelectUser" v-show="showSelectUser"
:position="selectUserPosition" :position="selectUserPosition"
@selectUser="selectUser" @selectUser="handleSelectUser"
/> />
</transition> </transition>
</div> </div>
...@@ -50,19 +53,16 @@ ...@@ -50,19 +53,16 @@
import { import {
getSelectionInfo, getSelectionInfo,
getPosition, getPosition,
getFilteredTags, preventDefault,
getLinkTags,
formatText,
rerender,
addLanguageClass,
throttle as throttleFn throttle as throttleFn
} from "@/assets/js/utils"; } from "@/assets/js/utils";
import marked from "marked";
import selectUser from "./components/user-select"; import selectUser from "./components/user-select";
import helpDoc from "./components/help-doc"; import helpDoc from "./components/help-doc";
import DOMPurify from "dompurify"; import renderMix from "./mixins/render-mixins";
import selectUserMix from "./mixins/select-user-mixins";
export default { export default {
components: { helpDoc, selectUser }, components: { helpDoc, selectUser },
mixins: [renderMix, selectUserMix],
props: { props: {
id: { id: {
type: String, type: String,
...@@ -143,6 +143,7 @@ export default { ...@@ -143,6 +143,7 @@ export default {
endPosition: "", endPosition: "",
keyWord: "" keyWord: ""
}, },
activeUserIndex: 0,
selectUserPosition: { left: 0, top: 0 } selectUserPosition: { left: 0, top: 0 }
}; };
}, },
...@@ -158,7 +159,9 @@ export default { ...@@ -158,7 +159,9 @@ export default {
if (val) { if (val) {
this.resetPreviewMinHeight(); this.resetPreviewMinHeight();
} else { } else {
this.showSelectUser = false; setTimeout(() => {
// this.showSelectUser = false;
}, 200);
} }
} }
}, },
...@@ -214,6 +217,22 @@ export default { ...@@ -214,6 +217,22 @@ export default {
} }
}, },
methods: { methods: {
changeActiveUserIndex(type) {
if (this.showSelectUser) {
const max = this.userList.length;
if (type === "down") {
this.activeUserIndex++;
if (this.activeUserIndex >= max) {
this.activeUserIndex = 0;
}
} else {
this.activeUserIndex--;
if (this.activeUserIndex < 0) {
this.activeUserIndex = max - 1;
}
}
}
},
resetPreviewMinHeight() { resetPreviewMinHeight() {
setTimeout(() => { setTimeout(() => {
const textEl = document.getElementById(this.id); const textEl = document.getElementById(this.id);
...@@ -229,130 +248,11 @@ export default { ...@@ -229,130 +248,11 @@ export default {
keyWord: "" keyWord: ""
}; };
}, },
transferMarkdown(val) {
rerender();
marked.setOptions({
breaks: true,
gfm: true,
langPrefix: "language-",
highlight: function(code, lang, callback) {
let html = require("highlight.js").highlightAuto(code).value;
return html;
}
});
const str = val + "";
const html = marked(str); // 解析markdown
const virtualDom = addLanguageClass(html); // 如果没指定语言,添加默认语言
const cleanHtml = DOMPurify.sanitize(virtualDom.innerHTML, {
FORBID_TAGS: [
"style",
"script",
"select",
"option",
"input",
"textarea",
"form",
"button"
]
}); // 去除标签
const filteredTags = getFilteredTags(html, cleanHtml); // 计算是否有标签被过滤
// 链接转换为卡片
const { vDom, links } = getLinkTags(this.id, cleanHtml);
this.$emit("getFilteredTags", filteredTags);
this.$emit("update:html", cleanHtml);
if (links.length) this.$emit("renderLinksHtml", { vDom, links });
},
input() { input() {
if (this.showSelectUser) this.handleQueryUser(); if (this.showSelectUser) this.handleQueryUser();
this.$emit("update:textLength", this.textContent.length); this.$emit("update:textLength", this.textContent.length);
this.emitText(); this.emitText();
}, },
selectUser(user) {
const originalText = this.textContent;
const queryInfo = this.queryInfo;
const cursorPosition = getPosition(this.id);
const username = user.name + " ";
const newText =
originalText.slice(0, queryInfo.startPosition) +
username +
originalText.slice(queryInfo.endPosition);
this.textContent = newText;
this.emitText();
this.showSelectUser = false;
this.$nextTick(() => {
const textEl = document.getElementById(this.id);
textEl.setSelectionRange(
cursorPosition + username.length,
cursorPosition + username.length
);
textEl.focus();
});
},
handleQueryUser() {
const endPosition = getPosition(this.id);
const startPosition = this.queryInfo.startPosition;
const keyWord = this.textContent.slice(startPosition, endPosition);
this.queryInfo.endPosition = endPosition;
if (endPosition < startPosition || keyWord.slice(-1) === " ") {
this.showSelectUser = false;
return;
}
this.queryInfo.keyWord = keyWord;
this.$emit("queryUserList", keyWord);
},
keyup(e) {
if (e.key === "@") {
this.createSelectUserDialog();
}
},
createSelectUserDialog() {
const textEl = document.getElementById(this.id);
if (!textEl) return;
const height = getComputedStyle(textEl).getPropertyValue("height");
const width = getComputedStyle(textEl).getPropertyValue("width");
const scrollTop = textEl.scrollTop;
const originalText = this.textContent;
const cursorPoint = getPosition(this.id);
const selectionInfo = {
selectionStart: cursorPoint,
selectionEnd: cursorPoint
};
const newText = formatText(
originalText,
selectionInfo,
"<span id='call_position'>",
"</span>"
);
const hideEl = this.createHideEl("clac_position_El_");
hideEl.style.position = "absolute";
hideEl.style.width = width;
hideEl.style.height = height;
hideEl.style.overflowY = "auto";
hideEl.style.wordBreak = "break-all";
hideEl.style.top = "14px";
hideEl.style.left = 0;
hideEl.style.whiteSpace = "pre-wrap";
hideEl.innerHTML = newText;
this.$nextTick(() => {
hideEl.scrollTop = scrollTop;
const pEl = document.getElementById("call_position");
this.selectUserPosition = {
left: pEl.offsetLeft,
top: 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);
});
},
createHideEl(type) { createHideEl(type) {
const textEl = document.getElementById(this.id); const textEl = document.getElementById(this.id);
if (!textEl) return; if (!textEl) return;
...@@ -394,14 +294,11 @@ export default { ...@@ -394,14 +294,11 @@ export default {
this.autoSize && !this.fullScreen && !this.height ? "hidden" : "auto"; this.autoSize && !this.fullScreen && !this.height ? "hidden" : "auto";
textEl.parentNode.removeChild(hideEl); textEl.parentNode.removeChild(hideEl);
}, },
handleEnter() { handleEnter(e) {
if (this.showSelectUser) { if (this.showSelectUser) {
const textEl = document.getElementById(this.id); const activeUser = this.userList[this.activeUserIndex];
textEl.blur(); this.handleSelectUser(activeUser);
setTimeout(() => { e.preventDefault();
textEl.focus();
// this.showSelectUser = false;
}, 0);
return; return;
} }
this.$emit("enter"); this.$emit("enter");
......
import {
getFilteredTags,
getLinkTags,
addLanguageClass
} from "@/assets/js/utils";
import marked from "marked";
import DOMPurify from "dompurify";
export default {
methods: {
transferMarkdown(val) {
this.rerender();
marked.setOptions({
breaks: true,
gfm: true,
langPrefix: "language-",
highlight: function(code, lang, callback) {
let html = require("highlight.js").highlightAuto(code).value;
return html;
}
});
const str = val + "";
const html = marked(str); // 解析markdown
const virtualDom = addLanguageClass(html); // 如果没指定语言,添加默认语言
const cleanHtml = DOMPurify.sanitize(virtualDom.innerHTML, {
FORBID_TAGS: [
"style",
"script",
"select",
"option",
"input",
"textarea",
"form",
"button"
]
}); // 去除标签
const filteredTags = getFilteredTags(html, cleanHtml); // 计算是否有标签被过滤
// 链接转换为卡片
const { vDom, links } = getLinkTags(this.id, cleanHtml);
this.$emit("getFilteredTags", filteredTags);
this.$emit("update:html", cleanHtml);
if (links.length) this.$emit("renderLinksHtml", { vDom, links });
},
rerender() {
const renderer = {
image(href, title, text) {
if (href === null) {
return text;
}
// ![file](...)渲染文件,只可以下载
if (text === "file") {
return `<div class="md_file_card md_flex_card">
<span class="md_file_img icon iconfont icon-doc"></span>
<span class="flex-1">
<span class="md_file_title">${title}</span>
<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>
</span>
</div>`;
}
// ![img](...)渲染图片
let out = '<img src="' + href + '" alt="' + text + '"';
if (title) {
out += ' title="' + title + '"';
}
out += "/>";
return out;
}
};
marked.use({ renderer });
}
}
};
import { getPosition, formatText } from "@/assets/js/utils";
export default {
methods: {
handleSelectUser(user) {
const originalText = this.textContent;
const queryInfo = this.queryInfo;
const cursorPosition = getPosition(this.id);
const username = user.name + " ";
const newText =
originalText.slice(0, queryInfo.startPosition) +
username +
originalText.slice(queryInfo.endPosition);
this.textContent = newText;
this.emitText();
this.showSelectUser = false;
this.$nextTick(() => {
const textEl = document.getElementById(this.id);
textEl.setSelectionRange(
cursorPosition + username.length,
cursorPosition + username.length
);
textEl.focus();
});
},
handleQueryUser() {
const endPosition = getPosition(this.id);
const startPosition = this.queryInfo.startPosition;
const keyWord = this.textContent.slice(startPosition, endPosition);
this.queryInfo.endPosition = endPosition;
if (endPosition < startPosition || keyWord.slice(-1) === " ") {
this.showSelectUser = false;
return;
}
this.queryInfo.keyWord = keyWord;
this.$emit("queryUserList", keyWord);
},
handleCallUser(e) {
if (e.key === "@") {
this.createSelectUserDialog();
}
},
createSelectUserDialog() {
const textEl = document.getElementById(this.id);
if (!textEl) return;
const height = getComputedStyle(textEl).getPropertyValue("height");
const width = getComputedStyle(textEl).getPropertyValue("width");
const scrollTop = textEl.scrollTop;
const originalText = this.textContent;
const cursorPoint = getPosition(this.id);
const selectionInfo = {
selectionStart: cursorPoint,
selectionEnd: cursorPoint
};
const newText = formatText(
originalText,
selectionInfo,
"<span id='call_position'>",
"</span>"
);
const hideEl = this.createHideEl("clac_position_El_");
hideEl.style.position = "absolute";
hideEl.style.width = width;
hideEl.style.height = height;
hideEl.style.overflowY = "auto";
hideEl.style.wordBreak = "break-all";
hideEl.style.top = "14px";
hideEl.style.left = 0;
hideEl.style.whiteSpace = "pre-wrap";
hideEl.innerHTML = newText;
this.$nextTick(() => {
hideEl.scrollTop = scrollTop;
const pEl = document.getElementById("call_position");
this.selectUserPosition = {
left: pEl.offsetLeft,
top: 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);
});
}
}
};
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册