Objective: Create custom navigation experiences in Kolibri to expand the appeal and possibilities for content in Kolibri
Channel creators are able to create custom browsing experiences using HTML5 apps to provide flexibility in display and interaction with the contents of topics (including the channel root topic).
Outcomes
Display of a custom content channel representation HTML5 app to immersively take over display of the contents of a channel in Kolibri when the channel is opened
Enhance Hashi and Content Renderer APIs to allow custom channel representations in HTML5 apps to do two way communication:
Query ContentNode metadata and availability
Carry out scoped search within the channel
(Less of an immediate priority, but nice to have)
Emit ‘navigate to’ and ‘render’ intents for content ('navigate to' could also be used outside of the custom channel context for e.g. internal content linking)
'render' will display some target content within context calling the 'render'
'navigate to' will go to the kolibri page of target content
Display of content in Kolibri that retains the context and theming of the custom channel representation to handle the ‘render’ intent
Create a guidance document for accessibility and how to test
Gherkin Specification
Feature: Custom channels representation
Scenario: Exploring a custom channel
Given Custom channels is enabled as an options flag in the Learn plugin
When The learner clicks on the card for the custom channel
Then The URL goes to #/topics/<topic_id> and displays a full page HTML5 app
Scenario: Exploring a disabled custom channel
Given Custom channels is disbled as an options flag in the Learn plugin
When The learner clicks on the card for the custom channel
Then The URL goes to #/topics/<topic_id> and displays the Kolibri topic interface
Scenario: Navigating in a custom channel
Given The learner has started exploring a custom channel
When The learner clicks on a topic in the custom navigation
And The HTML5 app displays the contents of the topic
Then The URL updates with new context and stays on the full page HTML5 app
Scenario: Showing resources in a custom channel
Given The learner has started exploring a custom channel
When The learner clicks on a resource in the custom navigation
Then An overlay showing the content displays over the full page HTML5 app
And The URL updates with new context
And The full page HTML5 app remains in the background
Scenario: User closing shown resources in a custom channel
Given The learner has opened a resource from within a custom channel
When The learner clicks close on the overlay
Then The URL updates with new context
And The overlay closes
And The full page HTML5 app is still displayed
Scenario: Custom nav closing shown resources in a custom channel
Given The learner has opened a resource from within a custom channel
When The custom nav app says the overlay should close
Then The URL updates with new context
And The overlay closes
And The full page HTML5 app is still displayed
Scenario: Navigating out of a custom channel
Given The learner has started exploring a custom channel
When The learner clicks on a link in the custom navigation
And The link is to a topic
Then The URL goes to #/topics/t/<topic_id> and displays the Kolibri topic interface
And The full page HTML5 app closes
Scenario: Navigating between resources
Given The learner has started exploring a resource with links
When The learner clicks on a link in the resource
Then The URL goes to #/topics/c/<node_id> and displays the Kolibri content page
And The old content page closes
API Spec for Hashi 'kolibri' namespace object
/**
* This class offers an API interface for interacting directly with the Kolibri app
* that the HTML5 app is embedded within
*/
import BaseShim from './baseShim';
/**
* Type definition for Language metadata
* @typedef {Object} Language
* @property {string} id - an IETF language tag
* @property {string} lang_code - the ISO 639‑1 language code
* @property {string} lang_subcode - the regional identifier
* @property {string} lang_name - the name of the language in that language
* @property {('ltr'|'rtl'|)} lang_direction - Direction of the language's script,
* top to bottom is not supported currently
*/
/**
* Type definition for ContentNode metadata
* @typedef {Object} ContentNode
* @property {string} id - unique id of the ContentNode
* @property {string} channel_id - unique channel_id of the channel that the ContentNode is in
* @property {string} content_id - identifier that is common across all instances of this resource
* @property {string} title - A title that summarizes this ContentNode for the user
* @property {string} description - detailed description of the ContentNode
* @property {string} author - author of the ContentNode
* @property {string} thumbnail_url - URL for the thumbnail for this ContentNode,
* this may be any valid URL format including base64 encoded or blob URL
* @property {boolean} available - Whether the ContentNode has all necessary files for rendering
* @property {boolean} coach_content - Whether the ContentNode is intended only for coach users
* @property {Language} lang - The primary language of the ContentNode
* @property {string} license_description - The description of the license, which may be localized
* @property {string} license_name - The human readable name of the license, localized
* @property {string} license_owner - The name of the person or organization that holds copyright
* @property {number} num_coach_contents - Number of coach contents that are descendants of this
* @property {string} parent - The unique id of the parent of this ContentNode
* @property {number} sort_order - The order of display for this node in its channel
* if depth recursion was not deep enough
*/
/**
* Type definition for pagination object
* @typedef {Object} PageResult
* @property {number} page - the page that this pagination object represents
* @property {number} pageSize - the page size for this pagination object
* @property {ContentNode[]} results - the array of ContentNodes for this page
*/
/**
* Type definition for Theme options
* properties TBD
* @typedef {Object} Theme
*/
function encodeContext(context) {
return encodeURI(Object.entries(context).map(([k,v]) => `${k}:${v}`));
}
/**
* Type definition for NavigationContext
* This can have arbitrary properties as defined
* by the navigating app that it uses to resume its state
* Should be able to be encoded down to <1600 characters using
* an encoding function something like 'encode context' above
* @typedef {Object} NavigationContext
* @property {string} node_id - The current node_id that is being displayed, custom apps should handle
* this as it may be used to generate links externally to jump to this state
*/
export default class Kolibri extends BaseShim {
constructor(mediator) {
super(mediator);
this.nameSpace = 'kolibri';
}
iframeInitialize(contentWindow) {
this.__setShimInterface();
Object.defineProperty(contentWindow, this.nameSpace, {
value: this.shim,
configurable: true,
});
}
__setShimInterface() {
const self = this;
class Shim {
/*
* Method to query contentnodes from Kolibri and return
* an array of matching metadata
* @param {Object} options - The different options to filter by
* @param {string=} options.parent - id of the parent node to filter by, or 'self'
* @param {string[]} options.ids - an array of ids to filter by
* @param {string[]} options.tags - an array of tags to do an OR filter by,
* returning nodes that match any of the tags
* @param {number} [options.page=1] - which page to return from the result set
* @param {number} [options.pageSize=50] - the page size for pagination
* @return {Promise<PageResult>} - a Promise that resolves to an array of ContentNodes
*/
getContentByFilter(options) {}
/*
* Method to query a single contentnodes from Kolibri and return
* a metadata object
* @param {string} id - id of the ContentNode
* @return {Promise<ContentNode>} - a Promise that resolves to a ContentNode
*/
getContentById(id) {}
/*
* Method to search for contentnodes on Kolibri and return
* an array of matching metadata
* @param {Object} options - The different options to search by
* @param {string=} options.keyword - search term for key word search
* @param {string=} options.under - id of topic to search under, or 'self'
* @param {number} [options.page=1] - which page to return from the result set
* @param {number} [options.pageSize=50] - the page size for pagination
* @return {Promise<PageResult>} - a Promise that resolves to an array of ContentNodes
*/
searchContent(options) {}
/*
* Method to set a default theme for any content rendering initiated by this app
* @param {Theme} options - The different options for custom themeing
* @return {Promise} - a Promise that resolves when the theme has been applied
*/
themeRenderer(options) {}
/*
* Method to allow navigation to or rendering of a specific node
* has optional parameter context that can update the URL for a custom context.
* When this is called for a resource node in the custom navigation context
* this will launch a renderer overlay to maintain the current state, and update the
* query parameters for the URL of the custom context to indicate the change
* If called for a topic in a custom context or outside of a custom context
* this will simply prompt navigation to that node in Kolibri.
* @param {string} nodeId - id of the parent node to navigate to
* @param {NavigationContext=} context - optional context describing the state update
* if node_id is missing from the context, it will be automatically filled in by this method
* @return {Promise} - a Promise that resolves when the navigation has completed
*/
navigateTo(nodeId, context = {}) {}
/*
* Method to allow updating of stored state in the URL
* @param {NavigationContext} context - context describing the state update
* @return {Promise} - a Promise that resolves when the context has been updated
*/
updateContext(context) {}
/*
* Method to request the current context state
* @return {Promise<NavigationContext>} - a Promise that resolves when the context has been updated
*/
getContext() {}
/*
* Getter to return the current version of Kolibri and hence the API available.
* @return {string} - A version string
*/
get version() {}
}
this.shim = new Shim();
return this.shim;
}
}
Objective: Create custom navigation experiences in Kolibri to expand the appeal and possibilities for content in Kolibri
Channel creators are able to create custom browsing experiences using HTML5 apps to provide flexibility in display and interaction with the contents of topics (including the channel root topic).
Outcomes
Gherkin Specification
API Spec for Hashi 'kolibri' namespace object