Register

Version 10 Journal Changes

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
});