mirror of https://github.com/artf/grapesjs.git
committed by
GitHub
80 changed files with 6351 additions and 3175 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
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,30 @@ |
|||
import { bindAll } from 'underscore'; |
|||
import Backbone from 'backbone'; |
|||
import model from './Frame'; |
|||
|
|||
export default Backbone.Collection.extend({ |
|||
model, |
|||
|
|||
initialize() { |
|||
bindAll(this, 'itemLoaded'); |
|||
}, |
|||
|
|||
itemLoaded() { |
|||
this.loadedItems++; |
|||
|
|||
if (this.loadedItems >= this.itemsToLoad) { |
|||
this.trigger('loaded:all'); |
|||
this.listenToLoadItems(0); |
|||
} |
|||
}, |
|||
|
|||
listenToLoad() { |
|||
this.loadedItems = 0; |
|||
this.itemsToLoad = this.length; |
|||
this.listenToLoadItems(1); |
|||
}, |
|||
|
|||
listenToLoadItems(on) { |
|||
this.forEach(item => item[on ? 'on' : 'off']('loaded', this.itemLoaded)); |
|||
} |
|||
}); |
|||
@ -0,0 +1,220 @@ |
|||
import Backbone from 'backbone'; |
|||
import FrameView from './FrameView'; |
|||
import { bindAll, isNumber, isNull, debounce } from 'underscore'; |
|||
import { createEl, motionsEv } from 'utils/dom'; |
|||
import Dragger from 'utils/Dragger'; |
|||
|
|||
export default Backbone.View.extend({ |
|||
events: { |
|||
'click [data-action-remove]': 'remove', |
|||
'mousedown [data-action-move]': 'startDrag' |
|||
}, |
|||
|
|||
initialize(opts = {}, conf = {}) { |
|||
bindAll( |
|||
this, |
|||
'onScroll', |
|||
'frameLoaded', |
|||
'updateOffset', |
|||
'remove', |
|||
'startDrag' |
|||
); |
|||
const { model } = this; |
|||
const config = { |
|||
...(opts.config || conf), |
|||
frameWrapView: this |
|||
}; |
|||
const { canvasView, em } = config; |
|||
this.cv = canvasView; |
|||
this.config = config; |
|||
this.em = em; |
|||
this.canvas = em && em.get('Canvas'); |
|||
this.ppfx = config.pStylePrefix || ''; |
|||
this.frame = new FrameView({ model, config }); |
|||
this.classAnim = `${this.ppfx}frame-wrapper--anim`; |
|||
this.listenTo(model, 'loaded', this.frameLoaded); |
|||
this.listenTo(model, 'change:x change:y', this.updatePos); |
|||
this.listenTo(model, 'change:width change:height', this.updateSize); |
|||
this.updatePos(); |
|||
this.setupDragger(); |
|||
}, |
|||
|
|||
setupDragger() { |
|||
const { canvas, model } = this; |
|||
let dragX, dragY, zoom; |
|||
const toggleEffects = on => { |
|||
canvas.toggleFramesEvents(on); |
|||
}; |
|||
|
|||
this.dragger = new Dragger({ |
|||
onStart: () => { |
|||
const { x, y } = model.attributes; |
|||
zoom = this.em.getZoomMultiplier(); |
|||
dragX = x; |
|||
dragY = y; |
|||
toggleEffects(); |
|||
}, |
|||
onEnd: () => toggleEffects(1), |
|||
setPosition: posOpts => { |
|||
model.set({ |
|||
x: dragX + posOpts.x * zoom, |
|||
y: dragY + posOpts.y * zoom |
|||
}); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
startDrag(ev) { |
|||
ev && this.dragger.start(ev); |
|||
}, |
|||
|
|||
remove() { |
|||
Backbone.View.prototype.remove.apply(this, arguments); |
|||
this.frame.remove(); |
|||
return this; |
|||
}, |
|||
|
|||
updateOffset: debounce(function() { |
|||
const { em, $el, frame } = this; |
|||
em.runDefault({ preserveSelected: 1 }); |
|||
$el.removeClass(this.classAnim); |
|||
frame.model._emitUpdated(); |
|||
}), |
|||
|
|||
updatePos(md) { |
|||
const { model, el } = this; |
|||
const { x, y } = model.attributes; |
|||
const { style } = el; |
|||
this.frame.rect = 0; |
|||
style.left = isNaN(x) ? x : `${x}px`; |
|||
style.top = isNaN(y) ? y : `${y}px`; |
|||
md && this.updateOffset(); |
|||
}, |
|||
|
|||
updateSize: debounce(function() { |
|||
this.updateDim(); |
|||
}), |
|||
|
|||
/** |
|||
* Update dimensions of the frame |
|||
* @private |
|||
*/ |
|||
updateDim() { |
|||
const { em, el, $el, model, classAnim } = this; |
|||
const { width, height } = model.attributes; |
|||
const { style } = el; |
|||
const currW = style.width || ''; |
|||
const currH = style.height || ''; |
|||
const newW = width || ''; |
|||
const newH = height || ''; |
|||
const noChanges = currW == newW && currH == newH; |
|||
const un = 'px'; |
|||
this.frame.rect = 0; |
|||
$el.addClass(classAnim); |
|||
style.width = isNumber(newW) ? `${newW}${un}` : newW; |
|||
style.height = isNumber(newH) ? `${newH}${un}` : newH; |
|||
|
|||
// Set width and height from DOM (should be done only once)
|
|||
if (isNull(width) || isNull(height)) { |
|||
const newDims = { |
|||
...(!width ? { width: el.offsetWidth } : {}), |
|||
...(!height ? { height: el.offsetHeight } : {}) |
|||
}; |
|||
model.set(newDims, { silent: 1 }); |
|||
} |
|||
|
|||
// Prevent fixed highlighting box which appears when on
|
|||
// component hover during the animation
|
|||
em.stopDefault({ preserveSelected: 1 }); |
|||
noChanges ? this.updateOffset() : $el.one(motionsEv, this.updateOffset); |
|||
}, |
|||
|
|||
onScroll() { |
|||
const { frame, em } = this; |
|||
em.trigger('frame:scroll', { |
|||
frame, |
|||
body: frame.getBody(), |
|||
target: frame.getWindow() |
|||
}); |
|||
}, |
|||
|
|||
frameLoaded() { |
|||
const { frame } = this; |
|||
frame.getWindow().onscroll = this.onScroll; |
|||
this.updateDim(); |
|||
}, |
|||
|
|||
render() { |
|||
const { frame, $el, ppfx, cv, model, el } = this; |
|||
const { onRender } = model.attributes; |
|||
frame.render(); |
|||
$el |
|||
.empty() |
|||
.attr({ class: `${ppfx}frame-wrapper` }) |
|||
.append( |
|||
` |
|||
<div class="${ppfx}frame-wrapper__top gjs-two-color" data-frame-top> |
|||
<div class="${ppfx}frame-wrapper__name" data-action-move> |
|||
${model.get('name') || ''} |
|||
</div> |
|||
<div class="${ppfx}frame-wrapper__top-r"> |
|||
<div class="${ppfx}frame-wrapper__icon" data-action-remove style="display: none"> |
|||
<svg viewBox="0 0 24 24"><path d="M19 4h-3.5l-1-1h-5l-1 1H5v2h14M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12z"></path></svg> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="${ppfx}frame-wrapper__right" data-frame-right></div> |
|||
<div class="${ppfx}frame-wrapper__left" data-frame-left></div> |
|||
<div class="${ppfx}frame-wrapper__bottom" data-frame-bottom></div> |
|||
` |
|||
) |
|||
.append(frame.el); |
|||
const elTools = createEl( |
|||
'div', |
|||
{ |
|||
class: `${ppfx}tools`, |
|||
style: 'pointer-events:none; opacity: 0' |
|||
}, |
|||
` |
|||
<div class="${ppfx}highlighter" data-hl></div> |
|||
<div class="${ppfx}badge" data-badge></div> |
|||
<div class="${ppfx}placeholder"> |
|||
<div class="${ppfx}placeholder-int"></div> |
|||
</div> |
|||
<div class="${ppfx}ghost"></div> |
|||
<div class="${ppfx}toolbar" style="pointer-events:all"></div> |
|||
<div class="${ppfx}resizer"></div> |
|||
<div class="${ppfx}offset-v" data-offset> |
|||
<div class="gjs-marginName" data-offset-m> |
|||
<div class="gjs-margin-v-el gjs-margin-v-top" data-offset-m-t></div> |
|||
<div class="gjs-margin-v-el gjs-margin-v-bottom" data-offset-m-b></div> |
|||
<div class="gjs-margin-v-el gjs-margin-v-left" data-offset-m-l></div> |
|||
<div class="gjs-margin-v-el gjs-margin-v-right" data-offset-m-r></div> |
|||
</div> |
|||
<div class="gjs-paddingName" data-offset-m> |
|||
<div class="gjs-padding-v-el gjs-padding-v-top" data-offset-p-t></div> |
|||
<div class="gjs-padding-v-el gjs-padding-v-bottom" data-offset-p-b></div> |
|||
<div class="gjs-padding-v-el gjs-padding-v-left" data-offset-p-l></div> |
|||
<div class="gjs-padding-v-el gjs-padding-v-right" data-offset-p-r></div> |
|||
</div> |
|||
</div> |
|||
<div class="${ppfx}offset-fixed-v"></div> |
|||
` |
|||
); |
|||
this.elTools = elTools; |
|||
cv.toolsWrapper.appendChild(elTools); // TODO remove on frame remove
|
|||
onRender && |
|||
onRender({ |
|||
el, |
|||
elTop: el.querySelector('[data-frame-top]'), |
|||
elRight: el.querySelector('[data-frame-right]'), |
|||
elBottom: el.querySelector('[data-frame-bottom]'), |
|||
elLeft: el.querySelector('[data-frame-left]'), |
|||
frame: model, |
|||
frameWrapperView: this, |
|||
remove: this.remove, |
|||
startDrag: this.startDrag |
|||
}); |
|||
return this; |
|||
} |
|||
}); |
|||
@ -0,0 +1,17 @@ |
|||
import DomainViews from 'domain_abstract/view/DomainViews'; |
|||
import FrameWrapView from './FrameWrapView'; |
|||
|
|||
export default DomainViews.extend({ |
|||
itemView: FrameWrapView, |
|||
autoAdd: 1, |
|||
|
|||
init() { |
|||
this.listenTo(this.collection, 'reset', this.render); |
|||
}, |
|||
|
|||
onRender() { |
|||
const { config, $el } = this; |
|||
const { em } = config; |
|||
em && $el.attr({ class: `${em.getConfig('stylePrefix')}frames` }); |
|||
} |
|||
}); |
|||
@ -1,9 +1,16 @@ |
|||
export default { |
|||
run(ed) { |
|||
ed.Canvas.getBody().className = this.ppfx + 'dashed'; |
|||
this.toggleVis(ed); |
|||
}, |
|||
|
|||
stop(ed) { |
|||
ed.Canvas.getBody().className = ''; |
|||
this.toggleVis(ed, 0); |
|||
}, |
|||
|
|||
toggleVis(ed, active = 1) { |
|||
const method = active ? 'add' : 'remove'; |
|||
ed.Canvas.getFrames().forEach(frame => { |
|||
frame.view.getBody().classList[method](`${this.ppfx}dashed`); |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
@ -0,0 +1,25 @@ |
|||
import Component from './ComponentSvg'; |
|||
|
|||
/** |
|||
* Component for inner SVG elements |
|||
*/ |
|||
export default Component.extend( |
|||
{ |
|||
defaults: { |
|||
...Component.prototype.defaults, |
|||
selectable: false, |
|||
hoverable: false, |
|||
layerable: false |
|||
} |
|||
}, |
|||
{ |
|||
isComponent(el) { |
|||
if (Component.isComponent(el) && el.tagName.toLowerCase() !== 'svg') { |
|||
return { |
|||
tagName: el.tagName, |
|||
type: 'svg-in' |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
); |
|||
@ -0,0 +1,200 @@ |
|||
const traitInputAttr = { placeholder: 'ex. Votre texte ici' }; |
|||
|
|||
export default { |
|||
assetManager: { |
|||
addButton: 'Bild hinzufügen', |
|||
inputPlh: 'http://chemin/vers/image.jpg', |
|||
modalTitle: 'Bild auswählen', |
|||
uploadTitle: 'Ziehe eine Datei hierher oder klicke auf upload' |
|||
}, |
|||
// 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': 'Kategorie Label',
|
|||
} |
|||
}, |
|||
domComponents: { |
|||
names: { |
|||
'': 'Box', |
|||
wrapper: 'Body', |
|||
text: 'Text', |
|||
comment: 'Kommentar', |
|||
image: 'Bild', |
|||
video: 'Video', |
|||
label: 'Label', |
|||
link: 'Link', |
|||
map: 'Karte', |
|||
tfoot: 'Tabellen Fußzeile', |
|||
tbody: 'Tabellen Inhalt', |
|||
thead: 'Tabellen Kopf', |
|||
table: 'Tabelle', |
|||
row: 'Zeile', |
|||
cell: 'Zelle' |
|||
} |
|||
}, |
|||
deviceManager: { |
|||
device: 'Gerät', |
|||
devices: { |
|||
desktop: 'Desktop', |
|||
tablet: 'Tablet', |
|||
mobileLandscape: 'Mobile Landscape', |
|||
mobilePortrait: 'Mobile Portrait' |
|||
} |
|||
}, |
|||
panels: { |
|||
buttons: { |
|||
titles: { |
|||
preview: 'Vorschau', |
|||
fullscreen: 'Vollbild', |
|||
'sw-visibility': 'Komponente anzeigen', |
|||
'export-template': 'Code anzeigen', |
|||
'open-sm': 'Stil Manager öffnen', |
|||
'open-tm': 'Parameter', |
|||
'open-layers': 'Layer Manager öffnen', |
|||
'open-blocks': 'Block öffnen' |
|||
} |
|||
} |
|||
}, |
|||
selectorManager: { |
|||
label: 'Klassen', |
|||
selected: 'Ausgewählt', |
|||
emptyState: '- Status -', |
|||
states: { |
|||
hover: 'Hover', |
|||
active: 'Klick', |
|||
'nth-of-type(2n)': 'Gerade/Ungerade' |
|||
} |
|||
}, |
|||
styleManager: { |
|||
empty: |
|||
"Wählen sie ein Element aus bevor sie den Stil Manager nutzen", |
|||
layer: 'Evene', |
|||
fileButton: 'Bilder', |
|||
sectors: { |
|||
general: 'Allgemein', |
|||
layout: 'Layout', |
|||
typography: 'Typographie', |
|||
decorations: 'Dekorationen', |
|||
extra: 'Extra', |
|||
flex: 'Flex', |
|||
dimension: 'Dimension' |
|||
}, |
|||
// The core library generates the name by their `property` name
|
|||
properties: { |
|||
float: 'Ausrichtung', |
|||
display: 'Anzeige', |
|||
position: 'Position', |
|||
top: 'Oben', |
|||
right: 'Rechts', |
|||
left: 'Links', |
|||
bottom: 'Unten', |
|||
width: 'Breite', |
|||
height: 'Höhe', |
|||
'max-width': 'Breite max.', |
|||
'max-height': 'Höhe max.', |
|||
margin: 'Äußerer Abstand', |
|||
'margin-top': 'Äußerer Abstand oben', |
|||
'margin-right': 'Äußerer Abstand rechts', |
|||
'margin-left': 'Äußerer Abstand links', |
|||
'margin-bottom': 'Äußerer Abstand unten', |
|||
padding: 'Innerer Abstand', |
|||
'padding-top': 'Innerer Abstand oben', |
|||
'padding-left': 'Innerer Abstand links', |
|||
'padding-right': 'Innerer Abstand rechts', |
|||
'padding-bottom': 'Innerer Abstand unten', |
|||
'font-family': 'Schrift Familie', |
|||
'font-size': 'Schriftgröße', |
|||
'font-weight': 'Schriftstärke', |
|||
'letter-spacing': 'Buchstaben Abstand', |
|||
color: 'Schriftfarbe', |
|||
'line-height': 'Linien Höhe', |
|||
'text-align': 'Text Ausrichtung', |
|||
'text-shadow': 'Text Schatten', |
|||
'text-shadow-h': 'Text Schatten: horizontal', |
|||
'text-shadow-v': 'Text Schatten: vertikal', |
|||
'text-shadow-blur': 'Text Schatten: unschärfe', |
|||
'text-shadow-color': 'Text Schatten: Farbe', |
|||
'border-top-left': 'Rand oben links', |
|||
'border-top-right': 'Rand oben rechts', |
|||
'border-bottom-left': 'Rand unten links', |
|||
'border-bottom-right': 'Rand unten rechts', |
|||
'border-radius-top-left': 'Rand Radius oben links', |
|||
'border-radius-top-right': 'Rand Radius oben rechts', |
|||
'border-radius-bottom-left': 'Rand Radius unten links', |
|||
'border-radius-bottom-right': 'Rand Radius unten rechts', |
|||
'border-radius': 'Rand Radius', |
|||
border: 'Rand', |
|||
'border-width': 'Rand Breite', |
|||
'border-style': 'Rand Stil', |
|||
'border-color': 'Rand Farbe', |
|||
'box-shadow': 'Box Schatten', |
|||
'box-shadow-h': 'Box Schatten: horizontal', |
|||
'box-shadow-v': 'Box Schatten: vertikal', |
|||
'box-shadow-blur': 'Box Schatten: Unschärfe', |
|||
'box-shadow-spread': "Box Schatten: Streuung", |
|||
'box-shadow-color': "Box Schatten: Farbe", |
|||
'box-shadow-type': "Box Schatten: Typ", |
|||
background: 'Hintergrund', |
|||
'background-image': 'Hintergrund Bild', |
|||
'background-repeat': 'Hintergrund wiederholen', |
|||
'background-position': 'Hintergrund Position', |
|||
'background-attachment': 'Hintergrund Anhang', |
|||
'background-size': 'Hintergrund Größe', |
|||
'background-color': 'Hintergrund Farbe', |
|||
transition: 'Übergang', |
|||
'transition-property': 'Übergang: Typ', |
|||
'transition-duration': 'Übergang: Dauer', |
|||
'transition-timing-function': 'Übergang: Zeitfunktion', |
|||
perspective: 'Perspektive', |
|||
transform: 'Transformation', |
|||
'transform-rotate-x': 'Transformation: Rotation x', |
|||
'transform-rotate-y': 'Transformation: Rotation y', |
|||
'transform-rotate-z': 'Transformation: Rotation z', |
|||
'transform-scale-x': 'Transformation: Skala x', |
|||
'transform-scale-y': 'Transformation: Skala y', |
|||
'transform-scale-z': 'Transformation: Skala z', |
|||
'flex-direction': 'Ausrichtung Flex', |
|||
'flex-wrap': 'Flex wrap', |
|||
'justify-content': 'Content abgleichen', |
|||
'align-items': 'Element Ausrichtung', |
|||
'align-content': 'Contente Ausrichtung', |
|||
order: 'Reihenfolge', |
|||
'flex-basis': 'Flex Basis', |
|||
'flex-grow': 'Flex Wachsen', |
|||
'flex-shrink': 'Flex Schrumpfen', |
|||
'align-self': 'Ausrichtung selbst' |
|||
} |
|||
}, |
|||
traitManager: { |
|||
empty: |
|||
"Wählen sie ein Element aus bevor sie den Komponenten Manager nutzen", |
|||
label: 'Komponenten Einstellungen', |
|||
traits: { |
|||
// The core library generates the name by their `name` property
|
|||
labels: { |
|||
id: 'ID', |
|||
alt: 'Text Alternative', |
|||
title: 'Titel', |
|||
href: 'Link' |
|||
}, |
|||
// In a simple trait, like text input, these are used on input attributes
|
|||
attributes: { |
|||
id: traitInputAttr, |
|||
alt: traitInputAttr, |
|||
title: traitInputAttr, |
|||
href: { placeholder: 'z.B. https://google.com' } |
|||
}, |
|||
// In a trait like select, these are used to translate option names
|
|||
options: { |
|||
target: { |
|||
false: 'Dieses Fenster', |
|||
_blank: 'Neues Fenster' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
@ -0,0 +1,34 @@ |
|||
import Component from 'dom_components/model/Component'; |
|||
import ComponentImage from 'dom_components/model/ComponentImage'; |
|||
|
|||
describe('ComponentImage', () => { |
|||
let componentImage; |
|||
|
|||
beforeEach(() => { |
|||
componentImage = new ComponentImage(); |
|||
}); |
|||
|
|||
describe('.getAttrToHTML', () => { |
|||
let getSrcResultSpy; |
|||
const fakeAttributes = {}; |
|||
|
|||
beforeEach(() => { |
|||
spyOn(Component.prototype, 'getAttrToHTML').and.returnValue( |
|||
fakeAttributes |
|||
); |
|||
getSrcResultSpy = spyOn(componentImage, 'getSrcResult'); |
|||
}); |
|||
|
|||
test('it should fill the `src` property with the result of `getSrcResult` if defined', () => { |
|||
let attributes = componentImage.getAttrToHTML(); |
|||
expect(getSrcResultSpy).toHaveBeenCalled(); |
|||
expect(attributes).toEqual(fakeAttributes); |
|||
|
|||
let fakeSrcResult = 'fakeSrcResult'; |
|||
getSrcResultSpy.and.returnValue(fakeSrcResult); |
|||
attributes = componentImage.getAttrToHTML(); |
|||
expect(getSrcResultSpy).toHaveBeenCalledTimes(2); |
|||
expect(attributes).toEqual({ src: fakeSrcResult }); |
|||
}); |
|||
}); |
|||
}); |
|||
Loading…
Reference in new issue