mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-22 09:30:14 +01:00
This commit is contained in:
parent
fb72af192e
commit
54ca2d6226
12 changed files with 112 additions and 65 deletions
|
|
@ -1,4 +1,6 @@
|
||||||
{
|
{
|
||||||
|
"backRelation": "双向关联",
|
||||||
|
"thisDatabase": "当前数据表格",
|
||||||
"relatedTo": "关联至",
|
"relatedTo": "关联至",
|
||||||
"relation": "关联",
|
"relation": "关联",
|
||||||
"rollup": "查询引用",
|
"rollup": "查询引用",
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&--narrow {
|
&--narrow {
|
||||||
margin: 0;
|
margin: 1px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--big {
|
&--big {
|
||||||
|
|
@ -79,6 +79,7 @@
|
||||||
|
|
||||||
.b3-list-item__meta {
|
.b3-list-item__meta {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,15 @@ export class MenuItem {
|
||||||
public element: HTMLElement;
|
public element: HTMLElement;
|
||||||
|
|
||||||
constructor(options: IMenu) {
|
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");
|
this.element = document.createElement("button");
|
||||||
if (options.disabled) {
|
if (options.disabled) {
|
||||||
this.element.setAttribute("disabled", "disabled");
|
this.element.setAttribute("disabled", "disabled");
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ ${unicode2Emoji(childItem.ial.icon, "b3-list-item__graphic", true)}
|
||||||
${unicode2Emoji(item.ial.icon, "b3-list-item__graphic", true)}
|
${unicode2Emoji(item.ial.icon, "b3-list-item__graphic", true)}
|
||||||
<span class="b3-list-item__text">${item.content}</span>
|
<span class="b3-list-item__text">${item.content}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="b3-list-item__text b3-list-item__meta" style="margin-top: -4px">${escapeGreat(title)}</span>
|
<span class="b3-list-item__text b3-list-item__meta">${escapeGreat(title)}</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -350,7 +350,7 @@ export const genHintItemHTML = (item: IBlock) => {
|
||||||
${iconHTML}
|
${iconHTML}
|
||||||
<span class="b3-list-item__text">${item.content}</span>
|
<span class="b3-list-item__text">${item.content}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="b3-list-item__meta b3-list-item__showall" style="margin-bottom: 4px">${item.hPath}</div>`;
|
<div class="b3-list-item__meta b3-list-item__showall">${item.hPath}</div>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hintRef = (key: string, protyle: IProtyle, source: THintSource): IHintData[] => {
|
export const hintRef = (key: string, protyle: IProtyle, source: THintSource): IHintData[] => {
|
||||||
|
|
|
||||||
|
|
@ -148,23 +148,21 @@ export const getEditHTML = (options: {
|
||||||
<textarea rows="${colData.template.split("\n").length}" placeholder="${window.siyuan.languages.template}" data-type="updateTemplate" style="margin: 4px 0" rows="1" class="fn__block b3-text-field">${colData.template}</textarea>
|
<textarea rows="${colData.template.split("\n").length}" placeholder="${window.siyuan.languages.template}" data-type="updateTemplate" style="margin: 4px 0" rows="1" class="fn__block b3-text-field">${colData.template}</textarea>
|
||||||
</button>`;
|
</button>`;
|
||||||
} else if (colData.type === "relation") {
|
} else if (colData.type === "relation") {
|
||||||
const databaseName = "TODO"
|
|
||||||
html += `<button class="b3-menu__item" data-type="goSearchAV">
|
html += `<button class="b3-menu__item" data-type="goSearchAV">
|
||||||
<span class="b3-menu__label">${window.siyuan.languages.relatedTo}</span>
|
<span class="b3-menu__label">${window.siyuan.languages.relatedTo}</span>
|
||||||
<span class="fn__space"></span>
|
<span class="b3-menu__accelerator">TODO</span>
|
||||||
<svg class="b3-menu__icon"><use xlink:href="#iconDatabase"></use></svg>
|
|
||||||
<span class="b3-menu__accelerator" style="margin-left: 0">${databaseName}</span>
|
|
||||||
<svg class="b3-menu__icon b3-menu__icon--small"><use xlink:href="#iconRight"></use></svg>
|
<svg class="b3-menu__icon b3-menu__icon--small"><use xlink:href="#iconRight"></use></svg>
|
||||||
</button>
|
</button>
|
||||||
<label class="b3-menu__item">
|
<label class="b3-menu__item">
|
||||||
<span class="fn__flex-center">${databaseName}</span>
|
<span class="fn__flex-center">${window.siyuan.languages.backRelation}</span>
|
||||||
<svg class="b3-menu__icon b3-menu__icon--small"><use xlink:href="#iconHelp"></use></svg>
|
<svg class="b3-menu__icon b3-menu__icon--small fn__none"><use xlink:href="#iconHelp"></use></svg>
|
||||||
<span class="fn__space fn__flex-1"></span>
|
<span class="fn__space fn__flex-1"></span>
|
||||||
<input type="checkbox" class="b3-switch b3-switch--menu">
|
<input type="checkbox" class="b3-switch b3-switch--menu">
|
||||||
</label>
|
</label>
|
||||||
<label class="b3-menu__item" data-type="nobg">
|
<div class="b3-menu__item fn__flex-column" data-type="nobg">
|
||||||
<input class="b3-text-field fn__block" style="margin: 4px" placeholder="todo">
|
<input data-type="colName" style="margin-top: 8px" class="b3-text-field fn__block" placeholder="${window.siyuan.languages.title}">
|
||||||
</label>`;
|
<button style="margin: 8px 0" class="b3-button fn__block" data-type="updateRelation">${window.siyuan.languages.confirm}</button>
|
||||||
|
</div>`;
|
||||||
}
|
}
|
||||||
return `<div class="b3-menu__items">
|
return `<div class="b3-menu__items">
|
||||||
${html}
|
${html}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import {removeBlock} from "../../wysiwyg/remove";
|
||||||
import {getEditorRange} from "../../util/selection";
|
import {getEditorRange} from "../../util/selection";
|
||||||
import {avRender} from "./render";
|
import {avRender} from "./render";
|
||||||
import {setPageSize} from "./row";
|
import {setPageSize} from "./row";
|
||||||
import {openSearchAV} from "./relation";
|
import {openSearchAV, updateRelation} from "./relation";
|
||||||
|
|
||||||
export const openMenuPanel = (options: {
|
export const openMenuPanel = (options: {
|
||||||
protyle: IProtyle,
|
protyle: IProtyle,
|
||||||
|
|
@ -740,8 +740,16 @@ export const openMenuPanel = (options: {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
break;
|
break;
|
||||||
} else if (type === "goSearchAV") {
|
} else if (type === "goSearchAV") {
|
||||||
openSearchAV();
|
openSearchAV(avID, target);
|
||||||
setPosition(menuElement, tabRect.right - menuElement.clientWidth, tabRect.bottom, tabRect.height);
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
break;
|
||||||
|
} else if (type === "updateRelation") {
|
||||||
|
updateRelation({
|
||||||
|
protyle: options.protyle,
|
||||||
|
avElement: avPanelElement,
|
||||||
|
avID
|
||||||
|
});
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,52 @@
|
||||||
import {Menu} from "../../../plugin/Menu";
|
import {Menu} from "../../../plugin/Menu";
|
||||||
import {isMobile} from "../../../util/functions";
|
import {hasClosestByClassName} from "../../util/hasClosest";
|
||||||
import {hasClosestByAttribute, hasClosestByClassName} from "../../util/hasClosest";
|
|
||||||
import {renderAssetsPreview} from "../../../asset/renderAssets";
|
|
||||||
import {upDownHint} from "../../../util/upDownHint";
|
import {upDownHint} from "../../../util/upDownHint";
|
||||||
import {hintRenderAssets} from "../../hint/extend";
|
|
||||||
import {focusByRange} from "../../util/selection";
|
|
||||||
import {fetchPost} from "../../../util/fetch";
|
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) => {
|
fetchPost("/api/av/searchAttributeView", {keyword}, (response) => {
|
||||||
let html = ""
|
let html = "";
|
||||||
response.data.forEach((item) => {
|
response.data.results.forEach((item: {
|
||||||
html += `<div class="b3-list-item" data-value="${item.url}">`
|
avID: string
|
||||||
|
avName: string
|
||||||
|
blockID: string
|
||||||
|
hPath: string
|
||||||
|
}, index: number) => {
|
||||||
|
html += `<div class="b3-list-item b3-list-item--narrow${index === 0 ? " b3-list-item--focus" : ""}" data-av-id="${item.avID}" data-block-id="${item.blockID}">
|
||||||
|
<div class="b3-list-item--two fn__flex-1">
|
||||||
|
<div class="b3-list-item__first">
|
||||||
|
<span class="b3-list-item__text">${escapeHtml(item.avName || window.siyuan.languages.title)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="b3-list-item__meta b3-list-item__showall">${escapeHtml(item.hPath)}</div>
|
||||||
|
</div>
|
||||||
|
<svg aria-label="${window.siyuan.languages.thisDatabase}" style="margin: 0 0 0 4px" class="b3-list-item__hinticon ariaLabel${item.avID === avId ? "" : " fn__none"}"><use xlink:href="#iconInfo"></use></svg>
|
||||||
|
</div>`
|
||||||
});
|
});
|
||||||
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();
|
window.siyuan.menus.menu.remove();
|
||||||
const menu = new Menu();
|
const menu = new Menu();
|
||||||
menu.addItem({
|
menu.addItem({
|
||||||
iconHTML: "",
|
iconHTML: "",
|
||||||
type: "readonly",
|
type: "empty",
|
||||||
label: `<div class="fn__flex-column" style = "min-width: 260px;max-width:420px;max-height: 50vh">
|
label: `<div class="fn__flex-column" style = "min-width: 260px;max-width:420px;max-height: 50vh">
|
||||||
<input class="b3-text-field fn__flex-1"/>
|
<input class="b3-text-field fn__flex-shrink"/>
|
||||||
<div class="b3-list fn__flex-1 b3-list--background" style="position: relative">
|
<div class="fn__hr"></div>
|
||||||
|
<div class="b3-list fn__flex-1 b3-list--background">
|
||||||
<img style="margin: 0 auto;display: block;width: 64px;height: 64px" src="/stage/loading-pure.svg">
|
<img style="margin: 0 auto;display: block;width: 64px;height: 64px" src="/stage/loading-pure.svg">
|
||||||
</div>
|
</div>
|
||||||
</div>`,
|
</div>`,
|
||||||
|
|
@ -36,54 +57,56 @@ export const openSearchAV = () => {
|
||||||
if (event.isComposing) {
|
if (event.isComposing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const isEmpty = element.querySelector(".b3-list--empty");
|
|
||||||
if (!isEmpty) {
|
|
||||||
const currentElement = upDownHint(listElement, event);
|
const currentElement = upDownHint(listElement, event);
|
||||||
if (currentElement) {
|
if (currentElement) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (event.key === "Enter") {
|
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.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
setDatabase(target, listElement.querySelector(".b3-list-item--focus"));
|
||||||
|
window.siyuan.menus.menu.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
inputElement.addEventListener("input", (event) => {
|
inputElement.addEventListener("input", (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
genSearchList(element, inputElement.value);
|
genSearchList(listElement, inputElement.value, avId);
|
||||||
});
|
});
|
||||||
element.lastElementChild.addEventListener("click", (event) => {
|
element.lastElementChild.addEventListener("click", (event) => {
|
||||||
const target = event.target as HTMLElement;
|
const listItemElement = hasClosestByClassName(event.target as HTMLElement, "b3-list-item");
|
||||||
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");
|
|
||||||
if (listItemElement) {
|
if (listItemElement) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const currentURL = listItemElement.getAttribute("data-value");
|
setDatabase(target, listItemElement)
|
||||||
// hintRenderAssets(currentURL, protyle);
|
|
||||||
window.siyuan.menus.menu.remove();
|
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();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -724,7 +724,7 @@ export const onTransaction = (protyle: IProtyle, operation: IOperation, isUndo:
|
||||||
"setAttrViewSorts", "setAttrViewColCalc", "removeAttrViewCol", "updateAttrViewColNumberFormat", "removeAttrViewBlock",
|
"setAttrViewSorts", "setAttrViewColCalc", "removeAttrViewCol", "updateAttrViewColNumberFormat", "removeAttrViewBlock",
|
||||||
"replaceAttrViewBlock", "updateAttrViewColTemplate", "setAttrViewColPin", "addAttrViewView",
|
"replaceAttrViewBlock", "updateAttrViewColTemplate", "setAttrViewColPin", "addAttrViewView",
|
||||||
"removeAttrViewView", "setAttrViewViewName", "setAttrViewViewIcon", "duplicateAttrViewView", "sortAttrViewView",
|
"removeAttrViewView", "setAttrViewViewName", "setAttrViewViewIcon", "duplicateAttrViewView", "sortAttrViewView",
|
||||||
"setAttrViewPageSize"].includes(operation.action)) {
|
"updateAttrViewColRelation", "setAttrViewPageSize"].includes(operation.action)) {
|
||||||
refreshAV(protyle, operation, isUndo);
|
refreshAV(protyle, operation, isUndo);
|
||||||
} else if (operation.action === "doUpdateUpdated") {
|
} else if (operation.action === "doUpdateUpdated") {
|
||||||
updateElements.forEach(item => {
|
updateElements.forEach(item => {
|
||||||
|
|
|
||||||
5
app/src/types/index.d.ts
vendored
5
app/src/types/index.d.ts
vendored
|
|
@ -49,6 +49,7 @@ type TOperation =
|
||||||
| "duplicateAttrViewView"
|
| "duplicateAttrViewView"
|
||||||
| "sortAttrViewView"
|
| "sortAttrViewView"
|
||||||
| "setAttrViewPageSize"
|
| "setAttrViewPageSize"
|
||||||
|
| "updateAttrViewColRelation"
|
||||||
type TBazaarType = "templates" | "icons" | "widgets" | "themes" | "plugins"
|
type TBazaarType = "templates" | "icons" | "widgets" | "themes" | "plugins"
|
||||||
type TCardType = "doc" | "notebook" | "all"
|
type TCardType = "doc" | "notebook" | "all"
|
||||||
type TEventBus = "ws-main" | "sync-start" | "sync-end" | "sync-fail" |
|
type TEventBus = "ws-main" | "sync-start" | "sync-end" | "sync-fail" |
|
||||||
|
|
@ -439,6 +440,8 @@ interface IScrollAttr {
|
||||||
interface IOperation {
|
interface IOperation {
|
||||||
action: TOperation, // move, delete 不需要传 data
|
action: TOperation, // move, delete 不需要传 data
|
||||||
id?: string,
|
id?: string,
|
||||||
|
isTwoWay?: boolean, // 是否双向关联
|
||||||
|
backRelationKeyID?: string, // 双向关联的目标关联列 ID
|
||||||
avID?: string, // av
|
avID?: string, // av
|
||||||
format?: string // updateAttrViewColNumberFormat 专享
|
format?: string // updateAttrViewColNumberFormat 专享
|
||||||
keyID?: string // updateAttrViewCell 专享
|
keyID?: string // updateAttrViewCell 专享
|
||||||
|
|
@ -981,7 +984,7 @@ interface IMenu {
|
||||||
iconClass?: string,
|
iconClass?: string,
|
||||||
label?: string,
|
label?: string,
|
||||||
click?: (element: HTMLElement, event: MouseEvent) => boolean | void | Promise<boolean | void>
|
click?: (element: HTMLElement, event: MouseEvent) => boolean | void | Promise<boolean | void>
|
||||||
type?: "separator" | "submenu" | "readonly",
|
type?: "separator" | "submenu" | "readonly" | "empty",
|
||||||
accelerator?: string,
|
accelerator?: string,
|
||||||
action?: string,
|
action?: string,
|
||||||
id?: string,
|
id?: string,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
export const escapeHtml = (html: string) => {
|
export const escapeHtml = (html: string) => {
|
||||||
|
if (!html) {
|
||||||
|
return html;
|
||||||
|
}
|
||||||
return html.replace(/&/g, "&").replace(/</g, "<");
|
return html.replace(/&/g, "&").replace(/</g, "<");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue