In version 10 there has been a fundamental change to the JournalEntry
Document. Instead of content being stored in the Document itself, it now contains a collection of JournalEntryPage
Documents which can each contain content in various formats.
Migrations
All existing journal entries that are opened in version 10 will be automatically migrated to the new page structure. Entries will be given a new page with the same title as the parent entry, either a text
page or an image
page, depending on the content of the entry. Entries that have both text and an image will have the image page title prefixed by "Figure:".
All formatting and content will be preserved, there should be no change to how the content looks except that it now appears in the new multi-page enabled journal UI.
Journal entries inside world-level compendia will also be automatically migrated. Journal entries inside module compendia will need to be migrated by the owner of the compendium. This can be accomplished by calling pack.migrate()
.
Data Model
The content
and img
fields have been removed from the JournalEntry
data model. At time of writing, the JournalEntryPage
data model is the following:
{
_id: new fields.DocumentIdField(),
name: new fields.StringField({required: true, blank: false}),
type: new fields.StringField({required: true, initial: "text", choices: () => this.TYPES}),
image: new fields.SchemaField({
caption: new fields.StringField({required: false, initial: undefined})
}),
text: new fields.SchemaField({
content: new fields.HTMLField({required: false, initial: undefined}),
markdown: new fields.StringField({required: false, initial: undefined}),
format: new fields.NumberField({
initial: CONST.JOURNAL_ENTRY_PAGE_FORMATS.HTML,
choices: Object.values(CONST.JOURNAL_ENTRY_PAGE_FORMATS)
})
}),
video: new fields.SchemaField({
loop: new fields.BooleanField({required: false, initial: undefined}),
autoplay: new fields.BooleanField({required: false, initial: undefined}),
volume: new fields.AlphaField({required: true, step: 0.01, initial: .5}),
timestamp: new fields.NumberField({required: false, min: 0, initial: undefined}),
width: new fields.NumberField({required: false, positive: true, integer: true, initial: undefined}),
height: new fields.NumberField({required: false, positive: true, integer: true, initial: undefined})
}),
src: new fields.StringField({required: false, blank: false, nullable: true, initial: null}),
system: new fields.SystemDataField(this),
sort: new fields.IntegerSortField(),
ownership: new fields.DocumentOwnershipField({initial: {default: CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT}}),
flags: new fields.ObjectField()
}
Like Actors
or Items
, JournalEntryPages
may have different types
, with each type storing and displaying its data in a different way. Systems are able to add their own types to the standard ones provided by the core API, and modules may register custom sheets to provide additional or differing functionality to the default.
Adapting Existing Code
Existing code that interfaces with JournalEntry
Documents will need to be updated. A deprecation path is in place until version 12 such that existing code should continue to work (emitting a warning).
Updating an Entry's Image
Version 9 (Before)
entry.update({img: "image.webp"});
Version 10 (After)
// If this is a new entry:
entry.createEmbeddedDocuments("JournalEntryPage", [{
name: "Image Page",
type: "image",
src: "image.webp"
}]);
// If this is an existing entry, find the migrated image page
// first:
const page = entry.pages.find(p => p.type === "image");
page?.update({src: "image.webp"});
Updating an Entry's Content
Version 9 (Before)
entry.update({content: "<p>Lorem ipsum dolor...</p>"});
Version 10 (After)
// If this is a new entry:
entry.createEmbeddedDocuments("JournalEntryPage", [{
name: "Text Page",
type: "text",
text: {
content: "<p>Lorem ipsum dolor...</p>",
format: CONST.JOURNAL_ENTRY_PAGE_FORMATS.HTML
}
}]);
// If this is an existing entry, find the migrated text page
// first:
const page = entry.pages.find(p => p.type === "text");
page?.update({content: "<p>Lorem ipsum dolor...</p>"});
Consolidating a Folder of Journal Entries
In version 9, a common way to organize content was to place multiple journal entries inside a folder. Now that a single journal entry may contain multiple pages, it may be desirable to consolidate that content into a single journal entry instead. The following script provides a way to do that.
const folderName = "A Folder"; // Change this.
const folder = game.folders.find(f => {
return (f.name === folderName) && (f.type === "JournalEntry");
});
if ( !folder ) return;
const sort = folder.sorting === "m"
? SidebarDirectory._sortStandard
: SidebarDirectory._sortAlphabetical;
const contents = folder.contents.sort(sort);
const pages = contents.flatMap((entry, i) => {
const pages = [];
// Preserve sort order in the folder.
let sort = (i + 1) * 200_000;
const textPage = entry.pages.find(p => p.type === "text")?.toObject();
const imagePage = entry.pages.find(p => p.type === "image")?.toObject();
if ( textPage ) {
textPage.title.show = true;
textPage.sort = sort;
pages.push(textPage);
sort -= 100_000;
}
if ( imagePage ) {
imagePage.sort = sort;
pages.push(imagePage);
}
return pages;
});
JournalEntry.implementation.create({
pages,
name: folder.name,
folder: folder.folder?.id
});