Merge branch 'feature/chat-message-styles' of https://github.com/Foundryborne/daggerheart into feature/chat-message-styles

This commit is contained in:
Dapoolp 2025-07-29 22:43:31 +02:00
commit f14cb3936a
19 changed files with 422 additions and 171 deletions

View file

@ -129,6 +129,7 @@ Hooks.once('init', () => {
CONFIG.ChatMessage.dataModels = models.chatMessages.config; CONFIG.ChatMessage.dataModels = models.chatMessages.config;
CONFIG.ChatMessage.documentClass = documents.DhChatMessage; CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';
CONFIG.Canvas.rulerClass = placeables.DhRuler; CONFIG.Canvas.rulerClass = placeables.DhRuler;
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer; CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;

View file

@ -1646,6 +1646,9 @@
}, },
"UI": { "UI": {
"Chat": { "Chat": {
"action": {
"title": "Action"
},
"applyEffect": { "applyEffect": {
"title": "Apply Effects - {name}" "title": "Apply Effects - {name}"
}, },

View file

@ -54,6 +54,8 @@ export default class DhpDeathMove extends HandlebarsApplicationMixin(Application
{ {
player: this.actor.name, player: this.actor.name,
actor: { name: this.actor.name, img: this.actor.img }, actor: { name: this.actor.name, img: this.actor.img },
author: game.users.get(game.user.id),
speaker: cls.getSpeaker(),
title: game.i18n.localize(this.selectedMove.name), title: game.i18n.localize(this.selectedMove.name),
img: this.selectedMove.img, img: this.selectedMove.img,
description: game.i18n.localize(this.selectedMove.description) description: game.i18n.localize(this.selectedMove.description)

View file

@ -139,6 +139,10 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
moves: moves, moves: moves,
actor: this.actor.uuid actor: this.actor.uuid
}, },
speaker: cls.getSpeaker(),
title: game.i18n.localize(
`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`
),
content: await foundry.applications.handlebars.renderTemplate( content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/downtime.hbs', 'systems/daggerheart/templates/ui/chat/downtime.hbs',
{ {

View file

@ -1,4 +1,4 @@
import { emitAsGM, GMUpdateEvent } from "../../systemRegistration/socket.mjs"; import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog { export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
constructor(options) { constructor(options) {
@ -15,6 +15,11 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
this.setupHooks(); this.setupHooks();
} }
/** @inheritDoc */
static DEFAULT_OPTIONS = {
classes: ['daggerheart']
};
addChatListeners = async (app, html, data) => { addChatListeners = async (app, html, data) => {
html.querySelectorAll('.duality-action-damage').forEach(element => html.querySelectorAll('.duality-action-damage').forEach(element =>
element.addEventListener('click', event => this.onRollDamage(event, data.message)) element.addEventListener('click', event => this.onRollDamage(event, data.message))
@ -100,22 +105,24 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
if (message.system.source.item && message.system.source.action) { if (message.system.source.item && message.system.source.action) {
const action = this.getAction(actor, message.system.source.item, message.system.source.action); const action = this.getAction(actor, message.system.source.item, message.system.source.action);
if (!action || !action?.hasSave) return; if (!action || !action?.hasSave) return;
action.rollSave(token.actor, event, message).then(result => emitAsGM( action.rollSave(token.actor, event, message).then(result =>
GMUpdateEvent.UpdateSaveMessage, emitAsGM(
action.updateSaveMessage.bind(action, result, message, token.id), GMUpdateEvent.UpdateSaveMessage,
{ action.updateSaveMessage.bind(action, result, message, token.id),
action: action.uuid, {
message: message._id, action: action.uuid,
token: token.id, message: message._id,
result token: token.id,
} result
)); }
)
);
} }
} }
async onRollAllSave(event, message) { async onRollAllSave(event, message) {
event.stopPropagation(); event.stopPropagation();
if(!game.user.isGM) return; if (!game.user.isGM) return;
const targets = event.target.parentElement.querySelectorAll( const targets = event.target.parentElement.querySelectorAll(
'.target-section > [data-token] .target-save-container' '.target-section > [data-token] .target-save-container'
); );
@ -124,16 +131,17 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
targets.forEach(async el => { targets.forEach(async el => {
const tokenId = el.closest('[data-token]')?.dataset.token, const tokenId = el.closest('[data-token]')?.dataset.token,
token = game.canvas.tokens.get(tokenId); token = game.canvas.tokens.get(tokenId);
if(!token.actor) return; if (!token.actor) return;
if(game.user === token.actor.owner) if (game.user === token.actor.owner) el.dispatchEvent(new PointerEvent('click', { shiftKey: true }));
el.dispatchEvent(new PointerEvent('click', { shiftKey: true }));
else { else {
token.actor.owner.query('reactionRoll', { token.actor.owner
actionId: action.uuid, .query('reactionRoll', {
actorId: token.actor.uuid, actionId: action.uuid,
event, actorId: token.actor.uuid,
message event,
}).then(result => action.updateSaveMessage(result, message, token.id)); message
})
.then(result => action.updateSaveMessage(result, message, token.id));
} }
}); });
} }
@ -172,7 +180,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
return { return {
isHit, isHit,
targets: isHit targets: isHit
? message.system.targets.filter(t => t.hit === true).map(target => game.canvas.tokens.documentCollection.find(t => t.actor.uuid === target.actorId)) ? message.system.targets
.filter(t => t.hit === true)
.map(target => game.canvas.tokens.documentCollection.find(t => t.actor.uuid === target.actorId))
: Array.from(game.user.targets) : Array.from(game.user.targets)
}; };
} }
@ -234,10 +244,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
}); });
} }
if(message.system.hasHealing) if (message.system.hasHealing) target.actor.takeHealing(damages);
target.actor.takeHealing(damages); else target.actor.takeDamage(damages);
else
target.actor.takeDamage(damages);
} }
} }

