Advertisement
N1ito

DIY Dynamic Views (Obsidian)

May 18th, 2025 (edited)
120
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 22.35 KB | Source Code | 0 0
  1. ```dataviewjs
  2. const mb = app.plugins.getPlugin('obsidian-meta-bind-plugin')?.api;
  3. const filePath = dv.current().file.path;
  4. const bindTargets = {};
  5. const NULL_LABEL = "<none>";
  6. const logicDefaultValue = "OR";
  7. const propertyReferenceNote = "exampleDatabase/Mod Entry 1.md"; // <- fill this with a path to your reference note
  8.  
  9.  
  10. this.container.appendChild(createHeader("Filter"));
  11.  
  12. const filterRow1 = document.createElement("div");
  13. filterRow1.className = "mb-flex-row";
  14.  
  15. const searchList = document.createElement("div");
  16. searchList.appendChild(createInlineTextField("Search", "search", this));
  17. searchList.appendChild(createInlineListSuggester("Categories", "categories", this, true));
  18. filterRow1.appendChild(searchList);
  19.  
  20. this.container.appendChild(filterRow1);
  21.  
  22.  
  23.  
  24. this.container.appendChild(createHeader("View"));
  25.  
  26. const viewRow1 = document.createElement("div");
  27. viewRow1.className = "mb-flex-row";
  28.  
  29. const columnsList = document.createElement("div");
  30. columnsList.className = "mb-horizontal-row";
  31. columnsList.appendChild(createInlineSuggesterFromPropertiesInNote("Show Columns", "propertiesToShow", this, propertyReferenceNote, ["name", "image", "content"], this));
  32. columnsList.appendChild(createInlineNumberField("Line Width", "lineWidth", this, 1000));
  33. columnsList.appendChild(createInlineNumberField("Image Size", "imageSize", this, 150));
  34. viewRow1.appendChild(columnsList);
  35.  
  36. const groupList = document.createElement("div");
  37. groupList.className = "mb-horizontal-row";
  38. groupList.appendChild(createInlineSelectFromExistingPropertiesInNote("Group By", "groupByProperty", this, propertyReferenceNote));
  39. groupList.appendChild(createInlineSelectFromOptions("Group Sort", "groupSort", ["Alphabetical", "Group Size", "Numeric"], this, "Alphabetical"));
  40. groupList.appendChild(createInlineSelectFromOptions("Group Sort Order", "groupSortOrder", ["asc", "desc"], this, "asc"));
  41. groupList.appendChild(createInlineSelectFromExistingPropertiesInNote("Order By", "orderByProperty", this, propertyReferenceNote, "name"));
  42. groupList.appendChild(createInlineSelectFromOptions("Order", "order", ["asc", "desc"], this, "asc"));
  43. viewRow1.appendChild(groupList);
  44.  
  45. this.container.appendChild(viewRow1);
  46.  
  47.  
  48.  
  49. this.container.appendChild(createHeader("Actions"));
  50.  
  51. const resetAllButton = dv.el("button", "πŸ”„ Reset Defaults", { attr: { class: "mb-button-inner" } });
  52.  
  53. resetAllButton.addEventListener("click", () => {
  54. for (const [_, { bindTarget, defaultValue }] of Object.entries(bindTargets)) {
  55. mb.updateMetadata(bindTarget, () => defaultValue ?? []);
  56. }
  57. });
  58.  
  59.  
  60. this.container.appendChild(resetAllButton);
  61.  
  62. this.container.appendChild(document.createElement("br"));
  63. this.container.appendChild(document.createElement("br"));
  64.  
  65. const outputContainer = document.createElement("div");
  66. outputContainer.id = "dv-output-container";
  67. this.container.appendChild(outputContainer);
  68.  
  69. var button = dv.el("button", "πŸ” Search", { attr: { id: "dv-search-button", class: "mb-button-inner mod-cta" } });
  70. button.addEventListener("click", renderTable);
  71.  
  72.  
  73. function renderTable()
  74. {
  75. const containerBlock = dv.container.closest(".block-language-dataviewjs");
  76. let clear = false;
  77. for (let child of Array.from(containerBlock.children)) {
  78. if (clear)
  79. child.remove();
  80. if (child.id === "dv-search-button")
  81. clear = true;
  82. }
  83.  
  84. const lineWidth = getMetaData("lineWidth");
  85. if (lineWidth && !isNaN(lineWidth)) {
  86. const scopedElements = document.querySelectorAll(".dynamicQueries");
  87. scopedElements.forEach(scopedElement => scopedElement.style.setProperty('--file-line-width', `${lineWidth}px`, "important"));
  88. //scopedElement.offsetHeight;//triggers reflow
  89. }
  90.  
  91. let search = getMetaData("search");
  92. if(search != null && typeof search === "string")
  93. search = search.toLowerCase();
  94. const { values: categories, logic: categoriesLogic } = getFilterData("categories");
  95.  
  96. //TODO: refactor into filter class
  97. const orderByProperty = getMetaData("orderByProperty");
  98. const order = getMetaData("order");
  99.  
  100. const propertiesToShow = getMetaData("propertiesToShow");
  101.  
  102. const imageSize = getMetaData("imageSize") ?? "150";
  103.  
  104. const groupSort = getMetaData("groupSort") ?? "Alphabetical";
  105. const groupSortOrder = getMetaData("groupSortOrder") ?? "asc";
  106.  
  107. let results = dv.pages().where(p => {
  108. if (!p.type || p.type !== "Mod" || p.file.path.includes("Templates"))
  109. return false;
  110.  
  111. if (!matchesMultiValueField(p.categories, categories, categoriesLogic))
  112. return false;
  113.  
  114. if (!isUnset(search)) {
  115. const nameMatch = matchesCaseInsensitive(p.file.name, search);
  116. const aliasMatch = matchesAnyInCollection(search, p.file.aliases);
  117. //const content = await dv.io.load(p.file.path);//TODO: include non-bullet point content
  118. const contentMatch = matchesAnyInCollection(search, p.file.lists.map(l => l.text));
  119. //const metadataMatch = matchesAnyInCollection(search, Object.entries(p.file.frontmatter));
  120.  
  121. if (!nameMatch && !aliasMatch && !contentMatch)// && !metadataMatch)
  122. return false;
  123. }
  124. return true;
  125. });
  126.  
  127. function matchesAnyInCollection(search, collection) {
  128. if(!collection)
  129. return false;
  130. return collection.filter(v => typeof v === "string" || Array.isArray(v))
  131. .some(v => {
  132. if (Array.isArray(v)) {
  133. return v.some(item => typeof item === "string" && matchesCaseInsensitive(item,search));
  134. }
  135. return typeof v === "string" && matchesCaseInsensitive(v, search);
  136. });
  137. }
  138.  
  139. function matchesCaseInsensitive(text, searchText) {
  140. return text.toLowerCase().contains(searchText.toLowerCase());
  141. }
  142.  
  143.  
  144. let orderKey = orderByProperty;
  145. if (Array.isArray(orderKey))
  146. orderKey = orderKey[0];
  147. if (orderKey && typeof orderKey === "string") {
  148. let orderByName = orderKey == "name";
  149. results = results
  150. .where(p => orderByName ? true : p[orderKey] !== undefined)
  151. .sort(p => orderByName ? p.file.name : p[orderKey], order);
  152. }
  153.  
  154.  
  155. const headers = [...propertiesToShow];
  156.  
  157. const rows = results.map(p => {
  158. const row = [];
  159.  
  160. for (let key of propertiesToShow) {
  161. let val = p[key];
  162.  
  163. if (key === "image") {
  164. if (Array.isArray(val)) val = val[0];
  165. val = val ? `![img|${imageSize}](${val.replace("/default.jpg", "/maxresdefault.jpg")})` : 'No image';
  166. val = val.replace(/\|/g, "\\|");
  167. } else if (key === "name") {
  168. val = markdownLink(p.file.path, p.file.name);
  169. } else if (key === "content") {
  170. const contentList = p.file?.lists?.filter(item => item.text && item.text.trim() !== "").map(item => item.text);
  171. val = contentList?.length > 0 ? contentList.join("<br>") : "";
  172. } else {
  173. if (Array.isArray(val)) {
  174. val = `<ul>${val.map(item => {
  175. if (typeof item === "object" && item.path)
  176. return `<li>[[${item.path}]]</li>`;
  177. return `<li>${item}</li>`;
  178. }).join("")}</ul>`;
  179. }
  180. else if (typeof val === "object" && val?.path) val = `[[${val.path}]]`;
  181. else if (val === true) val = "βœ…";
  182. else if (val === false) val = "❌";
  183. else val = val ?? "";
  184. }
  185.  
  186. row.push(val);
  187. }
  188.  
  189. return row;
  190. });
  191.  
  192. //needed for callouts to work
  193. function markdownLink(filePath, displayName) {
  194. const encodedPath = encodeURI(filePath.replace(".md", ""));
  195. const safeDisplay = displayName.replace(/\[/g, "\\[").replace(/\]/g, "\\]");
  196. return `[${safeDisplay}](${encodedPath})`;
  197. }
  198.  
  199. const calloutMap = {
  200. /*"admin": "abstract",
  201. "performance": "abstract",
  202. "magic": "tip",
  203. "combat": "danger",
  204. "mobs": "bug",
  205. "chat": "quote",
  206. "farming": "info",
  207. "utility": "note",
  208. "decoration": "quote",
  209. "skins": "note",
  210. "roleplay": "note",
  211. "_uncategorized": "warning"*/
  212. };
  213.  
  214. //TODO: expose as field?
  215. function getCategoryEmoji(category) {
  216. const map = {
  217. "decorations": "πŸͺ‘",
  218. "visuals": "🎨",
  219. "exploration": "🧭",
  220. "technology": "πŸ› οΈ",
  221. "map": "πŸ—ΊοΈ",
  222. "structures": "🏠",
  223. "transportation": "πŸš‹",//πŸ›»
  224. "skins": "🧍",
  225. "roleplay": "πŸ“–",
  226. "realism": "πŸ’§",
  227. "quality-of-life": "πŸ‘",
  228. "performance": "⚑",
  229. "NPC": "πŸ€–",
  230. "mobs": "πŸ‘Ή",
  231. "magic": "πŸͺ„",//πŸ§™
  232. "items": "πŸ’",
  233. "cosmetics": "πŸ‘”",
  234. "combat": "βš”οΈ",
  235. "chat": "πŸ—―οΈ",
  236. "camera": "πŸ“·",
  237. "audio": "πŸ”Š",
  238. "animations": "πŸƒ",
  239. "admin": "βš™οΈ",
  240. "balancing": "βš–οΈ",
  241. "dimension": "πŸšͺ",
  242. "classes": "🧝",
  243. "nuilding": "πŸ—οΈ",
  244. };
  245. return map[category] ?? "γ…€";
  246. }
  247.  
  248. const groupByProperty = getMetaData("groupByProperty");
  249. const groupKey = Array.isArray(groupByProperty) ? groupByProperty[0] : groupByProperty;
  250.  
  251. if (groupKey) {
  252. const grouped = {};
  253.  
  254. for (let i = 0; i < results.length; i++) {
  255. const page = results[i];
  256. let keys = page[groupKey];
  257.  
  258. if (!keys || keys.length === 0)
  259. keys = ["_uncategorized"];
  260. else if (!Array.isArray(keys))
  261. keys = [keys];
  262.  
  263. for (const k of keys) {
  264. const keyString = typeof k === "object" && k?.path ? k.path : String(k);
  265. if (!grouped[keyString]) grouped[keyString] = [];
  266. grouped[keyString].push({ page, row: rows[i] });
  267. }
  268. }
  269.  
  270. let sortedKeys = Object.keys(grouped);
  271.  
  272. if (groupSort === "Alphabetical") {
  273. sortedKeys.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
  274. } else if (groupSort === "Group Size") {
  275. sortedKeys.sort((a, b) => grouped[a].length - grouped[b].length);
  276. } else if (groupSort === "Numeric") {
  277. const parseVersion = (str) => {
  278. const ver = String(str).match(/(\d+(\.\d+)*)/);
  279. const match = ver ? ver[0].split(".").map(n => parseInt(n)) : [0];
  280. return match.concat(Array(3 - match.length).fill(0)); // pad to [x, y, z]
  281. };
  282.  
  283. sortedKeys.sort((a, b) => {
  284. const [a1, a2, a3] = parseVersion(a);
  285. const [b1, b2, b3] = parseVersion(b);
  286.  
  287. if (a1 !== b1) return a1 - b1;
  288. if (a2 !== b2) return a2 - b2;
  289. return a3 - b3;
  290. });
  291. }
  292.  
  293. if (groupSortOrder === "desc")
  294. sortedKeys.reverse();
  295.  
  296. for (const key of sortedKeys) {
  297. const calloutType = calloutMap[key.toLowerCase?.()] ?? "empty";
  298. const emoji = getCategoryEmoji?.(key) ?? ""; // fallback if key isn't a category
  299.  
  300. const tableHeader = `| ${headers.join(" | ")} |\n| ${headers.map(() => "---").join(" | ")} |`;
  301. const tableRows = grouped[key].map(obj => `| ${obj.row.join(" | ")} |`);
  302.  
  303. const callout = [
  304. `> [!${calloutType}]- ${emoji} ${key} (${grouped[key].length})`,
  305. `>`,
  306. ...tableHeader.split("\n").map(line => `> ${line}`),
  307. ...tableRows.map(row => `> ${row}`),
  308. `>`
  309. ].join("\n");
  310.  
  311. dv.paragraph(callout);
  312. }
  313. } else {
  314. dv.table(headers, rows);
  315. }
  316. }
  317.  
  318.  
  319. function getFilterData(propertyName) {
  320. const values = getMetaData(propertyName);
  321. const logic = (getMetaData(`${propertyName}_logic`) ?? logicDefaultValue).toUpperCase();
  322. return { values, logic };
  323. }
  324.  
  325. function setMetaData(name, value)
  326. {
  327. mb.setMetadata(mb.createBindTarget('memory', filePath, [name], false), value);
  328. }
  329. function getMetaData(name)
  330. {
  331. return mb.getMetadata(bindTargets[name]?.bindTarget);
  332. //mb.getMetadata(mb.createBindTarget('memory', filePath, [name], false));
  333. }
  334.  
  335. function setUpBindTarget(name, defaultValue)
  336. {
  337. const bindTarget = mb.createBindTarget('memory', filePath, [name], false);
  338. if(mb.getMetadata(bindTarget) == undefined)
  339. mb.setMetadata(bindTarget, defaultValue);
  340. return bindTarget;
  341. }
  342.  
  343.  
  344. function matchesFilter(propertyValue, selectedValues, logic = logicDefaultValue) {
  345. if (!selectedValues || selectedValues.length === 0)
  346. return true;
  347.  
  348. if (selectedValues.length === 1 && selectedValues[0] === NULL_LABEL)
  349. return !propertyValue || (Array.isArray(propertyValue) && propertyValue.length === 0);// We expect this field to be null or an empty array
  350.  
  351. if (!propertyValue)
  352. return false;
  353.  
  354. if (Array.isArray(propertyValue)) {
  355. const values = propertyValue.map(val => val?.path ?? val?.toString?.() ?? val);
  356. if (logic === "AND") {
  357. return selectedValues.every(sel => values.includes(sel));
  358. } else if (logic === "OR") {
  359. return selectedValues.some(sel => values.includes(sel));
  360. }
  361. } else {
  362. const val = propertyValue?.path ?? propertyValue?.toString?.() ?? propertyValue;
  363. return selectedValues.includes(val);
  364. }
  365.  
  366. return false;
  367. }
  368.  
  369. function matchesMultiValueField(propertyValue, selectedValues, logic = logicDefaultValue) {
  370. if (!selectedValues || selectedValues.length === 0)
  371. return true;
  372.  
  373. if (selectedValues.length === 1 && selectedValues[0] === NULL_LABEL)
  374. return !propertyValue || (Array.isArray(propertyValue) && propertyValue.length === 0);
  375.  
  376. if (!Array.isArray(propertyValue))
  377. {
  378. if (logic === "NOT")
  379. return selectedValues != propertyValue;
  380. return selectedValues == propertyValue;
  381. }
  382.  
  383. if (logic === "AND")
  384. return selectedValues.every(val => propertyValue.includes(val));
  385. else if (logic === "OR")
  386. return propertyValue.some(val => selectedValues.includes(val));
  387. else if (logic === "NOT")
  388. return !propertyValue.some(val => selectedValues.includes(val));
  389.  
  390. throw new Error("Unsupported filter logic.");
  391. }
  392.  
  393. function matchesLinkArrayField(propertyValue, selectedValues, logic = logicDefaultValue) {
  394. if (!selectedValues || selectedValues.length === 0)
  395. return true;
  396.  
  397. if (selectedValues.length === 1 && selectedValues[0] === NULL_LABEL)
  398. return !propertyValue || (Array.isArray(propertyValue) && propertyValue.length === 0);
  399.  
  400. if (!Array.isArray(propertyValue))
  401. return false;
  402.  
  403. const paths = propertyValue.map(val => val?.path ?? val?.toString?.());
  404. if (logic === "AND") {
  405. return selectedValues.every(sel => paths.includes(sel));
  406. } else if (logic === "OR") {
  407. return selectedValues.some(sel => paths.includes(sel));
  408. } else if (logic === "NOT") {
  409. return !selectedValues.some(sel => paths.includes(sel));
  410. }
  411.  
  412. throw new Error("Unsupported filter logic.");
  413. }
  414.  
  415. function matchesSingleValueField(propertyValue, selectedValues, logic = logicDefaultValue) {
  416. if (!selectedValues || selectedValues.length === 0)
  417. return true;
  418.  
  419. if (selectedValues.length === 1 && selectedValues[0] === NULL_LABEL)
  420. return propertyValue === null || propertyValue === undefined || propertyValue === "";
  421.  
  422. const val = propertyValue?.path ?? propertyValue?.toString?.() ?? propertyValue;
  423.  
  424. if (logic === "AND") {
  425. // For single values, "AND" means the value must match *all* selected (only possible if there's one selected)
  426. return selectedValues.length === 1 && selectedValues[0] === val;
  427. } else if (logic === "OR") {
  428. return selectedValues.includes(val);
  429. } else if (logic === "NOT") {
  430. return !selectedValues.includes(val);
  431. }
  432. throw new Error("Unsupported filter logic.");
  433. }
  434.  
  435.  
  436.  
  437. function createHeader(name) {
  438. const header = document.createElement("h3");
  439. header.textContent = name;
  440. return header;
  441. }
  442.  
  443. function createInlineSelectFromOptions(labelText, propertyName, optionsArray, note, defaultValue = null) {
  444. const filePath = dv.current().file.path;
  445.  
  446. const bindTarget = mb.createBindTarget('memory', filePath, [propertyName], false);
  447.  
  448. const selectArgs = optionsArray.map(option => ({
  449. name: 'option',
  450. value: [option],
  451. ...(defaultValue === option ? { isSelected: true } : {})
  452. }));
  453.  
  454. selectArgs.push({
  455. name: 'title',
  456. value: ['Choose an option'],
  457. });
  458.  
  459. const field = mb.createInputFieldMountable(filePath, {
  460. renderChildType: 'inline',
  461. declaration: {
  462. inputFieldType: 'inlineSelect',
  463. bindTarget: bindTarget,
  464. arguments: selectArgs,
  465. },
  466. });
  467.  
  468. return buildListItem(labelText, field, bindTarget, note, propertyName, defaultValue);
  469. }
  470.  
  471. function createInlineSelectFromExistingPropertiesInNote(labelText, propertyName, note, targetNotePath, defaultValue = null) {
  472. const filePath = dv.current().file.path;
  473.  
  474. const frontmatter = app.metadataCache.getCache(targetNotePath)?.frontmatter || {};
  475.  
  476. const keys = Object.keys(frontmatter).filter(k => !k.startsWith("cssclass"));
  477. keys.push("name");
  478.  
  479. const selectArgs = keys.map(key => ({
  480. name: 'option',
  481. value: [key],
  482. ...(defaultValue === key ? { isSelected: true } : {})
  483. }));
  484.  
  485. selectArgs.push({
  486. name: 'title',
  487. value: ['Choose a property'],
  488. });
  489.  
  490. const bindTarget = mb.createBindTarget('memory', filePath, [propertyName], false);
  491.  
  492. const field = mb.createInputFieldMountable(filePath, {
  493. renderChildType: 'inline',
  494. declaration: {
  495. inputFieldType: 'inlineSelect',
  496. bindTarget: bindTarget,
  497. arguments: selectArgs,
  498. },
  499. });
  500.  
  501. return buildListItem(labelText, field, bindTarget, note, propertyName, defaultValue);
  502. }
  503.  
  504. function createInlineSuggesterFromPropertiesInNote(labelText, propertyName, note, targetNotePath, defaultValue = []) {
  505. const filePath = dv.current().file.path;
  506.  
  507. const frontmatter = app.metadataCache.getCache(targetNotePath)?.frontmatter || {};
  508.  
  509. let keys = Object.keys(frontmatter).filter(k => !k.startsWith("cssclass")); // Optional filtering
  510.  
  511. const extraKeys = ["name", "content"];
  512. keys = [...new Set([...keys, ...extraKeys])];
  513.  
  514. const suggesterArgs = keys.map(key => ({
  515. name: 'option',
  516. value: [key],
  517. }));
  518.  
  519. suggesterArgs.push({
  520. name: 'title',
  521. value: ['Choose a property'],
  522. });
  523.  
  524. const bindTarget = mb.createBindTarget('memory', filePath, [propertyName], false);
  525.  
  526. const field = mb.createInputFieldMountable(filePath, {
  527. renderChildType: 'inline',
  528. declaration: {
  529. inputFieldType: 'inlineListSuggester',
  530. bindTarget: bindTarget,
  531. arguments: suggesterArgs,
  532. },
  533. });
  534.  
  535. return buildListItem(labelText, field, bindTarget, note, propertyName, defaultValue);
  536. }
  537.  
  538.  
  539. function createInlineListSuggester(labelText, propertyName, note, logicToggle = false, defaultValue = null) {
  540. const filePath = dv.current().file.path;
  541.  
  542. const pages = dv.pages().where(p => p[propertyName]);
  543. let values = [];
  544.  
  545. for (let p of pages) {
  546. const val = p[propertyName];
  547. if (Array.isArray(val)) {
  548. val.forEach(v => values.push(normalizeValue(v)));
  549. } else {
  550. values.push(normalizeValue(val));
  551. }
  552. }
  553. values.push(NULL_LABEL);
  554.  
  555. function normalizeValue(v) {
  556. if (typeof v === "string") return v;
  557. if (typeof v === "object" && v.path) return v.path; // Likely a Link object from [[wikilinks]]
  558. return String(v); // Fallback
  559. }
  560.  
  561. const uniqueValues = Array.from(new Set(values));
  562.  
  563. const suggesterArgs = uniqueValues.map(val => ({
  564. name: 'option',
  565. value: [val],
  566. }));
  567.  
  568. suggesterArgs.push({
  569. name: 'title',
  570. value: ['Choose a status'],
  571. });
  572.  
  573. const bindTarget = mb.createBindTarget('memory', filePath, [propertyName], false);
  574.  
  575. const field = mb.createInputFieldMountable(filePath, {
  576. renderChildType: 'inline',
  577. declaration: {
  578. inputFieldType: 'inlineListSuggester',
  579. bindTarget: bindTarget,
  580. arguments: suggesterArgs,
  581. },
  582. });
  583.  
  584. return buildListItem(labelText, field, bindTarget, note, propertyName, defaultValue, logicToggle);
  585. }
  586.  
  587. function createBoolInlineSelect(labelText, propertyName, note) {
  588. const filePath = dv.current().file.path;
  589.  
  590. const bindTarget = mb.createBindTarget('memory', filePath, [propertyName], false);
  591.  
  592. const selectArgs = [
  593. { name: 'option', value: ['yes'] },
  594. { name: 'option', value: ['no'] },
  595. { name: 'option', value: ['-'] },
  596. { name: 'title', value: ['Choose an option'] },
  597. ];
  598.  
  599. const field = mb.createInputFieldMountable(filePath, {
  600. renderChildType: 'inline',
  601. declaration: {
  602. inputFieldType: 'inlineSelect',
  603. bindTarget: bindTarget,
  604. arguments: selectArgs,
  605. },
  606. });
  607.  
  608. return buildListItem(labelText, field, bindTarget, note, propertyName);
  609. }
  610.  
  611. function createInlineTextField(labelText, propertyName, note, defaultValue = null) {
  612. return createInlineTextOrNumberField(false, labelText, propertyName, note, defaultValue);
  613. }
  614.  
  615. function createInlineNumberField(labelText, propertyName, note, defaultValue = null) {
  616. return createInlineTextOrNumberField(true, labelText, propertyName, note, defaultValue);
  617. }
  618.  
  619. function createInlineTextOrNumberField(onlyNumbers, labelText, propertyName, note, defaultValue = null) {
  620. const filePath = dv.current().file.path;
  621.  
  622. const bindTarget = mb.createBindTarget('memory', filePath, [propertyName], false);
  623.  
  624. const field = mb.createInputFieldMountable(filePath, {
  625. renderChildType: 'inline',
  626. declaration: {
  627. inputFieldType: onlyNumbers ? 'number' : 'text',
  628. bindTarget: bindTarget,
  629. },
  630. });
  631.  
  632. return buildListItem(labelText, field, bindTarget, note, propertyName, defaultValue);
  633. }
  634.  
  635. function buildListItem(labelText, field, bindTarget, note, propertyName, defaultValue = null, logicToggle = false) {
  636. if (propertyName)
  637. bindTargets[propertyName] = { bindTarget, defaultValue };
  638.  
  639. const container = document.createElement('div');
  640. container.className = "mb-field-item";
  641. const label = document.createElement('label');
  642. label.textContent = labelText + ": ";
  643. label.style.marginRight = "0.5em";
  644. container.appendChild(label);
  645.  
  646. const fieldContainer = document.createElement('span');
  647. mb.wrapInMDRC(field, fieldContainer, note.component);
  648. container.appendChild(fieldContainer);
  649.  
  650. const filePath = dv.current().file.path;
  651. const fileCache = app.metadataCache.getCache(filePath);
  652. const valueKey = propertyName ?? bindTarget?.key ?? "";
  653. const currentValue = mb.getMetadata(bindTarget);
  654.  
  655. if (isUnset(currentValue) && defaultValue !== null)
  656. mb.updateMetadata(bindTarget, () => defaultValue);
  657.  
  658. let logicBind;
  659. if (logicToggle && propertyName) {
  660. const logicProp = `${propertyName}_logic`;
  661. logicBind = setUpBindTarget(logicProp, logicDefaultValue);
  662. bindTargets[logicProp] = { bindTarget: logicBind, defaultValue: logicDefaultValue };
  663.  
  664. const toggle = mb.createInputFieldMountable(filePath, {
  665. renderChildType: 'inline',
  666. declaration: {
  667. inputFieldType: 'inlineSelect',
  668. bindTarget: logicBind,
  669. arguments: [
  670. { name: 'option', value: ['AND'] },
  671. { name: 'option', value: ['OR'], isSelected: true },
  672. { name: 'option', value: ['NOT'] },
  673. { name: 'title', value: ['Match Logic'] }
  674. ]
  675. }
  676. });
  677.  
  678. const toggleContainer = document.createElement("span");
  679. toggleContainer.style.marginLeft = "0.5em";
  680. mb.wrapInMDRC(toggle, toggleContainer, note.component);
  681. container.appendChild(toggleContainer);
  682. }
  683.  
  684. const resetButtonEl = document.createElement('button');
  685. resetButtonEl.textContent = 'Reset';
  686. resetButtonEl.style.marginLeft = '0.5em';
  687. resetButtonEl.onclick = () => {
  688. mb.updateMetadata(bindTarget, () => defaultValue);
  689. if(logicBind)
  690. mb.updateMetadata(logicBind, () => logicDefaultValue);
  691. };
  692. container.appendChild(resetButtonEl);
  693.  
  694. return container;
  695. }
  696.  
  697. function isUnset(value) {
  698. return value === undefined || value === null || (Array.isArray(value) && value.length === 0);
  699. }
  700. ```
  701.  
Tags: Obsidian
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement