(function() {
const ATTRIBUTES_MAPPING = {
appUrl: 'screen',
locale: 'language'
}
const TRANSFORM = {
/**
* @param {string} text
* @param {Object} params
* @param {string} params.mediaUrl
* @returns {string}
*/
processText: (text, {mediaUrl}) => {
if (typeof text !== 'string') return '';
// process images
text = text.replace(/<img.*?>/gi, (match) => {
const s = match.match(/src=(["'])(https?:\/\/.*?)\1/);
return '<img src="' + (s ? s[2] : match.replace(/.*?src=(["'])(.*?)\1.*?>/, mediaUrl + '$2')) + '">';
});
// process links
text = text.replace(/<a[^>]*?href=(["'])(.*?)\1.*?>/g, (match, p1, p2) => {
if (match.search(/class=(["'])xref\1/) >= 0) { // KM link
return match.search(/target=(["'])_blank\1/) >= 0
? '<a rel="noopener nofollow noreferrer" target="_blank" href="' + p2 + '">' // external KM link; XRAY-873
: '<a rel="noopener nofollow noreferrer" target="_blank" href="' + mediaUrl + p2 + '">'; // internal KM link
} else { // other
return match.replace(/target=(["']).*?\1/, '') // remove all targets...
.replace(/rel=(["']).*?\1/, '') // ... and remove all rel...
.replace(/>$/, ' rel="noopener nofollow noreferrer" target="_blank">') // ...force them to be _blank
.replace(/class=(["']).*?\1/, ''); // XRAY-1946
}
});
// see there for explanation
return Help4.control.input.HtmlEditor.transformFromUACP(text);
},
/**
* @param {string} isMachineTranslated
* @returns {boolean}
*/
processIsMachineTranslated: isMachineTranslated => isMachineTranslated === 'yes'
}
/**
* @typedef {Object} Help4.widget.help.project.UACP.ProjectTileHotspotAssignment
* @property {string} displayProperties
* @property {string} hotspotAnchor
*/
/**
* @typedef {Object} Help4.widget.help.project.UACP.ProjectTile
* @property {string} content
* @property {Help4.widget.help.project.UACP.ProjectTileHotspotAssignment[]} hotspotAssignments
* @property {string} id
* @property {string} lastModifiedDate
* @property {string} pageUrl
* @property {string} summaryText
* @property {number} tileOrder
* @property {string} title
* @property {string} tourAppUrl
* @property {string} tourProduct
* @property {string} tourVersion
* @property {boolean} isMachineTranslated
*/
/**
* @typedef {Object} Help4.widget.help.project.UACP.Project
* @property {string} alias
* @property {string} appUrl
* @property {Help4.widget.help.ContextTypes} contextType
* @property {string} editable
* @property {string} environment
* @property {string} id
* @property {string} lastModifiedDate
* @property {string} locale
* @property {string} loio
* @property {string|Object} otherMetadata
* @property {string} product
* @property {string} shortDescription
* @property {string} state
* @property {string} system
* @property {Help4.widget.help.project.UACP.ProjectTile[]} tiles
* @property {string} title
* @property {string} version
* @property {Object} [conditions]
*/
/**
* this backend connector is able to handle UACP project content for a RO model configuration
*/
Help4.widget.help.project.UACP = class {
/**
* @param {string} projectId - project ID
* @param {Help4.typedef.SystemConfiguration} config - the system configuration
* @param {Help4.widget.help.Data} data
* @param {Help4.widget.help.CatalogueTypes} [catalogueType = 'UACP']
* @returns {Promise<Help4.widget.help.Projects|null>}
*/
static async load(projectId, config, data, catalogueType = 'UACP') {
const {roModel, serviceUrl} = config.help;
if (!serviceUrl || roModel !== Help4.SERVICE_LAYER.uacp) return null;
const {featureProfileUACP} = config.help;
const profile = featureProfileUACP ? '&profile=' + encodeURIComponent(featureProfileUACP) : '';
const url = serviceUrl + '/context?id=' + projectId + profile;
/** @type {Help4.widget.companionCore.UACP.ProjectResponse} */
const serverResponse = await Help4.widget.companionCore.UACP.doGetRequest(url);
const {status, data: response} = serverResponse || {};
if (status === 'OK' && response) {
/** @type {Help4.widget.help.Project} */
const pub = this._parse(response, config, catalogueType);
return {pub};
}
return null;
}
/**
* @protected
* @param {Help4.widget.help.project.UACP.Project} data
* @param {Help4.typedef.SystemConfiguration} config
* @param {Help4.widget.help.CatalogueTypes} catalogueType
* @returns {Help4.widget.help.Project}
*/
static _parse(data, config, catalogueType) {
/** @param {Help4.widget.help.project.UACP.Project} data */
const setDefaults = data => {
for (const [key, {f, uacp}] of Object.entries(Help4.PROJECT_DEFAULTS)) {
/** @type {string[]} */
const path = (uacp || key).split('.');
let value = data;
while (path[0] && value) value = value[path.shift()];
data[key] = value || f; // attention: only works for json type!!!
}
}
/**
* @param {Help4.widget.help.project.UACP.Project} data
* @param {string} mediaUrl
* @param {Help4.widget.help.Project} project
* @returns {Help4.widget.help.ProjectTile[]}
*/
const parseTiles = (data, mediaUrl, project) => {
/** @type {Array<Help4.widget.help.ProjectTile>} */
const tiles = [];
const {id, _dataType, _catalogueType, language} = project;
const type = data.contextType.toLowerCase();
const isTour = type === 'tour';
for (const uacpTile of data.tiles) {
/** @type {Help4.widget.help.ProjectTile} */
const tile = parseTile(uacpTile, mediaUrl);
tile.type ||= type;
tile.language = language;
tile._catalogueType = _catalogueType;
tile._dataType = _dataType;
tile._projectId = id;
if (isTour && !tile.hotspotAnchor && !tile.hotspotCentered) {
// fix for old content, that could have no hotspot but also is not centered
// this constellation is no longer possible
tile.hotspotCentered = true;
}
tiles.push(tile);
}
return tiles;
}
/**
* @param {Help4.widget.help.project.UACP.ProjectTile} tile
* @param {string} mediaUrl
* @returns {Help4.widget.help.ProjectTile}
*/
const parseTile = (tile, mediaUrl) => {
/** @type {Help4.widget.help.ProjectTile} */
const parsed = {};
// parse tile hotspot:
// - create hotspot id
// - convert displayProperties to JSON
const hotspot = tile.hotspotAssignments?.[0];
if (hotspot) {
hotspot.id = `${tile.id}-0`;
const {displayProperties} = hotspot;
if (displayProperties) {
try {
hotspot.displayProperties = Help4.JSON.parse(displayProperties);
} catch (e) {
delete hotspot.displayProperties;
}
}
}
tile.hotspot = hotspot;
for (const [key, {t, f, /** @type {string} */ uacp, /** @type {string} */ uacpGet}] of Object.entries(Help4.DEFAULTS)) {
/** @type {string[]} */
const path = (uacp || key).split('.');
let value = tile;
while (path[0] && value) value = value[path.shift()];
const transform = TRANSFORM[uacpGet];
if (transform) value = transform(value, {mediaUrl});
value = Help4.clampValue(value, t, f);
if (value !== undefined) parsed[key] = value;
}
parsed._standalone = true;
const wta = whatsThisAppTile(parsed.content);
if (wta) {
// identify whether a saved data for showAsButton prop is available from server {tile}
// if not available (undefined) out.showAsButton will be false because Help4.DEFAULTS will be applied
// hence make it true if undefined
const path = Help4.DEFAULTS.showAsButton.uacp.split('.');
const showAsButton = path.reduce((prev, curr) => prev?.[curr], tile);
if (showAsButton === undefined) parsed.showAsButton = true;
parsed._special = 'wta';
parsed.type = 'link';
parsed.linkTo = wta;
parsed.hotspotAnchor = null; // link tiles do not support hotspots
}
return parsed;
}
/**
* @param {string} text
* @returns {string|null}
*/
const whatsThisAppTile = text => {
// XRAY-1138: "What's this app" support
// if a tile contains only a link we auto-convert it into a link tile
const t1 = text.replace(/[\r\n]/g, '');
const t2 = t1.replace(/^<p>\s*<a[^>]*?href=(["'])(.*?)\1.*?>.*?<\/a>\s*<\/p>$/, '$2');
return t1 !== t2
? t2.replace(/javascript:ctx\.cfg_show\((["'])(.*?)\1.*$/, '$2')
: null;
}
try {
data.otherMetadata = Help4.JSON.parse(data.otherMetadata);
} catch(e) {
data.otherMetadata = {};
}
setDefaults(data);
const {ACCEPTED_ATTRIBUTES} = Help4.widget.help.Data;
/** @type {Help4.widget.help.Project} */
const project = {};
for (const [key, value] of Object.entries(data)) {
const name = ATTRIBUTES_MAPPING[key] || key;
if (ACCEPTED_ATTRIBUTES.includes(name)) {
project[name] = value;
}
}
project._catalogueType = catalogueType;
project._dataType = 'UACP';
const {loio, version, locale} = data;
const baseUrl = config.help.mediaUrl;
const mediaUrl = [baseUrl, loio, encodeURIComponent(version), encodeURIComponent(locale), ''].join('/');
project.tiles = parseTiles(data, mediaUrl, project);
return project;
}
}
})();