/** * 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 page model of the html file being edited * It has methods to manipulate the pages * */ import { Constants } from '../../Constants'; import { Model, View } from '../types'; import { SilexNotification } from '../utils/notification'; /** * structure to store all of a page data * @struct */ export class PageData { name: string = ''; displayName: string = ''; linkName: string = ''; idx: number = -1; } /** * @param model model class which holds the other models * @param view view class which holds the other views */ export class Page { constructor(public model: Model, public view: View) {} /** * retrieve the first parent which is visible only on some pages * @return null or the element or one of its parents which has the css class * silex.model.Constants.PAGED_CLASS_NAME */ getParentPage(element: HTMLElement): HTMLElement { let parent = element.parentElement as HTMLElement; while (parent && !parent.classList.contains(Constants.PAGED_CLASS_NAME)) { parent = parent.parentElement as HTMLElement; } return (parent as HTMLElement | null); } /** * get the pages from the dom * @return an array of the page names I have found in the DOM */ getPages(): string[] { // retrieve all page names from the head section const pages = []; const bodyElement = this.model.body.getBodyElement(); if (!bodyElement) { console.warn('Can not get pages, the body element is null'); return []; } const elements = bodyElement.querySelectorAll(`a[data-silex-type="${Constants.TYPE_PAGE}"]`); elements.forEach((element) => { pages.push(element.getAttribute('id')); }); return pages; } /** * get the currently opened page from the dom * @return name of the page currently opened */ getCurrentPage(): string { // tslint:disable:no-string-literal if (!this.model.file.getContentWindow()['jQuery']) { throw new Error('JQuery not loaded in the opened website'); } const bodyElement = this.model.body.getBodyElement(); let pageName = null; try { if (this.model.file.getContentWindow()['jQuery'](bodyElement).pageable) { pageName = this.model.file.getContentWindow()['jQuery'](bodyElement).pageable('option', 'currentPage'); } } catch (e) { // there was a problem in the pageable plugin, return the first page console.warn( `warning, could not retrieve the current page, I will return the first page (${ this.getPages()})`); pageName = this.getPages()[0]; } return pageName; } /** * refresh the view */ refreshView() { const pages = this.getPages(); const currentPage = this.getCurrentPage(); const selectedElements = this.model.body.getSelection(); const states = this.view.stageWrapper.getSelection(); this.view.contextMenu.redraw(selectedElements, pages, currentPage); this.view.pageTool.redraw(selectedElements, pages, currentPage); this.view.propertyTool.redraw(states, pages, currentPage); this.view.textFormatBar.redraw(selectedElements, pages, currentPage); this.view.stageWrapper.redraw(); } /** * open the page * this is a static method, a helper * @param pageName name of the page to open */ setCurrentPage(pageName: string) { // tslint:disable:no-string-literal const bodyElement = this.model.body.getBodyElement(); if (this.model.file.getContentWindow()['jQuery'](bodyElement).pageable) { this.model.file.getContentWindow()['jQuery'](bodyElement).pageable({ currentPage: pageName, }); } this.view.stageWrapper.reset(); } /** * get a page from the dom by its name * @param pageName a page name * @return the page corresponding to the given page name */ getDisplayName(pageName: string): string { let displayName = ''; const pageElement = this.model.file.getContentDocument().getElementById(pageName); if (pageElement) { displayName = pageElement.innerHTML; } return displayName; } /** * remove a page from the dom * elements which are only in this page should be deleted */ removePage(pageName: string) { let pages = this.getPages(); const pageDisplayName = this.getDisplayName(pageName); if (pages.length < 2) { SilexNotification.notifyError( 'I could not delete this page because it is the only page!'); } else { // remove the DOM element const pageElements = Array.from(this.model.body.getBodyElement().querySelectorAll(`a[data-silex-type="${Constants.TYPE_PAGE}"]`)); pageElements.forEach((element) => { if (element.getAttribute('id') === pageName) { element.parentElement.removeChild(element); } }); // remove the links to this page const linkElements = Array.from(this.model.body.getBodyElement().querySelectorAll('*[data-silex-href="#!' + pageName + '"]')); linkElements.forEach((element) => { element.removeAttribute('data-silex-href'); }); // check elements which were only visible on this page // and returns them in this case const elementsOnlyOnThisPage = []; const elementsOfThisPage = Array.from(this.model.body.getBodyElement().getElementsByClassName(pageName)); elementsOfThisPage.forEach((element: HTMLElement) => { element.remove(); const pagesOfElement = this.getPagesForElement(element); if (pagesOfElement.length <= 0) { elementsOnlyOnThisPage.push(element); } }); // update the page list pages = this.getPages(); // open default/first page this.setCurrentPage(pages[0]); // handle elements which should be deleted if (elementsOnlyOnThisPage.length > 0) { SilexNotification.confirm('Delete elements' , ` ${elementsOnlyOnThisPage.length} elements were only visible on this page (${pageDisplayName}).
`, (accept) => { elementsOnlyOnThisPage.forEach((element) => { if (accept) { // remove these elements this.model.element.removeElement(element); } else { // remove from this page this.removeFromAllPages(element); } }); }, 'delete', 'keep'); } } } /** * move a page in the dom * @param direction up or down */ movePage(pageName: string, direction: string) { if (direction !== 'up' && direction !== 'down') { throw new Error('wrong direction ' + direction + ', can not move page'); } const elements = this.model.body.getBodyElement().querySelectorAll(`a[data-silex-type="${Constants.TYPE_PAGE}"]`); let prevEl = null; for (const el of elements) { if (prevEl && (el.id === pageName && direction === 'up' || prevEl.id === pageName && direction === 'down')) { el.parentElement.insertBefore(el, prevEl); const pages = this.getPages(); const currentPage = this.getCurrentPage(); this.view.pageTool.redraw(this.model.body.getSelection(), pages, currentPage); return; } prevEl = el; } console.error('page could not be moved', pageName, direction, prevEl); } /** * add a page to the dom */ createPage(name: string, displayName: string) { const container = this.model.body.getBodyElement().querySelector('.' + Constants.PAGES_CONTAINER_CLASS_NAME); // create the DOM element const aTag = this.model.file.getContentDocument().createElement('a'); aTag.setAttribute('id', name); aTag.setAttribute('href', '#!' + name); aTag.setAttribute('data-silex-type', Constants.TYPE_PAGE); aTag.innerHTML = displayName; container.appendChild(aTag); // for coherence with other silex elements aTag.classList.add(Constants.TYPE_PAGE); // select this page this.setCurrentPage(name); } /** * rename a page in the dom */ renamePage(oldName: string, newName: string, newDisplayName: string) { const bodyElement = this.model.body.getBodyElement(); // update the DOM element const pageElements = Array.from(bodyElement.querySelectorAll(`a[data-silex-type="${Constants.TYPE_PAGE}"]`)); pageElements.forEach((element) => { if (element.getAttribute('id') === oldName) { element.setAttribute('id', newName); element.setAttribute('href', '#!' + newName); element.innerHTML = newDisplayName; } }); // update the links to this page const linkElements = Array.from(bodyElement.querySelectorAll(`*[data-silex-href="#!${oldName}"]`)); linkElements.forEach((element) => { element.setAttribute('data-silex-href', '#!' + newName); }); // update the visibility of the compoents const componentElements = Array.from(bodyElement.getElementsByClassName(oldName)); componentElements.forEach((element) => { element.classList.remove(oldName); element.classList.add(newName); }); // wait until the dom reflects the changes setTimeout(() => { // select this page this.setCurrentPage(newName); }, 100); } /** * set/get a the visibility of an element in the given page * remove from all pages if visible in all pages */ addToPage(element: HTMLElement, pageName: string) { if (this.isInPage(element, pageName)) { console.error('Element is already in page', element, pageName); return; } element = this.model.element.noSectionContent(element); const pages = this.getPagesForElement(element); if (pages.length + 1 === this.getPages().length) { // from visible in some pages to visible everywhere this.removeFromAllPages(element); } else { element.classList.add(pageName); element.classList.add(Constants.PAGED_CLASS_NAME); if (pages.length === 0) { // from visible visible everywhere to visible in some pages if (pageName !== this.getCurrentPage()) { this.view.stageWrapper.removeElement(element); } else { this.refreshView(); } } else if (pageName === this.getCurrentPage()) { // from visible in some pages to visible in this page console.warn('How is this possible?'); this.view.stageWrapper.addElement(element); } else { console.warn('How is this possible?'); this.refreshView(); } } } /** * */ removeFromPage(element: HTMLElement, pageName: string) { if (!this.isInPage(element, pageName)) { console.error('Element is not in page', element, pageName); return; } element = this.model.element.noSectionContent(element); const pages = this.getPagesForElement(element); if (pages.length - 1 === 0) { // from visible in some pages to visible everywhere this.removeFromAllPages(element); } else { if (pageName === this.getCurrentPage()) { // update stage store this.view.stageWrapper.removeElement(element); } else { this.refreshView(); } element.classList.add(Constants.PAGED_CLASS_NAME); element.classList.remove(pageName); } } /** * */ removeFromAllPages(element: HTMLElement) { element = this.model.element.noSectionContent(element); const wasVisible = this.isVisible(element); const pages = this.getPagesForElement(element); pages.forEach((pageName) => { element.classList.remove(pageName); }); // the element is not "paged" anymore element.classList.remove(Constants.PAGED_CLASS_NAME); if (!wasVisible) { // update stage store this.view.stageWrapper.addElement(element); } else { this.refreshView(); } } /** * get the pages on which this element is visible */ getPagesForElement(element: HTMLElement): string[] { element = this.model.element.noSectionContent(element); return this.getPages().filter( (pageName) => element.classList.contains(pageName)); } /** * get all elements visible when the given page is opened */ getElementsForPage(page: string = this.getCurrentPage()): HTMLElement[] { return (Array.from(this.model.file.getContentDocument().querySelectorAll(`.${Constants.EDITABLE_CLASS_NAME}`)) as HTMLElement[]) .filter((el) => this.isVisible(el, page)); } /** * check if an element is in the given page (current page by default) */ isInPage(element: HTMLElement, opt_pageName: string = this.getCurrentPage()): boolean { element = this.model.element.noSectionContent(element); return element.classList.contains(opt_pageName); } /** * check if an element is visible in the given page * this means that the element is allways visible or it is visible in this page */ isVisible(element: HTMLElement, opt_pageName: string = this.getCurrentPage()) { if (element.classList.contains(Constants.PAGED_CLASS_NAME) && !this.isInPage(element, opt_pageName)) { return false; } const parentPaged = this.getParentPage(element); return !parentPaged || (this.isInPage(parentPaged, opt_pageName) && this.isVisible(parentPaged, opt_pageName)); } }