diff --git a/langs/en-ca.json b/langs/en-ca.json index 2ead115..02cd095 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -11,7 +11,7 @@ }, "chatImageLinks": { "name": "Image Shortcuts", - "hint": "(v13+) Automatically embeds image and gif links when posted in the chat. The link must point to a specific image file (e.g. png, webp, gif), otherwise it will not be embedded at all." + "hint": "(v13+) When attempting to send an image/gif in chat, this allows you to easily embed the actual image in the text by changing \"http\"/\"https\" into \"image\", automatically displaying the image after sending the message." }, "chatSidebarBackground": { "name": "Chat Background", @@ -65,20 +65,17 @@ "label": "Configure Hotbar" } }, - "keybindings": { - "toggleMouseBroadcast": { - "name": "Toggle Show Cursor", - "hint": "(v13+) Temporarily turns off the mouse cursor position that other players can see. Hides the cursor until you activate this keybind again." - } - }, "apps": { "no-settings-to-display": "No settings to display", "make-global-reference": "Make Global Reference" }, - "notifs": { - "toggleMouseBroadcast": { - "hidingCursor": "Hiding your cursor from others!", - "showingCursor": "Showing your cursor to others!" + "dialogs": { + "chatImageLinks": { + "didYouKnowImageLink": "", + "convertAndDontShowAgain": "", + "justConvert": "", + "ignoreAndDontShowAgain": "", + "disableEntirely": "" } } } diff --git a/module.json b/module.json index 92e7f6e..61d9791 100644 --- a/module.json +++ b/module.json @@ -1,7 +1,7 @@ { "id": "oft", "title": "Oliver's Foundry Tweaks", - "version": "1.2.0", + "version": "1.1.0", "authors": [ { "name": "Oliver" } ], diff --git a/module/apps/DevSettingsMenu.mjs b/module/apps/DevSettingsMenu.mjs index bee0b96..81a14e8 100644 --- a/module/apps/DevSettingsMenu.mjs +++ b/module/apps/DevSettingsMenu.mjs @@ -9,7 +9,6 @@ export class DevSettingsMenu extends OFTSettingsMenu { }; static get _SETTINGS() { - if (!categories.has(`dev`)) { return [] }; const devSettings = categories.get(`dev`); const settingIDs = []; for (const [settingID, shown] of devSettings.entries()) { diff --git a/module/apps/HotbarSettingsMenu.mjs b/module/apps/HotbarSettingsMenu.mjs index 5a722e3..0c7fb9e 100644 --- a/module/apps/HotbarSettingsMenu.mjs +++ b/module/apps/HotbarSettingsMenu.mjs @@ -9,7 +9,6 @@ export class HotbarSettingsMenu extends OFTSettingsMenu { }; static get _SETTINGS() { - if (!categories.has(`hotbar`)) { return [] }; const settings = categories.get(`hotbar`); const settingIDs = []; for (const [settingID, shown] of settings.entries()) { diff --git a/module/apps/OFTSettingsMenu.mjs b/module/apps/OFTSettingsMenu.mjs index 387dc0a..74ddf97 100644 --- a/module/apps/OFTSettingsMenu.mjs +++ b/module/apps/OFTSettingsMenu.mjs @@ -47,10 +47,6 @@ export class OFTSettingsMenu extends HAM(ApplicationV2) { }; static _SETTINGS = []; - - static get isEmpty() { - return this._SETTINGS.length === 0; - }; // #endregion Options // #region Data Prep diff --git a/module/conflicts/crucible.mjs b/module/conflicts/crucible.mjs deleted file mode 100644 index 6df24db..0000000 --- a/module/conflicts/crucible.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { key as preventMovementHistoryKey } from "../tweaks/preventMovementHistory.mjs"; - -Hooks.on(`oft.preRegisterTweak`, (tweakID) => { - if (game.system.id !== `crucible`) { return }; - - switch (tweakID) { - case preventMovementHistoryKey: - return false; - }; -}); diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 729a004..60cde1b 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -1,6 +1,5 @@ // Settings -import { preventMovementHistory } from "../tweaks/preventMovementHistory.mjs"; -import { toggleMouseBroadcast } from "../tweaks/toggleMouseBroadcast.mjs"; +import { preventMovementHistory } from "../settings/preventMovementHistory.mjs"; // Utils import { Logger } from "../utils/Logger.mjs"; @@ -15,5 +14,4 @@ Hooks.on(`init`, () => { Logger.log(`Initializing`); preventMovementHistory(); - toggleMouseBroadcast(); }); diff --git a/module/hooks/oft.preRegisterTweak.mjs b/module/hooks/oft.preRegisterTweak.mjs deleted file mode 100644 index 47de773..0000000 --- a/module/hooks/oft.preRegisterTweak.mjs +++ /dev/null @@ -1,19 +0,0 @@ -/* -This hook is used to give external modules or systems the ability to interact -with the tweak registration lifecycle to do something before a tweak is registered -or being able to prevent registration of incompatible tweaks. - -The hook receives a string indicating which tweak this hook is being called for -and a boolean value indicating if the tweak is considered invasive. Returning -explicit false prevents that tweak from being registered. - -Invasive tweaks are additions that manipulate or override Document or helper -classes. An example of an invasive tweak is the "toggleMouseBroadcast", -tweak which replaces the existing "CONFIG.Canvas.layers.controls.layerClass" -class, most of these tweaks do smartly extend from the same CONFIG class -that they replace, however if they override a part of the class that -other modules/systems rely on, then that is a good time to block that -specific tweak's registration. - -Call Signature: (tweakKey: string, isInvasive: boolean) => (void | boolean) -*/ diff --git a/module/hooks/oft.preventSetting.mjs b/module/hooks/oft.preventSetting.mjs new file mode 100644 index 0000000..500dc8e --- /dev/null +++ b/module/hooks/oft.preventSetting.mjs @@ -0,0 +1,12 @@ +/* +This hook is used for invasive hooks that we want to provide the +option for systems and other modules to be able to disable in case +of incompatabilities for whatever reason. This can also be used +internally within this module if we discover incompatabilites with +systems and want to disable it on our side. + +This file is meant more documentation than anything at this point in +time. + +Call Signature: (settingKey: string) => (void | boolean) +*/ diff --git a/module/hooks/oft.settingStatuses.mjs b/module/hooks/oft.settingStatuses.mjs new file mode 100644 index 0000000..46bef18 --- /dev/null +++ b/module/hooks/oft.settingStatuses.mjs @@ -0,0 +1,13 @@ +/* +This hook is used to enable any modules that attempt to disable settings +or just want to investigate what settings are enabled to be able to get +a ping with information about which settings where registered entirely +and which weren't. The object that is passed to this is frozen and is +not meant to be edited as you cannot de-register nor prevent setting +registration from this hook. For that see the "oft.preventSetting" hook. + +This file is meant more documentation than anything at this point in +time. + +Call Signature: (settings: Record) => void +*/ diff --git a/module/hooks/oft.tweakStatuses.mjs b/module/hooks/oft.tweakStatuses.mjs deleted file mode 100644 index 622d6f4..0000000 --- a/module/hooks/oft.tweakStatuses.mjs +++ /dev/null @@ -1,9 +0,0 @@ -/* -This hook is used to broadcast the final status of all tweaks within the module, -allowing other modules to either confirm their registration didn't happen or to -do something once all of the module setup has been finalized. Tweak statuses -cannot be blocked or changed from this hook, to prevent a tweak from being -registered you should use the "oft.preRegisterTweak" hook. - -Call Signature: (settings: Record) => void -*/ diff --git a/module/hooks/renderSettingsConfig.mjs b/module/hooks/renderSettingsConfig.mjs index b0ec83c..b4e28d8 100644 --- a/module/hooks/renderSettingsConfig.mjs +++ b/module/hooks/renderSettingsConfig.mjs @@ -6,19 +6,11 @@ prevent it from being as attention-grabbing compared to being at the top of the list. */ Hooks.on(`renderSettingsConfig`, (app) => { - // MARK: Hide empty menus - for (const [key, config] of game.settings.menus) { - if (!key.startsWith(__ID__)) { continue }; - if (config.type.isEmpty) { - const entry = app.element.querySelector(`.form-group:has(button[data-key="${key}"])`); - entry?.remove(); - }; - }; - - // MARK: devSettings Menu /** @type {Node | undefined} */ const settingList = app.element.querySelector(`.tab[data-group="categories"][data-tab="oft"]`); + + // MARK: devSettings Menu /** @type {Node | undefined} */ const devSettingsMenu = app.element.querySelector(`.form-group:has(button[data-key="${__ID__}.devSettings"])`); if (settingList && devSettingsMenu) { diff --git a/module/hooks/setup.mjs b/module/hooks/setup.mjs index 206867a..ae63ec6 100644 --- a/module/hooks/setup.mjs +++ b/module/hooks/setup.mjs @@ -1,15 +1,15 @@ // Settings -import { addGlobalDocReferrer } from "../tweaks/addGlobalDocReferrer.mjs"; -import { autoUnpauseOnLoad } from "../tweaks/autoUnpauseOnLoad.mjs"; -import { chatImageLinks } from "../tweaks/chatImageLinks.mjs"; -import { chatSidebarBackground } from "../tweaks/chatSidebarBackground.mjs"; -import { hotbarButtonGap } from "../tweaks/hotbarButtonGap.mjs"; -import { hotbarButtonSize } from "../tweaks/hotbarButtonSize.mjs"; -import { preventTokenRotation } from "../tweaks/preventTokenRotation.mjs"; -import { preventUserConfigOpen } from "../tweaks/preventUserConfigOpen.mjs"; -import { repositionHotbar } from "../tweaks/repositionHotbar.mjs"; -import { startingSidebarTab } from "../tweaks/startingSidebarTab.mjs"; -import { startSidebarExpanded } from "../tweaks/startSidebarExpanded.mjs"; +import { addGlobalDocReferrer } from "../settings/addGlobalDocReferrer.mjs"; +import { autoUnpauseOnLoad } from "../settings/autoUnpauseOnLoad.mjs"; +import { chatImageLinks } from "../settings/chatImageLinks.mjs"; +import { chatSidebarBackground } from "../settings/chatSidebarBackground.mjs"; +import { hotbarButtonGap } from "../settings/hotbarButtonGap.mjs"; +import { hotbarButtonSize } from "../settings/hotbarButtonSize.mjs"; +import { preventTokenRotation } from "../settings/preventTokenRotation.mjs"; +import { preventUserConfigOpen } from "../settings/preventUserConfigOpen.mjs"; +import { repositionHotbar } from "../settings/repositionHotbar.mjs"; +import { startingSidebarTab } from "../settings/startingSidebarTab.mjs"; +import { startSidebarExpanded } from "../settings/startSidebarExpanded.mjs"; // Apps import { DevSettingsMenu } from "../apps/DevSettingsMenu.mjs"; @@ -51,16 +51,7 @@ Hooks.on(`setup`, () => { preventTokenRotation(); preventUserConfigOpen(); - // Compatibility Code - if (Hooks.events[`oft.settingStatuses`] != null) { - foundry.utils.logCompatibilityWarning( - `The hook "${__ID__}.settingStatuses" has been renamed "${__ID__}.tweakStatuses".`, - { since: `v1.2.0`, until: `v2.0.0`, stack: false, once: true }, - ); - Hooks.callAll(`oft.settingStatuses`, deepFreeze(status)); - }; - - Hooks.callAll(`${__ID__}.tweakStatuses`, deepFreeze(status)); + Hooks.callAll(`oft.settingStatuses`, deepFreeze(status)); game.modules.get(__ID__).api = deepFreeze({ settings: status, }); diff --git a/module/oft.mjs b/module/oft.mjs index 12a83bf..8740d00 100644 --- a/module/oft.mjs +++ b/module/oft.mjs @@ -2,6 +2,3 @@ import "./hooks/init.mjs"; import "./hooks/setup.mjs"; import "./hooks/renderSettingsConfig.mjs"; - -// Compatibility w/ other packages -import "./conflicts/crucible.mjs"; diff --git a/module/tweaks/addGlobalDocReferrer.mjs b/module/settings/addGlobalDocReferrer.mjs similarity index 85% rename from module/tweaks/addGlobalDocReferrer.mjs rename to module/settings/addGlobalDocReferrer.mjs index 9ee5f81..d955d40 100644 --- a/module/tweaks/addGlobalDocReferrer.mjs +++ b/module/settings/addGlobalDocReferrer.mjs @@ -1,14 +1,12 @@ import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; import { __ID__ } from "../consts.mjs"; import { Logger } from "../utils/Logger.mjs"; -import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs"; import { registerDevSetting } from "../utils/SubMenuSettings.mjs"; -export const key = `addGlobalDocReferrer`; +const key = `addGlobalDocReferrer`; export function addGlobalDocReferrer() { status[key] = SettingStatusEnum.Unknown; - if (preventTweakRegistration(key)) { return }; // #region Registration Logger.log(`Registering setting: ${key}`); diff --git a/module/tweaks/autoUnpauseOnLoad.mjs b/module/settings/autoUnpauseOnLoad.mjs similarity index 79% rename from module/tweaks/autoUnpauseOnLoad.mjs rename to module/settings/autoUnpauseOnLoad.mjs index db72e4d..de15fa4 100644 --- a/module/tweaks/autoUnpauseOnLoad.mjs +++ b/module/settings/autoUnpauseOnLoad.mjs @@ -1,14 +1,19 @@ import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; import { __ID__ } from "../consts.mjs"; import { Logger } from "../utils/Logger.mjs"; -import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs"; import { registerDevSetting } from "../utils/SubMenuSettings.mjs"; -export const key = `autoUnpauseOnLoad`; +const key = `autoUnpauseOnLoad`; export function autoUnpauseOnLoad() { status[key] = SettingStatusEnum.Unknown; - if (preventTweakRegistration(key)) { return }; + + const prevented = Hooks.call(`${__ID__}.preventSetting`, key); + if (!prevented) { + Logger.log(`Preventing setting "${key}" from being registered`); + status[key] = SettingStatusEnum.Blocked; + return; + }; // #region Registration Logger.log(`Registering setting: ${key}`); diff --git a/module/settings/chatImageLinks.mjs b/module/settings/chatImageLinks.mjs new file mode 100644 index 0000000..c25d56f --- /dev/null +++ b/module/settings/chatImageLinks.mjs @@ -0,0 +1,130 @@ +import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; +import { __ID__ } from "../consts.mjs"; +import { Logger } from "../utils/Logger.mjs"; + +const { DialogV2 } = foundry.applications.api; + +const key = `chatImageLinks`; +const IMAGE_TYPES = [ + `png`, + `jpg`, + `jpeg`, + `webp`, + `svg`, +]; + +export function chatImageLinks() { + status[key] = SettingStatusEnum.Unknown; + + const prevented = Hooks.call(`${__ID__}.preventSetting`, key); + if (!prevented) { + Logger.log(`Preventing setting "${key}" from being registered`); + status[key] = SettingStatusEnum.Blocked; + return; + }; + + // #region Registration + Logger.log(`Registering setting: ${key}`); + game.settings.register(__ID__, key, { + name: `OFT.setting.${key}.name`, + hint: `OFT.setting.${key}.hint`, + scope: `user`, + type: Boolean, + default: true, + config: true, + requiresReload: true, + }); + + game.settings.register(__ID__, key + `-showPromptAgain`, { + scope: `user`, + type: Boolean, + default: true, + config: false, + }); + // #endregion Registration + + // #region Implementation + if (game.settings.get(__ID__, key)) { + Logger.log(`setting:${key} | Adding text enricher`); + + // MARK: Enricher + const pattern = new RegExp( + `(? { + Logger.debug(url); + url = url[0].replace(/^image:\/\//, ``); + const secure = `https://${url}`; + const insecure = `http://${url}`; + + if (await isAcceptableImage(secure)) { + const img = document.createElement(`img`); + img.src = secure; + img.alt = secure; + return img; + }; + + if (await isAcceptableImage(insecure)) { + const img = document.createElement(`img`); + img.src = insecure; + img.alt = insecure; + return img; + }; + + return null; + }, + }); + + // MARK: Chat Input + // Hooks.on(`chatMessage`, (chatLog, message, options) => { + // if (!game.settings.get(__ID__, key)) { return }; + + // const match = message.match(pattern); + // if (!match) { return }; + + // DialogV2.wait({ + // rejectClose: false, + // content: game.i18n.localize(`OFT.dialogs.chatImageLinks.didYouKnowImageLink`), + // buttons: [ + // { action: ``, label: `OFT.dialogs.chatImageLinks.convertAndDontShowAgain` }, + // { action: ``, label: `OFT.dialogs.chatImageLinks.justConvert` }, + // { action: ``, label: `OFT.dialogs.chatImageLinks.ignoreAndDontShowAgain` }, + // { action: ``, label: `OFT.dialogs.chatImageLinks.disableEntirely` }, + // ], + // }) + // .then((selected) => { + // chatLog.processMessage(message, options); + // }); + + // return false; + // }); + } + // #endregion Implementation + + status[key] = SettingStatusEnum.Registered; +}; + +// #region Helpers +async function isAcceptableImage(url) { + try { + const response = await fetch(url, { method: `HEAD` }); + const contentType = response.headers.get(`Content-Type`); + Logger.debug(`Image data:`, { url, contentType }); + let [ superType, subtype ] = contentType.split(`/`); + if (superType !== `image`) { + return false; + }; + if (subtype.includes(`+`)) { + subtype = subtype.split(`+`, 2).at(0); + }; + return IMAGE_TYPES.includes(subtype); + } catch { + return false; + }; +}; +// #endregion Helpers diff --git a/module/tweaks/chatSidebarBackground.mjs b/module/settings/chatSidebarBackground.mjs similarity index 85% rename from module/tweaks/chatSidebarBackground.mjs rename to module/settings/chatSidebarBackground.mjs index 8e98f55..5fce77a 100644 --- a/module/tweaks/chatSidebarBackground.mjs +++ b/module/settings/chatSidebarBackground.mjs @@ -1,13 +1,11 @@ import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; import { __ID__ } from "../consts.mjs"; import { Logger } from "../utils/Logger.mjs"; -import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs"; -export const key = `chatSidebarBackground`; +const key = `chatSidebarBackground`; export function chatSidebarBackground() { status[key] = SettingStatusEnum.Unknown; - if (preventTweakRegistration(key)) { return }; // #region Registration Logger.log(`Registering setting: ${key}`); diff --git a/module/tweaks/hotbarButtonGap.mjs b/module/settings/hotbarButtonGap.mjs similarity index 81% rename from module/tweaks/hotbarButtonGap.mjs rename to module/settings/hotbarButtonGap.mjs index 96c8239..98c7cbf 100644 --- a/module/tweaks/hotbarButtonGap.mjs +++ b/module/settings/hotbarButtonGap.mjs @@ -1,14 +1,19 @@ import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; import { __ID__ } from "../consts.mjs"; import { Logger } from "../utils/Logger.mjs"; -import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs"; import { registerCategorySetting } from "../utils/SubMenuSettings.mjs"; -export const key = `hotbarButtonGap`; +const key = `hotbarButtonGap`; export function hotbarButtonGap() { status[key] = SettingStatusEnum.Unknown; - if (preventTweakRegistration(key)) { return }; + + const prevented = Hooks.call(`${__ID__}.preventSetting`, key); + if (!prevented) { + Logger.log(`Preventing setting "${key}" from being registered`); + status[key] = SettingStatusEnum.Blocked; + return; + }; // #region Registration Logger.log(`Registering setting: ${key}`); diff --git a/module/tweaks/hotbarButtonSize.mjs b/module/settings/hotbarButtonSize.mjs similarity index 82% rename from module/tweaks/hotbarButtonSize.mjs rename to module/settings/hotbarButtonSize.mjs index 829b0bd..7b4797b 100644 --- a/module/tweaks/hotbarButtonSize.mjs +++ b/module/settings/hotbarButtonSize.mjs @@ -1,14 +1,19 @@ import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; import { __ID__ } from "../consts.mjs"; import { Logger } from "../utils/Logger.mjs"; -import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs"; import { registerCategorySetting } from "../utils/SubMenuSettings.mjs"; -export const key = `hotbarButtonSize`; +const key = `hotbarButtonSize`; export function hotbarButtonSize() { status[key] = SettingStatusEnum.Unknown; - if (preventTweakRegistration(key)) { return }; + + const prevented = Hooks.call(`${__ID__}.preventSetting`, key); + if (!prevented) { + Logger.log(`Preventing setting "${key}" from being registered`); + status[key] = SettingStatusEnum.Blocked; + return; + }; // #region Registration Logger.log(`Registering setting: ${key}`); diff --git a/module/tweaks/preventMovementHistory.mjs b/module/settings/preventMovementHistory.mjs similarity index 78% rename from module/tweaks/preventMovementHistory.mjs rename to module/settings/preventMovementHistory.mjs index 782e7f3..8f4d18c 100644 --- a/module/tweaks/preventMovementHistory.mjs +++ b/module/settings/preventMovementHistory.mjs @@ -1,13 +1,18 @@ import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; import { __ID__ } from "../consts.mjs"; import { Logger } from "../utils/Logger.mjs"; -import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs"; -export const key = `preventMovementHistory`; +const key = `preventMovementHistory`; export function preventMovementHistory() { status[key] = SettingStatusEnum.Unknown; - if (preventTweakRegistration(key, true)) { return }; + + const prevented = Hooks.call(`${__ID__}.preventSetting`, key); + if (!prevented) { + Logger.log(`Preventing setting "${key}" from being registered`); + status[key] = SettingStatusEnum.Blocked; + return; + }; // #region Registration Logger.log(`Registering setting: ${key}`); diff --git a/module/tweaks/preventTokenRotation.mjs b/module/settings/preventTokenRotation.mjs similarity index 82% rename from module/tweaks/preventTokenRotation.mjs rename to module/settings/preventTokenRotation.mjs index 53b8e3e..258920c 100644 --- a/module/tweaks/preventTokenRotation.mjs +++ b/module/settings/preventTokenRotation.mjs @@ -1,13 +1,18 @@ import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; import { __ID__ } from "../consts.mjs"; import { Logger } from "../utils/Logger.mjs"; -import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs"; -export const key = `preventTokenRotation`; +const key = `preventTokenRotation`; export function preventTokenRotation() { status[key] = SettingStatusEnum.Unknown; - if (preventTweakRegistration(key)) { return }; + + const prevented = Hooks.call(`${__ID__}.preventSetting`, key); + if (!prevented) { + Logger.log(`Preventing setting "${key}" from being registered`); + status[key] = SettingStatusEnum.Blocked; + return; + }; /** @type {number|null} */ let hookID = null; diff --git a/module/tweaks/preventUserConfigOpen.mjs b/module/settings/preventUserConfigOpen.mjs similarity index 77% rename from module/tweaks/preventUserConfigOpen.mjs rename to module/settings/preventUserConfigOpen.mjs index e13a237..3b7646d 100644 --- a/module/tweaks/preventUserConfigOpen.mjs +++ b/module/settings/preventUserConfigOpen.mjs @@ -1,13 +1,18 @@ import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; import { __ID__ } from "../consts.mjs"; import { Logger } from "../utils/Logger.mjs"; -import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs"; -export const key = `preventUserConfigOpen`; +const key = `preventUserConfigOpen`; export function preventUserConfigOpen() { status[key] = SettingStatusEnum.Unknown; - if (preventTweakRegistration(key)) { return }; + + const prevented = Hooks.call(`${__ID__}.preventSetting`, key); + if (!prevented) { + Logger.log(`Preventing setting "${key}" from being registered`); + status[key] = SettingStatusEnum.Blocked; + return; + }; // #region Registration Logger.log(`Registering setting: ${key}`); diff --git a/module/tweaks/repositionHotbar.mjs b/module/settings/repositionHotbar.mjs similarity index 85% rename from module/tweaks/repositionHotbar.mjs rename to module/settings/repositionHotbar.mjs index dd694d1..cce0c5f 100644 --- a/module/tweaks/repositionHotbar.mjs +++ b/module/settings/repositionHotbar.mjs @@ -1,14 +1,19 @@ import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; import { __ID__ } from "../consts.mjs"; import { Logger } from "../utils/Logger.mjs"; -import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs"; import { registerCategorySetting } from "../utils/SubMenuSettings.mjs"; -export const key = `repositionHotbar`; +const key = `repositionHotbar`; export function repositionHotbar() { status[key] = SettingStatusEnum.Unknown; - if (preventTweakRegistration(key)) { return }; + + const prevented = Hooks.call(`${__ID__}.preventSetting`, key); + if (!prevented) { + Logger.log(`Preventing setting "${key}" from being registered`); + status[key] = SettingStatusEnum.Blocked; + return; + }; // #region Registration Logger.log(`Registering setting: ${key}`); diff --git a/module/tweaks/startSidebarExpanded.mjs b/module/settings/startSidebarExpanded.mjs similarity index 83% rename from module/tweaks/startSidebarExpanded.mjs rename to module/settings/startSidebarExpanded.mjs index 14b39f4..4cf9f2d 100644 --- a/module/tweaks/startSidebarExpanded.mjs +++ b/module/settings/startSidebarExpanded.mjs @@ -1,13 +1,11 @@ import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; import { __ID__ } from "../consts.mjs"; import { Logger } from "../utils/Logger.mjs"; -import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs"; -export const key = `startSidebarExpanded`; +const key = `startSidebarExpanded`; export function startSidebarExpanded() { status[key] = SettingStatusEnum.Unknown; - if (preventTweakRegistration(key)) { return }; // #region Registration Logger.log(`Registering setting: ${key}`); diff --git a/module/tweaks/startingSidebarTab.mjs b/module/settings/startingSidebarTab.mjs similarity index 90% rename from module/tweaks/startingSidebarTab.mjs rename to module/settings/startingSidebarTab.mjs index aba7170..02fab17 100644 --- a/module/tweaks/startingSidebarTab.mjs +++ b/module/settings/startingSidebarTab.mjs @@ -1,13 +1,11 @@ import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; import { __ID__ } from "../consts.mjs"; import { Logger } from "../utils/Logger.mjs"; -import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs"; -export const key = `startingSidebarTab`; +const key = `startingSidebarTab`; export function startingSidebarTab() { status[key] = SettingStatusEnum.Unknown; - if (preventTweakRegistration(key)) { return }; // #region Registration Logger.log(`Registering setting: ${key}`); diff --git a/module/tweaks/chatImageLinks.mjs b/module/tweaks/chatImageLinks.mjs deleted file mode 100644 index 8d17d0b..0000000 --- a/module/tweaks/chatImageLinks.mjs +++ /dev/null @@ -1,125 +0,0 @@ -import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; -import { __ID__ } from "../consts.mjs"; -import { Logger } from "../utils/Logger.mjs"; -import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs"; - -export const key = `chatImageLinks`; -const IMAGE_TYPES = [ - `png`, - `jpg`, - `jpeg`, - `webp`, - `svg`, -]; - -export function chatImageLinks() { - status[key] = SettingStatusEnum.Unknown; - if (preventTweakRegistration(key)) { return }; - - /** @type {number|null} */ - let hookID = null; - - // #region Registration - Logger.log(`Registering setting: ${key}`); - game.settings.register(__ID__, key, { - name: `OFT.setting.${key}.name`, - hint: `OFT.setting.${key}.hint`, - scope: `user`, - type: Boolean, - default: true, - config: true, - requiresReload: false, - onChange: (newValue) => { - if (newValue) { - hookID = Hooks.on(`chatMessage`, chatMessageHandler); - } else if (hookID != null) { - Hooks.off(`chatMessage`, hookID); - }; - }, - }); - // #endregion Registration - - // #region Implementation - if (game.settings.get(__ID__, key)) { - Logger.log(`setting:${key} | Adding chat message listener`); - Hooks.on(`chatMessage`, chatMessageHandler); - }; - // #endregion Implementation - - status[key] = SettingStatusEnum.Registered; -}; - -// #region Helpers -const pattern = new RegExp( - `https?:\\/\\/\\S*\\.(?:${IMAGE_TYPES.join(`|`)})`, - `gi`, -); - -// MARK: Mutate & Resend -const handled = new Set(); -async function mutateAndResendMessage(chatLog, message, options) { - const validMatches = new Set(); - - const matches = message.match(pattern); - for (const match of matches) { - if (await isAcceptableImage(match)) { - validMatches.add(match); - }; - }; - - message = message.replaceAll( - pattern, - (url) => { - if (!validMatches.has(url)) { - return url; - }; - return `${url}`; - }, - ); - - handled.add(message); - chatLog.processMessage(message, options); -}; - -// MARK: Chat Message -/** - * Must be synchronous since it is a hook handler, but the mutation + - * resending can be done asynchronously since it doesn't matter how - * long it takes. - */ -function chatMessageHandler(chatLog, message, options) { - if (!game.settings.get(__ID__, key)) { return }; - - // Don't re-process the same message - if (handled.has(message)) { return }; - - // Prevent cancellation for non-matching messages - const match = message.match(pattern); - if (!match) { return }; - - mutateAndResendMessage(chatLog, message, options); - return false; -}; - -// MARK: isAcceptableImage -async function isAcceptableImage(url) { - if (!URL.canParse(url)) { return false }; - - try { - const response = await fetch(url, { method: `HEAD` }); - const contentType = response.headers.get(`Content-Type`); - Logger.debug(`Image data:`, { url, contentType }); - - let [ superType, subtype ] = contentType.split(`/`); - if (superType !== `image`) { - return false; - }; - if (subtype.includes(`+`)) { - subtype = subtype.split(`+`, 2).at(0); - }; - return IMAGE_TYPES.includes(subtype); - } catch { - return false; - }; -}; -// #endregion Helpers diff --git a/module/tweaks/toggleMouseBroadcast.mjs b/module/tweaks/toggleMouseBroadcast.mjs deleted file mode 100644 index 0fa123d..0000000 --- a/module/tweaks/toggleMouseBroadcast.mjs +++ /dev/null @@ -1,122 +0,0 @@ -import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs"; -import { __ID__ } from "../consts.mjs"; -import { Logger } from "../utils/Logger.mjs"; -import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs"; - -export const key = `toggleMouseBroadcast`; - -/** @type {number | null} */ -let notifID = null; - -export function toggleMouseBroadcast() { - status[key] = SettingStatusEnum.Unknown; - if (preventTweakRegistration(key, true)) { return }; - - // #region Registration - Logger.log(`Registering setting: ${key}`); - - // MARK: setting - game.settings.register(__ID__, key, { - scope: `client`, - config: false, - default: true, - }); - - // MARK: keybind - game.keybindings.register(__ID__, key, { - name: `OFT.keybindings.${key}.name`, - hint: `OFT.keybindings.${key}.hint`, - precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL, - restricted: true, - editable: [ - { key: `KeyH` }, - ], - onDown: (event) => { - if (!game.user.hasPermission(`SHOW_CURSOR`)) { return }; - event.preventDefault?.(); - const current = game.settings.get(__ID__, key); - - if (current) { - notifID = ui.notifications.warn( - `OFT.notifs.${key}.hidingCursor`, - { console: false, localize: true, permanent: true }, - ).id; - } else { - if (notifID != null) { - ui.notifications.remove(notifID); - notifID = null; - }; - ui.notifications.success( - `OFT.notifs.${key}.showingCursor`, - { console: false, localize: true }, - ); - }; - - game.settings.set(__ID__, key, !current); - - // Hide the existing cursor - game.user.broadcastActivity({ cursor: null }); - return true; - }, - }); - // #endregion Registration - - // #region Implementation - Hooks.on(`renderControlsConfig`, renderControlsConfigHandler); - Hooks.once(`ready`, readyHandler); - - class OFTControlsLayer extends CONFIG.Canvas.layers.controls.layerClass { - _onMouseMove(currentPos) { - if (!game.settings.get(__ID__, key)) { - game.user.broadcastActivity({}); - }; - super._onMouseMove(currentPos); - }; - }; - CONFIG.Canvas.layers.controls.layerClass = OFTControlsLayer; - - // #endregion Implementation - - status[key] = SettingStatusEnum.Registered; -}; - -// #region Helpers -const tabGroup = `categories`; - -/** - * Handle showing the "hiding your cursor" notification when the user - * connects to the server initially, allowing the current state to be - * correctly described to the user. - */ -function readyHandler() { - const hideCursor = !game.settings.get(__ID__, key); - const canShowCursor = game.user.hasPermission(`SHOW_CURSOR`); - if (hideCursor && canShowCursor) { - notifID = ui.notifications.warn( - `OFT.notifs.${key}.hidingCursor`, - { console: false, localize: true, permanent: true }, - ).id; - }; -}; - -/** - * Handles hiding the keybinding from the configuration if and when the user - * does not have the SHOW_CURSOR permission since this keybinding is really - * useless if they can't even broadcast their cursor in the first place. - */ -function renderControlsConfigHandler(_app, element) { - - if (game.user.hasPermission(`SHOW_CURSOR`)) { return }; - - const keybindingList = element.querySelector(`section[data-group="${tabGroup}"][data-tab="${__ID__}"]`); - const tabButton = element.querySelector(`button[data-group="${tabGroup}"][data-tab="${__ID__}"]`); - - const keybind = keybindingList.querySelector(`.form-group[data-action-id="${__ID__}.${key}"]`); - keybind?.remove(); - - if (keybindingList.childElementCount === 0) { - keybindingList.remove(); - tabButton.remove(); - }; -}; -// #endregion Helpers diff --git a/module/utils/preRegisterTweak.mjs b/module/utils/preRegisterTweak.mjs deleted file mode 100644 index f97bebb..0000000 --- a/module/utils/preRegisterTweak.mjs +++ /dev/null @@ -1,26 +0,0 @@ -import { SettingStatusEnum, status } from "./SettingStatus.mjs"; -import { __ID__ } from "../consts.mjs"; -import { Logger } from "./Logger.mjs"; - -export function preventTweakRegistration(key, invasive = false) { - let prevented = Hooks.call(`${__ID__}.preRegisterTweak`, key, invasive); - - // Compatibility Code - if (Hooks.events[`${__ID__}.preventSetting`] != null) { - foundry.utils.logCompatibilityWarning( - `The hook "${__ID__}.preventSetting" has been renamed "${__ID__}.registerTweak".`, - { since: `v1.2.0`, until: `v2.0.0`, stack: false, once: true }, - ); - if (prevented !== false) { - prevented = Hooks.call(`${__ID__}.preventSetting`, key); - }; - }; - - if (!prevented) { - Logger.log(`Preventing setting "${key}" from being registered`); - status[key] = SettingStatusEnum.Blocked; - return true; - }; - - return false; -}; diff --git a/oft.lock b/oft.lock deleted file mode 100644 index 82ef623..0000000 --- a/oft.lock +++ /dev/null @@ -1 +0,0 @@ -🔒 \ No newline at end of file diff --git a/styles/main.css b/styles/main.css index 1d0fec5..2312044 100644 --- a/styles/main.css +++ b/styles/main.css @@ -6,4 +6,4 @@ @import url("./apps.css"); /* Make the chat sidebar the same width as all the other tabs */ -.chat-sidebar:not(.sidebar-popout) { width: var(--sidebar-width); } +.chat-sidebar:not(.sidebar-popout) { width: unset; }