From 69db3ca7196e5f625e7b02672ff9b4aae54e10b3 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Mon, 10 Feb 2025 23:04:28 -0700 Subject: [PATCH] Add the ability to display / edit the description from the item sheet --- langs/en-ca.json | 4 +- module/Apps/GenericApp.mjs | 32 +++++++++++++- module/Apps/ItemSheets/AllItemSheetV1.mjs | 4 +- module/Apps/RichEditor.mjs | 5 --- module/consts.mjs | 20 +++++++++ module/data/Item/Craft.mjs | 11 ++++- module/data/Item/Skill.mjs | 7 +-- module/handlebarHelpers/inputs/formFields.mjs | 7 ++- .../inputs/prosemirrorInput.mjs | 43 +++++++++++++++++++ templates/Apps/AllItemSheetV1/style.css | 36 +++++++++++++++- templates/Apps/RichEditor/content.hbs | 5 +++ templates/css/common.css | 1 + templates/css/elements/p.css | 10 +++++ 13 files changed, 168 insertions(+), 17 deletions(-) create mode 100644 module/handlebarHelpers/inputs/prosemirrorInput.mjs create mode 100644 templates/css/elements/p.css diff --git a/langs/en-ca.json b/langs/en-ca.json index cd97cdc..e1f0484 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -56,6 +56,7 @@ }, "damage": "Damage", "delete": "Delete", + "description": "Description", "difficulties": { "easy": "Easy", "normal": "Normal", @@ -128,7 +129,8 @@ "location-placeholder": "New Location...", "numberOfDice": "# of Dice", "rollTarget": "Target", - "difficulty": "(DC: {dc})" + "difficulty": "(DC: {dc})", + "RichEditor-no-collaborative": "Warning: This editor is not collaborative, that means that if you and someone else are editing it at the same time, you won't see that someone else is making changes until they save, and then your changes will be lost." }, "notifs": { "error": { diff --git a/module/Apps/GenericApp.mjs b/module/Apps/GenericApp.mjs index 05efced..2c209ce 100644 --- a/module/Apps/GenericApp.mjs +++ b/module/Apps/GenericApp.mjs @@ -1,5 +1,7 @@ import { deleteItemFromElement, editItemFromElement } from "./utils.mjs"; import { DicePool } from "./DicePool.mjs"; +import { RichEditor } from "./RichEditor.mjs"; +import { toBoolean } from "../consts.mjs"; /** * A mixin that takes the class from HandlebarsApplicationMixin and @@ -16,6 +18,7 @@ export function GenericAppMixin(HandlebarsApp) { roll: this._rollDice, editItem: (_event, target) => editItemFromElement(target), deleteItem: (_event, target) => deleteItemFromElement(target), + openRichEditor: this.#openRichEditor, }, }; @@ -57,14 +60,39 @@ export function GenericAppMixin(HandlebarsApp) { // #region Actions /** @this {GenericRipCryptApp} */ - static async _rollDice(_$e, el) { - const data = el.dataset; + static async _rollDice(_event, target) { + const data = target.dataset; const diceCount = parseInt(data.diceCount); const flavor = data.flavor; const dp = new DicePool({ diceCount, flavor }); dp.render({ force: true }); }; + + /** @this {GenericRipCryptApp} */ + static async #openRichEditor(_event, target) { + const data = target.dataset; + const { + uuid, + path, + collaborative, + compact, + } = data; + + if (!uuid || !path) { + console.error(`Rich Editor requires a document uuid and path to edit`); + return; + }; + + const document = await fromUuid(uuid); + const app = new RichEditor({ + document, + path, + collaborative: toBoolean(collaborative), + compact: toBoolean(compact ), + }); + app.render({ force: true }); + }; // #endregion }; return GenericRipCryptApp; diff --git a/module/Apps/ItemSheets/AllItemSheetV1.mjs b/module/Apps/ItemSheets/AllItemSheetV1.mjs index 900cd35..0744e57 100644 --- a/module/Apps/ItemSheets/AllItemSheetV1.mjs +++ b/module/Apps/ItemSheets/AllItemSheetV1.mjs @@ -20,8 +20,6 @@ export class AllItemSheetV1 extends GenericAppMixin(HandlebarsApplicationMixin(I window: { resizable: false, }, - actions: { - }, form: { submitOnChange: true, closeOnSubmit: false, @@ -40,7 +38,7 @@ export class AllItemSheetV1 extends GenericAppMixin(HandlebarsApplicationMixin(I ctx = await super._preparePartContext(partId, ctx, opts); ctx.item = this.document; - ctx.formFields = this.document.system.getFormFields(ctx); + ctx.formFields = await this.document.system.getFormFields(ctx); Logger.debug(`Context:`, ctx); return ctx; diff --git a/module/Apps/RichEditor.mjs b/module/Apps/RichEditor.mjs index 87c07dc..2bd1bc4 100644 --- a/module/Apps/RichEditor.mjs +++ b/module/Apps/RichEditor.mjs @@ -79,11 +79,6 @@ export class RichEditor extends HandlebarsApplicationMixin(DocumentSheetV2) { path: this.path, }; - console.log({ - doc: this.document, - path: this.path, - value: this.document.system.description, - }); const value = getProperty(this.document, this.path); ctx.enriched = await TextEditor.enrichHTML(value); ctx.raw = value; diff --git a/module/consts.mjs b/module/consts.mjs index b8ec178..84d8740 100644 --- a/module/consts.mjs +++ b/module/consts.mjs @@ -1,3 +1,5 @@ +const { getType } = foundry.utils; + // MARK: filePath export function filePath(path) { if (path.startsWith(`/`)) { @@ -6,6 +8,24 @@ export function filePath(path) { return `systems/ripcrypt/${path}`; }; +// MARK: toBoolean +/** + * Converts a value into a boolean based on the type of the value provided + * + * @param {any} val The value to convert + */ +export function toBoolean(val) { + switch (getType(val)) { + case `string`: { + return val === `true`; + }; + case `number`: { + return val === 1; + }; + }; + return Boolean(val); +}; + // MARK: documentSorter /** * @typedef {Object} Sortable diff --git a/module/data/Item/Craft.mjs b/module/data/Item/Craft.mjs index 6a677a7..23aa9ba 100644 --- a/module/data/Item/Craft.mjs +++ b/module/data/Item/Craft.mjs @@ -35,7 +35,7 @@ export class CraftData extends SkillData { // #endregion // #region Sheet Data - getFormFields(_ctx) { + async getFormFields(_ctx) { const fields = [ { id: `fate-path`, @@ -48,6 +48,15 @@ export class CraftData extends SkillData { value: aspect, })), }, + { + id: `description`, + type: `prosemirror`, + label: `RipCrypt.common.description`, + path: `system.description`, + uuid: this.parent.uuid, + value: await TextEditor.enrichHTML(this.description), + collaborative: false, + }, { type: `group`, title: `RipCrypt.common.advances`, diff --git a/module/data/Item/Skill.mjs b/module/data/Item/Skill.mjs index 11c5562..520a344 100644 --- a/module/data/Item/Skill.mjs +++ b/module/data/Item/Skill.mjs @@ -48,7 +48,7 @@ export class SkillData extends foundry.abstract.TypeDataModel { // #endregion // #region Sheet Data - getFormFields(_ctx) { + async getFormFields(_ctx) { const fields = [ { id: `fate-path`, @@ -62,12 +62,13 @@ export class SkillData extends foundry.abstract.TypeDataModel { })), }, { - // TODO: Figure out how tf to make this work nicely on a generic level id: `description`, type: `prosemirror`, label: `RipCrypt.common.description`, path: `system.description`, - collaborative: true, + uuid: this.parent.uuid, + value: await TextEditor.enrichHTML(this.description), + collaborative: false, }, { type: `group`, diff --git a/module/handlebarHelpers/inputs/formFields.mjs b/module/handlebarHelpers/inputs/formFields.mjs index bf97126..518b054 100644 --- a/module/handlebarHelpers/inputs/formFields.mjs +++ b/module/handlebarHelpers/inputs/formFields.mjs @@ -3,6 +3,7 @@ import { booleanInput } from "./booleanInput.mjs"; import { dropdownInput } from "./dropdownInput.mjs"; import { groupInput } from "./groupInput.mjs"; import { numberInput } from "./numberInput.mjs"; +import { prosemirrorInput } from "./prosemirrorInput.mjs"; import { stringSet } from "./stringSet.mjs"; import { textInput } from "./textInput.mjs"; @@ -10,6 +11,7 @@ const { getType } = foundry.utils; const inputTypes = { "string-set": stringSet, + prosemirror: prosemirrorInput, integer: numberInput, bar: barInput, dropdown: dropdownInput, @@ -29,7 +31,10 @@ export function formFields(inputs, opts) { input.limited ??= true; }; - if (typesToSanitize.has(getType(input.value))) { + if ( + input.type !== `prosemirror` + && typesToSanitize.has(getType(input.value)) + ) { input.value = Handlebars.escapeExpression(input.value); }; fields.push(inputTypes[input.type](input, opts.data.root)); diff --git a/module/handlebarHelpers/inputs/prosemirrorInput.mjs b/module/handlebarHelpers/inputs/prosemirrorInput.mjs new file mode 100644 index 0000000..df84e6c --- /dev/null +++ b/module/handlebarHelpers/inputs/prosemirrorInput.mjs @@ -0,0 +1,43 @@ +import { localizer } from "../../utils/Localizer.mjs"; + +export function prosemirrorInput(input, data) { + const label = localizer(input.label); + + if (!data.meta.editable) { + return `
+
+
+ ${label} +
+
+
+ ${input.value} +
+
`; + }; + + return `
+
+
+ ${label} +
+ +
+ + +
${input.value}
+
`; +}; diff --git a/templates/Apps/AllItemSheetV1/style.css b/templates/Apps/AllItemSheetV1/style.css index fd168c9..acac966 100644 --- a/templates/Apps/AllItemSheetV1/style.css +++ b/templates/Apps/AllItemSheetV1/style.css @@ -13,6 +13,8 @@ --input-text: white; --input-background: var(--accent-2); + --button-text: white; + --button-background: var(--accent-2); --pill-width: 100%; --pill-border-radius: 4px; @@ -21,6 +23,7 @@ grid-template-columns: auto 200px; column-gap: var(--col-gap); row-gap: var(--row-gap); + max-width: 300px; padding: 8px; background: var(--base-background); @@ -29,6 +32,7 @@ [data-input-type] { display: contents; } + > [data-input-type="group"] { display: unset; grid-column: 1 / -1; @@ -41,6 +45,36 @@ } } + > [data-input-type="prose-mirror"] { + grid-column: 1 / -1; + display: flex; + flex-direction: column; + gap: var(--row-gap); + + > .label-row { + display: flex; + flex-direction: row; + width: 100%; + justify-content: space-between; + } + + .value { + background: var(--input-background); + color: var(--input-text); + + > :first-child { + margin-top: 0; + } + > :last-child { + margin-bottom: 0; + } + + &:empty { + display: none; + } + } + } + hr { background: var(--accent-1); grid-column: 1 / -1; @@ -62,7 +96,7 @@ font-weight: bold; } - input, select, .value, [data-tag-count] { + button, input, select, .value, [data-tag-count] { border-radius: 4px; padding: 2px 4px; } diff --git a/templates/Apps/RichEditor/content.hbs b/templates/Apps/RichEditor/content.hbs index 3d7ca58..3b203dd 100644 --- a/templates/Apps/RichEditor/content.hbs +++ b/templates/Apps/RichEditor/content.hbs @@ -1,5 +1,10 @@
{{#if editable}} + {{#if (not collaborative)}} +

+ {{ rc-i18n "RipCrypt.Apps.RichEditor-no-collaborative" }} +

+ {{/if}} .window-content p { + &.warning { + padding: 0.75rem; + margin: 0.25rem; + border-radius: 8px; + border-color: yellow; + border-style: solid; + border-width: 2px; + } +}