mirror of https://github.com/artf/grapesjs.git
committed by
GitHub
104 changed files with 3681 additions and 1617 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 22 KiB |
@ -0,0 +1,19 @@ |
|||
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> |
|||
|
|||
## Frame |
|||
|
|||
|
|||
|
|||
### Properties |
|||
|
|||
* `component` **([Object][1] | [String][2])** Wrapper component definition. You can also pass an HTML string as components of the default wrapper component. |
|||
* `width` **[String][2]?** Width of the frame. By default, the canvas width will be taken. |
|||
* `height` **[String][2]?** Height of the frame. By default, the canvas height will be taken. |
|||
* `x` **[Number][3]?** Horizontal position of the frame in the canvas. |
|||
* `y` **[Number][3]?** Vertical position of the frame in the canvas. |
|||
|
|||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object |
|||
|
|||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String |
|||
|
|||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number |
|||
@ -0,0 +1,60 @@ |
|||
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> |
|||
|
|||
## Sector |
|||
|
|||
|
|||
|
|||
### Properties |
|||
|
|||
* `id` **[String][1]** Sector id, eg. `typography` |
|||
* `name` **[String][1]** Sector name, eg. `Typography` |
|||
* `open` **[Boolean][2]?** Indicates the open state. |
|||
* `properties` **[Array][3]<[Object][4]>?** Indicate an array of Property defintions[Property]: property.html |
|||
|
|||
### getId |
|||
|
|||
Get sector id. |
|||
|
|||
Returns **[String][1]** |
|||
|
|||
### getName |
|||
|
|||
Get sector name. |
|||
|
|||
Returns **[String][1]** |
|||
|
|||
### setName |
|||
|
|||
Update sector name. |
|||
|
|||
#### Parameters |
|||
|
|||
* `value` **[String][1]** New sector name |
|||
|
|||
### isOpen |
|||
|
|||
Check if the sector is open |
|||
|
|||
Returns **[Boolean][2]** |
|||
|
|||
### setOpen |
|||
|
|||
Update Sector open state |
|||
|
|||
#### Parameters |
|||
|
|||
* `value` **[Boolean][2]** |
|||
|
|||
### getProperties |
|||
|
|||
Get sector properties. |
|||
|
|||
Returns **[Array][3]<[Property]>** |
|||
|
|||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String |
|||
|
|||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean |
|||
|
|||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array |
|||
|
|||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object |
|||
@ -0,0 +1,79 @@ |
|||
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> |
|||
|
|||
## Selector |
|||
|
|||
|
|||
|
|||
### Properties |
|||
|
|||
* `name` **[String][1]** Selector name, eg. `my-class` |
|||
* `label` **[String][1]** Selector label, eg. `My Class` |
|||
* `type` **[Number][2]?** Type of the selector. 1 (class) | 2 (id) |
|||
* `active` **[Boolean][3]?** If not active, it's not selectable by the Style Manager. |
|||
* `private` **[Boolean][3]?** If true, it can't be seen by the Style Manager, but it will be rendered in the canvas and in export code. |
|||
* `protected` **[Boolean][3]?** If true, it can't be removed from the attacched component. |
|||
|
|||
### toString |
|||
|
|||
Get selector as a string. |
|||
|
|||
#### Examples |
|||
|
|||
```javascript |
|||
// Given such selector: { name: 'my-selector', type: 2 } |
|||
console.log(selector.toString()); |
|||
// -> `#my-selector` |
|||
``` |
|||
|
|||
Returns **[String][1]** |
|||
|
|||
### getLabel |
|||
|
|||
Get selector label. |
|||
|
|||
#### Examples |
|||
|
|||
```javascript |
|||
// Given such selector: { name: 'my-selector', label: 'My selector' } |
|||
console.log(selector.getLabel()); |
|||
// -> `My selector` |
|||
``` |
|||
|
|||
Returns **[String][1]** |
|||
|
|||
### setLabel |
|||
|
|||
Update selector label. |
|||
|
|||
#### Parameters |
|||
|
|||
* `label` **[String][1]** New label |
|||
|
|||
#### Examples |
|||
|
|||
```javascript |
|||
// Given such selector: { name: 'my-selector', label: 'My selector' } |
|||
selector.setLabel('New Label') |
|||
console.log(selector.getLabel()); |
|||
// -> `New Label` |
|||
``` |
|||
|
|||
### getActive |
|||
|
|||
Get selector active state. |
|||
|
|||
Returns **[Boolean][3]** |
|||
|
|||
### setActive |
|||
|
|||
Update selector active state. |
|||
|
|||
#### Parameters |
|||
|
|||
* `value` **[Boolean][3]** New active state |
|||
|
|||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String |
|||
|
|||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number |
|||
|
|||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean |
|||
@ -0,0 +1,24 @@ |
|||
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> |
|||
|
|||
## State |
|||
|
|||
|
|||
|
|||
### Properties |
|||
|
|||
* `name` **[String][1]** State name, eg. `hover`, `nth-of-type(2n)` |
|||
* `label` **[String][1]** State label, eg. `Hover`, `Even/Odd` |
|||
|
|||
### getName |
|||
|
|||
Get state name |
|||
|
|||
Returns **[String][1]** |
|||
|
|||
### getLabel |
|||
|
|||
Get state label. If label was not provided, the name will be returned. |
|||
|
|||
Returns **[String][1]** |
|||
|
|||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String |
|||
@ -0,0 +1,150 @@ |
|||
--- |
|||
title: Selector Manager |
|||
--- |
|||
|
|||
# Selector Manager |
|||
|
|||
<p align="center"><img :src="$withBase('/selector-manager.jpg')" alt="GrapesJS - Selector Manager"/></p> |
|||
|
|||
|
|||
The [Selector] allows the reuse of styles across all of your [Components] in the project (exactly what classes do in HTML) and the main goal of the Selector Manager is to collect them and indicate the current state of the selection. |
|||
|
|||
::: warning |
|||
This guide is referring to GrapesJS v0.17.28 or higher |
|||
::: |
|||
|
|||
[[toc]] |
|||
|
|||
|
|||
## Configuration |
|||
|
|||
To change the default configurations you have to pass the `selectorManager` property with the main configuration object. |
|||
|
|||
```js |
|||
const editor = grapesjs.init({ |
|||
... |
|||
selectorManager: { |
|||
... |
|||
} |
|||
}); |
|||
``` |
|||
|
|||
Check the full list of available options here: [Selector Manager Config](https://github.com/artf/grapesjs/blob/master/src/selector_manager/config/config.js) |
|||
|
|||
|
|||
## Initialization |
|||
|
|||
The Selector Manager starts to collect data once componenets and styles are loaded. The default UI is displayed along with the default panels provided by GrapesJS core, in case you need to setup the editor with your own panels we recommend following the [Getting Started] guide. |
|||
|
|||
In the example below we init the editor with already provided components and styles. |
|||
|
|||
```js |
|||
const editor = grapesjs.init({ |
|||
container: '#gjs', |
|||
height: '100%', |
|||
storageManager: false, |
|||
components: ` |
|||
<div class="class-a">Element A</div> |
|||
<div class="class-a class-b">Element A-B</div> |
|||
<div class="class-a class-b class-c">Element A-B-C</div> |
|||
`, |
|||
style: ` |
|||
.class-a { color: red } |
|||
.class-b { color: green } |
|||
.class-c { color: blue } |
|||
`, |
|||
}); |
|||
``` |
|||
Internally, the example above will provide to Selector Manager 3 selectors: `class-a`, `class-b` and `class-c`. |
|||
|
|||
Without any selected component, the Selector Manager UI is hidden by default (along with the Style Manager). By selecting the `Element A-B-C` you will see the current selection of what will be actually styled. |
|||
|
|||
<img :src="$withBase('/sm-selected-component.jpg')" alt="Selected component" style="display: block; margin: auto"/> |
|||
|
|||
The label **Selected** indicates on which CSS query styles will be applied, so if you try to change the color of the current selection, this is what you'll get in the final code: |
|||
|
|||
```css |
|||
.class-a.class-b.class-c { |
|||
color: #483acb; |
|||
} |
|||
``` |
|||
|
|||
You can also disable specific selectors and change the state (eg. Hover) in order to switch the target of styling. |
|||
|
|||
<img :src="$withBase('/sm-disable-selector.jpg')" alt="Disabled selectors" style="display: block; margin: auto"/> |
|||
|
|||
|
|||
|
|||
|
|||
## Component-first selectors |
|||
|
|||
By default, selecting components with classes will indicate their selectors as target style. That means that any change in Style Manager will be applied to all components containing those **Selected** classes. |
|||
|
|||
In case you need to select single components as style targets, you can enable `componentFirst` option. |
|||
|
|||
```js |
|||
const editor = grapesjs.init({ |
|||
// ... |
|||
selectorManager: { |
|||
componentFirst: true, |
|||
}, |
|||
}); |
|||
``` |
|||
|
|||
This option enables also the possibility to style multiple components and the ability to sync common selectors with the current component styles (the refresh icon). |
|||
|
|||
<img :src="$withBase('/sm-component-first.jpg')" alt="Component First" style="display: block; margin: auto"/> |
|||
|
|||
::: warning |
|||
With multiple selection, the Style Manager shows always styles of the last selected component. |
|||
::: |
|||
|
|||
|
|||
|
|||
## Programmatic usage |
|||
If you need to manage your selectors programmatically you can use its [APIs][Selector API]. |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
## Customization |
|||
|
|||
The default UI can handle most of the common tasks but in case you need a more advanced logic/elements, that requires a replace of the default UI. |
|||
|
|||
All you have to do is to indicate the editor your intent to use a custom UI and then subscribe to the `selector:custom` event that will trigger on any necessary update of the UI. |
|||
|
|||
```js |
|||
const editor = grapesjs.init({ |
|||
// ... |
|||
selectorManager: { |
|||
custom: true, |
|||
// ... |
|||
}, |
|||
}); |
|||
|
|||
editor.on('selector:custom', props => { |
|||
// props.container (HTMLElement) - The default element where you can append your UI |
|||
|
|||
// Here you would put the logic to render/update your UI. |
|||
}); |
|||
``` |
|||
|
|||
In the example below we'll replicate most of the default functionality by using solely the Selector Manager API. |
|||
|
|||
<demo-viewer value="v8cgkLfr" height="500" darkcode/> |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
## Events |
|||
|
|||
For a complete list of available events, you can check it [here](/api/selector_manager.html#available-events). |
|||
|
|||
|
|||
[Selector]: </api/selector.html> |
|||
[Style Manager]: <Style-manager.html> |
|||
[Components]: <Components.html> |
|||
[Getting Started]: </getting-started.html> |
|||
[Selector API]: </api/selector_manager.html> |
|||
@ -1,28 +0,0 @@ |
|||
const fs = require('fs'); |
|||
const path = require('path'); |
|||
const localeDir = './locale'; |
|||
const localeSrcDir = './src/i18n/locale'; |
|||
|
|||
const copyRecursiveSync = (src, dest) => { |
|||
const exists = fs.existsSync(src); |
|||
const isDir = exists && fs.statSync(src).isDirectory(); |
|||
|
|||
if (isDir) { |
|||
fs.mkdirSync(dest); |
|||
fs.readdirSync(src).forEach((file) => { |
|||
copyRecursiveSync(path.join(src, file), path.join(dest, file)); |
|||
}); |
|||
} else if (exists) { |
|||
fs.createReadStream(src).pipe(fs.createWriteStream(dest)); |
|||
} |
|||
}; |
|||
|
|||
copyRecursiveSync(localeSrcDir, localeDir); |
|||
|
|||
// Create locale/index.js file
|
|||
let result = ''; |
|||
fs.readdirSync(localeDir).forEach(file => { |
|||
const name = file.replace('.js', ''); |
|||
result += `export { default as ${name} } from './${name}'\n`; |
|||
}); |
|||
fs.writeFileSync(`${localeDir}/index.js`, result); |
|||
@ -1,6 +1,6 @@ |
|||
import Backbone from 'backbone'; |
|||
import { Collection } from 'common'; |
|||
import Block from './Block'; |
|||
|
|||
export default Backbone.Collection.extend({ |
|||
model: Block |
|||
}); |
|||
export default class Blocks extends Collection {} |
|||
|
|||
Blocks.prototype.model = Block; |
|||
|
|||
@ -1,6 +1,6 @@ |
|||
import Backbone from 'backbone'; |
|||
import { Collection } from 'common'; |
|||
import Category from './Category'; |
|||
|
|||
export default Backbone.Collection.extend({ |
|||
model: Category |
|||
}); |
|||
export default class Categories extends Collection {} |
|||
|
|||
Categories.prototype.model = Category; |
|||
|
|||
@ -1,10 +1,12 @@ |
|||
import Backbone from 'backbone'; |
|||
import { Model } from 'common'; |
|||
|
|||
export default Backbone.Model.extend({ |
|||
defaults: { |
|||
id: '', |
|||
label: '', |
|||
open: true, |
|||
attributes: {} |
|||
export default class Category extends Model { |
|||
defaults() { |
|||
return { |
|||
id: '', |
|||
label: '', |
|||
open: true, |
|||
attributes: {} |
|||
}; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@ -1,23 +1,57 @@ |
|||
import { isFunction } from 'underscore'; |
|||
import { createEl } from '../../utils/dom'; |
|||
|
|||
export default { |
|||
run(editor, sender) { |
|||
const bm = editor.BlockManager; |
|||
const pn = editor.Panels; |
|||
open() { |
|||
const { container, editor, bm, config } = this; |
|||
const { custom, appendTo } = config; |
|||
|
|||
if (isFunction(custom.open)) { |
|||
return custom.open(bm.__customData()); |
|||
} |
|||
|
|||
if (!this.blocks) { |
|||
bm.render(); |
|||
if (this.firstRender && !appendTo) { |
|||
const id = 'views-container'; |
|||
const blocks = document.createElement('div'); |
|||
const pn = editor.Panels; |
|||
const panels = pn.getPanel(id) || pn.addPanel({ id }); |
|||
blocks.appendChild(bm.getContainer()); |
|||
panels.set('appendContent', blocks).trigger('change:appendContent'); |
|||
this.blocks = blocks; |
|||
panels.set('appendContent', container).trigger('change:appendContent'); |
|||
if (!custom) container.appendChild(bm.render()); |
|||
} |
|||
|
|||
if (container) container.style.display = 'block'; |
|||
}, |
|||
|
|||
close() { |
|||
const { container, config } = this; |
|||
const { custom } = config; |
|||
|
|||
if (isFunction(custom.close)) { |
|||
return custom.close(this.bm.__customData()); |
|||
} |
|||
|
|||
if (container) container.style.display = 'none'; |
|||
}, |
|||
|
|||
run(editor) { |
|||
const bm = editor.Blocks; |
|||
this.config = bm.getConfig(); |
|||
this.firstRender = !this.container; |
|||
this.container = this.container || createEl('div'); |
|||
this.editor = editor; |
|||
this.bm = bm; |
|||
const { container } = this; |
|||
bm.__behaviour({ |
|||
container |
|||
}); |
|||
|
|||
if (this.config.custom) { |
|||
bm.__trgCustom(); |
|||
} |
|||
|
|||
this.blocks.style.display = 'block'; |
|||
this.open(); |
|||
}, |
|||
|
|||
stop() { |
|||
const blocks = this.blocks; |
|||
blocks && (blocks.style.display = 'none'); |
|||
this.close(); |
|||
} |
|||
}; |
|||
|
|||
@ -1,129 +1,172 @@ |
|||
import Backbone from 'backbone'; |
|||
import { Model } from 'common'; |
|||
import { result, forEach, keys } from 'underscore'; |
|||
|
|||
const TYPE_CLASS = 1; |
|||
const TYPE_ID = 2; |
|||
const { Model } = Backbone; |
|||
|
|||
const Selector = Model.extend( |
|||
{ |
|||
idAttribute: 'name', |
|||
|
|||
defaults: { |
|||
/** |
|||
* @typedef Selector |
|||
* @property {String} name Selector name, eg. `my-class` |
|||
* @property {String} label Selector label, eg. `My Class` |
|||
* @property {Number} [type=1] Type of the selector. 1 (class) | 2 (id) |
|||
* @property {Boolean} [active=true] If not active, it's not selectable by the Style Manager. |
|||
* @property {Boolean} [private=false] If true, it can't be seen by the Style Manager, but it will be rendered in the canvas and in export code. |
|||
* @property {Boolean} [protected=false] If true, it can't be removed from the attacched component. |
|||
*/ |
|||
export default class Selector extends Model { |
|||
defaults() { |
|||
return { |
|||
name: '', |
|||
|
|||
label: '', |
|||
|
|||
// Type of the selector
|
|||
type: TYPE_CLASS, |
|||
|
|||
// If not active it's not selectable by the style manager (uncheckboxed)
|
|||
active: true, |
|||
|
|||
// Can't be seen by the style manager, therefore even by the user
|
|||
// Will be rendered only in export code
|
|||
private: false, |
|||
protected: false, |
|||
_undo: true |
|||
}; |
|||
} |
|||
|
|||
// If true, can't be removed from the attacched element
|
|||
protected: false |
|||
}, |
|||
initialize(props, opts = {}) { |
|||
const { config = {} } = opts; |
|||
const name = this.get('name'); |
|||
const label = this.get('label'); |
|||
|
|||
initialize(props, opts = {}) { |
|||
const { config = {} } = opts; |
|||
const name = this.get('name'); |
|||
const label = this.get('label'); |
|||
if (!name) { |
|||
this.set('name', label); |
|||
} else if (!label) { |
|||
this.set('label', name); |
|||
} |
|||
|
|||
if (!name) { |
|||
this.set('name', label); |
|||
} else if (!label) { |
|||
this.set('label', name); |
|||
} |
|||
const namePreEsc = this.get('name'); |
|||
const { escapeName } = config; |
|||
const nameEsc = escapeName |
|||
? escapeName(namePreEsc) |
|||
: Selector.escapeName(namePreEsc); |
|||
this.set('name', nameEsc); |
|||
this.em = config.em; |
|||
} |
|||
|
|||
const namePreEsc = this.get('name'); |
|||
const { escapeName } = config; |
|||
const nameEsc = escapeName |
|||
? escapeName(namePreEsc) |
|||
: Selector.escapeName(namePreEsc); |
|||
this.set('name', nameEsc); |
|||
this.em = config.em; |
|||
}, |
|||
|
|||
isId() { |
|||
return this.get('type') === TYPE_ID; |
|||
}, |
|||
|
|||
isClass() { |
|||
return this.get('type') === TYPE_CLASS; |
|||
}, |
|||
|
|||
/** |
|||
* Get full selector name |
|||
* @return {string} |
|||
*/ |
|||
getFullName(opts = {}) { |
|||
const { escape } = opts; |
|||
const name = this.get('name'); |
|||
let init = ''; |
|||
|
|||
switch (this.get('type')) { |
|||
case TYPE_CLASS: |
|||
init = '.'; |
|||
break; |
|||
case TYPE_ID: |
|||
init = '#'; |
|||
break; |
|||
} |
|||
isId() { |
|||
return this.get('type') === TYPE_ID; |
|||
} |
|||
|
|||
return init + (escape ? escape(name) : name); |
|||
}, |
|||
isClass() { |
|||
return this.get('type') === TYPE_CLASS; |
|||
} |
|||
|
|||
toJSON(opts = {}) { |
|||
const { em } = this; |
|||
let obj = Model.prototype.toJSON.call(this, [opts]); |
|||
const defaults = result(this, 'defaults'); |
|||
getFullName(opts = {}) { |
|||
const { escape } = opts; |
|||
const name = this.get('name'); |
|||
let pfx = ''; |
|||
|
|||
switch (this.get('type')) { |
|||
case TYPE_CLASS: |
|||
pfx = '.'; |
|||
break; |
|||
case TYPE_ID: |
|||
pfx = '#'; |
|||
break; |
|||
} |
|||
|
|||
if (em && em.getConfig('avoidDefaults')) { |
|||
forEach(defaults, (value, key) => { |
|||
if (obj[key] === value) { |
|||
delete obj[key]; |
|||
} |
|||
}); |
|||
return pfx + (escape ? escape(name) : name); |
|||
} |
|||
|
|||
if (obj.label === obj.name) { |
|||
delete obj.label; |
|||
} |
|||
/** |
|||
* Get selector as a string. |
|||
* @returns {String} |
|||
* @example |
|||
* // Given such selector: { name: 'my-selector', type: 2 }
|
|||
* console.log(selector.toString()); |
|||
* // -> `#my-selector`
|
|||
*/ |
|||
toString() { |
|||
return this.getFullName(); |
|||
} |
|||
|
|||
const objLen = keys(obj).length; |
|||
/** |
|||
* Get selector label. |
|||
* @returns {String} |
|||
* @example |
|||
* // Given such selector: { name: 'my-selector', label: 'My selector' }
|
|||
* console.log(selector.getLabel()); |
|||
* // -> `My selector`
|
|||
*/ |
|||
getLabel() { |
|||
return this.get('label'); |
|||
} |
|||
|
|||
if (objLen === 1 && obj.name) { |
|||
obj = obj.name; |
|||
} |
|||
/** |
|||
* Update selector label. |
|||
* @param {String} label New label |
|||
* @example |
|||
* // Given such selector: { name: 'my-selector', label: 'My selector' }
|
|||
* selector.setLabel('New Label') |
|||
* console.log(selector.getLabel()); |
|||
* // -> `New Label`
|
|||
*/ |
|||
setLabel(label) { |
|||
return this.set('label', label); |
|||
} |
|||
|
|||
/** |
|||
* Get selector active state. |
|||
* @returns {Boolean} |
|||
*/ |
|||
getActive() { |
|||
return this.get('active'); |
|||
} |
|||
|
|||
/** |
|||
* Update selector active state. |
|||
* @param {Boolean} value New active state |
|||
*/ |
|||
setActive(value) { |
|||
return this.set('active', value); |
|||
} |
|||
|
|||
toJSON(opts = {}) { |
|||
const { em } = this; |
|||
let obj = Model.prototype.toJSON.call(this, [opts]); |
|||
const defaults = result(this, 'defaults'); |
|||
|
|||
if (objLen === 2 && obj.name && obj.type) { |
|||
obj = this.getFullName(); |
|||
if (em && em.getConfig('avoidDefaults')) { |
|||
forEach(defaults, (value, key) => { |
|||
if (obj[key] === value) { |
|||
delete obj[key]; |
|||
} |
|||
}); |
|||
|
|||
if (obj.label === obj.name) { |
|||
delete obj.label; |
|||
} |
|||
|
|||
return obj; |
|||
} |
|||
}, |
|||
{ |
|||
// All type selectors: https://developer.mozilla.org/it/docs/Web/CSS/CSS_Selectors
|
|||
// Here I define only what I need
|
|||
TYPE_CLASS, |
|||
|
|||
TYPE_ID, |
|||
|
|||
/** |
|||
* Escape string |
|||
* @param {string} name |
|||
* @return {string} |
|||
* @private |
|||
*/ |
|||
escapeName(name) { |
|||
return `${name}`.trim().replace(/([^a-z0-9\w-\:]+)/gi, '-'); |
|||
const objLen = keys(obj).length; |
|||
|
|||
if (objLen === 1 && obj.name) { |
|||
obj = obj.name; |
|||
} |
|||
|
|||
if (objLen === 2 && obj.name && obj.type) { |
|||
obj = this.getFullName(); |
|||
} |
|||
} |
|||
} |
|||
); |
|||
|
|||
export default Selector; |
|||
return obj; |
|||
} |
|||
} |
|||
|
|||
Selector.prototype.idAttribute = 'name'; |
|||
|
|||
// Type selectors: https://developer.mozilla.org/it/docs/Web/CSS/CSS_Selectors
|
|||
Selector.TYPE_CLASS = TYPE_CLASS; |
|||
Selector.TYPE_ID = TYPE_ID; |
|||
|
|||
/** |
|||
* Escape string |
|||
* @param {string} name |
|||
* @return {string} |
|||
* @private |
|||
*/ |
|||
Selector.escapeName = name => { |
|||
return `${name}`.trim().replace(/([^a-z0-9\w-\:]+)/gi, '-'); |
|||
}; |
|||
|
|||
@ -0,0 +1,33 @@ |
|||
import { Model } from 'common'; |
|||
|
|||
/** |
|||
* @typedef State |
|||
* @property {String} name State name, eg. `hover`, `nth-of-type(2n)` |
|||
* @property {String} label State label, eg. `Hover`, `Even/Odd` |
|||
*/ |
|||
export default class State extends Model { |
|||
defaults() { |
|||
return { |
|||
name: '', |
|||
label: '' |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Get state name |
|||
* @returns {String} |
|||
*/ |
|||
getName() { |
|||
return this.get('name'); |
|||
} |
|||
|
|||
/** |
|||
* Get state label. If label was not provided, the name will be returned. |
|||
* @returns {String} |
|||
*/ |
|||
getLabel() { |
|||
return this.get('label') || this.getName(); |
|||
} |
|||
} |
|||
|
|||
State.prototype.idAttribute = 'name'; |
|||
@ -1,234 +1,276 @@ |
|||
import Backbone from 'backbone'; |
|||
import { isUndefined, isString } from 'underscore'; |
|||
import { Model } from 'common'; |
|||
import { isUndefined, isString, result } from 'underscore'; |
|||
import { capitalize } from 'utils/mixins'; |
|||
|
|||
const Property = Backbone.Model.extend( |
|||
{ |
|||
defaults: { |
|||
name: '', |
|||
property: '', |
|||
type: '', |
|||
defaults: '', |
|||
info: '', |
|||
value: '', |
|||
icon: '', |
|||
functionName: '', |
|||
status: '', |
|||
visible: true, |
|||
fixedValues: ['initial', 'inherit'], |
|||
|
|||
// If true, the property will be forced to be full width
|
|||
full: 0, |
|||
|
|||
// If true to the value will be added '!important'
|
|||
important: 0, |
|||
|
|||
// If true, will be hidden by default and will show up only for targets
|
|||
// which require this property (via `stylable-require`)
|
|||
// Use case:
|
|||
// you can add all SVG CSS properties with toRequire as true
|
|||
// and then require them on SVG Components
|
|||
toRequire: 0, |
|||
|
|||
// Specifies dependency on other properties of the selected object.
|
|||
// Property is shown only when all conditions are matched.
|
|||
//
|
|||
// example: { display: ['flex', 'block'], position: ['absolute'] };
|
|||
// in this case the property is only shown when display is
|
|||
// of value 'flex' or 'block' AND position is 'absolute'
|
|||
requires: null, |
|||
|
|||
// Specifies dependency on properties of the parent of the selected object.
|
|||
// Property is shown only when all conditions are matched.
|
|||
requiresParent: null |
|||
}, |
|||
|
|||
initialize(props = {}, opts = {}) { |
|||
const id = this.get('id') || ''; |
|||
const name = this.get('name') || ''; |
|||
!this.get('property') && |
|||
this.set('property', (name || id).replace(/ /g, '-')); |
|||
const prop = this.get('property'); |
|||
!this.get('id') && this.set('id', prop); |
|||
!name && this.set('name', capitalize(prop).replace(/-/g, ' ')); |
|||
Property.callInit(this, props, opts); |
|||
}, |
|||
|
|||
init() {}, |
|||
|
|||
/** |
|||
* Clear the value |
|||
* @return {this} |
|||
*/ |
|||
clearValue(opts = {}) { |
|||
this.set({ value: undefined, status: '' }, opts); |
|||
return this; |
|||
}, |
|||
|
|||
/** |
|||
* Update value |
|||
* @param {any} value |
|||
* @param {Boolen} [complete=true] Indicates if it's a final state |
|||
* @param {Object} [opts={}] Options |
|||
*/ |
|||
setValue(value, complete = 1, opts = {}) { |
|||
const parsed = this.parseValue(value); |
|||
const avoidStore = !complete; |
|||
!avoidStore && |
|||
this.set({ value: undefined }, { avoidStore, silent: true }); |
|||
this.set(parsed, { avoidStore, ...opts }); |
|||
}, |
|||
|
|||
/** |
|||
* Like `setValue` but, in addition, prevents the update of the input element |
|||
* as the changes should come from the input itself. |
|||
* This method is useful with the definition of custom properties |
|||
* @param {any} value |
|||
* @param {Boolen} [complete=true] Indicates if it's a final state |
|||
* @param {Object} [opts={}] Options |
|||
*/ |
|||
setValueFromInput(value, complete, opts = {}) { |
|||
this.setValue(value, complete, { ...opts, fromInput: 1 }); |
|||
}, |
|||
|
|||
/** |
|||
* Parse a raw value, generally fetched from the target, for this property |
|||
* @param {string} value Raw value string |
|||
* @return {Object} |
|||
* @example |
|||
* // example with an Input type
|
|||
* prop.parseValue('translateX(10deg)'); |
|||
* // -> { value: 10, unit: 'deg', functionName: 'translateX' }
|
|||
* |
|||
*/ |
|||
parseValue(value, opts = {}) { |
|||
const result = { value }; |
|||
const imp = '!important'; |
|||
|
|||
if (isString(value) && value.indexOf(imp) !== -1) { |
|||
result.value = value.replace(imp, '').trim(); |
|||
result.important = 1; |
|||
} |
|||
export default class Property extends Model { |
|||
initialize(props = {}, opts = {}) { |
|||
const id = this.get('id') || ''; |
|||
const name = this.get('name') || ''; |
|||
!this.get('property') && |
|||
this.set('property', (name || id).replace(/ /g, '-')); |
|||
const prop = this.get('property'); |
|||
!this.get('id') && this.set('id', prop); |
|||
!name && this.set('name', capitalize(prop).replace(/-/g, ' ')); |
|||
Property.callInit(this, props, opts); |
|||
} |
|||
|
|||
if (!this.get('functionName') && !opts.complete) { |
|||
return result; |
|||
} |
|||
init() {} |
|||
|
|||
const args = []; |
|||
let valueStr = `${result.value}`; |
|||
let start = valueStr.indexOf('(') + 1; |
|||
let end = valueStr.lastIndexOf(')'); |
|||
const functionName = valueStr.substring(0, start - 1); |
|||
if (functionName) result.functionName = functionName; |
|||
args.push(start); |
|||
|
|||
// Will try even if the last closing parentheses is not found
|
|||
if (end >= 0) { |
|||
args.push(end); |
|||
} |
|||
/** |
|||
* Get property id. |
|||
* @returns {String} |
|||
*/ |
|||
getId() { |
|||
return this.get('id'); |
|||
} |
|||
|
|||
result.value = String.prototype.substring.apply(valueStr, args); |
|||
/** |
|||
* Get property type. |
|||
* @returns {String} |
|||
*/ |
|||
getType() { |
|||
return this.get('type'); |
|||
} |
|||
|
|||
if (opts.numeric) { |
|||
const num = parseFloat(result.value); |
|||
result.unit = result.value.replace(num, ''); |
|||
result.value = num; |
|||
} |
|||
/** |
|||
* Get property name (usually is the CSS property name). |
|||
* @returns {String} |
|||
*/ |
|||
getName() { |
|||
return this.get('property'); |
|||
} |
|||
|
|||
/** |
|||
* Get property label. |
|||
* @returns {String} |
|||
*/ |
|||
getLabel() { |
|||
return this.get('name'); |
|||
} |
|||
|
|||
/** |
|||
* Get property value. |
|||
* @returns {String} |
|||
*/ |
|||
getValue() { |
|||
const val = this.get('value'); |
|||
return isUndefined(val) ? this.getDefaultValue() : val; |
|||
} |
|||
|
|||
/** |
|||
* Clear the value |
|||
* @return {this} |
|||
* @private |
|||
*/ |
|||
clearValue(opts = {}) { |
|||
this.set({ value: undefined, status: '' }, opts); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Update value |
|||
* @param {any} value |
|||
* @param {Boolen} [complete=true] Indicates if it's a final state |
|||
* @param {Object} [opts={}] Options |
|||
* @private |
|||
*/ |
|||
setValue(value, complete = 1, opts = {}) { |
|||
const parsed = this.parseValue(value); |
|||
const avoidStore = !complete; |
|||
!avoidStore && this.set({ value: undefined }, { avoidStore, silent: true }); |
|||
this.set(parsed, { avoidStore, ...opts }); |
|||
} |
|||
|
|||
/** |
|||
* Like `setValue` but, in addition, prevents the update of the input element |
|||
* as the changes should come from the input itself. |
|||
* This method is useful with the definition of custom properties |
|||
* @param {any} value |
|||
* @param {Boolen} [complete=true] Indicates if it's a final state |
|||
* @param {Object} [opts={}] Options |
|||
*/ |
|||
setValueFromInput(value, complete, opts = {}) { |
|||
this.setValue(value, complete, { ...opts, fromInput: 1 }); |
|||
} |
|||
|
|||
/** |
|||
* Parse a raw value, generally fetched from the target, for this property |
|||
* @param {string} value Raw value string |
|||
* @return {Object} |
|||
* @private |
|||
* @example |
|||
* // example with an Input type
|
|||
* prop.parseValue('translateX(10deg)'); |
|||
* // -> { value: 10, unit: 'deg', functionName: 'translateX' }
|
|||
* |
|||
*/ |
|||
parseValue(value, opts = {}) { |
|||
const result = { value }; |
|||
const imp = '!important'; |
|||
|
|||
if (isString(value) && value.indexOf(imp) !== -1) { |
|||
result.value = value.replace(imp, '').trim(); |
|||
result.important = 1; |
|||
} |
|||
|
|||
if (!this.get('functionName') && !opts.complete) { |
|||
return result; |
|||
}, |
|||
|
|||
/** |
|||
* Helper function to safely split a string of values. |
|||
* Useful when style values are inside functions |
|||
* eg: |
|||
* -> input: 'value(1,2,4), 123, value(4,5)' -- default separator: ',' |
|||
* -> output: ['value(1,2,4)', '123', 'value(4,5)'] |
|||
* @param {String} values Values to split |
|||
* @param {String} [separator] Separator |
|||
*/ |
|||
splitValues(values, separator = ',') { |
|||
const res = []; |
|||
const op = '('; |
|||
const cl = ')'; |
|||
let curr = ''; |
|||
let acc = 0; |
|||
|
|||
(values || '').split('').forEach(str => { |
|||
if (str == op) { |
|||
acc++; |
|||
curr = curr + op; |
|||
} else if (str == cl && acc > 0) { |
|||
acc--; |
|||
curr = curr + cl; |
|||
} else if (str === separator && acc == 0) { |
|||
res.push(curr); |
|||
curr = ''; |
|||
} else { |
|||
curr = curr + str; |
|||
} |
|||
}); |
|||
|
|||
curr !== '' && res.push(curr); |
|||
|
|||
return res.map(i => i.trim()); |
|||
}, |
|||
|
|||
/** |
|||
* Get the default value |
|||
* @return {string} |
|||
* @private |
|||
*/ |
|||
getDefaultValue() { |
|||
return this.get('defaults'); |
|||
}, |
|||
|
|||
/** |
|||
* Get a complete value of the property. |
|||
* This probably will replace the getValue when all |
|||
* properties models will be splitted |
|||
* @param {string} val Custom value to replace the one on the model |
|||
* @return {string} |
|||
* @private |
|||
*/ |
|||
getFullValue(val) { |
|||
const fn = this.get('functionName'); |
|||
const def = this.getDefaultValue(); |
|||
let value = isUndefined(val) ? this.get('value') : val; |
|||
const hasValue = !isUndefined(value) && value !== ''; |
|||
|
|||
if (value && def && value === def) { |
|||
return def; |
|||
} |
|||
} |
|||
|
|||
if (fn && hasValue) { |
|||
const fnParameter = |
|||
fn === 'url' ? `'${value.replace(/'/g, '')}'` : value; |
|||
value = `${fn}(${fnParameter})`; |
|||
} |
|||
const args = []; |
|||
let valueStr = `${result.value}`; |
|||
let start = valueStr.indexOf('(') + 1; |
|||
let end = valueStr.lastIndexOf(')'); |
|||
const functionName = valueStr.substring(0, start - 1); |
|||
if (functionName) result.functionName = functionName; |
|||
args.push(start); |
|||
|
|||
// Will try even if the last closing parentheses is not found
|
|||
if (end >= 0) { |
|||
args.push(end); |
|||
} |
|||
|
|||
if (hasValue && this.get('important')) { |
|||
value = `${value} !important`; |
|||
result.value = String.prototype.substring.apply(valueStr, args); |
|||
|
|||
if (opts.numeric) { |
|||
const num = parseFloat(result.value); |
|||
result.unit = result.value.replace(num, ''); |
|||
result.value = num; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* Helper function to safely split a string of values. |
|||
* Useful when style values are inside functions |
|||
* eg: |
|||
* -> input: 'value(1,2,4), 123, value(4,5)' -- default separator: ',' |
|||
* -> output: ['value(1,2,4)', '123', 'value(4,5)'] |
|||
* @param {String} values Values to split |
|||
* @param {String} [separator] Separator |
|||
* @private |
|||
*/ |
|||
splitValues(values, separator = ',') { |
|||
const res = []; |
|||
const op = '('; |
|||
const cl = ')'; |
|||
let curr = ''; |
|||
let acc = 0; |
|||
|
|||
(values || '').split('').forEach(str => { |
|||
if (str == op) { |
|||
acc++; |
|||
curr = curr + op; |
|||
} else if (str == cl && acc > 0) { |
|||
acc--; |
|||
curr = curr + cl; |
|||
} else if (str === separator && acc == 0) { |
|||
res.push(curr); |
|||
curr = ''; |
|||
} else { |
|||
curr = curr + str; |
|||
} |
|||
}); |
|||
|
|||
curr !== '' && res.push(curr); |
|||
|
|||
return value || ''; |
|||
return res.map(i => i.trim()); |
|||
} |
|||
|
|||
/** |
|||
* Get the default value |
|||
* @return {string} |
|||
* @private |
|||
*/ |
|||
getDefaultValue() { |
|||
return this.get('defaults'); |
|||
} |
|||
|
|||
/** |
|||
* Get a complete value of the property. |
|||
* This probably will replace the getValue when all |
|||
* properties models will be splitted |
|||
* @param {string} val Custom value to replace the one on the model |
|||
* @return {string} |
|||
* @private |
|||
*/ |
|||
getFullValue(val) { |
|||
const fn = this.get('functionName'); |
|||
const def = this.getDefaultValue(); |
|||
let value = isUndefined(val) ? this.get('value') : val; |
|||
const hasValue = !isUndefined(value) && value !== ''; |
|||
|
|||
if (value && def && value === def) { |
|||
return def; |
|||
} |
|||
}, |
|||
{ |
|||
callParentInit(property, ctx, props, opts = {}) { |
|||
property.prototype.initialize.apply(ctx, [ |
|||
props, |
|||
{ |
|||
...opts, |
|||
skipInit: 1 |
|||
} |
|||
]); |
|||
}, |
|||
|
|||
callInit(context, props, opts = {}) { |
|||
!opts.skipInit && context.init(props, opts); |
|||
|
|||
if (fn && hasValue) { |
|||
const fnParameter = fn === 'url' ? `'${value.replace(/'/g, '')}'` : value; |
|||
value = `${fn}(${fnParameter})`; |
|||
} |
|||
|
|||
if (hasValue && this.get('important')) { |
|||
value = `${value} !important`; |
|||
} |
|||
|
|||
return value || ''; |
|||
} |
|||
); |
|||
} |
|||
|
|||
Property.callParentInit = function(property, ctx, props, opts = {}) { |
|||
property.prototype.initialize.apply(ctx, [ |
|||
props, |
|||
{ |
|||
...opts, |
|||
skipInit: 1 |
|||
} |
|||
]); |
|||
}; |
|||
|
|||
Property.callInit = function(context, props, opts = {}) { |
|||
!opts.skipInit && context.init(props, opts); |
|||
}; |
|||
|
|||
Property.getDefaults = function() { |
|||
return result(this.prototype, 'defaults'); |
|||
}; |
|||
|
|||
Property.prototype.defaults = { |
|||
name: '', |
|||
property: '', |
|||
type: '', |
|||
defaults: '', |
|||
info: '', |
|||
value: '', |
|||
icon: '', |
|||
functionName: '', |
|||
status: '', |
|||
visible: true, |
|||
fixedValues: ['initial', 'inherit'], |
|||
|
|||
// If true, the property will be forced to be full width
|
|||
full: 0, |
|||
|
|||
// If true to the value will be added '!important'
|
|||
important: 0, |
|||
|
|||
// If true, will be hidden by default and will show up only for targets
|
|||
// which require this property (via `stylable-require`)
|
|||
// Use case:
|
|||
// you can add all SVG CSS properties with toRequire as true
|
|||
// and then require them on SVG Components
|
|||
toRequire: 0, |
|||
|
|||
// Specifies dependency on other properties of the selected object.
|
|||
// Property is shown only when all conditions are matched.
|
|||
//
|
|||
// example: { display: ['flex', 'block'], position: ['absolute'] };
|
|||
// in this case the property is only shown when display is
|
|||
// of value 'flex' or 'block' AND position is 'absolute'
|
|||
requires: null, |
|||
|
|||
export default Property; |
|||
// Specifies dependency on properties of the parent of the selected object.
|
|||
// Property is shown only when all conditions are matched.
|
|||
requiresParent: null |
|||
}; |
|||
|
|||
@ -1,15 +1,15 @@ |
|||
import Backbone from 'backbone'; |
|||
import { Collection } from 'common'; |
|||
import Sector from './Sector'; |
|||
|
|||
export default Backbone.Collection.extend({ |
|||
model: Sector, |
|||
|
|||
export default class Sectors extends Collection { |
|||
initialize() { |
|||
this.listenTo(this, 'reset', this.onReset); |
|||
}, |
|||
} |
|||
|
|||
onReset(models, opts = {}) { |
|||
const prev = opts.previousModels || []; |
|||
prev.forEach(sect => sect.get('properties').reset()); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
Sectors.prototype.model = Sector; |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue