Page tree
Skip to end of metadata
Go to start of metadata

Stage 0

Maven setup

https://wiki.magnolia-cms.com/display/DEV/Maven+setup

settings.xml

Git repo

https://git.magnolia-cms.com/projects/FORGE/repos/content-editor-workshop/browse


and use clone link from top left

or


git clone https://<username>@git.magnolia-cms.com/scm/forge/content-editor-workshop.git

(replace <username> by your user name)

Stage 1

Maven dependencies

To be put into magnolia-module-content-editor-workshop/pom.xml

<dependency>
  <groupId>info.magnolia.editor</groupId>
  <artifactId>magnolia-content-editor</artifactId>
</dependency>
<dependency>
  <groupId>info.magnolia.block</groupId>
  <artifactId>magnolia-block-templating</artifactId>
</dependency>

Magnolia module dependencies

To be put into src/main/resources/META-INF/magnolia/content-editor-workshop.xml

<dependency>
 <name>content-editor</name>
 <version>1.0.4/*</version>
</dependency>
<dependency>
 <name>block-templating</name>
 <version>1.0.4/*</version>
</dependency>

Custom workspace and node type import

To be put into src/main/resources/META-INF/magnolia/content-editor-workshop.xml

<repositories>
 <repository>
    <name>magnolia</name>
    <workspaces>
       <workspace>journals</workspace>
    </workspaces>
    <nodeTypeFile>/mgnl-nodetypes/content-editor-nodetypes.xml</nodeTypeFile>
 </repository>
</repositories>

App definition

src/main/resources/content-editor-workshop/apps/journals.yaml

Top-level properties
appClass: info.magnolia.ui.contentapp.ContentApp
class: info.magnolia.ui.contentapp.ContentAppDescriptor
theme: content-editor
icon: icon-work-item
label: Journals
Browser subapp config
browser:
  subAppClass: info.magnolia.ui.contentapp.browser.BrowserSubApp
  class: info.magnolia.ui.contentapp.browser.BrowserSubAppDescriptor
  closable: false
  contentConnector:
   includeProperties: false
   workspace: journals
   rootPath: /
   defaultOrder: jcrName
   nodeTypes:
     - icon: icon-file-text
       name: mgnl:composition
       strict: true
     - icon: icon-folder-l
       name: mgnl:folder
       strict: true
  workbench:
   dropConstraintClass: info.magnolia.ui.workbench.tree.drop.AlwaysTrueDropConstraint
   editable: true
   contentViews:
     - name: tree
       class: info.magnolia.ui.workbench.tree.TreePresenterDefinition
       columns: &defaultColumns
         - name: jcrName
           width: 0.6
           sortable: true
           propertyName: jcrName
           class: info.magnolia.ui.workbench.column.definition.PropertyColumnDefinition
         - name: author
           width: 0.2
           sortable: true
           propertyName: author
           class: info.magnolia.ui.workbench.column.definition.PropertyColumnDefinition
         - name: moddate
           width: 0.1
           sortable: true
           displayInChooseDialog: false
           formatterClass: info.magnolia.ui.workbench.column.DateColumnFormatter
           propertyName: mgnl:lastModified
           class: info.magnolia.ui.workbench.column.definition.MetaDataColumnDefinition
         - name: status
           width: 0.1
           sortable: true
           displayInChooseDialog: false
           formatterClass: info.magnolia.ui.workbench.column.StatusColumnFormatter
           class: info.magnolia.ui.workbench.column.definition.StatusColumnDefinition
     - name: list
       class: info.magnolia.ui.workbench.list.ListPresenterDefinition
       columns: *defaultColumns
     - name: search
       class: info.magnolia.ui.workbench.search.SearchPresenterDefinition
       columns: *defaultColumns
  actions:
   addEntry:
     subAppId: editor
     icon: icon-add-item
     nodeType: mgnl:composition
     appName: journals
     class: info.magnolia.ui.contentapp.detail.action.CreateItemActionDefinition
     availability:
       writePermissionRequired: true
       root: true
       nodeTypes:
         folder: mgnl:folder
       rules:
         - name: IsNotDeletedRule
           implementationClass: info.magnolia.ui.framework.availability.IsNotDeletedRule
   editEntry:
     subAppId: editor
     icon: icon-edit
     appName: journals
     class: info.magnolia.ui.contentapp.detail.action.EditItemActionDefinition
     availability:
       writePermissionRequired: true
       nodeTypes:
         entry: mgnl:composition
       rules:
         - name: IsNotDeletedRule
           implementationClass: info.magnolia.ui.framework.availability.IsNotDeletedRule
   renameEntry:
     icon: icon-edit
     dialogName: ui-framework:folder	
     class: info.magnolia.ui.framework.action.OpenEditDialogActionDefinition
     availability:
       writePermissionRequired: true
       nodeTypes:
         entry: mgnl:composition
       rules:
         - name: IsNotDeletedRule
           implementationClass: info.magnolia.ui.framework.availability.IsNotDeletedRule
   delete:
     icon: icon-delete
     class: info.magnolia.ui.framework.action.DeleteItemActionDefinition
     availability:
       writePermissionRequired: true
       rules:
         - name: IsNotDeletedRule
           implementationClass: info.magnolia.ui.framework.availability.IsNotDeletedRule
   confirmDeleteEntry:
     successActionName: delete
     icon: icon-delete
     class: info.magnolia.ui.framework.action.ConfirmationActionDefinition
     availability:
       multiple: true
       writePermissionRequired: true
       rules:
         - name: IsNotDeletedRule
           implementationClass: info.magnolia.ui.framework.availability.IsNotDeletedRule
  actionbar:
   defaultAction: editEntry
   sections:
     - name: root
       availability:
         nodes: false
         root: true
       groups:
         - name: addActions
           items:
             - name: addEntry
             - name: addFolder
         - name: editActions
           items:
             - name: editEntry
     - name: folder
       availability:
         nodeTypes:
           folder: mgnl:folder
       groups:
         - name: addActions
           items:
             - name: addEntry
             - name: addFolder
         - name: editActions
           items:
             - name: editFolder
             - name: confirmDeleteFolder
     - name: entry
       availability:
         nodeTypes:
           entry: mgnl:composition
       groups:
         - name: addActions
           items:
             - name: addEntry
             - name: addFolder
         - name: editActions
           items:
             - name: editEntry
             - name: renameEntry
             - name: confirmDeleteEntry
Editor subapp
editor:
  class: info.magnolia.editor.app.ContentEditorSubAppDescriptor
  actions:
    close:
      class: info.magnolia.editor.action.CloseContentEditorActionDefinition
    save: &saveAction
      class: info.magnolia.editor.action.SaveContentActionDefinition
  contentConnector:
    workspace: journals
    nodeTypes:
      - icon: icon-node-content
        name: mgnl:composition
        strict: true
  contentDefinition:
    outlineFields:
      title:
        class: info.magnolia.editor.app.field.ExpandingTextFieldDefinition
        required: true
        styleName: title
      lead:
        class: info.magnolia.editor.app.field.ExpandingTextFieldDefinition
        #This currently does not function due to the bug in ExpandingTextField
        rows: 3
        required: true
        styleName: lead-text
      image:
        class: info.magnolia.ui.form.field.definition.LinkFieldDefinition
        targetWorkspace: dam
        appName: assets
        identifierToPathConverter:
          class: info.magnolia.dam.app.assets.field.translator.AssetCompositeIdKeyTranslator
        contentPreviewDefinition:
          contentPreviewClass: info.magnolia.dam.app.ui.field.DamFilePreviewComponent
      entryMetaData:
        class: info.magnolia.editor.app.field.CollapsibleCompositeFieldDefinition
        layout: vertical
        styleName: toggleable
        fields:
          created:
            class: info.magnolia.ui.form.field.definition.DateFieldDefinition
            time: true
            required: true
          author:
            class: info.magnolia.editor.app.field.PlaceholderTextFieldDefinition
          jcrName:
            class: info.magnolia.editor.app.field.PlaceholderTextFieldDefinition
            required: true
    blocks:
      - text
      - image
      - video
      - externalLink
    initialBlock: text
    defaultBlock: image

Type mappings

src/main/resources/META-INF/magnolia/content-editor-workshop.xml

<components>
  <id>app-journals-editor</id>

  <component>
    <type>info.magnolia.editor.content.blockpicker.BlockTypePicker</type>
    <implementation>info.magnolia.editor.content.blockpicker.BlockTypePicker</implementation>
    <scope>singleton</scope>
  </component>

  <component>
    <type>info.magnolia.editor.content.blockpicker.BlockPickerHelper</type>
    <implementation>info.magnolia.editor.content.blockpicker.BlockPickerHelper</implementation>
    <scope>singleton</scope>
  </component>

  <component>
    <type>info.magnolia.editor.content.blockpicker.BlockRegistryContentConnector</type>
    <implementation>info.magnolia.editor.content.blockpicker.BlockRegistryContentConnector</implementation>
  </component>

  <component>
    <type>info.magnolia.editor.content.ContentDefinition</type>
    <provider>info.magnolia.editor.app.ContentDefinitionProvider</provider>
  </component>

  <type-mapping>
    <type>info.magnolia.editor.content.editor.ContentEditor</type>
    <implementation>info.magnolia.editor.content.editor.DefaultContentEditor</implementation>
  </type-mapping>
</components>

I18n

Put into src/main/resources/content-editor-workshop/i18n/content-editor-workshop-messages_en.properties

journals.app.label=Entries
journals.browser.label=Entries
journals.browser.actionbar.sections.root.label=Entries
journals.browser.actionbar.sections.entry.label=Entry
journals.browser.actions.addEntry.label=Add entry
journals.browser.actions.editEntry.label=Edit entry
journals.browser.actions.renameEntry.label=Rename entry
journals.browser.actions.confirmDeleteEntry.label=Delete entry

journals.browser.views.treeview.author.label=Author
journals.browser.views.listview.author.label=Author
journals.browser.views.searchview.author.label=Author

journals.entry.label=Entry

# Confirmation dialog - delete entry
journals.browser.actions.confirmDeleteEntry.confirmationHeader=Delete this entry?
journals.browser.actions.confirmDeleteEntry.confirmationMessage=This entry will be marked for deletion.
journals.browser.actions.confirmDeleteEntry.cancelLabel=Cancel
journals.browser.actions.confirmDeleteEntry.proceedLabel=Yes, delete

# Choose dialog
journals.chooseDialog.label=Entries

journals.editor.title.label=Title
journals.editor.title.description=The actual main title of the entry.
journals.editor.title.placeholder=Type here to add title...
journals.editor.lead.label=Lead text
journals.editor.lead.description=The lead text teasing the entry.
journals.editor.lead.placeholder=Type here to add lead text...

journals.editor.image.label=Lead Image
journals.editor.image.description=The main image displayed with this entry.
journals.editor.imageAltText.label=Alt text
journals.editor.imageAltText.description=The alternative text for the lead visual.
journals.editor.imageAltText.placeholder=Type here to add an alternative text...
journals.editor.imageCaption.label=Caption
journals.editor.imageCaption.description=The caption for the lead visual.
journals.editor.imageCaption.placeholder=Type here to add an image caption...
journals.editor.imageCredit.label=Image Credit
journals.editor.imageCredit.description=The source or copyright holder of the lead visual.
journals.editor.imageCredit.placeholder=Type here to add image credit...

journals.editor.entryMetaData.label=Meta data
journals.editor.entryMetaData.created.label=Creation date
journals.editor.entryMetaData.created.description=Date and time the entry was written
journals.editor.entryMetaData.updated.label=Update date
journals.editor.entryMetaData.updated.description=Date and time the entry was last updated
journals.editor.entryMetaData.jcrName.label=URI
journals.editor.entryMetaData.jcrNauthorInfoame.description=The slug of the entry which is part of a URL that identifies this entry in human-readable keywords. Slugs are generally entirely lowercase, with accented characters replaced by letters from the English alphabet and whitespace characters replaced by a dash or an underscore to avoid being encoded. Punctuation marks are generally removed, and some also remove short, common words such as conjunctions.
journals.editor.entryMetaData.jcrName.placeholder=Type URI (it's the title by default)...
journals.editor.entryMetaData.author.label=Author name
journals.editor.entryMetaData.author.description=The name of the author who wrote the entry.
journals.editor.entryMetaData.author.placeholder=Type your name...
journals.editor.entryMetaData.location.label=Location name
journals.editor.entryMetaData.location.description=Location name where the author has written the piece
journals.editor.entryMetaData.location.placeholder=Type your location...

ui-framework.folder=Rename entry
ui-framework.folder.folder=Entry
ui-framework.folder.folder.jcrName=Entry Name

# editor save/close buttons
journals.editor.actions.close.label=Close
journals.editor.actions.save.label=Save

# messages
journals.editor.actions.save.successMessage=Saved successfully
journals.editor.actions.save.failureMessage=Saving failed
journals.editor.actions.save.errorMessage=Error while saving

Version handler

In info.magnolia.workshop.editor.setup.ContentEditorWorkshopModuleVersionHandler.java

imports
import info.magnolia.module.InstallContext;
import info.magnolia.module.delta.Task;
import info.magnolia.ui.admincentral.setup.RegisterAppIntoAppLauncherTask;

import java.util.ArrayList;
import java.util.List;
@Override
protected List<Task> getExtraInstallTasks(InstallContext installContext) {
    final List<Task> parentTasks = super.getExtraInstallTasks(installContext);
    final List<Task> tasks = new ArrayList<>(parentTasks);

    tasks.add(new RegisterAppIntoAppLauncherTask("journals", "edit"));
		
    return tasks;
}

Area template

Stage 2

Page area definition

journalEntryArea.ftl
[#assign entryContent = cmsfn.contentById(content.entry, "journals") /]

<article class="journal-entry">
    <header>
        [#if entryContent.author?hasContent]
            <span class="author">${entryContent.author}</span> &#124;
        [/#if]

        <time datetime="${entryContent.created?isoLocal}">${entryContent.created?date?string.long}</time>

        <h1>${entryContent.title}</h1>

        <h3>${entryContent.lead}</h3>

        [#-- Lead image (uses mtk image macro) --]
        [#if entryContent.image?hasContent]
            [#include "/mtk/templates/macros/image.ftl"]
            [#assign rendition = damfn.getRendition(entryContent.image, "original")]
            [@image rendition entryContent "lead-image" false {} /]
        [/#if]
    </header>

    [#assign blocks = cmsfn.children(entryContent, "mgnl:block") /]
    [#list blocks as block]
        [@cms.block content=block /]
    [/#list]
</article>

Page template definition

journalEntryPage.yaml
# We use the basic template from mtk which provides us with reasonable defaults already
templateScript: /mtk/templates/pages/basic.ftl
renderType: freemarker
class: info.magnolia.module.site.templates.PageTemplateDefinition
dialog: content-editor-workshop:pages/journalEntryPage
cssFiles:
  journal:
    link: /.resources/content-editor-workshop/webresources/journal.css
areas:
  htmlHeader:
    type: noComponent
    createAreaNode: false
    templateScript: /mtk/templates/areas/htmlHeader.ftl
  main:
    type: noComponent
    createAreaNode: false
    templateScript: /content-editor-workshop/templates/pages/areas/journalEntryArea.ftl

Page dialog definition

journalEntryPage.yaml
form:
  tabs:
    - name: tabEntry
      fields:
      - name: entry
        class: info.magnolia.ui.form.field.definition.LinkFieldDefinition
        targetWorkspace: journals
        identifierToPathConverter:
          class: info.magnolia.ui.form.field.converter.BaseIdentifierToPathConverter
        appName: journals
        required: true

actions:
  commit:
    class: info.magnolia.ui.admincentral.dialog.action.SaveDialogActionDefinition
  cancel:
    class: info.magnolia.ui.admincentral.dialog.action.CancelDialogActionDefinition

Area template

journalEntryArea.ftl
[#assign entryContent = cmsfn.contentById(content.entry, "journals") /]

<article class="journal-entry">
    <header>
        [#if entryContent.author?hasContent]
            <span class="author">${entryContent.author}</span> &#124;
        [/#if]

        <time datetime="${entryContent.created?isoLocal}">${entryContent.created?date?string.long}</time>

        <h1>${entryContent.title}</h1>

        <h3>${entryContent.lead}</h3>

        [#-- Lead image (uses mtk image macro) --]
        [#if entryContent.image?hasContent]
            [#include "/mtk/templates/macros/image.ftl"]
            [#assign rendition = damfn.getRendition(entryContent.image, "original")]
            [@image rendition entryContent "lead-image" false {} /]
        [/#if]
    </header>

    [#assign blocks = cmsfn.children(entryContent, "mgnl:block") /]
    [#list blocks as block]
        [@cms.block content=block /]
    [/#list]
</article>

I18n

# page
content-editor-workshop.templates.pages.journalEntryPage=Journal entry page
content-editor-workshop.pages.journalEntryPage.tabEntry.label=Journal entry
content-editor-workshop.pages.journalEntryPage.tabEntry.entry.label=Journal entry

CSS

journal.css
@import url('https://fonts.googleapis.com/css?family=Spectral:400,600');
@import url('https://fonts.googleapis.com/css?family=Overpass:400,400i,700,700i');

html, body {
    background-color: whitesmoke;
    color: black;
    font-family: 'Overpass', sans-serif;
    font-size: 18px;
    margin: 0;
    padding: 0;
}

article.journal-entry {
    background-color: white;
    width: 80%;
    max-width: 900px;
    padding: 100px;
    box-shadow: 0 0 2em rgba(0, 0, 0, 0.2);
    margin: 0 auto;
}

article.journal-entry h1 {
    font-family: 'Spectral', serif;
    font-weight: 600;
    font-size: 2.5em;
}

article.journal-entry h3 {
    font-style: italic;
}

Stage 3

Complete files after manipulation

Journals App definition
appClass: info.magnolia.ui.contentapp.ContentApp
class: info.magnolia.ui.contentapp.ContentAppDescriptor
theme: content-editor
icon: icon-work-item
label: Journals
subApps:
  browser:
    subAppClass: info.magnolia.ui.contentapp.browser.BrowserSubApp
    class: info.magnolia.ui.contentapp.browser.BrowserSubAppDescriptor
    closable: false
    contentConnector:
      includeProperties: false
      workspace: journals
      rootPath: /
      defaultOrder: jcrName
      nodeTypes:
        - icon: icon-file-text
          name: mgnl:composition
          strict: true
    workbench:
      dropConstraintClass: info.magnolia.ui.workbench.tree.drop.AlwaysTrueDropConstraint
      editable: true
      contentViews:
        - name: tree
          class: info.magnolia.ui.workbench.tree.TreePresenterDefinition
          columns: &defaultColumns
            - name: jcrName
              width: 0.6
              sortable: true
              propertyName: jcrName
              class: info.magnolia.ui.workbench.column.definition.PropertyColumnDefinition
            - name: author
              width: 0.2
              sortable: true
              propertyName: author
              class: info.magnolia.ui.workbench.column.definition.PropertyColumnDefinition
            - name: moddate
              width: 0.1
              sortable: true
              displayInChooseDialog: false
              formatterClass: info.magnolia.ui.workbench.column.DateColumnFormatter
              propertyName: mgnl:lastModified
              class: info.magnolia.ui.workbench.column.definition.MetaDataColumnDefinition
            - name: status
              width: 0.1
              sortable: true
              displayInChooseDialog: false
              formatterClass: info.magnolia.ui.workbench.column.StatusColumnFormatter
              class: info.magnolia.ui.workbench.column.definition.StatusColumnDefinition
        - name: list
          class: info.magnolia.ui.workbench.list.ListPresenterDefinition
          columns: *defaultColumns
        - name: search
          class: info.magnolia.ui.workbench.search.SearchPresenterDefinition
          columns: *defaultColumns
    actions:
      addEntry:
        subAppId: editor
        icon: icon-add-item
        nodeType: mgnl:composition
        appName: journals
        class: info.magnolia.ui.contentapp.detail.action.CreateItemActionDefinition
        availability:
          writePermissionRequired: true
          root: true
          nodeTypes:
            folder: mgnl:folder
          rules:
            - name: IsNotDeletedRule
              implementationClass: info.magnolia.ui.framework.availability.IsNotDeletedRule
      editEntry:
        subAppId: editor
        icon: icon-edit
        appName: journals
        class: info.magnolia.ui.contentapp.detail.action.EditItemActionDefinition
        availability:
          writePermissionRequired: true
          nodeTypes:
            entry: mgnl:composition
          rules:
            - name: IsNotDeletedRule
              implementationClass: info.magnolia.ui.framework.availability.IsNotDeletedRule
      renameEntry:
        icon: icon-edit
        dialogName: ui-framework:folder
        class: info.magnolia.ui.framework.action.OpenEditDialogActionDefinition
        availability:
          writePermissionRequired: true
          nodeTypes:
            entry: mgnl:composition
          rules:
            - name: IsNotDeletedRule
              implementationClass: info.magnolia.ui.framework.availability.IsNotDeletedRule
      delete:
        icon: icon-delete
        class: info.magnolia.ui.framework.action.DeleteItemActionDefinition
        availability:
          writePermissionRequired: true
          rules:
            - name: IsNotDeletedRule
              implementationClass: info.magnolia.ui.framework.availability.IsNotDeletedRule
      confirmDeleteEntry:
        successActionName: delete
        icon: icon-delete
        class: info.magnolia.ui.framework.action.ConfirmationActionDefinition
        availability:
          multiple: true
          writePermissionRequired: true
          rules:
            - name: IsNotDeletedRule
              implementationClass: info.magnolia.ui.framework.availability.IsNotDeletedRule
    actionbar:
      defaultAction: editEntry
      sections:
        - name: root
          availability:
            nodes: false
            root: true
          groups:
            - name: addActions
              items:
                - name: addEntry
            - name: editActions
              items:
                - name: editEntry
        - name: entry
          availability:
            nodeTypes:
              entry: mgnl:composition
          groups:
            - name: addActions
              items:
                - name: addEntry
            - name: editActions
              items:
                - name: editEntry
                - name: renameEntry
                - name: confirmDeleteEntry
  editor:
    class: info.magnolia.editor.app.ContentEditorSubAppDescriptor
    actions:
      close:
        class: info.magnolia.editor.action.CloseContentEditorActionDefinition
      save: &saveAction
        class: info.magnolia.editor.action.SaveContentActionDefinition
    contentConnector:
      workspace: journals
      nodeTypes:
        - icon: icon-node-content
          name: mgnl:composition
          strict: true
    contentDefinition:
      outlineFields:
        title:
          class: info.magnolia.editor.app.field.ExpandingTextFieldDefinition
          required: true
          styleName: title
        lead:
          class: info.magnolia.editor.app.field.ExpandingTextFieldDefinition
          #This currently does not function due to the bug in ExpandingTextField
          rows: 3
          required: true
          styleName: lead-text
        image:
          class: info.magnolia.ui.form.field.definition.LinkFieldDefinition
          targetWorkspace: dam
          appName: assets
          identifierToPathConverter:
            class: info.magnolia.dam.app.assets.field.translator.AssetCompositeIdKeyTranslator
          contentPreviewDefinition:
            contentPreviewClass: info.magnolia.dam.app.ui.field.DamFilePreviewComponent
        entryMetaData:
          class: info.magnolia.editor.app.field.CollapsibleCompositeFieldDefinition
          layout: vertical
          styleName: toggleable
          fields:
            created:
              class: info.magnolia.ui.form.field.definition.DateFieldDefinition
              time: true
              required: true
            author:
              class: info.magnolia.editor.app.field.PlaceholderTextFieldDefinition
            jcrName:
              class: info.magnolia.editor.app.field.PlaceholderTextFieldDefinition
              required: true
      blocks:
        - text
        - image
        - video
        - externalLink
        - mapEmbed
      initialBlock: text
      defaultBlock: image
Custom block definition
class: info.magnolia.editor.content.block.stock.FieldSetBlockDefinition
templateId: content-editor-workshop:blocks/mapEmbed
icon: content-item
fields:
  embedCode:
    class: info.magnolia.ui.form.field.definition.TextFieldDefinition
    required: true
    rows: 4
Custom block i18n keys
journals.app.label=Entries
journals.browser.label=Entries
journals.browser.actionbar.sections.root.label=Entries
journals.browser.actionbar.sections.entry.label=Entry
journals.browser.actions.addEntry.label=Add entry
journals.browser.actions.editEntry.label=Edit entry
journals.browser.actions.renameEntry.label=Rename entry
journals.browser.actions.confirmDeleteEntry.label=Delete entry

journals.browser.views.treeview.author.label=Author
journals.browser.views.listview.author.label=Author
journals.browser.views.searchview.author.label=Author

journals.entry.label=Entry

# Confirmation dialog - delete entry
journals.browser.actions.confirmDeleteEntry.confirmationHeader=Delete this entry?
journals.browser.actions.confirmDeleteEntry.confirmationMessage=This entry will be marked for deletion.
journals.browser.actions.confirmDeleteEntry.cancelLabel=Cancel
journals.browser.actions.confirmDeleteEntry.proceedLabel=Yes, delete

# Choose dialog
journals.chooseDialog.label=Entries

journals.editor.title.label=Title
journals.editor.title.description=The actual main title of the entry.
journals.editor.title.placeholder=Type here to add title...
journals.editor.lead.label=Lead text
journals.editor.lead.description=The lead text teasing the entry.
journals.editor.lead.placeholder=Type here to add lead text...

journals.editor.image.label=Lead Image
journals.editor.image.description=The main image displayed with this entry.
journals.editor.imageAltText.label=Alt text
journals.editor.imageAltText.description=The alternative text for the lead visual.
journals.editor.imageAltText.placeholder=Type here to add an alternative text...
journals.editor.imageCaption.label=Caption
journals.editor.imageCaption.description=The caption for the lead visual.
journals.editor.imageCaption.placeholder=Type here to add an image caption...
journals.editor.imageCredit.label=Image Credit
journals.editor.imageCredit.description=The source or copyright holder of the lead visual.
journals.editor.imageCredit.placeholder=Type here to add image credit...

journals.editor.entryMetaData.label=Meta data
journals.editor.entryMetaData.created.label=Creation date
journals.editor.entryMetaData.created.description=Date and time the entry was written
journals.editor.entryMetaData.updated.label=Update date
journals.editor.entryMetaData.updated.description=Date and time the entry was last updated
journals.editor.entryMetaData.jcrName.label=URI
journals.editor.entryMetaData.jcrNauthorInfoame.description=The slug of the entry which is part of a URL that identifies this entry in human-readable keywords. Slugs are generally entirely lowercase, with accented characters replaced by letters from the English alphabet and whitespace characters replaced by a dash or an underscore to avoid being encoded. Punctuation marks are generally removed, and some also remove short, common words such as conjunctions.
journals.editor.entryMetaData.jcrName.placeholder=Type URI (it's the title by default)...
journals.editor.entryMetaData.author.label=Author name
journals.editor.entryMetaData.author.description=The name of the author who wrote the entry.
journals.editor.entryMetaData.author.placeholder=Type your name...
journals.editor.entryMetaData.location.label=Location name
journals.editor.entryMetaData.location.description=Location name where the author has written the piece
journals.editor.entryMetaData.location.placeholder=Type your location...

ui-framework.folder=Rename entry
ui-framework.folder.folder=Entry
ui-framework.folder.folder.jcrName=Entry Name

# editor save/close buttons
journals.editor.actions.close.label=Close
journals.editor.actions.save.label=Save

# messages
journals.editor.actions.save.successMessage=Saved successfully
journals.editor.actions.save.failureMessage=Saving failed
journals.editor.actions.save.errorMessage=Error while saving

# page
content-editor-workshop.templates.pages.journalEntryPage=Journal entry page
content-editor-workshop.pages.journalEntryPage.tabEntry.label=Journal entry
content-editor-workshop.pages.journalEntryPage.tabEntry.entry.label=Journal entry

# custom block
mapEmbed.embedCode.label=Embed code
blocks.mapEmbed.label=Embedded map
Custom block freemarker template
<div class="map-embed-wrapper">
    ${cmsfn.decode(content).embedCode}
</div>
Custom block template definition
templateScript: /content-editor-workshop/templates/blocks/mapEmbed.ftl
renderType: freemarker
Custom block css
@import url('https://fonts.googleapis.com/css?family=Spectral:400,600');
@import url('https://fonts.googleapis.com/css?family=Overpass:400,400i,700,700i');

html, body {
    background-color: whitesmoke;
    color: black;
    font-family: 'Overpass', sans-serif;
    font-size: 18px;
    margin: 0;
    padding: 0;
}

article.journal-entry {
    background-color: white;
    width: 80%;
    max-width: 900px;
    padding: 100px;
    box-shadow: 0 0 2em rgba(0, 0, 0, 0.2);
    margin: 0 auto;
}

article.journal-entry h1 {
    font-family: 'Spectral', serif;
    font-weight: 600;
    font-size: 2.5em;
}

article.journal-entry h3 {
    font-style: italic;
}

.map-embed-wrapper {
    margin: 0 -100px;
}

.map-embed-wrapper iframe {
    width: 100%;
    height: 12em;
}

Stage 4

Slide.js

https://github.com/SidKwok/slide.js

slide.js
'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

/**
 * @author Sid Kwok <oceankwok@hotmail.com>
 * version: 1.0.0
 * https://github.com/SidKwok/slide.js
 *
 */

