Skip to content
Stephan Kulla edited this page Feb 25, 2023 · 8 revisions

The content format of the Serlo editor

The basic idea behind the plugin system

Let's consider the following article about the difference quotient as an example:

example of an educational content

The content can be separated in a list of logical sub-elements like "text", "equation" or "GeoGebra applet" in the above example. More elements like "multiple choice exercise", "video" or "definition" can be considered. We call these logical elements of a content "plugins". A plugin is described in JSON in the form:

{
  "plugin": "...", // name of the plugin
  "state": ... // necessary information to describe the plugin
}

Each plugin defines the form of its state. For example, for rendering an externally hosted GeoGebra applet we only need its ID on geogebra.org and thus only a string. In the above example we have the GeoGebra applet with ID nnrmthf4 and thus we describe the applet integration via the JSON:

{
  "plugin": "geogebra",
  "state": "nnrmthf4"
}

In this form we can describe the whole content and thereby get a list of plugins. We unite this list in a so called rows plugin. The rows plugin describes semantically an arbitrary list of plugins from a list of possible plugin types. So we end up with the following JSON:

content with plugin system

This content format can be used to describe arbitrary educational content. When a new content format is needed (like a certain type of interactive exercise) we can define a plugin type. In this way the content format of Serlo is easily extendable.

Template plugins

Plugins can be also used to structure content. An example is our article plugin for serlo.org. We learned that a good article at our website follows a certain structure: There is often a short introduction in the beginning, possibly with a picture illustrating the topic. Afterwards, there is the main content which is followed by exercises and links to related content. Since we wanted to help our authors to create high quality content, we introduced the article plugin a couple of months ago. It follows the basic structure we identified to be useful (in the editing mode all elements besides the main content are optional):

the basic structure of the article plugin

You can use template plugins to standardize your own content formats or learning scenarios and guide your authors accordingly.

Combining template and content plugins

We can combine the power of template plugins with the list of available content plugins. In the following you see content format for the article "Differenzenquotient":

source code for article "Differenzenquotient"

Storing and editing Metadata

Metadata about a plugin - like the license and the description meta_description of an article - is also stored inside the state object:

{
  "plugin": "article",
  "state": {
    "id": 234583,
    "license": {
      "id": 1,
      "title": "Dieses Werk steht unter der freien Lizenz CC BY-SA 4.0.",
      "url": "https://creativecommons.org/licenses/by-sa/4.0/deed.de",
      "iconHref": "https://i.creativecommons.org/l/by-sa/4.0/88x31.png"
    },
    "title": "Differenzenquotient",
    "meta_title": "Differenzenquotient",
    "meta_description": "Der Differenzenquotient beschreibt..."
  }
}

List of plugins

List of already implemented plugins at serlo.org

The following plugins are already implemented (see also this list in our source code).

Standard elements

  • anchor – an anchor which can be referenced in a link
interface Anchor {
  plugin: "anchor",

  // Name of the anchor
  //
  // The anchor will be rendered as <div id="{plugin.state}"/>,
  // so that it can be referenced as http://serlo.org/xyz#{plugin.state}
  // in links
  state: string
}
  • box – a semantic box like an example, a definition, a theorem, a hint, etc.
interface Box {
  plugin: "box",

  state: {
    // type of box: "blank", "example", "citation", "approach", etc.
    type: string,
    // optional title for the box (see text plugin) 
    title: {
      plugin: "text",
      state: {}
    },
    // name of anchor so that the box can be referenced in links (similar to anchor plugin)
    anchorId: string,
    // a box can contain text, code, equations, image, multimedia content or a table
    // any of the above is wrapped in a rows plugin
    content: {
      plugin: "rows",
      state: [...]
    }
  }
}
  • equations
interface Equations {
  plugin: "equations",

  state: {
    // The transformations are saved as steps where each step contains
    // a left-hand side, a sign, a right-hand side, the transformation
    // and a textual explanation (inline)
    steps: [
      { 
        left: string,
        sign: string,
        right: string,
        transform: string,
        explanation: {
          plugin: "text", 
          state: {}
      }
    ],
    // The first explanation is rendered above the equation sign of the first step
    firstExplanation: {
      plugin: "text",
      state: {}
    }
    // Whether "equation" or "term" are transformed
    transformationTarget: 'equation',
  }
}
  • highlight - highlight of programming code
interface Highlight {
  plugin: "highlight",
  
