Compat FVTT v13 - adding namespaces

This commit is contained in:
Vlyan
2025-05-01 14:41:43 +02:00
parent 95fa36d7a8
commit 897ccefd71
19 changed files with 87 additions and 71 deletions

View File

@@ -6,6 +6,11 @@ Date format : day/month/year
> - `foundry-version`: Stick to the major version of FoundryVTT.
> - `system-version`: System functionalities and Fixes.
## 1.13.0 - ??/??/2025 - Foundry v13 Compatibility
__! Be certain to carefully back up any critical user data before installing this update !__
- Updated the System to FoundryVTT v13.
- Fix Compendium Typo : "Beseech Hida's MIght" -> "Beseech Hida's Might" (!59).
## 1.12.3 - 13/03/2025 - Fixes and Compendiums Filters (Thx to Litasa)
- Added Compendiums Filters (#41)
- Separated the reference and the page number from book reference for filtering purpose.

View File

@@ -237,7 +237,7 @@ export class ActorL5r5e extends Actor {
*/
async renderTextTemplate() {
const sheetData = (await this.sheet?.getData()) || this;
const tpl = await renderTemplate(`${CONFIG.l5r5e.paths.templates}actors/actor-text.html`, sheetData);
const tpl = await foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}actors/actor-text.html`, sheetData);
if (!tpl) {
return null;
}

View File

@@ -55,15 +55,15 @@ export class ArmySheetL5r5e extends BaseSheetL5r5e {
*/
_createDragDropHandlers() {
return [
new DragDrop({
new foundry.applications.ux.DragDrop.implementation({
dropSelector: ".warlord",
callbacks: { drop: this._onDropActors.bind(this, "warlord") },
}),
new DragDrop({
new foundry.applications.ux.DragDrop.implementation({
dropSelector: ".commander",
callbacks: { drop: this._onDropActors.bind(this, "commander") },
}),
new DragDrop({
new foundry.applications.ux.DragDrop.implementation({
dropSelector: null,
callbacks: { drop: this._onDrop.bind(this) },
}),
@@ -120,7 +120,7 @@ export class ArmySheetL5r5e extends BaseSheetL5r5e {
// Editors enrichment
for (const name of ["army_abilities", "supplies_logistics", "past_battles"]) {
sheetData.data.enrichedHtml[name] = await TextEditor.enrichHTML(sheetData.data.system[name], {
sheetData.data.enrichedHtml[name] = await foundry.applications.ux.TextEditor.implementation.enrichHTML(sheetData.data.system[name], {
async: true,
});
}

View File

@@ -1,7 +1,7 @@
/**
* Base Sheet for Actor and Npc
*/
export class BaseSheetL5r5e extends ActorSheet {
export class BaseSheetL5r5e extends foundry.appv1.sheets.ActorSheet {
/**
* Commons options
*/
@@ -81,8 +81,8 @@ export class BaseSheetL5r5e extends ActorSheet {
// Editors enrichment
sheetData.data.enrichedHtml = {
description: await TextEditor.enrichHTML(sheetData.data.system.description, { async: true }),
notes: await TextEditor.enrichHTML(sheetData.data.system.notes, { async: true }),
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(sheetData.data.system.description, { async: true }),
notes: await foundry.applications.ux.TextEditor.implementation.enrichHTML(sheetData.data.system.notes, { async: true }),
};
// Shortcut for some tests

View File

@@ -101,19 +101,19 @@ export class TwentyQuestionsDialog extends FormApplication {
*/
_createDragDropHandlers() {
return [
new DragDrop({
new foundry.applications.ux.DragDrop.implementation({
dragSelector: ".item",
dropSelector: ".items",
permissions: { dragstart: this.isEditable, drop: this.isEditable },
callbacks: { dragstart: this._onDragStart.bind(this), drop: this._onDropItem.bind(this, "item") },
}),
new DragDrop({
new foundry.applications.ux.DragDrop.implementation({
dragSelector: ".technique",
dropSelector: ".techniques",
permissions: { dragstart: this.isEditable, drop: this.isEditable },
callbacks: { dragstart: this._onDragStart.bind(this), drop: this._onDropItem.bind(this, "technique") },
}),
new DragDrop({
new foundry.applications.ux.DragDrop.implementation({
dragSelector: ".peculiarity",
dropSelector: ".peculiarities",
permissions: { dragstart: this.isEditable, drop: this.isEditable },
@@ -122,7 +122,7 @@ export class TwentyQuestionsDialog extends FormApplication {
drop: this._onDropItem.bind(this, "peculiarity"),
},
}),
new DragDrop({
new foundry.applications.ux.DragDrop.implementation({
dragSelector: ".bond",
dropSelector: ".bonds",
permissions: { dragstart: this.isEditable, drop: this.isEditable },

View File

@@ -193,7 +193,7 @@ export class RollnKeepDialog extends FormApplication {
*/
_createDragDropHandlers() {
return [
new DragDrop({
new foundry.applications.ux.DragDrop.implementation({
dragSelector: ".dice.draggable",
dropSelector: ".dropbox",
permissions: { dragstart: this.isEditable, drop: this.isEditable },
@@ -259,13 +259,13 @@ export class RollnKeepDialog extends FormApplication {
// GM Only, need to be before the editable check
if (game.user.isGM && this.object.currentStep > 0) {
// Add Context menu to rollback choices
new ContextMenu(html, ".l5r5e.profil", [
new foundry.applications.ux.ContextMenu.implementation(html[0], ".l5r5e.profil", [
{
name: game.i18n.localize("l5r5e.dice.roll_n_keep.undo"),
icon: '<i class="fas fa-undo"></i>',
callback: () => this._undoLastStepChoices(),
},
]);
], { jQuery: false });
}
// *** Everything below here is only needed if the sheet is editable ***

View File

@@ -257,7 +257,7 @@ export class RollL5r5e extends Roll {
displaySummary: contexte?.from !== "render",
};
return renderTemplate(CONFIG.l5r5e.paths.templates + this.constructor.TOOLTIP_TEMPLATE, { chatData });
return foundry.applications.handlebars.renderTemplate(CONFIG.l5r5e.paths.templates + this.constructor.TOOLTIP_TEMPLATE, { chatData });
}
/**
@@ -319,7 +319,7 @@ export class RollL5r5e extends Roll {
};
// Render the roll display template
return renderTemplate(chatOptions.template, chatData);
return foundry.applications.handlebars.renderTemplate(chatOptions.template, chatData);
}
/**

View File

@@ -124,10 +124,10 @@ export class GmMonitor extends HandlebarsApplicationMixin(ApplicationV2) {
async _onRender(context, options) {
await super._onRender(context, options);
// Todo: Move this to common l5r5e application v2
// Todo: Move this to common l5r5e application v2
game.l5r5e.HelpersL5r5e.commonListeners($(this.element));
this.#dragDrop = new DragDrop({
this.#dragDrop = new foundry.applications.ux.DragDrop.implementation({
dragSelector: null,
dropSelector: null,
callbacks: {
@@ -176,13 +176,13 @@ export class GmMonitor extends HandlebarsApplicationMixin(ApplicationV2) {
* @param {string} partId The part being rendered
* @param {ApplicationRenderContext} context Shared context provided by _prepareContext
* @returns {Promise<ApplicationRenderContext>} Context data for a specific part
*
*
* @override HandlebarsApplicationMixin
*/
async _preparePartContext(partId, context) {
switch (partId) {
case "character":
context.characters = this.context.actors.filter((actor) => !actor.isArmy);
context.characters = this.context.actors.filter((actor) => !actor.isArmy);
break;
case "army":
context.armies = this.context.actors.filter((actor) => actor.isArmy);
@@ -343,7 +343,7 @@ export class GmMonitor extends HandlebarsApplicationMixin(ApplicationV2) {
case 1: //Middle click
return 0;
case 2: //Right click
return Math.max(0, baseValue - 1);
return Math.max(0, baseValue - 1);
}
}
@@ -459,12 +459,12 @@ export class GmMonitor extends HandlebarsApplicationMixin(ApplicationV2) {
return;
}
const fatigue = actor.system.fatigue.value;
const fatigue = actor.system.fatigue.value;
return actor.update({
system: {
fatigue: {
value: GmMonitor.#newValue(fatigue, event.button)
}
}
}
});
}
@@ -479,7 +479,7 @@ export class GmMonitor extends HandlebarsApplicationMixin(ApplicationV2) {
return;
}
const strife = actor.system.strife.value;
const strife = actor.system.strife.value;
return actor.update({
system: {
strife: {
@@ -499,7 +499,7 @@ export class GmMonitor extends HandlebarsApplicationMixin(ApplicationV2) {
return;
}
const void_points = actor.system.void_points.value;
const void_points = actor.system.void_points.value;
const void_points_max = actor.system.void_points.max;
return actor.update({
system: {
@@ -545,7 +545,7 @@ export class GmMonitor extends HandlebarsApplicationMixin(ApplicationV2) {
);
// *** Template ***
return renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor/tooltips/armors.html`, {
return foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor/tooltips/armors.html`, {
armors,
});
}
@@ -579,7 +579,7 @@ export class GmMonitor extends HandlebarsApplicationMixin(ApplicationV2) {
.map((weapon) => display(weapon));
// *** Template ***
return renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor/tooltips/weapons.html`, {
return foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor/tooltips/weapons.html`, {
readied,
sheathed,
});
@@ -606,7 +606,7 @@ export class GmMonitor extends HandlebarsApplicationMixin(ApplicationV2) {
.join(", ");
// *** Template ***
return renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor/tooltips/global.html`, {
return foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor/tooltips/global.html`, {
actorData: actorData,
advantages: advantages,
disadvantages: disadvantages,
@@ -625,7 +625,7 @@ export class GmMonitor extends HandlebarsApplicationMixin(ApplicationV2) {
const actorData = (await actor.sheet?.getData()?.data) || actor;
// *** Template ***
return renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor/tooltips/global-armies.html`, {
return foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor/tooltips/global-armies.html`, {
actorData: actorData,
});
}

View File

@@ -382,7 +382,7 @@ export class HelpersL5r5e {
const title = game.i18n.format("DOCUMENT.Create", { type: game.i18n.localize("Item") });
// Render the template
const html = await renderTemplate(`${CONFIG.l5r5e.paths.templates}dialogs/choose-item-type-dialog.html`, {
const html = await foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}dialogs/choose-item-type-dialog.html`, {
type: null,
types: types.reduce((obj, t) => {
const label = CONFIG.Item.typeLabels[t] ?? t;

View File

@@ -78,6 +78,8 @@ export default class HooksL5r5e {
* SidebarTab
*/
static renderSidebarTab(app, html, data) {
html = $(html); // basic patch for v13
switch (app.tabName) {
case "chat":
// Add DP on dice icon
@@ -131,6 +133,8 @@ export default class HooksL5r5e {
* Chat Message
*/
static renderChatMessage(message, html, data) {
html = $(html); // basic patch for v13
if (message.isRoll) {
// Add an extra CSS class to roll
html.addClass("roll");
@@ -174,6 +178,8 @@ export default class HooksL5r5e {
return;
}
html = $(html); // basic patch for v13
// *** Conf ***
const encounterTypeList = Object.keys(CONFIG.l5r5e.initiativeSkills);
const prepared = {
@@ -183,7 +189,7 @@ export default class HooksL5r5e {
};
// *** Template ***
const tpl = await renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/combat-tracker-bar.html`, {
const tpl = await foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/combat-tracker-bar.html`, {
encounterType: game.settings.get(CONFIG.l5r5e.namespace, "initiative-encounter"),
encounterTypeList,
prepared,
@@ -229,6 +235,8 @@ export default class HooksL5r5e {
* Compendium display (Add filters)
*/
static async renderCompendium(app, html, data) {
html = $(html); // basic patch for v13
if (app.collection.documentName === "Item") {
const content = await app.collection.getDocuments();
const sourcesInThisCompendium = new Set([]);
@@ -289,7 +297,7 @@ export default class HooksL5r5e {
// Add ring/rank/rarity information on the item in the compendium view
if (document.system?.ring || document.system?.rarity || document.system?.rank) {
const ringRarityRank = await renderTemplate(`${CONFIG.l5r5e.paths.templates}compendium/ring-rarity-rank.html`, document.system);
const ringRarityRank = await foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}compendium/ring-rarity-rank.html`, document.system);
entry.append(ringRarityRank);
}
}
@@ -372,7 +380,7 @@ export default class HooksL5r5e {
if (!filtersToShow[filterType]) {
return;
}
const filterTemplate = await renderTemplate(
const filterTemplate = await foundry.applications.handlebars.renderTemplate(
`${CONFIG.l5r5e.paths.templates}compendium/${templateFile}.html`,
templateData
);

View File

@@ -177,7 +177,7 @@ export class ItemL5r5e extends Item {
await game.l5r5e.HelpersL5r5e.refreshItemProperties(this);
}
const type = this.type.replace("_", "-"); // ex: item_pattern
const tpl = await renderTemplate(`${CONFIG.l5r5e.paths.templates}items/${type}/${type}-text.html`, sheetData);
const tpl = await foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}items/${type}/${type}-text.html`, sheetData);
if (!tpl) {
return null;
}

View File

@@ -45,7 +45,7 @@ export class ArmyCohortSheetL5r5e extends ItemSheetL5r5e {
const sheetData = await super.getData(options);
// Editors enrichment
sheetData.data.enrichedHtml.abilities = await TextEditor.enrichHTML(sheetData.data.system.abilities, {
sheetData.data.enrichedHtml.abilities = await foundry.applications.ux.TextEditor.implementation.enrichHTML(sheetData.data.system.abilities, {
async: true,
});

View File

@@ -2,7 +2,7 @@
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class BaseItemSheetL5r5e extends ItemSheet {
export class BaseItemSheetL5r5e extends foundry.appv1.sheets.ItemSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {

View File

@@ -26,7 +26,7 @@ export class ItemSheetL5r5e extends BaseItemSheetL5r5e {
// Editors enrichment
sheetData.data.enrichedHtml = {
description: await TextEditor.enrichHTML(sheetData.data.system.description, { async: true }),
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(sheetData.data.system.description, { async: true }),
};
return sheetData;
@@ -87,7 +87,7 @@ export class ItemSheetL5r5e extends BaseItemSheetL5r5e {
// "this.isEditable" fail for tooltips (undefined "this.document")
const isEditable = this.options.editable;
return [
new DragDrop({
new foundry.applications.ux.DragDrop.implementation({
dragSelector: ".property",
dropSelector: null,
permissions: { dragstart: isEditable, drop: isEditable },

View File

@@ -10,7 +10,7 @@ export class JournalL5r5e extends JournalEntry {
const data = (await this.sheet?.getData()) || this;
const pageData = data.pages[0];
const tpl = await renderTemplate(`${CONFIG.l5r5e.paths.templates}journal/journal-text.html`, {
const tpl = await foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}journal/journal-text.html`, {
data: pageData,
});
return tpl || null;

View File

@@ -2,7 +2,7 @@
* Base JournalSheet for L5R5e
* @extends {JournalSheet}
*/
export class BaseJournalSheetL5r5e extends JournalSheet {
export class BaseJournalSheetL5r5e extends foundry.appv1.sheets.JournalSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {

View File

@@ -111,107 +111,110 @@ Hooks.once("init", async () => {
PreloadTemplates().then(() => {});
// ***** Register custom sheets *****
const fdc = foundry.documents.collections;
const fav1s = foundry.appv1.sheets;
// Actors
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet(L5R5E.namespace, CharacterSheetL5r5e, {
fdc.Actors.unregisterSheet("core", fav1s.ActorSheet);
fdc.Actors.registerSheet(L5R5E.namespace, CharacterSheetL5r5e, {
types: ["character"],
label: "TYPES.Actor.character",
makeDefault: true,
});
Actors.registerSheet(L5R5E.namespace, NpcSheetL5r5e, {
fdc.Actors.registerSheet(L5R5E.namespace, NpcSheetL5r5e, {
types: ["npc"],
label: "TYPES.Actor.npc",
makeDefault: true,
});
Actors.registerSheet(L5R5E.namespace, ArmySheetL5r5e, {
fdc.Actors.registerSheet(L5R5E.namespace, ArmySheetL5r5e, {
types: ["army"],
label: "TYPES.Actor.army",
makeDefault: true,
});
// Items
Items.unregisterSheet("core", ItemSheet);
Items.registerSheet(L5R5E.namespace, ItemSheetL5r5e, {
fdc.Items.unregisterSheet("core", fav1s.ItemSheet);
fdc.Items.registerSheet(L5R5E.namespace, ItemSheetL5r5e, {
types: ["item"],
label: "TYPES.Item.item",
makeDefault: true,
});
Items.registerSheet(L5R5E.namespace, ArmorSheetL5r5e, {
fdc.Items.registerSheet(L5R5E.namespace, ArmorSheetL5r5e, {
types: ["armor"],
label: "TYPES.Item.armor",
makeDefault: true,
});
Items.registerSheet(L5R5E.namespace, WeaponSheetL5r5e, {
fdc.Items.registerSheet(L5R5E.namespace, WeaponSheetL5r5e, {
types: ["weapon"],
label: "TYPES.Item.weapon",
makeDefault: true,
});
Items.registerSheet(L5R5E.namespace, TechniqueSheetL5r5e, {
fdc.Items.registerSheet(L5R5E.namespace, TechniqueSheetL5r5e, {
types: ["technique"],
label: "TYPES.Item.technique",
makeDefault: true,
});
Items.registerSheet(L5R5E.namespace, PropertySheetL5r5e, {
fdc.Items.registerSheet(L5R5E.namespace, PropertySheetL5r5e, {
types: ["property"],
label: "TYPES.Item.property",
makeDefault: true,
});
Items.registerSheet(L5R5E.namespace, PeculiaritySheetL5r5e, {
fdc.Items.registerSheet(L5R5E.namespace, PeculiaritySheetL5r5e, {
types: ["peculiarity"],
label: "TYPES.Item.peculiarity",
makeDefault: true,
});
Items.registerSheet(L5R5E.namespace, AdvancementSheetL5r5e, {
fdc.Items.registerSheet(L5R5E.namespace, AdvancementSheetL5r5e, {
types: ["advancement"],
label: "TYPES.Item.advancement",
makeDefault: true,
});
Items.registerSheet(L5R5E.namespace, TitleSheetL5r5e, {
fdc.Items.registerSheet(L5R5E.namespace, TitleSheetL5r5e, {
types: ["title"],
label: "TYPES.Item.title",
makeDefault: true,
});
Items.registerSheet(L5R5E.namespace, BondSheetL5r5e, {
fdc.Items.registerSheet(L5R5E.namespace, BondSheetL5r5e, {
types: ["bond"],
label: "TYPES.Item.bond",
makeDefault: true,
});
Items.registerSheet(L5R5E.namespace, SignatureScrollSheetL5r5e, {
fdc.Items.registerSheet(L5R5E.namespace, SignatureScrollSheetL5r5e, {
types: ["signature_scroll"],
label: "TYPES.Item.signature_scroll",
makeDefault: true,
});
Items.registerSheet(L5R5E.namespace, ItemPatternSheetL5r5e, {
fdc.Items.registerSheet(L5R5E.namespace, ItemPatternSheetL5r5e, {
types: ["item_pattern"],
label: "TYPES.Item.item_pattern",
makeDefault: true,
});
Items.registerSheet(L5R5E.namespace, ArmyCohortSheetL5r5e, {
fdc.Items.registerSheet(L5R5E.namespace, ArmyCohortSheetL5r5e, {
types: ["army_cohort"],
label: "TYPES.Item.army_cohort",
makeDefault: true,
});
Items.registerSheet(L5R5E.namespace, ArmyFortificationSheetL5r5e, {
fdc.Items.registerSheet(L5R5E.namespace, ArmyFortificationSheetL5r5e, {
types: ["army_fortification"],
label: "TYPES.Item.army_fortification",
makeDefault: true,
});
// Journal
Journal.unregisterSheet("core", JournalSheet);
Journal.registerSheet(L5R5E.namespace, BaseJournalSheetL5r5e, {
fdc.Journal.unregisterSheet("core", fav1s.JournalSheet);
fdc.Journal.registerSheet(L5R5E.namespace, BaseJournalSheetL5r5e, {
label: "TYPES.Journal.journal",
makeDefault: true,
});
// Override enrichHTML for Symbol replacement
const oldEnrichHTML = TextEditor.prototype.constructor.enrichHTML;
TextEditor.prototype.constructor.enrichHTML = async function (content, options = {}) {
const oldEnrichHTML = foundry.applications.ux.TextEditor.implementation.prototype.constructor.enrichHTML;
foundry.applications.ux.TextEditor.implementation.prototype.constructor.enrichHTML = async function (content, options = {}) {
return HelpersL5r5e.convertSymbols(await oldEnrichHTML.call(this, content, options), true);
};
// Override the default Token _drawBar function to allow fatigue bar reversing.
Token.prototype._drawBar = function (number, bar, data) {
foundry.canvas.placeables.Token.prototype._drawBar = function (number, bar, data) {
const barSettings = game.settings.get(L5R5E.namespace, "token-reverse-token-bars");
const reverseBar = barSettings === 'both' || barSettings === data.attribute;
@@ -257,7 +260,7 @@ Hooks.once("diceSoNiceReady", (dice3d) => HooksL5r5e.diceSoNiceReady(dice3d));
/* Hooks On */
/* ------------------------------------ */
Hooks.on("renderSidebarTab", (app, html, data) => HooksL5r5e.renderSidebarTab(app, html, data));
Hooks.on("renderChatMessage", (message, html, data) => HooksL5r5e.renderChatMessage(message, html, data));
Hooks.on("renderChatMessageHTML", (message, html, data) => HooksL5r5e.renderChatMessage(message, html, data));
Hooks.on("renderCombatTracker", (app, html, data) => HooksL5r5e.renderCombatTracker(app, html, data));
Hooks.on("renderCompendium", async (app, html, data) => HooksL5r5e.renderCompendium(app, html, data));
Hooks.on("diceSoNiceRollStart", (messageId, context) => HooksL5r5e.diceSoNiceRollStart(messageId, context));

View File

@@ -1,6 +1,6 @@
export const PreloadTemplates = async function () {
const tpl = CONFIG.l5r5e.paths.templates;
return loadTemplates([
return foundry.applications.handlebars.loadTemplates([
// Add paths to "systems/l5r5e/templates"
// *** Actors : PC ***
`${tpl}actors/character/advancement-school.html`,

View File

@@ -7,12 +7,12 @@
"changelog": "https://gitlab.com/teaml5r/l5r5e/-/blob/master/CHANGELOG.md",
"license": "https://gitlab.com/teaml5r/l5r5e/-/blob/master/LICENSE.md",
"manifest": "https://gitlab.com/teaml5r/l5r5e/-/raw/master/system/system.json",
"download": "https://gitlab.com/teaml5r/l5r5e/-/jobs/artifacts/v1.12.3/raw/l5r5e.zip?job=build",
"version": "1.12.3",
"download": "https://gitlab.com/teaml5r/l5r5e/-/jobs/artifacts/v1.13.0/raw/l5r5e.zip?job=build",
"version": "1.13.0",
"compatibility": {
"minimum": "12",
"verified": "12",
"maximum": "12"
"minimum": "13",
"verified": "13",
"maximum": "13"
},
"socket": true,
"authors": [