mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-16 14:12:51 +00:00
no message
This commit is contained in:
parent
f34766ade0
commit
790b05880a
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div class="app-view-loading no-dark-mode">
|
<div class="app-view-loading no-dark-content">
|
||||||
<div>
|
<div>
|
||||||
<div>PAGE LOADING</div>
|
<div>PAGE LOADING</div>
|
||||||
<span></span>
|
<span></span>
|
||||||
|
|||||||
4
resources/assets/js/app.js
vendored
4
resources/assets/js/app.js
vendored
@ -71,10 +71,6 @@ Vue.component('EDropdown', Dropdown);
|
|||||||
Vue.component('EDropdownMenu', DropdownMenu);
|
Vue.component('EDropdownMenu', DropdownMenu);
|
||||||
Vue.component('EDropdownItem', DropdownItem);
|
Vue.component('EDropdownItem', DropdownItem);
|
||||||
|
|
||||||
import emojiClass from './directives/emoji-class'
|
|
||||||
|
|
||||||
Vue.directive('emoji-class', emojiClass);
|
|
||||||
|
|
||||||
const originalPush = VueRouter.prototype.push
|
const originalPush = VueRouter.prototype.push
|
||||||
VueRouter.prototype.push = function push(location) {
|
VueRouter.prototype.push = function push(location) {
|
||||||
return originalPush.call(this, location).catch(err => err)
|
return originalPush.call(this, location).catch(err => err)
|
||||||
|
|||||||
@ -63,7 +63,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="item-content">
|
<div class="item-content">
|
||||||
<div class="item-title">
|
<div class="item-title">
|
||||||
<div class="title-text" v-emoji-class="`no-dark-content`">{{item.title}}</div>
|
<div class="title-text" v-html="transformEmojiToHtml(item.title)"></div>
|
||||||
<div
|
<div
|
||||||
v-if="item.activity"
|
v-if="item.activity"
|
||||||
class="title-activity"
|
class="title-activity"
|
||||||
@ -75,7 +75,7 @@
|
|||||||
v-if="item.tags"
|
v-if="item.tags"
|
||||||
v-for="tag in item.tags"
|
v-for="tag in item.tags"
|
||||||
:style="tag.style">{{tag.name}}</span>
|
:style="tag.style">{{tag.name}}</span>
|
||||||
<span class="desc-text" v-emoji-class="`no-dark-content`" v-html="item.desc"></span>
|
<span class="desc-text" v-html="transformEmojiToHtml(item.desc)"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -89,6 +89,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import {mapState} from "vuex";
|
import {mapState} from "vuex";
|
||||||
import emitter from "../store/events";
|
import emitter from "../store/events";
|
||||||
|
import transformEmojiToHtml from "../utils/emoji";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SearchBox',
|
name: 'SearchBox',
|
||||||
@ -187,6 +188,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
transformEmojiToHtml,
|
||||||
activityFormat(date) {
|
activityFormat(date) {
|
||||||
const local = $A.daytz(),
|
const local = $A.daytz(),
|
||||||
time = $A.dayjs(date);
|
time = $A.dayjs(date);
|
||||||
|
|||||||
249
resources/assets/js/directives/emoji-class.js
vendored
249
resources/assets/js/directives/emoji-class.js
vendored
@ -1,249 +0,0 @@
|
|||||||
/**
|
|
||||||
* Vue指令: v-emoji-class
|
|
||||||
*
|
|
||||||
* 用法:
|
|
||||||
* 1. 基本用法: v-emoji-class="className" - 将emoji包装在<span class="className">emoji</span>中
|
|
||||||
* 2. 高级用法: v-emoji-class="{className: 'className', tagName: 'div'}" - 自定义标签名
|
|
||||||
*
|
|
||||||
* 示例:
|
|
||||||
* <div v-emoji-class="emoji-icon">我爱中国🇨🇳</div>
|
|
||||||
* <p v-emoji-class="{className: 'large-emoji', tagName: 'em'}">Hello 😊</p>
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {debounce} from "lodash";
|
|
||||||
|
|
||||||
// 正则表达式用于匹配emoji - 使用预编译正则提高性能
|
|
||||||
const emojiRegex = /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g;
|
|
||||||
|
|
||||||
// 使用WeakMap存储元素状态,避免直接修改DOM元素
|
|
||||||
const elementStates = new WeakMap();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查文本是否包含emoji
|
|
||||||
* @param {string} text - 要检查的文本
|
|
||||||
* @returns {boolean} - 是否包含emoji
|
|
||||||
*/
|
|
||||||
function containsEmoji(text) {
|
|
||||||
emojiRegex.lastIndex = 0;
|
|
||||||
return emojiRegex.test(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理文本节点中的emoji
|
|
||||||
* @param {Text} textNode - 文本节点
|
|
||||||
* @param {string} className - 添加给emoji的类名
|
|
||||||
* @param {string} tagName - 包裹emoji的标签名
|
|
||||||
* @returns {boolean} - 是否有修改
|
|
||||||
*/
|
|
||||||
function processTextNode(textNode, className, tagName) {
|
|
||||||
const text = textNode.textContent;
|
|
||||||
|
|
||||||
// 快速检查是否包含emoji
|
|
||||||
if (!containsEmoji(text)) return false;
|
|
||||||
|
|
||||||
// 重置正则索引并准备替换
|
|
||||||
emojiRegex.lastIndex = 0;
|
|
||||||
const fragment = document.createDocumentFragment();
|
|
||||||
let lastIndex = 0;
|
|
||||||
let match;
|
|
||||||
|
|
||||||
// 逐个匹配emoji并替换
|
|
||||||
while ((match = emojiRegex.exec(text)) !== null) {
|
|
||||||
// 添加emoji前的文本
|
|
||||||
if (match.index > lastIndex) {
|
|
||||||
fragment.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建包装emoji的元素
|
|
||||||
const emojiWrapper = document.createElement(tagName);
|
|
||||||
emojiWrapper.className = className;
|
|
||||||
emojiWrapper.textContent = match[0];
|
|
||||||
fragment.appendChild(emojiWrapper);
|
|
||||||
|
|
||||||
lastIndex = emojiRegex.lastIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加剩余文本
|
|
||||||
if (lastIndex < text.length) {
|
|
||||||
fragment.appendChild(document.createTextNode(text.substring(lastIndex)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 替换原始节点
|
|
||||||
textNode.parentNode.replaceChild(fragment, textNode);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 递归处理元素及其子元素中的文本节点
|
|
||||||
* @param {Node} node - 要处理的节点
|
|
||||||
* @param {string} className - 添加给emoji的类名
|
|
||||||
* @param {string} tagName - 包裹emoji的标签名
|
|
||||||
* @returns {boolean} - 是否有修改
|
|
||||||
*/
|
|
||||||
function processNodeEmojis(node, className, tagName) {
|
|
||||||
// 如果是文本节点,直接处理
|
|
||||||
if (node.nodeType === Node.TEXT_NODE) {
|
|
||||||
return processTextNode(node, className, tagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是元素节点,递归处理其子节点
|
|
||||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
let modified = false;
|
|
||||||
|
|
||||||
// 使用childNodes的副本避免在迭代过程中修改集合
|
|
||||||
const childNodes = Array.from(node.childNodes);
|
|
||||||
for (const childNode of childNodes) {
|
|
||||||
if (processNodeEmojis(childNode, className, tagName)) {
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return modified;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析指令绑定值
|
|
||||||
* @param {Object} binding - 指令的绑定值
|
|
||||||
* @returns {Object} - 解析后的className和tagName
|
|
||||||
*/
|
|
||||||
function parseBinding(binding) {
|
|
||||||
const value = binding.value;
|
|
||||||
return {
|
|
||||||
className: typeof value === 'string' ? value : (value?.className || ''),
|
|
||||||
tagName: typeof value === 'object' ? (value?.tagName || 'span') : 'span'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算元素内容的哈希值
|
|
||||||
* @param {HTMLElement} el - 元素
|
|
||||||
* @returns {string} - 哈希值
|
|
||||||
*/
|
|
||||||
function getContentHash(el) {
|
|
||||||
// 使用innerHTML长度和前20个字符作为简单哈希
|
|
||||||
const content = el.innerHTML;
|
|
||||||
return `${content.length}:${content.substring(0, 20)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理元素中的emoji表情
|
|
||||||
* @param {HTMLElement} el - 指令所在的元素
|
|
||||||
* @param {Object} binding - 指令的绑定值
|
|
||||||
*/
|
|
||||||
function processEmoji(el, binding) {
|
|
||||||
if (!el) return;
|
|
||||||
|
|
||||||
// 解析绑定值
|
|
||||||
const {className, tagName} = parseBinding(binding);
|
|
||||||
if (!className) return;
|
|
||||||
|
|
||||||
// 获取或初始化元素状态
|
|
||||||
let state = elementStates.get(el) || {};
|
|
||||||
elementStates.set(el, state);
|
|
||||||
|
|
||||||
// 计算内容哈希值,用于快速比较
|
|
||||||
const contentHash = getContentHash(el);
|
|
||||||
|
|
||||||
// 如果内容哈希值与上次相同且已处理过,则跳过
|
|
||||||
if (state.contentHash === contentHash && state.processed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建一个克隆节点进行处理
|
|
||||||
const clone = el.cloneNode(true);
|
|
||||||
|
|
||||||
// 递归处理所有文本节点
|
|
||||||
if (processNodeEmojis(clone, className, tagName)) {
|
|
||||||
// 使用requestAnimationFrame优化DOM更新
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
el.innerHTML = clone.innerHTML;
|
|
||||||
|
|
||||||
// 更新元素状态
|
|
||||||
state.contentHash = contentHash;
|
|
||||||
state.processed = true;
|
|
||||||
elementStates.set(el, state);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将文本中的emoji转换成HTML标签
|
|
||||||
* @param {string} text - 输入文本
|
|
||||||
* @param {string} className - 添加给emoji的类名
|
|
||||||
* @param {string} tagName - 包裹emoji的标签名,默认为'span'
|
|
||||||
* @returns {string} - 转换后的HTML字符串
|
|
||||||
*
|
|
||||||
* 示例:
|
|
||||||
* transformEmojiToHtml("我❤️你", "heart", "span")
|
|
||||||
* // 返回: "我<span class=\"heart\">❤️</span>你"
|
|
||||||
*/
|
|
||||||
export function transformEmojiToHtml(text, className, tagName = 'span') {
|
|
||||||
// 参数验证
|
|
||||||
if (typeof text !== 'string') return '';
|
|
||||||
if (!className || typeof className !== 'string') return text;
|
|
||||||
if (!tagName || typeof tagName !== 'string') tagName = 'span';
|
|
||||||
|
|
||||||
// 快速检查是否包含emoji
|
|
||||||
if (!containsEmoji(text)) return text;
|
|
||||||
|
|
||||||
// 重置正则索引并准备替换
|
|
||||||
emojiRegex.lastIndex = 0;
|
|
||||||
let result = '';
|
|
||||||
let lastIndex = 0;
|
|
||||||
let match;
|
|
||||||
|
|
||||||
// 逐个匹配emoji并替换
|
|
||||||
while ((match = emojiRegex.exec(text)) !== null) {
|
|
||||||
// 添加emoji前的文本
|
|
||||||
if (match.index > lastIndex) {
|
|
||||||
result += text.substring(lastIndex, match.index);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加包装后的emoji
|
|
||||||
result += `<${tagName} class="${className}">${match[0]}</${tagName}>`;
|
|
||||||
|
|
||||||
lastIndex = emojiRegex.lastIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加剩余文本
|
|
||||||
if (lastIndex < text.length) {
|
|
||||||
result += text.substring(lastIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建防抖处理函数 - 使用更短的防抖时间提高响应速度
|
|
||||||
const debouncedProcessEmoji = debounce(processEmoji, 20);
|
|
||||||
|
|
||||||
export default {
|
|
||||||
inserted(el, binding) {
|
|
||||||
// 直接处理,不使用防抖
|
|
||||||
processEmoji(el, binding);
|
|
||||||
},
|
|
||||||
|
|
||||||
update(el, binding) {
|
|
||||||
// 获取元素状态
|
|
||||||
const state = elementStates.get(el) || {};
|
|
||||||
|
|
||||||
// 只有当绑定值变化时才重新处理
|
|
||||||
if (binding.oldValue !== binding.value) {
|
|
||||||
debouncedProcessEmoji(el, binding);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 内容变化时也需要重新处理
|
|
||||||
const contentHash = getContentHash(el);
|
|
||||||
if (state.contentHash !== contentHash) {
|
|
||||||
debouncedProcessEmoji(el, binding);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
unbind(el) {
|
|
||||||
// 清理元素状态
|
|
||||||
elementStates.delete(el);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
2
resources/assets/js/functions/web.js
vendored
2
resources/assets/js/functions/web.js
vendored
@ -1069,7 +1069,7 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
|
|||||||
[style*="background-image:url"],
|
[style*="background-image:url"],
|
||||||
[style*="background-image: url"],
|
[style*="background-image: url"],
|
||||||
[background],
|
[background],
|
||||||
.no-dark-mode,
|
.emoji-original,
|
||||||
.no-dark-content,
|
.no-dark-content,
|
||||||
.no-dark-before:before {
|
.no-dark-before:before {
|
||||||
${this.utils.reverseFilter()}
|
${this.utils.reverseFilter()}
|
||||||
|
|||||||
@ -138,7 +138,7 @@
|
|||||||
@click="toggleRoute('project', {projectId: item.id})">
|
@click="toggleRoute('project', {projectId: item.id})">
|
||||||
<div class="project-h1">
|
<div class="project-h1">
|
||||||
<em @click.stop="toggleOpenMenu(item.id)"></em>
|
<em @click.stop="toggleOpenMenu(item.id)"></em>
|
||||||
<div class="title" v-html="transformEmojiToHtml(item.name, 'no-dark-content')"></div>
|
<div class="title" v-html="transformEmojiToHtml(item.name)"></div>
|
||||||
<div v-if="item.top_at" class="icon-top"></div>
|
<div v-if="item.top_at" class="icon-top"></div>
|
||||||
<div v-if="item.task_my_num - item.task_my_complete > 0" class="num">{{item.task_my_num - item.task_my_complete}}</div>
|
<div v-if="item.task_my_num - item.task_my_complete > 0" class="num">{{item.task_my_num - item.task_my_complete}}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -388,7 +388,7 @@ import ApproveDetails from "./manage/approve/details.vue";
|
|||||||
import notificationKoro from "notification-koro1";
|
import notificationKoro from "notification-koro1";
|
||||||
import emitter from "../store/events";
|
import emitter from "../store/events";
|
||||||
import SearchBox from "../components/SearchBox.vue";
|
import SearchBox from "../components/SearchBox.vue";
|
||||||
import {transformEmojiToHtml} from "../directives/emoji-class";
|
import transformEmojiToHtml from "../utils/emoji";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
<template v-for="tag in $A.dialogTags(dialogData)" v-if="tag.color != 'success'">
|
<template v-for="tag in $A.dialogTags(dialogData)" v-if="tag.color != 'success'">
|
||||||
<Tag :color="tag.color" :fade="false">{{$L(tag.text)}}</Tag>
|
<Tag :color="tag.color" :fade="false">{{$L(tag.text)}}</Tag>
|
||||||
</template>
|
</template>
|
||||||
<h2 class="user-select-auto" @click="onViewDetail">{{dialogData.name}}</h2>
|
<h2 class="user-select-auto" @click="onViewDetail" v-html="transformEmojiToHtml(dialogData.name)"></h2>
|
||||||
<em v-if="peopleNum > 0" @click="onDialogMenu('groupInfo')">({{peopleNum}})</em>
|
<em v-if="peopleNum > 0" @click="onDialogMenu('groupInfo')">({{peopleNum}})</em>
|
||||||
<Tag v-if="dialogData.bot" class="after" :fade="false">{{$L('机器人')}}</Tag>
|
<Tag v-if="dialogData.bot" class="after" :fade="false">{{$L('机器人')}}</Tag>
|
||||||
<Tag v-if="dialogData.type === 'user' && approvaUserStatus" class="after" color="red" :fade="false">{{$L(approvaUserStatus)}}</Tag>
|
<Tag v-if="dialogData.type === 'user' && approvaUserStatus" class="after" color="red" :fade="false">{{$L(approvaUserStatus)}}</Tag>
|
||||||
@ -676,6 +676,7 @@ import {isLocalResourcePath} from "../../../components/Replace/utils";
|
|||||||
import emitter from "../../../store/events";
|
import emitter from "../../../store/events";
|
||||||
import Forwarder from "./Forwarder/index.vue";
|
import Forwarder from "./Forwarder/index.vue";
|
||||||
import {throttle} from "lodash";
|
import {throttle} from "lodash";
|
||||||
|
import transformEmojiToHtml from "../../../utils/emoji";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "DialogWrapper",
|
name: "DialogWrapper",
|
||||||
@ -1452,6 +1453,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
transformEmojiToHtml,
|
||||||
/**
|
/**
|
||||||
* 获取会话基本信息
|
* 获取会话基本信息
|
||||||
* @param dialog_id
|
* @param dialog_id
|
||||||
|
|||||||
@ -32,7 +32,7 @@
|
|||||||
<div class="project-item">
|
<div class="project-item">
|
||||||
<div class="item-left">
|
<div class="item-left">
|
||||||
<div class="project-h1">
|
<div class="project-h1">
|
||||||
<div class="project-name" v-html="transformEmojiToHtml(item.name, 'no-dark-content')"></div>
|
<div class="project-name" v-html="transformEmojiToHtml(item.name)"></div>
|
||||||
<div v-if="item.top_at" class="icon-top"></div>
|
<div v-if="item.top_at" class="icon-top"></div>
|
||||||
<div v-if="item.task_my_num - item.task_my_complete > 0" class="num">{{item.task_my_num - item.task_my_complete}}</div>
|
<div v-if="item.task_my_num - item.task_my_complete > 0" class="num">{{item.task_my_num - item.task_my_complete}}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -86,7 +86,7 @@
|
|||||||
import {mapState} from "vuex";
|
import {mapState} from "vuex";
|
||||||
import longpress from "../../../directives/longpress";
|
import longpress from "../../../directives/longpress";
|
||||||
import TransferDom from "../../../directives/transfer-dom";
|
import TransferDom from "../../../directives/transfer-dom";
|
||||||
import {transformEmojiToHtml} from "../../../directives/emoji-class";
|
import transformEmojiToHtml from "../../../utils/emoji";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ProjectList",
|
name: "ProjectList",
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
<div class="project-back" @click="onBack">
|
<div class="project-back" @click="onBack">
|
||||||
<i class="taskfont"></i>
|
<i class="taskfont"></i>
|
||||||
</div>
|
</div>
|
||||||
<h1 @click="showName" class="user-select-auto" v-html="transformEmojiToHtml(projectData.name, 'no-dark-content')"></h1>
|
<h1 @click="showName" class="user-select-auto" v-html="transformEmojiToHtml(projectData.name)"></h1>
|
||||||
<div v-if="loading" class="project-load"><Loading/></div>
|
<div v-if="loading" class="project-load"><Loading/></div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="project-icons">
|
<ul class="project-icons">
|
||||||
@ -119,7 +119,7 @@
|
|||||||
:class="['column-head', column.color ? 'custom-color' : '']"
|
:class="['column-head', column.color ? 'custom-color' : '']"
|
||||||
:style="column.color ? {backgroundColor: column.color} : {}">
|
:style="column.color ? {backgroundColor: column.color} : {}">
|
||||||
<div class="column-head-title">
|
<div class="column-head-title">
|
||||||
<AutoTip>{{column.name}}</AutoTip>
|
<AutoTip v-html="transformEmojiToHtml(column.name)"></AutoTip>
|
||||||
<em>({{panelTask(column.tasks).length}})</em>
|
<em>({{panelTask(column.tasks).length}})</em>
|
||||||
</div>
|
</div>
|
||||||
<div class="column-head-icon">
|
<div class="column-head-icon">
|
||||||
@ -575,7 +575,7 @@ import UserSelect from "../../../components/UserSelect.vue";
|
|||||||
import UserAvatarTip from "../../../components/UserAvatar/tip.vue";
|
import UserAvatarTip from "../../../components/UserAvatar/tip.vue";
|
||||||
import VMPreviewNostyle from "../../../components/VMEditor/nostyle.vue";
|
import VMPreviewNostyle from "../../../components/VMEditor/nostyle.vue";
|
||||||
import emitter from "../../../store/events";
|
import emitter from "../../../store/events";
|
||||||
import {transformEmojiToHtml} from "../../../directives/emoji-class";
|
import transformEmojiToHtml from "../../../utils/emoji";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ProjectPanel",
|
name: "ProjectPanel",
|
||||||
|
|||||||
@ -104,7 +104,7 @@
|
|||||||
<template v-for="tag in $A.dialogTags(dialog)" v-if="tag.color != 'success'">
|
<template v-for="tag in $A.dialogTags(dialog)" v-if="tag.color != 'success'">
|
||||||
<Tag :color="tag.color" :fade="false" @on-click="openDialog(dialog.id)">{{$L(tag.text)}}</Tag>
|
<Tag :color="tag.color" :fade="false" @on-click="openDialog(dialog.id)">{{$L(tag.text)}}</Tag>
|
||||||
</template>
|
</template>
|
||||||
<span>{{dialog.name}}</span>
|
<span v-html="transformEmojiToHtml(dialog.name)"></span>
|
||||||
<Icon v-if="dialog.type == 'user' && lastMsgReadDone(dialog.last_msg) && dialog.dialog_user.userid != userId" :type="lastMsgReadDone(dialog.last_msg)"/>
|
<Icon v-if="dialog.type == 'user' && lastMsgReadDone(dialog.last_msg) && dialog.dialog_user.userid != userId" :type="lastMsgReadDone(dialog.last_msg)"/>
|
||||||
<em v-if="dialog.last_at">{{$A.timeFormat(dialog.last_at)}}</em>
|
<em v-if="dialog.last_at">{{$A.timeFormat(dialog.last_at)}}</em>
|
||||||
</div>
|
</div>
|
||||||
@ -282,6 +282,7 @@ import DialogWrapper from "./components/DialogWrapper";
|
|||||||
import longpress from "../../directives/longpress";
|
import longpress from "../../directives/longpress";
|
||||||
import TransferDom from "../../directives/transfer-dom";
|
import TransferDom from "../../directives/transfer-dom";
|
||||||
import emitter from "../../store/events";
|
import emitter from "../../store/events";
|
||||||
|
import transformEmojiToHtml from "../../utils/emoji";
|
||||||
|
|
||||||
const navDatas = {
|
const navDatas = {
|
||||||
menus: [
|
menus: [
|
||||||
@ -654,6 +655,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
transformEmojiToHtml,
|
||||||
listTouch() {
|
listTouch() {
|
||||||
if (this.$refs.navMenu?.visible) {
|
if (this.$refs.navMenu?.visible) {
|
||||||
this.$refs.navMenu.hide()
|
this.$refs.navMenu.hide()
|
||||||
|
|||||||
62
resources/assets/js/utils/emoji.js
vendored
Normal file
62
resources/assets/js/utils/emoji.js
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* Emoji正则表达式
|
||||||
|
* @type {RegExp}
|
||||||
|
*/
|
||||||
|
const emojiRegex = /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查文本是否包含emoji
|
||||||
|
* @param {string} text - 要检查的文本
|
||||||
|
* @returns {boolean} - 是否包含emoji
|
||||||
|
*/
|
||||||
|
const containsEmoji = (text) => {
|
||||||
|
emojiRegex.lastIndex = 0;
|
||||||
|
return emojiRegex.test(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将文本中的emoji转换成HTML标签
|
||||||
|
* @param {string} text - 输入文本
|
||||||
|
* @param {string} className - 添加给emoji的类名,默认为'emoji-original'
|
||||||
|
* @param {string} tagName - 包裹emoji的标签名,默认为'span'
|
||||||
|
* @returns {string} - 转换后的HTML字符串
|
||||||
|
*
|
||||||
|
* 示例:
|
||||||
|
* transformEmojiToHtml("我❤️你", "heart", "span")
|
||||||
|
* // 返回: "我<span class=\"heart\">❤️</span>你"
|
||||||
|
*/
|
||||||
|
export default function transformEmojiToHtml(text, className = 'emoji-original', tagName = 'span') {
|
||||||
|
// 参数验证
|
||||||
|
if (typeof text !== 'string') return '';
|
||||||
|
if (!className || typeof className !== 'string') className = 'emoji-original';
|
||||||
|
if (!tagName || typeof tagName !== 'string') tagName = 'span';
|
||||||
|
|
||||||
|
// 快速检查是否包含emoji
|
||||||
|
if (!containsEmoji(text)) return text;
|
||||||
|
|
||||||
|
// 重置正则索引并准备替换
|
||||||
|
emojiRegex.lastIndex = 0;
|
||||||
|
let result = '';
|
||||||
|
let lastIndex = 0;
|
||||||
|
let match;
|
||||||
|
|
||||||
|
// 逐个匹配emoji并替换
|
||||||
|
while ((match = emojiRegex.exec(text)) !== null) {
|
||||||
|
// 添加emoji前的文本
|
||||||
|
if (match.index > lastIndex) {
|
||||||
|
result += text.substring(lastIndex, match.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加包装后的emoji
|
||||||
|
result += `<${tagName} class="${className}">${match[0]}</${tagName}>`;
|
||||||
|
|
||||||
|
lastIndex = emojiRegex.lastIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加剩余文本
|
||||||
|
if (lastIndex < text.length) {
|
||||||
|
result += text.substring(lastIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
@extends('ie')
|
@extends('ie')
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div class="app-view-loading no-dark-mode">
|
<div class="app-view-loading no-dark-content">
|
||||||
<div>
|
<div>
|
||||||
<div>PAGE LOADING</div>
|
<div>PAGE LOADING</div>
|
||||||
<span></span>
|
<span></span>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user