mirror of https://github.com/artf/grapesjs.git
committed by
GitHub
47 changed files with 741 additions and 258 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,9 +1,19 @@ |
|||
import Backbone from 'backbone'; |
|||
import { Model } from 'backbone'; |
|||
|
|||
export default Backbone.Model.extend({ |
|||
defaults: { |
|||
title: '', |
|||
content: '', |
|||
open: false |
|||
export default class Modal extends Model { |
|||
defaults() { |
|||
return { |
|||
title: '', |
|||
content: '', |
|||
open: false |
|||
}; |
|||
} |
|||
}); |
|||
|
|||
open() { |
|||
this.set('open', true); |
|||
} |
|||
|
|||
close() { |
|||
this.set('open', false); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,336 @@ |
|||
// The initial version of this library was borrowed from https://github.com/madrobby/keymaster
|
|||
// and adapted to the GrapesJS's need
|
|||
|
|||
var k, |
|||
_handlers = {}, |
|||
_mods = { |
|||
16: false, |
|||
18: false, |
|||
17: false, |
|||
91: false |
|||
}, |
|||
_scope = 'all', |
|||
// modifier keys
|
|||
_MODIFIERS = { |
|||
'⇧': 16, |
|||
shift: 16, |
|||
'⌥': 18, |
|||
alt: 18, |
|||
option: 18, |
|||
'⌃': 17, |
|||
ctrl: 17, |
|||
control: 17, |
|||
'⌘': 91, |
|||
command: 91 |
|||
}, |
|||
// special keys
|
|||
_MAP = { |
|||
backspace: 8, |
|||
tab: 9, |
|||
clear: 12, |
|||
enter: 13, |
|||
return: 13, |
|||
esc: 27, |
|||
escape: 27, |
|||
space: 32, |
|||
left: 37, |
|||
up: 38, |
|||
right: 39, |
|||
down: 40, |
|||
del: 46, |
|||
delete: 46, |
|||
home: 36, |
|||
end: 35, |
|||
pageup: 33, |
|||
pagedown: 34, |
|||
',': 188, |
|||
'.': 190, |
|||
'/': 191, |
|||
'`': 192, |
|||
'-': 189, |
|||
'=': 187, |
|||
';': 186, |
|||
"'": 222, |
|||
'[': 219, |
|||
']': 221, |
|||
'\\': 220 |
|||
}, |
|||
code = function(x) { |
|||
return _MAP[x] || x.toUpperCase().charCodeAt(0); |
|||
}, |
|||
_downKeys = []; |
|||
|
|||
for (k = 1; k < 20; k++) _MAP['f' + k] = 111 + k; |
|||
|
|||
// IE doesn't support Array#indexOf, so have a simple replacement
|
|||
function index(array, item) { |
|||
var i = array.length; |
|||
while (i--) if (array[i] === item) return i; |
|||
return -1; |
|||
} |
|||
|
|||
// for comparing mods before unassignment
|
|||
function compareArray(a1, a2) { |
|||
if (a1.length != a2.length) return false; |
|||
for (var i = 0; i < a1.length; i++) { |
|||
if (a1[i] !== a2[i]) return false; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
var modifierMap = { |
|||
16: 'shiftKey', |
|||
18: 'altKey', |
|||
17: 'ctrlKey', |
|||
91: 'metaKey' |
|||
}; |
|||
|
|||
function updateModifierKey(event) { |
|||
for (k in _mods) _mods[k] = event[modifierMap[k]]; |
|||
} |
|||
|
|||
// handle keydown event
|
|||
function dispatch(event) { |
|||
var key, handler, k, i, modifiersMatch, scope; |
|||
key = event.keyCode; |
|||
|
|||
if (index(_downKeys, key) == -1) { |
|||
_downKeys.push(key); |
|||
} |
|||
|
|||
// if a modifier key, set the key.<modifierkeyname> property to true and return
|
|||
if (key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko
|
|||
if (key in _mods) { |
|||
_mods[key] = true; |
|||
// 'assignKey' from inside this closure is exported to window.key
|
|||
for (k in _MODIFIERS) if (_MODIFIERS[k] == key) assignKey[k] = true; |
|||
return; |
|||
} |
|||
updateModifierKey(event); |
|||
|
|||
// see if we need to ignore the keypress (filter() can can be overridden)
|
|||
// by default ignore key presses if a select, textarea, or input is focused
|
|||
if (!assignKey.filter.call(this, event)) return; |
|||
|
|||
// abort if no potentially matching shortcuts found
|
|||
if (!(key in _handlers)) return; |
|||
|
|||
scope = getScope(); |
|||
|
|||
// for each potential shortcut
|
|||
for (i = 0; i < _handlers[key].length; i++) { |
|||
handler = _handlers[key][i]; |
|||
|
|||
// see if it's in the current scope
|
|||
if (handler.scope == scope || handler.scope == 'all') { |
|||
// check if modifiers match if any
|
|||
modifiersMatch = handler.mods.length > 0; |
|||
for (k in _mods) |
|||
if ( |
|||
(!_mods[k] && index(handler.mods, +k) > -1) || |
|||
(_mods[k] && index(handler.mods, +k) == -1) |
|||
) |
|||
modifiersMatch = false; |
|||
// call the handler and stop the event if neccessary
|
|||
if ( |
|||
(handler.mods.length == 0 && |
|||
!_mods[16] && |
|||
!_mods[18] && |
|||
!_mods[17] && |
|||
!_mods[91]) || |
|||
modifiersMatch |
|||
) { |
|||
if (handler.method(event, handler) === false) { |
|||
if (event.preventDefault) event.preventDefault(); |
|||
else event.returnValue = false; |
|||
if (event.stopPropagation) event.stopPropagation(); |
|||
if (event.cancelBubble) event.cancelBubble = true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// unset modifier keys on keyup
|
|||
function clearModifier(event) { |
|||
var key = event.keyCode, |
|||
k, |
|||
i = index(_downKeys, key); |
|||
|
|||
// remove key from _downKeys
|
|||
if (i >= 0) { |
|||
_downKeys.splice(i, 1); |
|||
} |
|||
|
|||
if (key == 93 || key == 224) key = 91; |
|||
if (key in _mods) { |
|||
_mods[key] = false; |
|||
for (k in _MODIFIERS) if (_MODIFIERS[k] == key) assignKey[k] = false; |
|||
} |
|||
} |
|||
|
|||
function resetModifiers() { |
|||
for (k in _mods) _mods[k] = false; |
|||
for (k in _MODIFIERS) assignKey[k] = false; |
|||
} |
|||
|
|||
// parse and assign shortcut
|
|||
function assignKey(key, scope, method) { |
|||
var keys, mods; |
|||
keys = getKeys(key); |
|||
if (method === undefined) { |
|||
method = scope; |
|||
scope = 'all'; |
|||
} |
|||
|
|||
// for each shortcut
|
|||
for (var i = 0; i < keys.length; i++) { |
|||
// set modifier keys if any
|
|||
mods = []; |
|||
key = keys[i].split('+'); |
|||
if (key.length > 1) { |
|||
mods = getMods(key); |
|||
key = [key[key.length - 1]]; |
|||
} |
|||
// convert to keycode and...
|
|||
key = key[0]; |
|||
key = code(key); |
|||
// ...store handler
|
|||
if (!(key in _handlers)) _handlers[key] = []; |
|||
_handlers[key].push({ |
|||
shortcut: keys[i], |
|||
scope: scope, |
|||
method: method, |
|||
key: keys[i], |
|||
mods: mods |
|||
}); |
|||
} |
|||
} |
|||
|
|||
// unbind all handlers for given key in current scope
|
|||
function unbindKey(key, scope) { |
|||
var multipleKeys, |
|||
keys, |
|||
mods = [], |
|||
i, |
|||
j, |
|||
obj; |
|||
|
|||
multipleKeys = getKeys(key); |
|||
|
|||
for (j = 0; j < multipleKeys.length; j++) { |
|||
keys = multipleKeys[j].split('+'); |
|||
|
|||
if (keys.length > 1) { |
|||
mods = getMods(keys); |
|||
} |
|||
|
|||
key = keys[keys.length - 1]; |
|||
key = code(key); |
|||
|
|||
if (scope === undefined) { |
|||
scope = getScope(); |
|||
} |
|||
if (!_handlers[key]) { |
|||
return; |
|||
} |
|||
for (i = 0; i < _handlers[key].length; i++) { |
|||
obj = _handlers[key][i]; |
|||
// only clear handlers if correct scope and mods match
|
|||
if (obj.scope === scope && compareArray(obj.mods, mods)) { |
|||
_handlers[key][i] = {}; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Returns true if the key with code 'keyCode' is currently down
|
|||
// Converts strings into key codes.
|
|||
function isPressed(keyCode) { |
|||
if (typeof keyCode == 'string') { |
|||
keyCode = code(keyCode); |
|||
} |
|||
return index(_downKeys, keyCode) != -1; |
|||
} |
|||
|
|||
function getPressedKeyCodes() { |
|||
return _downKeys.slice(0); |
|||
} |
|||
|
|||
function filter(event) { |
|||
var tagName = (event.target || event.srcElement).tagName; |
|||
// ignore keypressed in any elements that support keyboard data input
|
|||
return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA'); |
|||
} |
|||
|
|||
// initialize key.<modifier> to false
|
|||
for (k in _MODIFIERS) assignKey[k] = false; |
|||
|
|||
// set current scope (default 'all')
|
|||
function setScope(scope) { |
|||
_scope = scope || 'all'; |
|||
} |
|||
|
|||
function getScope() { |
|||
return _scope || 'all'; |
|||
} |
|||
|
|||
// delete all handlers for a given scope
|
|||
function deleteScope(scope) { |
|||
var key, handlers, i; |
|||
|
|||
for (key in _handlers) { |
|||
handlers = _handlers[key]; |
|||
for (i = 0; i < handlers.length; ) { |
|||
if (handlers[i].scope === scope) handlers.splice(i, 1); |
|||
else i++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// abstract key logic for assign and unassign
|
|||
function getKeys(key) { |
|||
var keys; |
|||
key = key.replace(/\s/g, ''); |
|||
keys = key.split(','); |
|||
if (keys[keys.length - 1] == '') { |
|||
keys[keys.length - 2] += ','; |
|||
} |
|||
return keys; |
|||
} |
|||
|
|||
// abstract mods logic for assign and unassign
|
|||
function getMods(key) { |
|||
var mods = key.slice(0, key.length - 1); |
|||
for (var mi = 0; mi < mods.length; mi++) mods[mi] = _MODIFIERS[mods[mi]]; |
|||
return mods; |
|||
} |
|||
|
|||
// cross-browser events
|
|||
function addEvent(object, event, method) { |
|||
if (object.addEventListener) object.addEventListener(event, method, false); |
|||
else if (object.attachEvent) |
|||
object.attachEvent('on' + event, function() { |
|||
method(window.event); |
|||
}); |
|||
} |
|||
|
|||
// set window.key and window.key.set/get/deleteScope, and the default filter
|
|||
assignKey.setScope = setScope; |
|||
assignKey.getScope = getScope; |
|||
assignKey.deleteScope = deleteScope; |
|||
assignKey.filter = filter; |
|||
assignKey.isPressed = isPressed; |
|||
assignKey.getPressedKeyCodes = getPressedKeyCodes; |
|||
assignKey.unbind = unbindKey; |
|||
assignKey.init = win => { |
|||
// set the handlers globally on document
|
|||
// Passing _scope to a callback to ensure it remains the same by execution. Fixes #48
|
|||
addEvent(win.document, 'keydown', function(event) { |
|||
dispatch(event); |
|||
}); |
|||
addEvent(win.document, 'keyup', clearModifier); |
|||
addEvent(win, 'focus', resetModifiers); |
|||
}; |
|||
|
|||
export default assignKey; |
|||
@ -0,0 +1,62 @@ |
|||
/** |
|||
* @jest-environment node |
|||
*/ |
|||
|
|||
describe('GrapesJS Headless', () => { |
|||
test('Can init and destroy an editor', () => { |
|||
const editor = grapesjs.init({ headless: true }); |
|||
expect(editor).toBeTruthy(); |
|||
editor.destroy(); |
|||
}); |
|||
|
|||
describe('Headless operations', () => { |
|||
let editor; |
|||
const cmpObj = { |
|||
attributes: { class: 'cls', test: 'value' }, |
|||
components: { type: 'textnode', content: 'Test' } |
|||
}; |
|||
const cmpStr = '<div class="cls" test="value">Test</div>'; |
|||
const styleObj = { |
|||
selectors: [{ name: 'cls' }], |
|||
style: { color: 'red' } |
|||
}; |
|||
const styleStr = '.cls{color:red;}'; |
|||
|
|||
beforeEach(() => { |
|||
editor = grapesjs.init({ headless: true, protectedCss: '' }); |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
editor.destroy(); |
|||
}); |
|||
|
|||
test('Add components', () => { |
|||
const res = editor.addComponents(cmpObj); |
|||
expect(res.length).toBe(1); |
|||
const comp = res[0]; |
|||
expect(comp.toHTML()).toBe(cmpStr); |
|||
expect(editor.Selectors.getAll().length).toBe(1); // 1 selector is created
|
|||
expect(editor.Css.getAll().length).toBe(0); // No CSS
|
|||
expect(editor.getHtml()).toBe(cmpStr); |
|||
expect(editor.getCss()).toBe(''); // same as default
|
|||
}); |
|||
|
|||
test('Add styles', () => { |
|||
const res = editor.addStyle(styleObj); |
|||
expect(res.length).toBe(1); |
|||
const rule = res[0]; |
|||
expect(rule.toCSS()).toBe(styleStr); |
|||
expect(editor.Selectors.getAll().length).toBe(1); // 1 selector is created
|
|||
}); |
|||
|
|||
test('Load data', () => { |
|||
editor.loadData({ |
|||
components: [cmpObj], |
|||
styles: [styleObj] |
|||
}); |
|||
expect(editor.Selectors.getAll().length).toBe(1); |
|||
expect(editor.getHtml()).toBe(cmpStr); |
|||
expect(editor.getCss()).toBe(styleStr); |
|||
}); |
|||
}); |
|||
}); |
|||
Loading…
Reference in new issue