(function () {

    var Animation = {
        slideUp: function slideUp(page) {
            this.pics.style.top = '-' + (page - 1) * this.options.height + 'px';
        },
        slideLeft: function slideLeft(page) {
            this.pics.style.left = '-' + (page - 1) * this.options.width + 'px';
        },
        fade: function fade(page) {
            var imgs = this.pics.children;

            for (var i = 0; i < imgs.length; i++) {
                imgs[i].style.opacity = i + 1 === page ? '1' : '0';
            }
        }
    };

    var DEFAULT = {
        imgs: [],
        width: 600,
        height: 400,
        autoswitch: {
            open: false,
            delay: 5000
        },
        animation: 'slideUp'
    };

    var Slide = function () {
        function Slide(el, opt) {
            _classCallCheck(this, Slide);

            this.options = DEFAULT;
            this.$el = document.querySelector(el);
            this.currentPage = 1;
            this.picsLayer = null;
            this.pics = null;
            this.pagination = null;

            if (opt) {
                for (var key in opt) {
                    if (key === 'autoswitch') {
                        this.options.autoswitch.open = opt.autoswitch.open;
                        this.options.autoswitch.delay = opt.autoswitch.delay;
                    } else {
                        this.options[key] = opt[key];
                    }
                }
            }

            this.init();
        }

        _createClass(Slide, [{
            key: 'init',
            value: function init() {
                var $el = this.$el;
                var _options = this.options;
                var width = _options.width;
                var height = _options.height;

                var picsLayer = document.createElement('div');
                var pics = document.createElement('div');

                this.picsLayer = picsLayer;
                this.pics = pics;

                // 设置样式
                picsLayer.style.width = width + 'px';
                picsLayer.style.height = height + 'px';
                picsLayer.setAttribute('class', 'pics-layer');
                $el.setAttribute('class', 'slide');

                picsLayer.appendChild(pics);
                $el.appendChild(picsLayer);

                this.initImgs();
                this.initAnimation();
                this.initPagination();

                if (this.options.autoswitch.open) {
                    this.initAutoSwitch();
                }
            }
        }, {
            key: 'initImgs',
            value: function initImgs() {
                var srcs = this.options.imgs;
                var pics = this.pics;
                var _options2 = this.options;
                var width = _options2.width;
                var height = _options2.height;
                var animation = _options2.animation;

                // 设置样式

                pics.setAttribute('class', 'pics');

                var _iteratorNormalCompletion = true;
                var _didIteratorError = false;
                var _iteratorError = undefined;

                try {
                    for (var _iterator = srcs[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
                        var src = _step.value;

                        var img = new Image();
                        img.src = src;
                        img.width = width;
                        img.height = height;
                        pics.appendChild(img);
                    }
                } catch (err) {
                    _didIteratorError = true;
                    _iteratorError = err;
                } finally {
                    try {
                        if (!_iteratorNormalCompletion && _iterator.return) {
                            _iterator.return();
                        }
                    } finally {
                        if (_didIteratorError) {
                            throw _iteratorError;
                        }
                    }
                }
            }
        }, {
            key: 'initPagination',
            value: function initPagination() {
                var _this = this;

                var picsLayer = this.picsLayer;
                var pics = this.pics;
                var _options3 = this.options;
                var width = _options3.width;
                var height = _options3.height;

                var length = this.options.imgs.length;
                var pagination = document.createElement('div');

                this.pagination = pagination;

                pagination.setAttribute('class', 'pagination');

                // animation
                pagination.addEventListener('click', function (event) {
                    var page = Number.parseInt(event.target.innerHTML);
                    if (page) {
                        _this.switchSlide(page);
                    }
                }, false);

                picsLayer.appendChild(pagination);

                for (var i = 0; i < length; i++) {
                    var a = document.createElement('a');
                    a.innerHTML = i + 1;
                    a.href = 'javascript: void(0);';
                    if (i == 0) {
                        a.className = 'active';
                    }
                    pagination.appendChild(a);
                }
            }
        }, {
            key: 'initAnimation',
            value: function initAnimation() {
                var pics = this.pics;
                var _options4 = this.options;
                var width = _options4.width;
                var height = _options4.height;
                var animation = _options4.animation;


                switch (animation) {
                    case 'slideUp':
                        pics.style.position = 'absolute';
                        pics.style.top = '0px';
                        var _iteratorNormalCompletion2 = true;
                        var _didIteratorError2 = false;
                        var _iteratorError2 = undefined;

                        try {
                            for (var _iterator2 = pics.children[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
                                var img = _step2.value;

                                img.style.display = 'block';
                            }
                        } catch (err) {
                            _didIteratorError2 = true;
                            _iteratorError2 = err;
                        } finally {
                            try {
                                if (!_iteratorNormalCompletion2 && _iterator2.return) {
                                    _iterator2.return();
                                }
                            } finally {
                                if (_didIteratorError2) {
                                    throw _iteratorError2;
                                }
                            }
                        }

                        this.doAnimation = Animation.slideUp;
                        break;
                    case 'slideLeft':
                        pics.style.position = 'absolute';
                        pics.style.left = '0px';
                        pics.style.width = pics.children.length * width + 'px';
                        var _iteratorNormalCompletion3 = true;
                        var _didIteratorError3 = false;
                        var _iteratorError3 = undefined;

                        try {
                            for (var _iterator3 = pics.children[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
                                var _img = _step3.value;

                                _img.style.display = 'inline-block';
                            }
                        } catch (err) {
                            _didIteratorError3 = true;
                            _iteratorError3 = err;
                        } finally {
                            try {
                                if (!_iteratorNormalCompletion3 && _iterator3.return) {
                                    _iterator3.return();
                                }
                            } finally {
                                if (_didIteratorError3) {
                                    throw _iteratorError3;
                                }
                            }
                        }

                        this.doAnimation = Animation.slideLeft;
                        break;
                    case 'fade':
                        var imgs = pics.children;
                        for (var i = 0; i < imgs.length; i++) {
                            imgs[i].style.position = 'absolute';
                            imgs[i].style.opacity = i === 0 ? '1' : '0';
                        }
                        this.doAnimation = Animation.fade;
                        break;
                    default:

                }
            }
        }, {
            key: 'initAutoSwitch',
            value: function initAutoSwitch() {
                var _this2 = this;

                var delay = this.options.autoswitch.delay;

                setInterval(function () {
                    _this2.switchSlide(_this2.currentPage + 1);
                }, delay);
            }
        }, {
            key: 'doAnimation',
            value: function doAnimation() {}
        }, {
            key: 'switchSlide',
            value: function switchSlide(page) {
                var pages = this.pagination.children;
                if (page > this.options.imgs.length) {
                    page = 1;
                }
                this.currentPage = page;
                this.doAnimation(page);

                for (var i = 0; i < pages.length; i++) {
                    pages[i].className = i === page - 1 ? 'active' : '';
                }
            }
        }]);

        return Slide;
    }();

    /**
     * expose Slide
     */


    window.Slide = Slide;
})();
slide.css
/**
 * @author Sid Kwok <oceankwok@hotmail.com>
 * version: 1.0.0
 * https://github.com/SidKwok/slide.js
 *
 */

.slide .pics-layer {
    margin: 0 auto;
    position: relative;
    overflow: hidden;
}

.slide .pics-layer .pics {
    transition: 1s;
}
.slide .pics-layer .pics img {
    transition: 1s;
}

.slide .pics-layer .pagination {
    position: absolute;
    right: 20px;
    bottom: 10px;
}

.slide .pics-layer .pagination a {
    display: inline-block;
    width: 20px;
    height: 20px;
    color: #999;
    background-color: rgba(255,255,255,.5);
    text-decoration: none;
    margin-right: 8px;
    text-align: center;
    line-height: 23px;
}
.slide .pics-layer .pagination a.active {
    background-color: rgba(255,102,0,.8);
    color: #fff;
}

gallery.js

gallery.js
$(document).ready(initGallery);

function initGallery() {
    $('button.show-gallery').click(function() {
        showGallery($(this).data('journal-entry'));
        $(this).remove();
    });
}

function showGallery(entryName) {
    $.get(`.rest/nodes/v1/journals/${entryName}?depth=1`).done(data => {
        const propertyBlobs = data.nodes
            .filter(node => node.type === 'mgnl:block')
            .map(node => node.properties)
            .reduce(flatten, []);
        const urls = propertyBlobs
            .filter(blob => blob.name === 'image')
            .map(blob => blob.values)
            .reduce(flatten, [])
            .map(uuid => `dam/${uuid}`);
        startSlideshow(urls);
    });
}

function startSlideshow(urls) {
    new Slide('#gallery', {
        imgs: urls,
        autoswitch: {
            open: true,
            delay: 3000
        }
    });
}

// reduce function to flatten 2-dimensional array to one dimension
function flatten(accumulator, current) {
    return accumulator.concat(current);
}

CSS

journal.css
#gallery {
    margin-top: 2em;
}

button.show-gallery {
    width: 100%;
}

Area template

journalEntryArea.ftl - exclude image blocks
[#if block['mgnl:type'] != 'image']
    [@cms.block content=block /]
[/#if]
journalEntryArea.ftl - gallery container and button
<div id="gallery"></div>
<button class="show-gallery" data-journal-entry="${entryContent.@name}">Show gallery</button>

Link front end files

journalEntryPage.yaml - CSS and JS linkings
cssFiles:
  journal:
    link: /.resources/content-editor-workshop/webresources/journal.css
  slide:
    link: /.resources/content-editor-workshop/webresources/slide/slide.css
jsFiles:
  jQuery:
    link: https://code.jquery.com/jquery-latest.min.js
  slide:
    link: /.resources/content-editor-workshop/webresources/slide/slide.js
  gallery:
    link: /.resources/content-editor-workshop/webresources/gallery.js
  • No labels