import dom from 'sesajstools/dom'
import log from 'sesajstools/utils/log'
import sjtUrl from 'sesajstools/http/url'
import loadjs from 'loadjs'
import autosize from './autosize'
import checkBrowser from 'client-react/utils/checkBrowser'
const inBrowser = (typeof window) !== 'undefined'
const w = inBrowser ? window : {}
const wd = w.document
/**
* En attendant la gestion du load async avec es6, on utilise loadjs,
* on garde ici un mapping vers les modules tiers que l'on utilise
*/
const externalModules = {
ckeditor: 'vendor/ckeditor/ckeditor.js',
ckeditorJquery: 'vendor/ckeditor/adapters/jquery.js',
swfobject: 'vendor/swfobject/swfobject.2.3.min.js'
}
let base = '/'
/**
* Ajoute un texte d'erreur dans errorsContainer (#errors ou #error ou #warnings) ET dans console.error (si ça existe)
* L'existence de cette fonction est testée par init.js pour savoir si on doit être chargé.
* @param {string|Error} error Le message à afficher
* @param {number} [delay] Un éventuel délai d'affichage en secondes
*/
export function addError (error, delay) {
// on log toujours en console
if (!error) return log.error(new Error('page.addError appelé sans erreur à afficher'))
log.error(error)
const errorsContainer = wd.getElementById('errors') || wd.getElementById('error') || wd.getElementById('warnings')
let errorMsg = (error instanceof Error) ? error.toString() : error
if (/^TypeError:/.test(errorMsg)) {
// on envoie qqchose de plus compréhensible
errorMsg = 'Une erreur est survenue (voir la console pour les détails)'
}
if (errorsContainer) {
// on ajoute un peu de margin à ce div s'il n'en a pas
if (errorsContainer.style && !errorsContainer.style.margin) errorsContainer.style.margin = '0.2em'
const errorBlock = dom.addElement(errorsContainer, 'p', { class: 'error' }, errorMsg)
// si on a jQuery sous la main on scroll, sinon tant pis
if (window.jQuery) window.jQuery(errorsContainer).scrollTop(0)
if (delay) {
setTimeout(function () {
errorsContainer.remove(errorBlock)
}, delay * 1000)
}
} else {
log.error(new Error("errorsContainer n’existe pas, impossible d'afficher une erreur dedans " + errorMsg))
}
}
/**
* Affiche le bouton vu, ajoute son comportement au clic et le retourne
* @param {function} onClickCb sera appelée à chaque clic sur le bouton
* @return {Element} undefined si #boutonVu n'existait pas dans la page
*/
export function addBoutonVu (onClickCb) {
try {
const boutonVu = wd.getElementById('boutonVu')
if (boutonVu) {
boutonVu.addEventListener('click', onClickCb)
dom.setStyles(boutonVu, { display: 'block' })
}
return boutonVu
} catch (e) {
/* tant pis */
}
}
/**
* Cache le #titre (en global pour que les plugins puissent le faire)
*/
export function hideTitle () {
try {
const titre = wd.getElementById('titre')
if (titre && titre.style) titre.style.display = 'none'
log(titre ? 'titre masqué' : 'demande de masquage mais titre non trouvé')
const picto = wd.getElementById('pictoFeedback')
if (picto && picto.style) picto.style.display = 'none'
log(picto ? 'picto feedback masqué' : 'demande de masquage mais picto feedback non trouvé')
} catch (e) {
/* tant pis */
}
}
/**
* Complète les options si besoin avec base, container, errorsContainer qui seront créés si besoin,
* et ajoute aux options 'urlResultatCallback', 'userOrigine', 'userId' si elles n'y sont pas et sont dans l'url
* @param {initOptions} options
* @param {errorCallback} [next]
*/
export function init (options, next) {
if (inBrowser && !checkBrowser()) return // redirection en cours vers les navigateurs obsolètes
if (!options) options = {}
if (options.base) setBase(options.base)
else options.base = base
// (des)active la fct de log si on le demande, l'url est prioritaire sur options
let verbose = sjtUrl.getParameter('verbose') || options.verbose
if (verbose === '0' || verbose === 'false') verbose = false
if (verbose) log.enable()
else log.disable()
// on vérifie que l'on a nos containers et on les créé sinon
/**
* Le conteneur html pour afficher la ressource, passé en options ou pris dans le dom si #display
* @type {Element}
*/
let container = options.container || wd.getElementById('display')
/**
* Le conteneur html pour afficher d'éventuelles erreurs, passé en options ou pris dans le dom si #errors
* @type {Element}
*/
let errorsContainer = options.errorsContainer || wd.getElementById('errors')
if (!errorsContainer) errorsContainer = dom.addElement(wd.getElementsByTagName('body')[0], 'div', { id: 'errors' })
if (!container) container = dom.addElement(wd.getElementsByTagName('body')[0], 'div', { id: 'display' })
// et on ajoute ces deux éléments aux options
options.container = container
options.errorsContainer = errorsContainer
// on regarde si d'autres options ont été passé en GET
let paramGet
;['resultatMessageAction', 'urlResultatCallback', 'userOrigine', 'userId'].forEach(function (param) {
paramGet = sjtUrl.getParameter(param)
if (!options[param] && paramGet) options[param] = paramGet
})
paramGet = sjtUrl.getParameter('showTitle')
if (paramGet === '0' || paramGet === 'false') options.showTitle = false
// bugsnag s'ajoute tout seul à window, suffit de le charger
if (inBrowser) {
import('./bugsnag').then(() => {
if (next) next()
}).catch(console.error.bind(console))
} else if (next) {
next()
}
}
/**
* Pour charger des modules référencé ici en async, avec loadjs
* @param {Array} moduleNames
* @param {boolean} [parallelLoad=true]
* @param callback
*/
export function loadAsync (moduleNames, parallelLoad, callback) {
if (callback === undefined) {
callback = parallelLoad
parallelLoad = true
}
// on accepte les string
if (typeof moduleNames === 'string') moduleNames = [moduleNames]
// on passe par loadjs qui gèrera au passage l'unicité de l'appel
const paths = []
const errors = []
moduleNames.forEach(function (moduleName) {
let path = externalModules[moduleName]
// si moduleName est un externalModule connu on le prend
if (path) path = base + path
// sinon moduleName doit être une url absolue (http… ou //domain/path)
else if (/^(https?:)?\/\//.test(moduleName)) path = moduleName
if (path) paths.push(path)
else errors.push(moduleName)
})
if (errors.length) {
addError('Impossible de charger le ou les modules inconnus suivants ' + errors.join(', '))
} else if (paths.length) {
const { body } = wd
const waitingElt = dom.addElement(body, 'div', { className: 'waiting' }, 'chargement en cours…')
loadjs(paths, {
async: parallelLoad,
success: () => {
body.removeChild(waitingElt)
callback()
},
error: (modules) => {
body.removeChild(waitingElt)
addError('Impossible de charger le ou les modules suivants ' + modules.join(', '))
}
})
} else {
callback()
}
}
/**
* Change la base (pour la mettre absolue après chargement de ce module en cross domain)
* @param newBase
*/
export function setBase (newBase) {
if (newBase.substring(-1) !== '/') newBase += '/'
base = newBase
}
/**
* Affiche une notification
* @param {string} htmlString
* @param {number} [delay=5] délai en s avant fermeture auto
*/
export function showNotification (htmlString, delay = 5) {
const parent = document.body
const style = {
position: 'absolute',
top: '80px',
left: '80px',
padding: '1em',
'max-width': '50%',
'max-height': '50%',
overflow: 'auto',
'background-color': '#fff',
border: 'solid #333'
}
const notif = dom.addElement(parent, 'div', { style })
notif.innerHTML = htmlString
const closerElt = dom.addElement(notif, 'img', { src: '/medias/cocheVerte.png', alt: '', style: { position: 'absolute', top: '5px', right: '5px' } })
const closeNotif = () => {
if (isClosed) return
isClosed = true
closerElt.removeEventListener('click', closeNotif)
parent.removeChild(notif)
}
let isClosed = false
closerElt.addEventListener('click', closeNotif)
setTimeout(closeNotif, delay * 1000)
}
/**
* Module de base pour les méthodes spécifiques à sesatheque et son dom (addError, hideTitle)
* @service page
*/
export default {
addError,
addBoutonVu,
// utilisé par les plugins ato & url
autosize,
hideTitle,
init,
loadAsync,
setBase,
showNotification
}
/**
* Options à passer à init() ou à display(), les autres propriétés seront laissées intactes
* @typedef initOptions
* @type {Object}
* @property {string} [base=/] Le préfixe de chemin vers la racine de la sésathèque.
* Il faut passer un chemin http://… complet si ce module est utilisé sur un autre domaine que la sésathèque
* @property {Element} [container] L'élément html qui servira de conteneur au plugin pour afficher sa ressource, créé si non fourni
* @property {boolean} [showTitle=true] Passer false pour ne pas afficher le titre
* @property {Element} [errorsContainer] L'élément html pour afficher des erreurs éventuelles, créé si non fourni
* @property {boolean} [verbose=false] Passer true pour ajouter des log en console
*/
/**
* Un élément du Dom HTML
* @typedef Element
* @type {Object}
* @see https://developer.mozilla.org/fr/docs/Web/API/Element
*/