Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions module.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"type": "module",
"version": "2.0.0",
"compatibility": {
"minimum": "14.359",
"verified": "13.351"
"minimum": "13.351",
"verified": "14.359"
},
"relationships": {
"systems": [],
Expand Down
8 changes: 4 additions & 4 deletions scripts/archivist-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -1238,17 +1238,17 @@ function installRealtimeSyncListeners() {
});
return;
} else if (sheetType === 'quest') {
res = await archivistApi.updateQuest(apiKey, flags.archivistId, {
questName: parent?.name || page.name,
});
// Quest page content is not a synced API field (quests use structured
// fields like successDefinition, not a free description), and name
// changes are handled by the updateJournalEntry title-sync hook. So a
// page-content edit has nothing to push — skip the redundant PATCH.
return;
} else if (sheetType === 'journal') {
res = await archivistApi.updateJournal(apiKey, {
id: flags.archivistId,
title: parent?.name || page.name,
content: Utils.toMarkdownIfHtml?.(html) || html,
});
return;
}

if (res && !res?.success && res?.isDescriptionTooLong) {
Expand Down
62 changes: 52 additions & 10 deletions scripts/dialogs/sync-dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export class SyncDialog extends foundry.applications.api.HandlebarsApplicationMi
this._lastToggleClick = row;
return;
}
// Range-select handles the clicked row itself; stop the event here so
// the root-level toggleRow action doesn't flip it back afterwards.
e.stopPropagation();
const allRows = [...this.element.querySelectorAll('tr[data-id]')];
const startIdx = allRows.indexOf(this._lastToggleClick);
const endIdx = allRows.indexOf(row);
Expand Down Expand Up @@ -102,6 +105,34 @@ export class SyncDialog extends foundry.applications.api.HandlebarsApplicationMi
btn.disabled = !hasSelected;
}

/**
* Update the in-progress panel's text directly rather than re-rendering the
* whole dialog for every synced item. The panel is rendered once before the
* sync loop begins; a skipped update (panel absent) is corrected by the
* final render when syncProgress is cleared.
* @private
*/
_updateProgressUI() {
const panel = this.element?.querySelector?.('.loading-panel');
if (!panel || !this.syncProgress) return;
const { processed, total, current } = this.syncProgress;
const textEl = panel.querySelector('.sync-progress-text');
if (textEl) {
textEl.textContent = `Processing ${processed} / ${total}…`;
}
let detailEl = panel.querySelector('.sync-progress-detail');
if (current) {
if (!detailEl) {
detailEl = document.createElement('span');
detailEl.className = 'sync-progress-detail';
panel.appendChild(detailEl);
}
detailEl.textContent = current;
} else if (detailEl) {
detailEl.remove();
}
}

