diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index 09460ca15..5fcacd004 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -1,4 +1,6 @@ { + "backRelation": "双向关联", + "thisDatabase": "当前数据表格", "relatedTo": "关联至", "relation": "关联", "rollup": "查询引用", diff --git a/app/src/assets/scss/component/_list.scss b/app/src/assets/scss/component/_list.scss index d6df40971..2c07bf97d 100644 --- a/app/src/assets/scss/component/_list.scss +++ b/app/src/assets/scss/component/_list.scss @@ -56,7 +56,7 @@ } &--narrow { - margin: 0; + margin: 1px 0; } &--big { @@ -79,6 +79,7 @@ .b3-list-item__meta { margin-left: 0; + line-height: 18px; } } diff --git a/app/src/menus/Menu.ts b/app/src/menus/Menu.ts index 67c8a5a74..8e3c725ec 100644 --- a/app/src/menus/Menu.ts +++ b/app/src/menus/Menu.ts @@ -162,6 +162,15 @@ export class MenuItem { public element: HTMLElement; constructor(options: IMenu) { + if (options.type === "empty") { + this.element = document.createElement("div"); + this.element.innerHTML = options.label; + if (options.bind) { + options.bind(this.element); + } + return; + } + this.element = document.createElement("button"); if (options.disabled) { this.element.setAttribute("disabled", "disabled"); diff --git a/app/src/mobile/menu/search.ts b/app/src/mobile/menu/search.ts index 35c2ec37a..9d4752b09 100644 --- a/app/src/mobile/menu/search.ts +++ b/app/src/mobile/menu/search.ts @@ -190,7 +190,7 @@ ${unicode2Emoji(childItem.ial.icon, "b3-list-item__graphic", true)} ${unicode2Emoji(item.ial.icon, "b3-list-item__graphic", true)} ${item.content} -${escapeGreat(title)} +${escapeGreat(title)} `; } }); diff --git a/app/src/plugin/Menu.ts b/app/src/plugin/Menu.ts index 2d00739cb..5e97a2448 100644 --- a/app/src/plugin/Menu.ts +++ b/app/src/plugin/Menu.ts @@ -41,7 +41,7 @@ export class Menu { return this.menu.addSeparator(index); } - open(options:IPosition) { + open(options: IPosition) { if (this.isOpen) { return; } diff --git a/app/src/protyle/hint/extend.ts b/app/src/protyle/hint/extend.ts index 8d517ae31..848e4ce87 100644 --- a/app/src/protyle/hint/extend.ts +++ b/app/src/protyle/hint/extend.ts @@ -350,7 +350,7 @@ export const genHintItemHTML = (item: IBlock) => { ${iconHTML} ${item.content} -
${item.hPath}
`; +
${item.hPath}
`; }; export const hintRef = (key: string, protyle: IProtyle, source: THintSource): IHintData[] => { diff --git a/app/src/protyle/render/av/col.ts b/app/src/protyle/render/av/col.ts index b5ba506cd..6d4bdf539 100644 --- a/app/src/protyle/render/av/col.ts +++ b/app/src/protyle/render/av/col.ts @@ -148,23 +148,21 @@ export const getEditHTML = (options: { `; } else if (colData.type === "relation") { - const databaseName = "TODO" html += ` -`; +
+ + +
`; } return `
${html} diff --git a/app/src/protyle/render/av/openMenuPanel.ts b/app/src/protyle/render/av/openMenuPanel.ts index 1eee816b4..bedde9b0f 100644 --- a/app/src/protyle/render/av/openMenuPanel.ts +++ b/app/src/protyle/render/av/openMenuPanel.ts @@ -26,7 +26,7 @@ import {removeBlock} from "../../wysiwyg/remove"; import {getEditorRange} from "../../util/selection"; import {avRender} from "./render"; import {setPageSize} from "./row"; -import {openSearchAV} from "./relation"; +import {openSearchAV, updateRelation} from "./relation"; export const openMenuPanel = (options: { protyle: IProtyle, @@ -740,8 +740,16 @@ export const openMenuPanel = (options: { event.stopPropagation(); break; } else if (type === "goSearchAV") { - openSearchAV(); - setPosition(menuElement, tabRect.right - menuElement.clientWidth, tabRect.bottom, tabRect.height); + openSearchAV(avID, target); + event.preventDefault(); + event.stopPropagation(); + break; + } else if (type === "updateRelation") { + updateRelation({ + protyle: options.protyle, + avElement: avPanelElement, + avID + }); event.preventDefault(); event.stopPropagation(); break; diff --git a/app/src/protyle/render/av/relation.ts b/app/src/protyle/render/av/relation.ts index 3829c3914..cb42223aa 100644 --- a/app/src/protyle/render/av/relation.ts +++ b/app/src/protyle/render/av/relation.ts @@ -1,31 +1,52 @@ import {Menu} from "../../../plugin/Menu"; -import {isMobile} from "../../../util/functions"; -import {hasClosestByAttribute, hasClosestByClassName} from "../../util/hasClosest"; -import {renderAssetsPreview} from "../../../asset/renderAssets"; +import {hasClosestByClassName} from "../../util/hasClosest"; import {upDownHint} from "../../../util/upDownHint"; -import {hintRenderAssets} from "../../hint/extend"; -import {focusByRange} from "../../util/selection"; import {fetchPost} from "../../../util/fetch"; +import {escapeHtml} from "../../../util/escape"; +import {transaction} from "../../wysiwyg/transaction"; -const genSearchList = (element: HTMLElement, keyword: string) => { +const genSearchList = (element: Element, keyword: string, avId: string, cb?: () => void) => { fetchPost("/api/av/searchAttributeView", {keyword}, (response) => { - let html = "" - response.data.forEach((item) => { - html += `
` + let html = ""; + response.data.results.forEach((item: { + avID: string + avName: string + blockID: string + hPath: string + }, index: number) => { + html += `
+
+
+ ${escapeHtml(item.avName || window.siyuan.languages.title)} +
+
${escapeHtml(item.hPath)}
+
+ +
` }); - element.lastElementChild.innerHTML = html; + element.innerHTML = html; + if (cb) { + cb() + } }) } -export const openSearchAV = () => { +const setDatabase = (element: HTMLElement, item: HTMLElement) => { + element.dataset.avId = item.dataset.avId; + element.dataset.blockId = item.dataset.blockId; + element.querySelector(".b3-menu__accelerator").textContent = item.querySelector(".b3-list-item__hinticon").classList.contains("fn__none") ? item.querySelector(".b3-list-item__text").textContent : window.siyuan.languages.thisDatabase +} + +export const openSearchAV = (avId: string, target: HTMLElement) => { window.siyuan.menus.menu.remove(); const menu = new Menu(); menu.addItem({ iconHTML: "", - type: "readonly", + type: "empty", label: `
- -
+ +
+
`, @@ -36,54 +57,56 @@ export const openSearchAV = () => { if (event.isComposing) { return; } - const isEmpty = element.querySelector(".b3-list--empty"); - if (!isEmpty) { - const currentElement = upDownHint(listElement, event); - if (currentElement) { - event.stopPropagation(); - } + const currentElement = upDownHint(listElement, event); + if (currentElement) { + event.stopPropagation(); } - if (event.key === "Enter") { - if (!isEmpty) { - const currentURL = element.querySelector(".b3-list-item--focus").getAttribute("data-value"); - - } else { - window.siyuan.menus.menu.remove(); - // focusByRange(protyle.toolbar.range); - } - // 空行处插入 mp3 会多一个空的 mp3 块 event.preventDefault(); event.stopPropagation(); + setDatabase(target, listElement.querySelector(".b3-list-item--focus")); + window.siyuan.menus.menu.remove(); } }); inputElement.addEventListener("input", (event) => { event.stopPropagation(); - genSearchList(element, inputElement.value); + genSearchList(listElement, inputElement.value, avId); }); element.lastElementChild.addEventListener("click", (event) => { - const target = event.target as HTMLElement; - const previousElement = hasClosestByAttribute(target, "data-type", "previous"); - if (previousElement) { - inputElement.dispatchEvent(new KeyboardEvent("keydown", {key: "ArrowUp"})); - event.stopPropagation(); - return; - } - const nextElement = hasClosestByAttribute(target, "data-type", "next"); - if (nextElement) { - inputElement.dispatchEvent(new KeyboardEvent("keydown", {key: "ArrowDown"})); - event.stopPropagation(); - return; - } - const listItemElement = hasClosestByClassName(target, "b3-list-item"); + const listItemElement = hasClosestByClassName(event.target as HTMLElement, "b3-list-item"); if (listItemElement) { event.stopPropagation(); - const currentURL = listItemElement.getAttribute("data-value"); - // hintRenderAssets(currentURL, protyle); + setDatabase(target, listItemElement) window.siyuan.menus.menu.remove(); } }); - genSearchList(element, ""); + genSearchList(listElement, "", avId, () => { + const rect = target.getBoundingClientRect(); + menu.open({ + x: rect.left, + y: rect.bottom, + h: rect.height, + }) + element.querySelector("input").focus(); + }); } }); + menu.element.querySelector(".b3-menu__items").setAttribute("style", "overflow: initial"); +} + +export const updateRelation = (options: { + protyle: IProtyle, + avID: string, + avElement: Element +}) => { + transaction(options.protyle, [{ + action: "updateAttrViewColRelation", + avID: options.avID, + id: options.avElement.querySelector('.b3-menu__item[data-type="goSearchAV"]').getAttribute("data-av-id"), + keyID: options.avElement.querySelector(".b3-menu__item").getAttribute("data-col-id"), // 源 av 关联列 ID + backRelationKeyID: Lute.NewNodeID(), // 双向关联的目标关联列 ID + isTwoWay: (options.avElement.querySelector(".b3-switch") as HTMLInputElement).checked, + name: (options.avElement.querySelector('input[data-type="colName"]') as HTMLInputElement).value, + }], []); + options.avElement.remove(); } diff --git a/app/src/protyle/wysiwyg/transaction.ts b/app/src/protyle/wysiwyg/transaction.ts index ed92196af..8c1ac0fd1 100644 --- a/app/src/protyle/wysiwyg/transaction.ts +++ b/app/src/protyle/wysiwyg/transaction.ts @@ -724,7 +724,7 @@ export const onTransaction = (protyle: IProtyle, operation: IOperation, isUndo: "setAttrViewSorts", "setAttrViewColCalc", "removeAttrViewCol", "updateAttrViewColNumberFormat", "removeAttrViewBlock", "replaceAttrViewBlock", "updateAttrViewColTemplate", "setAttrViewColPin", "addAttrViewView", "removeAttrViewView", "setAttrViewViewName", "setAttrViewViewIcon", "duplicateAttrViewView", "sortAttrViewView", - "setAttrViewPageSize"].includes(operation.action)) { + "updateAttrViewColRelation", "setAttrViewPageSize"].includes(operation.action)) { refreshAV(protyle, operation, isUndo); } else if (operation.action === "doUpdateUpdated") { updateElements.forEach(item => { diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts index ce7e3a76a..473b64482 100644 --- a/app/src/types/index.d.ts +++ b/app/src/types/index.d.ts @@ -49,6 +49,7 @@ type TOperation = | "duplicateAttrViewView" | "sortAttrViewView" | "setAttrViewPageSize" + | "updateAttrViewColRelation" type TBazaarType = "templates" | "icons" | "widgets" | "themes" | "plugins" type TCardType = "doc" | "notebook" | "all" type TEventBus = "ws-main" | "sync-start" | "sync-end" | "sync-fail" | @@ -439,6 +440,8 @@ interface IScrollAttr { interface IOperation { action: TOperation, // move, delete 不需要传 data id?: string, + isTwoWay?: boolean, // 是否双向关联 + backRelationKeyID?: string, // 双向关联的目标关联列 ID avID?: string, // av format?: string // updateAttrViewColNumberFormat 专享 keyID?: string // updateAttrViewCell 专享 @@ -981,7 +984,7 @@ interface IMenu { iconClass?: string, label?: string, click?: (element: HTMLElement, event: MouseEvent) => boolean | void | Promise - type?: "separator" | "submenu" | "readonly", + type?: "separator" | "submenu" | "readonly" | "empty", accelerator?: string, action?: string, id?: string, diff --git a/app/src/util/escape.ts b/app/src/util/escape.ts index b1a413ebb..c8e52b575 100644 --- a/app/src/util/escape.ts +++ b/app/src/util/escape.ts @@ -1,4 +1,7 @@ export const escapeHtml = (html: string) => { + if (!html) { + return html; + } return html.replace(/&/g, "&").replace(/