/** * Silex, live web creation * http://projects.silexlabs.org/?/silex/ * * Copyright (c) 2012 Silex Labs * http://www.silexlabs.org/ * * Silex is available under the GPL license * http://www.silexlabs.org/silex/silex-licensing/ */ /** * @fileoverview * This class represents a the head section of the opened file, * which is rendered by the Stage class * It has methods to manipulate the dom */ import { Constants } from '../../Constants'; import {View} from '../types'; import {FileInfo} from '../types'; import {Model} from '../types'; import {Font} from '../types'; const BEAUTIFY_CSS_OPTIONS = { indent_size: 2, }; /** * @param model model class which holds the other models * @param view view class which holds the other views */ export class Head { userHeadTag: string = ''; constructor(public model: Model, public view: View) {} /** * Update the settings from the DOM after a website was loaded */ updateFromDom() { this.setPublicationPath(this.getPublicationPath()); this.setWebsiteUrl(this.getWebsiteUrl()); this.setFaviconPath(this.getFaviconPath()); this.setThumbnailSocialPath(this.getThumbnailSocialPath()); this.setDescription(this.getDescription()); this.setDescriptionSocial(this.getDescriptionSocial()); this.setTitleSocial(this.getTitleSocial()); this.setTitle(this.getTitle()); this.setLang(this.getLang()); this.setEnableMobile(this.getEnableMobile()); this.setWebsiteWidth(this.getWebsiteWidth()); // update text editor with the website custom styles and script this.model.head.setHeadStyle(this.model.head.getHeadStyle()); this.model.head.setHeadScript(this.model.head.getHeadScript()); } /** * set/get HEAD tag * the head tag edited by the user is a portion of the real head tag * it is delimited by specific comments * it can not be interpreted while editing, in case it has bad HTML tags, it * could break the whole site, insert tags into the body instead of the * head... * @return the head tag content */ getUserHeadTag(): string { return this.userHeadTag; } /** * set/get HEAD tag * the head tag edited by the user is a portion of the real head tag * it is delimited by specific comments * it can not be interpreted while editing, in case it has bad HTML tags, it * could break the whole site, insert tags into the body instead of the * head... */ setUserHeadTag(str: string) { this.userHeadTag = str; } /** * set/get silex editable js scripts * @return the string defining the js script */ getHeadScript(): string { // get silex scripts from the DOM const scriptTag = this.getHeadElement().querySelector('.' + Constants.SILEX_SCRIPT_ELEMENT_CSS_CLASS); if (!scriptTag) { return ''; } return scriptTag.innerHTML; } /** * set/get silex editable js script * @param jsString the string defining Silex script */ setHeadScript(jsString: string) { let scriptTag = this.getHeadElement().querySelector('.' + Constants.SILEX_SCRIPT_ELEMENT_CSS_CLASS) as HTMLScriptElement; if (!scriptTag) { const doc = this.model.file.getContentDocument(); scriptTag = doc.createElement('script'); scriptTag.type = 'text/javascript'; scriptTag.className = Constants.SILEX_SCRIPT_ELEMENT_CSS_CLASS; this.getHeadElement().appendChild(scriptTag); } scriptTag.innerHTML = jsString; } /** * set/get silex editable css styles * @return the css string defining all Silex styles */ getHeadStyle(): string { // get silex styles from the DOM const silexStyle = this.getHeadElement().querySelector('.' + Constants.SILEX_STYLE_ELEMENT_CSS_CLASS); if (!silexStyle) { console.warn('no silex editable styles defined'); return ''; } // tslint:disable:no-string-literal return window['css_beautify'](silexStyle.innerHTML, BEAUTIFY_CSS_OPTIONS); } /** * set/get silex editable css styles * @param cssString the css string defining all Silex styles */ setHeadStyle(cssString: string) { let silexStyle = this.getHeadElement().querySelector('.' + Constants.SILEX_STYLE_ELEMENT_CSS_CLASS) as HTMLStyleElement; if (!silexStyle) { const doc = this.model.file.getContentDocument(); silexStyle = doc.createElement('style'); silexStyle.type = 'text/css'; silexStyle.className = Constants.SILEX_STYLE_ELEMENT_CSS_CLASS; this.getHeadElement().appendChild(silexStyle); } silexStyle.innerHTML = cssString; // update metrics this.view.stageWrapper.redraw(); } /** * get/set a meta data */ getMeta(name: string): string { const metaNode = this.model.file.getContentDocument().querySelector( 'meta[name="' + name + '"]'); if (metaNode) { return metaNode.getAttribute('content'); } else { return null; } } /** * get/set a meta data */ setMeta(name: string, opt_value?: string) { // update the DOM element let metaNode = this.model.file.getContentDocument().querySelector('meta[name="' + name + '"]') as HTMLMetaElement; if (!metaNode && opt_value && opt_value !== '') { // create the DOM element const doc = this.model.file.getContentDocument(); metaNode = doc.createElement('meta'); metaNode.name = name; metaNode.content = opt_value; this.getHeadElement().appendChild(metaNode); } else { if (opt_value && opt_value !== '') { // update opt_value metaNode.setAttribute('content', opt_value); } else { // remove the opt_value if (metaNode) { metaNode.parentElement.removeChild(metaNode); } } } } /** * get/set the publication path * publication path is always absolute url */ setPublicationPath(opt_fileInfo?: FileInfo) { this.setMeta( 'publicationPath', opt_fileInfo == null ? null : JSON.stringify(opt_fileInfo)); this.view.settingsDialog.setPublicationPath(opt_fileInfo); } /** * get/set the publication path * publication path is always absolute url */ getPublicationPath(): FileInfo { const fileInfo = this.getMeta('publicationPath'); try { return fileInfo == null ? null : (JSON.parse(fileInfo) as FileInfo); } catch (e) { // this happens with old publication path (just a string) return null; } } /** * get/set the website URL */ setWebsiteUrl(opt_url?: string) { this.setMeta('websiteUrl', opt_url); this.view.settingsDialog.setWebsiteUrl(opt_url); } /** * get/set the website URL */ getWebsiteUrl(): string { return this.getMeta('websiteUrl'); } /** * get/set the hosting provider */ setHostingProvider(opt_url?: string) { this.setMeta('hostingProvider', opt_url); } /** * get/set the hosting provider */ getHostingProvider(): string { return this.getMeta('hostingProvider'); } /** * enable/disable the mobile version */ setEnableMobile(enable: boolean) { const doc = this.model.file.getContentDocument(); if (doc.body === null) { // body is null, this happens while undoing or redoing return; } let viewport = doc.querySelector('meta[name=viewport]') as HTMLMetaElement; if (enable === true) { doc.body.classList.add(Constants.ENABLE_MOBILE_CSS_CLASS); if (!viewport) { viewport = doc.createElement('meta') as HTMLMetaElement; viewport.name = 'viewport'; viewport.content = 'width=device-width, initial-scale=1'; doc.head.appendChild(viewport); } } else { doc.body.classList.remove(Constants.ENABLE_MOBILE_CSS_CLASS); if (viewport) { doc.head.removeChild(viewport); } } this.view.settingsDialog.setEnableMobile(enable); // redraw UI const pages = this.model.page.getPages(); const page = this.model.page.getCurrentPage(); const selectedElements = this.model.body.getSelection(); const states = this.view.stageWrapper.getSelection(); this.view.propertyTool.redraw(states, pages, page); this.view.textFormatBar.redraw(selectedElements, pages, page); } /** * enable/disable the mobile version */ getEnableMobile(): boolean { const body = this.model.file.getContentDocument().body; if (body === null) { // body is null, this happens while undoing or redoing return false; } return body.classList.contains(Constants.ENABLE_MOBILE_CSS_CLASS); } /** * get/set the website width */ setWebsiteWidth(opt_value?: number) { this.view.settingsDialog.setWebsiteWidth(opt_value); const width: string = opt_value ? opt_value.toString() : null; this.setMeta('website-width', width); let silexStyle = this.getHeadElement().querySelector('.silex-style-settings') as HTMLStyleElement; if (width && width !== '') { if (!silexStyle) { const doc = this.model.file.getContentDocument(); silexStyle = doc.createElement('style'); silexStyle.type = 'text/css'; silexStyle.className = 'silex-style-settings'; this.getHeadElement().appendChild(silexStyle); } silexStyle.innerHTML = ` .${Constants.WEBSITE_WIDTH_CLASS_NAME} { width: ${width}px; } `; } else { if (silexStyle) { this.getHeadElement().removeChild(silexStyle); } } } /** * get/set the website width */ getWebsiteWidth(): number { const width = this.getMeta('website-width'); return !!width ? parseInt(width) : null; } /** * get/set the description */ setDescription(opt_description?: string) { this.setMeta('description', opt_description); this.view.settingsDialog.setDescription(opt_description); } /** * get/set the description */ getDescription(): string { return this.getMeta('description'); } /** * website title */ getTitle(): string { const titleNode = this.getHeadElement().querySelector('title'); if (titleNode) { return titleNode.innerHTML; } else { return null; } } /** * website title */ setTitle(name: string) { // find or create the title tag in the head section let titleNode = this.getHeadElement().querySelector('title') as HTMLTitleElement; if (!titleNode) { const doc = this.model.file.getContentDocument(); titleNode = doc.createElement('title'); this.getHeadElement().appendChild(titleNode); } // update website title titleNode.innerHTML = name || ''; // update view this.view.settingsDialog.setTitle(name); } /** * website default website language */ getLang(): string { return this.model.file.getContentDocument().querySelector('html').lang; } /** * website default website language */ setLang(name: string) { this.model.file.getContentDocument().querySelector('html').lang = name || ''; // update view this.view.settingsDialog.setLang(name || ''); } /** * website favicon */ getFaviconPath(): string { const faviconTag = this.getHeadElement().querySelector('link[rel="shortcut icon"]'); let url = null; if (faviconTag) { url = faviconTag.getAttribute('href'); } return url; } /** * website favicon */ setFaviconPath(opt_path?: string) { let faviconTag = this.getHeadElement().querySelector('link[rel="shortcut icon"]'); if (!faviconTag) { if (opt_path) { const doc = this.model.file.getContentDocument(); faviconTag = doc.createElement('link'); faviconTag.setAttribute('href', opt_path); faviconTag.setAttribute('rel', 'shortcut icon'); this.getHeadElement().appendChild(faviconTag); } } else { if (!opt_path) { faviconTag.parentElement.removeChild(faviconTag); } } if (opt_path) { // update website title faviconTag.setAttribute('href', opt_path); } // update view this.view.settingsDialog.setFaviconPath(opt_path); } /** * get/set the title for social networks */ setTitleSocial(opt_data?: string) { this.setMeta('twitter:card', opt_data ? 'summary' : ''); this.setMeta('twitter:title', opt_data); this.setMeta('og:title', opt_data); this.view.settingsDialog.setTitleSocial(opt_data); } /** * get/set the title for social networks */ getTitleSocial(): string { return this.getMeta('twitter:title') || this.getMeta('og:title'); } /** * get/set the description for social networks */ setDescriptionSocial(opt_data?: string) { this.setMeta('twitter:card', opt_data ? 'summary' : ''); this.setMeta('twitter:description', opt_data); this.setMeta('og:description', opt_data); this.view.settingsDialog.setDescriptionSocial(opt_data); } /** * get/set the description for social networks */ getDescriptionSocial(): string { return this.getMeta('twitter:description') || this.getMeta('og:description'); } /** * get/set the thumbnail image for social networks */ setThumbnailSocialPath(opt_path?: string) { this.setMeta('twitter:card', opt_path ? 'summary' : ''); this.setMeta('twitter:image', opt_path); this.setMeta('og:image', opt_path); this.view.settingsDialog.setThumbnailSocialPath(opt_path); } /** * get/set the thumbnail image for social networks */ getThumbnailSocialPath(): string { const url = this.getMeta('og:image') || this.getMeta('twitter:image'); return url; } /** * get/set the twitter account */ setTwitterSocial(opt_data?: string) { this.setMeta('twitter:card', opt_data ? 'summary' : ''); this.setMeta('twitter:site', opt_data); this.view.settingsDialog.setTwitterSocial(opt_data); } /** * get/set the twitter account */ getTwitterSocial(): string { return this.getMeta('twitter:site'); } /** * @return head element of the loaded site */ getHeadElement(): HTMLElement { // returns the head of the document in the iframe return this.model.file.getContentDocument().head; } /** * @return all the fonts for this website */ getFonts(): Font[] { return this.model.property.getFonts(); } setFonts(fonts: Font[]) { /** * @return true if the fonts are the same */ function compareFonts(f1: Font, f2: Font): boolean { return f1.family === f2.family && f1.href === f2.href; } // remove fonts which are not in fonts anymore const head = this.getHeadElement(); const doc = this.model.file.getContentDocument(); const oldFonts = this.getFonts(); oldFonts.filter((font) => !fonts.find((f) => compareFonts(f, font))) .forEach((font) => { const link = head.querySelector(`link[href="${font.href}"]`); if (link) { head.removeChild(link); } else { console.warn('Could not remove font from the dom', font, link); } }); // add new fonts fonts.filter((font) => !oldFonts.find((f) => compareFonts(f, font))) .forEach((font) => { const link = doc.createElement('link'); link.href = font.href; link.rel = 'stylesheet'; link.className = Constants.CUSTOM_FONTS_CSS_CLASS; head.appendChild(link); }); // store the list in db this.model.property.setFonts(fonts); // update views this.view.settingsDialog.redraw(); } }