_captureScrollPosition() {
const content = this.element?.querySelector?.('.sync-dialog-content');
if (content) {
Expand Down Expand Up @@ -244,7 +275,9 @@ export class SyncDialog extends foundry.applications.api.HandlebarsApplicationMi
// Confirm deletions before proceeding
const deleteDiffs = selectedDiffs.filter((d) => d.deleted);
if (deleteDiffs.length > 0) {
const names = deleteDiffs.map((d) => d.name).join(', ');
const names = deleteDiffs
.map((d) => foundry.utils.escapeHTML(String(d.name ?? '')))
.join(', ');
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { title: 'Confirm Deletion' },
content: `<p><strong>${deleteDiffs.length}</strong> journal${deleteDiffs.length > 1 ? 's' : ''} will be permanently deleted:</p><p>${names}</p><p>This cannot be undone. Continue?</p>`,
Expand All @@ -267,7 +300,7 @@ export class SyncDialog extends foundry.applications.api.HandlebarsApplicationMi
for (const d of selectedDiffs) {
this.syncProgress.current = `${d.type}: ${d.name}`;
this.syncProgress.processed = processed;
await this.render();
this._updateProgressUI();
try {
await this._applyDiff(d);
} catch (e) {
Expand All @@ -280,7 +313,7 @@ export class SyncDialog extends foundry.applications.api.HandlebarsApplicationMi
for (const i of selectedImports) {
this.syncProgress.current = `${i.type}: ${i.name}`;
this.syncProgress.processed = processed;
await this.render();
this._updateProgressUI();
try {
await this._applyImport(i, campaignId, apiKey);
} catch (e) {
Expand All @@ -289,6 +322,9 @@ export class SyncDialog extends foundry.applications.api.HandlebarsApplicationMi
}
processed++;
}
// Reflect the final processed count before the panel is torn down.
this.syncProgress.processed = processed;
this._updateProgressUI();

this.syncProgress = null;

Expand Down Expand Up @@ -677,7 +713,7 @@ export class SyncDialog extends foundry.applications.api.HandlebarsApplicationMi
type,
id,
name: row.character_name || row.name || row.title || 'Untitled',
description: row.description || row.summary || '',
description: row.description || row.summary || row.content || '',
image: row.image || '',
selected: false,
createCore: false,
Expand Down Expand Up @@ -862,7 +898,7 @@ export class SyncDialog extends foundry.applications.api.HandlebarsApplicationMi
: null;
if (!sheetType) return;
// Convert markdown from Archivist to HTML for Foundry storage (sessions use summary)
const markdownContent = String(row.description || row.summary || '');
const markdownContent = String(row.description || row.summary || row.content || '');
const htmlContent = Utils.markdownToStoredHtml(markdownContent);

// Determine folder ID based on sheet type using saved destinations
Expand All @@ -882,10 +918,16 @@ export class SyncDialog extends foundry.applications.api.HandlebarsApplicationMi
folderId = destinations.location;
} else if (sheetType === 'faction' && destinations.faction) {
folderId = destinations.faction;
} else if (sheetType === 'journal' && destinations.journal) {
folderId = destinations.journal;
} else if (sheetType === 'quest' && destinations.quest) {
folderId = destinations.quest;
} else if (sheetType === 'journal') {
// Worlds set up before journals existed have no saved destination —
// fall back to ensuring the default folder like recaps do.
folderId =
destinations.journal ||
(await Utils.ensureJournalFolder('Archivist - Journals'));
} else if (sheetType === 'quest') {
folderId =
destinations.quest ||
(await Utils.ensureJournalFolder('Archivist - Quests'));
} else if (sheetType === 'recap') {
// For sessions/recaps, use Recaps folder and preserve session_date ordering
folderId = await Utils.ensureJournalFolder('Recaps');
Expand Down Expand Up @@ -1160,7 +1202,7 @@ export class SyncDialog extends foundry.applications.api.HandlebarsApplicationMi
else if (itemId) targetDoc = game.items?.get?.(itemId) || null;
else if (sceneId) targetDoc = game.scenes?.get?.(sceneId) || null;

const md = String(row.description || row.summary || '');
const md = String(row.description || row.summary || row.content || '');
const html = Utils.markdownToStoredHtml(md);

if (targetDoc) {
Expand Down
32 changes: 26 additions & 6 deletions scripts/dialogs/world-setup-dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -1828,6 +1828,26 @@ export class WorldSetupDialog extends foundry.applications.api.HandlebarsApplica

// Removed Archivist journal folder setup; journals are created only in user destinations

/**
* Update the Step 6 progress bar/text in place instead of re-rendering the
* whole wizard step for every imported entity. The panel is rendered once
* before the import loops begin; the final render after the loops reflects
* the completed state.
* @private
*/
_updateSyncStatusUI() {
const root = this.element;
if (!root || !this.syncStatus?.total) return;
const { processed, total, current } = this.syncStatus;
const pct = total > 0 ? Math.round((processed / total) * 100) : 0;
const fill = root.querySelector('.ws-sync-progress-fill');
if (fill) fill.style.width = `${pct}%`;
const text = root.querySelector('.ws-sync-progress-text');
if (text) {
text.textContent = `${processed} / ${total} — ${current || ''}`;
}
}

async _importArchivistMissing(apiKey, campaignId) {
try {
const allCharacters = this.archivistCandidates?.characters || [];
Expand Down Expand Up @@ -2385,13 +2405,13 @@ export class WorldSetupDialog extends foundry.applications.api.HandlebarsApplica
this.syncStatus.current = `Import ${c.type || c.character_type || 'PC'}: ${c.character_name || c.name}`;
await createActor(c);
this.syncStatus.processed++;
await this.render();
this._updateSyncStatusUI();
}
for (const it of items) {
this.syncStatus.current = `Import Item: ${it.name}`;
await createItem(it);
this.syncStatus.processed++;
await this.render();
this._updateSyncStatusUI();
}
locations.sort((a, b) =>
String(a.name || '').localeCompare(String(b.name || ''))
Expand All @@ -2400,7 +2420,7 @@ export class WorldSetupDialog extends foundry.applications.api.HandlebarsApplica
this.syncStatus.current = `Import Location: ${l.name || l.title}`;
await upsertIntoContainer(l, 'Location');
this.syncStatus.processed++;
await this.render();
this._updateSyncStatusUI();
}
factions.sort((a, b) =>
String(a.name || '').localeCompare(String(b.name || ''))
Expand All @@ -2409,7 +2429,7 @@ export class WorldSetupDialog extends foundry.applications.api.HandlebarsApplica
this.syncStatus.current = `Import Faction: ${f.name || f.title}`;
await upsertIntoContainer(f, 'Faction');
this.syncStatus.processed++;
await this.render();
this._updateSyncStatusUI();
}

// Import Journals with folder structure (GM-only, point-in-time snapshot)
Expand Down Expand Up @@ -2497,7 +2517,7 @@ export class WorldSetupDialog extends foundry.applications.api.HandlebarsApplica
console.warn('[World Setup] Failed to import journal', j, e);
}
this.syncStatus.processed++;
await this.render();
this._updateSyncStatusUI();
}
ui.notifications?.info?.(
'Journals imported as GM-only. Folder structure reflects the import snapshot and will not auto-sync.'
Expand Down Expand Up @@ -2575,7 +2595,7 @@ export class WorldSetupDialog extends foundry.applications.api.HandlebarsApplica
console.warn('[World Setup] Failed to import quest', q, e);
}
this.syncStatus.processed++;
await this.render();
this._updateSyncStatusUI();
}

// After creating journals and pages, hydrate link graph from Archivist Links
Expand Down
6 changes: 3 additions & 3 deletions scripts/modules/projection/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ export function mergeArchivistSection(existingHtml, archivistHtml) {
}

/**
* Strip HTML down to plain text via Foundry's TextEditor if available.
* Strip HTML down to plain text. Foundry exposes no core strip-to-text
* helper (in v13 or v14), so parse into a detached element and read its
* text content.
* @param {string} html
*/
export function stripHtml(html) {
try {
const s = String(html ?? '');
const te = foundry?.utils?.TextEditor;
if (te?.stripHTML) return te.stripHTML(s);
const tmp = document.createElement('div');
tmp.innerHTML = s;
return (tmp.textContent || '').trim();
Expand Down
Loading