浏览代码

:art: https://github.com/siyuan-note/siyuan/issues/12943

Vanessa 8 月之前
父节点
当前提交
cd8a89e5da

+ 1 - 0
app/src/assets/scss/base.scss

@@ -34,6 +34,7 @@
 @import "business/custom";
 @import "business/custom";
 @import "business/resize";
 @import "business/resize";
 @import "business/av";
 @import "business/av";
+@import "business/emojis";
 
 
 /*
 /*
 .status: 2
 .status: 2

+ 107 - 0
app/src/assets/scss/business/_emojis.scss

@@ -0,0 +1,107 @@
+
+.emojis {
+  word-break: break-all;
+  white-space: normal;
+  display: flex;
+  flex-direction: column;
+  padding: 8px 0;
+  height: 100%;
+  box-sizing: border-box;
+
+  &__tabheader {
+    display: flex;
+    border-bottom: 1px solid var(--b3-border-color);
+    padding: 0 8px 8px;
+  }
+
+  &__tabbody {
+    flex: 1;
+    overflow: auto;
+
+    div[data-type="tab-emoji"] {
+      display: flex;
+      flex-direction: column;
+      height: 100%;
+    }
+  }
+
+  .emoji__dynamic {
+    &-item {
+      width: 73px;
+      margin: 8px;
+      cursor: pointer;
+    }
+
+    &-color {
+      padding: 8px 8px 4px 4px;
+    }
+  }
+
+  &__item {
+    font-size: 24px;
+    line-height: .9em; // windows 需要这样设置
+    font-family: var(--b3-font-family-emoji);
+    text-align: center;
+    height: 28px;
+    padding: 2px 4px;
+    cursor: pointer;
+    display: inline-block;
+    transition: var(--b3-transition);
+    background-color: transparent;
+    border: 0;
+    margin: 1px;
+    overflow: hidden;
+    border-radius: var(--b3-border-radius);
+
+    img, svg {
+      height: 24px;
+      display: block;
+      width: 24px;
+    }
+
+    &--current,
+    &:hover {
+      background: var(--b3-list-hover);
+    }
+  }
+
+  &__title {
+    color: var(--b3-theme-on-surface);
+    padding: 8px 4px 4px 4px;
+  }
+
+  &__panel {
+    flex: 1;
+    overflow: auto;
+    padding: 0 8px;
+  }
+
+  &__content {
+    display: flex;
+    flex-wrap: wrap;
+  }
+
+  &__type {
+    cursor: pointer;
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 28px;
+    line-height: 28px;
+    transition: var(--b3-list-hover);
+    font-size: 16px;
+    background-color: transparent;
+    border: 0;
+    padding: 0;
+
+    &:hover {
+      background-color: var(--b3-theme-surface-lighter);
+    }
+
+    svg {
+      height: 16px;
+      width: 16px;
+    }
+  }
+}

+ 0 - 78
app/src/assets/scss/component/_menu.scss

@@ -366,81 +366,3 @@
     }
     }
   }
   }
 }
 }
-
-.emojis {
-  word-break: break-all;
-  white-space: normal;
-  display: flex;
-  flex-direction: column;
-  padding: 8px 0;
-  height: 100%;
-  box-sizing: border-box;
-
-  &__item {
-    font-size: 24px;
-    line-height: .9em; // windows 需要这样设置
-    font-family: var(--b3-font-family-emoji);
-    text-align: center;
-    height: 28px;
-    padding: 2px 4px;
-    cursor: pointer;
-    display: inline-block;
-    transition: var(--b3-transition);
-    background-color: transparent;
-    border: 0;
-    margin: 1px;
-    overflow: hidden;
-    border-radius: var(--b3-border-radius);
-
-    img, svg {
-      height: 24px;
-      display: block;
-      width: 24px;
-    }
-
-    &--current,
-    &:hover {
-      background: var(--b3-list-hover);
-    }
-  }
-
-  &__title {
-    color: var(--b3-theme-on-surface);
-    padding: 8px 4px 4px 4px;
-  }
-
-  &__panel {
-    flex: 1;
-    overflow: auto;
-    padding: 0 8px;
-  }
-
-  &__content {
-    display: flex;
-    flex-wrap: wrap;
-  }
-
-  &__type {
-    cursor: pointer;
-    flex: 1;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    height: 28px;
-    line-height: 28px;
-    transition: var(--b3-list-hover);
-    font-size: 16px;
-    background-color: transparent;
-    border: 0;
-    padding: 0;
-
-    &:hover {
-      background-color: var(--b3-theme-surface-lighter);
-    }
-
-    svg {
-      height: 16px;
-      width: 16px;
-    }
-  }
-}

+ 1 - 0
app/src/assets/scss/mobile.scss

@@ -27,6 +27,7 @@
 @import "business/custom";
 @import "business/custom";
 @import "business/av";
 @import "business/av";
 @import "business/search";
 @import "business/search";
+@import "business/emojis";
 
 
 .block__popover {
 .block__popover {
   width: 80vw;
   width: 80vw;

+ 1 - 0
app/src/constants.ts

@@ -123,6 +123,7 @@ export abstract class Constants {
     public static readonly LOCAL_OUTLINE = "local-outline";
     public static readonly LOCAL_OUTLINE = "local-outline";
     public static readonly LOCAL_PLUGIN_DOCKS = "local-plugin-docks";
     public static readonly LOCAL_PLUGIN_DOCKS = "local-plugin-docks";
     public static readonly LOCAL_IMAGES = "local-images";
     public static readonly LOCAL_IMAGES = "local-images";
+    public static readonly LOCAL_EMOJIS = "local-emojis";
 
 
     // dialog
     // dialog
     public static readonly DIALOG_OPENCARD = "dialog-opencard";
     public static readonly DIALOG_OPENCARD = "dialog-opencard";

+ 252 - 103
app/src/emoji/index.ts

@@ -1,6 +1,5 @@
 import {getRandom, isMobile} from "../util/functions";
 import {getRandom, isMobile} from "../util/functions";
 import {fetchPost} from "../util/fetch";
 import {fetchPost} from "../util/fetch";
-import {hasClosestByClassName} from "../protyle/util/hasClosest";
 import {Constants} from "../constants";
 import {Constants} from "../constants";
 import {Files} from "../layout/dock/Files";
 import {Files} from "../layout/dock/Files";
 /// #if !MOBILE
 /// #if !MOBILE
@@ -11,6 +10,8 @@ import {getAllEditor} from "../layout/getAll";
 import {setNoteBook} from "../util/pathName";
 import {setNoteBook} from "../util/pathName";
 import {Dialog} from "../dialog";
 import {Dialog} from "../dialog";
 import {setPosition} from "../util/setPosition";
 import {setPosition} from "../util/setPosition";
+import {setStorageVal} from "../protyle/util/compatibility";
+import * as dayjs from "dayjs";
 
 
 export const getRandomEmoji = () => {
 export const getRandomEmoji = () => {
     const emojis = window.siyuan.emojis[getRandom(0, window.siyuan.emojis.length - 1)];
     const emojis = window.siyuan.emojis[getRandom(0, window.siyuan.emojis.length - 1)];
@@ -27,6 +28,8 @@ export const unicode2Emoji = (unicode: string, className = "", needSpan = false,
     let emoji = "";
     let emoji = "";
     if (unicode.indexOf(".") > -1) {
     if (unicode.indexOf(".") > -1) {
         emoji = `<img class="${className}" ${lazy ? "data-" : ""}src="/emojis/${unicode}"/>`;
         emoji = `<img class="${className}" ${lazy ? "data-" : ""}src="/emojis/${unicode}"/>`;
+    } else if (unicode.startsWith("api/icon/getDynamicIcon")) {
+        emoji = `<img class="${className}" ${lazy ? "data-" : ""}src="/${unicode}"/>`;
     } else {
     } else {
         try {
         try {
             unicode.split("-").forEach(item => {
             unicode.split("-").forEach(item => {
@@ -198,28 +201,37 @@ export const openEmojiPanel = (id: string, type: "doc" | "notebook" | "av", posi
         window.siyuan.menus.menu.removeScrollEvent();
         window.siyuan.menus.menu.removeScrollEvent();
     }
     }
 
 
+    const dynamicURL = 'api/icon/getDynamicIcon?'
     const dialog = new Dialog({
     const dialog = new Dialog({
         disableAnimation: true,
         disableAnimation: true,
         transparent: true,
         transparent: true,
         hideCloseIcon: true,
         hideCloseIcon: true,
-        width: isMobile() ? "80vw" : "360px",
+        width: isMobile() ? "80vw" : "368px",
         height: "50vh",
         height: "50vh",
         content: `<div class="emojis">
         content: `<div class="emojis">
-    <div class="fn__flex">
-        <span class="fn__space"></span>
-        <label class="b3-form__icon fn__flex-1" style="overflow:initial;">
-            <svg class="b3-form__icon-icon"><use xlink:href="#iconSearch"></use></svg>
-            <input class="b3-form__icon-input b3-text-field fn__block" placeholder="${window.siyuan.languages.search}">
-        </label>
-        <span class="fn__space"></span>
-        <span class="block__icon block__icon--show fn__flex-center b3-tooltips b3-tooltips__sw" aria-label="${window.siyuan.languages.random}"><svg><use xlink:href="#iconRefresh"></use></svg></span>
-        <span class="fn__space"></span>
-        <span class="block__icon block__icon--show fn__flex-center b3-tooltips b3-tooltips__sw" aria-label="${window.siyuan.languages.remove}"><svg><use xlink:href="#iconTrashcan"></use></svg></span>
-        <span class="fn__space"></span>
+    <div class="emojis__tabheader">
+        <div data-type="tab-emoji" class="ariaLabel block__icon block__icon--show" aria-label="${window.siyuan.languages.emoji}"><svg><use xlink:href="#iconEmoji"></use></svg></div>
+        <div class="fn__space"></div>
+        <div data-type="tab-dynamic" class="ariaLabel block__icon block__icon--show" aria-label="${window.siyuan.languages.dynamicEmoji}"><svg><use xlink:href="#iconCalendar"></use></svg></div>
+        <div class="fn__flex-1"></div>
+        <span class="block__icon block__icon--show fn__flex-center ariaLabel" data-action="remove" aria-label="${window.siyuan.languages.remove}"><svg><use xlink:href="#iconTrashcan"></use></svg></span>
     </div>
     </div>
-    <div class="emojis__panel">${filterEmoji()}</div>
-    <div class="fn__flex">
-        ${[
+    <div class="emojis__tabbody">
+        <div class="fn__none" data-type="tab-emoji">
+            <div class="fn__hr"></div>
+            <div class="fn__flex">
+                <span class="fn__space"></span>
+                <label class="b3-form__icon fn__flex-1" style="overflow:initial;">
+                    <svg class="b3-form__icon-icon"><use xlink:href="#iconSearch"></use></svg>
+                    <input class="b3-form__icon-input b3-text-field fn__block" placeholder="${window.siyuan.languages.search}">
+                </label>
+                <span class="fn__space"></span>
+                <span class="block__icon block__icon--show fn__flex-center ariaLabel" data-action="random" aria-label="${window.siyuan.languages.random}"><svg><use xlink:href="#iconRefresh"></use></svg></span>
+                <span class="fn__space"></span>
+            </div>
+            <div class="emojis__panel">${filterEmoji()}</div>
+            <div class="fn__flex">
+                ${[
             ["2b50", window.siyuan.languages.recentEmoji],
             ["2b50", window.siyuan.languages.recentEmoji],
             ["1f527", getEmojiTitle(0)],
             ["1f527", getEmojiTitle(0)],
             ["1f60d", getEmojiTitle(1)],
             ["1f60d", getEmojiTitle(1)],
@@ -233,6 +245,75 @@ export const openEmojiPanel = (id: string, type: "doc" | "notebook" | "av", posi
         ].map(([unicode, title], index) =>
         ].map(([unicode, title], index) =>
             `<div data-type="${index}" class="emojis__type ariaLabel" aria-label="${title}">${unicode2Emoji(unicode)}</div>`
             `<div data-type="${index}" class="emojis__type ariaLabel" aria-label="${title}">${unicode2Emoji(unicode)}</div>`
         ).join("")}
         ).join("")}
+            </div>
+        </div>
+        <div class="fn__none" data-type="tab-dynamic">
+            <div class="fn__flex emoji__dynamic-color">
+                <div class="color__square fn__pointer" style="background-color:#d23f31"></div>
+                <div class="color__square fn__pointer" style="background-color:#3575f0"></div>
+                <div class="color__square fn__pointer" style="background-color:#f3a92f"></div>
+                <div class="color__square fn__pointer" style="background-color:#65b84d"></div>
+                <div class="color__square fn__pointer" style="background-color:#e099ff"></div>
+                <div class="color__square fn__pointer" style="background-color:#ea5d97"></div>
+                <div class="color__square fn__pointer" style="background-color:#93627f"></div>
+                <div class="color__square fn__pointer" style="background-color:#5f6368"></div>
+                <div class="fn__space--small"></div>
+                <input type="text" class="b3-text-field fn__flex-1 fn__flex-center" style="background-color: #d23f31;color:#fff" value="#d23f31">
+            </div>
+            <div class="fn__flex">
+                <span class="fn__space"></span>
+                <span class="fn__flex-center ft__on-surface">${window.siyuan.languages.language}</span>
+                <span class="fn__space--small"></span>
+                <select class="b3-select fn__flex-1">
+                    <option value="" selected>${window.siyuan.languages.themeOS}</option>
+                    <option value="en_US">English (en_US)</option>
+                    <option value="zh_CHT">繁體中文 (zh_CHT)</option>
+                    <option value="zh_CN">简体中文 (zh_CN)</option>
+                </select>
+                <span class="fn__space"></span>
+            </div>
+            <div class="fn__hr"></div>
+            <div class="fn__flex">
+                <span class="fn__space"></span>
+                <span class="fn__flex-center ft__on-surface">${window.siyuan.languages.date}</span>
+                <span class="fn__space--small"></span>
+                <input type="date" class="b3-text-field fn__flex-1"/>
+                <span class="fn__space"></span>
+            </div>
+            <div class="fn__hr"></div>
+            <div class="fn__flex">
+                <span class="fn__space"></span>
+                <span class="fn__flex-center ft__on-surface">${window.siyuan.languages.format}</span>
+                <span class="fn__space--small"></span>
+                <select class="b3-select fn__flex-1">
+                    <option value="1" selected>周日/Sun</option>
+                    <option value="2">周天/SUN</option>
+                    <option value="3">星期日/Sunday</option>
+                    <option value="4">星期天/SUNDAY</option>
+                </select>
+                <span class="fn__space"></span>
+            </div>
+            <div class="fn__flex fn__flex-wrap">
+                <img class="emoji__dynamic-item" src="${dynamicURL}type=1&color=d23f31">
+                <img class="emoji__dynamic-item" src="${dynamicURL}type=2&color=d23f31">
+                <img class="emoji__dynamic-item" src="${dynamicURL}type=3&color=d23f31">
+                <img class="emoji__dynamic-item" src="${dynamicURL}type=4&color=d23f31">
+                <img class="emoji__dynamic-item" src="${dynamicURL}type=5&color=d23f31">
+                <img class="emoji__dynamic-item" src="${dynamicURL}type=6&color=d23f31">
+                <img class="emoji__dynamic-item" src="${dynamicURL}type=7&color=d23f31">
+            </div>
+            <div class="fn__hr"></div>
+            <div class="fn__flex">
+                <span class="fn__space"></span>
+                <span class="fn__flex-center ft__on-surface">${window.siyuan.languages.custom}</span>
+                <span class="fn__space--small"></span>
+                <input type="text" class="b3-text-field fn__flex-1" value="SiYuan">
+                <span class="fn__space"></span>
+            </div>
+            <div>
+                <img data-type="text" class="emoji__dynamic-item" src="${dynamicURL}type=8&color=d23f31&content=SiYuan">
+            </div>
+        </div>
     </div>
     </div>
 </div>`
 </div>`
     });
     });
@@ -241,42 +322,45 @@ export const openEmojiPanel = (id: string, type: "doc" | "notebook" | "av", posi
     const dialogElement = dialog.element.querySelector(".b3-dialog") as HTMLElement;
     const dialogElement = dialog.element.querySelector(".b3-dialog") as HTMLElement;
     dialogElement.style.justifyContent = "inherit";
     dialogElement.style.justifyContent = "inherit";
     dialogElement.style.alignItems = "inherit";
     dialogElement.style.alignItems = "inherit";
+    const currentTab = window.siyuan.storage[Constants.LOCAL_EMOJIS].currentTab;
+    dialog.element.querySelector(`.emojis__tabheader [data-type="tab-${currentTab}"]`).classList.add("block__icon--active");
+    dialog.element.querySelector(`.emojis__tabbody [data-type="tab-${currentTab}"]`).classList.remove("fn__none");
     setPosition(dialog.element.querySelector(".b3-dialog__container"), position.x, position.y, position.h, position.w);
     setPosition(dialog.element.querySelector(".b3-dialog__container"), position.x, position.y, position.h, position.w);
     dialog.element.querySelector(".emojis__item").classList.add("emojis__item--current");
     dialog.element.querySelector(".emojis__item").classList.add("emojis__item--current");
-    const inputElement = dialog.element.querySelector(".b3-text-field") as HTMLInputElement;
+    const emojiSearchInputElement = dialog.element.querySelector('[data-type="tab-emoji"] .b3-text-field') as HTMLInputElement;
     const emojisContentElement = dialog.element.querySelector(".emojis__panel");
     const emojisContentElement = dialog.element.querySelector(".emojis__panel");
-    inputElement.addEventListener("compositionend", () => {
-        emojisContentElement.innerHTML = filterEmoji(inputElement.value);
-        if (inputElement.value) {
+    emojiSearchInputElement.addEventListener("compositionend", () => {
+        emojisContentElement.innerHTML = filterEmoji(emojiSearchInputElement.value);
+        if (emojiSearchInputElement.value) {
             emojisContentElement.nextElementSibling.classList.add("fn__none");
             emojisContentElement.nextElementSibling.classList.add("fn__none");
         } else {
         } else {
             emojisContentElement.nextElementSibling.classList.remove("fn__none");
             emojisContentElement.nextElementSibling.classList.remove("fn__none");
         }
         }
         emojisContentElement.scrollTop = 0;
         emojisContentElement.scrollTop = 0;
         dialog.element.querySelector(".emojis__item")?.classList.add("emojis__item--current");
         dialog.element.querySelector(".emojis__item")?.classList.add("emojis__item--current");
-        if (inputElement.value === "") {
+        if (emojiSearchInputElement.value === "") {
             lazyLoadEmoji(dialog.element);
             lazyLoadEmoji(dialog.element);
         }
         }
         lazyLoadEmojiImg(dialog.element);
         lazyLoadEmojiImg(dialog.element);
     });
     });
-    inputElement.addEventListener("input", (event: InputEvent) => {
+    emojiSearchInputElement.addEventListener("input", (event: InputEvent) => {
         if (event.isComposing) {
         if (event.isComposing) {
             return;
             return;
         }
         }
-        emojisContentElement.innerHTML = filterEmoji(inputElement.value);
-        if (inputElement.value) {
+        emojisContentElement.innerHTML = filterEmoji(emojiSearchInputElement.value);
+        if (emojiSearchInputElement.value) {
             emojisContentElement.nextElementSibling.classList.add("fn__none");
             emojisContentElement.nextElementSibling.classList.add("fn__none");
         } else {
         } else {
             emojisContentElement.nextElementSibling.classList.remove("fn__none");
             emojisContentElement.nextElementSibling.classList.remove("fn__none");
         }
         }
         emojisContentElement.scrollTop = 0;
         emojisContentElement.scrollTop = 0;
         dialog.element.querySelector(".emojis__item")?.classList.add("emojis__item--current");
         dialog.element.querySelector(".emojis__item")?.classList.add("emojis__item--current");
-        if (inputElement.value === "") {
+        if (emojiSearchInputElement.value === "") {
             lazyLoadEmoji(dialog.element);
             lazyLoadEmoji(dialog.element);
         }
         }
         lazyLoadEmojiImg(dialog.element);
         lazyLoadEmojiImg(dialog.element);
     });
     });
-    inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
+    emojiSearchInputElement.addEventListener("keydown", (event: KeyboardEvent) => {
         if (event.isComposing) {
         if (event.isComposing) {
             return;
             return;
         }
         }
@@ -379,7 +463,7 @@ export const openEmojiPanel = (id: string, type: "doc" | "notebook" | "av", posi
         }
         }
         if (newCurrentElement) {
         if (newCurrentElement) {
             newCurrentElement.classList.add("emojis__item--current");
             newCurrentElement.classList.add("emojis__item--current");
-            const inputHeight = inputElement.clientHeight + 6;
+            const inputHeight = emojiSearchInputElement.clientHeight + 6;
             if (newCurrentElement.offsetTop - inputHeight < emojisContentElement.scrollTop) {
             if (newCurrentElement.offsetTop - inputHeight < emojisContentElement.scrollTop) {
                 emojisContentElement.scrollTop = newCurrentElement.offsetTop - inputHeight - 6;
                 emojisContentElement.scrollTop = newCurrentElement.offsetTop - inputHeight - 6;
             } else if (newCurrentElement.offsetTop - inputHeight - emojisContentElement.clientHeight + newCurrentElement.clientHeight > emojisContentElement.scrollTop) {
             } else if (newCurrentElement.offsetTop - inputHeight - emojisContentElement.clientHeight + newCurrentElement.clientHeight > emojisContentElement.scrollTop) {
@@ -387,94 +471,159 @@ export const openEmojiPanel = (id: string, type: "doc" | "notebook" | "av", posi
             }
             }
         }
         }
     });
     });
-    if (!isMobile()) {
-        inputElement.focus();
+    if (!isMobile() && currentTab === "emoji") {
+        emojiSearchInputElement.focus();
     }
     }
     lazyLoadEmoji(dialog.element);
     lazyLoadEmoji(dialog.element);
     lazyLoadEmojiImg(dialog.element);
     lazyLoadEmojiImg(dialog.element);
     // 不能使用 getEventName 否则 https://github.com/siyuan-note/siyuan/issues/5472
     // 不能使用 getEventName 否则 https://github.com/siyuan-note/siyuan/issues/5472
     dialog.element.addEventListener("click", (event) => {
     dialog.element.addEventListener("click", (event) => {
-        const eventTarget = event.target as HTMLElement;
-        const typeElement = hasClosestByClassName(eventTarget, "emojis__type");
-        if (typeElement) {
-            const titleElement = emojisContentElement.querySelector(`[data-type="${typeElement.getAttribute("data-type")}"]`) as HTMLElement;
-            if (titleElement) {
-                const index = titleElement.nextElementSibling.getAttribute("data-index");
-                if (index) {
-                    let html = "";
-                    window.siyuan.emojis[parseInt(index)].items.forEach(emoji => {
-                        html += `<button data-unicode="${emoji.unicode}" class="emojis__item ariaLabel" aria-label="${getEmojiDesc(emoji)}">
+        let target = event.target as HTMLElement;
+        while (target && target !== dialog.element) {
+            if (target.classList.contains("emojis__type")) {
+                const titleElement = emojisContentElement.querySelector(`[data-type="${target.getAttribute("data-type")}"]`) as HTMLElement;
+                if (titleElement) {
+                    const index = titleElement.nextElementSibling.getAttribute("data-index");
+                    if (index) {
+                        let html = "";
+                        window.siyuan.emojis[parseInt(index)].items.forEach(emoji => {
+                            html += `<button data-unicode="${emoji.unicode}" class="emojis__item ariaLabel" aria-label="${getEmojiDesc(emoji)}">
 ${unicode2Emoji(emoji.unicode)}</button>`;
 ${unicode2Emoji(emoji.unicode)}</button>`;
+                        });
+                        titleElement.nextElementSibling.innerHTML = html;
+                        titleElement.nextElementSibling.removeAttribute("data-index");
+                    }
+
+                    emojisContentElement.scrollTo({
+                        top: titleElement.offsetTop - 77,
+                        // behavior: "smooth"  不能使用,否则无法定位
                     });
                     });
-                    titleElement.nextElementSibling.innerHTML = html;
-                    titleElement.nextElementSibling.removeAttribute("data-index");
                 }
                 }
-
-                emojisContentElement.scrollTo({
-                    top: titleElement.offsetTop - 34,
-                    // behavior: "smooth"  不能使用,否则无法定位
-                });
-            }
-            return;
-        }
-        const iconElement = hasClosestByClassName(eventTarget, "block__icon");
-        if (iconElement && iconElement.getAttribute("aria-label") === window.siyuan.languages.remove) {
-            if (type === "notebook") {
-                fetchPost("/api/notebook/setNotebookIcon", {
-                    notebook: id,
-                    icon: ""
-                }, () => {
-                    dialog.destroy();
-                    updateFileTreeEmoji("", id, "iconFilesRoot");
-                });
-            } else if (type === "doc") {
-                fetchPost("/api/attr/setBlockAttrs", {
-                    id: id,
-                    attrs: {"icon": ""}
-                }, () => {
-                    dialog.destroy();
-                    updateFileTreeEmoji("", id);
-                    updateOutlineEmoji("", id);
-                });
-            } else {
-                avCB("");
-            }
-            return;
-        }
-        const emojiElement = hasClosestByClassName(eventTarget, "emojis__item");
-        if (emojiElement || iconElement) {
-            let unicode = "";
-            if (emojiElement) {
-                unicode = emojiElement.getAttribute("data-unicode");
-                if (type !== "av") {
-                    dialog.destroy();
+                break;
+            } else if (target.getAttribute("data-action") === "remove") {
+                if (type === "notebook") {
+                    fetchPost("/api/notebook/setNotebookIcon", {
+                        notebook: id,
+                        icon: ""
+                    }, () => {
+                        dialog.destroy();
+                        updateFileTreeEmoji("", id, "iconFilesRoot");
+                    });
+                } else if (type === "doc") {
+                    fetchPost("/api/attr/setBlockAttrs", {
+                        id: id,
+                        attrs: {"icon": ""}
+                    }, () => {
+                        dialog.destroy();
+                        updateFileTreeEmoji("", id);
+                        updateOutlineEmoji("", id);
+                    });
+                } else {
+                    avCB("");
                 }
                 }
-            } else {
-                // 随机
-                unicode = getRandomEmoji();
+                break;
+            } else if (target.classList.contains("emojis__item") || target.getAttribute("data-action") === "random" || target.classList.contains("emoji__dynamic-item")) {
+                let unicode = "";
+                if (target.classList.contains("emojis__item")) {
+                    unicode = target.getAttribute("data-unicode");
+                    if (type !== "av") {
+                        dialog.destroy();
+                    }
+                } else if (target.classList.contains("emoji__dynamic-item")) {
+                    unicode = target.getAttribute("src");
+                } else {
+                    // 随机
+                    unicode = getRandomEmoji();
+                }
+                if (type === "notebook") {
+                    fetchPost("/api/notebook/setNotebookIcon", {
+                        notebook: id,
+                        icon: unicode
+                    }, () => {
+                        addEmoji(unicode);
+                        updateFileTreeEmoji(unicode, id, "iconFilesRoot");
+                    });
+                } else if (type === "doc") {
+                    fetchPost("/api/attr/setBlockAttrs", {
+                        id,
+                        attrs: {"icon": unicode}
+                    }, () => {
+                        addEmoji(unicode);
+                        updateFileTreeEmoji(unicode, id);
+                        updateOutlineEmoji(unicode, id);
+                    });
+                } else {
+                    avCB(unicode);
+                }
+                break
+            } else if (target.getAttribute("data-type")?.startsWith("tab-")) {
+                dialogElement.querySelectorAll('.emojis__tabheader [data-type|="tab"]').forEach((item: HTMLElement) => {
+                    if (item.dataset.type === target.dataset.type) {
+                        item.classList.add("block__icon--active")
+                    } else {
+                        item.classList.remove("block__icon--active")
+                    }
+                })
+                dialogElement.querySelectorAll('.emojis__tabbody > div').forEach((item: HTMLElement) => {
+                    if (item.dataset.type === target.dataset.type) {
+                        item.classList.remove("fn__none")
+                    } else {
+                        item.classList.add("fn__none")
+                    }
+                })
+                window.siyuan.storage[Constants.LOCAL_EMOJIS].currentTab = target.dataset.type.replace("tab-", "");
+                setStorageVal(Constants.LOCAL_EMOJIS, window.siyuan.storage[Constants.LOCAL_EMOJIS]);
+                break
+            } else if (target.classList.contains("color__square")) {
+                dynamicTextElements[0].value = target.getAttribute("style").replace("background-color:", "");
+                dynamicTextElements[0].style.backgroundColor = dynamicTextElements[0].value;
+                dynamicTextElements[0].dispatchEvent(new CustomEvent("input"))
+                break;
             }
             }
-            if (type === "notebook") {
-                fetchPost("/api/notebook/setNotebookIcon", {
-                    notebook: id,
-                    icon: unicode
-                }, () => {
-                    addEmoji(unicode);
-                    updateFileTreeEmoji(unicode, id, "iconFilesRoot");
-                });
-            } else if (type === "doc") {
-                fetchPost("/api/attr/setBlockAttrs", {
-                    id,
-                    attrs: {"icon": unicode}
-                }, () => {
-                    addEmoji(unicode);
-                    updateFileTreeEmoji(unicode, id);
-                    updateOutlineEmoji(unicode, id);
-                });
+            target = target.parentElement;
+        }
+    });
+    const dynamicLangElements: NodeListOf<HTMLSelectElement> = dialog.element.querySelectorAll('[data-type="tab-dynamic"] .b3-select')
+    dynamicLangElements[0].addEventListener("change", () => {
+        dialog.element.querySelectorAll(".fn__flex-wrap .emoji__dynamic-item").forEach(item => {
+            const url = new URLSearchParams(item.getAttribute("src").replace(dynamicURL, ""));
+            if (dynamicLangElements[0].value) {
+                url.set("lang", dynamicLangElements[0].value);
             } else {
             } else {
-                avCB(unicode);
+                url.delete("lang");
             }
             }
-            return;
-        }
+            item.setAttribute("src", dynamicURL + url.toString());
+        })
+    });
+    dynamicLangElements[1].addEventListener("change", () => {
+        dialog.element.querySelectorAll(".fn__flex-wrap .emoji__dynamic-item").forEach(item => {
+            const url = new URLSearchParams(item.getAttribute("src").replace(dynamicURL, ""));
+            url.set("weekdayType", dynamicLangElements[1].value);
+            item.setAttribute("src", dynamicURL + url.toString());
+        })
+    });
+    const dynamicDateElement = dialog.element.querySelector('[data-type="tab-dynamic"] [type="date"]') as HTMLInputElement
+    dynamicDateElement.addEventListener("change", () => {
+        dialog.element.querySelectorAll(".fn__flex-wrap .emoji__dynamic-item").forEach(item => {
+            const url = new URLSearchParams(item.getAttribute("src").replace(dynamicURL, ""));
+            url.set("date", dynamicDateElement.value ? dayjs(dynamicDateElement.value).format("YYYY-MM-DD") : "");
+            item.setAttribute("src", dynamicURL + url.toString());
+        })
+    });
+    const dynamicTextElements: NodeListOf<HTMLInputElement> = dialog.element.querySelectorAll('[data-type="tab-dynamic"] [type="text"]')
+    const dynamicTextImgElement = dialog.element.querySelector('.emoji__dynamic-item[data-type="text"]')
+    dynamicTextElements[0].addEventListener("input", () => {
+        dialog.element.querySelectorAll(".emoji__dynamic-item").forEach(item => {
+            const url = new URLSearchParams(item.getAttribute("src").replace(dynamicURL, ""));
+            url.set("color", dynamicTextElements[0].value);
+            item.setAttribute("src", dynamicURL + url.toString());
+            dynamicTextElements[0].style.backgroundColor = dynamicTextElements[0].value;
+        })
+    });
+    dynamicTextElements[1].addEventListener("input", () => {
+        const url = new URLSearchParams(dynamicTextImgElement.getAttribute("src").replace(dynamicURL, ""));
+        url.set("content", dynamicTextElements[1].value);
+        dynamicTextImgElement.setAttribute("src", dynamicURL + url.toString());
     });
     });
 };
 };
 
 

+ 4 - 1
app/src/protyle/util/compatibility.ts

@@ -251,6 +251,9 @@ export const getLocalStorage = (cb: () => void) => {
             note: "1f5c3",
             note: "1f5c3",
             folder: "1f4d1"
             folder: "1f4d1"
         };
         };
+        defaultStorage[Constants.LOCAL_EMOJIS] = {
+            currentTab: "emoji"
+        };
         defaultStorage[Constants.LOCAL_FONTSTYLES] = [];
         defaultStorage[Constants.LOCAL_FONTSTYLES] = [];
         defaultStorage[Constants.LOCAL_FILESPATHS] = [];    // filesPath[]
         defaultStorage[Constants.LOCAL_FILESPATHS] = [];    // filesPath[]
         defaultStorage[Constants.LOCAL_SEARCHDATA] = {
         defaultStorage[Constants.LOCAL_SEARCHDATA] = {
@@ -288,7 +291,7 @@ export const getLocalStorage = (cb: () => void) => {
             Constants.LOCAL_PLUGINTOPUNPIN, Constants.LOCAL_SEARCHASSET, Constants.LOCAL_FLASHCARD,
             Constants.LOCAL_PLUGINTOPUNPIN, Constants.LOCAL_SEARCHASSET, Constants.LOCAL_FLASHCARD,
             Constants.LOCAL_DIALOGPOSITION, Constants.LOCAL_SEARCHUNREF, Constants.LOCAL_HISTORY,
             Constants.LOCAL_DIALOGPOSITION, Constants.LOCAL_SEARCHUNREF, Constants.LOCAL_HISTORY,
             Constants.LOCAL_OUTLINE, Constants.LOCAL_FILEPOSITION, Constants.LOCAL_FILESPATHS, Constants.LOCAL_IMAGES,
             Constants.LOCAL_OUTLINE, Constants.LOCAL_FILEPOSITION, Constants.LOCAL_FILESPATHS, Constants.LOCAL_IMAGES,
-            Constants.LOCAL_PLUGIN_DOCKS].forEach((key) => {
+            Constants.LOCAL_PLUGIN_DOCKS, Constants.LOCAL_EMOJIS].forEach((key) => {
             if (typeof response.data[key] === "string") {
             if (typeof response.data[key] === "string") {
                 try {
                 try {
                     const parseData = JSON.parse(response.data[key]);
                     const parseData = JSON.parse(response.data[key]);