feat: notes de bord et sauvegarde position calendrier
- bouton crayon sur la fenêtre calendrier (MJ) → dialogue de note - date impériale + date réelle préremplies - append au journal configuré (créé auto si inexistant) - appendJournalNote : labels passés depuis l'ouverture (pas de re-lecture) - échappement XSS via jQuery .text().html() - onSave async + await + try/catch - sauvegarde position calendrier (client scope, debounced 300ms) - restore position au constructeur - close() async avec await settings.set - boutons note et config groupés dans .mgt2-cal-actions (flex row)
This commit is contained in:
+35
-2
@@ -22,10 +22,16 @@ export class CalendarApp extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor({ onConfig, onClose } = {}) {
|
constructor({ onConfig, onClose, onNote } = {}) {
|
||||||
super();
|
super();
|
||||||
|
const saved = game.settings?.get(MODULE_ID, 'calendarPosition');
|
||||||
|
if (saved?.left != null && saved?.top != null) {
|
||||||
|
this.position.left = saved.left;
|
||||||
|
this.position.top = saved.top;
|
||||||
|
}
|
||||||
this._onConfig = onConfig;
|
this._onConfig = onConfig;
|
||||||
this._onClose = onClose;
|
this._onClose = onClose;
|
||||||
|
this._onNote = onNote;
|
||||||
this._data = { year: 1116, day: 1, hour: 12, minute: 0, planet: '' };
|
this._data = { year: 1116, day: 1, hour: 12, minute: 0, planet: '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +52,26 @@ export class CalendarApp extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
html.find('.mgt2-cal-config-btn').on('click', () => {
|
html.find('.mgt2-cal-config-btn').on('click', () => {
|
||||||
this._onConfig?.();
|
this._onConfig?.();
|
||||||
});
|
});
|
||||||
|
html.find('.mgt2-cal-note-btn').on('click', () => {
|
||||||
|
this._onNote?.();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setPosition(position) {
|
||||||
|
const result = super.setPosition(position);
|
||||||
|
this._debouncedSavePos();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
_debouncedSavePos() {
|
||||||
|
if (this._savePosTimeout) clearTimeout(this._savePosTimeout);
|
||||||
|
this._savePosTimeout = setTimeout(() => {
|
||||||
|
game.settings?.set(MODULE_ID, 'calendarPosition', {
|
||||||
|
left: this.position.left,
|
||||||
|
top: this.position.top,
|
||||||
|
});
|
||||||
|
this._savePosTimeout = null;
|
||||||
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDisplay(data) {
|
updateDisplay(data) {
|
||||||
@@ -53,8 +79,15 @@ export class CalendarApp extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
async close() {
|
||||||
|
if (game.settings) {
|
||||||
|
await game.settings.set(MODULE_ID, 'calendarPosition', {
|
||||||
|
left: this.position.left,
|
||||||
|
top: this.position.top,
|
||||||
|
});
|
||||||
|
}
|
||||||
this._onConfig = null;
|
this._onConfig = null;
|
||||||
|
this._onNote = null;
|
||||||
this._onClose?.();
|
this._onClose?.();
|
||||||
this._onClose = null;
|
this._onClose = null;
|
||||||
return super.close();
|
return super.close();
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||||
|
|
||||||
|
export class JournalNoteDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
id: 'mgt2-journal-note',
|
||||||
|
classes: ['mgt2-calendar'],
|
||||||
|
position: { width: 320, height: 'auto' },
|
||||||
|
window: {
|
||||||
|
icon: 'fas fa-pen',
|
||||||
|
title: 'Note de bord',
|
||||||
|
resizable: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
main: {
|
||||||
|
template: `modules/${MODULE_ID}/templates/journal-note-dialog.hbs`,
|
||||||
|
root: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor({ imperialDate, realDate, onSave } = {}) {
|
||||||
|
super();
|
||||||
|
this._imperialDate = imperialDate;
|
||||||
|
this._realDate = realDate;
|
||||||
|
this._onSave = onSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return 'Note de bord';
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
return {
|
||||||
|
imperialDate: this._imperialDate,
|
||||||
|
realDate: this._realDate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onRender(context, options) {
|
||||||
|
await super._onRender(context, options);
|
||||||
|
const html = $(this.element);
|
||||||
|
|
||||||
|
html.find('[data-action="save"]').on('click', async () => {
|
||||||
|
const content = html.find('[name="content"]').val()?.trim();
|
||||||
|
try {
|
||||||
|
await this._onSave?.(content);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`${MODULE_ID} | Erreur sauvegarde note:`, err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this._onSave = null;
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -93,3 +93,13 @@ export function formatImperialDate({ year, day, hour, minute, planet } = {}) {
|
|||||||
export function getDefaultCalendar() {
|
export function getDefaultCalendar() {
|
||||||
return { year: 1116, day: 1, hour: 12, minute: 0, planet: '' };
|
return { year: 1116, day: 1, hour: 12, minute: 0, planet: '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatRealDate() {
|
||||||
|
const d = new Date();
|
||||||
|
const y = d.getFullYear();
|
||||||
|
const m = String(d.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(d.getDate()).padStart(2, '0');
|
||||||
|
const h = String(d.getHours()).padStart(2, '0');
|
||||||
|
const min = String(d.getMinutes()).padStart(2, '0');
|
||||||
|
return `${day}/${m}/${y} ${h}:${min}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { CalendarApp } from './CalendarApp.js';
|
import { CalendarApp } from './CalendarApp.js';
|
||||||
import { CalendarConfigDialog } from './CalendarConfigDialog.js';
|
import { CalendarConfigDialog } from './CalendarConfigDialog.js';
|
||||||
import { getDefaultCalendar } from './calendarDate.js';
|
import { JournalNoteDialog } from './JournalNoteDialog.js';
|
||||||
|
import { getDefaultCalendar, formatImperialDate, formatRealDate } from './calendarDate.js';
|
||||||
|
|
||||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||||
const ChatLogV2 = foundry.applications.sidebar.tabs.ChatLog;
|
const ChatLogV2 = foundry.applications.sidebar.tabs.ChatLog;
|
||||||
@@ -47,6 +48,7 @@ function openCalendarApp() {
|
|||||||
const data = readCalendar();
|
const data = readCalendar();
|
||||||
_calendarApp = new CalendarApp({
|
_calendarApp = new CalendarApp({
|
||||||
onConfig: () => openCalendarConfig(),
|
onConfig: () => openCalendarConfig(),
|
||||||
|
onNote: () => openJournalNote(),
|
||||||
onClose: () => { _calendarApp = null; },
|
onClose: () => { _calendarApp = null; },
|
||||||
});
|
});
|
||||||
_calendarApp.updateDisplay(data);
|
_calendarApp.updateDisplay(data);
|
||||||
@@ -67,6 +69,55 @@ function openCalendarConfig() {
|
|||||||
dialog.render({ force: true });
|
dialog.render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function appendJournalNote(content, { dateLabel, realLabel } = {}) {
|
||||||
|
if (!content) return false;
|
||||||
|
const journalName = game.settings.get(MODULE_ID, 'calendarJournalName') || 'Journal de bord';
|
||||||
|
let journal = game.journal.getName(journalName);
|
||||||
|
if (!journal) {
|
||||||
|
journal = await JournalEntry.create({ name: journalName });
|
||||||
|
if (!journal) {
|
||||||
|
ui.notifications.error('Impossible de créer le journal');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!dateLabel || !realLabel) {
|
||||||
|
const c = readCalendar();
|
||||||
|
dateLabel = formatImperialDate(c);
|
||||||
|
realLabel = formatRealDate();
|
||||||
|
}
|
||||||
|
const safeContent = $('<div>').text(content).html().replace(/\n/g, '<br>');
|
||||||
|
const noteHtml = `<hr>
|
||||||
|
<h3>${realLabel} — ${dateLabel}</h3>
|
||||||
|
<p>${safeContent}</p>`;
|
||||||
|
let page = journal.pages.contents[0];
|
||||||
|
if (!page) {
|
||||||
|
[page] = await journal.createEmbeddedDocuments('JournalEntryPage', [{ name: 'Notes', text: { content: '' } }]);
|
||||||
|
}
|
||||||
|
if (!page) {
|
||||||
|
ui.notifications.error('Impossible de créer une page dans le journal');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const existing = page.text?.content || '';
|
||||||
|
await page.update({ 'text.content': existing + '\n' + noteHtml });
|
||||||
|
ui.notifications.info(`Note ajoutée au journal « ${journalName} »`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openJournalNote() {
|
||||||
|
const calendar = readCalendar();
|
||||||
|
const dateLabel = formatImperialDate(calendar);
|
||||||
|
const realLabel = formatRealDate();
|
||||||
|
const dialog = new JournalNoteDialog({
|
||||||
|
imperialDate: dateLabel,
|
||||||
|
realDate: realLabel,
|
||||||
|
onSave: async (content) => {
|
||||||
|
const ok = await appendJournalNote(content, { dateLabel, realLabel });
|
||||||
|
if (ok) dialog.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dialog.render({ force: true });
|
||||||
|
}
|
||||||
|
|
||||||
function registerCalCommand() {
|
function registerCalCommand() {
|
||||||
if (!ChatLogV2?.CHAT_COMMANDS) return;
|
if (!ChatLogV2?.CHAT_COMMANDS) return;
|
||||||
ChatLogV2.CHAT_COMMANDS.cal = {
|
ChatLogV2.CHAT_COMMANDS.cal = {
|
||||||
@@ -125,12 +176,27 @@ Hooks.once('init', () => {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
});
|
});
|
||||||
|
game.settings.register(MODULE_ID, 'calendarJournalName', {
|
||||||
|
name: 'Journal de bord',
|
||||||
|
hint: 'Nom du journal utilisé pour les notes de session. Créé automatiquement s\'il n\'existe pas.',
|
||||||
|
scope: 'world',
|
||||||
|
config: true,
|
||||||
|
type: String,
|
||||||
|
default: 'Journal de bord',
|
||||||
|
});
|
||||||
|
game.settings.register(MODULE_ID, 'calendarPosition', {
|
||||||
|
scope: 'client',
|
||||||
|
config: false,
|
||||||
|
type: Object,
|
||||||
|
default: {},
|
||||||
|
});
|
||||||
|
|
||||||
const loadTemplatesFn = foundry.applications?.handlebars?.loadTemplates || loadTemplates;
|
const loadTemplatesFn = foundry.applications?.handlebars?.loadTemplates || loadTemplates;
|
||||||
if (loadTemplatesFn) {
|
if (loadTemplatesFn) {
|
||||||
loadTemplatesFn([
|
loadTemplatesFn([
|
||||||
`modules/${MODULE_ID}/templates/calendar-app.hbs`,
|
`modules/${MODULE_ID}/templates/calendar-app.hbs`,
|
||||||
`modules/${MODULE_ID}/templates/calendar-config.hbs`,
|
`modules/${MODULE_ID}/templates/calendar-config.hbs`,
|
||||||
|
`modules/${MODULE_ID}/templates/journal-note-dialog.hbs`,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1193,6 +1193,14 @@ a.mgt2-world-link:hover {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mgt2-cal-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.mgt2-cal-date {
|
.mgt2-cal-date {
|
||||||
color: #d8c79a;
|
color: #d8c79a;
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
@@ -1352,3 +1360,79 @@ a.mgt2-world-link:hover {
|
|||||||
.mgt2-cal-conv-label {
|
.mgt2-cal-conv-label {
|
||||||
color: #8a7a5a;
|
color: #8a7a5a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Note button on calendar app */
|
||||||
|
|
||||||
|
.mgt2-cal-note-btn {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: #8a7a5a;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.85em;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: color 0.15s, background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mgt2-cal-note-btn:hover {
|
||||||
|
color: #d9b24c;
|
||||||
|
background: rgba(201, 162, 39, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Note dialog */
|
||||||
|
|
||||||
|
.mgt2-note-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
background: #1a1a2e;
|
||||||
|
color: #d8c79a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mgt2-note-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mgt2-note-row label {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #8a7a5a;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mgt2-note-value {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #d8c79a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mgt2-note-row textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px;
|
||||||
|
border: 1px solid #3a3a4e;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #252540;
|
||||||
|
color: #d8c79a;
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-family: inherit;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mgt2-note-row textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #c9a227;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mgt2-note-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,13 @@
|
|||||||
<span class="mgt2-cal-date">{{dateLabel}}</span>
|
<span class="mgt2-cal-date">{{dateLabel}}</span>
|
||||||
</div>
|
</div>
|
||||||
{{#if isGM}}
|
{{#if isGM}}
|
||||||
|
<div class="mgt2-cal-actions">
|
||||||
|
<button type="button" class="mgt2-cal-note-btn" title="Ajouter une note de bord">
|
||||||
|
<i class="fas fa-pen"></i>
|
||||||
|
</button>
|
||||||
<button type="button" class="mgt2-cal-config-btn" title="Configurer le calendrier">
|
<button type="button" class="mgt2-cal-config-btn" title="Configurer le calendrier">
|
||||||
<i class="fas fa-cog"></i>
|
<i class="fas fa-cog"></i>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<form class="mgt2-note-form">
|
||||||
|
<div class="mgt2-note-row">
|
||||||
|
<label>Date impériale</label>
|
||||||
|
<span class="mgt2-note-value">{{imperialDate}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mgt2-note-row">
|
||||||
|
<label>Date réelle</label>
|
||||||
|
<span class="mgt2-note-value">{{realDate}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mgt2-note-row">
|
||||||
|
<label for="note-content">Note</label>
|
||||||
|
<textarea id="note-content" name="content" rows="6" placeholder="Saisissez votre note…" autofocus></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mgt2-note-actions">
|
||||||
|
<button type="button" class="mgt2-cal-btn mgt2-cal-btn-primary" data-action="save">
|
||||||
|
<i class="fas fa-save"></i> Enregistrer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
Reference in New Issue
Block a user