All user data for FoundryVTT. Includes worlds, systems, modules, and any asset in the "foundryuserdata" directory. Does NOT include the FoundryVTT installation itself.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3602 lines
114 KiB

1 year ago
  1. import { __classPrivateFieldSet, __classPrivateFieldGet, Fuse, SvelteComponent, init, safe_not_equal, empty, insert, noop, detach, createEventDispatcher, afterUpdate, element, attr, update_keyed_each, space, text, toggle_class, append, listen, set_data, destroy_each, run_all, binding_callbacks, destroy_block, stop_propagation, src_url_equal, HtmlTag } from './vendor.js';
  2. const MODULE_NAME = "quick-insert";
  3. function registerSetting(setting, callback, { ...options }) {
  4. game.settings.register(MODULE_NAME, setting, {
  5. config: true,
  6. scope: "client",
  7. ...options,
  8. onChange: callback || undefined,
  9. });
  10. }
  11. function getSetting(setting) {
  12. return game.settings.get(MODULE_NAME, setting);
  13. }
  14. function setSetting(setting, value) {
  15. return game.settings.set(MODULE_NAME, setting, value);
  16. }
  17. function registerMenu({ menu, ...options }) {
  18. game.settings.registerMenu(MODULE_NAME, menu, options);
  19. }
  20. const SAVE_SETTINGS_REVISION = 1;
  21. var ModuleSetting;
  22. (function (ModuleSetting) {
  23. // QUICKOPEN = "quickOpen", // dead setting
  24. ModuleSetting["ENABLE_GLOBAL_CONTEXT"] = "enableGlobalContext";
  25. ModuleSetting["INDEXING_DISABLED"] = "indexingDisabled";
  26. ModuleSetting["FILTERS_CLIENT"] = "filtersClient";
  27. ModuleSetting["FILTERS_WORLD"] = "filtersWorld";
  28. ModuleSetting["FILTERS_SHEETS"] = "filtersSheets";
  29. ModuleSetting["FILTERS_SHEETS_ENABLED"] = "filtersSheetsEnabled";
  30. ModuleSetting["GM_ONLY"] = "gmOnly";
  31. ModuleSetting["AUTOMATIC_INDEXING"] = "automaticIndexing";
  32. ModuleSetting["INDEX_TIMEOUT"] = "indexTimeout";
  33. ModuleSetting["SEARCH_BUTTON"] = "searchButton";
  34. ModuleSetting["KEY_BIND"] = "keyBind";
  35. ModuleSetting["DEFAULT_ACTION_SCENE"] = "defaultSceneAction";
  36. ModuleSetting["DEFAULT_ACTION_ROLL_TABLE"] = "defaultActionRollTable";
  37. ModuleSetting["DEFAULT_ACTION_MACRO"] = "defaultActionMacro";
  38. ModuleSetting["SEARCH_TOOLTIPS"] = "searchTooltips";
  39. ModuleSetting["EMBEDDED_INDEXING"] = "embeddedIndexing";
  40. })(ModuleSetting || (ModuleSetting = {}));
  41. const i18n = (name, replacements) => {
  42. let namespace = "QUICKINSERT";
  43. if (name.includes(".")) {
  44. [namespace, name] = name.split(".", 2);
  45. }
  46. if (replacements) {
  47. return game.i18n.format(`${namespace}.${name}`, replacements);
  48. }
  49. return game.i18n.localize(`${namespace}.${name}`);
  50. };
  51. function isTextInputElement(element) {
  52. return (element.tagName == "TEXTAREA" ||
  53. (element.tagName == "INPUT" && element.type == "text"));
  54. }
  55. // General utils
  56. const ALPHA = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  57. function randomId(idLength = 10) {
  58. const values = new Uint8Array(idLength);
  59. window.crypto.getRandomValues(values);
  60. return String.fromCharCode(...values.map((x) => ALPHA.charCodeAt(x % ALPHA.length)));
  61. }
  62. // Some black magic from the internet,
  63. // places caret at end of contenteditable
  64. function placeCaretAtEnd(el) {
  65. if (!el)
  66. return;
  67. el.focus();
  68. const range = document.createRange();
  69. range.selectNodeContents(el);
  70. range.collapse(false);
  71. const sel = window.getSelection();
  72. sel?.removeAllRanges();
  73. sel?.addRange(range);
  74. }
  75. // Simple utility function for async waiting
  76. // Nicer to await waitFor(100) than nesting setTimeout callback hell
  77. function resolveAfter(msec) {
  78. return new Promise((res) => setTimeout(res, msec));
  79. }
  80. class TimeoutError extends Error {
  81. constructor(timeoutMsec) {
  82. super(`did not complete within ${timeoutMsec}ms`);
  83. }
  84. }
  85. function withDeadline(p, timeoutMsec) {
  86. return Promise.race([
  87. p,
  88. new Promise((res, rej) => setTimeout(() => rej(new TimeoutError(timeoutMsec)), timeoutMsec)),
  89. ]);
  90. }
  91. function permissionListEq(a, b) {
  92. return a.length === b.length && [...a].every((value) => b.includes(value));
  93. }
  94. // Match keybinds even if it's in input fields or with explicit context
  95. function customKeybindHandler(evt, context) {
  96. if (evt.isComposing || (!evt.key && !evt.code)) {
  97. return;
  98. }
  99. if (!context && !game.keyboard?.hasFocus)
  100. return;
  101. const ctx = KeyboardManager.getKeyboardEventContext(evt, false);
  102. if (ctx.event.target?.dataset?.engine === "prosemirror") {
  103. return;
  104. }
  105. if (context) {
  106. ctx._quick_insert_extra = { context };
  107. }
  108. //@ts-expect-error using private, I know
  109. const actions = KeyboardManager._getMatchingActions(ctx)
  110. .map((action) => game.keybindings.actions.get(action.action))
  111. .filter((action) => action?.textInput);
  112. if (!actions.length)
  113. return;
  114. let handled = false;
  115. for (const action of actions) {
  116. //@ts-expect-error using private, I know
  117. handled = KeyboardManager._executeKeybind(action, ctx);
  118. if (handled)
  119. break;
  120. }
  121. if (handled) {
  122. evt.preventDefault();
  123. evt.stopPropagation();
  124. }
  125. }
  126. var _EmbeddedEntitySearchItem_tagline, _EmbeddedCompendiumSearchItem_tagline;
  127. var DocumentType;
  128. (function (DocumentType) {
  129. DocumentType["ACTOR"] = "Actor";
  130. DocumentType["ITEM"] = "Item";
  131. DocumentType["JOURNALENTRY"] = "JournalEntry";
  132. DocumentType["MACRO"] = "Macro";
  133. DocumentType["ROLLTABLE"] = "RollTable";
  134. DocumentType["SCENE"] = "Scene";
  135. })(DocumentType || (DocumentType = {}));
  136. const IndexedDocumentTypes = [
  137. DocumentType.ACTOR,
  138. DocumentType.ITEM,
  139. DocumentType.JOURNALENTRY,
  140. DocumentType.MACRO,
  141. DocumentType.ROLLTABLE,
  142. DocumentType.SCENE,
  143. ];
  144. const EmbeddedDocumentTypes = {
  145. [DocumentType.JOURNALENTRY]: "JournalEntryPage",
  146. };
  147. const EmbeddedDocumentCollections = {
  148. [DocumentType.JOURNALENTRY]: "pages",
  149. };
  150. const DocumentMeta = {
  151. [DocumentType.ACTOR]: CONFIG.Actor.documentClass.metadata,
  152. [DocumentType.ITEM]: CONFIG.Item.documentClass.metadata,
  153. [DocumentType.JOURNALENTRY]: CONFIG.JournalEntry.documentClass.metadata,
  154. [DocumentType.MACRO]: CONFIG.Macro.documentClass.metadata,
  155. [DocumentType.ROLLTABLE]: CONFIG.RollTable.documentClass.metadata,
  156. [DocumentType.SCENE]: CONFIG.Scene.documentClass.metadata,
  157. };
  158. const documentIcons = {
  159. [DocumentType.ACTOR]: "fa-user",
  160. [DocumentType.ITEM]: "fa-suitcase",
  161. [DocumentType.JOURNALENTRY]: "fa-book-open",
  162. [DocumentType.MACRO]: "fa-terminal",
  163. [DocumentType.ROLLTABLE]: "fa-th-list",
  164. [DocumentType.SCENE]: "fa-map",
  165. };
  166. function extractEmbeddedIndex(item, pack) {
  167. if (!("pages" in item))
  168. return;
  169. if (pack) {
  170. return item.pages.name.map((name, i) => new EmbeddedCompendiumSearchItem(pack, {
  171. _id: item.pages._id[i],
  172. parentName: item.name,
  173. embeddedName: name,
  174. parentId: item._id,
  175. type: "JournalEntryPage",
  176. tagline: `Pg. ${i} - ${pack?.metadata?.label || pack.title}`,
  177. }));
  178. }
  179. // TODO: Index directory
  180. }
  181. function getCollectionFromType(type) {
  182. //@ts-expect-error not documented
  183. return CONFIG[type].collection.instance;
  184. }
  185. const ignoredFolderNames = { _fql_quests: true };
  186. function enabledDocumentTypes() {
  187. const disabled = getSetting(ModuleSetting.INDEXING_DISABLED);
  188. return IndexedDocumentTypes.filter((t) => !disabled?.entities?.[t]?.includes(game.user?.role));
  189. }
  190. function enabledEmbeddedDocumentTypes() {
  191. if (enabledDocumentTypes().includes(DocumentType.JOURNALENTRY) &&
  192. getSetting(ModuleSetting.EMBEDDED_INDEXING)) {
  193. return [EmbeddedDocumentTypes[DocumentType.JOURNALENTRY]];
  194. }
  195. return [];
  196. }
  197. function packEnabled(pack) {
  198. const disabled = getSetting(ModuleSetting.INDEXING_DISABLED);
  199. // Pack entity type enabled?
  200. if (disabled?.entities?.[pack.metadata.type]?.includes(game.user?.role)) {
  201. return false;
  202. }
  203. // Pack enabled?
  204. if (disabled?.packs?.[pack.collection]?.includes(game.user?.role)) {
  205. return false;
  206. }
  207. // Pack entity type indexed?
  208. if (!IndexedDocumentTypes.includes(pack.metadata.type)) {
  209. return false;
  210. }
  211. // Not hidden?
  212. return !(pack.private && !game.user?.isGM);
  213. }
  214. function getDirectoryName(type) {
  215. const documentLabel = DocumentMeta[type].labelPlural;
  216. return i18n("SIDEBAR.DirectoryTitle", {
  217. type: documentLabel ? i18n(documentLabel) : type,
  218. });
  219. }
  220. class SearchItem {
  221. constructor(data) {
  222. this.id = data.id;
  223. this.uuid = data.uuid;
  224. this.name = data.name;
  225. this.documentType = data.documentType;
  226. this.img = data.img;
  227. }
  228. // Get the drag data for drag operations
  229. get dragData() {
  230. return {};
  231. }
  232. // Get the html for an icon that represents the item
  233. get icon() {
  234. return "";
  235. }
  236. // Reference the entity in a journal, chat or other places that support it
  237. get journalLink() {
  238. return "";
  239. }
  240. // Reference the entity in a script
  241. get script() {
  242. return "";
  243. }
  244. // Short tagline that explains where/what this is
  245. get tagline() {
  246. return "";
  247. }
  248. // Additional details for result tooltips
  249. get tooltip() {
  250. const type = i18n(DocumentMeta[this.documentType]?.label);
  251. return `${type}, ${this.tagline}`;
  252. }
  253. // Show the sheet or equivalent of this search result
  254. async show() {
  255. return;
  256. }
  257. // Fetch the original object (or null if no longer available).
  258. // NEVER call as part of indexing or filtering.
  259. // It can be slow and most calls will cause a request to the database!
  260. // Call it once a decision is made, do not call for every SearchItem!
  261. async get() {
  262. return null;
  263. }
  264. }
  265. class EntitySearchItem extends SearchItem {
  266. constructor(data) {
  267. super(data);
  268. const folder = data.folder;
  269. if (folder) {
  270. this.folder = {
  271. id: folder.id,
  272. name: folder.name,
  273. };
  274. }
  275. }
  276. static fromEntities(entities) {
  277. return entities
  278. .filter((e) => {
  279. return (e.visible && !(e.folder?.name && ignoredFolderNames[e.folder.name]));
  280. })
  281. .map((doc) => {
  282. let embedded;
  283. if (EmbeddedDocumentTypes[doc.documentName] &&
  284. enabledEmbeddedDocumentTypes().includes(EmbeddedDocumentTypes[doc.documentName])) {
  285. const collection =
  286. //@ts-expect-error can't type this right now
  287. doc[EmbeddedDocumentCollections[doc.documentName]];
  288. embedded = collection.map(EmbeddedEntitySearchItem.fromDocument);
  289. }
  290. return embedded
  291. ? [...embedded, this.fromDocument(doc)]
  292. : [this.fromDocument(doc)];
  293. })
  294. .flat();
  295. }
  296. static fromDocument(doc) {
  297. if ("PDFoundry" in ui && "pdfoundry" in doc.data.flags) {
  298. return new PDFoundySearchItem({
  299. id: doc.id,
  300. uuid: doc.uuid,
  301. name: doc.name,
  302. documentType: doc.documentName,
  303. //@ts-expect-error data is merged wih doc
  304. img: doc.img,
  305. folder: doc.folder || undefined,
  306. });
  307. }
  308. return new EntitySearchItem({
  309. id: doc.id,
  310. uuid: doc.uuid,
  311. name: doc.name,
  312. documentType: doc.documentName,
  313. //@ts-expect-error data is merged wih doc
  314. img: doc.img,
  315. folder: doc.folder || undefined,
  316. });
  317. }
  318. // Get the drag data for drag operations
  319. get dragData() {
  320. return {
  321. type: this.documentType,
  322. uuid: this.uuid,
  323. };
  324. }
  325. get icon() {
  326. return `<i class="fas ${documentIcons[this.documentType]} entity-icon"></i>`;
  327. }
  328. // Reference the entity in a journal, chat or other places that support it
  329. get journalLink() {
  330. return `@${this.documentType}[${this.id}]{${this.name}}`;
  331. }
  332. // Reference the entity in a script
  333. get script() {
  334. return `game.${DocumentMeta[this.documentType].collection}.get("${this.id}")`;
  335. }
  336. // Short tagline that explains where/what this is
  337. get tagline() {
  338. if (this.folder) {
  339. return `${this.folder.name}`;
  340. }
  341. return `${getDirectoryName(this.documentType)}`;
  342. }
  343. async show() {
  344. (await this.get())?.sheet?.render(true);
  345. }
  346. async get() {
  347. return getCollectionFromType(this.documentType).get(this.id);
  348. }
  349. }
  350. class PDFoundySearchItem extends EntitySearchItem {
  351. get icon() {
  352. return `<img class="pdf-thumbnail" src="modules/pdfoundry/assets/pdf_icon.svg" alt="PDF Icon">`;
  353. }
  354. get journalLink() {
  355. return `@PDF[${this.name}|page=1]{${this.name}}`;
  356. }
  357. async show() {
  358. const entity = await this.get();
  359. ui?.PDFoundry.openPDFByName(this.name, { entity });
  360. }
  361. }
  362. class CompendiumSearchItem extends SearchItem {
  363. constructor(pack, item) {
  364. const packName = pack.collection;
  365. super({
  366. id: item._id,
  367. uuid: `Compendium.${packName}.${item._id}`,
  368. name: item.name,
  369. documentType: pack.metadata.type,
  370. img: item.img,
  371. });
  372. this.package = packName;
  373. this.packageName = pack?.metadata?.label || pack.title;
  374. this.documentType = pack.metadata.type;
  375. this.uuid = `Compendium.${this.package}.${this.id}`;
  376. }
  377. static fromCompendium(pack) {
  378. const cIndex = pack.index;
  379. return cIndex
  380. .map((item) => {
  381. const embedded = extractEmbeddedIndex(item, pack);
  382. const searchItem = new CompendiumSearchItem(pack, item);
  383. return embedded ? [searchItem, embedded] : searchItem;
  384. })
  385. .flat(2);
  386. }
  387. // Get the drag data for drag operations
  388. get dragData() {
  389. return {
  390. type: this.documentType,
  391. uuid: this.uuid,
  392. };
  393. }
  394. get icon() {
  395. return `<i class="fas ${documentIcons[this.documentType]} entity-icon"></i>`;
  396. }
  397. // Reference the entity in a journal, chat or other places that support it
  398. get journalLink() {
  399. return `@Compendium[${this.package}.${this.id}]{${this.name}}`;
  400. }
  401. // Reference the entity in a script
  402. get script() {
  403. return `fromUuid("${this.uuid}")`; // TODO: note that this is async somehow?
  404. }
  405. // Short tagline that explains where/what this is
  406. get tagline() {
  407. return `${this.packageName}`;
  408. }
  409. async show() {
  410. (await this.get())?.sheet?.render(true);
  411. }
  412. async get() {
  413. return (await fromUuid(this.uuid));
  414. }
  415. }
  416. class EmbeddedEntitySearchItem extends SearchItem {
  417. constructor(item) {
  418. super({
  419. id: item.id,
  420. uuid: item.uuid,
  421. name: `${item.embeddedName} | ${item.parentName}`,
  422. documentType: item.type,
  423. img: item.img,
  424. });
  425. _EmbeddedEntitySearchItem_tagline.set(this, void 0);
  426. __classPrivateFieldSet(this, _EmbeddedEntitySearchItem_tagline, item.tagline, "f");
  427. }
  428. static fromDocument(document) {
  429. if (!document.parent || !document.id) {
  430. throw new Error("Not properly embedded");
  431. }
  432. //@ts-expect-error There has to be an easier way...
  433. const number = [...document.parent[document.collectionName].keys()].indexOf(document.id);
  434. const parentType = document.parent.documentName;
  435. return new EmbeddedEntitySearchItem({
  436. id: document.id,
  437. uuid: document.uuid,
  438. parentName: document.parent.name || undefined,
  439. embeddedName: document.name,
  440. type: parentType,
  441. tagline: `Pg. ${number} - ${document.parent.folder?.name || getDirectoryName(parentType)}`,
  442. });
  443. }
  444. // Get the drag data for drag operations
  445. get dragData() {
  446. return {
  447. // TODO: Use type from index
  448. type: "JournalEntryPage",
  449. uuid: this.uuid,
  450. };
  451. }
  452. get icon() {
  453. // TODO: Add table tor subtypes
  454. return `<i class="fa-duotone fa-book-open entity-icon"></i>`;
  455. }
  456. // Reference the entity in a journal, chat or other places that support it
  457. get journalLink() {
  458. return `@UUID[${this.uuid}]{${this.name}}`;
  459. }
  460. // Reference the entity in a script
  461. get script() {
  462. return `fromUuid("${this.uuid}")`;
  463. }
  464. // Short tagline that explains where/what this is
  465. get tagline() {
  466. return __classPrivateFieldGet(this, _EmbeddedEntitySearchItem_tagline, "f") || "";
  467. }
  468. get tooltip() {
  469. const type = i18n(DocumentMeta[this.documentType]?.label);
  470. //@ts-expect-error Update types!
  471. const page = i18n(CONFIG.JournalEntryPage.documentClass.metadata.label);
  472. return `${type} ${page}, ${__classPrivateFieldGet(this, _EmbeddedEntitySearchItem_tagline, "f")}`;
  473. }
  474. async show() {
  475. //@ts-expect-error This is good enough for now
  476. (await this.get())?._onClickDocumentLink({
  477. currentTarget: { dataset: {} },
  478. });
  479. }
  480. async get() {
  481. return (await fromUuid(this.uuid));
  482. }
  483. }
  484. _EmbeddedEntitySearchItem_tagline = new WeakMap();
  485. class EmbeddedCompendiumSearchItem extends SearchItem {
  486. constructor(pack, item) {
  487. const packName = pack.collection;
  488. const uuid = `Compendium.${packName}.${item.parentId}.${item.type}.${item._id}`;
  489. super({
  490. id: item._id,
  491. uuid,
  492. name: `${item.embeddedName} | ${item.parentName}`,
  493. documentType: item.type,
  494. img: item.img,
  495. });
  496. // Inject overrides??
  497. _EmbeddedCompendiumSearchItem_tagline.set(this, void 0);
  498. this.uuid = uuid;
  499. this.package = packName;
  500. this.packageName = pack?.metadata?.label || pack.title;
  501. this.documentType = pack.metadata.type;
  502. __classPrivateFieldSet(this, _EmbeddedCompendiumSearchItem_tagline, item.tagline, "f");
  503. }
  504. static fromDocument(document) {
  505. if (!document.parent) {
  506. throw new Error("Document is not embedded");
  507. }
  508. if (!document.pack) {
  509. throw new Error("Document has no pack");
  510. }
  511. const pack = game.packs.get(document.pack);
  512. if (!pack) {
  513. throw new Error("Document has invalid pack");
  514. }
  515. //@ts-expect-error There has to be an easier way...
  516. const number = [...document.parent[document.collectionName].keys()].indexOf(document.id);
  517. return new EmbeddedCompendiumSearchItem(pack, {
  518. _id: document.id,
  519. parentName: document.parent.name || undefined,
  520. embeddedName: document.name,
  521. parentId: document.parent.id,
  522. type: "JournalEntryPage",
  523. tagline: `Pg. ${number} - ${pack?.metadata?.label || pack.title}`,
  524. });
  525. }
  526. // Get the drag data for drag operations
  527. get dragData() {
  528. return {
  529. // TODO: Use type from index
  530. type: "JournalEntryPage",
  531. uuid: this.uuid,
  532. };
  533. }
  534. get icon() {
  535. // TODO: Add table tor subtypes
  536. return `<i class="fa-duotone fa-book-open entity-icon"></i>`;
  537. }
  538. // Reference the entity in a journal, chat or other places that support it
  539. get journalLink() {
  540. return `@UUID[${this.uuid}]{${this.name}}`;
  541. }
  542. // Reference the entity in a script
  543. get script() {
  544. return `fromUuid("${this.uuid}")`; // TODO: note that this is async somehow?
  545. }
  546. // Short tagline that explains where/what this is
  547. get tagline() {
  548. return __classPrivateFieldGet(this, _EmbeddedCompendiumSearchItem_tagline, "f") || `${this.packageName}`;
  549. }
  550. get tooltip() {
  551. const type = i18n(DocumentMeta[this.documentType]?.label);
  552. //@ts-expect-error Update types!
  553. const page = i18n(CONFIG.JournalEntryPage.documentClass.metadata.label);
  554. return `${type} ${page}, ${__classPrivateFieldGet(this, _EmbeddedCompendiumSearchItem_tagline, "f")}`;
  555. }
  556. async show() {
  557. //@ts-expect-error This is good enough for now
  558. (await this.get())?._onClickDocumentLink({
  559. currentTarget: { dataset: {} },
  560. });
  561. }
  562. async get() {
  563. return (await fromUuid(this.uuid));
  564. }
  565. }
  566. _EmbeddedCompendiumSearchItem_tagline = new WeakMap();
  567. function searchItemFromDocument(document) {
  568. if (document.parent) {
  569. if (document.compendium) {
  570. return EmbeddedCompendiumSearchItem.fromDocument(document);
  571. }
  572. return EmbeddedEntitySearchItem.fromDocument(document);
  573. }
  574. if (document.compendium) {
  575. return new CompendiumSearchItem(document.compendium, {
  576. _id: document.id,
  577. name: document.name,
  578. //@ts-ignore
  579. img: document.img,
  580. });
  581. }
  582. return EntitySearchItem.fromDocument(document);
  583. }
  584. function isEntity(item) {
  585. return item instanceof EntitySearchItem;
  586. }
  587. function isCompendiumEntity(item) {
  588. return item instanceof CompendiumSearchItem;
  589. }
  590. class FuseSearchIndex {
  591. constructor() {
  592. this.fuse = new Fuse([], {
  593. keys: ["name"],
  594. includeMatches: true,
  595. threshold: 0.3,
  596. });
  597. }
  598. addAll(items) {
  599. for (const item of items) {
  600. this.fuse.add(item);
  601. }
  602. }
  603. add(item) {
  604. this.fuse.add(item);
  605. }
  606. removeByUuid(uuid) {
  607. this.fuse.remove((i) => i?.uuid == uuid);
  608. }
  609. search(query) {
  610. return this.fuse.search(query).map((res) => ({
  611. item: res.item,
  612. match: res.matches,
  613. }));
  614. }
  615. }
  616. class SearchLib {
  617. constructor() {
  618. this.index = new FuseSearchIndex();
  619. }
  620. indexCompendium(compendium) {
  621. if (!compendium)
  622. return;
  623. if (packEnabled(compendium)) {
  624. const index = CompendiumSearchItem.fromCompendium(compendium);
  625. this.index.addAll(index);
  626. }
  627. }
  628. async indexCompendiums() {
  629. if (!game.packs)
  630. return;
  631. for await (const res of loadIndexes()) {
  632. if (res.error) {
  633. console.log("Quick Insert | Index loading failure", res);
  634. continue;
  635. }
  636. console.log("Quick Insert | Index loading success", res);
  637. this.indexCompendium(game.packs.get(res.pack));
  638. }
  639. }
  640. indexDocuments() {
  641. for (const type of enabledDocumentTypes()) {
  642. this.index.addAll(EntitySearchItem.fromEntities(getCollectionFromType(type).contents));
  643. }
  644. }
  645. addItem(item) {
  646. this.index.add(item);
  647. }
  648. removeItem(entityUuid) {
  649. this.index.removeByUuid(entityUuid);
  650. }
  651. replaceItem(item) {
  652. this.removeItem(item.uuid);
  653. this.addItem(item);
  654. }
  655. search(text, filter, max) {
  656. if (filter) {
  657. return this.index.search(text).filter(filter).slice(0, max);
  658. }
  659. return this.index.search(text).slice(0, max);
  660. }
  661. }
  662. function formatMatch(result, formatFn) {
  663. const match = result.match[0];
  664. if (!match.value)
  665. return "";
  666. let text = match.value;
  667. [...match.indices].reverse().forEach(([start, end]) => {
  668. // if (start === end) return;
  669. text =
  670. text.substring(0, start) +
  671. formatFn(text.substring(start, end + 1)) +
  672. text.substring(end + 1);
  673. });
  674. return text;
  675. }
  676. async function* loadIndexes() {
  677. if (!game.packs) {
  678. console.error("Can't load indexes before packs are initialized");
  679. return;
  680. }
  681. // Information about failures
  682. const failures = {};
  683. const timeout = getSetting(ModuleSetting.INDEX_TIMEOUT);
  684. const packsRemaining = [];
  685. for (const pack of game.packs) {
  686. if (packEnabled(pack)) {
  687. failures[pack.collection] = { errors: 0 };
  688. packsRemaining.push(pack);
  689. }
  690. }
  691. while (packsRemaining.length > 0) {
  692. const pack = packsRemaining.shift();
  693. if (!pack)
  694. break;
  695. let promise;
  696. try {
  697. let options;
  698. if (getSetting(ModuleSetting.EMBEDDED_INDEXING)) {
  699. if (pack.documentClass.documentName === "JournalEntry") {
  700. options = { fields: ["pages.name", "pages._id"] };
  701. }
  702. }
  703. promise = failures[pack.collection].waiting ?? pack.getIndex(options);
  704. await withDeadline(promise, timeout * (failures[pack.collection].errors + 1));
  705. }
  706. catch (error) {
  707. ++failures[pack.collection].errors;
  708. if (error instanceof TimeoutError) {
  709. failures[pack.collection].waiting = promise;
  710. }
  711. else {
  712. delete failures[pack.collection].waiting;
  713. }
  714. yield {
  715. error: error,
  716. pack: pack.collection,
  717. packsLeft: packsRemaining.length,
  718. errorCount: failures[pack.collection].errors,
  719. };
  720. if (failures[pack.collection].errors <= 4) {
  721. // Pack failed, will be retried later.
  722. packsRemaining.push(pack);
  723. }
  724. else {
  725. console.warn(`Quick Insert | Package "${pack.collection}" could not be indexed `);
  726. }
  727. continue;
  728. }
  729. yield {
  730. pack: pack.collection,
  731. packsLeft: packsRemaining.length,
  732. errorCount: failures[pack.collection].errors,
  733. };
  734. }
  735. }
  736. function checkIndexed(document, embedded = false) {
  737. if (!document.visible)
  738. return false;
  739. // Check embedded state
  740. if ((embedded && !document.parent) || (!embedded && document.parent)) {
  741. return false;
  742. }
  743. // Check enabled types
  744. if (document.parent) {
  745. if (!enabledEmbeddedDocumentTypes().includes(document.documentName))
  746. return false;
  747. }
  748. else {
  749. if (!enabledDocumentTypes().includes(document.documentName))
  750. return false;
  751. }
  752. // Check disabled packs
  753. return !(document.pack && !packEnabled(document.compendium));
  754. }
  755. function setupDocumentHooks(quickInsert) {
  756. enabledDocumentTypes().forEach((type) => {
  757. Hooks.on(`create${type}`, (document) => {
  758. if (document.parent || !checkIndexed(document))
  759. return;
  760. quickInsert.searchLib?.addItem(searchItemFromDocument(document));
  761. });
  762. Hooks.on(`update${type}`, (document) => {
  763. if (document.parent)
  764. return;
  765. if (!checkIndexed(document)) {
  766. quickInsert.searchLib?.removeItem(document.uuid);
  767. return;
  768. }
  769. quickInsert.searchLib?.replaceItem(searchItemFromDocument(document));
  770. });
  771. Hooks.on(`delete${type}`, (document) => {
  772. if (document.parent || !checkIndexed(document))
  773. return;
  774. quickInsert.searchLib?.removeItem(document.uuid);
  775. });
  776. });
  777. enabledEmbeddedDocumentTypes().forEach((type) => {
  778. Hooks.on(`create${type}`, (document) => {
  779. if (!document.parent || !checkIndexed(document, true))
  780. return;
  781. const item = searchItemFromDocument(document);
  782. quickInsert.searchLib?.addItem(item);
  783. });
  784. Hooks.on(`update${type}`, (document) => {
  785. if (!document.parent)
  786. return;
  787. if (!checkIndexed(document, true)) {
  788. quickInsert.searchLib?.removeItem(document.uuid);
  789. return;
  790. }
  791. const item = searchItemFromDocument(document);
  792. quickInsert.searchLib?.replaceItem(item);
  793. });
  794. Hooks.on(`delete${type}`, (document) => {
  795. if (!document.parent || !checkIndexed(document, true))
  796. return;
  797. quickInsert.searchLib?.removeItem(document.uuid);
  798. });
  799. });
  800. }
  801. var FilterType;
  802. (function (FilterType) {
  803. FilterType[FilterType["Default"] = 0] = "Default";
  804. FilterType[FilterType["World"] = 1] = "World";
  805. FilterType[FilterType["Client"] = 2] = "Client";
  806. })(FilterType || (FilterType = {}));
  807. var ContextMode;
  808. (function (ContextMode) {
  809. ContextMode[ContextMode["Browse"] = 0] = "Browse";
  810. ContextMode[ContextMode["Insert"] = 1] = "Insert";
  811. })(ContextMode || (ContextMode = {}));
  812. class SearchContext {
  813. constructor() {
  814. this.mode = ContextMode.Insert;
  815. this.spawnCSS = {};
  816. this.allowMultiple = true;
  817. }
  818. onClose() {
  819. return;
  820. }
  821. }
  822. // Default browse context
  823. class BrowseContext extends SearchContext {
  824. constructor() {
  825. super();
  826. this.mode = ContextMode.Browse;
  827. this.startText = document.getSelection()?.toString();
  828. }
  829. onSubmit(item) {
  830. // Render the sheet for selected item
  831. item.show();
  832. }
  833. }
  834. class InputContext extends SearchContext {
  835. constructor(input) {
  836. super();
  837. this.selectionStart = null;
  838. this.selectionEnd = null;
  839. this.input = input;
  840. const targetRect = input.getBoundingClientRect();
  841. const bodyRect = document.body.getBoundingClientRect();
  842. const top = targetRect.top - bodyRect.top;
  843. // TODO: Real calculation!!!
  844. this.spawnCSS = {
  845. left: targetRect.left + 5,
  846. bottom: bodyRect.height - top - 30,
  847. width: targetRect.width - 10,
  848. };
  849. this.selectionStart = input.selectionStart;
  850. this.selectionEnd = input.selectionEnd;
  851. if (this.selectionStart !== null && this.selectionEnd !== null) {
  852. if (this.selectionStart != this.selectionEnd) {
  853. this.startText = this.input.value.slice(this.selectionStart, this.selectionEnd);
  854. }
  855. }
  856. $(input).addClass("quick-insert-context");
  857. }
  858. insertResult(result) {
  859. if (this.selectionStart !== null && this.selectionEnd !== null) {
  860. this.input.value =
  861. this.input.value.slice(0, this.selectionStart) +
  862. result +
  863. this.input.value.slice(this.selectionEnd);
  864. }
  865. else {
  866. this.input.value = result;
  867. }
  868. }
  869. onSubmit(item) {
  870. if (typeof item == "string") {
  871. this.insertResult(item);
  872. }
  873. else {
  874. this.insertResult(item.journalLink);
  875. }
  876. }
  877. onClose() {
  878. $(this.input).removeClass("quick-insert-context");
  879. this.input.focus();
  880. }
  881. }
  882. class ScriptMacroContext extends InputContext {
  883. onSubmit(item) {
  884. if (typeof item == "string") {
  885. this.insertResult(`"${item}"`);
  886. }
  887. else {
  888. this.insertResult(item.script);
  889. }
  890. }
  891. }
  892. class RollTableContext extends InputContext {
  893. constructor(input) {
  894. super(input);
  895. this.allowMultiple = false;
  896. // Set filter depending on selected dropdown!
  897. // const resultRow = this.input.closest("li.table-result")
  898. }
  899. onSubmit(item) {
  900. if (typeof item == "string") {
  901. this.insertResult(item);
  902. return;
  903. }
  904. const row = $(this.input).closest(".table-result");
  905. const resultId = row.data("result-id");
  906. const appId = row.closest(".window-app").data("appid");
  907. const app = ui.windows[parseInt(appId)];
  908. if (isEntity(item)) {
  909. app.object.updateEmbeddedDocuments("TableResult", [
  910. {
  911. _id: resultId,
  912. collection: item.documentType,
  913. type: 1,
  914. resultId: item.id,
  915. text: item.name,
  916. img: item.img || null,
  917. },
  918. ]);
  919. }
  920. else if (isCompendiumEntity(item)) {
  921. app.object.updateEmbeddedDocuments("TableResult", [
  922. {
  923. _id: resultId,
  924. collection: item.package,
  925. type: 2,
  926. resultId: item.id,
  927. text: item.name,
  928. img: item.img || null,
  929. },
  930. ]);
  931. }
  932. }
  933. }
  934. class TinyMCEContext extends SearchContext {
  935. constructor(editor) {
  936. super();
  937. const targetRect = editor.selection.getBoundingClientRect();
  938. const bodyRect = document.body.getBoundingClientRect();
  939. const containerRect = editor.contentAreaContainer.getBoundingClientRect();
  940. const top = containerRect.top + targetRect.top;
  941. this.spawnCSS = {
  942. left: containerRect.left + targetRect.left,
  943. bottom: bodyRect.height - top - 20,
  944. width: targetRect.width,
  945. maxHeight: top + 20,
  946. };
  947. this.editor = editor;
  948. this.startText = editor.selection.getContent().trim();
  949. }
  950. onSubmit(item) {
  951. if (typeof item == "string") {
  952. this.editor.insertContent(item);
  953. }
  954. else {
  955. this.editor.insertContent(item.journalLink);
  956. }
  957. }
  958. onClose() {
  959. this.editor.focus();
  960. }
  961. }
  962. class ProseMirrorContext extends SearchContext {
  963. constructor(state, dispatch, view) {
  964. super();
  965. this.state = state;
  966. this.dispatch = dispatch;
  967. this.view = view;
  968. this.startText = document.getSelection()?.toString();
  969. const start = view.coordsAtPos(state.selection.from);
  970. const end = view.coordsAtPos(state.selection.to);
  971. const bodyRect = document.body.getBoundingClientRect();
  972. const bottom = bodyRect.height - start.top - 22;
  973. this.spawnCSS = {
  974. left: start.left,
  975. bottom,
  976. width: end.left - start.left,
  977. maxHeight: bodyRect.height - bottom,
  978. };
  979. }
  980. onSubmit(item) {
  981. const tr = this.state.tr;
  982. const text = typeof item == "string" ? item : item.journalLink;
  983. const textNode = this.state.schema.text(text);
  984. tr.replaceSelectionWith(textNode);
  985. this.dispatch(tr);
  986. this.view.focus();
  987. }
  988. onClose() {
  989. this.view.focus();
  990. }
  991. }
  992. class CharacterSheetContext extends SearchContext {
  993. constructor(documentSheet, anchor) {
  994. super();
  995. this.restrictTypes = [DocumentType.ITEM];
  996. this.documentSheet = documentSheet;
  997. this.anchor = anchor;
  998. const targetRect = anchor.get()[0].getBoundingClientRect();
  999. const bodyRect = document.body.getBoundingClientRect();
  1000. const top = bodyRect.top + targetRect.top;
  1001. this.spawnCSS = {
  1002. left: targetRect.left - 280,
  1003. bottom: bodyRect.height - top - 23,
  1004. width: 300,
  1005. maxHeight: top + 23,
  1006. };
  1007. }
  1008. onSubmit(item) {
  1009. if (typeof item == "string")
  1010. return;
  1011. //@ts-ignore
  1012. return this.documentSheet._onDropItem({}, {
  1013. type: item.documentType,
  1014. uuid: item.uuid,
  1015. });
  1016. }
  1017. }
  1018. function identifyContext(target) {
  1019. if (target && isTextInputElement(target)) {
  1020. if (target.name === "command") {
  1021. if (target
  1022. .closest(".macro-sheet")
  1023. ?.querySelector('select[name="type"]')?.value === "script") {
  1024. return new ScriptMacroContext(target);
  1025. }
  1026. return new InputContext(target);
  1027. }
  1028. else if (target.name.startsWith("results.") &&
  1029. target.closest(".result-details")) {
  1030. return new RollTableContext(target);
  1031. }
  1032. // Right now, only allow in chat!
  1033. if (target.id === "chat-message") {
  1034. return new InputContext(target);
  1035. }
  1036. }
  1037. // No/unknown context, browse only.
  1038. if (getSetting(ModuleSetting.ENABLE_GLOBAL_CONTEXT) === true) {
  1039. return new BrowseContext();
  1040. }
  1041. return null;
  1042. }
  1043. class EmbeddedContext extends BrowseContext {
  1044. constructor() {
  1045. super(...arguments);
  1046. this.spawnCSS = {
  1047. top: "unset",
  1048. left: "0",
  1049. bottom: "0",
  1050. "max-height": "100%",
  1051. width: "100%",
  1052. "box-shadow": "none",
  1053. };
  1054. }
  1055. onSubmit() {
  1056. return;
  1057. }
  1058. }
  1059. class SearchFilterCollection {
  1060. constructor() {
  1061. this.disabled = [];
  1062. this.dirty = true;
  1063. this.defaultFilters = [];
  1064. this.clientFilters = [];
  1065. this.worldFilters = [];
  1066. this.combinedFilters = [];
  1067. }
  1068. get filters() {
  1069. if (this.dirty) {
  1070. this.combinedFilters = [
  1071. ...this.defaultFilters,
  1072. ...this.worldFilters,
  1073. ...this.clientFilters,
  1074. ];
  1075. this.combinedFilters.forEach((f) => (f.disabled = this.disabled.includes(f.id)));
  1076. this.dirty = false;
  1077. }
  1078. return this.combinedFilters;
  1079. }
  1080. // Someone changed the filters, will be saved etc.
  1081. filtersChanged(which) {
  1082. if (which === FilterType.Client) {
  1083. this.saveClient();
  1084. }
  1085. else if (which === FilterType.World) {
  1086. this.saveWorld();
  1087. }
  1088. else {
  1089. this.save();
  1090. }
  1091. }
  1092. search(query) {
  1093. if (!query) {
  1094. return [...this.filters];
  1095. }
  1096. return this.filters.filter((f) => f.tag.includes(query));
  1097. }
  1098. getFilter(id) {
  1099. return this.filters.find((f) => f.id == id);
  1100. }
  1101. getFilterByTag(tag) {
  1102. return this.filters.filter((f) => !f.disabled).find((f) => f.tag == tag);
  1103. }
  1104. addFilter(filter) {
  1105. if (filter.type == FilterType.World) {
  1106. this.worldFilters.push(filter);
  1107. this.filtersChanged(filter.type);
  1108. }
  1109. else if (filter.type == FilterType.Client) {
  1110. this.clientFilters.push(filter);
  1111. this.filtersChanged(filter.type);
  1112. }
  1113. }
  1114. deleteFilter(id) {
  1115. const f = this.filters.find((f) => f.id === id);
  1116. if (!f)
  1117. return;
  1118. if (f.type == FilterType.World) {
  1119. const x = this.worldFilters.findIndex((f) => f.id === id);
  1120. if (x != -1) {
  1121. this.worldFilters.splice(x, 1);
  1122. }
  1123. }
  1124. else if (f.type == FilterType.Client) {
  1125. const x = this.clientFilters.findIndex((f) => f.id === id);
  1126. if (x != -1) {
  1127. this.clientFilters.splice(x, 1);
  1128. }
  1129. }
  1130. this.filtersChanged(f.type);
  1131. }
  1132. resetFilters() {
  1133. this.defaultFilters = [];
  1134. this.clientFilters = [];
  1135. this.worldFilters = [];
  1136. this.combinedFilters = [];
  1137. this.dirty = false;
  1138. }
  1139. loadDefaultFilters() {
  1140. this.loadCompendiumFilters();
  1141. // this.loadDirectoryFilters();
  1142. this.loadEntityFilters();
  1143. this.dirty = true;
  1144. }
  1145. loadEntityFilters() {
  1146. this.defaultFilters = this.defaultFilters.concat(enabledDocumentTypes().map((type) => {
  1147. const metadata = DocumentMeta[type];
  1148. return {
  1149. id: metadata.collection,
  1150. type: FilterType.Default,
  1151. tag: metadata.collection,
  1152. subTitle: `${game.i18n.localize(metadata.label)}`,
  1153. filterConfig: {
  1154. folders: "any",
  1155. compendiums: "any",
  1156. entities: [metadata.name],
  1157. },
  1158. };
  1159. }));
  1160. }
  1161. loadDirectoryFilters() {
  1162. // TODO: find a way to find directories that the user is allowed to see
  1163. if (!game.user?.isGM)
  1164. return;
  1165. this.defaultFilters = this.defaultFilters.concat(enabledDocumentTypes().map((type) => {
  1166. const metadata = DocumentMeta[type];
  1167. return {
  1168. id: `dir.${metadata.collection}`,
  1169. type: FilterType.Default,
  1170. tag: `dir.${metadata.collection}`,
  1171. subTitle: getCollectionFromType(type).directory?.title,
  1172. filterConfig: {
  1173. folders: "any",
  1174. compendiums: [],
  1175. entities: [metadata.name],
  1176. },
  1177. };
  1178. }));
  1179. }
  1180. loadCompendiumFilters() {
  1181. if (!game.packs)
  1182. return;
  1183. this.defaultFilters = this.defaultFilters.concat(game.packs.filter(packEnabled).map((pack) => {
  1184. return {
  1185. id: pack.collection,
  1186. type: FilterType.Default,
  1187. tag: pack.collection,
  1188. subTitle: pack.metadata.label,
  1189. filterConfig: {
  1190. folders: [],
  1191. compendiums: [pack.collection],
  1192. entities: "any",
  1193. },
  1194. };
  1195. }));
  1196. }
  1197. loadClientSave() {
  1198. const clientSave = getSetting(ModuleSetting.FILTERS_CLIENT);
  1199. this.disabled = clientSave.disabled || [];
  1200. this.clientFilters = clientSave.filters || [];
  1201. this.dirty = true;
  1202. }
  1203. loadWorldSave() {
  1204. const worldSave = getSetting(ModuleSetting.FILTERS_WORLD);
  1205. this.worldFilters = worldSave.filters || [];
  1206. this.dirty = true;
  1207. }
  1208. loadSave() {
  1209. this.loadClientSave();
  1210. this.loadWorldSave();
  1211. Hooks.call("QuickInsert:FiltersUpdated");
  1212. }
  1213. saveWorld() {
  1214. if (!game.user?.isGM)
  1215. return;
  1216. const worldSave = {
  1217. filters: [],
  1218. };
  1219. for (const filter of this.worldFilters) {
  1220. delete filter.disabled;
  1221. worldSave.filters.push(filter);
  1222. }
  1223. setSetting(ModuleSetting.FILTERS_WORLD, worldSave);
  1224. }
  1225. saveClient() {
  1226. const clientSave = {
  1227. disabled: [],
  1228. filters: [],
  1229. };
  1230. for (const filter of [
  1231. ...this.defaultFilters,
  1232. ...this.worldFilters,
  1233. ...this.clientFilters,
  1234. ]) {
  1235. if (filter.disabled) {
  1236. clientSave.disabled.push(filter.id);
  1237. }
  1238. if (filter.type === FilterType.Client) {
  1239. clientSave.filters.push(filter);
  1240. }
  1241. }
  1242. setSetting(ModuleSetting.FILTERS_CLIENT, clientSave);
  1243. }
  1244. save() {
  1245. this.saveClient();
  1246. this.saveWorld();
  1247. }
  1248. }
  1249. // Is parentFolder inside targetFolder?
  1250. function isInFolder(parentFolder, targetFolder) {
  1251. while (parentFolder) {
  1252. if (parentFolder === targetFolder)
  1253. return true;
  1254. //@ts-expect-error "parent" migrated to "folder"
  1255. parentFolder = game.folders?.get(parentFolder)?.folder;
  1256. }
  1257. return false;
  1258. }
  1259. function matchFilterConfig(config, item) {
  1260. let folderMatch = false;
  1261. let compendiumMatch = false;
  1262. let entityMatch = true;
  1263. if (isEntity(item.item)) {
  1264. if (config.folders === "any") {
  1265. folderMatch = true;
  1266. }
  1267. else {
  1268. for (const f of config.folders) {
  1269. if (isInFolder(item.item.folder?.id, f)) {
  1270. folderMatch = true;
  1271. break;
  1272. }
  1273. }
  1274. }
  1275. }
  1276. else if (isCompendiumEntity(item.item)) {
  1277. if (config.compendiums == "any") {
  1278. compendiumMatch = true;
  1279. }
  1280. else {
  1281. compendiumMatch = config.compendiums.includes(item.item.package);
  1282. }
  1283. }
  1284. if (config.entities == "any") {
  1285. entityMatch = true;
  1286. }
  1287. else {
  1288. entityMatch = config.entities.includes(item.item.documentType);
  1289. }
  1290. return (folderMatch || compendiumMatch) && entityMatch;
  1291. }
  1292. // Module singleton class that contains everything
  1293. class QuickInsertCore {
  1294. constructor() {
  1295. this.filters = new SearchFilterCollection();
  1296. }
  1297. get hasIndex() {
  1298. return Boolean(this.searchLib?.index);
  1299. }
  1300. /**
  1301. * Incorrect to match like this with new keybinds!
  1302. * @deprecated
  1303. */
  1304. matchBoundKeyEvent() {
  1305. return false;
  1306. }
  1307. // If the global key binds are not enough - e.g. in a custom editor,
  1308. // include the custom search context!
  1309. handleKeybind(evt, context) {
  1310. if (!context)
  1311. throw new Error("A custom context is required!");
  1312. customKeybindHandler(evt, context);
  1313. }
  1314. open(context) {
  1315. this.app?.render(true, { context });
  1316. }
  1317. toggle(context) {
  1318. if (this.app?.open) {
  1319. this.app.closeDialog();
  1320. }
  1321. else {
  1322. this.open(context);
  1323. }
  1324. }
  1325. search(text, filter = null, max = 100) {
  1326. return this.searchLib?.search(text, filter, max) || [];
  1327. }
  1328. async forceIndex() {
  1329. return loadSearchIndex();
  1330. }
  1331. }
  1332. const QuickInsert = new QuickInsertCore();
  1333. // Ensure that only one loadSearchIndex function is running at any one time.
  1334. let isLoading = false;
  1335. async function loadSearchIndex() {
  1336. if (isLoading)
  1337. return;
  1338. isLoading = true;
  1339. console.log("Quick Insert | Preparing search index...");
  1340. const start = performance.now();
  1341. QuickInsert.searchLib = new SearchLib();
  1342. QuickInsert.searchLib.indexDocuments();
  1343. QuickInsert.filters.resetFilters();
  1344. QuickInsert.filters.loadDefaultFilters();
  1345. QuickInsert.filters.loadSave();
  1346. console.log(`Quick Insert | Indexing compendiums with timeout set to ${getSetting(ModuleSetting.INDEX_TIMEOUT)}ms`);
  1347. await QuickInsert.searchLib.indexCompendiums();
  1348. console.log(`Quick Insert | Search index and filters completed. Indexed ${
  1349. // @ts-ignore
  1350. QuickInsert.searchLib?.index?.fuse._docs.length || 0} items in ${performance.now() - start}ms`);
  1351. isLoading = false;
  1352. Hooks.callAll("QuickInsert:IndexCompleted", QuickInsert);
  1353. }
  1354. function parseFilterConfig(collections) {
  1355. const filters = {
  1356. folders: [],
  1357. compendiums: [],
  1358. entities: [],
  1359. };
  1360. for (const coll of collections) {
  1361. const x = coll.indexOf(".");
  1362. const base = coll.slice(0, x);
  1363. const rest = coll.slice(x + 1);
  1364. if (base === "Folder") {
  1365. if (rest === "Any") {
  1366. filters.folders = "any";
  1367. }
  1368. else if (!(typeof filters.folders === "string")) {
  1369. filters.folders.push(rest);
  1370. }
  1371. }
  1372. else if (base === "Compendium") {
  1373. if (rest === "Any") {
  1374. filters.compendiums = "any";
  1375. }
  1376. else if (!(typeof filters.compendiums === "string")) {
  1377. filters.compendiums.push(rest);
  1378. }
  1379. }
  1380. else if (base === "Document" || base === "Entity") {
  1381. if (rest === "Any") {
  1382. filters.entities = "any";
  1383. }
  1384. else if (!(typeof filters.entities === "string")) {
  1385. filters.entities.push(rest);
  1386. }
  1387. }
  1388. }
  1389. return filters;
  1390. }
  1391. class FilterEditor extends Application {
  1392. constructor(filter) {
  1393. super({
  1394. title: i18n("FilterEditorTitle"),
  1395. classes: ["filter-editor"],
  1396. template: "modules/quick-insert/templates/filter-editor.hbs",
  1397. resizable: true,
  1398. width: 550,
  1399. height: 560,
  1400. scrollY: [
  1401. ".collection-list.compendium-list",
  1402. ".collection-list.directory-list",
  1403. ".collection-list.entity-list",
  1404. ],
  1405. });
  1406. this.searchInput = "";
  1407. this.filter = filter;
  1408. this.idPrefix = new RegExp(`^${this.filter.id}_`);
  1409. }
  1410. get element() {
  1411. return super.element;
  1412. }
  1413. prefix(name) {
  1414. return `${this.filter.id}_${name}`;
  1415. }
  1416. unPrefix(name) {
  1417. return name.replace(this.idPrefix, "");
  1418. }
  1419. render(force, options) {
  1420. return super.render(force, options);
  1421. }
  1422. isEditable() {
  1423. return Boolean(this.filter.type == FilterType.Client ||
  1424. (this.filter.type == FilterType.World && game.user?.isGM));
  1425. }
  1426. fixAny(type, form, formData) {
  1427. form
  1428. .find(`input[name^="${this.filter.id}_${type}."].disabled`)
  1429. .removeClass("disabled");
  1430. const selectedAny = formData.find((r) => r.name.endsWith(".Any"));
  1431. if (selectedAny) {
  1432. const other = form.find(`input[name^="${this.filter.id}_${type}."]:not(input[name="${this.filter.id}_${selectedAny.name}"])`);
  1433. other.prop("checked", false);
  1434. other.addClass("disabled");
  1435. }
  1436. }
  1437. close() {
  1438. if (this.element.find(".quick-insert").length > 0 && QuickInsert.app) {
  1439. QuickInsert.app.embeddedMode = false;
  1440. QuickInsert.app.closeDialog();
  1441. }
  1442. return super.close();
  1443. }
  1444. processForm() {
  1445. const form = this.element.find("form");
  1446. let formData = form.serializeArray();
  1447. formData.forEach((d) => {
  1448. d.name = this.unPrefix(d.name);
  1449. });
  1450. const name = formData.find((p) => p.name == "name")?.value.trim();
  1451. const title = formData.find((p) => p.name == "title")?.value;
  1452. formData = formData.filter((p) => p.name != "name" && p.name != "title");
  1453. const compendiums = formData.filter((r) => r.name.startsWith("Compendium."));
  1454. const folders = formData.filter((r) => r.name.startsWith("Folder."));
  1455. const entity = formData.filter((r) => r.name.startsWith("Document."));
  1456. this.fixAny("Compendium", form, compendiums);
  1457. this.fixAny("Folder", form, folders);
  1458. this.fixAny("Document", form, entity);
  1459. return {
  1460. name,
  1461. title,
  1462. formData,
  1463. };
  1464. }
  1465. formChange() {
  1466. if (!this.isEditable())
  1467. return;
  1468. const { name, title, formData } = this.processForm();
  1469. const config = parseFilterConfig(formData.map((x) => x.name));
  1470. const oldTag = this.filter.tag;
  1471. if (name != "") {
  1472. this.filter.tag = name;
  1473. }
  1474. this.filter.subTitle = title;
  1475. this.filter.filterConfig = config;
  1476. // Hacky way to keep/update state of input
  1477. this.searchInput =
  1478. QuickInsert.app?.input?.text().replace(`@${oldTag}`, "").trim() || "";
  1479. QuickInsert.filters.filtersChanged(this.filter.type);
  1480. }
  1481. attachQuickInsert() {
  1482. const context = new EmbeddedContext();
  1483. context.filter = this.filter;
  1484. context.startText = this.searchInput;
  1485. if (!QuickInsert.app)
  1486. return;
  1487. if (QuickInsert.app.embeddedMode) {
  1488. this.element.find(".example-out").append(QuickInsert.app.element);
  1489. }
  1490. else {
  1491. Hooks.once(`render${QuickInsert.app?.constructor.name}`, (app) => {
  1492. this.element.find(".example-out").append(app.element);
  1493. });
  1494. }
  1495. QuickInsert.app.embeddedMode = true;
  1496. QuickInsert.app.render(true, { context });
  1497. }
  1498. activateListeners() {
  1499. this.attachQuickInsert();
  1500. const form = this.element.find("form");
  1501. form.on("change", () => {
  1502. this.formChange();
  1503. });
  1504. this.processForm();
  1505. if (this.filter.type == FilterType.Default ||
  1506. (this.filter.type == FilterType.World && !game.user?.isGM)) {
  1507. this.element.find("input").prop("disabled", true);
  1508. }
  1509. this.element.find(".open-here").on("click", (evt) => {
  1510. evt.preventDefault();
  1511. this.attachQuickInsert();
  1512. });
  1513. }
  1514. getData() {
  1515. let folders = [];
  1516. if (!game.packs)
  1517. return {};
  1518. if (game.user?.isGM) {
  1519. folders =
  1520. game.folders?.map((folder) => ({
  1521. label: folder.name,
  1522. name: this.prefix(`Folder.${folder.id}`),
  1523. selected: this.filter.filterConfig?.folders.includes(folder.id),
  1524. })) || [];
  1525. }
  1526. return {
  1527. tag: this.filter.tag,
  1528. subTitle: this.filter.subTitle,
  1529. isDefault: this.filter.type === FilterType.Default,
  1530. forbiddenWorld: this.filter.type == FilterType.World && !game.user?.isGM,
  1531. collections: [
  1532. {
  1533. name: this.prefix("Compendium.Any"),
  1534. label: i18n("FilterEditorCompendiumAny"),
  1535. selected: this.filter.filterConfig?.compendiums === "any",
  1536. },
  1537. ...game.packs
  1538. .filter((pack) => packEnabled(pack))
  1539. .map((pack) => ({
  1540. name: this.prefix(`Compendium.${pack.collection}`),
  1541. label: `${pack.metadata.label} - ${pack.collection}`,
  1542. selected: this.filter.filterConfig?.compendiums.includes(pack.collection),
  1543. })),
  1544. ],
  1545. documentTypes: [
  1546. {
  1547. name: this.prefix("Document.Any"),
  1548. label: i18n("FilterEditorEntityAny"),
  1549. selected: this.filter.filterConfig?.entities === "any",
  1550. },
  1551. ...enabledDocumentTypes().map((type) => ({
  1552. name: this.prefix(`Document.${type}`),
  1553. label: game.i18n.localize(`DOCUMENT.${type}`),
  1554. selected: this.filter.filterConfig?.entities.includes(type),
  1555. })),
  1556. ],
  1557. folders: [
  1558. {
  1559. name: this.prefix("Folder.Any"),
  1560. label: i18n("FilterEditorFolderAny"),
  1561. selected: this.filter.filterConfig?.folders === "any",
  1562. },
  1563. ...folders,
  1564. ],
  1565. };
  1566. }
  1567. }
  1568. const typeIcons = {
  1569. [FilterType.Default]: `<i class="fas fa-lock" title="Default filter"></i>`,
  1570. [FilterType.World]: `<i class="fas fa-globe" title="World filter"></i>`,
  1571. [FilterType.Client]: `<i class="fas fa-user" title="Client filter"></i>`,
  1572. };
  1573. function cloneFilterConfig(original) {
  1574. const res = {
  1575. compendiums: "any",
  1576. folders: "any",
  1577. entities: "any",
  1578. };
  1579. if (typeof original.compendiums !== "string") {
  1580. res.compendiums = [...original.compendiums];
  1581. }
  1582. if (typeof original.folders !== "string") {
  1583. res.folders = [...original.folders];
  1584. }
  1585. if (typeof original.entities !== "string") {
  1586. res.entities = [...original.entities];
  1587. }
  1588. return res;
  1589. }
  1590. class FilterList extends FormApplication {
  1591. constructor() {
  1592. super(...arguments);
  1593. this.filterEditors = {};
  1594. this.onFiltersUpdated = () => {
  1595. this.render(true);
  1596. Object.entries(this.filterEditors).forEach(([id, editor]) => {
  1597. const filter = QuickInsert.filters.getFilter(id);
  1598. if (filter)
  1599. editor.filter = filter;
  1600. editor.rendered && editor.render(true);
  1601. });
  1602. };
  1603. }
  1604. static get defaultOptions() {
  1605. return {
  1606. ...super.defaultOptions,
  1607. title: i18n("FilterListTitle"),
  1608. id: "filter-list",
  1609. template: "modules/quick-insert/templates/filter-list.hbs",
  1610. resizable: true,
  1611. height: 500,
  1612. width: 350,
  1613. scrollY: [".table-container"],
  1614. };
  1615. }
  1616. getData() {
  1617. return {
  1618. filters: [
  1619. ...QuickInsert.filters.filters.map((filter) => ({
  1620. id: filter.id,
  1621. icon: typeIcons[filter.type],
  1622. tag: filter.tag,
  1623. subTitle: filter.subTitle,
  1624. disabled: filter.disabled,
  1625. deletable: filter.type == FilterType.Client ||
  1626. (filter.type == FilterType.World && game.user?.isGM),
  1627. })),
  1628. ],
  1629. };
  1630. }
  1631. render(force, options) {
  1632. if (this._state <= 0) {
  1633. Hooks.on("QuickInsert:FiltersUpdated", this.onFiltersUpdated);
  1634. }
  1635. return super.render(force, options);
  1636. }
  1637. close() {
  1638. Hooks.off("QuickInsert:FiltersUpdated", this.onFiltersUpdated);
  1639. return super.close();
  1640. }
  1641. activateListeners() {
  1642. this.element.find(".create-filter").on("click", () => {
  1643. this.newFilter();
  1644. });
  1645. this.element.find("i.delete").on("click", (evt) => {
  1646. const id = evt.target.closest("tr")?.dataset["id"];
  1647. if (id)
  1648. QuickInsert.filters.deleteFilter(id);
  1649. });
  1650. this.element.find("i.edit").on("click", (evt) => {
  1651. const id = evt.target.closest("tr")?.dataset["id"];
  1652. if (id)
  1653. this.editFilter(id);
  1654. });
  1655. this.element.find("i.duplicate").on("click", (evt) => {
  1656. const id = evt.target.closest("tr")?.dataset["id"];
  1657. this.newFilter(QuickInsert.filters.filters.find((f) => f.id === id));
  1658. });
  1659. this.element.find("i.enable").on("click", (evt) => {
  1660. const id = evt.target.closest("tr")?.dataset["id"];
  1661. const filter = QuickInsert.filters.filters.find((f) => f.id === id);
  1662. if (filter)
  1663. filter.disabled = false;
  1664. QuickInsert.filters.filtersChanged(FilterType.Client);
  1665. });
  1666. this.element.find("i.disable").on("click", (evt) => {
  1667. const id = evt.target.closest("tr")?.dataset["id"];
  1668. const filter = QuickInsert.filters.filters.find((f) => f.id === id);
  1669. if (filter)
  1670. filter.disabled = true;
  1671. QuickInsert.filters.filtersChanged(FilterType.Client);
  1672. });
  1673. }
  1674. editFilter(id) {
  1675. if (!this.filterEditors[id]) {
  1676. const filter = QuickInsert.filters.filters.find((f) => f.id === id);
  1677. if (filter)
  1678. this.filterEditors[id] = new FilterEditor(filter);
  1679. }
  1680. this.filterEditors[id].render(true);
  1681. }
  1682. newFilter(original) {
  1683. const scope = `
  1684. <p>
  1685. <label>${i18n("FilterListFilterScope")}</label>
  1686. <select>
  1687. <option value="world">${i18n("FilterListFilterScopeWorld")}</option>
  1688. <option value="client">${i18n("FilterListFilterScopeClient")}</option>
  1689. </select>
  1690. </p>`;
  1691. const newDialog = new Dialog({
  1692. title: original
  1693. ? i18n("FilterListDuplicateFilterTitle", { original: original.tag })
  1694. : i18n("FilterListNewFilterTitle"),
  1695. content: `
  1696. <div class="new-filter-name">
  1697. @<input type="text" name="name" id="name" value="" placeholder="${i18n("FilterListFilterTagPlaceholder")}" pattern="[A-Za-z0-9\\._-]+" minlength="1">
  1698. </div>
  1699. ${game.user?.isGM ? scope : ""}
  1700. `,
  1701. buttons: {
  1702. apply: {
  1703. icon: "<i class='fas fa-plus'></i>",
  1704. label: i18n("FilterListCreateFilter"),
  1705. callback: async (html) => {
  1706. if (!("find" in html))
  1707. return;
  1708. const input = html.find("input");
  1709. const val = html.find("input").val();
  1710. const selected = html.find("select").val();
  1711. if (input.get(0)?.checkValidity() && val !== "") {
  1712. this.createFilter(val, selected === "world" ? FilterType.World : FilterType.Client, original);
  1713. }
  1714. else {
  1715. ui.notifications?.error(`Incorrect filter tag: "${val}"`);
  1716. }
  1717. },
  1718. },
  1719. },
  1720. default: "apply",
  1721. close: () => {
  1722. return;
  1723. },
  1724. });
  1725. newDialog.render(true);
  1726. }
  1727. createFilter(tag, scope, original) {
  1728. const newId = randomId(30);
  1729. if (original) {
  1730. QuickInsert.filters.addFilter({
  1731. id: newId,
  1732. type: scope,
  1733. tag,
  1734. subTitle: `${original.subTitle} (Copy)`,
  1735. filterConfig: original.filterConfig && cloneFilterConfig(original.filterConfig),
  1736. });
  1737. return;
  1738. }
  1739. else {
  1740. QuickInsert.filters.addFilter({
  1741. id: newId,
  1742. type: scope,
  1743. tag,
  1744. subTitle: tag,
  1745. filterConfig: {
  1746. compendiums: [],
  1747. folders: [],
  1748. entities: "any",
  1749. },
  1750. });
  1751. }
  1752. if (scope == FilterType.Client) {
  1753. this.editFilter(newId);
  1754. }
  1755. else {
  1756. Hooks.once("QuickInsert:FiltersUpdated", () => this.editFilter(newId));
  1757. }
  1758. }
  1759. async _updateObject() {
  1760. return;
  1761. }
  1762. }
  1763. class SheetFilters extends FormApplication {
  1764. get element() {
  1765. return super.element;
  1766. }
  1767. static get defaultOptions() {
  1768. return {
  1769. ...super.defaultOptions,
  1770. title: i18n("SheetFiltersTitle"),
  1771. id: "sheet-filters",
  1772. template: "modules/quick-insert/templates/sheet-filters.hbs",
  1773. resizable: true,
  1774. };
  1775. }
  1776. getData() {
  1777. const filters = QuickInsert.filters.filters;
  1778. const customFilters = getSetting(ModuleSetting.FILTERS_SHEETS).baseFilters;
  1779. return {
  1780. filters: Object.entries(customFilters).map(([key, filter]) => ({
  1781. key,
  1782. noFilter: filter === "",
  1783. options: filters.map((f) => ({
  1784. ...f,
  1785. selected: filter === f.tag || filter === f.id,
  1786. })),
  1787. })),
  1788. };
  1789. }
  1790. activateListeners(html) {
  1791. super.activateListeners(html);
  1792. }
  1793. async _updateObject(event, formData) {
  1794. setSetting(ModuleSetting.FILTERS_SHEETS, {
  1795. baseFilters: formData,
  1796. });
  1797. }
  1798. }
  1799. async function importSystemIntegration() {
  1800. let system = null;
  1801. switch (game.system.id) {
  1802. case "dnd5e":
  1803. system = await import('./dnd5e.js');
  1804. break;
  1805. case "pf2e":
  1806. system = await import('./pf2e.js');
  1807. break;
  1808. case "swade":
  1809. system = await import('./swade.js');
  1810. break;
  1811. case "wfrp4e":
  1812. system = await import('./wfrp4e.js');
  1813. break;
  1814. case "sfrpg":
  1815. system = await import('./sfrpg.js');
  1816. break;
  1817. case "demonlord":
  1818. system = await import('./demonlord.js');
  1819. break;
  1820. default:
  1821. return;
  1822. }
  1823. return {
  1824. id: game.system.id,
  1825. ...system,
  1826. };
  1827. }
  1828. function registerTinyMCEPlugin() {
  1829. // TinyMCE addon registration
  1830. tinymce.PluginManager.add("quickinsert", function (editor) {
  1831. editor.on("keydown", (evt) => {
  1832. const context = new TinyMCEContext(editor);
  1833. customKeybindHandler(evt, context);
  1834. });
  1835. editor.ui.registry.addButton("quickinsert", {
  1836. tooltip: "Quick Insert",
  1837. icon: "search",
  1838. onAction: function () {
  1839. if (QuickInsert.app?.embeddedMode)
  1840. return;
  1841. // Open window
  1842. QuickInsert.open(new TinyMCEContext(editor));
  1843. },
  1844. });
  1845. });
  1846. CONFIG.TinyMCE.plugins = CONFIG.TinyMCE.plugins + " quickinsert";
  1847. CONFIG.TinyMCE.toolbar = CONFIG.TinyMCE.toolbar + " quickinsert";
  1848. }
  1849. const DOCUMENTACTIONS = {
  1850. show: (item) => item.show(),
  1851. roll: (item) => item.get().then((d) => d.draw()),
  1852. viewScene: (item) => item.get().then((d) => d.view()),
  1853. activateScene: (item) => item.get().then((d) => {
  1854. game.user?.isGM && d.activate();
  1855. }),
  1856. execute: (item) => item.get().then((d) => d.execute()),
  1857. insert: (item) => item,
  1858. rollInsert: (item) => item.get().then(async (d) => {
  1859. const roll = await d.roll();
  1860. for (const data of roll.results) {
  1861. if (!data.documentId) {
  1862. return data.text;
  1863. }
  1864. if (data.documentCollection.includes(".")) {
  1865. const pack = game.packs.get(data.documentCollection);
  1866. if (!pack)
  1867. return data.text;
  1868. const indexItem = game.packs
  1869. .get(data.documentCollection)
  1870. ?.index.find((i) => i._id === data.documentId);
  1871. return indexItem
  1872. ? new CompendiumSearchItem(pack, indexItem)
  1873. : data.text;
  1874. }
  1875. else {
  1876. const entity = getCollectionFromType(data.documentCollection).get(data.documentId);
  1877. return entity ? new EntitySearchItem(entity) : data.text;
  1878. }
  1879. }
  1880. }),
  1881. };
  1882. const BrowseDocumentActions = (() => {
  1883. const actions = {
  1884. [DocumentType.SCENE]: [
  1885. {
  1886. id: "activateScene",
  1887. icon: "fas fa-bullseye",
  1888. title: "Activate",
  1889. },
  1890. {
  1891. id: "viewScene",
  1892. icon: "fas fa-eye",
  1893. title: "View",
  1894. },
  1895. {
  1896. id: "show",
  1897. icon: "fas fa-cogs",
  1898. title: "Configure",
  1899. },
  1900. ],
  1901. [DocumentType.ROLLTABLE]: [
  1902. {
  1903. id: "roll",
  1904. icon: "fas fa-dice-d20",
  1905. title: "Roll",
  1906. },
  1907. {
  1908. id: "show",
  1909. icon: `fas ${documentIcons[DocumentType.ROLLTABLE]}`,
  1910. title: "Edit",
  1911. },
  1912. ],
  1913. [DocumentType.MACRO]: [
  1914. {
  1915. id: "execute",
  1916. icon: "fas fa-play",
  1917. title: "Execute",
  1918. },
  1919. {
  1920. id: "show",
  1921. icon: `fas ${documentIcons[DocumentType.ROLLTABLE]}`,
  1922. title: "Edit",
  1923. },
  1924. ],
  1925. };
  1926. IndexedDocumentTypes.forEach((type) => {
  1927. if (type in actions)
  1928. return;
  1929. actions[type] = [
  1930. {
  1931. id: "show",
  1932. icon: `fas ${documentIcons[type]}`,
  1933. title: "Show",
  1934. },
  1935. ];
  1936. });
  1937. return actions;
  1938. })();
  1939. // Same for all inserts
  1940. const insertAction = {
  1941. id: "insert",
  1942. icon: `fas fa-plus`,
  1943. title: "Insert",
  1944. };
  1945. const InsertDocumentActions = (() => {
  1946. const actions = {
  1947. [DocumentType.SCENE]: [
  1948. {
  1949. id: "show",
  1950. icon: "fas fa-cogs",
  1951. title: "Configure",
  1952. },
  1953. ],
  1954. [DocumentType.ROLLTABLE]: [
  1955. {
  1956. id: "rollInsert",
  1957. icon: "fas fa-play",
  1958. title: "Roll and Insert",
  1959. },
  1960. {
  1961. id: "show",
  1962. icon: `fas ${documentIcons[DocumentType.ROLLTABLE]}`,
  1963. title: "Show",
  1964. },
  1965. ],
  1966. };
  1967. // Add others
  1968. IndexedDocumentTypes.forEach((type) => {
  1969. if (!actions[type]) {
  1970. // If nothing else, add "Show"
  1971. actions[type] = [
  1972. {
  1973. id: "show",
  1974. icon: `fas ${documentIcons[type]}`,
  1975. title: "Show",
  1976. },
  1977. ];
  1978. }
  1979. actions[type].push(insertAction);
  1980. });
  1981. return actions;
  1982. })();
  1983. function getActions(type, isInsertContext) {
  1984. return isInsertContext
  1985. ? InsertDocumentActions[type]
  1986. : BrowseDocumentActions[type];
  1987. }
  1988. function defaultAction(type, isInsertContext) {
  1989. if (!isInsertContext) {
  1990. switch (type) {
  1991. case DocumentType.SCENE:
  1992. return getSetting(ModuleSetting.DEFAULT_ACTION_SCENE);
  1993. case DocumentType.ROLLTABLE:
  1994. return getSetting(ModuleSetting.DEFAULT_ACTION_ROLL_TABLE);
  1995. case DocumentType.MACRO:
  1996. return getSetting(ModuleSetting.DEFAULT_ACTION_MACRO);
  1997. }
  1998. }
  1999. const actions = getActions(type, isInsertContext);
  2000. return actions[actions.length - 1].id;
  2001. }
  2002. /* src/app/SearchResults.svelte generated by Svelte v3.49.0 */
  2003. function get_each_context$1(ctx, list, i) {
  2004. const child_ctx = ctx.slice();
  2005. child_ctx[13] = list[i].item;
  2006. child_ctx[14] = list[i].match;
  2007. child_ctx[15] = list[i].actions;
  2008. child_ctx[16] = list[i].defaultAction;
  2009. child_ctx[18] = i;
  2010. return child_ctx;
  2011. }
  2012. function get_each_context_1(ctx, list, i) {
  2013. const child_ctx = ctx.slice();
  2014. child_ctx[19] = list[i];
  2015. return child_ctx;
  2016. }
  2017. // (52:0) {#if active}
  2018. function create_if_block$1(ctx) {
  2019. let ul;
  2020. let each_blocks = [];
  2021. let each_1_lookup = new Map();
  2022. let each_value = /*results*/ ctx[2];
  2023. const get_key = ctx => /*item*/ ctx[13].uuid;
  2024. for (let i = 0; i < each_value.length; i += 1) {
  2025. let child_ctx = get_each_context$1(ctx, each_value, i);
  2026. let key = get_key(child_ctx);
  2027. each_1_lookup.set(key, each_blocks[i] = create_each_block$1(key, child_ctx));
  2028. }
  2029. return {
  2030. c() {
  2031. ul = element("ul");
  2032. for (let i = 0; i < each_blocks.length; i += 1) {
  2033. each_blocks[i].c();
  2034. }
  2035. attr(ul, "class", "quick-insert-result");
  2036. attr(ul, "data-tooltip-direction", /*tooltips*/ ctx[0]);
  2037. },
  2038. m(target, anchor) {
  2039. insert(target, ul, anchor);
  2040. for (let i = 0; i < each_blocks.length; i += 1) {
  2041. each_blocks[i].m(ul, null);
  2042. }
  2043. /*ul_binding*/ ctx[11](ul);
  2044. },
  2045. p(ctx, dirty) {
  2046. if (dirty & /*getTooltip, results, tooltips, selectedIndex, JSON, callAction, selectedAction, formatMatch*/ 221) {
  2047. each_value = /*results*/ ctx[2];
  2048. each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, ul, destroy_block, create_each_block$1, null, get_each_context$1);
  2049. }
  2050. if (dirty & /*tooltips*/ 1) {
  2051. attr(ul, "data-tooltip-direction", /*tooltips*/ ctx[0]);
  2052. }
  2053. },
  2054. d(detaching) {
  2055. if (detaching) detach(ul);
  2056. for (let i = 0; i < each_blocks.length; i += 1) {
  2057. each_blocks[i].d();
  2058. }
  2059. /*ul_binding*/ ctx[11](null);
  2060. }
  2061. };
  2062. }
  2063. // (77:10) {:else}
  2064. function create_else_block(ctx) {
  2065. let html_tag;
  2066. let raw_value = /*item*/ ctx[13].icon + "";
  2067. let html_anchor;
  2068. return {
  2069. c() {
  2070. html_tag = new HtmlTag(false);
  2071. html_anchor = empty();
  2072. html_tag.a = html_anchor;
  2073. },
  2074. m(target, anchor) {
  2075. html_tag.m(raw_value, target, anchor);
  2076. insert(target, html_anchor, anchor);
  2077. },
  2078. p(ctx, dirty) {
  2079. if (dirty & /*results*/ 4 && raw_value !== (raw_value = /*item*/ ctx[13].icon + "")) html_tag.p(raw_value);
  2080. },
  2081. d(detaching) {
  2082. if (detaching) detach(html_anchor);
  2083. if (detaching) html_tag.d();
  2084. }
  2085. };
  2086. }
  2087. // (75:10) {#if item.img}
  2088. function create_if_block_1(ctx) {
  2089. let img;
  2090. let img_src_value;
  2091. return {
  2092. c() {
  2093. img = element("img");
  2094. if (!src_url_equal(img.src, img_src_value = /*item*/ ctx[13].img)) attr(img, "src", img_src_value);
  2095. },
  2096. m(target, anchor) {
  2097. insert(target, img, anchor);
  2098. },
  2099. p(ctx, dirty) {
  2100. if (dirty & /*results*/ 4 && !src_url_equal(img.src, img_src_value = /*item*/ ctx[13].img)) {
  2101. attr(img, "src", img_src_value);
  2102. }
  2103. },
  2104. d(detaching) {
  2105. if (detaching) detach(img);
  2106. }
  2107. };
  2108. }
  2109. // (88:12) {#each actions as action}
  2110. function create_each_block_1(ctx) {
  2111. let i;
  2112. let i_class_value;
  2113. let i_title_value;
  2114. let i_data_action_id_value;
  2115. let mounted;
  2116. let dispose;
  2117. function click_handler(...args) {
  2118. return /*click_handler*/ ctx[8](/*action*/ ctx[19], /*item*/ ctx[13], ...args);
  2119. }
  2120. return {
  2121. c() {
  2122. i = element("i");
  2123. attr(i, "class", i_class_value = "" + (/*action*/ ctx[19].icon + " action-icon"));
  2124. attr(i, "title", i_title_value = "" + (/*action*/ ctx[19].title + " '" + /*item*/ ctx[13].name + "'"));
  2125. attr(i, "data-action-id", i_data_action_id_value = /*action*/ ctx[19].id);
  2126. toggle_class(i, "selected", /*i*/ ctx[18] === /*selectedIndex*/ ctx[3] && (/*selectedAction*/ ctx[4]
  2127. ? /*action*/ ctx[19].id === /*selectedAction*/ ctx[4]
  2128. : /*action*/ ctx[19].id == /*defaultAction*/ ctx[16]));
  2129. },
  2130. m(target, anchor) {
  2131. insert(target, i, anchor);
  2132. if (!mounted) {
  2133. dispose = listen(i, "click", stop_propagation(click_handler));
  2134. mounted = true;
  2135. }
  2136. },
  2137. p(new_ctx, dirty) {
  2138. ctx = new_ctx;
  2139. if (dirty & /*results*/ 4 && i_class_value !== (i_class_value = "" + (/*action*/ ctx[19].icon + " action-icon"))) {
  2140. attr(i, "class", i_class_value);
  2141. }
  2142. if (dirty & /*results*/ 4 && i_title_value !== (i_title_value = "" + (/*action*/ ctx[19].title + " '" + /*item*/ ctx[13].name + "'"))) {
  2143. attr(i, "title", i_title_value);
  2144. }
  2145. if (dirty & /*results*/ 4 && i_data_action_id_value !== (i_data_action_id_value = /*action*/ ctx[19].id)) {
  2146. attr(i, "data-action-id", i_data_action_id_value);
  2147. }
  2148. if (dirty & /*results, results, selectedIndex, selectedAction*/ 28) {
  2149. toggle_class(i, "selected", /*i*/ ctx[18] === /*selectedIndex*/ ctx[3] && (/*selectedAction*/ ctx[4]
  2150. ? /*action*/ ctx[19].id === /*selectedAction*/ ctx[4]
  2151. : /*action*/ ctx[19].id == /*defaultAction*/ ctx[16]));
  2152. }
  2153. },
  2154. d(detaching) {
  2155. if (detaching) detach(i);
  2156. mounted = false;
  2157. dispose();
  2158. }
  2159. };
  2160. }
  2161. // (58:4) {#each results as { item, match, actions, defaultAction }
  2162. function create_each_block$1(key_1, ctx) {
  2163. let li;
  2164. let a;
  2165. let t0;
  2166. let span0;
  2167. let raw_value = formatMatch(
  2168. {
  2169. item: /*item*/ ctx[13],
  2170. match: /*match*/ ctx[14]
  2171. },
  2172. func
  2173. ) + "";
  2174. let t1;
  2175. let span1;
  2176. let t2_value = /*item*/ ctx[13].tagline + "";
  2177. let t2;
  2178. let t3;
  2179. let span2;
  2180. let a_title_value;
  2181. let t4;
  2182. let li_data_tooltip_value;
  2183. let mounted;
  2184. let dispose;
  2185. function select_block_type(ctx, dirty) {
  2186. if (/*item*/ ctx[13].img) return create_if_block_1;
  2187. return create_else_block;
  2188. }
  2189. let current_block_type = select_block_type(ctx);
  2190. let if_block = current_block_type(ctx);
  2191. let each_value_1 = /*actions*/ ctx[15];
  2192. let each_blocks = [];
  2193. for (let i = 0; i < each_value_1.length; i += 1) {
  2194. each_blocks[i] = create_each_block_1(get_each_context_1(ctx, each_value_1, i));
  2195. }
  2196. function dragstart_handler(...args) {
  2197. return /*dragstart_handler*/ ctx[9](/*item*/ ctx[13], ...args);
  2198. }
  2199. function click_handler_1(...args) {
  2200. return /*click_handler_1*/ ctx[10](/*defaultAction*/ ctx[16], /*item*/ ctx[13], ...args);
  2201. }
  2202. return {
  2203. key: key_1,
  2204. first: null,
  2205. c() {
  2206. li = element("li");
  2207. a = element("a");
  2208. if_block.c();
  2209. t0 = space();
  2210. span0 = element("span");
  2211. t1 = space();
  2212. span1 = element("span");
  2213. t2 = text(t2_value);
  2214. t3 = space();
  2215. span2 = element("span");
  2216. for (let i = 0; i < each_blocks.length; i += 1) {
  2217. each_blocks[i].c();
  2218. }
  2219. t4 = space();
  2220. attr(span0, "class", "title");
  2221. attr(span1, "class", "sub");
  2222. attr(span2, "class", "action-icons");
  2223. attr(a, "draggable", "true");
  2224. attr(a, "title", a_title_value = "" + (/*item*/ ctx[13].name + ", " + /*item*/ ctx[13].tagline));
  2225. attr(li, "data-tooltip", li_data_tooltip_value = /*getTooltip*/ ctx[7](/*item*/ ctx[13], /*tooltips*/ ctx[0]));
  2226. toggle_class(li, "search-selected", /*i*/ ctx[18] === /*selectedIndex*/ ctx[3]);
  2227. this.first = li;
  2228. },
  2229. m(target, anchor) {
  2230. insert(target, li, anchor);
  2231. append(li, a);
  2232. if_block.m(a, null);
  2233. append(a, t0);
  2234. append(a, span0);
  2235. span0.innerHTML = raw_value;
  2236. append(a, t1);
  2237. append(a, span1);
  2238. append(span1, t2);
  2239. append(a, t3);
  2240. append(a, span2);
  2241. for (let i = 0; i < each_blocks.length; i += 1) {
  2242. each_blocks[i].m(span2, null);
  2243. }
  2244. append(li, t4);
  2245. if (!mounted) {
  2246. dispose = [
  2247. listen(a, "dragstart", dragstart_handler),
  2248. listen(a, "click", stop_propagation(click_handler_1))
  2249. ];
  2250. mounted = true;
  2251. }
  2252. },
  2253. p(new_ctx, dirty) {
  2254. ctx = new_ctx;
  2255. if (current_block_type === (current_block_type = select_block_type(ctx)) && if_block) {
  2256. if_block.p(ctx, dirty);
  2257. } else {
  2258. if_block.d(1);
  2259. if_block = current_block_type(ctx);
  2260. if (if_block) {
  2261. if_block.c();
  2262. if_block.m(a, t0);
  2263. }
  2264. }
  2265. if (dirty & /*results*/ 4 && raw_value !== (raw_value = formatMatch(
  2266. {
  2267. item: /*item*/ ctx[13],
  2268. match: /*match*/ ctx[14]
  2269. },
  2270. func
  2271. ) + "")) span0.innerHTML = raw_value;
  2272. if (dirty & /*results*/ 4 && t2_value !== (t2_value = /*item*/ ctx[13].tagline + "")) set_data(t2, t2_value);
  2273. if (dirty & /*results, selectedIndex, selectedAction, callAction*/ 92) {
  2274. each_value_1 = /*actions*/ ctx[15];
  2275. let i;
  2276. for (i = 0; i < each_value_1.length; i += 1) {
  2277. const child_ctx = get_each_context_1(ctx, each_value_1, i);
  2278. if (each_blocks[i]) {
  2279. each_blocks[i].p(child_ctx, dirty);
  2280. } else {
  2281. each_blocks[i] = create_each_block_1(child_ctx);
  2282. each_blocks[i].c();
  2283. each_blocks[i].m(span2, null);
  2284. }
  2285. }
  2286. for (; i < each_blocks.length; i += 1) {
  2287. each_blocks[i].d(1);
  2288. }
  2289. each_blocks.length = each_value_1.length;
  2290. }
  2291. if (dirty & /*results*/ 4 && a_title_value !== (a_title_value = "" + (/*item*/ ctx[13].name + ", " + /*item*/ ctx[13].tagline))) {
  2292. attr(a, "title", a_title_value);
  2293. }
  2294. if (dirty & /*results, tooltips*/ 5 && li_data_tooltip_value !== (li_data_tooltip_value = /*getTooltip*/ ctx[7](/*item*/ ctx[13], /*tooltips*/ ctx[0]))) {
  2295. attr(li, "data-tooltip", li_data_tooltip_value);
  2296. }
  2297. if (dirty & /*results, selectedIndex*/ 12) {
  2298. toggle_class(li, "search-selected", /*i*/ ctx[18] === /*selectedIndex*/ ctx[3]);
  2299. }
  2300. },
  2301. d(detaching) {
  2302. if (detaching) detach(li);
  2303. if_block.d();
  2304. destroy_each(each_blocks, detaching);
  2305. mounted = false;
  2306. run_all(dispose);
  2307. }
  2308. };
  2309. }
  2310. function create_fragment$1(ctx) {
  2311. let if_block_anchor;
  2312. let if_block = /*active*/ ctx[1] && create_if_block$1(ctx);
  2313. return {
  2314. c() {
  2315. if (if_block) if_block.c();
  2316. if_block_anchor = empty();
  2317. },
  2318. m(target, anchor) {
  2319. if (if_block) if_block.m(target, anchor);
  2320. insert(target, if_block_anchor, anchor);
  2321. },
  2322. p(ctx, [dirty]) {
  2323. if (/*active*/ ctx[1]) {
  2324. if (if_block) {
  2325. if_block.p(ctx, dirty);
  2326. } else {
  2327. if_block = create_if_block$1(ctx);
  2328. if_block.c();
  2329. if_block.m(if_block_anchor.parentNode, if_block_anchor);
  2330. }
  2331. } else if (if_block) {
  2332. if_block.d(1);
  2333. if_block = null;
  2334. }
  2335. },
  2336. i: noop,
  2337. o: noop,
  2338. d(detaching) {
  2339. if (if_block) if_block.d(detaching);
  2340. if (detaching) detach(if_block_anchor);
  2341. }
  2342. };
  2343. }
  2344. const func = str => `<strong>${str}</strong>`;
  2345. function instance$1($$self, $$props, $$invalidate) {
  2346. const dispatch = createEventDispatcher();
  2347. let { tooltips = "LEFT" } = $$props;
  2348. let { active = false } = $$props;
  2349. let { results = [] } = $$props;
  2350. let { selectedIndex = 0 } = $$props;
  2351. let { selectedAction = "show" } = $$props;
  2352. let resultList;
  2353. afterUpdate(() => {
  2354. const tooltipMode = getSetting(ModuleSetting.SEARCH_TOOLTIPS);
  2355. if (resultList?.children[selectedIndex]) {
  2356. const selected = resultList.children[selectedIndex];
  2357. selected.scrollIntoView({ block: "nearest" });
  2358. if (tooltipMode !== "off" && selected.dataset?.tooltip) {
  2359. //@ts-expect-error update types...
  2360. game.tooltip.activate(selected);
  2361. } else {
  2362. //@ts-expect-error update types...
  2363. game.tooltip.deactivate();
  2364. }
  2365. } else {
  2366. if (tooltipMode !== "off") {
  2367. //@ts-expect-error update types...
  2368. game.tooltip.deactivate();
  2369. }
  2370. }
  2371. });
  2372. function callAction(actionId, item, shiftKey) {
  2373. dispatch("callAction", { actionId, item, shiftKey });
  2374. }
  2375. function getTooltip(item, side) {
  2376. const tooltipMode = getSetting(ModuleSetting.SEARCH_TOOLTIPS);
  2377. if (tooltipMode === "off") return "";
  2378. const showImage = tooltipMode === "full" || tooltipMode === "image";
  2379. const img = showImage && item.img
  2380. ? `<img src=${item.img} style="max-width: 120px; float:${side === "LEFT" ? "right" : "left"};"/>`
  2381. : "";
  2382. const text = tooltipMode !== "image"
  2383. ? `<p style='margin:0;text-align:left'>${item.icon} ${item.name}</p>
  2384. <p style='font-size: 90%; opacity:0.8;margin:0;'>${item.tooltip}</p>`
  2385. : "";
  2386. return text + img;
  2387. }
  2388. const click_handler = (action, item, e) => callAction(action.id, item, e.shiftKey);
  2389. const dragstart_handler = (item, event) => event.dataTransfer?.setData("text/plain", JSON.stringify(item.dragData));
  2390. const click_handler_1 = (defaultAction, item, e) => callAction(defaultAction, item, e.shiftKey);
  2391. function ul_binding($$value) {
  2392. binding_callbacks[$$value ? 'unshift' : 'push'](() => {
  2393. resultList = $$value;
  2394. $$invalidate(5, resultList);
  2395. });
  2396. }
  2397. $$self.$$set = $$props => {
  2398. if ('tooltips' in $$props) $$invalidate(0, tooltips = $$props.tooltips);
  2399. if ('active' in $$props) $$invalidate(1, active = $$props.active);
  2400. if ('results' in $$props) $$invalidate(2, results = $$props.results);
  2401. if ('selectedIndex' in $$props) $$invalidate(3, selectedIndex = $$props.selectedIndex);
  2402. if ('selectedAction' in $$props) $$invalidate(4, selectedAction = $$props.selectedAction);
  2403. };
  2404. return [
  2405. tooltips,
  2406. active,
  2407. results,
  2408. selectedIndex,
  2409. selectedAction,
  2410. resultList,
  2411. callAction,
  2412. getTooltip,
  2413. click_handler,
  2414. dragstart_handler,
  2415. click_handler_1,
  2416. ul_binding
  2417. ];
  2418. }
  2419. class SearchResults extends SvelteComponent {
  2420. constructor(options) {
  2421. super();
  2422. init(this, options, instance$1, create_fragment$1, safe_not_equal, {
  2423. tooltips: 0,
  2424. active: 1,
  2425. results: 2,
  2426. selectedIndex: 3,
  2427. selectedAction: 4
  2428. });
  2429. }
  2430. }
  2431. /* src/app/SearchFiltersResults.svelte generated by Svelte v3.49.0 */
  2432. function get_each_context(ctx, list, i) {
  2433. const child_ctx = ctx.slice();
  2434. child_ctx[8] = list[i];
  2435. child_ctx[10] = i;
  2436. return child_ctx;
  2437. }
  2438. // (15:0) {#if active}
  2439. function create_if_block(ctx) {
  2440. let ul;
  2441. let each_blocks = [];
  2442. let each_1_lookup = new Map();
  2443. let each_value = /*results*/ ctx[1];
  2444. const get_key = ctx => /*item*/ ctx[8].id;
  2445. for (let i = 0; i < each_value.length; i += 1) {
  2446. let child_ctx = get_each_context(ctx, each_value, i);
  2447. let key = get_key(child_ctx);
  2448. each_1_lookup.set(key, each_blocks[i] = create_each_block(key, child_ctx));
  2449. }
  2450. return {
  2451. c() {
  2452. ul = element("ul");
  2453. for (let i = 0; i < each_blocks.length; i += 1) {
  2454. each_blocks[i].c();
  2455. }
  2456. attr(ul, "class", "quick-insert-result");
  2457. },
  2458. m(target, anchor) {
  2459. insert(target, ul, anchor);
  2460. for (let i = 0; i < each_blocks.length; i += 1) {
  2461. each_blocks[i].m(ul, null);
  2462. }
  2463. /*ul_binding*/ ctx[6](ul);
  2464. },
  2465. p(ctx, dirty) {
  2466. if (dirty & /*results, selectedIndex, selected*/ 22) {
  2467. each_value = /*results*/ ctx[1];
  2468. each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, ul, destroy_block, create_each_block, null, get_each_context);
  2469. }
  2470. },
  2471. d(detaching) {
  2472. if (detaching) detach(ul);
  2473. for (let i = 0; i < each_blocks.length; i += 1) {
  2474. each_blocks[i].d();
  2475. }
  2476. /*ul_binding*/ ctx[6](null);
  2477. }
  2478. };
  2479. }
  2480. // (17:4) {#each results as item, i (item.id)}
  2481. function create_each_block(key_1, ctx) {
  2482. let li;
  2483. let a;
  2484. let span0;
  2485. let t0;
  2486. let t1_value = /*item*/ ctx[8].tag + "";
  2487. let t1;
  2488. let t2;
  2489. let span1;
  2490. let t3_value = /*item*/ ctx[8].subTitle + "";
  2491. let t3;
  2492. let t4;
  2493. let mounted;
  2494. let dispose;
  2495. function click_handler() {
  2496. return /*click_handler*/ ctx[5](/*i*/ ctx[10]);
  2497. }
  2498. return {
  2499. key: key_1,
  2500. first: null,
  2501. c() {
  2502. li = element("li");
  2503. a = element("a");
  2504. span0 = element("span");
  2505. t0 = text("@");
  2506. t1 = text(t1_value);
  2507. t2 = space();
  2508. span1 = element("span");
  2509. t3 = text(t3_value);
  2510. t4 = space();
  2511. attr(span0, "class", "title");
  2512. attr(span1, "class", "sub");
  2513. toggle_class(li, "search-selected", /*i*/ ctx[10] === /*selectedIndex*/ ctx[2]);
  2514. this.first = li;
  2515. },
  2516. m(target, anchor) {
  2517. insert(target, li, anchor);
  2518. append(li, a);
  2519. append(a, span0);
  2520. append(span0, t0);
  2521. append(span0, t1);
  2522. append(a, t2);
  2523. append(a, span1);
  2524. append(span1, t3);
  2525. append(li, t4);
  2526. if (!mounted) {
  2527. dispose = listen(a, "click", click_handler);
  2528. mounted = true;
  2529. }
  2530. },
  2531. p(new_ctx, dirty) {
  2532. ctx = new_ctx;
  2533. if (dirty & /*results*/ 2 && t1_value !== (t1_value = /*item*/ ctx[8].tag + "")) set_data(t1, t1_value);
  2534. if (dirty & /*results*/ 2 && t3_value !== (t3_value = /*item*/ ctx[8].subTitle + "")) set_data(t3, t3_value);
  2535. if (dirty & /*results, selectedIndex*/ 6) {
  2536. toggle_class(li, "search-selected", /*i*/ ctx[10] === /*selectedIndex*/ ctx[2]);
  2537. }
  2538. },
  2539. d(detaching) {
  2540. if (detaching) detach(li);
  2541. mounted = false;
  2542. dispose();
  2543. }
  2544. };
  2545. }
  2546. function create_fragment(ctx) {
  2547. let if_block_anchor;
  2548. let if_block = /*active*/ ctx[0] && create_if_block(ctx);
  2549. return {
  2550. c() {
  2551. if (if_block) if_block.c();
  2552. if_block_anchor = empty();
  2553. },
  2554. m(target, anchor) {
  2555. if (if_block) if_block.m(target, anchor);
  2556. insert(target, if_block_anchor, anchor);
  2557. },
  2558. p(ctx, [dirty]) {
  2559. if (/*active*/ ctx[0]) {
  2560. if (if_block) {
  2561. if_block.p(ctx, dirty);
  2562. } else {
  2563. if_block = create_if_block(ctx);
  2564. if_block.c();
  2565. if_block.m(if_block_anchor.parentNode, if_block_anchor);
  2566. }
  2567. } else if (if_block) {
  2568. if_block.d(1);
  2569. if_block = null;
  2570. }
  2571. },
  2572. i: noop,
  2573. o: noop,
  2574. d(detaching) {
  2575. if (if_block) if_block.d(detaching);
  2576. if (detaching) detach(if_block_anchor);
  2577. }
  2578. };
  2579. }
  2580. function instance($$self, $$props, $$invalidate) {
  2581. const dispatch = createEventDispatcher();
  2582. let { active = false } = $$props;
  2583. let { results = [] } = $$props;
  2584. let { selectedIndex = 0 } = $$props;
  2585. let resultList;
  2586. afterUpdate(() => {
  2587. resultList?.children[selectedIndex]?.scrollIntoView({ block: "nearest" });
  2588. });
  2589. function selected(index) {
  2590. dispatch("selected", { index });
  2591. }
  2592. const click_handler = i => selected(i);
  2593. function ul_binding($$value) {
  2594. binding_callbacks[$$value ? 'unshift' : 'push'](() => {
  2595. resultList = $$value;
  2596. $$invalidate(3, resultList);
  2597. });
  2598. }
  2599. $$self.$$set = $$props => {
  2600. if ('active' in $$props) $$invalidate(0, active = $$props.active);
  2601. if ('results' in $$props) $$invalidate(1, results = $$props.results);
  2602. if ('selectedIndex' in $$props) $$invalidate(2, selectedIndex = $$props.selectedIndex);
  2603. };
  2604. return [
  2605. active,
  2606. results,
  2607. selectedIndex,
  2608. resultList,
  2609. selected,
  2610. click_handler,
  2611. ul_binding
  2612. ];
  2613. }
  2614. class SearchFiltersResults extends SvelteComponent {
  2615. constructor(options) {
  2616. super();
  2617. init(this, options, instance, create_fragment, safe_not_equal, { active: 0, results: 1, selectedIndex: 2 });
  2618. }
  2619. }
  2620. // A search controller controls a specific search output.
  2621. // This lets us implement multiple different searches with the same search app,
  2622. // e.g. entities and filters, or maybe in the future; commands, open windows, etc.
  2623. class SearchController {
  2624. constructor(app) {
  2625. this.results = [];
  2626. this.selectedIndex = -1;
  2627. this.selectedAction = null;
  2628. this.app = app;
  2629. }
  2630. get isInsertMode() {
  2631. return (this.app.attachedContext?.mode == undefined ||
  2632. this.app.attachedContext.mode == ContextMode.Insert);
  2633. }
  2634. activate() {
  2635. const left = this.app.attachedContext?.spawnCSS?.left;
  2636. const tooltipSide = left !== undefined && left < 300 ? "RIGHT" : "LEFT";
  2637. this.view?.$$set?.({ active: true, tooltips: tooltipSide });
  2638. }
  2639. deactivate() {
  2640. this.view?.$$set?.({ active: false });
  2641. }
  2642. selectNext() {
  2643. this.selectedIndex = (this.selectedIndex + 1) % this.results.length;
  2644. this.view?.$$set?.({
  2645. selectedIndex: this.selectedIndex,
  2646. selectedAction: (this.selectedAction = null),
  2647. });
  2648. }
  2649. selectPrevious() {
  2650. this.selectedIndex =
  2651. this.selectedIndex > 0 ? this.selectedIndex - 1 : this.results.length - 1;
  2652. this.view?.$$set?.({
  2653. selectedIndex: this.selectedIndex,
  2654. selectedAction: (this.selectedAction = null),
  2655. });
  2656. }
  2657. }
  2658. class DocumentController extends SearchController {
  2659. constructor() {
  2660. super(...arguments);
  2661. this.results = [];
  2662. this.selectedAction = null; // null means use defaultAction
  2663. this.search = (textInput) => {
  2664. if (!QuickInsert.searchLib)
  2665. return;
  2666. textInput = textInput.trim();
  2667. if (textInput.length == 0) {
  2668. this.view?.$$set?.({
  2669. results: [],
  2670. selectedIndex: (this.selectedIndex = -1),
  2671. });
  2672. return;
  2673. }
  2674. // Set a lower maximum if search is single char (single-character search is fast, but rendering is slow).
  2675. const max = textInput.length == 1 ? 20 : 100;
  2676. let results = [];
  2677. if (this.app.selectedFilter) {
  2678. if (this.app.selectedFilter.filterConfig) {
  2679. results = QuickInsert.searchLib.search(textInput, (item) => this.app.selectedFilter?.filterConfig
  2680. ? matchFilterConfig(this.app.selectedFilter.filterConfig, item)
  2681. : true, max);
  2682. }
  2683. }
  2684. else {
  2685. results = QuickInsert.searchLib.search(textInput, null, max);
  2686. }
  2687. if (this.app.attachedContext &&
  2688. this.app.attachedContext.restrictTypes &&
  2689. this.app.attachedContext.restrictTypes.length > 0) {
  2690. results = results.filter((i) => this.app.attachedContext?.restrictTypes?.includes(i.item.documentType));
  2691. }
  2692. this.results = results.map((res) => ({
  2693. item: res.item,
  2694. match: res.match,
  2695. actions: getActions(res.item.documentType, this.isInsertMode),
  2696. defaultAction: defaultAction(res.item.documentType, this.isInsertMode),
  2697. }));
  2698. this.view?.$$set?.({
  2699. results: this.results.reverse(),
  2700. selectedIndex: (this.selectedIndex = this.results.length - 1),
  2701. selectedAction: (this.selectedAction = null),
  2702. });
  2703. };
  2704. }
  2705. onTab(index) {
  2706. const actions = this.results[index].actions;
  2707. if (actions.length == 0)
  2708. return;
  2709. let idx;
  2710. if (this.selectedAction) {
  2711. idx = actions.findIndex((a) => a.id == this.selectedAction);
  2712. }
  2713. else {
  2714. idx = actions.findIndex((a) => a.id == this.results[index].defaultAction);
  2715. }
  2716. const nextIdx = (idx + 1) % actions.length;
  2717. this.view?.$$set?.({
  2718. selectedAction: (this.selectedAction = actions[nextIdx].id),
  2719. });
  2720. }
  2721. onEnter(index, evt) {
  2722. // TODO: get selected action
  2723. this.onAction(this.selectedAction || this.results[index].defaultAction, this.results[index].item, Boolean(evt.shiftKey));
  2724. }
  2725. async onAction(actionId, item, shiftKey) {
  2726. console.info(`Quick Insert | Invoked Action [${actionId}] on [${item.name}] shiftKey:${shiftKey}`);
  2727. const val = await DOCUMENTACTIONS[actionId](item);
  2728. if (val && this.isInsertMode) {
  2729. this.app.keepOpen = shiftKey; // Keep open until onSubmit completes
  2730. this.app.attachedContext?.onSubmit(val);
  2731. }
  2732. if (this.app.attachedContext?.allowMultiple === false || !shiftKey) {
  2733. this.app.closeDialog();
  2734. }
  2735. this.app.keepOpen = false;
  2736. }
  2737. }
  2738. class FilterController extends SearchController {
  2739. constructor() {
  2740. super(...arguments);
  2741. this.results = [];
  2742. }
  2743. onTab(index) {
  2744. this.onEnter(index);
  2745. }
  2746. onEnter(index) {
  2747. this.selectFilter(this.results[index]);
  2748. }
  2749. selectFilter(filter) {
  2750. this.app.setFilterTag(filter);
  2751. this.app.selectedFilter = filter;
  2752. this.deactivate();
  2753. this.app.showHint(`Searching: ${filter.subTitle}`);
  2754. }
  2755. onClick(index) {
  2756. this.onEnter(index);
  2757. this.app.focusInput();
  2758. }
  2759. search(textInput) {
  2760. const cleanedInput = textInput.toLowerCase().trim();
  2761. if (/\s$/g.test(textInput)) {
  2762. // User has added a space after tag -> selected
  2763. const matchingFilter = QuickInsert.filters.getFilterByTag(cleanedInput);
  2764. if (matchingFilter) {
  2765. this.selectFilter(matchingFilter);
  2766. return;
  2767. }
  2768. }
  2769. this.results = QuickInsert.filters.filters
  2770. .filter((f) => !f.disabled)
  2771. .filter((f) => f.tag.includes(cleanedInput));
  2772. this.view?.$$set?.({
  2773. results: this.results,
  2774. selectedIndex: (this.selectedIndex = this.results.length - 1),
  2775. });
  2776. }
  2777. }
  2778. var ActiveMode;
  2779. (function (ActiveMode) {
  2780. ActiveMode[ActiveMode["Search"] = 1] = "Search";
  2781. ActiveMode[ActiveMode["Filter"] = 2] = "Filter";
  2782. })(ActiveMode || (ActiveMode = {}));
  2783. class SearchApp extends Application {
  2784. constructor() {
  2785. super({
  2786. template: "modules/quick-insert/templates/quick-insert.html",
  2787. popOut: false,
  2788. });
  2789. this.debug = false;
  2790. this.mouseFocus = false;
  2791. this.inputFocus = false;
  2792. this.keepOpen = false;
  2793. this.mode = ActiveMode.Search;
  2794. this.selectedFilter = null;
  2795. this.attachedContext = null;
  2796. this.embeddedMode = false;
  2797. this.filterController = new FilterController(this);
  2798. this.documentController = new DocumentController(this);
  2799. this._checkFocus = () => {
  2800. if (this.debug || this.embeddedMode)
  2801. return;
  2802. if (!this.mouseFocus && !this.inputFocus && !this.keepOpen) {
  2803. this.closeDialog();
  2804. }
  2805. };
  2806. this._onKeyTab = (evt) => {
  2807. evt.preventDefault();
  2808. if (!this.embeddedMode)
  2809. this.controller.onTab(this.controller.selectedIndex);
  2810. };
  2811. this._onKeyEsc = (evt) => {
  2812. if (this.embeddedMode)
  2813. return;
  2814. evt.preventDefault();
  2815. evt.stopPropagation();
  2816. this.closeDialog();
  2817. };
  2818. this._onKeyDown = (evt) => {
  2819. evt.preventDefault();
  2820. this.selectNext();
  2821. };
  2822. this._onKeyUp = (evt) => {
  2823. evt.preventDefault();
  2824. this.selectPrevious();
  2825. };
  2826. this._onKeyEnter = (evt) => {
  2827. evt.preventDefault();
  2828. evt.stopImmediatePropagation();
  2829. if (this.controller.selectedIndex > -1) {
  2830. this.controller.onEnter(this.controller.selectedIndex, evt);
  2831. }
  2832. };
  2833. }
  2834. get open() {
  2835. return this._state > 0;
  2836. }
  2837. get controller() {
  2838. if (this.mode === ActiveMode.Filter) {
  2839. return this.filterController;
  2840. }
  2841. return this.documentController;
  2842. }
  2843. activateMode(mode) {
  2844. this.controller?.deactivate();
  2845. this.mode = mode;
  2846. this.controller?.activate();
  2847. }
  2848. resetInput(full = false) {
  2849. if (!full && this.selectedFilter) {
  2850. this.setFilterTag(this.selectedFilter);
  2851. }
  2852. else {
  2853. this.input?.html("");
  2854. }
  2855. this.text = undefined;
  2856. this.focusInput();
  2857. }
  2858. selectNext() {
  2859. this.controller?.selectNext();
  2860. }
  2861. selectPrevious() {
  2862. this.controller?.selectPrevious();
  2863. }
  2864. setFilterTag(filter) {
  2865. if (!this.input)
  2866. return;
  2867. const focus = this.input.is(":focus");
  2868. this.input.html("");
  2869. const editable = this.embeddedMode ? `contenteditable="false"` : "";
  2870. $(`<span class="search-tag" ${editable}>@${filter.tag}</span>`).prependTo(this.input);
  2871. $('<span class="breaker">&nbsp</span>').appendTo(this.input);
  2872. if (focus) {
  2873. this.focusInput();
  2874. }
  2875. }
  2876. closeDialog() {
  2877. if (this.embeddedMode)
  2878. return;
  2879. this.attachedContext?.onClose?.();
  2880. this.selectedFilter = null;
  2881. //@ts-expect-error tooltip not in types yet
  2882. game.tooltip.deactivate();
  2883. this.close();
  2884. }
  2885. render(force, options) {
  2886. if (options && options.context) {
  2887. this.attachedContext = options.context;
  2888. return super.render(force, options);
  2889. }
  2890. // Try to infer context
  2891. const target = document.activeElement;
  2892. if (target) {
  2893. this.attachedContext = identifyContext(target);
  2894. }
  2895. if (!this.attachedContext) {
  2896. return null;
  2897. }
  2898. //@ts-expect-error keyboard is never null really
  2899. game.keyboard.downKeys = new Set();
  2900. return super.render(force, options);
  2901. }
  2902. showHint(notice) {
  2903. this.hint?.html(notice);
  2904. }
  2905. focusInput() {
  2906. if (!this.input)
  2907. return;
  2908. placeCaretAtEnd(this.input.get(0));
  2909. this.inputFocus = true;
  2910. }
  2911. activateListeners(html) {
  2912. // (Re-)set position
  2913. html.removeAttr("style");
  2914. if (this.attachedContext?.spawnCSS) {
  2915. html.css(this.attachedContext.spawnCSS);
  2916. }
  2917. if (this.attachedContext?.classes) {
  2918. html.addClass(this.attachedContext.classes);
  2919. }
  2920. this.input = html.find(".search-editable-input");
  2921. this.hint = html.find(".quick-insert-hint");
  2922. this.input.on("input", () => {
  2923. this.searchInput();
  2924. });
  2925. this.input.on("dragstart", (evt) => evt.stopPropagation());
  2926. this.input.on("keydown", (evt) => {
  2927. switch (evt.which) {
  2928. case 13:
  2929. return this._onKeyEnter(evt);
  2930. case 40:
  2931. return this._onKeyDown(evt);
  2932. case 38:
  2933. return this._onKeyUp(evt);
  2934. case 27:
  2935. return this._onKeyEsc(evt);
  2936. case 9:
  2937. return this._onKeyTab(evt);
  2938. }
  2939. });
  2940. $(this.element).hover(() => {
  2941. this.mouseFocus = true;
  2942. this._checkFocus();
  2943. }, (e) => {
  2944. if (e.originalEvent?.shiftKey)
  2945. return;
  2946. this.mouseFocus = false;
  2947. this._checkFocus();
  2948. });
  2949. $(this.element).on("focusout", () => {
  2950. this.inputFocus = false;
  2951. this._checkFocus();
  2952. });
  2953. $(this.element).on("focusin", () => {
  2954. this.inputFocus = true;
  2955. this._checkFocus();
  2956. });
  2957. this.focusInput();
  2958. const node = this.element.get(0);
  2959. if (node) {
  2960. this.documentController.view = new SearchResults({
  2961. target: node,
  2962. });
  2963. this.filterController.view = new SearchFiltersResults({
  2964. target: node,
  2965. });
  2966. }
  2967. this.documentController.view?.$on("callAction", (data) => {
  2968. const { actionId, item, shiftKey } = data.detail;
  2969. this.documentController.onAction(actionId, item, shiftKey);
  2970. });
  2971. this.filterController.view?.$on("selected", (data) => {
  2972. const { index } = data.detail;
  2973. this.filterController.onClick(index);
  2974. });
  2975. if (this.attachedContext?.filter) {
  2976. this.activateMode(ActiveMode.Filter);
  2977. if (typeof this.attachedContext.filter === "string") {
  2978. const found = QuickInsert.filters.getFilterByTag(this.attachedContext.filter) ??
  2979. QuickInsert.filters.getFilter(this.attachedContext.filter);
  2980. if (found) {
  2981. this.filterController.selectFilter(found);
  2982. }
  2983. }
  2984. else {
  2985. this.filterController.selectFilter(this.attachedContext.filter);
  2986. }
  2987. }
  2988. if (this.attachedContext?.startText) {
  2989. this.input.append(this.attachedContext.startText);
  2990. this.focusInput();
  2991. this.searchInput();
  2992. }
  2993. if (!QuickInsert.searchLib) {
  2994. this.showHint(`<i class="fas fa-spinner"></i> Loading index...`);
  2995. loadSearchIndex()
  2996. .then(() => {
  2997. if (this.input?.text().trim().length) {
  2998. this.searchInput();
  2999. }
  3000. else {
  3001. this.showHint(`Index loaded successfully`);
  3002. }
  3003. })
  3004. .catch((reason) => {
  3005. this.showHint(`Failed to load index ${reason}`);
  3006. });
  3007. // @ts-ignore
  3008. }
  3009. else if (QuickInsert.searchLib?.index?.fuse._docs.length == 0) {
  3010. this.showHint(`Search index is empty for some reason`);
  3011. }
  3012. }
  3013. searchInput() {
  3014. if (!this.input)
  3015. return;
  3016. const text = this.input.text();
  3017. this.text = text;
  3018. const breaker = $(this.input).find(".breaker");
  3019. this.showHint("");
  3020. if (this.selectedFilter) {
  3021. // Text was changed or breaker was removed
  3022. if (!text.startsWith(`@${this.selectedFilter.tag}`) ||
  3023. breaker.length === 0 ||
  3024. breaker.is(":empty") ||
  3025. breaker.html() === "<br>") {
  3026. if (this.embeddedMode) {
  3027. this.setFilterTag(this.selectedFilter);
  3028. return;
  3029. }
  3030. // Selectedfilter doesn't match any more :(
  3031. this.input.html(text);
  3032. this.focusInput();
  3033. this.selectedFilter = null;
  3034. this.activateMode(ActiveMode.Filter);
  3035. this.filterController.search(text.substr(1).trim());
  3036. }
  3037. else {
  3038. this.activateMode(ActiveMode.Search);
  3039. const search = text.replace(`@${this.selectedFilter.tag}`, "").trim();
  3040. this.documentController.search(search);
  3041. }
  3042. }
  3043. else if (text.startsWith("@")) {
  3044. this.activateMode(ActiveMode.Filter);
  3045. this.filterController.search(text.substr(1));
  3046. }
  3047. else {
  3048. this.activateMode(ActiveMode.Search);
  3049. this.documentController.search(text);
  3050. }
  3051. }
  3052. }
  3053. class IndexingSettings extends FormApplication {
  3054. static get defaultOptions() {
  3055. return {
  3056. ...super.defaultOptions,
  3057. title: i18n("IndexingSettingsTitle"),
  3058. id: "indexing-settings",
  3059. template: "modules/quick-insert/templates/indexing-settings.hbs",
  3060. resizable: true,
  3061. width: 660,
  3062. };
  3063. }
  3064. getData() {
  3065. if (!game.packs)
  3066. return null;
  3067. const disabled = getSetting(ModuleSetting.INDEXING_DISABLED);
  3068. return {
  3069. documentTypes: IndexedDocumentTypes.map((type) => ({
  3070. type,
  3071. title: `DOCUMENT.${type}`,
  3072. values: [1, 2, 3, 4].map((role) => ({
  3073. role,
  3074. disabled: disabled?.entities?.[type]?.includes(role),
  3075. })),
  3076. })),
  3077. compendiums: [...game.packs.keys()].map((pack) => ({
  3078. pack,
  3079. values: [1, 2, 3, 4].map((role) => ({
  3080. role,
  3081. disabled: disabled?.packs?.[pack]?.includes(role),
  3082. })),
  3083. })),
  3084. };
  3085. }
  3086. activateListeners(html) {
  3087. super.activateListeners(html);
  3088. // Set initial state for all
  3089. const disabled = getSetting(ModuleSetting.INDEXING_DISABLED);
  3090. Object.entries(disabled.packs).forEach(([pack, val]) => {
  3091. const check = html.find(`[data-disable="${pack}"]`);
  3092. if (permissionListEq(val, [1, 2, 3, 4])) {
  3093. check.prop("checked", false);
  3094. }
  3095. else {
  3096. check.prop("indeterminate", true);
  3097. }
  3098. });
  3099. // Root check change -> updates regular checks
  3100. html.find("input.disable-pack").on("change", function () {
  3101. const compendium = this.dataset.disable;
  3102. html
  3103. .find(`input[name^="${compendium}."]`)
  3104. .prop("checked", this.checked);
  3105. });
  3106. // Regular check change -> updates root check
  3107. html.find(".form-fields input").on("change", function () {
  3108. const compendium = this.name.slice(0, -2);
  3109. const checks = html
  3110. .find(`input[name^="${compendium}."]`)
  3111. .toArray();
  3112. if (checks.every((e) => e.checked)) {
  3113. html
  3114. .find(`[data-disable="${compendium}"]`)
  3115. .prop("checked", true)
  3116. .prop("indeterminate", false);
  3117. }
  3118. else if (checks.every((e) => !e.checked)) {
  3119. html
  3120. .find(`[data-disable="${compendium}"]`)
  3121. .prop("checked", false)
  3122. .prop("indeterminate", false);
  3123. }
  3124. else {
  3125. html
  3126. .find(`[data-disable="${compendium}"]`)
  3127. .prop("checked", checks.some((e) => e.checked))
  3128. .prop("indeterminate", true);
  3129. }
  3130. });
  3131. // Deselect all button
  3132. html.find("button.deselect-all").on("click", (e) => {
  3133. e.preventDefault();
  3134. e.stopPropagation();
  3135. html
  3136. .find(`.form-group.pack input[type="checkbox"]`)
  3137. .prop("checked", false)
  3138. .prop("indeterminate", false);
  3139. });
  3140. // Select all button
  3141. html.find("button.select-all").on("click", (e) => {
  3142. e.preventDefault();
  3143. e.stopPropagation();
  3144. html
  3145. .find(`.form-group.pack input[type="checkbox"]`)
  3146. .prop("checked", true)
  3147. .prop("indeterminate", false);
  3148. });
  3149. }
  3150. async _updateObject(event, formData) {
  3151. const res = {
  3152. entities: {},
  3153. packs: {},
  3154. };
  3155. for (const [name, checked] of Object.entries(formData)) {
  3156. if (!checked) {
  3157. const [base, middle, last] = name.split(".");
  3158. if (last) {
  3159. const pack = `${base}.${middle}`;
  3160. res.packs[pack] = res.packs[pack] || [];
  3161. res.packs[pack].push(parseInt(last));
  3162. }
  3163. else {
  3164. const type = base;
  3165. res.entities[type] = res.entities[type] || [];
  3166. res.entities[type].push(parseInt(middle));
  3167. }
  3168. }
  3169. }
  3170. setSetting(ModuleSetting.INDEXING_DISABLED, res);
  3171. }
  3172. }
  3173. const moduleSettings = {
  3174. [ModuleSetting.GM_ONLY]: {
  3175. setting: ModuleSetting.GM_ONLY,
  3176. name: "QUICKINSERT.SettingsGmOnly",
  3177. hint: "QUICKINSERT.SettingsGmOnlyHint",
  3178. type: Boolean,
  3179. default: false,
  3180. scope: "world",
  3181. },
  3182. [ModuleSetting.FILTERS_SHEETS_ENABLED]: {
  3183. setting: ModuleSetting.FILTERS_SHEETS_ENABLED,
  3184. name: "QUICKINSERT.SettingsFiltersSheetsEnabled",
  3185. hint: "QUICKINSERT.SettingsFiltersSheetsEnabledHint",
  3186. type: Boolean,
  3187. default: true,
  3188. scope: "world",
  3189. },
  3190. [ModuleSetting.AUTOMATIC_INDEXING]: {
  3191. setting: ModuleSetting.AUTOMATIC_INDEXING,
  3192. name: "QUICKINSERT.SettingsAutomaticIndexing",
  3193. hint: "QUICKINSERT.SettingsAutomaticIndexingHint",
  3194. type: Number,
  3195. choices: {
  3196. 3000: "QUICKINSERT.SettingsAutomaticIndexing3s",
  3197. 5000: "QUICKINSERT.SettingsAutomaticIndexing5s",
  3198. 10000: "QUICKINSERT.SettingsAutomaticIndexing10s",
  3199. "-1": "QUICKINSERT.SettingsAutomaticIndexingOnFirstOpen",
  3200. },
  3201. default: -1,
  3202. scope: "client",
  3203. },
  3204. [ModuleSetting.INDEX_TIMEOUT]: {
  3205. setting: ModuleSetting.INDEX_TIMEOUT,
  3206. name: "QUICKINSERT.SettingsIndexTimeout",
  3207. hint: "QUICKINSERT.SettingsIndexTimeoutHint",
  3208. type: Number,
  3209. choices: {
  3210. 1500: "QUICKINSERT.SettingsIndexTimeout1_5s",
  3211. 3000: "QUICKINSERT.SettingsIndexTimeout3s",
  3212. 7000: "QUICKINSERT.SettingsIndexTimeout7s",
  3213. 9500: "QUICKINSERT.SettingsIndexTimeou9_5s",
  3214. },
  3215. default: 1500,
  3216. scope: "world",
  3217. },
  3218. [ModuleSetting.SEARCH_BUTTON]: {
  3219. setting: ModuleSetting.SEARCH_BUTTON,
  3220. name: "QUICKINSERT.SettingsSearchButton",
  3221. hint: "QUICKINSERT.SettingsSearchButtonHint",
  3222. type: Boolean,
  3223. default: false,
  3224. scope: "client",
  3225. },
  3226. [ModuleSetting.ENABLE_GLOBAL_CONTEXT]: {
  3227. setting: ModuleSetting.ENABLE_GLOBAL_CONTEXT,
  3228. name: "QUICKINSERT.SettingsEnableGlobalContext",
  3229. hint: "QUICKINSERT.SettingsEnableGlobalContextHint",
  3230. type: Boolean,
  3231. default: true,
  3232. },
  3233. [ModuleSetting.DEFAULT_ACTION_SCENE]: {
  3234. setting: ModuleSetting.DEFAULT_ACTION_SCENE,
  3235. name: "QUICKINSERT.SettingsDefaultActionScene",
  3236. hint: "QUICKINSERT.SettingsDefaultActionSceneHint",
  3237. type: String,
  3238. choices: {
  3239. show: "SCENES.Configure",
  3240. viewScene: "SCENES.View",
  3241. activateScene: "SCENES.Activate",
  3242. },
  3243. default: "show",
  3244. },
  3245. [ModuleSetting.DEFAULT_ACTION_ROLL_TABLE]: {
  3246. setting: ModuleSetting.DEFAULT_ACTION_ROLL_TABLE,
  3247. name: "QUICKINSERT.SettingsDefaultActionRollTable",
  3248. hint: "QUICKINSERT.SettingsDefaultActionRollTableHint",
  3249. type: String,
  3250. choices: {
  3251. show: "QUICKINSERT.ActionEdit",
  3252. roll: "TABLE.Roll",
  3253. },
  3254. default: "show",
  3255. },
  3256. [ModuleSetting.DEFAULT_ACTION_MACRO]: {
  3257. setting: ModuleSetting.DEFAULT_ACTION_MACRO,
  3258. name: "QUICKINSERT.SettingsDefaultActionMacro",
  3259. hint: "QUICKINSERT.SettingsDefaultActionMacroHint",
  3260. type: String,
  3261. choices: {
  3262. show: "QUICKINSERT.ActionEdit",
  3263. execute: "QUICKINSERT.ActionExecute",
  3264. },
  3265. default: "show",
  3266. },
  3267. [ModuleSetting.SEARCH_TOOLTIPS]: {
  3268. setting: ModuleSetting.SEARCH_TOOLTIPS,
  3269. name: "QUICKINSERT.SettingsSearchTooltips",
  3270. hint: "QUICKINSERT.SettingsSearchTooltipsHint",
  3271. type: String,
  3272. choices: {
  3273. off: "QUICKINSERT.SettingsSearchTooltipsValueOff",
  3274. text: "QUICKINSERT.SettingsSearchTooltipsValueText",
  3275. image: "QUICKINSERT.SettingsSearchTooltipsValueImage",
  3276. full: "QUICKINSERT.SettingsSearchTooltipsValueFull",
  3277. },
  3278. default: "text",
  3279. },
  3280. [ModuleSetting.EMBEDDED_INDEXING]: {
  3281. setting: ModuleSetting.EMBEDDED_INDEXING,
  3282. name: "QUICKINSERT.SettingsEmbeddedIndexing",
  3283. hint: "QUICKINSERT.SettingsEmbeddedIndexingHint",
  3284. type: Boolean,
  3285. default: false,
  3286. scope: "world",
  3287. },
  3288. [ModuleSetting.INDEXING_DISABLED]: {
  3289. setting: ModuleSetting.INDEXING_DISABLED,
  3290. name: "Things that have indexing disabled",
  3291. type: Object,
  3292. default: {
  3293. entities: {
  3294. Macro: [1, 2],
  3295. Scene: [1, 2],
  3296. Playlist: [1, 2],
  3297. RollTable: [1, 2],
  3298. },
  3299. packs: {},
  3300. },
  3301. scope: "world",
  3302. config: false, // Doesn't show up in config
  3303. },
  3304. [ModuleSetting.FILTERS_CLIENT]: {
  3305. setting: ModuleSetting.FILTERS_CLIENT,
  3306. name: "Own filters",
  3307. type: Object,
  3308. default: {
  3309. saveRev: SAVE_SETTINGS_REVISION,
  3310. disabled: [],
  3311. filters: [],
  3312. },
  3313. config: false, // Doesn't show up in config
  3314. },
  3315. [ModuleSetting.FILTERS_WORLD]: {
  3316. setting: ModuleSetting.FILTERS_WORLD,
  3317. name: "World filters",
  3318. type: Object,
  3319. default: {
  3320. saveRev: SAVE_SETTINGS_REVISION,
  3321. filters: [],
  3322. },
  3323. scope: "world",
  3324. config: false, // Doesn't show up in config
  3325. },
  3326. [ModuleSetting.FILTERS_SHEETS]: {
  3327. setting: ModuleSetting.FILTERS_SHEETS,
  3328. name: "Sheet filters",
  3329. type: Object,
  3330. default: {},
  3331. scope: "world",
  3332. config: false, // Doesn't show up in config
  3333. },
  3334. };
  3335. function registerSettings(callbacks = {}) {
  3336. Object.entries(moduleSettings).forEach(([setting, item]) => {
  3337. registerSetting(setting, (value) => {
  3338. callbacks[item.setting]?.(value);
  3339. }, item);
  3340. });
  3341. }
  3342. function mapKey(key) {
  3343. if (key.startsWith("Key")) {
  3344. return key[key.length - 1].toLowerCase();
  3345. }
  3346. return key;
  3347. }
  3348. function registerProseMirrorKeys() {
  3349. const binds = game?.keybindings?.bindings?.get("quick-insert." + ModuleSetting.KEY_BIND);
  3350. if (!binds?.length) {
  3351. console.info("Quick Insert | ProseMirror extension found no key binding");
  3352. return;
  3353. }
  3354. function keyCallback(state, dispatch, view) {
  3355. if (QuickInsert.app?.embeddedMode)
  3356. return false;
  3357. // Open window
  3358. QuickInsert.open(new ProseMirrorContext(state, dispatch, view));
  3359. return true;
  3360. }
  3361. const keyMap = Object.fromEntries(binds.map((bind) => {
  3362. return [
  3363. `${bind.modifiers?.map((m) => m + "-").join("")}${mapKey(bind.key)}`,
  3364. keyCallback,
  3365. ];
  3366. }));
  3367. ProseMirror.defaultPlugins.QuickInsert = ProseMirror.keymap(keyMap);
  3368. }
  3369. function quickInsertDisabled() {
  3370. return !game.user?.isGM && getSetting(ModuleSetting.GM_ONLY);
  3371. }
  3372. // Client is currently reindexing?
  3373. let reIndexing = false;
  3374. Hooks.once("init", async function () {
  3375. registerMenu({
  3376. menu: "indexingSettings",
  3377. name: "QUICKINSERT.SettingsIndexingSettings",
  3378. label: "QUICKINSERT.SettingsIndexingSettingsLabel",
  3379. icon: "fas fa-search",
  3380. type: IndexingSettings,
  3381. restricted: false,
  3382. });
  3383. registerMenu({
  3384. menu: "filterMenu",
  3385. name: "QUICKINSERT.SettingsFilterMenu",
  3386. label: "QUICKINSERT.SettingsFilterMenuLabel",
  3387. icon: "fas fa-filter",
  3388. type: FilterList,
  3389. restricted: false,
  3390. });
  3391. registerSettings({
  3392. [ModuleSetting.FILTERS_WORLD]: () => {
  3393. if (quickInsertDisabled())
  3394. return;
  3395. QuickInsert.filters.loadSave();
  3396. },
  3397. [ModuleSetting.FILTERS_CLIENT]: () => {
  3398. if (quickInsertDisabled())
  3399. return;
  3400. QuickInsert.filters.loadSave();
  3401. },
  3402. [ModuleSetting.INDEXING_DISABLED]: async () => {
  3403. if (quickInsertDisabled())
  3404. return;
  3405. // Active users will start reindexing in deterministic order, once per 300ms
  3406. if (reIndexing)
  3407. return;
  3408. reIndexing = true;
  3409. if (game.users && game.userId !== null) {
  3410. const order = [...game.users.contents]
  3411. .filter((u) => u.active)
  3412. .map((u) => u.id)
  3413. .indexOf(game.userId);
  3414. await resolveAfter(order * 300);
  3415. }
  3416. await QuickInsert.forceIndex();
  3417. reIndexing = false;
  3418. },
  3419. });
  3420. game.keybindings.register("quick-insert", ModuleSetting.KEY_BIND, {
  3421. name: "QUICKINSERT.SettingsQuickOpen",
  3422. textInput: true,
  3423. editable: [
  3424. { key: "Space", modifiers: [KeyboardManager.MODIFIER_KEYS.CONTROL] },
  3425. ],
  3426. onDown: (ctx) => {
  3427. QuickInsert.toggle(ctx._quick_insert_extra?.context);
  3428. return true;
  3429. },
  3430. precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL,
  3431. });
  3432. });
  3433. Hooks.once("ready", function () {
  3434. if (quickInsertDisabled())
  3435. return;
  3436. console.log("Quick Insert | Initializing...");
  3437. // Initialize application base
  3438. QuickInsert.filters = new SearchFilterCollection();
  3439. QuickInsert.app = new SearchApp();
  3440. registerTinyMCEPlugin();
  3441. registerProseMirrorKeys();
  3442. importSystemIntegration().then((systemIntegration) => {
  3443. if (systemIntegration) {
  3444. QuickInsert.systemIntegration = systemIntegration;
  3445. QuickInsert.systemIntegration.init();
  3446. if (QuickInsert.systemIntegration.defaultSheetFilters) {
  3447. registerMenu({
  3448. menu: "sheetFilters",
  3449. name: "QUICKINSERT.SettingsSheetFilters",
  3450. label: "QUICKINSERT.SettingsSheetFiltersLabel",
  3451. icon: "fas fa-filter",
  3452. type: SheetFilters,
  3453. restricted: false,
  3454. });
  3455. }
  3456. }
  3457. });
  3458. document.addEventListener("keydown", (evt) => {
  3459. // Allow in input fields...
  3460. customKeybindHandler(evt);
  3461. });
  3462. setupDocumentHooks(QuickInsert);
  3463. console.log("Quick Insert | Search Application ready");
  3464. const indexDelay = getSetting(ModuleSetting.AUTOMATIC_INDEXING);
  3465. if (indexDelay != -1) {
  3466. setTimeout(() => {
  3467. console.log("Quick Insert | Automatic indexing initiated");
  3468. loadSearchIndex();
  3469. }, indexDelay);
  3470. }
  3471. });
  3472. Hooks.on("renderSceneControls", (controls, html) => {
  3473. if (!getSetting(ModuleSetting.SEARCH_BUTTON))
  3474. return;
  3475. const searchBtn = $(`<li class="scene-control" title="Quick Insert" class="quick-insert-open">
  3476. <i class="fas fa-search"></i>
  3477. </li>`);
  3478. html.children(".main-controls").append(searchBtn);
  3479. searchBtn.on("click", () => QuickInsert.open());
  3480. });
  3481. // Exports and API usage
  3482. //@ts-ignore
  3483. globalThis.QuickInsert = QuickInsert;
  3484. export { CharacterSheetContext, ModuleSetting, QuickInsert, SearchContext, getSetting, setSetting };
  3485. //# sourceMappingURL=quick-insert.js.map