diff --git a/app/assets/stylesheets/zammad.scss b/app/assets/stylesheets/zammad.scss
index 0206c9424..394ba381e 100644
--- a/app/assets/stylesheets/zammad.scss
+++ b/app/assets/stylesheets/zammad.scss
@@ -10684,10 +10684,6 @@ output {
padding: 4px;
}
- .color-shadow {
- display: none;
- }
-
.color-field {
width: 31px;
height: 100%;
diff --git a/app/models/knowledge_base.rb b/app/models/knowledge_base.rb
index 87af42a03..723ab5476 100644
--- a/app/models/knowledge_base.rb
+++ b/app/models/knowledge_base.rb
@@ -26,8 +26,9 @@ class KnowledgeBase < ApplicationModel
validates :category_layout, inclusion: { in: KnowledgeBase::LAYOUTS }
validates :homepage_layout, inclusion: { in: KnowledgeBase::LAYOUTS }
- validates :color_highlight, presence: true
- validates :color_header, presence: true
+ validates :color_highlight, presence: true, color: true
+ validates :color_header, presence: true, color: true
+
validates :iconset, inclusion: { in: KnowledgeBase::ICONSETS }
scope :active, -> { where(active: true) }
diff --git a/app/views/tests/color_object.html.erb b/app/views/tests/color_object.html.erb
new file mode 100644
index 000000000..410d26775
--- /dev/null
+++ b/app/views/tests/color_object.html.erb
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/views/tests/form_color.html.erb b/app/views/tests/form_color.html.erb
new file mode 100644
index 000000000..b10567c10
--- /dev/null
+++ b/app/views/tests/form_color.html.erb
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/routes/test.rb b/config/routes/test.rb
index dce9d16de..6f3052c44 100644
--- a/config/routes/test.rb
+++ b/config/routes/test.rb
@@ -15,6 +15,7 @@ Zammad::Application.routes.draw do
match '/tests_form_trim', to: 'tests#form_trim', via: :get
match '/tests_form_extended', to: 'tests#form_extended', via: :get
match '/tests_form_timer', to: 'tests#form_timer', via: :get
+ match '/tests_form_color', to: 'tests#form_color', via: :get
match '/tests_form_validation', to: 'tests#form_validation', via: :get
match '/tests_form_column_select', to: 'tests#form_column_select', via: :get
match '/tests_form_searchable_select', to: 'tests#form_searchable_select', via: :get
@@ -25,6 +26,7 @@ Zammad::Application.routes.draw do
match '/tests_ticket_selector', to: 'tests#ticket_selector', via: :get
match '/tests_taskbar', to: 'tests#taskbar', via: :get
match '/tests_text_module', to: 'tests#text_module', via: :get
+ match '/tests_color_object', to: 'tests#color_object', via: :get
match '/tests/wait/:sec', to: 'tests#wait', via: :get
match '/tests/raised_exception', to: 'tests#error_raised_exception', via: :get
diff --git a/db/migrate/20190531180304_initialize_knowledge_base.rb b/db/migrate/20190531180304_initialize_knowledge_base.rb
index 3fff395e6..a456737c9 100644
--- a/db/migrate/20190531180304_initialize_knowledge_base.rb
+++ b/db/migrate/20190531180304_initialize_knowledge_base.rb
@@ -6,8 +6,8 @@ class InitializeKnowledgeBase < ActiveRecord::Migration[5.0]
create_table :knowledge_bases do |t|
t.string :iconset, limit: 30, null: false
- t.string :color_highlight, limit: 9, null: false
- t.string :color_header, limit: 9, null: false
+ t.string :color_highlight, limit: 25, null: false
+ t.string :color_header, limit: 25, null: false
t.string :homepage_layout, null: false
t.string :category_layout, null: false
diff --git a/db/migrate/20190717210244_issue_2641_kb_color_change_limit.rb b/db/migrate/20190717210244_issue_2641_kb_color_change_limit.rb
new file mode 100644
index 000000000..76fbf9c3f
--- /dev/null
+++ b/db/migrate/20190717210244_issue_2641_kb_color_change_limit.rb
@@ -0,0 +1,8 @@
+class Issue2641KbColorChangeLimit < ActiveRecord::Migration[5.2]
+ def change
+ return if !Setting.find_by(name: 'system_init_done')
+
+ change_column :knowledge_bases, :color_highlight, :string, limit: 25
+ change_column :knowledge_bases, :color_header, :string, limit: 25
+ end
+end
diff --git a/lib/color_validator.rb b/lib/color_validator.rb
new file mode 100644
index 000000000..000c29f3c
--- /dev/null
+++ b/lib/color_validator.rb
@@ -0,0 +1,19 @@
+class ColorValidator < ActiveModel::EachValidator
+ REGEXP = {
+ RGB: /^rgb\((((((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?)){2}|((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5])\s)){2})((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]))|((((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){2}|((([1-9]?\d(\.\d+)?)|100|(\.\d+))%\s){2})(([1-9]?\d(\.\d+)?)|100|(\.\d+))%))\)$/i,
+ HSL: /^hsl\(((((([12]?[1-9]?\d)|[12]0\d|(3[0-6]\d))(\.\d+)?)|(\.\d+))(deg)?|(0|0?\.\d+)turn|(([0-6](\.\d+)?)|(\.\d+))rad)((,\s?(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}|(\s(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2})\)$/i,
+ HEX: /^#([\da-f]{3}){1,2}$/i
+ }.freeze
+
+ def validate_each(record, attribute, value)
+ return if color?(value)
+
+ record.errors[attribute] << (options[:message] || 'is not a color. Only Hex, RGB and HSL colors are supported.')
+ end
+
+ def color?(value)
+ sanitized_value = value.to_s.strip.gsub(', ', ',')
+
+ REGEXP.values.any? { |regexp| regexp.match? sanitized_value }
+ end
+end
diff --git a/public/assets/tests/color_object.js b/public/assets/tests/color_object.js
new file mode 100644
index 000000000..2e0e0c7c1
--- /dev/null
+++ b/public/assets/tests/color_object.js
@@ -0,0 +1,21 @@
+test('test color object', function() {
+ let hex = new App.ColorObject('#09f609')
+ let hsl = new App.ColorObject([0.5, 0.2, 0.3])
+
+ deepEqual(hex.asHslArray(), [1/3, 0.9294117647058824, 0.5], 'HEX converted to HSL components')
+ deepEqual(hsl.asHslArray(), [0.5, 0.2, 0.3], 'HSL components returned as original input')
+ equal(hex.asString(), '#09f609', 'HEX represented as original input')
+ equal(hsl.asString(), 'hsl(180,20%,30%)', 'HSL components represented as HSL string')
+
+ hex.updateWithString('#fff')
+ equal(hex.asString(), '#fff', 'color updated')
+
+ hsl.updateWithHslComponent(0.25, 1)
+ deepEqual(hsl.asHslArray(), [0.5, 0.25, 0.3], 'given HSL component updated')
+
+ deepEqual(Array.from(App.ColorObject.anyToRgb('#ff0000')), [255, 0, 0, 255], 'any to RGB')
+ deepEqual(App.ColorObject.anyToHslArray('#ff0000'), [0,1,0.5], 'any to HSL components')
+ equal(App.ColorObject.anyToHslString('#ff0000'), 'hsl(0,100%,50%)', 'any to HSL string')
+ deepEqual(App.ColorObject.rgbToHslArray([255, 0, 0]), [0,1,0.5], 'RGB to HSL components')
+ equal(App.ColorObject.hslArrayToHslString([0.5, 0.25, 0.3]), 'hsl(180,25%,30%)', 'HSL components to HSL string')
+})
diff --git a/public/assets/tests/form_color.js b/public/assets/tests/form_color.js
new file mode 100644
index 000000000..690fcd3c6
--- /dev/null
+++ b/public/assets/tests/form_color.js
@@ -0,0 +1,111 @@
+test("form elements check", function(assert) {
+ var done = assert.async(1)
+
+ $('#forms').append('
form elements check
')
+
+ var el = $('#form1')
+ var defaults = {
+ }
+ new App.ControllerForm({
+ el: el,
+ model: {
+ configure_attributes: [
+ { name: 'color', display: 'Color', tag: 'color', null: false, default: '#fff' }
+ ]
+ },
+ autofocus: true
+ });
+
+ var params = App.ControllerForm.params(el)
+ var test_params = {
+ color: '#fff'
+ }
+
+ deepEqual(params, test_params, 'default param check')
+
+ var inputEl = el.find('.js-input')[0]
+
+ var getSwatchColor = () => { return el.find('.js-swatch').css('background-color') }
+ var previousSwatchColor = undefined
+
+ new Promise( (resolve,reject) => {
+ syn.click(inputEl).type('[ctrl]+[a]+[backspace]', resolve)
+ })
+ .then( function() {
+ var params = App.ControllerForm.params(el)
+ var test_params = {
+ color: ''
+ }
+
+ deepEqual(params, test_params, 'UI allows color field to be empty')
+ })
+ .then( function() {
+ previousSwatchColor = getSwatchColor()
+ return new Promise( (resolve,reject) => {
+ syn.click(inputEl).type('rgb(0,100,100)', resolve)
+ })
+ })
+ .then( function() {
+ var params = App.ControllerForm.params(el)
+ var test_params = {
+ color: 'rgb(0,100,100)'
+ }
+
+ deepEqual(params, test_params, 'UI allows to type in RGB colors')
+ notEqual(previousSwatchColor, getSwatchColor(), 'color in swatch was updated')
+ })
+ .then( function() {
+ var circle = el.find('.js-colorpicker-circle')[0]
+
+ previousSwatchColor = getSwatchColor()
+ return new Promise( (resolve,reject) => {
+ syn.click(inputEl).drag(circle, { to: '-10x-10'}, resolve)
+ })
+ })
+ .then( function() {
+ var params = App.ControllerForm.params(el)
+ var test_params = {
+ color: 'hsl(169,100%,20%)'
+ }
+
+ deepEqual(params, test_params, 'Color is transformed to HSL after moving the circle')
+ notEqual(previousSwatchColor, getSwatchColor(), 'color in swatch was updated')
+ })
+ .then( function() {
+ var slider = el.find('.js-colorpicker-slider')[0]
+
+ previousSwatchColor = getSwatchColor()
+ return new Promise( (resolve,reject) => {
+ syn.drag(slider, { to: '-0x-10'}, resolve)
+ })
+ })
+ .then( function() {
+ var params = App.ControllerForm.params(el)
+ var test_params = {
+ color: 'hsl(169,100%,27%)'
+ }
+
+ deepEqual(params, test_params, 'Color code is changed after draging slider')
+ notEqual(previousSwatchColor, getSwatchColor(), 'color in swatch was updated')
+ })
+ .then( function() {
+ let circle = el.find('.js-colorpicker-circle').position()
+ let slider = el.find('.js-colorpicker-slider').position()
+
+ previousSwatchColor = getSwatchColor()
+ return new Promise( (resolve,reject) => {
+ syn.click(inputEl).type('[ctrl]+[a]+[backspace]#ff0000', () => {
+ syn.click(inputEl, resolve)
+ })
+ }).then(function() {
+ let new_circle = el.find('.js-colorpicker-circle').position()
+ let new_slider = el.find('.js-colorpicker-slider').position()
+
+ notDeepEqual(circle, new_circle, 'Color picker is updated after typing in color')
+ notDeepEqual(slider, new_slider, 'Color picker is updated after typing in color')
+
+ notEqual(previousSwatchColor, getSwatchColor(), 'color in swatch was updated')
+ })
+ })
+ .finally(done)
+});
diff --git a/public/assets/tests/syn-0.14.1.js b/public/assets/tests/syn-0.14.1.js
new file mode 100644
index 000000000..540735f4f
--- /dev/null
+++ b/public/assets/tests/syn-0.14.1.js
@@ -0,0 +1,2818 @@
+/*[global-shim-start]*/
+(function(exports, global, doEval) {
+ // jshint ignore:line
+ var origDefine = global.define;
+
+ var get = function(name) {
+ var parts = name.split("."),
+ cur = global,
+ i;
+ for (i = 0; i < parts.length; i++) {
+ if (!cur) {
+ break;
+ }
+ cur = cur[parts[i]];
+ }
+ return cur;
+ };
+ var set = function(name, val) {
+ var parts = name.split("."),
+ cur = global,
+ i,
+ part,
+ next;
+ for (i = 0; i < parts.length - 1; i++) {
+ part = parts[i];
+ next = cur[part];
+ if (!next) {
+ next = cur[part] = {};
+ }
+ cur = next;
+ }
+ part = parts[parts.length - 1];
+ cur[part] = val;
+ };
+ var useDefault = function(mod) {
+ if (!mod || !mod.__esModule) return false;
+ var esProps = { __esModule: true, default: true };
+ for (var p in mod) {
+ if (!esProps[p]) return false;
+ }
+ return true;
+ };
+
+ var hasCjsDependencies = function(deps) {
+ return (
+ deps[0] === "require" && deps[1] === "exports" && deps[2] === "module"
+ );
+ };
+
+ var modules =
+ (global.define && global.define.modules) ||
+ (global._define && global._define.modules) ||
+ {};
+ var ourDefine = (global.define = function(moduleName, deps, callback) {
+ var module;
+ if (typeof deps === "function") {
+ callback = deps;
+ deps = [];
+ }
+ var args = [],
+ i;
+ for (i = 0; i < deps.length; i++) {
+ args.push(
+ exports[deps[i]]
+ ? get(exports[deps[i]])
+ : modules[deps[i]] || get(deps[i])
+ );
+ }
+ // CJS has no dependencies but 3 callback arguments
+ if (hasCjsDependencies(deps) || (!deps.length && callback.length)) {
+ module = { exports: {} };
+ args[0] = function(name) {
+ return exports[name] ? get(exports[name]) : modules[name];
+ };
+ args[1] = module.exports;
+ args[2] = module;
+ } else if (!args[0] && deps[0] === "exports") {
+ // Babel uses the exports and module object.
+ module = { exports: {} };
+ args[0] = module.exports;
+ if (deps[1] === "module") {
+ args[1] = module;
+ }
+ } else if (!args[0] && deps[0] === "module") {
+ args[0] = { id: moduleName };
+ }
+
+ global.define = origDefine;
+ var result = callback ? callback.apply(null, args) : undefined;
+ global.define = ourDefine;
+
+ // Favor CJS module.exports over the return value
+ result = module && module.exports ? module.exports : result;
+ modules[moduleName] = result;
+
+ // Set global exports
+ var globalExport = exports[moduleName];
+ if (globalExport && !get(globalExport)) {
+ if (useDefault(result)) {
+ result = result["default"];
+ }
+ set(globalExport, result);
+ }
+ });
+ global.define.orig = origDefine;
+ global.define.modules = modules;
+ global.define.amd = true;
+ ourDefine("@loader", [], function() {
+ // shim for @@global-helpers
+ var noop = function() {};
+ return {
+ get: function() {
+ return { prepareGlobal: noop, retrieveGlobal: noop };
+ },
+ global: global,
+ __exec: function(__load) {
+ doEval(__load.source, global);
+ }
+ };
+ });
+})(
+ {},
+ typeof self == "object" && self.Object == Object ? self : window,
+ function(__$source__, __$global__) {
+ // jshint ignore:line
+ eval("(function() { " + __$source__ + " \n }).call(__$global__);");
+ }
+);
+
+/*syn@0.14.1#synthetic*/
+define('syn/synthetic', function (require, exports, module) {
+ var opts = window.syn ? window.syn : {};
+ var extend = function (d, s) {
+ var p;
+ for (p in s) {
+ d[p] = s[p];
+ }
+ return d;
+ }, browser = {
+ msie: !!(window.attachEvent && !window.opera) || navigator.userAgent.indexOf('Trident/') > -1,
+ opera: !!window.opera,
+ webkit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+ safari: navigator.userAgent.indexOf('AppleWebKit/') > -1 && navigator.userAgent.indexOf('Chrome/') === -1,
+ gecko: navigator.userAgent.indexOf('Gecko') > -1,
+ mobilesafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/),
+ rhino: navigator.userAgent.match(/Rhino/) && true,
+ chrome: !!window.chrome && !!window.chrome.webstore
+ }, createEventObject = function (type, options, element) {
+ var event = element.ownerDocument.createEventObject();
+ return extend(event, options);
+ }, data = {}, id = 1, expando = '_synthetic' + new Date().getTime(), bind, unbind, schedule, key = /keypress|keyup|keydown/, page = /load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll/, activeElement, syn = function (type, element, options, callback) {
+ return new syn.init(type, element, options, callback);
+ };
+ syn.config = opts;
+ syn.__tryFocus = function tryFocus(element) {
+ try {
+ element.focus();
+ } catch (e) {
+ }
+ };
+ bind = function (el, ev, f) {
+ return el.addEventListener ? el.addEventListener(ev, f, false) : el.attachEvent('on' + ev, f);
+ };
+ unbind = function (el, ev, f) {
+ return el.addEventListener ? el.removeEventListener(ev, f, false) : el.detachEvent('on' + ev, f);
+ };
+ schedule = syn.config.schedule || function (fn, ms) {
+ setTimeout(fn, ms);
+ };
+ extend(syn, {
+ init: function (type, element, options, callback) {
+ var args = syn.args(options, element, callback), self = this;
+ this.queue = [];
+ this.element = args.element;
+ if (typeof this[type] === 'function') {
+ this[type](args.element, args.options, function (defaults, el) {
+ if (args.callback) {
+ args.callback.apply(self, arguments);
+ }
+ self.done.apply(self, arguments);
+ });
+ } else {
+ this.result = syn.trigger(args.element, type, args.options);
+ if (args.callback) {
+ args.callback.call(this, args.element, this.result);
+ }
+ }
+ },
+ jquery: function (el, fast) {
+ if (window.FuncUnit && window.FuncUnit.jQuery) {
+ return window.FuncUnit.jQuery;
+ }
+ if (el) {
+ return syn.helpers.getWindow(el).jQuery || window.jQuery;
+ } else {
+ return window.jQuery;
+ }
+ },
+ args: function () {
+ var res = {}, i = 0;
+ for (; i < arguments.length; i++) {
+ if (typeof arguments[i] === 'function') {
+ res.callback = arguments[i];
+ } else if (arguments[i] && arguments[i].jquery) {
+ res.element = arguments[i][0];
+ } else if (arguments[i] && arguments[i].nodeName) {
+ res.element = arguments[i];
+ } else if (res.options && typeof arguments[i] === 'string') {
+ res.element = document.getElementById(arguments[i]);
+ } else if (arguments[i]) {
+ res.options = arguments[i];
+ }
+ }
+ return res;
+ },
+ click: function (element, options, callback) {
+ syn('click!', element, options, callback);
+ },
+ defaults: {
+ focus: function focus() {
+ if (!syn.support.focusChanges) {
+ var element = this, nodeName = element.nodeName.toLowerCase();
+ syn.data(element, 'syntheticvalue', element.value);
+ if (nodeName === 'input' || nodeName === 'textarea') {
+ bind(element, 'blur', function blur() {
+ if (syn.data(element, 'syntheticvalue') !== element.value) {
+ syn.trigger(element, 'change', {});
+ }
+ unbind(element, 'blur', blur);
+ });
+ }
+ }
+ },
+ submit: function () {
+ syn.onParents(this, function (el) {
+ if (el.nodeName.toLowerCase() === 'form') {
+ el.submit();
+ return false;
+ }
+ });
+ }
+ },
+ changeOnBlur: function (element, prop, value) {
+ bind(element, 'blur', function onblur() {
+ if (value !== element[prop]) {
+ syn.trigger(element, 'change', {});
+ }
+ unbind(element, 'blur', onblur);
+ });
+ },
+ closest: function (el, type) {
+ while (el && el.nodeName.toLowerCase() !== type.toLowerCase()) {
+ el = el.parentNode;
+ }
+ return el;
+ },
+ data: function (el, key, value) {
+ var d;
+ if (!el[expando]) {
+ el[expando] = id++;
+ }
+ if (!data[el[expando]]) {
+ data[el[expando]] = {};
+ }
+ d = data[el[expando]];
+ if (value) {
+ data[el[expando]][key] = value;
+ } else {
+ return data[el[expando]][key];
+ }
+ },
+ onParents: function (el, func) {
+ var res;
+ while (el && res !== false) {
+ res = func(el);
+ el = el.parentNode;
+ }
+ return el;
+ },
+ focusable: /^(a|area|frame|iframe|label|input|select|textarea|button|html|object)$/i,
+ isFocusable: function (elem) {
+ var attributeNode;
+ if (elem.getAttributeNode) {
+ attributeNode = elem.getAttributeNode('tabIndex');
+ }
+ return this.focusable.test(elem.nodeName) || attributeNode && attributeNode.specified && syn.isVisible(elem);
+ },
+ isVisible: function (elem) {
+ return elem.offsetWidth && elem.offsetHeight || elem.clientWidth && elem.clientHeight;
+ },
+ tabIndex: function (elem) {
+ var attributeNode = elem.getAttributeNode('tabIndex');
+ return attributeNode && attributeNode.specified && (parseInt(elem.getAttribute('tabIndex')) || 0);
+ },
+ bind: bind,
+ unbind: unbind,
+ schedule: schedule,
+ browser: browser,
+ helpers: {
+ createEventObject: createEventObject,
+ createBasicStandardEvent: function (type, defaults, doc) {
+ var event;
+ try {
+ event = doc.createEvent('Events');
+ } catch (e2) {
+ event = doc.createEvent('UIEvents');
+ } finally {
+ event.initEvent(type, true, true);
+ extend(event, defaults);
+ }
+ return event;
+ },
+ inArray: function (item, array) {
+ var i = 0;
+ for (; i < array.length; i++) {
+ if (array[i] === item) {
+ return i;
+ }
+ }
+ return -1;
+ },
+ getWindow: function (element) {
+ if (element.ownerDocument) {
+ return element.ownerDocument.defaultView || element.ownerDocument.parentWindow;
+ }
+ },
+ extend: extend,
+ scrollOffset: function (win, set) {
+ var doc = win.document.documentElement, body = win.document.body;
+ if (set) {
+ window.scrollTo(set.left, set.top);
+ } else {
+ return {
+ left: (doc && doc.scrollLeft || body && body.scrollLeft || 0) + (doc.clientLeft || 0),
+ top: (doc && doc.scrollTop || body && body.scrollTop || 0) + (doc.clientTop || 0)
+ };
+ }
+ },
+ scrollDimensions: function (win) {
+ var doc = win.document.documentElement, body = win.document.body, docWidth = doc.clientWidth, docHeight = doc.clientHeight, compat = win.document.compatMode === 'CSS1Compat';
+ return {
+ height: compat && docHeight || body.clientHeight || docHeight,
+ width: compat && docWidth || body.clientWidth || docWidth
+ };
+ },
+ addOffset: function (options, el) {
+ var jq = syn.jquery(el), off;
+ if (typeof options === 'object' && options.clientX === undefined && options.clientY === undefined && options.pageX === undefined && options.pageY === undefined && jq) {
+ el = jq(el);
+ off = el.offset();
+ options.pageX = off.left + el.width() / 2;
+ options.pageY = off.top + el.height() / 2;
+ }
+ }
+ },
+ key: {
+ ctrlKey: null,
+ altKey: null,
+ shiftKey: null,
+ metaKey: null
+ },
+ dispatch: function (event, element, type, autoPrevent) {
+ if (element.dispatchEvent && event) {
+ var preventDefault = event.preventDefault, prevents = autoPrevent ? -1 : 0;
+ if (autoPrevent) {
+ bind(element, type, function ontype(ev) {
+ ev.preventDefault();
+ unbind(this, type, ontype);
+ });
+ }
+ event.preventDefault = function () {
+ prevents++;
+ if (++prevents > 0) {
+ preventDefault.apply(this, []);
+ }
+ };
+ element.dispatchEvent(event);
+ return prevents <= 0;
+ } else {
+ try {
+ window.event = event;
+ } catch (e) {
+ }
+ return element.sourceIndex <= 0 || element.fireEvent && element.fireEvent('on' + type, event);
+ }
+ },
+ create: {
+ page: {
+ event: function (type, options, element) {
+ var doc = syn.helpers.getWindow(element).document || document, event;
+ if (doc.createEvent) {
+ event = doc.createEvent('Events');
+ event.initEvent(type, true, true);
+ return event;
+ } else {
+ try {
+ event = createEventObject(type, options, element);
+ } catch (e) {
+ }
+ return event;
+ }
+ }
+ },
+ focus: {
+ event: function (type, options, element) {
+ syn.onParents(element, function (el) {
+ if (syn.isFocusable(el)) {
+ if (el.nodeName.toLowerCase() !== 'html') {
+ syn.__tryFocus(el);
+ activeElement = el;
+ } else if (activeElement) {
+ var doc = syn.helpers.getWindow(element).document;
+ if (doc !== window.document) {
+ return false;
+ } else if (doc.activeElement) {
+ doc.activeElement.blur();
+ activeElement = null;
+ } else {
+ activeElement.blur();
+ activeElement = null;
+ }
+ }
+ return false;
+ }
+ });
+ return true;
+ }
+ }
+ },
+ support: {
+ clickChanges: false,
+ clickSubmits: false,
+ keypressSubmits: false,
+ mouseupSubmits: false,
+ radioClickChanges: false,
+ focusChanges: false,
+ linkHrefJS: false,
+ keyCharacters: false,
+ backspaceWorks: false,
+ mouseDownUpClicks: false,
+ tabKeyTabs: false,
+ keypressOnAnchorClicks: false,
+ optionClickBubbles: false,
+ pointerEvents: false,
+ touchEvents: false,
+ ready: 0
+ },
+ trigger: function (element, type, options) {
+ if (!options) {
+ options = {};
+ }
+ var create = syn.create, setup = create[type] && create[type].setup, kind = key.test(type) ? 'key' : page.test(type) ? 'page' : 'mouse', createType = create[type] || {}, createKind = create[kind], event, ret, autoPrevent, dispatchEl = element;
+ if (syn.support.ready === 2 && setup) {
+ setup(type, options, element);
+ }
+ autoPrevent = options._autoPrevent;
+ delete options._autoPrevent;
+ if (createType.event) {
+ ret = createType.event(type, options, element);
+ } else {
+ options = createKind.options ? createKind.options(type, options, element) : options;
+ if (!syn.support.changeBubbles && /option/i.test(element.nodeName)) {
+ dispatchEl = element.parentNode;
+ }
+ event = createKind.event(type, options, dispatchEl);
+ ret = syn.dispatch(event, dispatchEl, type, autoPrevent);
+ }
+ if (ret && syn.support.ready === 2 && syn.defaults[type]) {
+ syn.defaults[type].call(element, options, autoPrevent);
+ }
+ return ret;
+ },
+ eventSupported: function (eventName) {
+ var el = document.createElement('div');
+ eventName = 'on' + eventName;
+ var isSupported = eventName in el;
+ if (!isSupported) {
+ el.setAttribute(eventName, 'return;');
+ isSupported = typeof el[eventName] === 'function';
+ }
+ el = null;
+ return isSupported;
+ }
+ });
+ extend(syn.init.prototype, {
+ then: function (type, element, options, callback) {
+ if (syn.autoDelay) {
+ this.delay();
+ }
+ var args = syn.args(options, element, callback), self = this;
+ this.queue.unshift(function (el, prevented) {
+ if (typeof this[type] === 'function') {
+ this.element = args.element || el;
+ this[type](this.element, args.options, function (defaults, el) {
+ if (args.callback) {
+ args.callback.apply(self, arguments);
+ }
+ self.done.apply(self, arguments);
+ });
+ } else {
+ this.result = syn.trigger(args.element, type, args.options);
+ if (args.callback) {
+ args.callback.call(this, args.element, this.result);
+ }
+ return this;
+ }
+ });
+ return this;
+ },
+ delay: function (timeout, callback) {
+ if (typeof timeout === 'function') {
+ callback = timeout;
+ timeout = null;
+ }
+ timeout = timeout || 600;
+ var self = this;
+ this.queue.unshift(function () {
+ schedule(function () {
+ if (callback) {
+ callback.apply(self, []);
+ }
+ self.done.apply(self, arguments);
+ }, timeout);
+ });
+ return this;
+ },
+ done: function (defaults, el) {
+ if (el) {
+ this.element = el;
+ }
+ if (this.queue.length) {
+ this.queue.pop().call(this, this.element, defaults);
+ }
+ },
+ '_click': function (element, options, callback, force) {
+ syn.helpers.addOffset(options, element);
+ if (syn.support.pointerEvents) {
+ syn.trigger(element, 'pointerdown', options);
+ }
+ if (syn.support.touchEvents) {
+ syn.trigger(element, 'touchstart', options);
+ }
+ syn.trigger(element, 'mousedown', options);
+ schedule(function () {
+ if (syn.support.pointerEvents) {
+ syn.trigger(element, 'pointerup', options);
+ }
+ if (syn.support.touchEvents) {
+ syn.trigger(element, 'touchend', options);
+ }
+ syn.trigger(element, 'mouseup', options);
+ if (!syn.support.mouseDownUpClicks || force) {
+ syn.trigger(element, 'click', options);
+ callback(true);
+ } else {
+ syn.create.click.setup('click', options, element);
+ syn.defaults.click.call(element);
+ schedule(function () {
+ callback(true);
+ }, 1);
+ }
+ }, 1);
+ },
+ '_rightClick': function (element, options, callback) {
+ syn.helpers.addOffset(options, element);
+ var mouseopts = extend(extend({}, syn.mouse.browser.right.mouseup), options);
+ if (syn.support.pointerEvents) {
+ syn.trigger(element, 'pointerdown', mouseopts);
+ }
+ syn.trigger(element, 'mousedown', mouseopts);
+ schedule(function () {
+ if (syn.support.pointerEvents) {
+ syn.trigger(element, 'pointerup', mouseopts);
+ }
+ syn.trigger(element, 'mouseup', mouseopts);
+ if (syn.mouse.browser.right.contextmenu) {
+ syn.trigger(element, 'contextmenu', extend(extend({}, syn.mouse.browser.right.contextmenu), options));
+ }
+ callback(true);
+ }, 1);
+ },
+ '_dblclick': function (element, options, callback) {
+ syn.helpers.addOffset(options, element);
+ var self = this;
+ this._click(element, options, function () {
+ schedule(function () {
+ self._click(element, options, function () {
+ syn.trigger(element, 'dblclick', options);
+ callback(true);
+ }, true);
+ }, 2);
+ });
+ }
+ });
+ var actions = [
+ 'click',
+ 'dblclick',
+ 'move',
+ 'drag',
+ 'key',
+ 'type',
+ 'rightClick'
+ ], makeAction = function (name) {
+ syn[name] = function (element, options, callback) {
+ return syn('_' + name, element, options, callback);
+ };
+ syn.init.prototype[name] = function (element, options, callback) {
+ return this.then('_' + name, element, options, callback);
+ };
+ }, i = 0;
+ for (; i < actions.length; i++) {
+ makeAction(actions[i]);
+ }
+ module.exports = syn;
+});
+/*syn@0.14.1#mouse*/
+define('syn/mouse', [
+ 'require',
+ 'exports',
+ 'module',
+ 'syn/synthetic'
+], function (require, exports, module) {
+ var syn = require('syn/synthetic');
+ var h = syn.helpers, getWin = h.getWindow;
+ syn.mouse = {};
+ h.extend(syn.defaults, {
+ mousedown: function (options) {
+ syn.trigger(this, 'focus', {});
+ },
+ click: function () {
+ var element = this, href, type, createChange, radioChanged, nodeName, scope;
+ try {
+ href = element.href;
+ type = element.type;
+ createChange = syn.data(element, 'createChange');
+ radioChanged = syn.data(element, 'radioChanged');
+ scope = getWin(element);
+ nodeName = element.nodeName.toLowerCase();
+ } catch (e) {
+ return;
+ }
+ if (!syn.support.linkHrefJS && /^\s*javascript:/.test(href)) {
+ var code = href.replace(/^\s*javascript:/, '');
+ if (code !== '//' && code.indexOf('void(0)') === -1) {
+ if (window.selenium) {
+ eval('with(selenium.browserbot.getCurrentWindow()){' + code + '}');
+ } else {
+ eval('with(scope){' + code + '}');
+ }
+ }
+ }
+ if (!syn.support.clickSubmits && ((nodeName === 'input' || nodeName === 'button') && type === 'submit')) {
+ var form = syn.closest(element, 'form');
+ if (form) {
+ syn.trigger(form, 'submit', {});
+ }
+ }
+ if (nodeName === 'a' && element.href && !/^\s*javascript:/.test(href)) {
+ scope.location.href = href;
+ }
+ if (nodeName === 'input' && type === 'checkbox') {
+ if (!syn.support.clickChanges) {
+ syn.trigger(element, 'change', {});
+ }
+ }
+ if (nodeName === 'input' && type === 'radio') {
+ if (radioChanged && !syn.support.radioClickChanges) {
+ syn.trigger(element, 'change', {});
+ }
+ }
+ if (nodeName === 'option' && createChange) {
+ syn.trigger(element.parentNode, 'change', {});
+ syn.data(element, 'createChange', false);
+ }
+ }
+ });
+ h.extend(syn.create, {
+ mouse: {
+ options: function (type, options, element) {
+ var doc = document.documentElement, body = document.body, center = [
+ options.pageX || 0,
+ options.pageY || 0
+ ], left = syn.mouse.browser && syn.mouse.browser.left[type], right = syn.mouse.browser && syn.mouse.browser.right[type];
+ return h.extend({
+ bubbles: true,
+ cancelable: true,
+ view: window,
+ detail: 1,
+ screenX: 1,
+ screenY: 1,
+ clientX: options.clientX || center[0] - (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0),
+ clientY: options.clientY || center[1] - (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0),
+ ctrlKey: !!syn.key.ctrlKey,
+ altKey: !!syn.key.altKey,
+ shiftKey: !!syn.key.shiftKey,
+ metaKey: !!syn.key.metaKey,
+ button: left && left.button !== null ? left.button : right && right.button || (type === 'contextmenu' ? 2 : 0),
+ relatedTarget: document.documentElement
+ }, options);
+ },
+ event: function (type, defaults, element) {
+ var doc = getWin(element).document || document, event;
+ if (doc.createEvent) {
+ try {
+ event = doc.createEvent('MouseEvents');
+ event.initMouseEvent(type, defaults.bubbles, defaults.cancelable, defaults.view, defaults.detail, defaults.screenX, defaults.screenY, defaults.clientX, defaults.clientY, defaults.ctrlKey, defaults.altKey, defaults.shiftKey, defaults.metaKey, defaults.button, defaults.relatedTarget);
+ } catch (e) {
+ event = h.createBasicStandardEvent(type, defaults, doc);
+ }
+ event.synthetic = true;
+ return event;
+ } else {
+ try {
+ event = h.createEventObject(type, defaults, element);
+ } catch (e) {
+ }
+ return event;
+ }
+ }
+ },
+ click: {
+ setup: function (type, options, element) {
+ var nodeName = element.nodeName.toLowerCase();
+ if (!syn.support.clickChecks && !syn.support.changeChecks && nodeName === 'input') {
+ type = element.type.toLowerCase();
+ if (type === 'checkbox') {
+ element.checked = !element.checked;
+ }
+ if (type === 'radio') {
+ if (!element.checked) {
+ try {
+ syn.data(element, 'radioChanged', true);
+ } catch (e) {
+ }
+ element.checked = true;
+ }
+ }
+ }
+ if (nodeName === 'a' && element.href && !/^\s*javascript:/.test(element.href)) {
+ syn.data(element, 'href', element.href);
+ }
+ if (/option/i.test(element.nodeName)) {
+ var child = element.parentNode.firstChild, i = -1;
+ while (child) {
+ if (child.nodeType === 1) {
+ i++;
+ if (child === element) {
+ break;
+ }
+ }
+ child = child.nextSibling;
+ }
+ if (i !== element.parentNode.selectedIndex) {
+ element.parentNode.selectedIndex = i;
+ syn.data(element, 'createChange', true);
+ }
+ }
+ }
+ },
+ mousedown: {
+ setup: function (type, options, element) {
+ var nn = element.nodeName.toLowerCase();
+ if (syn.browser.safari && (nn === 'select' || nn === 'option')) {
+ options._autoPrevent = true;
+ }
+ }
+ }
+ });
+});
+/*syn@0.14.1#mouse.support*/
+define('syn/mouse.support', [
+ 'require',
+ 'exports',
+ 'module',
+ 'syn/synthetic',
+ 'syn/mouse'
+], function (require, exports, module) {
+ var syn = require('syn/synthetic');
+ require('syn/mouse');
+ (function checkSupport() {
+ if (!document.body) {
+ return syn.schedule(checkSupport, 1);
+ }
+ window.__synthTest = function () {
+ syn.support.linkHrefJS = true;
+ };
+ var div = document.createElement('div'), checkbox, submit, form, select;
+ div.innerHTML = '
';
+ document.documentElement.appendChild(div);
+ form = div.firstChild;
+ checkbox = form.childNodes[0];
+ submit = form.childNodes[2];
+ select = form.getElementsByTagName('select')[0];
+ syn.trigger(form.childNodes[6], 'click', {});
+ checkbox.checked = false;
+ checkbox.onchange = function () {
+ syn.support.clickChanges = true;
+ };
+ syn.trigger(checkbox, 'click', {});
+ syn.support.clickChecks = checkbox.checked;
+ checkbox.checked = false;
+ syn.trigger(checkbox, 'change', {});
+ syn.support.changeChecks = checkbox.checked;
+ form.onsubmit = function (ev) {
+ if (ev.preventDefault) {
+ ev.preventDefault();
+ }
+ syn.support.clickSubmits = true;
+ return false;
+ };
+ syn.trigger(submit, 'click', {});
+ form.childNodes[1].onchange = function () {
+ syn.support.radioClickChanges = true;
+ };
+ syn.trigger(form.childNodes[1], 'click', {});
+ syn.bind(div, 'click', function onclick() {
+ syn.support.optionClickBubbles = true;
+ syn.unbind(div, 'click', onclick);
+ });
+ syn.trigger(select.firstChild, 'click', {});
+ syn.support.changeBubbles = syn.eventSupported('change');
+ div.onclick = function () {
+ syn.support.mouseDownUpClicks = true;
+ };
+ syn.trigger(div, 'mousedown', {});
+ syn.trigger(div, 'mouseup', {});
+ document.documentElement.removeChild(div);
+ syn.support.pointerEvents = syn.eventSupported('pointerdown');
+ syn.support.touchEvents = syn.eventSupported('touchstart');
+ syn.support.ready++;
+ }());
+});
+/*syn@0.14.1#browsers*/
+define('syn/browsers', [
+ 'require',
+ 'exports',
+ 'module',
+ 'syn/synthetic',
+ 'syn/mouse'
+], function (require, exports, module) {
+ var syn = require('syn/synthetic');
+ require('syn/mouse');
+ syn.key.browsers = {
+ webkit: {
+ 'prevent': {
+ 'keyup': [],
+ 'keydown': [
+ 'char',
+ 'keypress'
+ ],
+ 'keypress': ['char']
+ },
+ 'character': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keypress': [
+ 'char',
+ 'char'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'specialChars': {
+ 'keydown': [
+ 0,
+ 'char'
+ ],
+ 'keyup': [
+ 0,
+ 'char'
+ ]
+ },
+ 'navigation': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'special': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'tab': {
+ 'keydown': [
+ 0,
+ 'char'
+ ],
+ 'keyup': [
+ 0,
+ 'char'
+ ]
+ },
+ 'pause-break': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'caps': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'escape': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'num-lock': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'scroll-lock': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'print': {
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'function': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ '\r': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keypress': [
+ 'char',
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ }
+ },
+ gecko: {
+ 'prevent': {
+ 'keyup': [],
+ 'keydown': ['char'],
+ 'keypress': ['char']
+ },
+ 'character': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keypress': [
+ 'char',
+ 0
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'specialChars': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keypress': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'navigation': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keypress': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'special': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ '\t': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keypress': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'pause-break': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keypress': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'caps': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'escape': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keypress': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'num-lock': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'scroll-lock': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'print': {
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ 'function': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ },
+ '\r': {
+ 'keydown': [
+ 0,
+ 'key'
+ ],
+ 'keypress': [
+ 0,
+ 'key'
+ ],
+ 'keyup': [
+ 0,
+ 'key'
+ ]
+ }
+ },
+ msie: {
+ 'prevent': {
+ 'keyup': [],
+ 'keydown': [
+ 'char',
+ 'keypress'
+ ],
+ 'keypress': ['char']
+ },
+ 'character': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keypress': [
+ null,
+ 'char'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ 'specialChars': {
+ 'keydown': [
+ null,
+ 'char'
+ ],
+ 'keyup': [
+ null,
+ 'char'
+ ]
+ },
+ 'navigation': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ 'special': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ 'tab': {
+ 'keydown': [
+ null,
+ 'char'
+ ],
+ 'keyup': [
+ null,
+ 'char'
+ ]
+ },
+ 'pause-break': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ 'caps': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ 'escape': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keypress': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ 'num-lock': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ 'scroll-lock': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ 'print': {
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ 'function': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ '\r': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keypress': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ }
+ },
+ opera: {
+ 'prevent': {
+ 'keyup': [],
+ 'keydown': [],
+ 'keypress': ['char']
+ },
+ 'character': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keypress': [
+ null,
+ 'char'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ 'specialChars': {
+ 'keydown': [
+ null,
+ 'char'
+ ],
+ 'keypress': [
+ null,
+ 'char'
+ ],
+ 'keyup': [
+ null,
+ 'char'
+ ]
+ },
+ 'navigation': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keypress': [
+ null,
+ 'key'
+ ]
+ },
+ 'special': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keypress': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ 'tab': {
+ 'keydown': [
+ null,
+ 'char'
+ ],
+ 'keypress': [
+ null,
+ 'char'
+ ],
+ 'keyup': [
+ null,
+ 'char'
+ ]
+ },
+ 'pause-break': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keypress': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ 'caps': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ 'escape': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keypress': [
+ null,
+ 'key'
+ ]
+ },
+ 'num-lock': {
+ 'keyup': [
+ null,
+ 'key'
+ ],
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keypress': [
+ null,
+ 'key'
+ ]
+ },
+ 'scroll-lock': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keypress': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ 'print': {},
+ 'function': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keypress': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ },
+ '\r': {
+ 'keydown': [
+ null,
+ 'key'
+ ],
+ 'keypress': [
+ null,
+ 'key'
+ ],
+ 'keyup': [
+ null,
+ 'key'
+ ]
+ }
+ }
+ };
+ syn.mouse.browsers = {
+ webkit: {
+ 'right': {
+ 'mousedown': {
+ 'button': 2,
+ 'which': 3
+ },
+ 'mouseup': {
+ 'button': 2,
+ 'which': 3
+ },
+ 'contextmenu': {
+ 'button': 2,
+ 'which': 3
+ }
+ },
+ 'left': {
+ 'mousedown': {
+ 'button': 0,
+ 'which': 1
+ },
+ 'mouseup': {
+ 'button': 0,
+ 'which': 1
+ },
+ 'click': {
+ 'button': 0,
+ 'which': 1
+ }
+ }
+ },
+ opera: {
+ 'right': {
+ 'mousedown': {
+ 'button': 2,
+ 'which': 3
+ },
+ 'mouseup': {
+ 'button': 2,
+ 'which': 3
+ }
+ },
+ 'left': {
+ 'mousedown': {
+ 'button': 0,
+ 'which': 1
+ },
+ 'mouseup': {
+ 'button': 0,
+ 'which': 1
+ },
+ 'click': {
+ 'button': 0,
+ 'which': 1
+ }
+ }
+ },
+ msie: {
+ 'right': {
+ 'mousedown': { 'button': 2 },
+ 'mouseup': { 'button': 2 },
+ 'contextmenu': { 'button': 0 }
+ },
+ 'left': {
+ 'mousedown': { 'button': 1 },
+ 'mouseup': { 'button': 1 },
+ 'click': { 'button': 0 }
+ }
+ },
+ chrome: {
+ 'right': {
+ 'mousedown': {
+ 'button': 2,
+ 'which': 3
+ },
+ 'mouseup': {
+ 'button': 2,
+ 'which': 3
+ },
+ 'contextmenu': {
+ 'button': 2,
+ 'which': 3
+ }
+ },
+ 'left': {
+ 'mousedown': {
+ 'button': 0,
+ 'which': 1
+ },
+ 'mouseup': {
+ 'button': 0,
+ 'which': 1
+ },
+ 'click': {
+ 'button': 0,
+ 'which': 1
+ }
+ }
+ },
+ gecko: {
+ 'left': {
+ 'mousedown': {
+ 'button': 0,
+ 'which': 1
+ },
+ 'mouseup': {
+ 'button': 0,
+ 'which': 1
+ },
+ 'click': {
+ 'button': 0,
+ 'which': 1
+ }
+ },
+ 'right': {
+ 'mousedown': {
+ 'button': 2,
+ 'which': 3
+ },
+ 'mouseup': {
+ 'button': 2,
+ 'which': 3
+ },
+ 'contextmenu': {
+ 'button': 2,
+ 'which': 3
+ }
+ }
+ }
+ };
+ syn.key.browser = function () {
+ if (syn.key.browsers[window.navigator.userAgent]) {
+ return syn.key.browsers[window.navigator.userAgent];
+ }
+ for (var browser in syn.browser) {
+ if (syn.browser[browser] && syn.key.browsers[browser]) {
+ return syn.key.browsers[browser];
+ }
+ }
+ return syn.key.browsers.gecko;
+ }();
+ syn.mouse.browser = function () {
+ if (syn.mouse.browsers[window.navigator.userAgent]) {
+ return syn.mouse.browsers[window.navigator.userAgent];
+ }
+ for (var browser in syn.browser) {
+ if (syn.browser[browser] && syn.mouse.browsers[browser]) {
+ return syn.mouse.browsers[browser];
+ }
+ }
+ return syn.mouse.browsers.gecko;
+ }();
+});
+/*syn@0.14.1#typeable*/
+define('syn/typeable', [
+ 'require',
+ 'exports',
+ 'module',
+ 'syn/synthetic'
+], function (require, exports, module) {
+ var syn = require('syn/synthetic');
+ var typeables = [];
+ var __indexOf = [].indexOf || function (item) {
+ for (var i = 0, l = this.length; i < l; i++) {
+ if (i in this && this[i] === item) {
+ return i;
+ }
+ }
+ return -1;
+ };
+ syn.typeable = function (fn) {
+ if (__indexOf.call(typeables, fn) === -1) {
+ typeables.push(fn);
+ }
+ };
+ syn.typeable.test = function (el) {
+ for (var i = 0, len = typeables.length; i < len; i++) {
+ if (typeables[i](el)) {
+ return true;
+ }
+ }
+ return false;
+ };
+ var type = syn.typeable;
+ var typeableExp = /input|textarea/i;
+ type(function (el) {
+ return typeableExp.test(el.nodeName);
+ });
+ type(function (el) {
+ return __indexOf.call([
+ '',
+ 'true'
+ ], el.getAttribute('contenteditable')) !== -1;
+ });
+});
+/*syn@0.14.1#key*/
+define('syn/key', [
+ 'require',
+ 'exports',
+ 'module',
+ 'syn/synthetic',
+ 'syn/typeable',
+ 'syn/browsers'
+], function (require, exports, module) {
+ var syn = require('syn/synthetic');
+ require('syn/typeable');
+ require('syn/browsers');
+ var h = syn.helpers, formElExp = /input|textarea/i, supportsSelection = function (el) {
+ var result;
+ try {
+ result = el.selectionStart !== undefined && el.selectionStart !== null;
+ } catch (e) {
+ result = false;
+ }
+ return result;
+ }, getSelection = function (el) {
+ var real, r, start;
+ if (supportsSelection(el)) {
+ if (document.activeElement && document.activeElement !== el && el.selectionStart === el.selectionEnd && el.selectionStart === 0) {
+ return {
+ start: el.value.length,
+ end: el.value.length
+ };
+ }
+ return {
+ start: el.selectionStart,
+ end: el.selectionEnd
+ };
+ } else {
+ try {
+ if (el.nodeName.toLowerCase() === 'input') {
+ real = h.getWindow(el).document.selection.createRange();
+ r = el.createTextRange();
+ r.setEndPoint('EndToStart', real);
+ start = r.text.length;
+ return {
+ start: start,
+ end: start + real.text.length
+ };
+ } else {
+ real = h.getWindow(el).document.selection.createRange();
+ r = real.duplicate();
+ var r2 = real.duplicate(), r3 = real.duplicate();
+ r2.collapse();
+ r3.collapse(false);
+ r2.moveStart('character', -1);
+ r3.moveStart('character', -1);
+ r.moveToElementText(el);
+ r.setEndPoint('EndToEnd', real);
+ start = r.text.length - real.text.length;
+ var end = r.text.length;
+ if (start !== 0 && r2.text === '') {
+ start += 2;
+ }
+ if (end !== 0 && r3.text === '') {
+ end += 2;
+ }
+ return {
+ start: start,
+ end: end
+ };
+ }
+ } catch (e) {
+ var prop = formElExp.test(el.nodeName) ? 'value' : 'textContent';
+ return {
+ start: el[prop].length,
+ end: el[prop].length
+ };
+ }
+ }
+ }, getFocusable = function (el) {
+ var document = h.getWindow(el).document, res = [];
+ var els = document.getElementsByTagName('*'), len = els.length;
+ for (var i = 0; i < len; i++) {
+ if (syn.isFocusable(els[i]) && els[i] !== document.documentElement) {
+ res.push(els[i]);
+ }
+ }
+ return res;
+ }, textProperty = function () {
+ var el = document.createElement('span');
+ return el.textContent != null ? 'textContent' : 'innerText';
+ }(), getText = function (el) {
+ if (formElExp.test(el.nodeName)) {
+ return el.value;
+ }
+ return el[textProperty];
+ }, setText = function (el, value) {
+ if (formElExp.test(el.nodeName)) {
+ el.value = value;
+ } else {
+ el[textProperty] = value;
+ }
+ };
+ h.extend(syn, {
+ keycodes: {
+ '\b': 8,
+ '\t': 9,
+ '\r': 13,
+ 'shift': 16,
+ 'ctrl': 17,
+ 'alt': 18,
+ 'meta': 91,
+ 'pause-break': 19,
+ 'caps': 20,
+ 'escape': 27,
+ 'num-lock': 144,
+ 'scroll-lock': 145,
+ 'print': 44,
+ 'page-up': 33,
+ 'page-down': 34,
+ 'end': 35,
+ 'home': 36,
+ 'left': 37,
+ 'up': 38,
+ 'right': 39,
+ 'down': 40,
+ 'insert': 45,
+ 'delete': 46,
+ ' ': 32,
+ '0': 48,
+ '1': 49,
+ '2': 50,
+ '3': 51,
+ '4': 52,
+ '5': 53,
+ '6': 54,
+ '7': 55,
+ '8': 56,
+ '9': 57,
+ 'a': 65,
+ 'b': 66,
+ 'c': 67,
+ 'd': 68,
+ 'e': 69,
+ 'f': 70,
+ 'g': 71,
+ 'h': 72,
+ 'i': 73,
+ 'j': 74,
+ 'k': 75,
+ 'l': 76,
+ 'm': 77,
+ 'n': 78,
+ 'o': 79,
+ 'p': 80,
+ 'q': 81,
+ 'r': 82,
+ 's': 83,
+ 't': 84,
+ 'u': 85,
+ 'v': 86,
+ 'w': 87,
+ 'x': 88,
+ 'y': 89,
+ 'z': 90,
+ 'num0': 96,
+ 'num1': 97,
+ 'num2': 98,
+ 'num3': 99,
+ 'num4': 100,
+ 'num5': 101,
+ 'num6': 102,
+ 'num7': 103,
+ 'num8': 104,
+ 'num9': 105,
+ '*': 106,
+ '+': 107,
+ 'subtract': 109,
+ 'decimal': 110,
+ 'divide': 111,
+ ';': 186,
+ '=': 187,
+ ',': 188,
+ 'dash': 189,
+ '-': 189,
+ 'period': 190,
+ '.': 190,
+ 'forward-slash': 191,
+ '/': 191,
+ '`': 192,
+ '[': 219,
+ '\\': 220,
+ ']': 221,
+ '\'': 222,
+ 'left window key': 91,
+ 'right window key': 92,
+ 'select key': 93,
+ 'f1': 112,
+ 'f2': 113,
+ 'f3': 114,
+ 'f4': 115,
+ 'f5': 116,
+ 'f6': 117,
+ 'f7': 118,
+ 'f8': 119,
+ 'f9': 120,
+ 'f10': 121,
+ 'f11': 122,
+ 'f12': 123
+ },
+ selectText: function (el, start, end) {
+ if (supportsSelection(el)) {
+ if (!end) {
+ syn.__tryFocus(el);
+ el.setSelectionRange(start, start);
+ } else {
+ el.selectionStart = start;
+ el.selectionEnd = end;
+ }
+ } else if (el.createTextRange) {
+ var r = el.createTextRange();
+ r.moveStart('character', start);
+ end = end || start;
+ r.moveEnd('character', end - el.value.length);
+ r.select();
+ }
+ },
+ getText: function (el) {
+ if (syn.typeable.test(el)) {
+ var sel = getSelection(el);
+ return el.value.substring(sel.start, sel.end);
+ }
+ var win = syn.helpers.getWindow(el);
+ if (win.getSelection) {
+ return win.getSelection().toString();
+ } else if (win.document.getSelection) {
+ return win.document.getSelection().toString();
+ } else {
+ return win.document.selection.createRange().text;
+ }
+ },
+ getSelection: getSelection
+ });
+ h.extend(syn.key, {
+ data: function (key) {
+ if (syn.key.browser[key]) {
+ return syn.key.browser[key];
+ }
+ for (var kind in syn.key.kinds) {
+ if (h.inArray(key, syn.key.kinds[kind]) > -1) {
+ return syn.key.browser[kind];
+ }
+ }
+ return syn.key.browser.character;
+ },
+ isSpecial: function (keyCode) {
+ var specials = syn.key.kinds.special;
+ for (var i = 0; i < specials.length; i++) {
+ if (syn.keycodes[specials[i]] === keyCode) {
+ return specials[i];
+ }
+ }
+ },
+ options: function (key, event) {
+ var keyData = syn.key.data(key);
+ if (!keyData[event]) {
+ return null;
+ }
+ var charCode = keyData[event][0], keyCode = keyData[event][1], result = {};
+ if (keyCode === 'key') {
+ result.keyCode = syn.keycodes[key];
+ } else if (keyCode === 'char') {
+ result.keyCode = key.charCodeAt(0);
+ } else {
+ result.keyCode = keyCode;
+ }
+ if (charCode === 'char') {
+ result.charCode = key.charCodeAt(0);
+ } else if (charCode !== null) {
+ result.charCode = charCode;
+ }
+ if (result.keyCode) {
+ result.which = result.keyCode;
+ } else {
+ result.which = result.charCode;
+ }
+ return result;
+ },
+ kinds: {
+ special: [
+ 'shift',
+ 'ctrl',
+ 'alt',
+ 'meta',
+ 'caps'
+ ],
+ specialChars: ['\b'],
+ navigation: [
+ 'page-up',
+ 'page-down',
+ 'end',
+ 'home',
+ 'left',
+ 'up',
+ 'right',
+ 'down',
+ 'insert',
+ 'delete'
+ ],
+ 'function': [
+ 'f1',
+ 'f2',
+ 'f3',
+ 'f4',
+ 'f5',
+ 'f6',
+ 'f7',
+ 'f8',
+ 'f9',
+ 'f10',
+ 'f11',
+ 'f12'
+ ]
+ },
+ getDefault: function (key) {
+ if (syn.key.defaults[key]) {
+ return syn.key.defaults[key];
+ }
+ for (var kind in syn.key.kinds) {
+ if (h.inArray(key, syn.key.kinds[kind]) > -1 && syn.key.defaults[kind]) {
+ return syn.key.defaults[kind];
+ }
+ }
+ return syn.key.defaults.character;
+ },
+ defaults: {
+ 'character': function (options, scope, key, force, sel) {
+ if (/num\d+/.test(key)) {
+ key = key.match(/\d+/)[0];
+ }
+ if (force || !syn.support.keyCharacters && syn.typeable.test(this)) {
+ var current = getText(this), before = current.substr(0, sel.start), after = current.substr(sel.end), character = key;
+ setText(this, before + character + after);
+ var charLength = character === '\n' && syn.support.textareaCarriage ? 2 : character.length;
+ syn.selectText(this, before.length + charLength);
+ }
+ },
+ 'c': function (options, scope, key, force, sel) {
+ if (syn.key.ctrlKey) {
+ syn.key.clipboard = syn.getText(this);
+ } else {
+ syn.key.defaults.character.apply(this, arguments);
+ }
+ },
+ 'v': function (options, scope, key, force, sel) {
+ if (syn.key.ctrlKey) {
+ syn.key.defaults.character.call(this, options, scope, syn.key.clipboard, true, sel);
+ } else {
+ syn.key.defaults.character.apply(this, arguments);
+ }
+ },
+ 'a': function (options, scope, key, force, sel) {
+ if (syn.key.ctrlKey) {
+ syn.selectText(this, 0, getText(this).length);
+ } else {
+ syn.key.defaults.character.apply(this, arguments);
+ }
+ },
+ 'home': function () {
+ syn.onParents(this, function (el) {
+ if (el.scrollHeight !== el.clientHeight) {
+ el.scrollTop = 0;
+ return false;
+ }
+ });
+ },
+ 'end': function () {
+ syn.onParents(this, function (el) {
+ if (el.scrollHeight !== el.clientHeight) {
+ el.scrollTop = el.scrollHeight;
+ return false;
+ }
+ });
+ },
+ 'page-down': function () {
+ syn.onParents(this, function (el) {
+ if (el.scrollHeight !== el.clientHeight) {
+ var ch = el.clientHeight;
+ el.scrollTop += ch;
+ return false;
+ }
+ });
+ },
+ 'page-up': function () {
+ syn.onParents(this, function (el) {
+ if (el.scrollHeight !== el.clientHeight) {
+ var ch = el.clientHeight;
+ el.scrollTop -= ch;
+ return false;
+ }
+ });
+ },
+ '\b': function (options, scope, key, force, sel) {
+ if (!syn.support.backspaceWorks && syn.typeable.test(this)) {
+ var current = getText(this), before = current.substr(0, sel.start), after = current.substr(sel.end);
+ if (sel.start === sel.end && sel.start > 0) {
+ setText(this, before.substring(0, before.length - 1) + after);
+ syn.selectText(this, sel.start - 1);
+ } else {
+ setText(this, before + after);
+ syn.selectText(this, sel.start);
+ }
+ }
+ },
+ 'delete': function (options, scope, key, force, sel) {
+ if (!syn.support.backspaceWorks && syn.typeable.test(this)) {
+ var current = getText(this), before = current.substr(0, sel.start), after = current.substr(sel.end);
+ if (sel.start === sel.end && sel.start <= getText(this).length - 1) {
+ setText(this, before + after.substring(1));
+ } else {
+ setText(this, before + after);
+ }
+ syn.selectText(this, sel.start);
+ }
+ },
+ '\r': function (options, scope, key, force, sel) {
+ var nodeName = this.nodeName.toLowerCase();
+ if (nodeName === 'input') {
+ syn.trigger(this, 'change', {});
+ }
+ if (!syn.support.keypressSubmits && nodeName === 'input') {
+ var form = syn.closest(this, 'form');
+ if (form) {
+ syn.trigger(form, 'submit', {});
+ }
+ }
+ if (!syn.support.keyCharacters && nodeName === 'textarea') {
+ syn.key.defaults.character.call(this, options, scope, '\n', undefined, sel);
+ }
+ if (!syn.support.keypressOnAnchorClicks && nodeName === 'a') {
+ syn.trigger(this, 'click', {});
+ }
+ },
+ '\t': function (options, scope) {
+ var focusEls = getFocusable(this), current = null, i = 0, el, firstNotIndexed, orders = [];
+ for (; i < focusEls.length; i++) {
+ orders.push([
+ focusEls[i],
+ i
+ ]);
+ }
+ var sort = function (order1, order2) {
+ var el1 = order1[0], el2 = order2[0], tab1 = syn.tabIndex(el1) || 0, tab2 = syn.tabIndex(el2) || 0;
+ if (tab1 === tab2) {
+ return order1[1] - order2[1];
+ } else {
+ if (tab1 === 0) {
+ return 1;
+ } else if (tab2 === 0) {
+ return -1;
+ } else {
+ return tab1 - tab2;
+ }
+ }
+ };
+ orders.sort(sort);
+ var ordersLength = orders.length;
+ for (i = 0; i < ordersLength; i++) {
+ el = orders[i][0];
+ if (this === el) {
+ var nextIndex = i;
+ if (syn.key.shiftKey) {
+ nextIndex--;
+ current = nextIndex >= 0 && orders[nextIndex][0] || orders[ordersLength - 1][0];
+ } else {
+ nextIndex++;
+ current = nextIndex < ordersLength && orders[nextIndex][0] || orders[0][0];
+ }
+ }
+ }
+ if (!current) {
+ current = firstNotIndexed;
+ } else {
+ syn.__tryFocus(current);
+ }
+ return current;
+ },
+ 'left': function (options, scope, key, force, sel) {
+ if (syn.typeable.test(this)) {
+ if (syn.key.shiftKey) {
+ syn.selectText(this, sel.start === 0 ? 0 : sel.start - 1, sel.end);
+ } else {
+ syn.selectText(this, sel.start === 0 ? 0 : sel.start - 1);
+ }
+ }
+ },
+ 'right': function (options, scope, key, force, sel) {
+ if (syn.typeable.test(this)) {
+ if (syn.key.shiftKey) {
+ syn.selectText(this, sel.start, sel.end + 1 > getText(this).length ? getText(this).length : sel.end + 1);
+ } else {
+ syn.selectText(this, sel.end + 1 > getText(this).length ? getText(this).length : sel.end + 1);
+ }
+ }
+ },
+ 'up': function () {
+ if (/select/i.test(this.nodeName)) {
+ this.selectedIndex = this.selectedIndex ? this.selectedIndex - 1 : 0;
+ }
+ },
+ 'down': function () {
+ if (/select/i.test(this.nodeName)) {
+ syn.changeOnBlur(this, 'selectedIndex', this.selectedIndex);
+ this.selectedIndex = this.selectedIndex + 1;
+ }
+ },
+ 'shift': function () {
+ return null;
+ },
+ 'ctrl': function () {
+ return null;
+ },
+ 'alt': function () {
+ return null;
+ },
+ 'meta': function () {
+ return null;
+ }
+ }
+ });
+ h.extend(syn.create, {
+ keydown: {
+ setup: function (type, options, element) {
+ if (h.inArray(options, syn.key.kinds.special) !== -1) {
+ syn.key[options + 'Key'] = element;
+ }
+ }
+ },
+ keypress: {
+ setup: function (type, options, element) {
+ if (syn.support.keyCharacters && !syn.support.keysOnNotFocused) {
+ syn.__tryFocus(element);
+ }
+ }
+ },
+ keyup: {
+ setup: function (type, options, element) {
+ if (h.inArray(options, syn.key.kinds.special) !== -1) {
+ syn.key[options + 'Key'] = null;
+ }
+ }
+ },
+ key: {
+ options: function (type, options, element) {
+ options = typeof options !== 'object' ? { character: options } : options;
+ options = h.extend({}, options);
+ if (options.character) {
+ h.extend(options, syn.key.options(options.character, type));
+ delete options.character;
+ }
+ options = h.extend({
+ ctrlKey: !!syn.key.ctrlKey,
+ altKey: !!syn.key.altKey,
+ shiftKey: !!syn.key.shiftKey,
+ metaKey: !!syn.key.metaKey
+ }, options);
+ return options;
+ },
+ event: function (type, options, element) {
+ var doc = h.getWindow(element).document || document, event;
+ if (doc.createEvent) {
+ try {
+ event = doc.createEvent('KeyEvents');
+ event.initKeyEvent(type, true, true, window, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode);
+ } catch (e) {
+ event = h.createBasicStandardEvent(type, options, doc);
+ }
+ event.synthetic = true;
+ return event;
+ } else {
+ try {
+ event = h.createEventObject.apply(this, arguments);
+ h.extend(event, options);
+ } catch (e) {
+ }
+ return event;
+ }
+ }
+ }
+ });
+ var convert = {
+ 'enter': '\r',
+ 'backspace': '\b',
+ 'tab': '\t',
+ 'space': ' '
+ };
+ h.extend(syn.init.prototype, {
+ _key: function (element, options, callback) {
+ if (/-up$/.test(options) && h.inArray(options.replace('-up', ''), syn.key.kinds.special) !== -1) {
+ syn.trigger(element, 'keyup', options.replace('-up', ''));
+ return callback(true, element);
+ }
+ var activeElement = h.getWindow(element).document.activeElement, caret = syn.typeable.test(element) && getSelection(element), key = convert[options] || options, runDefaults = syn.trigger(element, 'keydown', key), getDefault = syn.key.getDefault, prevent = syn.key.browser.prevent, defaultResult, keypressOptions = syn.key.options(key, 'keypress');
+ if (runDefaults) {
+ if (!keypressOptions) {
+ defaultResult = getDefault(key).call(element, keypressOptions, h.getWindow(element), key, undefined, caret);
+ } else {
+ if (activeElement !== h.getWindow(element).document.activeElement) {
+ element = h.getWindow(element).document.activeElement;
+ }
+ runDefaults = syn.trigger(element, 'keypress', keypressOptions);
+ if (runDefaults) {
+ defaultResult = getDefault(key).call(element, keypressOptions, h.getWindow(element), key, undefined, caret);
+ }
+ }
+ } else {
+ if (keypressOptions && h.inArray('keypress', prevent.keydown) === -1) {
+ if (activeElement !== h.getWindow(element).document.activeElement) {
+ element = h.getWindow(element).document.activeElement;
+ }
+ syn.trigger(element, 'keypress', keypressOptions);
+ }
+ }
+ if (defaultResult && defaultResult.nodeName) {
+ element = defaultResult;
+ }
+ if (defaultResult !== null) {
+ syn.schedule(function () {
+ if (key === '\r' && element.nodeName.toLowerCase() === 'input') {
+ } else if (syn.support.oninput) {
+ syn.trigger(element, 'input', syn.key.options(key, 'input'));
+ }
+ syn.trigger(element, 'keyup', syn.key.options(key, 'keyup'));
+ callback(runDefaults, element);
+ }, 1);
+ } else {
+ callback(runDefaults, element);
+ }
+ return element;
+ },
+ _type: function (element, options, callback) {
+ var parts = (options + '').match(/(\[[^\]]+\])|([^\[])/g), self = this, runNextPart = function (runDefaults, el) {
+ var part = parts.shift();
+ if (!part) {
+ callback(runDefaults, el);
+ return;
+ }
+ el = el || element;
+ if (part.length > 1) {
+ part = part.substr(1, part.length - 2);
+ }
+ self._key(el, part, runNextPart);
+ };
+ runNextPart();
+ }
+ });
+});
+/*syn@0.14.1#key.support*/
+define('syn/key.support', [
+ 'require',
+ 'exports',
+ 'module',
+ 'syn/synthetic',
+ 'syn/key'
+], function (require, exports, module) {
+ var syn = require('syn/synthetic');
+ require('syn/key');
+ if (!syn.config.support) {
+ (function checkForSupport() {
+ if (!document.body) {
+ return syn.schedule(checkForSupport, 1);
+ }
+ var div = document.createElement('div'), checkbox, submit, form, anchor, textarea, inputter, one, doc;
+ doc = document.documentElement;
+ div.innerHTML = '
';
+ doc.insertBefore(div, doc.firstElementChild || doc.children[0]);
+ form = div.firstChild;
+ checkbox = form.childNodes[0];
+ submit = form.childNodes[2];
+ anchor = form.getElementsByTagName('a')[0];
+ textarea = form.getElementsByTagName('textarea')[0];
+ inputter = form.childNodes[3];
+ one = form.childNodes[4];
+ form.onsubmit = function (ev) {
+ if (ev.preventDefault) {
+ ev.preventDefault();
+ }
+ syn.support.keypressSubmits = true;
+ ev.returnValue = false;
+ return false;
+ };
+ syn.__tryFocus(inputter);
+ syn.trigger(inputter, 'keypress', '\r');
+ syn.trigger(inputter, 'keypress', 'a');
+ syn.support.keyCharacters = inputter.value === 'a';
+ inputter.value = 'a';
+ syn.trigger(inputter, 'keypress', '\b');
+ syn.support.backspaceWorks = inputter.value === '';
+ inputter.onchange = function () {
+ syn.support.focusChanges = true;
+ };
+ syn.__tryFocus(inputter);
+ syn.trigger(inputter, 'keypress', 'a');
+ syn.__tryFocus(form.childNodes[5]);
+ syn.trigger(inputter, 'keypress', 'b');
+ syn.support.keysOnNotFocused = inputter.value === 'ab';
+ syn.bind(anchor, 'click', function (ev) {
+ if (ev.preventDefault) {
+ ev.preventDefault();
+ }
+ syn.support.keypressOnAnchorClicks = true;
+ ev.returnValue = false;
+ return false;
+ });
+ syn.trigger(anchor, 'keypress', '\r');
+ syn.support.textareaCarriage = textarea.value.length === 4;
+ syn.support.oninput = 'oninput' in one;
+ doc.removeChild(div);
+ syn.support.ready++;
+ }());
+ } else {
+ syn.helpers.extend(syn.support, syn.config.support);
+ }
+});
+/*syn@0.14.1#drag*/
+define('syn/drag', [
+ 'require',
+ 'exports',
+ 'module',
+ 'syn/synthetic'
+], function (require, exports, module) {
+ var syn = require('syn/synthetic');
+ var elementFromPoint = function (point, win) {
+ var clientX = point.clientX;
+ var clientY = point.clientY;
+ if (point == null) {
+ return null;
+ }
+ if (syn.support.elementFromPage) {
+ var off = syn.helpers.scrollOffset(win);
+ clientX = clientX + off.left;
+ clientY = clientY + off.top;
+ }
+ return win.document.elementFromPoint(Math.round(clientX), Math.round(clientY));
+ };
+ var DragonDrop = {
+ html5drag: false,
+ focusWindow: null,
+ dragAndDrop: function (focusWindow, fromPoint, toPoint, duration, callback) {
+ this.currentDataTransferItem = null;
+ this.focusWindow = focusWindow;
+ this._mouseOver(fromPoint);
+ this._mouseEnter(fromPoint);
+ this._mouseMove(fromPoint);
+ this._mouseDown(fromPoint);
+ this._dragStart(fromPoint);
+ this._drag(fromPoint);
+ this._dragEnter(fromPoint);
+ this._dragOver(fromPoint);
+ DragonDrop.startMove(fromPoint, toPoint, duration, function () {
+ DragonDrop._dragLeave(fromPoint);
+ DragonDrop._dragEnd(fromPoint);
+ DragonDrop._mouseOut(fromPoint);
+ DragonDrop._mouseLeave(fromPoint);
+ DragonDrop._drop(toPoint);
+ DragonDrop._dragEnd(toPoint);
+ DragonDrop._mouseOver(toPoint);
+ DragonDrop._mouseEnter(toPoint);
+ DragonDrop._mouseMove(toPoint);
+ DragonDrop._mouseOut(toPoint);
+ DragonDrop._mouseLeave(toPoint);
+ callback();
+ DragonDrop.cleanup();
+ });
+ },
+ _dragStart: function (node, options) {
+ this.createAndDispatchEvent(node, 'dragstart', options);
+ },
+ _drag: function (node, options) {
+ this.createAndDispatchEvent(node, 'drag', options);
+ },
+ _dragEnter: function (node, options) {
+ this.createAndDispatchEvent(node, 'dragenter', options);
+ },
+ _dragOver: function (node, options) {
+ this.createAndDispatchEvent(node, 'dragover', options);
+ },
+ _dragLeave: function (node, options) {
+ this.createAndDispatchEvent(node, 'dragleave', options);
+ },
+ _drop: function (node, options) {
+ this.createAndDispatchEvent(node, 'drop', options);
+ },
+ _dragEnd: function (node, options) {
+ this.createAndDispatchEvent(node, 'dragend', options);
+ },
+ _mouseDown: function (node, options) {
+ this.createAndDispatchEvent(node, 'mousedown', options);
+ },
+ _mouseMove: function (node, options) {
+ this.createAndDispatchEvent(node, 'mousemove', options);
+ },
+ _mouseEnter: function (node, options) {
+ this.createAndDispatchEvent(node, 'mouseenter', options);
+ },
+ _mouseOver: function (node, options) {
+ this.createAndDispatchEvent(node, 'mouseover', options);
+ },
+ _mouseOut: function (node, options) {
+ this.createAndDispatchEvent(node, 'mouseout', options);
+ },
+ _mouseLeave: function (node, options) {
+ this.createAndDispatchEvent(node, 'mouseleave', options);
+ },
+ createAndDispatchEvent: function (point, eventName, options) {
+ if (point) {
+ var targetElement = elementFromPoint(point, this.focusWindow);
+ syn.trigger(targetElement, eventName, options);
+ }
+ },
+ getDataTransferObject: function () {
+ if (!this.currentDataTransferItem) {
+ return this.currentDataTransferItem = this.createDataTransferObject();
+ } else {
+ return this.currentDataTransferItem;
+ }
+ },
+ cleanup: function () {
+ this.currentDataTransferItem = null;
+ this.focusWindow = null;
+ },
+ createDataTransferObject: function () {
+ var dataTransfer = {
+ dropEffect: 'none',
+ effectAllowed: 'uninitialized',
+ files: [],
+ items: [],
+ types: [],
+ data: [],
+ setData: function (dataFlavor, value) {
+ var tempdata = {};
+ tempdata.dataFlavor = dataFlavor;
+ tempdata.val = value;
+ this.data.push(tempdata);
+ },
+ getData: function (dataFlavor) {
+ for (var i = 0; i < this.data.length; i++) {
+ var tempdata = this.data[i];
+ if (tempdata.dataFlavor === dataFlavor) {
+ return tempdata.val;
+ }
+ }
+ }
+ };
+ return dataTransfer;
+ },
+ startMove: function (start, end, duration, callback) {
+ var startTime = new Date();
+ var distX = end.clientX - start.clientX;
+ var distY = end.clientY - start.clientY;
+ var win = this.focusWindow;
+ var current = start;
+ var cursor = win.document.createElement('div');
+ var calls = 0;
+ var move;
+ move = function onmove() {
+ var now = new Date();
+ var scrollOffset = syn.helpers.scrollOffset(win);
+ var fraction = (calls === 0 ? 0 : now - startTime) / duration;
+ var options = {
+ clientX: distX * fraction + start.clientX,
+ clientY: distY * fraction + start.clientY
+ };
+ calls++;
+ if (fraction < 1) {
+ syn.helpers.extend(cursor.style, {
+ left: options.clientX + scrollOffset.left + 2 + 'px',
+ top: options.clientY + scrollOffset.top + 2 + 'px'
+ });
+ current = DragonDrop.mouseMove(options, current);
+ syn.schedule(onmove, 15);
+ } else {
+ current = DragonDrop.mouseMove(end, current);
+ win.document.body.removeChild(cursor);
+ callback();
+ }
+ };
+ syn.helpers.extend(cursor.style, {
+ height: '5px',
+ width: '5px',
+ backgroundColor: 'red',
+ position: 'absolute',
+ zIndex: 19999,
+ fontSize: '1px'
+ });
+ win.document.body.appendChild(cursor);
+ move();
+ },
+ mouseMove: function (thisPoint, previousPoint) {
+ var thisElement = elementFromPoint(thisPoint, this.focusWindow);
+ var previousElement = elementFromPoint(previousPoint, this.focusWindow);
+ var options = syn.helpers.extend({}, thisPoint);
+ if (thisElement !== previousElement) {
+ options.relatedTarget = thisElement;
+ this._dragLeave(previousPoint, options);
+ options.relatedTarget = previousElement;
+ this._dragEnter(thisPoint, options);
+ }
+ this._dragOver(thisPoint, options);
+ return thisPoint;
+ }
+ };
+ function createDragEvent(eventName, options, element) {
+ var dragEvent = syn.create.mouse.event(eventName, options, element);
+ dragEvent.dataTransfer = DragonDrop.getDataTransferObject();
+ return syn.dispatch(dragEvent, element, eventName, false);
+ }
+ syn.create.dragstart = { event: createDragEvent };
+ syn.create.dragenter = { event: createDragEvent };
+ syn.create.dragover = { event: createDragEvent };
+ syn.create.dragleave = { event: createDragEvent };
+ syn.create.drag = { event: createDragEvent };
+ syn.create.drop = { event: createDragEvent };
+ syn.create.dragend = { event: createDragEvent };
+ (function dragSupport() {
+ if (!document.body) {
+ syn.schedule(dragSupport, 1);
+ return;
+ }
+ var div = document.createElement('div');
+ document.body.appendChild(div);
+ syn.helpers.extend(div.style, {
+ width: '100px',
+ height: '10000px',
+ backgroundColor: 'blue',
+ position: 'absolute',
+ top: '10px',
+ left: '0px',
+ zIndex: 19999
+ });
+ document.body.scrollTop = 11;
+ if (!document.elementFromPoint) {
+ return;
+ }
+ var el = document.elementFromPoint(3, 1);
+ if (el === div) {
+ syn.support.elementFromClient = true;
+ } else {
+ syn.support.elementFromPage = true;
+ }
+ document.body.removeChild(div);
+ document.body.scrollTop = 0;
+ }());
+ var mouseMove = function (point, win, last) {
+ var el = elementFromPoint(point, win);
+ if (last !== el && el && last) {
+ var options = syn.helpers.extend({}, point);
+ options.relatedTarget = el;
+ if (syn.support.pointerEvents) {
+ syn.trigger(last, 'pointerout', options);
+ syn.trigger(last, 'pointerleave', options);
+ }
+ syn.trigger(last, 'mouseout', options);
+ syn.trigger(last, 'mouseleave', options);
+ options.relatedTarget = last;
+ if (syn.support.pointerEvents) {
+ syn.trigger(el, 'pointerover', options);
+ syn.trigger(el, 'pointerenter', options);
+ }
+ syn.trigger(el, 'mouseover', options);
+ syn.trigger(el, 'mouseenter', options);
+ }
+ if (syn.support.pointerEvents) {
+ syn.trigger(el || win, 'pointermove', point);
+ }
+ if (syn.support.touchEvents) {
+ syn.trigger(el || win, 'touchmove', point);
+ }
+ if (DragonDrop.html5drag) {
+ if (!syn.support.pointerEvents) {
+ syn.trigger(el || win, 'mousemove', point);
+ }
+ } else {
+ syn.trigger(el || win, 'mousemove', point);
+ }
+ return el;
+ }, createEventAtPoint = function (event, point, win) {
+ var el = elementFromPoint(point, win);
+ syn.trigger(el || win, event, point);
+ return el;
+ }, startMove = function (win, start, end, duration, callback) {
+ var startTime = new Date(), distX = end.clientX - start.clientX, distY = end.clientY - start.clientY, current = elementFromPoint(start, win), cursor = win.document.createElement('div'), calls = 0, move;
+ move = function onmove() {
+ var now = new Date(), scrollOffset = syn.helpers.scrollOffset(win), fraction = (calls === 0 ? 0 : now - startTime) / duration, options = {
+ clientX: distX * fraction + start.clientX,
+ clientY: distY * fraction + start.clientY
+ };
+ calls++;
+ if (fraction < 1) {
+ syn.helpers.extend(cursor.style, {
+ left: options.clientX + scrollOffset.left + 2 + 'px',
+ top: options.clientY + scrollOffset.top + 2 + 'px'
+ });
+ current = mouseMove(options, win, current);
+ syn.schedule(onmove, 15);
+ } else {
+ current = mouseMove(end, win, current);
+ win.document.body.removeChild(cursor);
+ callback();
+ }
+ };
+ syn.helpers.extend(cursor.style, {
+ height: '5px',
+ width: '5px',
+ backgroundColor: 'red',
+ position: 'absolute',
+ zIndex: 19999,
+ fontSize: '1px'
+ });
+ win.document.body.appendChild(cursor);
+ move();
+ }, startDrag = function (win, fromPoint, toPoint, duration, callback) {
+ if (syn.support.pointerEvents) {
+ createEventAtPoint('pointerover', fromPoint, win);
+ createEventAtPoint('pointerenter', fromPoint, win);
+ }
+ createEventAtPoint('mouseover', fromPoint, win);
+ createEventAtPoint('mouseenter', fromPoint, win);
+ if (syn.support.pointerEvents) {
+ createEventAtPoint('pointermove', fromPoint, win);
+ }
+ createEventAtPoint('mousemove', fromPoint, win);
+ if (syn.support.pointerEvents) {
+ createEventAtPoint('pointerdown', fromPoint, win);
+ }
+ if (syn.support.touchEvents) {
+ createEventAtPoint('touchstart', fromPoint, win);
+ }
+ createEventAtPoint('mousedown', fromPoint, win);
+ startMove(win, fromPoint, toPoint, duration, function () {
+ if (syn.support.pointerEvents) {
+ createEventAtPoint('pointerup', toPoint, win);
+ }
+ if (syn.support.touchEvents) {
+ createEventAtPoint('touchend', toPoint, win);
+ }
+ createEventAtPoint('mouseup', toPoint, win);
+ if (syn.support.pointerEvents) {
+ createEventAtPoint('pointerleave', toPoint, win);
+ }
+ createEventAtPoint('mouseleave', toPoint, win);
+ callback();
+ });
+ }, center = function (el) {
+ var j = syn.jquery()(el), o = j.offset();
+ return {
+ pageX: o.left + j.outerWidth() / 2,
+ pageY: o.top + j.outerHeight() / 2
+ };
+ }, convertOption = function (option, win, from) {
+ var page = /(\d+)[x ](\d+)/, client = /(\d+)X(\d+)/, relative = /([+-]\d+)[xX ]([+-]\d+)/, parts;
+ if (typeof option === 'string' && relative.test(option) && from) {
+ var cent = center(from);
+ parts = option.match(relative);
+ option = {
+ pageX: cent.pageX + parseInt(parts[1]),
+ pageY: cent.pageY + parseInt(parts[2])
+ };
+ }
+ if (typeof option === 'string' && page.test(option)) {
+ parts = option.match(page);
+ option = {
+ pageX: parseInt(parts[1]),
+ pageY: parseInt(parts[2])
+ };
+ }
+ if (typeof option === 'string' && client.test(option)) {
+ parts = option.match(client);
+ option = {
+ clientX: parseInt(parts[1]),
+ clientY: parseInt(parts[2])
+ };
+ }
+ if (typeof option === 'string') {
+ option = syn.jquery()(option, win.document)[0];
+ }
+ if (option.nodeName) {
+ option = center(option);
+ }
+ if (option.pageX != null) {
+ var off = syn.helpers.scrollOffset(win);
+ option = {
+ clientX: option.pageX - off.left,
+ clientY: option.pageY - off.top
+ };
+ }
+ return option;
+ }, adjust = function (from, to, win) {
+ if (from.clientY < 0) {
+ var off = syn.helpers.scrollOffset(win);
+ var top = off.top + from.clientY - 100, diff = top - off.top;
+ if (top > 0) {
+ } else {
+ top = 0;
+ diff = -off.top;
+ }
+ from.clientY = from.clientY - diff;
+ to.clientY = to.clientY - diff;
+ syn.helpers.scrollOffset(win, {
+ top: top,
+ left: off.left
+ });
+ }
+ };
+ syn.helpers.extend(syn.init.prototype, {
+ _move: function (from, options, callback) {
+ var win = syn.helpers.getWindow(from);
+ var sourceCoordinates = convertOption(options.from || from, win, from);
+ var destinationCoordinates = convertOption(options.to || options, win, from);
+ DragonDrop.html5drag = syn.support.pointerEvents;
+ if (options.adjust !== false) {
+ adjust(sourceCoordinates, destinationCoordinates, win);
+ }
+ startMove(win, sourceCoordinates, destinationCoordinates, options.duration || 500, callback);
+ },
+ _drag: function (from, options, callback) {
+ var win = syn.helpers.getWindow(from);
+ var sourceCoordinates = convertOption(options.from || from, win, from);
+ var destinationCoordinates = convertOption(options.to || options, win, from);
+ if (options.adjust !== false) {
+ adjust(sourceCoordinates, destinationCoordinates, win);
+ }
+ DragonDrop.html5drag = from.draggable;
+ if (DragonDrop.html5drag) {
+ DragonDrop.dragAndDrop(win, sourceCoordinates, destinationCoordinates, options.duration || 500, callback);
+ } else {
+ startDrag(win, sourceCoordinates, destinationCoordinates, options.duration || 500, callback);
+ }
+ }
+ });
+});
+/*syn@0.14.1#syn*/
+define('syn', [
+ 'require',
+ 'exports',
+ 'module',
+ 'syn/synthetic',
+ 'syn/mouse.support',
+ 'syn/browsers',
+ 'syn/key.support',
+ 'syn/drag'
+], function (require, exports, module) {
+ var syn = require('syn/synthetic');
+ require('syn/mouse.support');
+ require('syn/browsers');
+ require('syn/key.support');
+ require('syn/drag');
+ window.syn = syn;
+ module.exports = syn;
+});
+/*[global-shim-end]*/
+(function(global) { // jshint ignore:line
+ global._define = global.define;
+ global.define = global.define.orig;
+}
+)(typeof self == "object" && self.Object == Object ? self : window);
\ No newline at end of file
diff --git a/spec/db/migrate/issue2641_kb_color_change_limit_spec.rb b/spec/db/migrate/issue2641_kb_color_change_limit_spec.rb
new file mode 100644
index 000000000..70a96bd96
--- /dev/null
+++ b/spec/db/migrate/issue2641_kb_color_change_limit_spec.rb
@@ -0,0 +1,42 @@
+require 'rails_helper'
+
+RSpec.describe Issue2641KbColorChangeLimit, type: :db_migration do
+ subject(:knowledge_base) { create(:knowledge_base) }
+
+ before do
+ Setting.create_if_not_exists(
+ title: 'Kb active',
+ name: 'kb_active',
+ area: 'Kb::Core',
+ description: 'Defines if KB navbar button is enabled. Updated in KnowledgeBase callback.',
+ state: false,
+ preferences: {
+ prio: 1,
+ trigger: ['menu:render'],
+ authentication: true,
+ permission: ['admin.knowledge_base'],
+ },
+ frontend: true
+ )
+
+ Setting.create_if_not_exists(
+ title: 'Kb active publicly',
+ name: 'kb_active_publicly',
+ area: 'Kb::Core',
+ description: 'Defines if KB navbar button is enabled for users without KB permission. Updated in CanBePublished callback.',
+ state: false,
+ preferences: {
+ prio: 1,
+ trigger: ['menu:render'],
+ authentication: true,
+ permission: [],
+ },
+ frontend: true
+ )
+ end
+
+ it "doesn't change value for existing KB" do
+ expect { migrate }
+ .to not_change { knowledge_base.color_header }.and not_change { knowledge_base.color_highlight }
+ end
+end
diff --git a/spec/factories/knowledge_base/locale.rb b/spec/factories/knowledge_base/locale.rb
index 475010de4..102e90f1c 100644
--- a/spec/factories/knowledge_base/locale.rb
+++ b/spec/factories/knowledge_base/locale.rb
@@ -1,7 +1,7 @@
FactoryBot.define do
factory 'knowledge_base/locale', aliases: %i[knowledge_base_locale] do
knowledge_base { nil }
- system_locale { Locale.first }
+ system_locale { Locale.first || create(:locale) }
before :create do |kb_locale|
if kb_locale.knowledge_base.blank?
diff --git a/spec/models/knowledge_base_spec.rb b/spec/models/knowledge_base_spec.rb
index 080b46dbf..ba9f482cc 100644
--- a/spec/models/knowledge_base_spec.rb
+++ b/spec/models/knowledge_base_spec.rb
@@ -63,4 +63,14 @@ RSpec.describe KnowledgeBase, type: :model do
end
end
end
+
+ context 'acceptable colors' do
+ let(:allowed_values) { ['#aaa', '#ff0000', 'rgb(0,100,100)', 'hsl(0,100%,50%)'] }
+ let(:not_allowed_values) { ['aaa', '#aa', '#ff000', 'rgb(0,100,100', 'def(0,100%,0.5)', 'test'] }
+
+ it { is_expected.to allow_values(*allowed_values).for(:color_header) }
+ it { is_expected.to allow_values(*allowed_values).for(:color_highlight) }
+ it { is_expected.not_to allow_values(*not_allowed_values).for(:color_header) }
+ it { is_expected.not_to allow_values(*not_allowed_values).for(:color_highlight) }
+ end
end
diff --git a/spec/system/js/q_unit_spec.rb b/spec/system/js/q_unit_spec.rb
index c30117f86..1905ed2bf 100644
--- a/spec/system/js/q_unit_spec.rb
+++ b/spec/system/js/q_unit_spec.rb
@@ -82,6 +82,10 @@ RSpec.describe 'QUnit', type: :system, authenticated: false, set_up: true, webso
q_unit_tests('form_timer')
end
+ it 'Color' do
+ q_unit_tests('form_color')
+ end
+
it 'Extended' do
q_unit_tests('form_extended')
end