  state: {
    // The programming code
    code: string,
    // Choose a programming language
    language: string,
    // Option to show line numbers (similar to IDE)
    showLineNumbers: false,
  }
  • image – an image
interface Image {
  plugin: "image",

  state: {
    // The url to the image's location 
    src: string,
    // The image can be a link
    link?: {
        // The url of the link
        href: string,
        // Option to open the link in a new tab
        openInNewTab: false
    },
    // alternative information for an image if image cannot be viewed
    alt?: string,
    // Optional setting of the maximal width of the image
    maxWidth?: number,
    // Optional caption that can be added to the image
    caption?: {
      plugin: 'text', 
      state: {}
    }
  }
} 
  • multimedia – text with an illustration media object (like an image)
interface Multimedia {
  plugin: "multimedia",
  state: {
    // The left-hand side of the multimedia object can contain text,
    // a highlight, an anchor, equations, an image or a serloTable
    explanation: {
      plugin: "rows",
      state: {}
    },
    // The right-hand side can be either an image, a video or a geogebra applet
    multimedia: {
      plugin: "image",
      state: {}
    },
    illustrating: true,
    width: number
  },
}
  • rows – an arbitrary list of plugins from a defined list of plugin types
interface Rows {
  plugin: "rows",
  
  // The rows plugin is rendered as a div with an tab index
  // The rows are a list of plugins from a predefined plugin registry
  state: [
    {
      plugin: "text", 
      state: {}
    },
    ...
  ]
}
  • separator – a separator (e.g. <hr/> in HTML)
interface Separator {
  plugin: "separator",
  state: undefined
}
  • spoiler – a spoiler
interface Spoiler {
  plugin: "spoiler",

  // The spoiler is a drop down that can be opened inside an article 
  state: {
    // title of the spoiler
    title: string, 
    // A spoiler contains rows of text or other content types
    content: {
      plugin: "rows",
      state: {}
    }
  }
}
  • serloTable - a table with row and column headers
interface SerloTable {
  plugin: "serloTable",

  // The Serlo Table is structured into rows where each row contains a column list. Each row-column entry is a text content.
  state: {
    rows: [
      {
        columns: [
          {
            content: {
              plugin: "text",
              state: {}
            }
          }
        ]
      }
    ],
    // Specifies whether only column headers or
    // only row headers or both are displayed
    tableType: string
  }
}
  • text – a rich text element
interface Text {
  plugin: "text",

  // We use SlateJS for rich-text editing
  state: SlateValue
}

type SlateValue =
  | Paragraph
  | OrderedList
  | UnorderedList
  | ListItem
  | ListItemText
  | Heading
  | Link
  | MathElementType

interface Heading {
  type: 'h'
  level: 1 | 2 | 3
  children: CustomText[]
}

interface Paragraph {
  type: 'p'
  children: CustomText[]
}

interface Link {
  type: 'a'
  href: string
  children: CustomText[]
}

interface UnorderedList {
  type: 'unordered-list'
  children: ListItem[]
}

interface OrderedList {
  type: 'ordered-list'
  children: ListItem[]
}

interface ListItem {
  type: 'list-item'
  children: ListItemText[]
}

interface ListItemText {
  type: 'list-item-child'
  children: CustomText[]
}

interface CustomText {
  text: string
  strong?: true
  em?: true
  code?: true
  color?: number
}
  • video – an included video
interface Video {
  plugin: "video",
  state: {
    // The url to the video's location 
    src: string,
    // Alternative information for the video
    // if the video cannot be viewed
    alt: string
  }
} 

Educational content plugins

  • geogebra – an included GeoGebra applet
interface Geogebra {
  plugin: "geogebra",

  // Id of the geogebra applet
  state: string
}
  • inputExercise – an exercise with an input element
interface InputExercise {
  plugin: "inputExercise",

