Add the ability to display / edit the description from the item sheet
This commit is contained in:
parent
46a235b603
commit
69db3ca719
13 changed files with 168 additions and 17 deletions
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
43
module/handlebarHelpers/inputs/prosemirrorInput.mjs
Normal file
43
module/handlebarHelpers/inputs/prosemirrorInput.mjs
Normal file
|
|
@ -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 `<div data-input-type="prose-mirror">
|
||||
<div class="label-row">
|
||||
<div class="label">
|
||||
${label}
|
||||
</div>
|
||||
</div>
|
||||
<div class="value">
|
||||
${input.value}
|
||||
</div>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
return `<div data-input-type="prose-mirror">
|
||||
<div class="label-row">
|
||||
<div class="label">
|
||||
${label}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-action="openRichEditor"
|
||||
data-uuid="${input.uuid}"
|
||||
data-path="${input.path}"
|
||||
data-compact="${input.compact}"
|
||||
data-collaborative="${input.collaborative}"
|
||||
>
|
||||
${localizer(`RipCrypt.common.edit`)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
This cannot be spread across multiple lines because of the :empty selector
|
||||
considering whitespace as "not being empty". Though browsers will eventually
|
||||
treat :empty as "empty, or only whitespace".
|
||||
-->
|
||||
<div class="value">${input.value}</div>
|
||||
</div>`;
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
<div>
|
||||
{{#if editable}}
|
||||
{{#if (not collaborative)}}
|
||||
<p class="warning">
|
||||
{{ rc-i18n "RipCrypt.Apps.RichEditor-no-collaborative" }}
|
||||
</p>
|
||||
{{/if}}
|
||||
<prose-mirror
|
||||
name="{{ path }}"
|
||||
value="{{ raw }}"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
@import url("./elements/button.css");
|
||||
@import url("./elements/input.css");
|
||||
@import url("./elements/lists.css");
|
||||
@import url("./elements/p.css");
|
||||
@import url("./elements/pill-bar.css");
|
||||
@import url("./elements/prose-mirror.css") layer(exceptions);
|
||||
@import url("./elements/select.css");
|
||||
|
|
|
|||
10
templates/css/elements/p.css
Normal file
10
templates/css/elements/p.css
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
.ripcrypt > .window-content p {
|
||||
&.warning {
|
||||
padding: 0.75rem;
|
||||
margin: 0.25rem;
|
||||
border-radius: 8px;
|
||||
border-color: yellow;
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue