mirror of https://github.com/artf/grapesjs.git
committed by
GitHub
81 changed files with 2884 additions and 888 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,177 @@ |
|||
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> |
|||
|
|||
## Pages |
|||
|
|||
You can customize the initial state of the module from the editor initialization |
|||
|
|||
```js |
|||
const editor = grapesjs.init({ |
|||
.... |
|||
pageManager: { |
|||
pages: [ |
|||
{ |
|||
id: 'page-id', |
|||
styles: `.my-class { color: red }`, // or a JSON of styles |
|||
component: '<div class="my-class">My element</div>', // or a JSON of components |
|||
} |
|||
] |
|||
}, |
|||
}) |
|||
``` |
|||
|
|||
Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance |
|||
|
|||
```js |
|||
const pageManager = editor.Pages; |
|||
``` |
|||
|
|||
## Available Events |
|||
|
|||
- `page:add` - Added new page. The page is passed as an argument to the callback |
|||
- `page:remove` - Page removed. The page is passed as an argument to the callback |
|||
- `page:select` - New page selected. The newly selected page and the previous one, are passed as arguments to the callback |
|||
- `page:update` - Page updated. The updated page and the object containing changes are passed as arguments to the callback |
|||
- `page` - Catch-all event for all the events mentioned above. An object containing all the available data about the triggered event is passed as an argument to the callback |
|||
|
|||
## Methods |
|||
|
|||
- [add][1] |
|||
- [get][2] |
|||
- [getAll][3] |
|||
- [getMain][4] |
|||
- [remove][5] |
|||
- [select][6] |
|||
- [getSelected][7] |
|||
|
|||
## add |
|||
|
|||
Add new page |
|||
|
|||
### Parameters |
|||
|
|||
- `props` **[Object][8]** Page properties |
|||
- `opts` **[Object][8]?** Options (optional, default `{}`) |
|||
|
|||
### Examples |
|||
|
|||
```javascript |
|||
const newPage = pageManager.add({ |
|||
id: 'new-page-id', // without an explicit ID, a random one will be created |
|||
styles: `.my-class { color: red }`, // or a JSON of styles |
|||
component: '<div class="my-class">My element</div>', // or a JSON of components |
|||
}); |
|||
``` |
|||
|
|||
Returns **Page** |
|||
|
|||
## remove |
|||
|
|||
Remove page |
|||
|
|||
### Parameters |
|||
|
|||
- `page` **([String][9] | Page)** Page or page id |
|||
- `opts` (optional, default `{}`) |
|||
|
|||
### Examples |
|||
|
|||
```javascript |
|||
const removedPage = pageManager.remove('page-id'); |
|||
// or by passing the page |
|||
const somePage = pageManager.get('page-id'); |
|||
pageManager.remove(somePage); |
|||
``` |
|||
|
|||
Returns **Page** |
|||
|
|||
## get |
|||
|
|||
Get page by id |
|||
|
|||
### Parameters |
|||
|
|||
- `id` **[String][9]** Page id |
|||
|
|||
### Examples |
|||
|
|||
```javascript |
|||
const somePage = pageManager.get('page-id'); |
|||
``` |
|||
|
|||
Returns **Page** |
|||
|
|||
## getMain |
|||
|
|||
Get main page (the first one available) |
|||
|
|||
### Examples |
|||
|
|||
```javascript |
|||
const mainPage = pageManager.getMain(); |
|||
``` |
|||
|
|||
Returns **Page** |
|||
|
|||
## getAll |
|||
|
|||
Get all pages |
|||
|
|||
### Examples |
|||
|
|||
```javascript |
|||
const arrayOfPages = pageManager.getAll(); |
|||
``` |
|||
|
|||
Returns **[Array][10]<Page>** |
|||
|
|||
## select |
|||
|
|||
Change the selected page. This will switch the page rendered in canvas |
|||
|
|||
### Parameters |
|||
|
|||
- `page` **([String][9] | Page)** Page or page id |
|||
- `opts` (optional, default `{}`) |
|||
|
|||
### Examples |
|||
|
|||
```javascript |
|||
pageManager.select('page-id'); |
|||
// or by passing the page |
|||
const somePage = pageManager.get('page-id'); |
|||
pageManager.select(somePage); |
|||
``` |
|||
|
|||
Returns **this** |
|||
|
|||
## getSelected |
|||
|
|||
Get the selected page |
|||
|
|||
### Examples |
|||
|
|||
```javascript |
|||
const selectedPage = pageManager.getSelected(); |
|||
``` |
|||
|
|||
Returns **Page** |
|||
|
|||
[1]: #add |
|||
|
|||
[2]: #get |
|||
|
|||
[3]: #getall |
|||
|
|||
[4]: #getmain |
|||
|
|||
[5]: #remove |
|||
|
|||
[6]: #select |
|||
|
|||
[7]: #getselected |
|||
|
|||
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object |
|||
|
|||
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String |
|||
|
|||
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array |
|||
@ -0,0 +1,44 @@ |
|||
import { Collection, Model } from 'backbone'; |
|||
import { isArray } from 'underscore'; |
|||
|
|||
export class Selectable extends Model {} |
|||
|
|||
export default class Selected extends Collection { |
|||
getByComponent(component) { |
|||
return this.filter(s => this.getComponent(s) === component)[0]; |
|||
} |
|||
|
|||
addComponent(component, opts) { |
|||
const toAdd = (isArray(component) ? component : [component]) |
|||
.filter(c => !this.hasComponent(c)) |
|||
.map(component => ({ component })); |
|||
return this.push(toAdd, opts); |
|||
} |
|||
|
|||
getComponent(model) { |
|||
return model.get('component'); |
|||
} |
|||
|
|||
hasComponent(component) { |
|||
const model = this.getByComponent(component); |
|||
return model && this.contains(model); |
|||
} |
|||
|
|||
lastComponent() { |
|||
const last = this.last(); |
|||
return last && this.getComponent(last); |
|||
} |
|||
|
|||
allComponents() { |
|||
return this.map(s => this.getComponent(s)).filter(i => i); |
|||
} |
|||
|
|||
removeComponent(component, opts) { |
|||
const toRemove = (isArray(component) ? component : [component]).map(c => |
|||
this.getByComponent(c) |
|||
); |
|||
return this.remove(toRemove, opts); |
|||
} |
|||
} |
|||
|
|||
Selected.prototype.model = Selectable; |
|||
@ -0,0 +1,117 @@ |
|||
const traitInputAttr = { placeholder: 'eg. Text here' }; |
|||
|
|||
export default { |
|||
assetManager: { |
|||
addButton: 'Προσθήκη Εικόνας', |
|||
inputPlh: 'http://διαδρομή/μέχρι/την/εικόνα.jpg', |
|||
modalTitle: 'Επιλογή Εικόνας', |
|||
uploadTitle: 'Αφήστε τα αρχεία εδώ ή κάντε κλικ για ανέβασμα' |
|||
}, |
|||
// Εδώ υπάρχει απλά αναφορά, ο πυρήνας του GrapesJS δεν διαθέτει κανένα πλαίσιο,
|
|||
// οπότε αυτό θα πρέπει να αγνοηθεί από τα υπόλοιπα αρχεία μετάφρασης
|
|||
blockManager: { |
|||
labels: { |
|||
// 'block-id': 'Ετικέτα Πλαισίου',
|
|||
}, |
|||
categories: { |
|||
// 'category-id': 'Ετικέτα Κατηγορίας',
|
|||
} |
|||
}, |
|||
domComponents: { |
|||
names: { |
|||
'': 'Κουτί', |
|||
wrapper: 'Κορμός', |
|||
text: 'Κείμενο', |
|||
comment: 'Σχόλιο', |
|||
image: 'Εικόνα', |
|||
video: 'Βίντεο', |
|||
label: 'Ετικέτα', |
|||
link: 'Σύνδεσμος', |
|||
map: 'Χάρτης', |
|||
tfoot: 'Υποσέλιδο πίνακα', |
|||
tbody: 'Κορμός πίνακα', |
|||
thead: 'Κεφαλίδα πίνακα', |
|||
table: 'Πίνακας', |
|||
row: 'Γραμμή πίνακα', |
|||
cell: 'Κελί πίνακα' |
|||
} |
|||
}, |
|||
deviceManager: { |
|||
device: 'Συσκευή', |
|||
devices: { |
|||
desktop: 'Σθαθερός Υπολογιστής', |
|||
tablet: 'Τάμπλετ', |
|||
mobileLandscape: 'Κινητό Οριζόντια', |
|||
mobilePortrait: 'Κινητό Κάθετα' |
|||
} |
|||
}, |
|||
panels: { |
|||
buttons: { |
|||
titles: { |
|||
preview: 'Προεπισκόπηση', |
|||
fullscreen: 'Πλήρη Οθόνη', |
|||
'sw-visibility': 'Προβολή συστατικών', |
|||
'export-template': 'Προβολή κώδικα', |
|||
'open-sm': 'Άνοιγμα Του Διαχειριστή Μορφοποίησης', |
|||
'open-tm': 'Ρυθμίσεις', |
|||
'open-layers': 'Άνοιγμα Του Διαχειριστή Επιπέδων', |
|||
'open-blocks': 'Άνοιγμα Πλαισίων' |
|||
} |
|||
} |
|||
}, |
|||
selectorManager: { |
|||
label: 'Κλάσεις', |
|||
selected: 'Επιλεγμένο', |
|||
emptyState: '- Κατάσταση -', |
|||
states: { |
|||
hover: 'Αιώρηση', |
|||
active: 'Κλικ', |
|||
'nth-of-type(2n)': 'Μονές/Ζυγές' |
|||
} |
|||
}, |
|||
styleManager: { |
|||
empty: 'Επιλέξτε ένα στοιχεία πριν χρησιμοποιήσετε τον διαχειριστή μορφοποίησης', |
|||
layer: 'Επίπεδο', |
|||
fileButton: 'Εικόνες', |
|||
sectors: { |
|||
general: 'Γενικά', |
|||
layout: 'Δομή', |
|||
typography: 'Τυπογραφία', |
|||
decorations: 'Μορφοποίηση', |
|||
extra: 'Επιπρόσθετα', |
|||
flex: 'Φλεξ', |
|||
dimension: 'Διάσταση' |
|||
}, |
|||
// Η βασική βιβλιοθήκη παράγει το όνομα από την δικού του `ιδιότητα` name
|
|||
properties: { |
|||
// float: 'Float',
|
|||
} |
|||
}, |
|||
traitManager: { |
|||
empty: 'Επιλέξτε ένα στοιχεία πριν χρησιμοποιήσετε τον Διαχειριστή Χαρακτηριστικών', |
|||
label: 'Ρυθμίσεις Συστατικού', |
|||
traits: { |
|||
// Η βασική βιβλιοθήκη παράγει το όνομα από την δικού του `ιδιότητα` name
|
|||
labels: { |
|||
// id: 'Id',
|
|||
// alt: 'Εναλλακτικό Κείμενο',
|
|||
// title: 'Τίτλος',
|
|||
// href: 'Href',
|
|||
}, |
|||
// Σε ένα απλό χαρακτηριστικό, όπως ένα πεδίο κείμενο, χρησιμοποιούνται στις ιδιότητες του πεδίου εισαγωγής
|
|||
attributes: { |
|||
id: traitInputAttr, |
|||
alt: traitInputAttr, |
|||
title: traitInputAttr, |
|||
href: { placeholder: 'πχ. https://google.gr' } |
|||
}, |
|||
// Σε χαρακτηριστικό όπως το select, αυτά χρησιμοποιούνται για την μετάφραση των ονομάτων των επιλογών
|
|||
options: { |
|||
target: { |
|||
false: 'Στο ίδιο παράθυρο', |
|||
_blank: 'Σε νέο παράθυρο' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
@ -0,0 +1,210 @@ |
|||
const traitInputAttr = { placeholder: 't.ex. Text här' }; |
|||
|
|||
export default { |
|||
assetManager: { |
|||
addButton: 'Lägg till bild', |
|||
inputPlh: 'http://adress/till/bilden.jpg', |
|||
modalTitle: 'Välj bild', |
|||
uploadTitle: 'Dra och släpp filer här eller klicka för att ladda upp' |
|||
}, |
|||
// Here just as a reference, GrapesJS core doesn't contain any block,
|
|||
// so this should be omitted from other local files
|
|||
blockManager: { |
|||
labels: { |
|||
// 'block-id': 'Block Label',
|
|||
}, |
|||
categories: { |
|||
// 'category-id': 'Category Label',
|
|||
} |
|||
}, |
|||
domComponents: { |
|||
names: { |
|||
'': 'Box', |
|||
wrapper: 'Body', |
|||
text: 'Text', |
|||
comment: 'Kommentar', |
|||
image: 'Bild', |
|||
video: 'Video', |
|||
label: 'Etikett', |
|||
link: 'Länk', |
|||
map: 'Karta', |
|||
tfoot: 'Tabellfot', |
|||
tbody: 'Tabellinnehåll', |
|||
thead: 'Tabellhuvud', |
|||
table: 'Tabell', |
|||
row: 'Tabellrad', |
|||
cell: 'Tabellcell' |
|||
} |
|||
}, |
|||
deviceManager: { |
|||
device: 'Enhet', |
|||
devices: { |
|||
desktop: 'Dator', |
|||
tablet: 'Surfplatta', |
|||
mobileLandscape: 'Mobil liggande', |
|||
mobilePortrait: 'Mobil stående' |
|||
} |
|||
}, |
|||
panels: { |
|||
buttons: { |
|||
titles: { |
|||
preview: 'Förhandsgranska', |
|||
fullscreen: 'Helskärm', |
|||
'sw-visibility': 'Visa komponenter', |
|||
'export-template': 'Visa kod', |
|||
'open-sm': 'Öppna stilhanterare', |
|||
'open-tm': 'Inställningar', |
|||
'open-layers': 'Öppna lagerhanterare', |
|||
'open-blocks': 'Öppna block' |
|||
} |
|||
} |
|||
}, |
|||
selectorManager: { |
|||
label: 'Klasser', |
|||
selected: 'Markerad', |
|||
emptyState: '- Status -', |
|||
states: { |
|||
hover: 'Mus över', |
|||
active: 'Klick', |
|||
'nth-of-type(2n)': 'Jämn/udda' |
|||
} |
|||
}, |
|||
styleManager: { |
|||
empty: 'Markera ett element innan du använder stilhanteraren', |
|||
layer: 'Lager', |
|||
fileButton: 'Bilder', |
|||
sectors: { |
|||
general: 'Allmänt', |
|||
layout: 'Layout', |
|||
typography: 'Typografi', |
|||
decorations: 'Dekoration', |
|||
extra: 'Extra', |
|||
flex: 'Flex', |
|||
dimension: 'Dimension' |
|||
}, |
|||
// The core library generates the name by their `property` name
|
|||
properties: { |
|||
float: 'Justering', |
|||
display: 'Visning', |
|||
position: 'Position', |
|||
top: 'Topp', |
|||
right: 'Höger', |
|||
left: 'Vänster', |
|||
bottom: 'Botten', |
|||
width: 'Bredd', |
|||
height: 'Höjd', |
|||
'max-width': 'Maxbredd', |
|||
'max-height': 'Maxhöjd', |
|||
margin: 'Marginal', |
|||
'margin-top': 'Övre marginal', |
|||
'margin-right': 'Höger marginal', |
|||
'margin-left': 'Vänster marginal', |
|||
'margin-bottom': 'Undre marginal', |
|||
padding: 'Utfyllnad', |
|||
'padding-top': 'Övre utfyllnad', |
|||
'padding-left': 'Vänster utfyllnad', |
|||
'padding-right': 'Höger utfyllnad', |
|||
'padding-bottom': 'Nedre utfyllnad', |
|||
'font-family': 'Typsnitt', |
|||
'font-size': 'Teckenstorlek', |
|||
'font-weight': 'Texttyngd', |
|||
'letter-spacing': 'Textmellanrum', |
|||
color: 'Färg', |
|||
'line-height': 'Radhöjd', |
|||
'text-align': 'Textjustering', |
|||
'text-shadow': 'Textskugga', |
|||
'text-shadow-h': 'Horisontell textskugga', |
|||
'text-shadow-v': 'Vertikal textskugga', |
|||
'text-shadow-blur': 'Textskuggans luddighet', |
|||
'text-shadow-color': 'Textskuggans färg', |
|||
'border-top-left': 'Vänster övre kantlinje', |
|||
'border-top-right': 'Höger övre kantlinje', |
|||
'border-bottom-left': 'Vänster nedre kantlinje', |
|||
'border-bottom-right': 'Höger nedre kantlinje', |
|||
'border-left': 'Vänster kantlinje', |
|||
'border-top': 'Övre kantlinje', |
|||
'border-right': 'Höger kantlinje', |
|||
'border-bottom': 'Nedre kantlinje', |
|||
'border-radius-top-left': 'Vänster övre hörnradie', |
|||
'border-radius-top-right': 'Höger övre hörnradie', |
|||
'border-radius-bottom-left': 'Vänster nedre hörnradie', |
|||
'border-radius-bottom-right': 'Höger nedre hörnradie', |
|||
'border-radius': 'Hörnradie', |
|||
border: 'Kantlinje', |
|||
'border-width': 'Kantbredd', |
|||
'border-style': 'Kantstil', |
|||
'border-color': 'Kantfärg', |
|||
'box-shadow': 'Skugga', |
|||
'box-shadow-h': 'Horisontell skugga', |
|||
'box-shadow-v': 'Vertikal skugga', |
|||
'box-shadow-blur': 'Skuggans luddighet', |
|||
'box-shadow-spread': "Skuggans spridning", |
|||
'box-shadow-color': "Skuggans färg", |
|||
'box-shadow-type': "Typ av skugga", |
|||
background: 'Bakgrund', |
|||
'background-image': 'Bakgrundsbild', |
|||
'background-repeat': 'Upprepa bakgrund', |
|||
'background-position': 'Bakgrundsplacering', |
|||
'background-attachment': 'Bakgrundslåsning', |
|||
'background-size': 'Bakgrundsstorlek', |
|||
'background-color': 'Bakgrundsfärg', |
|||
transition: 'Övergång', |
|||
'transition-property': 'Övergångstyp', |
|||
'transition-duration': 'Övergångens varaktighet', |
|||
'transition-timing-function': 'Övergångens tidsfunktion', |
|||
perspective: 'Perspektiv', |
|||
transform: 'Transformera', |
|||
'transform-rotate-x': 'Rotera längs X-axeln', |
|||
'transform-rotate-y': 'Rotera längs Y-axeln', |
|||
'transform-rotate-z': 'Rotera längs Z-axeln', |
|||
'transform-scale-x': 'Skala längs X-axeln', |
|||
'transform-scale-y': 'Skala längs Y-axeln', |
|||
'transform-scale-z': 'Skala längs Z-axeln', |
|||
'flex-direction': 'Flexriktning', |
|||
'flex-wrap': 'Flexbrytning', |
|||
'justify-content': 'Justera innehåll', |
|||
'align-items': 'Elementjustering', |
|||
'align-content': 'Innehållsjustering', |
|||
order: 'Ordning', |
|||
'flex-basis': 'Flexbas', |
|||
'flex-grow': 'Flex väx', |
|||
'flex-shrink': 'Flex krymp', |
|||
'align-self': 'Självjustering' |
|||
} |
|||
}, |
|||
traitManager: { |
|||
empty: 'Markera ett element innan du använder egenskapshanteraren', |
|||
label: 'Komponentinställningar', |
|||
traits: { |
|||
// The core library generates the name by their `name` property
|
|||
labels: { |
|||
// id: 'Id',
|
|||
// alt: 'Alt',
|
|||
title: 'Titel', |
|||
placeholder: 'Platshållartext', |
|||
value: 'Värde', |
|||
required: 'Obligatorisk', |
|||
selected: 'Markerad', |
|||
checked: 'Ikryssad', |
|||
type: 'Typ', |
|||
style: 'Stil', |
|||
class: 'Klass', |
|||
// href: 'Href',
|
|||
}, |
|||
// In a simple trait, like text input, these are used on input attributes
|
|||
attributes: { |
|||
id: traitInputAttr, |
|||
alt: traitInputAttr, |
|||
title: traitInputAttr, |
|||
href: { placeholder: 't.ex. https://google.com' } |
|||
}, |
|||
// In a trait like select, these are used to translate option names
|
|||
options: { |
|||
target: { |
|||
false: 'Detta fönster', |
|||
_blank: 'Nytt fönster' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
@ -0,0 +1,297 @@ |
|||
/** |
|||
* You can customize the initial state of the module from the editor initialization |
|||
* ```js
|
|||
* const editor = grapesjs.init({ |
|||
* .... |
|||
* pageManager: { |
|||
* pages: [ |
|||
* { |
|||
* id: 'page-id', |
|||
* styles: `.my-class { color: red }`, // or a JSON of styles
|
|||
* component: '<div class="my-class">My element</div>', // or a JSON of components
|
|||
* } |
|||
* ] |
|||
* }, |
|||
* }) |
|||
* ``` |
|||
* |
|||
* Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance |
|||
* |
|||
* ```js
|
|||
* const pageManager = editor.Pages; |
|||
* ``` |
|||
* |
|||
* ## Available Events |
|||
* * `page:add` - Added new page. The page is passed as an argument to the callback |
|||
* * `page:remove` - Page removed. The page is passed as an argument to the callback |
|||
* * `page:select` - New page selected. The newly selected page and the previous one, are passed as arguments to the callback |
|||
* * `page:update` - Page updated. The updated page and the object containing changes are passed as arguments to the callback |
|||
* * `page` - Catch-all event for all the events mentioned above. An object containing all the available data about the triggered event is passed as an argument to the callback |
|||
* |
|||
* ## Methods |
|||
* * [add](#add) |
|||
* * [get](#get) |
|||
* * [getAll](#getall) |
|||
* * [getMain](#getmain) |
|||
* * [remove](#remove) |
|||
* * [select](#select) |
|||
* * [getSelected](#getselected) |
|||
* |
|||
* @module Pages |
|||
*/ |
|||
|
|||
import { isString, bindAll } from 'underscore'; |
|||
import { createId } from 'utils/mixins'; |
|||
import { Model } from 'backbone'; |
|||
import Pages from './model/Pages'; |
|||
import Page from './model/Page'; |
|||
|
|||
export const evAll = 'page'; |
|||
export const evPfx = `${evAll}:`; |
|||
export const evPageSelect = `${evPfx}select`; |
|||
export const evPageSelectBefore = `${evPageSelect}:before`; |
|||
export const evPageUpdate = `${evPfx}update`; |
|||
export const evPageAdd = `${evPfx}add`; |
|||
export const evPageAddBefore = `${evPageAdd}:before`; |
|||
export const evPageRemove = `${evPfx}remove`; |
|||
export const evPageRemoveBefore = `${evPageRemove}:before`; |
|||
const chnSel = 'change:selected'; |
|||
const typeMain = 'main'; |
|||
|
|||
export default () => { |
|||
return { |
|||
name: 'PageManager', |
|||
|
|||
storageKey: 'pages', |
|||
|
|||
Page, |
|||
|
|||
Pages, |
|||
|
|||
events: { |
|||
all: evAll, |
|||
select: evPageSelect, |
|||
selectBefore: evPageSelectBefore, |
|||
update: evPageUpdate, |
|||
add: evPageAdd, |
|||
addBefore: evPageAddBefore, |
|||
remove: evPageRemove, |
|||
removeBefore: evPageRemoveBefore |
|||
}, |
|||
|
|||
/** |
|||
* Initialize module |
|||
* @param {Object} config Configurations |
|||
* @private |
|||
*/ |
|||
init(opts = {}) { |
|||
bindAll(this, '_onPageChange'); |
|||
const { em } = opts; |
|||
const cnf = { ...opts }; |
|||
this.config = cnf; |
|||
this.em = em; |
|||
const pages = new Pages([], cnf); |
|||
this.pages = pages; |
|||
const model = new Model({ _undo: true }); |
|||
this.model = model; |
|||
pages.on('add', (p, c, o) => em.trigger(evPageAdd, p, o)); |
|||
pages.on('remove', (p, c, o) => em.trigger(evPageRemove, p, o)); |
|||
pages.on('change', (p, c) => { |
|||
em.trigger(evPageUpdate, p, p.changedAttributes(), c); |
|||
}); |
|||
pages.on('reset', coll => coll.at(0) && this.select(coll.at(0))); |
|||
pages.on('all', this.__onChange, this); |
|||
model.on(chnSel, this._onPageChange); |
|||
|
|||
return this; |
|||
}, |
|||
|
|||
__onChange(event, page, coll, opts) { |
|||
const options = opts || coll; |
|||
this.em.trigger(evAll, { event, page, options }); |
|||
}, |
|||
|
|||
onLoad() { |
|||
const { pages } = this; |
|||
const opt = { silent: true }; |
|||
pages.add(this.config.pages || [], opt); |
|||
const mainPage = !pages.length |
|||
? this.add({ type: typeMain }, opt) |
|||
: this.getMain(); |
|||
this.select(mainPage, opt); |
|||
}, |
|||
|
|||
_onPageChange(m, page, opts) { |
|||
const { em } = this; |
|||
const lm = em.get('LayerManager'); |
|||
const mainComp = page.getMainComponent(); |
|||
lm && mainComp && lm.setRoot(mainComp); |
|||
em.trigger(evPageSelect, page, m.previous('selected')); |
|||
this.__onChange(chnSel, page, opts); |
|||
}, |
|||
|
|||
postLoad() { |
|||
const { em, model } = this; |
|||
const um = em.get('UndoManager'); |
|||
um && um.add(model); |
|||
um && um.add(this.pages); |
|||
}, |
|||
|
|||
/** |
|||
* Add new page |
|||
* @param {Object} props Page properties |
|||
* @param {Object} [opts] Options |
|||
* @returns {Page} |
|||
* @example |
|||
* const newPage = pageManager.add({ |
|||
* id: 'new-page-id', // without an explicit ID, a random one will be created
|
|||
* styles: `.my-class { color: red }`, // or a JSON of styles
|
|||
* component: '<div class="my-class">My element</div>', // or a JSON of components
|
|||
* }); |
|||
*/ |
|||
add(props, opts = {}) { |
|||
const { em } = this; |
|||
props.id = props.id || this._createId(); |
|||
const add = () => { |
|||
const page = this.pages.add(props, opts); |
|||
opts.select && this.select(page); |
|||
return page; |
|||
}; |
|||
!opts.silent && em.trigger(evPageAddBefore, props, add, opts); |
|||
return !opts.abort && add(); |
|||
}, |
|||
|
|||
/** |
|||
* Remove page |
|||
* @param {String|Page} page Page or page id |
|||
* @returns {Page} |
|||
* @example |
|||
* const removedPage = pageManager.remove('page-id'); |
|||
* // or by passing the page
|
|||
* const somePage = pageManager.get('page-id'); |
|||
* pageManager.remove(somePage); |
|||
*/ |
|||
remove(page, opts = {}) { |
|||
const { em } = this; |
|||
const pg = isString(page) ? this.get(page) : page; |
|||
const rm = () => { |
|||
pg && this.pages.remove(pg, opts); |
|||
return pg; |
|||
}; |
|||
!opts.silent && em.trigger(evPageRemoveBefore, pg, rm, opts); |
|||
return !opts.abort && rm(); |
|||
}, |
|||
|
|||
/** |
|||
* Get page by id |
|||
* @param {String} id Page id |
|||
* @returns {Page} |
|||
* @example |
|||
* const somePage = pageManager.get('page-id'); |
|||
*/ |
|||
get(id) { |
|||
return this.pages.filter(p => p.get('id') === id)[0]; |
|||
}, |
|||
|
|||
/** |
|||
* Get main page (the first one available) |
|||
* @returns {Page} |
|||
* @example |
|||
* const mainPage = pageManager.getMain(); |
|||
*/ |
|||
getMain() { |
|||
const { pages } = this; |
|||
return pages.filter(p => p.get('type') === typeMain)[0] || pages.at(0); |
|||
}, |
|||
|
|||
/** |
|||
* Get all pages |
|||
* @returns {Array<Page>} |
|||
* @example |
|||
* const arrayOfPages = pageManager.getAll(); |
|||
*/ |
|||
getAll() { |
|||
return this.pages.models; |
|||
}, |
|||
|
|||
getAllMap() { |
|||
return this.getAll().reduce((acc, i) => { |
|||
acc[i.get('id')] = i; |
|||
return acc; |
|||
}, {}); |
|||
}, |
|||
|
|||
/** |
|||
* Change the selected page. This will switch the page rendered in canvas |
|||
* @param {String|Page} page Page or page id |
|||
* @returns {this} |
|||
* @example |
|||
* pageManager.select('page-id'); |
|||
* // or by passing the page
|
|||
* const somePage = pageManager.get('page-id'); |
|||
* pageManager.select(somePage); |
|||
*/ |
|||
select(page, opts = {}) { |
|||
const pg = isString(page) ? this.get(page) : page; |
|||
if (pg) { |
|||
this.em.trigger(evPageSelectBefore, pg, opts); |
|||
this.model.set('selected', pg, opts); |
|||
} |
|||
return this; |
|||
}, |
|||
|
|||
/** |
|||
* Get the selected page |
|||
* @returns {Page} |
|||
* @example |
|||
* const selectedPage = pageManager.getSelected(); |
|||
*/ |
|||
getSelected() { |
|||
return this.model.get('selected'); |
|||
}, |
|||
|
|||
destroy() { |
|||
this.pages.off().reset(); |
|||
this.model.stopListening(); |
|||
this.model.clear({ silent: true }); |
|||
['selected', 'config', 'em', 'pages', 'model'].map(i => (this[i] = 0)); |
|||
}, |
|||
|
|||
store(noStore) { |
|||
if (!this.em.get('hasPages')) return {}; |
|||
const obj = {}; |
|||
const cnf = this.config; |
|||
obj[this.storageKey] = JSON.stringify(this.getAll()); |
|||
if (!noStore && cnf.stm) cnf.stm.store(obj); |
|||
return obj; |
|||
}, |
|||
|
|||
load(data = {}) { |
|||
const key = this.storageKey; |
|||
let res = data[key] || []; |
|||
|
|||
if (typeof res == 'string') { |
|||
try { |
|||
res = JSON.parse(data[key]); |
|||
} catch (err) {} |
|||
} |
|||
|
|||
res && res.length && this.pages.reset(res); |
|||
|
|||
return res; |
|||
}, |
|||
|
|||
_createId() { |
|||
const pages = this.getAll(); |
|||
const len = pages.length + 16; |
|||
const pagesMap = this.getAllMap(); |
|||
let id; |
|||
|
|||
do { |
|||
id = createId(len); |
|||
} while (pagesMap[id]); |
|||
|
|||
return id; |
|||
} |
|||
}; |
|||
}; |
|||
@ -0,0 +1,61 @@ |
|||
import { Model } from 'backbone'; |
|||
import { result, forEach } from 'underscore'; |
|||
import Frames from 'canvas/model/Frames'; |
|||
|
|||
export default Model.extend({ |
|||
defaults: () => ({ |
|||
frames: [], |
|||
_undo: true |
|||
}), |
|||
|
|||
initialize(props, opts = {}) { |
|||
const { config = {} } = opts; |
|||
const { em } = config; |
|||
const defFrame = {}; |
|||
this.em = em; |
|||
if (!props.frames) { |
|||
defFrame.component = props.component; |
|||
defFrame.styles = props.styles; |
|||
['component', 'styles'].map(i => this.unset(i)); |
|||
} |
|||
const frms = props.frames || [defFrame]; |
|||
const frames = new Frames(frms, config); |
|||
frames.page = this; |
|||
this.set('frames', frames); |
|||
const um = em && em.get('UndoManager'); |
|||
um && um.add(frames); |
|||
}, |
|||
|
|||
onRemove() { |
|||
this.get('frames').reset(); |
|||
}, |
|||
|
|||
getFrames() { |
|||
return this.get('frames'); |
|||
}, |
|||
|
|||
getMainFrame() { |
|||
return this.getFrames().at(0); |
|||
}, |
|||
|
|||
getMainComponent() { |
|||
const frame = this.getMainFrame(); |
|||
return frame && frame.getComponent(); |
|||
}, |
|||
|
|||
toJSON(opts = {}) { |
|||
const obj = Model.prototype.toJSON.call(this, opts); |
|||
const defaults = result(this, 'defaults'); |
|||
|
|||
// Remove private keys
|
|||
forEach(obj, (value, key) => { |
|||
key.indexOf('_') === 0 && delete obj[key]; |
|||
}); |
|||
|
|||
forEach(defaults, (value, key) => { |
|||
if (obj[key] === value) delete obj[key]; |
|||
}); |
|||
|
|||
return obj; |
|||
} |
|||
}); |
|||
@ -0,0 +1,26 @@ |
|||
import { Collection } from 'backbone'; |
|||
import Page from './Page'; |
|||
|
|||
export default Collection.extend({ |
|||
model: Page, |
|||
|
|||
initialize(models, config = {}) { |
|||
this.config = config; |
|||
this.on('reset', this.onReset); |
|||
this.on('remove', this.onRemove); |
|||
}, |
|||
|
|||
onReset(m, opts = {}) { |
|||
const prev = opts.previousModels || []; |
|||
prev.map(p => this.onRemove(p)); |
|||
}, |
|||
|
|||
onRemove(removed) { |
|||
removed && removed.onRemove(); |
|||
}, |
|||
|
|||
add(m, o = {}) { |
|||
const { config } = this; |
|||
return Collection.prototype.add.call(this, m, { ...o, config }); |
|||
} |
|||
}); |
|||
@ -0,0 +1,252 @@ |
|||
import Editor from 'editor'; |
|||
|
|||
describe('Pages', () => { |
|||
let editor; |
|||
let em; |
|||
let domc; |
|||
let initCmpLen; |
|||
let pm; |
|||
|
|||
beforeAll(() => { |
|||
editor = new Editor({ pageManager: true }); |
|||
em = editor.getModel(); |
|||
domc = em.get('DomComponents'); |
|||
pm = em.get('PageManager'); |
|||
pm.onLoad(); |
|||
initCmpLen = Object.keys(domc.allById()).length; |
|||
}); |
|||
|
|||
afterAll(() => { |
|||
editor.destroy(); |
|||
pm = 0; |
|||
em = 0; |
|||
domc = 0; |
|||
}); |
|||
|
|||
beforeEach(() => {}); |
|||
afterEach(() => {}); |
|||
|
|||
test('Pages module exists', () => { |
|||
expect(pm).toBeTruthy(); |
|||
}); |
|||
|
|||
test('Has by default one page created', () => { |
|||
expect(pm.getAll().length).toBe(1); |
|||
}); |
|||
|
|||
test('The default page is selected', () => { |
|||
expect(pm.getMain()).toBe(pm.getSelected()); |
|||
}); |
|||
|
|||
test('The default page has one frame', () => { |
|||
expect(pm.getMain().getFrames().length).toBe(1); |
|||
}); |
|||
|
|||
test('The default frame has the wrapper component', () => { |
|||
const frame = pm |
|||
.getMain() |
|||
.getFrames() |
|||
.at(0); |
|||
const frameCmp = frame.getComponent(); |
|||
expect(frameCmp.is('wrapper')).toBe(true); |
|||
}); |
|||
|
|||
test('The default wrapper has no content', () => { |
|||
const frame = pm |
|||
.getMain() |
|||
.getFrames() |
|||
.at(0); |
|||
const frameCmp = frame.getComponent(); |
|||
expect(frameCmp.components().length).toBe(0); |
|||
expect(frame.getStyles().length).toBe(0); |
|||
expect(initCmpLen).toBe(1); |
|||
}); |
|||
|
|||
test('Adding new page with selection', () => { |
|||
const name = 'Test page'; |
|||
const page = pm.add({ name }, { select: 1 }); |
|||
expect(page.id).toBeTruthy(); |
|||
expect(page.get('name')).toBe(name); |
|||
expect(pm.getSelected()).toBe(page); |
|||
expect(pm.getAll().length).toBe(2); |
|||
const pageComp = page.getMainComponent(); |
|||
expect(pageComp.is('wrapper')).toBe(true); |
|||
expect(pageComp.components().length).toBe(0); |
|||
}); |
|||
|
|||
describe('Init with pages', () => { |
|||
let idPage1, idComp1, idComp2, comp1, comp2, initPages, allbyId; |
|||
const createCompDef = id => ({ |
|||
attributes: { |
|||
id, |
|||
class: id, |
|||
customattr: id |
|||
}, |
|||
components: `Component ${id}` |
|||
}); |
|||
beforeAll(() => { |
|||
idPage1 = 'page-1'; |
|||
idComp1 = 'comp1'; |
|||
idComp2 = 'comp2'; |
|||
comp1 = createCompDef(idComp1); |
|||
comp2 = createCompDef(idComp2); |
|||
initPages = [ |
|||
{ |
|||
id: idPage1, |
|||
component: [comp1], |
|||
styles: `#${idComp1} { color: red }` |
|||
}, |
|||
{ |
|||
id: 'page-2', |
|||
frames: [ |
|||
{ |
|||
component: [comp2], |
|||
styles: `#${idComp2} { color: blue }` |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
id: 'page-3', |
|||
frames: [ |
|||
{ |
|||
component: '<div id="comp3">Component 3</div>', |
|||
styles: `#comp3 { color: green }` |
|||
} |
|||
] |
|||
} |
|||
]; |
|||
editor = new Editor({ |
|||
pageManager: { |
|||
pages: initPages |
|||
} |
|||
}); |
|||
em = editor.getModel(); |
|||
domc = em.get('DomComponents'); |
|||
pm = em.get('PageManager'); |
|||
pm.onLoad(); |
|||
allbyId = domc.allById(); |
|||
initCmpLen = Object.keys(allbyId).length; |
|||
}); |
|||
|
|||
afterAll(() => { |
|||
editor.destroy(); |
|||
pm = 0; |
|||
em = 0; |
|||
domc = 0; |
|||
}); |
|||
|
|||
test('Pages are created correctly', () => { |
|||
const pages = pm.getAll(); |
|||
expect(pages.length).toBe(initPages.length); |
|||
pages.map(page => { |
|||
// All pages should have an ID
|
|||
expect(page.get('id')).toBeTruthy(); |
|||
// The main component is always a wrapper
|
|||
expect( |
|||
page |
|||
.getMainFrame() |
|||
.getComponent() |
|||
.is('wrapper') |
|||
).toBe(true); |
|||
}); |
|||
// Components container should contain the same amount of wrappers as pages
|
|||
const wrappers = Object.keys(allbyId) |
|||
.map(id => allbyId[id]) |
|||
.filter(i => i.is('wrapper')); |
|||
expect(wrappers.length).toBe(initPages.length); |
|||
// Components container should contain the right amount of components
|
|||
// Number of wrappers (eg. 3) where each one containes 1 component and 1 textnode (3 * 3)
|
|||
expect(initCmpLen).toBe(initPages.length * 3); |
|||
// Each page contains 1 rule per component
|
|||
expect(em.get('CssComposer').getAll().length).toBe(initPages.length); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
describe('Managing pages', () => { |
|||
let editor; |
|||
let em; |
|||
let domc; |
|||
let initCmpLen; |
|||
let pm; |
|||
|
|||
beforeEach(() => { |
|||
editor = new Editor({ pageManager: true }); |
|||
em = editor.getModel(); |
|||
domc = em.get('DomComponents'); |
|||
pm = em.get('PageManager'); |
|||
editor.getModel().loadOnStart(); |
|||
initCmpLen = Object.keys(domc.allById()).length; |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
editor.destroy(); |
|||
pm = 0; |
|||
em = 0; |
|||
domc = 0; |
|||
}); |
|||
|
|||
test('Add page', () => { |
|||
const eventAdd = jest.fn(); |
|||
em.on(pm.events.add, eventAdd); |
|||
pm.add({}); |
|||
expect(pm.getAll().length).toBe(2); |
|||
expect(eventAdd).toBeCalledTimes(1); |
|||
}); |
|||
|
|||
test('Abort add page', () => { |
|||
em.on(pm.events.addBefore, (p, c, opts) => { |
|||
opts.abort = 1; |
|||
}); |
|||
pm.add({}); |
|||
expect(pm.getAll().length).toBe(1); |
|||
}); |
|||
|
|||
test('Abort add page and complete', () => { |
|||
em.on(pm.events.addBefore, (p, complete, opts) => { |
|||
opts.abort = 1; |
|||
complete(); |
|||
}); |
|||
pm.add({}); |
|||
expect(pm.getAll().length).toBe(2); |
|||
}); |
|||
|
|||
test('Remove page', () => { |
|||
const eventRm = jest.fn(); |
|||
em.on(pm.events.remove, eventRm); |
|||
const page = pm.add({}); |
|||
pm.remove(page.id); |
|||
expect(pm.getAll().length).toBe(1); |
|||
expect(eventRm).toBeCalledTimes(1); |
|||
}); |
|||
|
|||
test('Abort remove page', () => { |
|||
em.on(pm.events.removeBefore, (p, c, opts) => { |
|||
opts.abort = 1; |
|||
}); |
|||
const page = pm.add({}); |
|||
pm.remove(page.id); |
|||
expect(pm.getAll().length).toBe(2); |
|||
}); |
|||
|
|||
test('Abort remove page and complete', () => { |
|||
em.on(pm.events.removeBefore, (p, complete, opts) => { |
|||
opts.abort = 1; |
|||
complete(); |
|||
}); |
|||
const page = pm.add({}); |
|||
pm.remove(page.id); |
|||
expect(pm.getAll().length).toBe(1); |
|||
}); |
|||
|
|||
test('Change page', () => { |
|||
const event = jest.fn(); |
|||
em.on(pm.events.update, event); |
|||
const page = pm.add({}); |
|||
const up = { name: 'Test' }; |
|||
const opts = { myopts: 1 }; |
|||
page.set(up, opts); |
|||
expect(event).toBeCalledTimes(1); |
|||
expect(event).toBeCalledWith(page, up, opts); |
|||
}); |
|||
}); |
|||
Loading…
Reference in new issue