View file

@ -254,11 +254,15 @@ export function ActionMixin(Base) {
origin: origin, origin: origin,
action: { name: this.name, img: this.img, tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'] }, action: { name: this.name, img: this.img, tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'] },
itemOrigin: this.item, itemOrigin: this.item,
description: this.description, description: this.description
}; };
const msg = { const msg = {
type: 'abilityUse', type: 'abilityUse',
user: game.user.id, user: game.user.id,
actor: { name: this.actor.name, img: this.actor.img },
author: this.author,
speaker: cls.getSpeaker(),
title: game.i18n.localize('DAGGERHEART.UI.Chat.action.title'),
system: systemData, system: systemData,
content: await foundry.applications.handlebars.renderTemplate( content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/action.hbs', 'systems/daggerheart/templates/ui/chat/action.hbs',

View file

@ -6,8 +6,13 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
_source: this.system._source _source: this.system._source
}); });
const actor = game.actors.get(this.speaker.actor);
const actorData = actor ?? {
img: this.author.avatar ? this.author.avatar : 'icons/svg/mystery-man.svg',
name: ''
};
/* We can change to fully implementing the renderHTML function if needed, instead of augmenting it. */ /* We can change to fully implementing the renderHTML function if needed, instead of augmenting it. */
const html = await super.renderHTML(); const html = await super.renderHTML({ actor: actorData, author: this.author });
this.applyPermission(html); this.applyPermission(html);
if (this.type === 'dualityRoll') { if (this.type === 'dualityRoll') {

View file

@ -128,7 +128,11 @@ export default class DHItem extends foundry.documents.Item {
const msg = new cls({ const msg = new cls({
type: 'abilityUse', type: 'abilityUse',
user: game.user.id, user: game.user.id,
actor: this.actor,
author: this.author,
speaker: cls.getSpeaker(),
system: systemData, system: systemData,
title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.displayInChat'),
content: await foundry.applications.handlebars.renderTemplate( content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/ability-use.hbs', 'systems/daggerheart/templates/ui/chat/ability-use.hbs',
systemData systemData

View file

@ -1,18 +1,74 @@
@import '../utils/colors.less'; @import '../utils/colors.less';
@import '../utils/fonts.less'; @import '../utils/fonts.less';
@import '../utils/mixin.less'; @import '../utils/mixin.less';
.dh-chat-message { .theme-light {
background-image: url('../assets/parchments/dh-parchment-dark.png'); .daggerheart.chat-sidebar .chat-log,
border: none !important; #chat-notifications .chat-log {
padding: 8px 0; .chat-message {
background-image: url('../assets/parchments/dh-parchment-light.png');
.message-header { }
padding: 0 8px 8px; }
color: @beige; }
.message-sender,
.message-metadata { #chat-message {
font-family: @font-body; font-family: @font-body;
} padding: 8px;
} }
}
.daggerheart.chat-sidebar,
#chat-notifications {
.chat-log {
.chat-message {
border: none !important;
padding: 8px 0;
background-image: url('../assets/parchments/dh-parchment-dark.png');
.message-header {
display: flex;
gap: 4px;
padding: 0 8px 8px;
.message-header-metadata {
flex: none;
display: flex;
.message-metadata {
font-family: @font-body;
color: light-dark(@dark, @beige);
}
}
.message-header-main {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
.actor-img {
border-radius: 50%;
width: 40px;
height: 40px;
object-fit: cover;
}
.message-sub-header-container {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
color: light-dark(@dark, @beige);
h4 {
font-size: 16px;
font-weight: bold;
margin-bottom: 0;
font-family: @font-subtitle;
color: light-dark(@dark-blue, @golden);
}
}
}
}
}
}
}

View file

@ -15,40 +15,71 @@
object-fit: cover; object-fit: cover;
} }
.domain-card-header { details[open] {
display: flex; .fa-chevron-down {
flex-direction: column; transform: rotate(180deg);
width: 100%; transition: all 0.3s ease;
margin: 8px 8px 0; }
padding-bottom: 5px; }
width: -webkit-fill-available;
gap: 5px;
border-bottom: 1px solid @golden;
.title { .domain-card-move {
font-size: 20px; width: 100%;
color: @golden;
font-family: @font-subtitle; .fa-chevron-down {
margin: 0; transition: all 0.3s ease;
margin-left: auto;
} }
.tags { .domain-card-header {
display: flex; display: flex;
gap: 10px; flex-direction: row;
align-items: center;
margin: 8px;
padding-bottom: 5px;
width: -webkit-fill-available;
gap: 5px;
border-bottom: 1px solid @golden;
.tag { &:hover {
background: light-dark(@dark-blue-10, @golden-10);
cursor: pointer;
transition: all 0.3s ease;
}
.domain-label {
display: flex; display: flex;
flex-direction: row; flex-direction: column;
justify-content: center; width: 100%;
align-items: center; padding-bottom: 5px;
padding: 3px 5px; width: -webkit-fill-available;
font-size: 12px; gap: 5px;
font-family: @font-body;
background: light-dark(@dark-15, @beige-15); .title {
border: 1px solid light-dark(@dark, @beige); font-size: 20px;
color: light-dark(@dark, @beige); color: @golden;
border-radius: 3px; font-family: @font-subtitle;
margin: 0;
}
.tags {
display: flex;
gap: 10px;
.tag {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 3px 5px;
font-size: 12px;
font-family: @font-body;
background: light-dark(@dark-15, @beige-15);
border: 1px solid light-dark(@dark, @beige);
color: light-dark(@dark, @beige);
border-radius: 3px;
}
}
} }
} }
} }

View file

@ -8,40 +8,62 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
.action-section { details[open] {
display: flex; .fa-chevron-down {
flex-direction: row; transform: rotate(180deg);
width: 100%; transition: all 0.3s ease;
margin: 8px 8px 0; }
padding-bottom: 5px; }
width: -webkit-fill-available;
gap: 5px;
border-bottom: 1px solid @golden;
.action-img { .action-move {
width: 40px; width: 100%;
height: 40px;
border-radius: 3px; .fa-chevron-down {
object-fit: cover; transition: all 0.3s ease;
margin-left: auto;
} }
.action-header { .action-section {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
align-items: center;
margin: 8px 8px 0;
padding-bottom: 5px;
width: -webkit-fill-available;
gap: 5px; gap: 5px;
border-bottom: 1px solid @golden;
.title { &:hover {
font-size: 20px; background: light-dark(@dark-blue-10, @golden-10);
color: @golden; cursor: pointer;
font-family: @font-subtitle; transition: all 0.3s ease;
margin: 0;
} }
.label { .action-img {
font-size: 12px; width: 40px;
color: @beige; height: 40px;
font-family: @font-body; border-radius: 3px;
margin: 0; object-fit: cover;
}
.action-header {
display: flex;
flex-direction: column;
gap: 5px;
.title {
font-size: 20px;
color: @golden;
font-family: @font-subtitle;
margin: 0;
}
.label {
font-size: 12px;
color: @beige;
font-family: @font-body;
margin: 0;
}
} }
} }
} }

View file

@ -319,6 +319,9 @@
button { button {
flex: 1; flex: 1;
height: 40px;
font-family: @font-body;
font-weight: 600;
} }
} }

View file

@ -8,35 +8,10 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
.downtime-header { details[open] {
display: flex; .fa-chevron-down {
gap: 13px; transform: rotate(180deg);
flex-direction: row; transition: all 0.3s ease;
align-items: center;
width: 100%;
padding-left: 8px;
margin-bottom: 12px;
.profile {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.header-label {
.title {
font-size: 20px;
color: @golden;
font-family: @font-subtitle;
margin: 0;
}
.label {
font-size: 12px;
color: @beige;
font-family: @font-body;
margin: 0;
}
} }
} }
@ -44,6 +19,12 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 5px; gap: 5px;
width: 100%;
.fa-chevron-down {
transition: all 0.3s ease;
margin-left: auto;
}
.downtime-move { .downtime-move {
width: 100%; width: 100%;
@ -57,6 +38,12 @@
padding-bottom: 5px; padding-bottom: 5px;
width: -webkit-fill-available; width: -webkit-fill-available;
&:hover {
background: light-dark(@dark-blue-10, @golden-10);
cursor: pointer;
transition: all 0.3s ease;
}
.downtime-image { .downtime-image {
width: 40px; width: 40px;
height: 40px; height: 40px;

View file

@ -1,3 +1,12 @@
@import '../../utils/colors.less';
@import '../../utils/fonts.less';
.chat-message.dh-chat-message {
.message-content {
padding: 0;
}
}
.chat-message { .chat-message {
.duality-modifiers, .duality-modifiers,
.duality-result, .duality-result,
@ -6,6 +15,66 @@
} }
.message-content { .message-content {
padding: 0 8px;
font-family: @font-body;
color: light-dark(@dark, @beige);
blockquote {
border-left: 5px solid light-dark(@dark-blue-40, @golden-40);
}
a[href] {
color: light-dark(@dark-blue, @golden);
}
a[href]:hover,
a[href].active {
font-weight: bold;
text-shadow: 0 0 8px light-dark(@dark-blue, @golden);
}
button {
background: light-dark(transparent, @golden);
border: 1px solid light-dark(@dark-blue, @dark-blue);
color: light-dark(@dark-blue, @dark-blue);
outline: none;
box-shadow: none;
&:hover {
background: light-dark(@light-black, @dark-blue);
color: light-dark(@dark-blue, @golden);
}
&.glow {
animation: glow 0.75s infinite alternate;
}
&:disabled {
background: light-dark(transparent, @golden);
color: light-dark(@dark-blue, @dark-blue);
opacity: 0.6;
cursor: not-allowed;
&:hover {
background: light-dark(transparent, @golden);
color: light-dark(@dark-blue, @dark-blue);
}
}
&.reverted {
background: light-dark(@dark-blue-10, @golden-10);
color: light-dark(@dark-blue, @golden);
border: 1px solid light-dark(@dark, transparent);
&:hover {
background: light-dark(transparent, @golden);
color: light-dark(@dark-blue, @dark-blue);
}
img {
border-radius: 3px;
}
}
}
.enriched-effect { .enriched-effect {
display: flex; display: flex;
align-items: center; align-items: center;
@ -24,6 +93,24 @@
white-space: nowrap; white-space: nowrap;
} }
} }
.dice-roll .dice-formula,
.dice-roll .dice-total {
box-shadow: none;
border: none;
background: light-dark(@dark-blue-40, @golden-40);
color: light-dark(@dark-blue, @golden);
font-weight: 600;
align-content: center;
}
.dice-roll .dice-formula {
height: 27px;
}
.dice-roll .dice-total {
height: 34px;
}
} }
} }

View file

@ -1,14 +1,19 @@
<div class="daggerheart chat domain-card"> <div class="daggerheart chat domain-card">
<img class="card-img" src="{{item.img}}" /> <img class="card-img" src="{{item.img}}" />
<div class="domain-card-header"> <details class="domain-card-move">
<h2 class="title">{{item.name}}</h2> <summary class="domain-card-header">
<ul class="tags"> <div class="domain-label">
{{#each item.tags as |tag|}} <h2 class="title">{{item.name}}</h2>
<li class="tag">{{tag}}</li> <ul class="tags">
{{/each}} {{#each item.tags as |tag|}}
</ul> <li class="tag">{{tag}}</li>
</div> {{/each}}
<div class="description">{{{description}}}</div> </ul>
</div>
<i class="fa-solid fa-chevron-down"></i>
</summary>
<div class="description">{{{description}}}</div>
</details>
<footer class="ability-card-footer"> <footer class="ability-card-footer">
{{#each actions as |action index|}} {{#each actions as |action index|}}
<button class="ability-use-button" data-index="{{index}}"> <button class="ability-use-button" data-index="{{index}}">

View file

@ -1,12 +1,13 @@
<div class="daggerheart chat action"> <div class="daggerheart chat action">
<div class="action-section"> <details class="action-move">
<img class="action-img" src="{{action.img}}" /> <summary class="action-section">
<div class="action-header"> <img class="action-img" src="{{action.img}}" />
<h2 class="title">{{action.name}}</h2> <div class="action-header">
<span class="label">{{itemOrigin.name}}</span> <h2 class="title">{{action.name}}</h2>
</div> <span class="label">{{itemOrigin.name}}</span>
</div> </div>
{{#if description}} <i class="fa-solid fa-chevron-down"></i>
</summary>
<div class="description">{{{description}}}</div> <div class="description">{{{description}}}</div>
{{/if}} </details>
</div> </div>

View file

@ -0,0 +1,40 @@
<li class="chat-message message flexcol {{cssClass}}" data-message-id="{{message._id}}"
{{#if borderColor}}style="border-color:{{borderColor}}"{{/if}}>
<header class="message-header flexrow">
<div class="message-header-main">
<img class="actor-img" src="{{actor.img}}" />
<div class="message-sub-header-container">
<h4>{{ifThen message.title message.title alias}}</h4>
{{#if actor.name}}
<div>{{actor.name}} {{#if author.isGM}}(GM){{/if}}</div>
{{/if}}
</div>
</div>
<div class="message-header-metadata">
<span class="message-metadata">
<time class="message-timestamp">{{timeSince message.timestamp}}</time>
{{#if canDelete}}
<a aria-label="{{localize 'Delete'}}" class="message-delete" data-action="deleteMessage">
<i class="fa-solid fa-trash" inert></i>
</a>
{{/if}}
{{#if canClose}}
<a aria-label="{{ localize "CHAT.Dismiss" }}" class="message-dismiss" data-action="dismissMessage">
<i class="fa-solid fa-xmark" inert></i>
</a>
{{/if}}
</span>
{{#if isWhisper}}
<span class="whisper-to">{{localize 'CHAT.To'}}: {{whisperTo}}</span>
{{/if}}
{{#if message.flavor}}
<span class="flavor-text">{{{message.flavor}}}</span>
{{/if}}
</div>
</header>
<div class="message-content">
{{{message.content}}}
</div>
</li>

View file

@ -1,23 +1,17 @@
<div class="daggerheart chat downtime"> <div class="daggerheart chat downtime">
<div class="downtime-header">
<img class="profile" src="{{actor.img}}">
<div class="header-label">
<h2 class="title">{{localize 'DAGGERHEART.UI.Chat.deathMove.title'}}</h2>
<span class="label">{{actor.name}}</span>
</div>
</div>
<ul class="downtime-moves-list"> <ul class="downtime-moves-list">
<li class="downtime-move"> <details class="downtime-move">
<div class="downtime-label"> <summary class="downtime-label">
<img class="downtime-image" src="{{this.img}}" /> <img class="downtime-image" src="{{this.img}}" />
<div class="header-label"> <div class="header-label">
<h2 class="title">{{this.title}}</h2> <h2 class="title">{{this.title}}</h2>
<span class="label">{{localize 'DAGGERHEART.UI.Chat.deathMove.title'}}</span> <span class="label">{{localize 'DAGGERHEART.UI.Chat.deathMove.title'}}</span>
</div> </div>
</div> <i class="fa-solid fa-chevron-down"></i>
</summary>
<div class="description"> <div class="description">
{{{this.description}}} {{{this.description}}}
</div> </div>
</li> </details>
</ul> </ul>
</div> </div>

View file

@ -1,28 +1,22 @@
<div class="daggerheart chat downtime"> <div class="daggerheart chat downtime">
<div class="downtime-header">
<img class="profile" src="{{actor.img}}">
<div class="header-label">
<h2 class="title">{{title}}</h2>
<span class="label">{{actor.name}}</span>
</div>
</div>
<ul class="downtime-moves-list"> <ul class="downtime-moves-list">
{{#each moves as | move index |}} {{#each moves as | move index |}}
<li class="downtime-move"> <details class="downtime-move">
<div class="downtime-label"> <summary class="downtime-label">
<img class="downtime-image" src="{{move.img}}" /> <img class="downtime-image" src="{{move.img}}" />
<div class="header-label"> <div class="header-label">
<h2 class="title">{{move.name}}</h2> <h2 class="title">{{move.name}}</h2>
<span class="label">{{localize 'DAGGERHEART.GENERAL.Bonuses.rest.downtimeAction'}}</span> <span class="label">{{localize 'DAGGERHEART.GENERAL.Bonuses.rest.downtimeAction'}}</span>
</div> </div>
</div> <i class="fa-solid fa-chevron-down"></i>
</summary>
<div class="description"> <div class="description">
{{{move.description}}} {{{move.description}}}
</div> </div>
{{#each move.actions as | action index |}} </details>
<button class="action-use-button" data-move-index="{{@../key}}" data-action-index="{{index}}">{{localize action.name}}</button> {{#each move.actions as | action index |}}
{{/each}} <button class="action-use-button" data-move-index="{{@../key}}" data-action-index="{{index}}">{{localize action.name}}</button>
</li> {{/each}}
{{/each}} {{/each}}
</ul> </ul>
</div> </div>