  // The input exercise contains only the response part of the
  // exercise (the task is a separate content)
  state: {
    // response type: either text, number or mathematical expression
    type: string, 
    // option to specifiy unit for answer (e.g. "kg")
    unit: string, 
    // multiple answers can be entered
    answers: [
      { 
        // the answer as a string
        value: string,
        // whether the given answer is correct
        isCorrect: boolean,
        // customizable text feedback for the user, e.g. "Well done!"
        feedback: {
          plugin: "text",
          state: {}
        }
      }
    ]
  }
}
  • scMcExercise – single or multiple choice exercise
interface ScMcExercise {
  plugin: "scMcExercise",

  // // The scmc exercise contains only the response part of the exercise (the task is a separate content)
  state: {
    // single or multiple choice
    isSingleChoice: false, 
    // multiple answers can be entered
    answers: [
      { 
        // each answer contains a text content
        content: {
          plugin: "text", 
          state: {}
        },
        // flag whether answer is correct or false
        isCorrect: false,
        // text feedback for the user
        feedback: {
          plugin: "text",
          state: {}
        }
      }
    ]
  }
} 
  • serloInjection – another content of serlo.org which shall be included
interface SerloInjection {
  plugin: "injection",

  // Id of the Serlo article that is injected
  state: string
} 
  • solution – the solution of an exercise
interface Solution {
  plugin: "solution",
  state: {
    // prerequisites specified for understanding the exercise
    prerequisite?: {
      // id of the referenced Serlo article
      id: string,
      // title of the referenced Serlo article
      title: string,
    },
    // solution strategy described textually 
    strategy: {
      plugin: "text",
      state: {}
    },
    // the solution is structured into different steps where each step can contain text, images or other content types
    steps: {
      plugin: "rows",
      state: {}
    }
  }
}
  • textExercise – an exercise
interface Exercise {
  plugin: "exercise",

  // The exercise plugin combines the task description and the responses
  state: {
    // The task
    content: {
      plugin: "rows",
      state: {}
    },
    // The responses, either of type scMcExercise or inputExercise
    interactive: {
      plugin: "scMcExercise",
      state: {}
    }
  }
}

Template Plugins

  • serloArticle – a basic structure of an article for serlo.org
interface Article {
  plugin: "article",

  // 
  state: {
    introduction: {
      plugin: "articleIntroduction", 
      state: {}
    },
    content: {
      plugin: "rows",
      state: {}
    },
    exercises: [
      { 
        plugin: "injection", 
        state: string
      }
    ],
    exerciseFolder: RelatedContent,
    relatedContent: {
      articles: RelatedContent[],
      courses: RelatedContent[],
      videos: RelatedContent[],
    }),
    sources: [
      {
        href: string,
        title: string,
      }
    ]
  ),
}

interface RelatedContent {
  // ID to Serlo content
  id: string
  
  // Name which shall be displayed
  title: string
}
  • serloArticleIntroduction – the introduction of an article
interface SerloArticleIntroduction {
  plugin: "articleIntroduction",

  // The articleIntroduction is a special type of the multimedia plugin where the explanation is a text plugin and the multimedia content type is restricted to images
  state: {
    // The text on the left-hand side of the image
    explanation: {
      plugin: "text",
      state: {}
    }
    // The image to the right-hand side of the text
    multimedia: {
      plugin: "image",
      state: {}
    }
  }
}

List of deprecated features

  • blockquote – a citation
interface Blockquote {
  plugin: "blockquote",

  // The blockquote is rendered as <blockquote/> and contains a text plugin
  state: {
    content: {
      plugin: "text", 
      state: {}
    }
  }
}
  • important – a box with an important message
interface Important {
  plugin: "important",

  // important is a rendered as a special box and
  // contains the message as a text plugin
  state: {
    plugin: "text", 
    state: {}
  }
}
  • table – a table stored in the markdown format
interface Table {
  plugin: "table",
  // the markdown string
  state: string
}

List of planned features

  • "Fill the blanks" exercises
  • "Drag and Drop" exercises
  • "Image hotspot" exercises
Clone this wiki locally