mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-07-21 18:09:54 +02:00
[Feature] Add support for GM Notes (#2082)
Some checks failed
Project CI / build (24.x) (push) Has been cancelled
Some checks failed
Project CI / build (24.x) (push) Has been cancelled
* Add support for GM Notes * Localize GM Notes header label * Fix active editor height and menu auto sizing * Add tooltip to add gm note button
This commit is contained in:
parent
0c2d257871
commit
79d6522614
17 changed files with 278 additions and 64 deletions
|
|
@ -267,6 +267,10 @@ Hooks.on('i18nInit', () => {
|
|||
});
|
||||
|
||||
Hooks.on('setup', () => {
|
||||
if (game.user.isGM) {
|
||||
document.body.dataset.gm = true;
|
||||
}
|
||||
|
||||
CONFIG.statusEffects = [
|
||||
...CONFIG.statusEffects.filter(x => !['dead', 'unconscious'].includes(x.id)),
|
||||
...Object.values(SYSTEM.GENERAL.conditions()).map(x => ({
|
||||
|
|
|
|||
|
|
@ -2550,6 +2550,9 @@
|
|||
},
|
||||
"identifier": {
|
||||
"label": "Identifier"
|
||||
},
|
||||
"gmNotes": {
|
||||
"label": "GM Notes"
|
||||
}
|
||||
},
|
||||
"Ancestry": {
|
||||
|
|
@ -2565,6 +2568,9 @@
|
|||
"severe": "Severe Threshold"
|
||||
}
|
||||
},
|
||||
"Base": {
|
||||
"addGMNote": "Add GM Note"
|
||||
},
|
||||
"Beastform": {
|
||||
"FIELDS": {
|
||||
"beastformType": { "label": "Beastform Type" },
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
addFeature: DHBaseItemSheet.#addFeature,
|
||||
deleteFeature: DHBaseItemSheet.#deleteFeature,
|
||||
addResource: DHBaseItemSheet.#addResource,
|
||||
removeResource: DHBaseItemSheet.#removeResource
|
||||
removeResource: DHBaseItemSheet.#removeResource,
|
||||
editGMNote: DHBaseItemSheet.#onEditGMNote
|
||||
},
|
||||
dragDrop: [
|
||||
{ dragSelector: null, dropSelector: '.drop-section' },
|
||||
|
|
@ -76,10 +77,16 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
/**@inheritdoc */
|
||||
async _preparePartContext(partId, context, options) {
|
||||
await super._preparePartContext(partId, context, options);
|
||||
const TextEditor = foundry.applications.ux.TextEditor.implementation;
|
||||
|
||||
switch (partId) {
|
||||
case 'description':
|
||||
context.enrichedDescription = await this.document.system.getEnrichedDescription();
|
||||
context.enrichedDescription = await this.document.system.getEnrichedDescription({ gmNotes: false });
|
||||
context.enrichedGMNotes = await TextEditor.implementation.enrichHTML(this.item.system.gmNotes, {
|
||||
relativeTo: this.item,
|
||||
rollData: this.item.getRollData(),
|
||||
secrets: this.item.isOwner
|
||||
})
|
||||
break;
|
||||
case 'effects':
|
||||
await this._prepareEffectsContext(context, options);
|
||||
|
|
@ -331,4 +338,45 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the Add GM Note button being pressed. This is only used when an item has no GM notes.
|
||||
* Later edits to a GM note instead go through the normal editor toggle workflow.
|
||||
* @this DHBaseItemSheet
|
||||
*/
|
||||
static #onEditGMNote() {
|
||||
// Open the editor, which might be hidden. We remove the css class to hide temporarily
|
||||
// so that menu auto resizing functions properly.
|
||||
const editor = this.element.querySelector('prose-mirror[name="system.gmNotes"]');
|
||||
const wasHidden = editor.classList.contains('hide-if-inactive');
|
||||
editor.classList.remove('hide-if-inactive');
|
||||
editor.open = true;
|
||||
window.setTimeout(() => {
|
||||
if (wasHidden) editor.classList.add('hide-if-inactive');
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
|
||||
// Render an add gmnotes button if there are no set GM notes.
|
||||
// We need to re-render on close since its possible to prosemirror to close *without* triggering a full re-render
|
||||
if (game.user.isGM && !this.item.system.gmNotes) {
|
||||
const description = this.element.querySelector('[name="system.description"]');
|
||||
const addButton = () => {
|
||||
if (description.querySelector('[data-action=editGMNote]')) return;
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.classList.add('icon', 'toggle', 'fa-regular', 'fa-note-medical');
|
||||
button.dataset.action = 'editGMNote';
|
||||
button.dataset.tooltip = 'DAGGERHEART.ITEMS.Base.addGMNote';
|
||||
description.appendChild(button);
|
||||
}
|
||||
|
||||
addButton();
|
||||
description.addEventListener('close', () => addButton());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,10 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
})
|
||||
};
|
||||
|
||||
if (this.metadata.hasDescription) schema.description = new fields.HTMLField({ required: true, nullable: true });
|
||||
if (this.metadata.hasDescription) {
|
||||
schema.description = new fields.HTMLField({ required: true, nullable: true });
|
||||
schema.gmNotes = new fields.HTMLField({ required: true, nullable: true });
|
||||
}
|
||||
|
||||
if (this.metadata.hasResource) {
|
||||
schema.resource = new fields.SchemaField(
|
||||
|
|
@ -134,7 +137,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
/**
|
||||
* Augments the description for the item with type specific info to display. Implemented in applicable item subtypes.
|
||||
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
|
||||
* @returns {string}
|
||||
* @returns {Promise<{ prefix: string | null; value: string | null; suffix: string | null }>}
|
||||
*/
|
||||
async getDescriptionData(_options) {
|
||||
return { prefix: null, value: this.description, suffix: null };
|
||||
|
|
@ -145,14 +148,24 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async getEnrichedDescription() {
|
||||
async getEnrichedDescription({ gmNotes = true } = {}) {
|
||||
if (!this.metadata.hasDescription) return '';
|
||||
|
||||
const { prefix, value, suffix } = await this.getDescriptionData();
|
||||
const fullDescription = [prefix, value, suffix].filter(p => !!p).join('\n<hr>\n');
|
||||
let fullDescription = [prefix, value, suffix].filter(p => !!p).join('\n<hr>\n');
|
||||
if (this.gmNotes && gmNotes) {
|
||||
const gmNotesElement = document.createElement('section');
|
||||
gmNotesElement.classList.add('gm-notes-section');
|
||||
gmNotesElement.dataset.visibility = 'gm';
|
||||
const header = document.createElement('header');
|
||||
header.classList.add('gm-notes');
|
||||
header.textContent = _loc('DAGGERHEART.ITEMS.FIELDS.gmNotes.label');
|
||||
gmNotesElement.innerHTML = header.outerHTML + this.gmNotes;
|
||||
fullDescription += gmNotesElement.outerHTML;
|
||||
}
|
||||
|
||||
return await foundry.applications.ux.TextEditor.implementation.enrichHTML(fullDescription, {
|
||||
relativeTo: this,
|
||||
relativeTo: this.parent,
|
||||
rollData: this.getRollData(),
|
||||
secrets: this.parent.isOwner
|
||||
});
|
||||
|
|
|
|||
|
|
@ -595,7 +595,37 @@
|
|||
margin-top: 4px;
|
||||
color: light-dark(#14142599, #efe6d850);
|
||||
font-size: var(--font-size-12);
|
||||
padding-left: 3px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
section.gm-notes-section {
|
||||
padding-bottom: var(--spacer-4);
|
||||
header.gm-notes + p {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
header.gm-notes {
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
&::before,
|
||||
&::after {
|
||||
content: " ";
|
||||
flex: 1;
|
||||
border-bottom: 1px solid var(--color-dark-6);
|
||||
}
|
||||
&::before {
|
||||
mask-image: linear-gradient(270deg, black 0%, black calc(100% - 10px), transparent 100%);
|
||||
}
|
||||
&::after {
|
||||
mask-image: linear-gradient(270deg, transparent 0%, black 10px, black 100%);
|
||||
}
|
||||
margin-top: var(--spacer-8);
|
||||
margin-bottom: var(--spacer-4);
|
||||
font-size: var(--font-size-11);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
secret-block {
|
||||
|
|
@ -866,4 +896,8 @@
|
|||
right: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.gm-notes {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
.sheet.daggerheart.dh-style.item {
|
||||
.tab.features {
|
||||
padding: 0 10px;
|
||||
padding: 7px 10px;
|
||||
overflow-y: auto;
|
||||
.feature-list {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -111,3 +111,7 @@ body.theme-light,
|
|||
.themed.theme-light {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
body:not([data-gm=true]) [data-visibility="gm"] {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -12,12 +12,14 @@
|
|||
});
|
||||
|
||||
.application.sheet.daggerheart.dh-style {
|
||||
--portrait-size: 150px;
|
||||
|
||||
.item-sheet-header {
|
||||
display: flex;
|
||||
|
||||
.profile {
|
||||
height: 150px;
|
||||
width: 150px;
|
||||
height: var(--portrait-size);
|
||||
width: var(--portrait-size);
|
||||
object-fit: cover;
|
||||
border-right: 1px solid light-dark(@dark-blue, @golden);
|
||||
border-bottom: 1px solid light-dark(@dark-blue, @golden);
|
||||
|
|
@ -34,14 +36,18 @@
|
|||
text-align: center;
|
||||
width: 80%;
|
||||
|
||||
.item-name input[type='text'] {
|
||||
font-size: var(--font-size-32);
|
||||
height: 42px;
|
||||
.item-name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 10px 10px 0 10px;
|
||||
input[type='text'] {
|
||||
font-size: var(--font-size-30);
|
||||
text-align: center;
|
||||
width: 90%;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
outline: 2px solid transparent;
|
||||
border: 1px solid transparent;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover[type='text'],
|
||||
&:focus[type='text'] {
|
||||
|
|
@ -49,6 +55,7 @@
|
|||
outline: 2px solid light-dark(@dark-blue, @golden);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-description {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -6,11 +6,80 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow-y: hidden !important;
|
||||
padding-top: 10px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
prose-mirror.active + .artist-attribution {
|
||||
.description-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
padding: 12px 16px 4px 16px;
|
||||
.with-scroll-shadows();
|
||||
prose-mirror {
|
||||
button.toggle {
|
||||
top: 0px;
|
||||
right: 0;
|
||||
}
|
||||
button[data-action=editGMNote] {
|
||||
right: calc(var(--button-size) + 4px);
|
||||
}
|
||||
&.inactive {
|
||||
height: unset!important;
|
||||
overflow: unset;
|
||||
.editor-content {
|
||||
position: relative;
|
||||
overflow: unset;
|
||||
|
||||
// Allows content links to peek out
|
||||
margin-top: -4px;
|
||||
padding: 4px 0 0 0;
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
--min-height: 250px;
|
||||
padding: 8px 0 0 16px;
|
||||
button[data-action=editGMNote] {
|
||||
display: none;
|
||||
}
|
||||
.editor-content {
|
||||
padding-right: 16px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Hide editors that are empty when inactive if we need them to be */
|
||||
prose-mirror.inactive.hide-if-inactive {
|
||||
display: none;
|
||||
}
|
||||
&:has(prose-mirror.active) {
|
||||
padding: 0;
|
||||
}
|
||||
/** Description should fill available room (with overriden exceptions) */
|
||||
prose-mirror[name="system.description"] {
|
||||
flex: 1 0;
|
||||
}
|
||||
&:has(prose-mirror[name="system.gmNotes"]:not(.hide-if-inactive)) {
|
||||
prose-mirror.inactive {
|
||||
--min-height: 3rem;
|
||||
&[name="system.description"] {
|
||||
flex: 0 0;
|
||||
}
|
||||
&[name="system.gmNotes"] {
|
||||
flex: 1 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Hide other elements if an editor is open */
|
||||
&:has(prose-mirror.active) {
|
||||
prose-mirror.inactive,
|
||||
header.gm-notes,
|
||||
.artist-attribution {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,10 +93,6 @@
|
|||
padding: 8px 0 0 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.artist-attribution {
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-section {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
.application.sheet.daggerheart.dh-style.beastform {
|
||||
--portrait-size: 130px;
|
||||
|
||||
.settings.tab {
|
||||
.advantage-on-section {
|
||||
display: flex;
|
||||
|
|
@ -9,4 +11,11 @@
|
|||
font-style: italic;
|
||||
}
|
||||
}
|
||||
.tab.features.active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 8px calc(12px - var(--scrollbar-width)) 4px 12px;
|
||||
.stable-scroll-container();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,9 @@
|
|||
@import '../../utils/fonts.less';
|
||||
|
||||
.application.sheet.daggerheart.dh-style.feature {
|
||||
.item-sheet-header {
|
||||
display: flex;
|
||||
|
||||
.profile {
|
||||
height: 130px;
|
||||
width: 130px;
|
||||
}
|
||||
}
|
||||
--portrait-size: 130px;
|
||||
|
||||
section.tab {
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import './item-sheet-shared.less';
|
||||
@import './beastform.less';
|
||||
@import './class.less';
|
||||
@import './domain-card.less';
|
||||
@import './feature.less';
|
||||
@import './heritage.less';
|
||||
@import './item-sheet-shared.less';
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
.application.sheet.daggerheart.dh-style.item {
|
||||
.item.daggerheart.dh-style:where(.application.sheet) {
|
||||
&.minimized {
|
||||
.attribution-header-label {
|
||||
display: none;
|
||||
|
|
@ -14,4 +14,22 @@
|
|||
button.plain.inline-control {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.tab-navigation {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/** Default tab stylings */
|
||||
.tab.active {
|
||||
padding-top: 8px;
|
||||
.with-scroll-shadows();
|
||||
|
||||
&.effects {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 8px calc(12px - var(--scrollbar-width)) 4px 12px;
|
||||
.stable-scroll-container();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@
|
|||
|
||||
ul,
|
||||
ol {
|
||||
margin: 1rem 0;
|
||||
margin: 0.5rem 0;
|
||||
padding: 0 0 0 1.25rem;
|
||||
|
||||
li {
|
||||
|
|
|
|||
20
system.json
20
system.json
|
|
@ -256,34 +256,34 @@
|
|||
},
|
||||
"Item": {
|
||||
"ancestry": {
|
||||
"htmlFields": ["description"]
|
||||
"htmlFields": ["description", "gmNotes"]
|
||||
},
|
||||
"community": {
|
||||
"htmlFields": ["description"]
|
||||
"htmlFields": ["description", "gmNotes"]
|
||||
},
|
||||
"class": {
|
||||
"htmlFields": ["description"]
|
||||
"htmlFields": ["description", "gmNotes"]
|
||||
},
|
||||
"subclass": {
|
||||
"htmlFields": ["description"]
|
||||
"htmlFields": ["description", "gmNotes"]
|
||||
},
|
||||
"feature": {
|
||||
"htmlFields": ["description"]
|
||||
"htmlFields": ["description", "gmNotes"]
|
||||
},
|
||||
"domainCard": {
|
||||
"htmlFields": ["description"]
|
||||
"htmlFields": ["description", "gmNotes"]
|
||||
},
|
||||
"loot": {
|
||||
"htmlFields": ["description"]
|
||||
"htmlFields": ["description", "gmNotes"]
|
||||
},
|
||||
"consumable": {
|
||||
"htmlFields": ["description"]
|
||||
"htmlFields": ["description", "gmNotes"]
|
||||
},
|
||||
"weapon": {
|
||||
"htmlFields": ["description"]
|
||||
"htmlFields": ["description", "gmNotes"]
|
||||
},
|
||||
"armor": {
|
||||
"htmlFields": ["description"]
|
||||
"htmlFields": ["description", "gmNotes"]
|
||||
},
|
||||
"beastform": {}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,8 +3,22 @@
|
|||
data-tab='{{tabs.description.id}}'
|
||||
data-group='{{tabs.description.group}}'
|
||||
>
|
||||
<div class="description-section">
|
||||
{{formInput systemFields.description value=document.system.description enriched=enrichedDescription toggled=true}}
|
||||
|
||||
{{#if (and systemFields.gmNotes @root.user.isGM)}}
|
||||
<section class="gm-notes-section">
|
||||
{{#if enrichedGMNotes}}
|
||||
<header class="gm-notes">{{localize "DAGGERHEART.ITEMS.FIELDS.gmNotes.label"}}</header>
|
||||
{{/if}}
|
||||
<prose-mirror
|
||||
name="system.gmNotes"
|
||||
{{#unless enrichedGMNotes}}class="hide-if-inactive"{{/unless}}
|
||||
toggled="true"
|
||||
value="{{document.system.gmNotes}}"
|
||||
>{{{enrichedGMNotes}}}</prose-mirror>
|
||||
</section>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if (and showAttribution document.system.attribution.artist)}}
|
||||
<label class="artist-attribution">{{localize "DAGGERHEART.GENERAL.artistAttribution" artist=document.system.attribution.artist}}</label>
|
||||
{{/if}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue