entities_fnote.js.html 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>JSDoc: Source: entities/fnote.js</title>
  6. <script src="scripts/prettify/prettify.js"> </script>
  7. <script src="scripts/prettify/lang-css.js"> </script>
  8. <!--[if lt IE 9]>
  9. <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
  10. <![endif]-->
  11. <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
  12. <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
  13. </head>
  14. <body>
  15. <div id="main">
  16. <h1 class="page-title">Source: entities/fnote.js</h1>
  17. <section>
  18. <article>
  19. <pre class="prettyprint source linenums"><code>import server from '../services/server.js';
  20. import noteAttributeCache from "../services/note_attribute_cache.js";
  21. import ws from "../services/ws.js";
  22. import froca from "../services/froca.js";
  23. import protectedSessionHolder from "../services/protected_session_holder.js";
  24. import cssClassManager from "../services/css_class_manager.js";
  25. const LABEL = 'label';
  26. const RELATION = 'relation';
  27. const NOTE_TYPE_ICONS = {
  28. "file": "bx bx-file",
  29. "image": "bx bx-image",
  30. "code": "bx bx-code",
  31. "render": "bx bx-extension",
  32. "search": "bx bx-file-find",
  33. "relationMap": "bx bx-map-alt",
  34. "book": "bx bx-book",
  35. "noteMap": "bx bx-map-alt",
  36. "mermaid": "bx bx-selection",
  37. "canvas": "bx bx-pen",
  38. "webView": "bx bx-globe-alt",
  39. "launcher": "bx bx-link",
  40. "doc": "bx bxs-file-doc",
  41. "contentWidget": "bx bxs-widget"
  42. };
  43. /**
  44. * There are many different Note types, some of which are entirely opaque to the
  45. * end user. Those types should be used only for checking against, they are
  46. * not for direct use.
  47. * @typedef {"file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code"} NoteType
  48. */
  49. /**
  50. * @typedef {Object} NotePathRecord
  51. * @property {boolean} isArchived
  52. * @property {boolean} isInHoistedSubTree
  53. * @property {boolean} isSearch
  54. * @property {Array&lt;string>} notePath
  55. * @property {boolean} isHidden
  56. */
  57. /**
  58. * Note is the main node and concept in Trilium.
  59. */
  60. class FNote {
  61. /**
  62. * @param {Froca} froca
  63. * @param {Object.&lt;string, Object>} row
  64. */
  65. constructor(froca, row) {
  66. /** @type {Froca} */
  67. this.froca = froca;
  68. /** @type {string[]} */
  69. this.attributes = [];
  70. /** @type {string[]} */
  71. this.targetRelations = [];
  72. /** @type {string[]} */
  73. this.parents = [];
  74. /** @type {string[]} */
  75. this.children = [];
  76. /** @type {Object.&lt;string, string>} */
  77. this.parentToBranch = {};
  78. /** @type {Object.&lt;string, string>} */
  79. this.childToBranch = {};
  80. /** @type {FAttachment[]|null} */
  81. this.attachments = null; // lazy loaded
  82. this.update(row);
  83. }
  84. update(row) {
  85. /** @type {string} */
  86. this.noteId = row.noteId;
  87. /** @type {string} */
  88. this.title = row.title;
  89. /** @type {boolean} */
  90. this.isProtected = !!row.isProtected;
  91. /**
  92. * See {@see NoteType} for info on values.
  93. * @type {NoteType}
  94. */
  95. this.type = row.type;
  96. /**
  97. * content-type, e.g. "application/json"
  98. * @type {string}
  99. */
  100. this.mime = row.mime;
  101. // the main use case to keep this is to detect content change which should trigger refresh
  102. this.blobId = row.blobId;
  103. }
  104. addParent(parentNoteId, branchId, sort = true) {
  105. if (parentNoteId === 'none') {
  106. return;
  107. }
  108. if (!this.parents.includes(parentNoteId)) {
  109. this.parents.push(parentNoteId);
  110. }
  111. this.parentToBranch[parentNoteId] = branchId;
  112. if (sort) {
  113. this.sortParents();
  114. }
  115. }
  116. addChild(childNoteId, branchId, sort = true) {
  117. if (!(childNoteId in this.childToBranch)) {
  118. this.children.push(childNoteId);
  119. }
  120. this.childToBranch[childNoteId] = branchId;
  121. if (sort) {
  122. this.sortChildren();
  123. }
  124. }
  125. sortChildren() {
  126. const branchIdPos = {};
  127. for (const branchId of Object.values(this.childToBranch)) {
  128. branchIdPos[branchId] = this.froca.getBranch(branchId).notePosition;
  129. }
  130. this.children.sort((a, b) => branchIdPos[this.childToBranch[a]] - branchIdPos[this.childToBranch[b]]);
  131. }
  132. /** @returns {boolean} */
  133. isJson() {
  134. return this.mime === "application/json";
  135. }
  136. async getContent() {
  137. const blob = await this.getBlob();
  138. return blob?.content;
  139. }
  140. async getJsonContent() {
  141. const content = await this.getContent();
  142. try {
  143. return JSON.parse(content);
  144. }
  145. catch (e) {
  146. console.log(`Cannot parse content of note '${this.noteId}': `, e.message);
  147. return null;
  148. }
  149. }
  150. /**
  151. * @returns {string[]}
  152. */
  153. getParentBranchIds() {
  154. return Object.values(this.parentToBranch);
  155. }
  156. /**
  157. * @returns {string[]}
  158. * @deprecated use getParentBranchIds() instead
  159. */
  160. getBranchIds() {
  161. return this.getParentBranchIds();
  162. }
  163. /**
  164. * @returns {FBranch[]}
  165. */
  166. getParentBranches() {
  167. const branchIds = Object.values(this.parentToBranch);
  168. return this.froca.getBranches(branchIds);
  169. }
  170. /**
  171. * @returns {FBranch[]}
  172. * @deprecated use getParentBranches() instead
  173. */
  174. getBranches() {
  175. return this.getParentBranches();
  176. }
  177. /** @returns {boolean} */
  178. hasChildren() {
  179. return this.children.length > 0;
  180. }
  181. /** @returns {FBranch[]} */
  182. getChildBranches() {
  183. // don't use Object.values() to guarantee order
  184. const branchIds = this.children.map(childNoteId => this.childToBranch[childNoteId]);
  185. return this.froca.getBranches(branchIds);
  186. }
  187. /** @returns {string[]} */
  188. getParentNoteIds() {
  189. return this.parents;
  190. }
  191. /** @returns {FNote[]} */
  192. getParentNotes() {
  193. return this.froca.getNotesFromCache(this.parents);
  194. }
  195. // will sort the parents so that non-search &amp; non-archived are first and archived at the end
  196. // this is done so that non-search &amp; non-archived paths are always explored as first when looking for a note path
  197. sortParents() {
  198. this.parents.sort((aNoteId, bNoteId) => {
  199. const aBranchId = this.parentToBranch[aNoteId];
  200. if (aBranchId &amp;&amp; aBranchId.startsWith('virt-')) {
  201. return 1;
  202. }
  203. const aNote = this.froca.getNoteFromCache(aNoteId);
  204. if (aNote.isArchived || aNote.isHiddenCompletely()) {
  205. return 1;
  206. }
  207. return aNoteId &lt; bNoteId ? -1 : 1;
  208. });
  209. }
  210. get isArchived() {
  211. return this.hasAttribute('label', 'archived');
  212. }
  213. /** @returns {string[]} */
  214. getChildNoteIds() {
  215. return this.children;
  216. }
  217. /** @returns {Promise&lt;FNote[]>} */
  218. async getChildNotes() {
  219. return await this.froca.getNotes(this.children);
  220. }
  221. /** @returns {Promise&lt;FAttachment[]>} */
  222. async getAttachments() {
  223. if (!this.attachments) {
  224. this.attachments = await this.froca.getAttachmentsForNote(this.noteId);
  225. }
  226. return this.attachments;
  227. }
  228. /** @returns {Promise&lt;FAttachment[]>} */
  229. async getAttachmentsByRole(role) {
  230. return (await this.getAttachments())
  231. .filter(attachment => attachment.role === role);
  232. }
  233. /** @returns {Promise&lt;FAttachment>} */
  234. async getAttachmentById(attachmentId) {
  235. const attachments = await this.getAttachments();
  236. return attachments.find(att => att.attachmentId === attachmentId);
  237. }
  238. isEligibleForConversionToAttachment() {
  239. if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) {
  240. return false;
  241. }
  242. const targetRelations = this.getTargetRelations().filter(relation => relation.name === 'imageLink');
  243. if (targetRelations.length > 1) {
  244. return false;
  245. }
  246. const parentNote = this.getParentNotes()[0]; // at this point note can have only one parent
  247. const referencingNote = targetRelations[0]?.getNote();
  248. if (referencingNote &amp;&amp; referencingNote !== parentNote) {
  249. return false;
  250. } else if (parentNote.type !== 'text' || !parentNote.isContentAvailable()) {
  251. return false;
  252. }
  253. return true;
  254. }
  255. /**
  256. * @param {string} [type] - (optional) attribute type to filter
  257. * @param {string} [name] - (optional) attribute name to filter
  258. * @returns {FAttribute[]} all note's attributes, including inherited ones
  259. */
  260. getOwnedAttributes(type, name) {
  261. const attrs = this.attributes
  262. .map(attributeId => this.froca.attributes[attributeId])
  263. .filter(Boolean); // filter out nulls;
  264. return this.__filterAttrs(attrs, type, name);
  265. }
  266. /**
  267. * @param {string} [type] - (optional) attribute type to filter
  268. * @param {string} [name] - (optional) attribute name to filter
  269. * @returns {FAttribute[]} all note's attributes, including inherited ones
  270. */
  271. getAttributes(type, name) {
  272. return this.__filterAttrs(this.__getCachedAttributes([]), type, name);
  273. }
  274. /**
  275. * @param {string[]} path
  276. * @return {FAttribute[]}
  277. * @private
  278. */
  279. __getCachedAttributes(path) {
  280. // notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates
  281. // when template instance is a parent of template itself
  282. if (path.includes(this.noteId)) {
  283. return [];
  284. }
  285. if (!(this.noteId in noteAttributeCache.attributes)) {
  286. const newPath = [...path, this.noteId];
  287. const attrArrs = [ this.getOwnedAttributes() ];
  288. // inheritable attrs on root are typically not intended to be applied to hidden subtree #3537
  289. if (this.noteId !== 'root' &amp;&amp; this.noteId !== '_hidden') {
  290. for (const parentNote of this.getParentNotes()) {
  291. // these virtual parent-child relationships are also loaded into froca
  292. if (parentNote.type !== 'search') {
  293. attrArrs.push(parentNote.__getInheritableAttributes(newPath));
  294. }
  295. }
  296. }
  297. for (const templateAttr of attrArrs.flat().filter(attr => attr.type === 'relation' &amp;&amp; ['template', 'inherit'].includes(attr.name))) {
  298. const templateNote = this.froca.notes[templateAttr.value];
  299. if (templateNote &amp;&amp; templateNote.noteId !== this.noteId) {
  300. attrArrs.push(
  301. templateNote.__getCachedAttributes(newPath)
  302. // template attr is used as a marker for templates, but it's not meant to be inherited
  303. .filter(attr => !(attr.type === 'label' &amp;&amp; (attr.name === 'template' || attr.name === 'workspacetemplate')))
  304. );
  305. }
  306. }
  307. noteAttributeCache.attributes[this.noteId] = [];
  308. const addedAttributeIds = new Set();
  309. for (const attr of attrArrs.flat()) {
  310. if (!addedAttributeIds.has(attr.attributeId)) {
  311. addedAttributeIds.add(attr.attributeId);
  312. noteAttributeCache.attributes[this.noteId].push(attr);
  313. }
  314. }
  315. }
  316. return noteAttributeCache.attributes[this.noteId];
  317. }
  318. isRoot() {
  319. return this.noteId === 'root';
  320. }
  321. /**
  322. * Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles)
  323. *
  324. * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path)
  325. */
  326. getAllNotePaths() {
  327. if (this.noteId === 'root') {
  328. return [['root']];
  329. }
  330. const parentNotes = this.getParentNotes().filter(note => note.type !== 'search');
  331. const notePaths = parentNotes.length === 1
  332. ? parentNotes[0].getAllNotePaths() // optimization for the most common case
  333. : parentNotes.flatMap(parentNote => parentNote.getAllNotePaths());
  334. for (const notePath of notePaths) {
  335. notePath.push(this.noteId);
  336. }
  337. return notePaths;
  338. }
  339. /**
  340. * @param {string} [hoistedNoteId='root']
  341. * @return {Array&lt;NotePathRecord>}
  342. */
  343. getSortedNotePathRecords(hoistedNoteId = 'root') {
  344. const isHoistedRoot = hoistedNoteId === 'root';
  345. const notePaths = this.getAllNotePaths().map(path => ({
  346. notePath: path,
  347. isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId),
  348. isArchived: path.some(noteId => froca.notes[noteId].isArchived),
  349. isSearch: path.find(noteId => froca.notes[noteId].type === 'search'),
  350. isHidden: path.includes('_hidden')
  351. }));
  352. notePaths.sort((a, b) => {
  353. if (a.isInHoistedSubTree !== b.isInHoistedSubTree) {
  354. return a.isInHoistedSubTree ? -1 : 1;
  355. } else if (a.isArchived !== b.isArchived) {
  356. return a.isArchived ? 1 : -1;
  357. } else if (a.isHidden !== b.isHidden) {
  358. return a.isHidden ? 1 : -1;
  359. } else if (a.isSearch !== b.isSearch) {
  360. return a.isSearch ? 1 : -1;
  361. } else {
  362. return a.notePath.length - b.notePath.length;
  363. }
  364. });
  365. return notePaths;
  366. }
  367. /**
  368. * Returns the note path considered to be the "best"
  369. *
  370. * @param {string} [hoistedNoteId='root']
  371. * @return {string[]} array of noteIds constituting the particular note path
  372. */
  373. getBestNotePath(hoistedNoteId = 'root') {
  374. return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath;
  375. }
  376. /**
  377. * Returns the note path considered to be the "best"
  378. *
  379. * @param {string} [hoistedNoteId='root']
  380. * @return {string} serialized note path (e.g. 'root/a1h315/js725h')
  381. */
  382. getBestNotePathString(hoistedNoteId = 'root') {
  383. const notePath = this.getBestNotePath(hoistedNoteId);
  384. return notePath?.join("/");
  385. }
  386. /**
  387. * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree
  388. */
  389. isHiddenCompletely() {
  390. if (this.noteId === '_hidden') {
  391. return true;
  392. } else if (this.noteId === 'root') {
  393. return false;
  394. }
  395. for (const parentNote of this.getParentNotes()) {
  396. if (parentNote.noteId === 'root') {
  397. return false;
  398. } else if (parentNote.noteId === '_hidden' || parentNote.type === 'search') {
  399. continue;
  400. }
  401. if (!parentNote.isHiddenCompletely()) {
  402. return false;
  403. }
  404. }
  405. return true;
  406. }
  407. /**
  408. * @param {FAttribute[]} attributes
  409. * @param {AttributeType} type
  410. * @param {string} name
  411. * @return {FAttribute[]}
  412. * @private
  413. */
  414. __filterAttrs(attributes, type, name) {
  415. this.__validateTypeName(type, name);
  416. if (!type &amp;&amp; !name) {
  417. return attributes;
  418. } else if (type &amp;&amp; name) {
  419. return attributes.filter(attr => attr.name === name &amp;&amp; attr.type === type);
  420. } else if (type) {
  421. return attributes.filter(attr => attr.type === type);
  422. } else if (name) {
  423. return attributes.filter(attr => attr.name === name);
  424. }
  425. }
  426. __getInheritableAttributes(path) {
  427. const attrs = this.__getCachedAttributes(path);
  428. return attrs.filter(attr => attr.isInheritable);
  429. }
  430. __validateTypeName(type, name) {
  431. if (type &amp;&amp; type !== 'label' &amp;&amp; type !== 'relation') {
  432. throw new Error(`Unrecognized attribute type '${type}'. Only 'label' and 'relation' are possible values.`);
  433. }
  434. if (name) {
  435. const firstLetter = name.charAt(0);
  436. if (firstLetter === '#' || firstLetter === '~') {
  437. throw new Error(`Detect '#' or '~' in the attribute's name. In the API, attribute names should be set without these characters.`);
  438. }
  439. }
  440. }
  441. /**
  442. * @param {string} [name] - label name to filter
  443. * @returns {FAttribute[]} all note's labels (attributes with type label), including inherited ones
  444. */
  445. getOwnedLabels(name) {
  446. return this.getOwnedAttributes(LABEL, name);
  447. }
  448. /**
  449. * @param {string} [name] - label name to filter
  450. * @returns {FAttribute[]} all note's labels (attributes with type label), including inherited ones
  451. */
  452. getLabels(name) {
  453. return this.getAttributes(LABEL, name);
  454. }
  455. getIcon() {
  456. const iconClassLabels = this.getLabels('iconClass');
  457. const workspaceIconClass = this.getWorkspaceIconClass();
  458. if (iconClassLabels.length > 0) {
  459. return iconClassLabels[0].value;
  460. }
  461. else if (workspaceIconClass) {
  462. return workspaceIconClass;
  463. }
  464. else if (this.noteId === 'root') {
  465. return "bx bx-chevrons-right";
  466. }
  467. if (this.noteId === '_share') {
  468. return "bx bx-share-alt";
  469. }
  470. else if (this.type === 'text') {
  471. if (this.isFolder()) {
  472. return "bx bx-folder";
  473. }
  474. else {
  475. return "bx bx-note";
  476. }
  477. }
  478. else if (this.type === 'code' &amp;&amp; this.mime.startsWith('text/x-sql')) {
  479. return "bx bx-data";
  480. }
  481. else {
  482. return NOTE_TYPE_ICONS[this.type];
  483. }
  484. }
  485. getColorClass() {
  486. const color = this.getLabelValue("color");
  487. return cssClassManager.createClassForColor(color);
  488. }
  489. isFolder() {
  490. return this.type === 'search'
  491. || this.getFilteredChildBranches().length > 0;
  492. }
  493. getFilteredChildBranches() {
  494. let childBranches = this.getChildBranches();
  495. if (!childBranches) {
  496. ws.logError(`No children for '${this.noteId}'. This shouldn't happen.`);
  497. return;
  498. }
  499. // we're not checking hideArchivedNotes since that would mean we need to lazy load the child notes
  500. // which would seriously slow down everything.
  501. // we check this flag only once user chooses to expand the parent. This has the negative consequence that
  502. // note may appear as a folder but not contain any children when all of them are archived
  503. return childBranches;
  504. }
  505. /**
  506. * @param {string} [name] - relation name to filter
  507. * @returns {FAttribute[]} all note's relations (attributes with type relation), including inherited ones
  508. */
  509. getOwnedRelations(name) {
  510. return this.getOwnedAttributes(RELATION, name);
  511. }
  512. /**
  513. * @param {string} [name] - relation name to filter
  514. * @returns {FAttribute[]} all note's relations (attributes with type relation), including inherited ones
  515. */
  516. getRelations(name) {
  517. return this.getAttributes(RELATION, name);
  518. }
  519. /**
  520. * @param {AttributeType} type - attribute type (label, relation, etc.)
  521. * @param {string} name - attribute name
  522. * @returns {boolean} true if note has an attribute with given type and name (including inherited)
  523. */
  524. hasAttribute(type, name) {
  525. const attributes = this.getAttributes();
  526. return attributes.some(attr => attr.name === name &amp;&amp; attr.type === type);
  527. }
  528. /**
  529. * @param {AttributeType} type - attribute type (label, relation, etc.)
  530. * @param {string} name - attribute name
  531. * @returns {boolean} true if note has an attribute with given type and name (including inherited)
  532. */
  533. hasOwnedAttribute(type, name) {
  534. return !!this.getOwnedAttribute(type, name);
  535. }
  536. /**
  537. * @param {AttributeType} type - attribute type (label, relation, etc.)
  538. * @param {string} name - attribute name
  539. * @returns {FAttribute} attribute of the given type and name. If there are more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
  540. */
  541. getOwnedAttribute(type, name) {
  542. const attributes = this.getOwnedAttributes();
  543. return attributes.find(attr => attr.name === name &amp;&amp; attr.type === type);
  544. }
  545. /**
  546. * @param {AttributeType} type - attribute type (label, relation, etc.)
  547. * @param {string} name - attribute name
  548. * @returns {FAttribute} attribute of the given type and name. If there are more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
  549. */
  550. getAttribute(type, name) {
  551. const attributes = this.getAttributes();
  552. return attributes.find(attr => attr.name === name &amp;&amp; attr.type === type);
  553. }
  554. /**
  555. * @param {AttributeType} type - attribute type (label, relation, etc.)
  556. * @param {string} name - attribute name
  557. * @returns {string} attribute value of the given type and name or null if no such attribute exists.
  558. */
  559. getOwnedAttributeValue(type, name) {
  560. const attr = this.getOwnedAttribute(type, name);
  561. return attr ? attr.value : null;
  562. }
  563. /**
  564. * @param {AttributeType} type - attribute type (label, relation, etc.)
  565. * @param {string} name - attribute name
  566. * @returns {string} attribute value of the given type and name or null if no such attribute exists.
  567. */
  568. getAttributeValue(type, name) {
  569. const attr = this.getAttribute(type, name);
  570. return attr ? attr.value : null;
  571. }
  572. /**
  573. * @param {string} name - label name
  574. * @returns {boolean} true if label exists (excluding inherited)
  575. */
  576. hasOwnedLabel(name) { return this.hasOwnedAttribute(LABEL, name); }
  577. /**
  578. * @param {string} name - label name
  579. * @returns {boolean} true if label exists (including inherited)
  580. */
  581. hasLabel(name) { return this.hasAttribute(LABEL, name); }
  582. /**
  583. * @param {string} name - label name
  584. * @returns {boolean} true if label exists (including inherited) and does not have "false" value.
  585. */
  586. isLabelTruthy(name) {
  587. const label = this.getLabel(name);
  588. if (!label) {
  589. return false;
  590. }
  591. return label &amp;&amp; label.value !== 'false';
  592. }
  593. /**
  594. * @param {string} name - relation name
  595. * @returns {boolean} true if relation exists (excluding inherited)
  596. */
  597. hasOwnedRelation(name) { return this.hasOwnedAttribute(RELATION, name); }
  598. /**
  599. * @param {string} name - relation name
  600. * @returns {boolean} true if relation exists (including inherited)
  601. */
  602. hasRelation(name) { return this.hasAttribute(RELATION, name); }
  603. /**
  604. * @param {string} name - label name
  605. * @returns {FAttribute} label if it exists, null otherwise
  606. */
  607. getOwnedLabel(name) { return this.getOwnedAttribute(LABEL, name); }
  608. /**
  609. * @param {string} name - label name
  610. * @returns {FAttribute} label if it exists, null otherwise
  611. */
  612. getLabel(name) { return this.getAttribute(LABEL, name); }
  613. /**
  614. * @param {string} name - relation name
  615. * @returns {FAttribute} relation if it exists, null otherwise
  616. */
  617. getOwnedRelation(name) { return this.getOwnedAttribute(RELATION, name); }
  618. /**
  619. * @param {string} name - relation name
  620. * @returns {FAttribute} relation if it exists, null otherwise
  621. */
  622. getRelation(name) { return this.getAttribute(RELATION, name); }
  623. /**
  624. * @param {string} name - label name
  625. * @returns {string} label value if label exists, null otherwise
  626. */
  627. getOwnedLabelValue(name) { return this.getOwnedAttributeValue(LABEL, name); }
  628. /**
  629. * @param {string} name - label name
  630. * @returns {string} label value if label exists, null otherwise
  631. */
  632. getLabelValue(name) { return this.getAttributeValue(LABEL, name); }
  633. /**
  634. * @param {string} name - relation name
  635. * @returns {string} relation value if relation exists, null otherwise
  636. */
  637. getOwnedRelationValue(name) { return this.getOwnedAttributeValue(RELATION, name); }
  638. /**
  639. * @param {string} name - relation name
  640. * @returns {string} relation value if relation exists, null otherwise
  641. */
  642. getRelationValue(name) { return this.getAttributeValue(RELATION, name); }
  643. /**
  644. * @param {string} name
  645. * @returns {Promise&lt;FNote>|null} target note of the relation or null (if target is empty or note was not found)
  646. */
  647. async getRelationTarget(name) {
  648. const targets = await this.getRelationTargets(name);
  649. return targets.length > 0 ? targets[0] : null;
  650. }
  651. /**
  652. * @param {string} [name] - relation name to filter
  653. * @returns {Promise&lt;FNote[]>}
  654. */
  655. async getRelationTargets(name) {
  656. const relations = this.getRelations(name);
  657. const targets = [];
  658. for (const relation of relations) {
  659. targets.push(await this.froca.getNote(relation.value));
  660. }
  661. return targets;
  662. }
  663. /**
  664. * @returns {FNote[]}
  665. */
  666. getNotesToInheritAttributesFrom() {
  667. const relations = [
  668. ...this.getRelations('template'),
  669. ...this.getRelations('inherit')
  670. ];
  671. return relations.map(rel => this.froca.notes[rel.value]);
  672. }
  673. getPromotedDefinitionAttributes() {
  674. if (this.isLabelTruthy('hidePromotedAttributes')) {
  675. return [];
  676. }
  677. const promotedAttrs = this.getAttributes()
  678. .filter(attr => attr.isDefinition())
  679. .filter(attr => {
  680. const def = attr.getDefinition();
  681. return def &amp;&amp; def.isPromoted;
  682. });
  683. // attrs are not resorted if position changes after the initial load
  684. promotedAttrs.sort((a, b) => {
  685. if (a.noteId === b.noteId) {
  686. return a.position &lt; b.position ? -1 : 1;
  687. } else {
  688. // inherited promoted attributes should stay grouped: https://github.com/zadam/trilium/issues/3761
  689. return a.noteId &lt; b.noteId ? -1 : 1;
  690. }
  691. });
  692. return promotedAttrs;
  693. }
  694. hasAncestor(ancestorNoteId, followTemplates = false, visitedNoteIds = null) {
  695. if (this.noteId === ancestorNoteId) {
  696. return true;
  697. }
  698. if (!visitedNoteIds) {
  699. visitedNoteIds = new Set();
  700. } else if (visitedNoteIds.has(this.noteId)) {
  701. // to avoid infinite cycle when template is descendent of the instance
  702. return false;
  703. }
  704. visitedNoteIds.add(this.noteId);
  705. if (followTemplates) {
  706. for (const templateNote of this.getNotesToInheritAttributesFrom()) {
  707. if (templateNote.hasAncestor(ancestorNoteId, followTemplates, visitedNoteIds)) {
  708. return true;
  709. }
  710. }
  711. }
  712. for (const parentNote of this.getParentNotes()) {
  713. if (parentNote.hasAncestor(ancestorNoteId, followTemplates, visitedNoteIds)) {
  714. return true;
  715. }
  716. }
  717. return false;
  718. }
  719. isInHiddenSubtree() {
  720. return this.noteId === '_hidden' || this.hasAncestor('_hidden');
  721. }
  722. /**
  723. * @deprecated NOOP
  724. */
  725. invalidateAttributeCache() {}
  726. /**
  727. * Get relations which target this note
  728. *
  729. * @returns {FAttribute[]}
  730. */
  731. getTargetRelations() {
  732. return this.targetRelations
  733. .map(attributeId => this.froca.attributes[attributeId]);
  734. }
  735. /**
  736. * Get relations which target this note
  737. *
  738. * @returns {Promise&lt;FNote[]>}
  739. */
  740. async getTargetRelationSourceNotes() {
  741. const targetRelations = this.getTargetRelations();
  742. return await this.froca.getNotes(targetRelations.map(tr => tr.noteId));
  743. }
  744. /**
  745. * @deprecated use getBlob() instead
  746. * @return {Promise&lt;FBlob>}
  747. */
  748. async getNoteComplement() {
  749. return this.getBlob();
  750. }
  751. /** @return {Promise&lt;FBlob>} */
  752. async getBlob() {
  753. return await this.froca.getBlob('notes', this.noteId);
  754. }
  755. toString() {
  756. return `Note(noteId=${this.noteId}, title=${this.title})`;
  757. }
  758. get dto() {
  759. const dto = Object.assign({}, this);
  760. delete dto.froca;
  761. return dto;
  762. }
  763. getCssClass() {
  764. const labels = this.getLabels('cssClass');
  765. return labels.map(l => l.value).join(' ');
  766. }
  767. getWorkspaceIconClass() {
  768. const labels = this.getLabels('workspaceIconClass');
  769. return labels.length > 0 ? labels[0].value : "";
  770. }
  771. getWorkspaceTabBackgroundColor() {
  772. const labels = this.getLabels('workspaceTabBackgroundColor');
  773. return labels.length > 0 ? labels[0].value : "";
  774. }
  775. /** @returns {boolean} true if this note is JavaScript (code or file) */
  776. isJavaScript() {
  777. return (this.type === "code" || this.type === "file" || this.type === 'launcher')
  778. &amp;&amp; (this.mime.startsWith("application/javascript")
  779. || this.mime === "application/x-javascript"
  780. || this.mime === "text/javascript");
  781. }
  782. /** @returns {boolean} true if this note is HTML */
  783. isHtml() {
  784. return (this.type === "code" || this.type === "file" || this.type === "render") &amp;&amp; this.mime === "text/html";
  785. }
  786. /** @returns {string|null} JS script environment - either "frontend" or "backend" */
  787. getScriptEnv() {
  788. if (this.isHtml() || (this.isJavaScript() &amp;&amp; this.mime.endsWith('env=frontend'))) {
  789. return "frontend";
  790. }
  791. if (this.type === 'render') {
  792. return "frontend";
  793. }
  794. if (this.isJavaScript() &amp;&amp; this.mime.endsWith('env=backend')) {
  795. return "backend";
  796. }
  797. return null;
  798. }
  799. async executeScript() {
  800. if (!this.isJavaScript()) {
  801. throw new Error(`Note ${this.noteId} is of type ${this.type} and mime ${this.mime} and thus cannot be executed`);
  802. }
  803. const env = this.getScriptEnv();
  804. if (env === "frontend") {
  805. const bundleService = (await import("../services/bundle.js")).default;
  806. return await bundleService.getAndExecuteBundle(this.noteId);
  807. }
  808. else if (env === "backend") {
  809. const resp = await server.post(`script/run/${this.noteId}`);
  810. }
  811. else {
  812. throw new Error(`Unrecognized env type ${env} for note ${this.noteId}`);
  813. }
  814. }
  815. isShared() {
  816. for (const parentNoteId of this.parents) {
  817. if (parentNoteId === 'root' || parentNoteId === 'none') {
  818. continue;
  819. }
  820. const parentNote = froca.notes[parentNoteId];
  821. if (!parentNote || parentNote.type === 'search') {
  822. continue;
  823. }
  824. if (parentNote.noteId === '_share' || parentNote.isShared()) {
  825. return true;
  826. }
  827. }
  828. return false;
  829. }
  830. isContentAvailable() {
  831. return !this.isProtected || protectedSessionHolder.isProtectedSessionAvailable()
  832. }
  833. isLaunchBarConfig() {
  834. return this.type === 'launcher' || ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(this.noteId);
  835. }
  836. isOptions() {
  837. return this.noteId.startsWith("_options");
  838. }
  839. /**
  840. * Provides note's date metadata.
  841. *
  842. * @returns {Promise&lt;{dateCreated: string, utcDateCreated: string, dateModified: string, utcDateModified: string}>}
  843. */
  844. async getMetadata() {
  845. return await server.get(`notes/${this.noteId}/metadata`);
  846. }
  847. }
  848. export default FNote;
  849. </code></pre>
  850. </article>
  851. </section>
  852. </div>
  853. <nav>
  854. <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="BasicWidget.html">BasicWidget</a></li><li><a href="FAttachment.html">FAttachment</a></li><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="NoteContextAwareWidget.html">NoteContextAwareWidget</a></li><li><a href="RightPanelWidget.html">RightPanelWidget</a></li></ul><h3>Global</h3><ul><li><a href="global.html#api">api</a></li><li><a href="global.html#getJsonContent">getJsonContent</a></li><li><a href="global.html#getJsonContentSafely">getJsonContentSafely</a></li></ul>
  855. </nav>
  856. <br class="clear">
  857. <footer>
  858. Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
  859. </footer>
  860. <script> prettyPrint(); </script>
  861. <script src="scripts/linenumber.js"> </script>
  862. </body>
  863. </html>