Skip to content

Commit b355790

Browse files
committed
v4.4.0
1 parent 42e8997 commit b355790

4 files changed

Lines changed: 460 additions & 9 deletions

File tree

module.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"id": "investigation-board",
33
"title": "Investigation Board",
44
"description": "<p>A Foundry VTT module that lets everyone create, edit, and move sticky and photo notes on the scene as collaborative investigation tools.</p>",
5-
"version": "4.3.2",
5+
"version": "4.4.0",
66
"socket": true,
77
"authors": [
88
{

scripts/main.js

Lines changed: 136 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ import {
1919
createPhotoNoteFromScene,
2020
createPhotoNoteFromItem,
2121
createHandoutNoteFromPage,
22-
createMediaNoteFromSound
22+
createMediaNoteFromSound,
23+
importFolderAsNotes,
24+
importPlaylistAsNotes
2325
} from "./utils/creation-utils.js";
2426

2527
// v13 namespaced imports
@@ -254,6 +256,7 @@ Hooks.once("init", () => {
254256

255257
// Hook to initialize socket for collaborative editing
256258
Hooks.once("ready", () => {
259+
console.log("Investigation Board: Ready hook fired. v13 detected.");
257260
initSocket();
258261

259262
// Show setup warning to GM if enabled
@@ -425,13 +428,33 @@ async function _resolveDocumentFromLi(li, collection) {
425428
const el = li instanceof HTMLElement ? li : li[0];
426429
if (!el) return null;
427430

431+
// v13 resolution is much more varied
432+
const target = el.closest(".directory-item") || el.closest(".document") || el.closest(".playlist") || el;
433+
434+
console.log("Investigation Board DEBUG: Resolving document from element", target);
435+
console.log("Investigation Board DEBUG: Target dataset:", JSON.stringify(target.dataset));
436+
428437
// 1. Try UUID directly (most reliable in v13)
429-
const uuid = el.dataset.uuid || el.getAttribute("data-uuid");
430-
if (uuid) return await fromUuid(uuid);
438+
const uuid = target.dataset.uuid || target.getAttribute("data-uuid");
439+
if (uuid) {
440+
console.log("Investigation Board DEBUG: Found UUID:", uuid);
441+
return await fromUuid(uuid);
442+
}
431443

432444
// 2. Try ID and Pack
433-
const docId = el.dataset.documentId || el.dataset.entryId || el.dataset.id || el.getAttribute("data-id");
434-
const packName = el.closest("[data-pack]")?.dataset.pack || el.closest(".compendium")?.dataset.pack;
445+
// v13 uses entryId or documentId often. Playlists sometimes use playlistId.
446+
const docId = target.dataset.documentId ||
447+
target.dataset.entryId ||
448+
target.dataset.id ||
449+
target.dataset.playlistId ||
450+
target.getAttribute("data-document-id") ||
451+
target.getAttribute("data-entry-id") ||
452+
target.getAttribute("data-id") ||
453+
target.getAttribute("data-playlist-id");
454+
455+
console.log("Investigation Board DEBUG: Resolved docId:", docId);
456+
457+
const packName = target.closest("[data-pack]")?.dataset.pack || target.closest(".compendium")?.dataset.pack;
435458

436459
if (docId) {
437460
// If we have a pack name, use it
@@ -552,6 +575,113 @@ Hooks.on("getPlaylistSoundContextOptions", (html, entryOptions) => {
552575
});
553576
});
554577

578+
// Context menu hook for Playlist entries
579+
Hooks.on("getPlaylistContextOptions", (html, entryOptions) => {
580+
console.log("Investigation Board: getPlaylistContextOptions fired");
581+
entryOptions.push({
582+
name: "Import Playlist as Notes",
583+
icon: '<i class="fas fa-cassette-tape"></i>',
584+
callback: async (li) => {
585+
const playlist = await _resolveDocumentFromLi(li, game.playlists);
586+
if (playlist) {
587+
await importPlaylistAsNotes(playlist);
588+
} else {
589+
ui.notifications.warn("Investigation Board: Could not resolve Playlist.");
590+
}
591+
},
592+
condition: () => game.user.isGM
593+
});
594+
});
595+
596+
Hooks.on("getPlaylistDirectoryEntryContext", (html, entryOptions) => {
597+
// Keeping this as a secondary variation for compatibility
598+
if (entryOptions.find(e => e.name === "Import Playlist as Notes")) return;
599+
entryOptions.push({
600+
name: "Import Playlist as Notes",
601+
icon: '<i class="fas fa-cassette-tape"></i>',
602+
callback: async (li) => {
603+
const playlist = await _resolveDocumentFromLi(li, game.playlists);
604+
if (playlist) {
605+
await importPlaylistAsNotes(playlist);
606+
} else {
607+
ui.notifications.warn("Investigation Board: Could not resolve Playlist.");
608+
}
609+
},
610+
condition: () => game.user.isGM
611+
});
612+
});
613+
614+
/**
615+
* Shared callback for folder context menu to import content
616+
*/
617+
async function _onImportFolderAsNotes(li) {
618+
// li might be the jQuery element or the raw element from our manual hook
619+
const el = (li instanceof HTMLElement) ? li : (li[0] || li);
620+
621+
// v13 resolution is much more varied
622+
const folderId = el.dataset.id ||
623+
el.dataset.folderId ||
624+
el.getAttribute("data-id") ||
625+
el.getAttribute("data-folder-id") ||
626+
el.closest(".folder")?.dataset.folderId ||
627+
el.closest(".folder")?.dataset.id ||
628+
el.closest("[data-folder-id]")?.dataset.folderId;
629+
630+
if (!folderId) {
631+
console.warn("Investigation Board: Could not find folder ID on element", el);
632+
return;
633+
}
634+
635+
const folder = game.folders.get(folderId);
636+
if (folder) {
637+
await importFolderAsNotes(folder);
638+
} else {
639+
console.warn("Investigation Board: Could not find folder with ID", folderId);
640+
}
641+
}
642+
643+
const _folderHookCallback = (html, entryOptions) => {
644+
// Avoid duplicate entries
645+
if (entryOptions.find(e => e.name === "Import Folder as Notes")) return;
646+
647+
entryOptions.push({
648+
name: "Import Folder as Notes",
649+
icon: '<i class="fa-solid fa-camera-polaroid"></i>',
650+
callback: (li) => _onImportFolderAsNotes(li),
651+
condition: () => game.user.isGM
652+
});
653+
};
654+
655+
// Folder Context Hooks - using multiple variations for v13 compatibility
656+
Hooks.on("getDirectoryFolderContext", _folderHookCallback);
657+
Hooks.on("getActorDirectoryFolderContext", _folderHookCallback);
658+
Hooks.on("getItemDirectoryFolderContext", _folderHookCallback);
659+
Hooks.on("getSceneDirectoryFolderContext", _folderHookCallback);
660+
Hooks.on("getPlaylistDirectoryFolderContext", _folderHookCallback);
661+
Hooks.on("getFolderContextOptions", _folderHookCallback);
662+
663+
// Some v13 sidebars might trigger EntryContext for folders
664+
Hooks.on("getActorDirectoryEntryContext", (html, entryOptions) => {
665+
if (html[0]?.classList.contains("folder") || html[0]?.closest(".folder")) {
666+
_folderHookCallback(html, entryOptions);
667+
}
668+
});
669+
Hooks.on("getItemDirectoryEntryContext", (html, entryOptions) => {
670+
if (html[0]?.classList.contains("folder") || html[0]?.closest(".folder")) {
671+
_folderHookCallback(html, entryOptions);
672+
}
673+
});
674+
Hooks.on("getSceneDirectoryEntryContext", (html, entryOptions) => {
675+
if (html[0]?.classList.contains("folder") || html[0]?.closest(".folder")) {
676+
_folderHookCallback(html, entryOptions);
677+
}
678+
});
679+
Hooks.on("getPlaylistDirectoryEntryContext", (html, entryOptions) => {
680+
if (html[0]?.classList.contains("folder") || html[0]?.closest(".folder")) {
681+
_folderHookCallback(html, entryOptions);
682+
}
683+
});
684+
555685

556686
// Hook to deactivate connect mode on scene change and initialize connection lines
557687
Hooks.on("canvasReady", () => {
@@ -577,4 +707,4 @@ Hooks.on("canvasReady", () => {
577707
updatePins();
578708
drawAllConnectionLines();
579709
}, 100);
580-
});
710+
});

0 commit comments

Comments
 (0)