summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitriy Zaporozhets <dzaporozhets@gitlab.com>2015-04-22 19:57:40 +0000
committerDmitriy Zaporozhets <dzaporozhets@gitlab.com>2015-04-22 19:57:40 +0000
commita22f3139f3f3d37531cc5ed75fea3c9aa6f257ef (patch)
treeef1a5cb3f7bc4d46b9e0c665d95625e910bffed4
parentb5ee60c51263c981098d3268a8cb7105376651fa (diff)
parent3ae0b8853b6f1169e0341c091d8a4e1b13aac3ad (diff)
downloadgitlab-ce-a22f3139f3f3d37531cc5ed75fea3c9aa6f257ef.tar.gz
Merge branch 'rs-reply-hotkey' into 'master'
"Reply quoting selected text" shortcut/hotkey Adds the <kbd>r</kbd> hotkey for quoting selected text on Issuable forms. This MR also updates the jasmine gem and adds jasmine-rails to let us use the asset pipeline (and Coffeescript) in JS specs. See merge request !1775
-rw-r--r--CHANGELOG1
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock16
-rw-r--r--app/assets/javascripts/application.js.coffee2
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee4
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee48
-rw-r--r--app/assets/javascripts/shortcuts_issueable.coffee19
-rw-r--r--app/assets/javascripts/stat_graph_contributors.js.coffee4
-rw-r--r--app/assets/javascripts/stat_graph_contributors_graph.js.coffee4
-rw-r--r--app/views/help/_shortcuts.html.haml10
-rw-r--r--config/routes.rb1
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js.coffee83
-rw-r--r--spec/javascripts/stat_graph_contributors_graph_spec.js2
-rw-r--r--spec/javascripts/stat_graph_contributors_util_spec.js2
-rw-r--r--spec/javascripts/stat_graph_spec.js2
-rw-r--r--spec/javascripts/support/jasmine.yml84
-rw-r--r--spec/javascripts/support/jasmine_helper.rb6
-rw-r--r--vendor/assets/javascripts/jasmine-fixture.js433
18 files changed, 624 insertions, 100 deletions
diff --git a/CHANGELOG b/CHANGELOG
index fa8ecbd8c74..5bb6842f500 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,7 @@ v 7.11.0 (unreleased)
- Ignore invalid lines in .gitmodules
- Fix "Cannot move project" error message from popping up after a successful transfer (Stan Hu)
-
+ - Add "Reply quoting selected text" shortcut key (`r`)
-
-
-
diff --git a/Gemfile b/Gemfile
index 9662f15bf9e..c7a59a82d1a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -251,7 +251,8 @@ group :development, :test do
# PhantomJS driver for Capybara
gem 'poltergeist', '~> 1.5.1'
- gem 'jasmine', '2.0.2'
+ gem 'jasmine', '~> 2.2.0'
+ gem 'jasmine-rails'
gem "spring", '~> 1.3.1'
gem "spring-commands-rspec", '1.0.4'
diff --git a/Gemfile.lock b/Gemfile.lock
index 8679da009c2..d905ac927fc 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -287,12 +287,17 @@ GEM
i18n (0.7.0)
ice_cube (0.11.1)
ice_nine (0.10.0)
- jasmine (2.0.2)
- jasmine-core (~> 2.0.0)
+ jasmine (2.2.0)
+ jasmine-core (~> 2.2)
phantomjs
rack (>= 1.2.1)
rake
- jasmine-core (2.0.0)
+ jasmine-core (2.2.0)
+ jasmine-rails (0.10.8)
+ jasmine-core (>= 1.3, < 3.0)
+ phantomjs (>= 1.9)
+ railties (>= 3.2.0)
+ sprockets-rails
jquery-atwho-rails (0.3.3)
jquery-rails (3.1.0)
railties (>= 3.0, < 5.0)
@@ -387,7 +392,7 @@ GEM
parser (2.2.0.2)
ast (>= 1.1, < 3.0)
pg (0.15.1)
- phantomjs (1.9.2.0)
+ phantomjs (1.9.8.0)
poltergeist (1.5.1)
capybara (~> 2.1)
cliver (~> 0.3.1)
@@ -713,7 +718,8 @@ DEPENDENCIES
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
httparty
- jasmine (= 2.0.2)
+ jasmine (~> 2.2.0)
+ jasmine-rails
jquery-atwho-rails (~> 0.3.3)
jquery-rails
jquery-scrollto-rails
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 345af363775..020c103dbc5 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -38,7 +38,7 @@
#= require shortcuts
#= require shortcuts_navigation
#= require shortcuts_dashboard_navigation
-#= require shortcuts_issueable
+#= require shortcuts_issuable
#= require shortcuts_network
#= require cal-heatmap
#= require_tree .
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 330ebac6f75..9aee3b281f3 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -22,7 +22,7 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show'
new Issue()
- shortcut_handler = new ShortcutsIssueable()
+ shortcut_handler = new ShortcutsIssuable()
new ZenMode()
when 'projects:milestones:show'
new Milestone()
@@ -47,7 +47,7 @@ class Dispatcher
new IssuableForm($('.merge-request-form'))
when 'projects:merge_requests:show'
new Diff()
- shortcut_handler = new ShortcutsIssueable()
+ shortcut_handler = new ShortcutsIssuable()
new ZenMode()
when "projects:merge_requests:diffs"
new Diff()
diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
new file mode 100644
index 00000000000..6b534f29218
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_issuable.coffee
@@ -0,0 +1,48 @@
+#= require jquery
+#= require mousetrap
+
+#= require shortcuts_navigation
+
+class @ShortcutsIssuable extends ShortcutsNavigation
+ constructor: (isMergeRequest) ->
+ super()
+ Mousetrap.bind('a', ->
+ $('.js-assignee').select2('open')
+ return false
+ )
+ Mousetrap.bind('m', ->
+ $('.js-milestone').select2('open')
+ return false
+ )
+ Mousetrap.bind('r', =>
+ @replyWithSelectedText()
+ return false
+ )
+
+ if isMergeRequest
+ @enabledHelp.push('.hidden-shortcut.merge_requests')
+ else
+ @enabledHelp.push('.hidden-shortcut.issues')
+
+ replyWithSelectedText: ->
+ if window.getSelection
+ selected = window.getSelection().toString()
+ replyField = $('.js-main-target-form #note_note')
+
+ return if selected.trim() == ""
+
+ # Put a '>' character before each non-empty line in the selection
+ quote = _.map selected.split("\n"), (val) ->
+ "> #{val}\n" if val.trim() != ''
+
+ # If replyField already has some content, add a newline before our quote
+ separator = replyField.val().trim() != "" and "\n" or ''
+
+ replyField.val (_, current) ->
+ current + separator + quote.join('') + "\n"
+
+ # Trigger autosave for the added text
+ replyField.trigger('input')
+
+ # Focus the input field
+ replyField.focus()
diff --git a/app/assets/javascripts/shortcuts_issueable.coffee b/app/assets/javascripts/shortcuts_issueable.coffee
deleted file mode 100644
index b8dae71e037..00000000000
--- a/app/assets/javascripts/shortcuts_issueable.coffee
+++ /dev/null
@@ -1,19 +0,0 @@
-#= require shortcuts_navigation
-
-class @ShortcutsIssueable extends ShortcutsNavigation
- constructor: (isMergeRequest) ->
- super()
- Mousetrap.bind('a', ->
- $('.js-assignee').select2('open')
- return false
- )
- Mousetrap.bind('m', ->
- $('.js-milestone').select2('open')
- return false
- )
-
- if isMergeRequest
- @enabledHelp.push('.hidden-shortcut.merge_reuests')
- else
- @enabledHelp.push('.hidden-shortcut.issues')
-
diff --git a/app/assets/javascripts/stat_graph_contributors.js.coffee b/app/assets/javascripts/stat_graph_contributors.js.coffee
index 27f0fd31d50..ed12bdcef22 100644
--- a/app/assets/javascripts/stat_graph_contributors.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors.js.coffee
@@ -1,3 +1,7 @@
+#= require d3
+#= require jquery
+#= require stat_graph_contributors_util
+
class @ContributorsStatGraph
init: (log) ->
@parsed_log = ContributorsStatGraphUtil.parse_log(log)
diff --git a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
index 8b82d20c6c2..0e6fbdef3bc 100644
--- a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
@@ -1,3 +1,7 @@
+#= require d3
+#= require jquery
+#= require underscore
+
class @ContributorsGraph
MARGIN:
top: 20
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 7b21ca30d8c..ae072bacfb1 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -187,7 +187,11 @@
%td.shortcut
.key m
%td Change milestone
- %tbody{ class: 'hidden-shortcut merge_reuests', style: 'display:none' }
+ %tr
+ %td.shortcut
+ .key r
+ %td Reply (quoting selected text)
+ %tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' }
%tr
%th
%th Merge Requests
@@ -199,6 +203,10 @@
%td.shortcut
.key m
%td Change milestone
+ %tr
+ %td.shortcut
+ .key r
+ %td Reply (quoting selected text)
:javascript
diff --git a/config/routes.rb b/config/routes.rb
index 744a99feded..82b376aecbc 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -2,6 +2,7 @@ require 'sidekiq/web'
require 'api/api'
Gitlab::Application.routes.draw do
+ mount JasmineRails::Engine => '/specs' if defined?(JasmineRails)
use_doorkeeper do
controllers applications: 'oauth/applications',
authorized_applications: 'oauth/authorized_applications',
diff --git a/spec/javascripts/shortcuts_issuable_spec.js.coffee b/spec/javascripts/shortcuts_issuable_spec.js.coffee
new file mode 100644
index 00000000000..57dcc2161d3
--- /dev/null
+++ b/spec/javascripts/shortcuts_issuable_spec.js.coffee
@@ -0,0 +1,83 @@
+#= require jquery
+#= require jasmine-fixture
+
+#= require shortcuts_issuable
+
+describe 'ShortcutsIssuable', ->
+ beforeEach ->
+ @shortcut = new ShortcutsIssuable()
+
+ describe '#replyWithSelectedText', ->
+ # Stub window.getSelection to return the provided String.
+ stubSelection = (text) ->
+ window.getSelection = -> text
+
+ beforeEach ->
+ @selector = 'form.js-main-target-form textarea#note_note'
+ affix(@selector)
+
+ describe 'with empty selection', ->
+ it 'does nothing', ->
+ stubSelection('')
+ @shortcut.replyWithSelectedText()
+ expect($(@selector).val()).toBe('')
+
+ describe 'with any selection', ->
+ beforeEach ->
+ stubSelection('Selected text.')
+
+ it 'leaves existing input intact', ->
+ $(@selector).val('This text was already here.')
+ expect($(@selector).val()).toBe('This text was already here.')
+
+ @shortcut.replyWithSelectedText()
+ expect($(@selector).val()).
+ toBe("This text was already here.\n> Selected text.\n\n")
+
+ it 'triggers `input`', ->
+ triggered = false
+ $(@selector).on 'input', -> triggered = true
+ @shortcut.replyWithSelectedText()
+
+ expect(triggered).toBe(true)
+
+ it 'triggers `focus`', ->
+ focused = false
+ $(@selector).on 'focus', -> focused = true
+ @shortcut.replyWithSelectedText()
+
+ expect(focused).toBe(true)
+
+ describe 'with a one-line selection', ->
+ it 'quotes the selection', ->
+ stubSelection('This text has been selected.')
+
+ @shortcut.replyWithSelectedText()
+
+ expect($(@selector).val()).
+ toBe("> This text has been selected.\n\n")
+
+ describe 'with a multi-line selection', ->
+ it 'quotes the selected lines as a group', ->
+ stubSelection(
+ """
+ Selected line one.
+
+ Selected line two.
+ Selected line three.
+
+ """
+ )
+
+ @shortcut.replyWithSelectedText()
+
+ expect($(@selector).val()).
+ toBe(
+ """
+ > Selected line one.
+ > Selected line two.
+ > Selected line three.
+
+
+ """
+ )
diff --git a/spec/javascripts/stat_graph_contributors_graph_spec.js b/spec/javascripts/stat_graph_contributors_graph_spec.js
index 1090cb7f620..78d39f1b428 100644
--- a/spec/javascripts/stat_graph_contributors_graph_spec.js
+++ b/spec/javascripts/stat_graph_contributors_graph_spec.js
@@ -1,3 +1,5 @@
+//= require stat_graph_contributors_graph
+
describe("ContributorsGraph", function () {
describe("#set_x_domain", function () {
it("set the x_domain", function () {
diff --git a/spec/javascripts/stat_graph_contributors_util_spec.js b/spec/javascripts/stat_graph_contributors_util_spec.js
index 9c1b588861d..ee90892eb48 100644
--- a/spec/javascripts/stat_graph_contributors_util_spec.js
+++ b/spec/javascripts/stat_graph_contributors_util_spec.js
@@ -1,3 +1,5 @@
+//= require stat_graph_contributors_util
+
describe("ContributorsStatGraphUtil", function () {
describe("#parse_log", function () {
diff --git a/spec/javascripts/stat_graph_spec.js b/spec/javascripts/stat_graph_spec.js
index b589af34610..4c652910cd6 100644
--- a/spec/javascripts/stat_graph_spec.js
+++ b/spec/javascripts/stat_graph_spec.js
@@ -1,3 +1,5 @@
+//= require stat_graph
+
describe("StatGraph", function () {
describe("#get_log", function () {
diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml
index 9bfa261a356..f4b01c9f27a 100644
--- a/spec/javascripts/support/jasmine.yml
+++ b/spec/javascripts/support/jasmine.yml
@@ -1,76 +1,20 @@
-# src_files
+# path to parent directory of spec_files
+# relative path from Rails.root
#
-# Return an array of filepaths relative to src_dir to include before jasmine specs.
-# Default: []
+# Alternatively accept an array of directory to include external spec files
+# spec_dir:
+# - spec/javascripts
+# - ../engine/spec/javascripts
#
-# EXAMPLE:
-#
-# src_files:
-# - lib/source1.js
-# - lib/source2.js
-# - dist/**/*.js
-#
-src_files:
- - assets/application.js
-
-# stylesheets
-#
-# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
-# Default: []
-#
-# EXAMPLE:
-#
-# stylesheets:
-# - css/style.css
-# - stylesheets/*.css
-#
-stylesheets:
- - stylesheets/**/*.css
+# defaults to spec/javascripts
+spec_dir: spec/javascripts
-# helpers
-#
-# Return an array of filepaths relative to spec_dir to include before jasmine specs.
-# Default: ["helpers/**/*.js"]
-#
-# EXAMPLE:
-#
-# helpers:
-# - helpers/**/*.js
-#
+# list of file expressions to include as helpers into spec runner
+# relative path from spec_dir
helpers:
- - helpers/**/*.js
+ - "helpers/**/*.{js.coffee,js,coffee}"
-# spec_files
-#
-# Return an array of filepaths relative to spec_dir to include.
-# Default: ["**/*[sS]pec.js"]
-#
-# EXAMPLE:
-#
-# spec_files:
-# - **/*[sS]pec.js
-#
+# list of file expressions to include as specs into spec runner
+# relative path from spec_dir
spec_files:
- - '**/*[sS]pec.js'
-
-# src_dir
-#
-# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
-# Default: project root
-#
-# EXAMPLE:
-#
-# src_dir: public
-#
-src_dir:
-
-# spec_dir
-#
-# Spec directory path. Your spec_files must be returned relative to this path.
-# Default: spec/javascripts
-#
-# EXAMPLE:
-#
-# spec_dir: spec/javascripts
-#
-spec_dir: spec/javascripts
+ - "**/*[Ss]pec.{js.coffee,js,coffee}"
diff --git a/spec/javascripts/support/jasmine_helper.rb b/spec/javascripts/support/jasmine_helper.rb
index b4919802afe..4d73aec5a31 100644
--- a/spec/javascripts/support/jasmine_helper.rb
+++ b/spec/javascripts/support/jasmine_helper.rb
@@ -8,4 +8,8 @@
# config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] }
#end
#
-
+#Example: prevent PhantomJS auto install, uses PhantomJS already on your path.
+#Jasmine.configure do |config|
+# config.prevent_phantom_js_auto_install = true
+#end
+#
diff --git a/vendor/assets/javascripts/jasmine-fixture.js b/vendor/assets/javascripts/jasmine-fixture.js
new file mode 100644
index 00000000000..3815731fd2f
--- /dev/null
+++ b/vendor/assets/javascripts/jasmine-fixture.js
@@ -0,0 +1,433 @@
+/* jasmine-fixture - 1.2.2
+ * Makes injecting HTML snippets into the DOM easy & clean!
+ * https://github.com/searls/jasmine-fixture
+ */
+(function() {
+ var createHTMLBlock,
+ __slice = [].slice;
+
+ (function($) {
+ var ewwSideEffects, jasmineFixture, originalAffix, originalJasmineDotFixture, originalJasmineFixture, root, _, _ref;
+ root = this;
+ originalJasmineFixture = root.jasmineFixture;
+ originalJasmineDotFixture = (_ref = root.jasmine) != null ? _ref.fixture : void 0;
+ originalAffix = root.affix;
+ _ = function(list) {
+ return {
+ inject: function(iterator, memo) {
+ var item, _i, _len, _results;
+ _results = [];
+ for (_i = 0, _len = list.length; _i < _len; _i++) {
+ item = list[_i];
+ _results.push(memo = iterator(memo, item));
+ }
+ return _results;
+ }
+ };
+ };
+ root.jasmineFixture = function($) {
+ var $whatsTheRootOf, affix, create, jasmineFixture, noConflict;
+ affix = function(selectorOptions) {
+ return create.call(this, selectorOptions, true);
+ };
+ create = function(selectorOptions, attach) {
+ var $top;
+ $top = null;
+ _(selectorOptions.split(/[ ](?=[^\]]*?(?:\[|$))/)).inject(function($parent, elementSelector) {
+ var $el;
+ if (elementSelector === ">") {
+ return $parent;
+ }
+ $el = createHTMLBlock($, elementSelector);
+ if (attach || $top) {
+ $el.appendTo($parent);
+ }
+ $top || ($top = $el);
+ return $el;
+ }, $whatsTheRootOf(this));
+ return $top;
+ };
+ noConflict = function() {
+ var currentJasmineFixture, _ref1;
+ currentJasmineFixture = jasmine.fixture;
+ root.jasmineFixture = originalJasmineFixture;
+ if ((_ref1 = root.jasmine) != null) {
+ _ref1.fixture = originalJasmineDotFixture;
+ }
+ root.affix = originalAffix;
+ return currentJasmineFixture;
+ };
+ $whatsTheRootOf = function(that) {
+ if (that.jquery != null) {
+ return that;
+ } else if ($('#jasmine_content').length > 0) {
+ return $('#jasmine_content');
+ } else {
+ return $('<div id="jasmine_content"></div>').appendTo('body');
+ }
+ };
+ jasmineFixture = {
+ affix: affix,
+ create: create,
+ noConflict: noConflict
+ };
+ ewwSideEffects(jasmineFixture);
+ return jasmineFixture;
+ };
+ ewwSideEffects = function(jasmineFixture) {
+ var _ref1;
+ if ((_ref1 = root.jasmine) != null) {
+ _ref1.fixture = jasmineFixture;
+ }
+ $.fn.affix = root.affix = jasmineFixture.affix;
+ return afterEach(function() {
+ return $('#jasmine_content').remove();
+ });
+ };
+ if ($) {
+ return jasmineFixture = root.jasmineFixture($);
+ } else {
+ return root.affix = function() {
+ var nowJQueryExists;
+ nowJQueryExists = window.jQuery || window.$;
+ if (nowJQueryExists != null) {
+ jasmineFixture = root.jasmineFixture(nowJQueryExists);
+ return affix.call.apply(affix, [this].concat(__slice.call(arguments)));
+ } else {
+ throw new Error("jasmine-fixture requires jQuery to be defined at window.jQuery or window.$");
+ }
+ };
+ }
+ })(window.jQuery || window.$);
+
+ createHTMLBlock = (function() {
+ var bindData, bindEvents, parseAttributes, parseClasses, parseContents, parseEnclosure, parseReferences, parseVariableScope, regAttr, regAttrDfn, regAttrs, regCBrace, regClass, regClasses, regData, regDatas, regEvent, regEvents, regExclamation, regId, regReference, regTag, regTagNotContent, regZenTagDfn;
+ createHTMLBlock = function($, ZenObject, data, functions, indexes) {
+ var ZenCode, arr, block, blockAttrs, blockClasses, blockHTML, blockId, blockTag, blocks, el, el2, els, forScope, indexName, inner, len, obj, origZenCode, paren, result, ret, zc, zo;
+ if ($.isPlainObject(ZenObject)) {
+ ZenCode = ZenObject.main;
+ } else {
+ ZenCode = ZenObject;
+ ZenObject = {
+ main: ZenCode
+ };
+ }
+ origZenCode = ZenCode;
+ if (indexes === undefined) {
+ indexes = {};
+ }
+ if (ZenCode.charAt(0) === "!" || $.isArray(data)) {
+ if ($.isArray(data)) {
+ forScope = ZenCode;
+ } else {
+ obj = parseEnclosure(ZenCode, "!");
+ obj = obj.substring(obj.indexOf(":") + 1, obj.length - 1);
+ forScope = parseVariableScope(ZenCode);
+ }
+ while (forScope.charAt(0) === "@") {
+ forScope = parseVariableScope("!for:!" + parseReferences(forScope, ZenObject));
+ }
+ zo = ZenObject;
+ zo.main = forScope;
+ el = $();
+ if (ZenCode.substring(0, 5) === "!for:" || $.isArray(data)) {
+ if (!$.isArray(data) && obj.indexOf(":") > 0) {
+ indexName = obj.substring(0, obj.indexOf(":"));
+ obj = obj.substr(obj.indexOf(":") + 1);
+ }
+ arr = ($.isArray(data) ? data : data[obj]);
+ zc = zo.main;
+ if ($.isArray(arr) || $.isPlainObject(arr)) {
+ $.map(arr, function(value, index) {
+ var next;
+ zo.main = zc;
+ if (indexName !== undefined) {
+ indexes[indexName] = index;
+ }
+ if (!$.isPlainObject(value)) {
+ value = {
+ value: value
+ };
+ }
+ next = createHTMLBlock($, zo, value, functions, indexes);
+ if (el.length !== 0) {
+ return $.each(next, function(index, value) {
+ return el.push(value);
+ });
+ }
+ });
+ }
+ if (!$.isArray(data)) {
+ ZenCode = ZenCode.substr(obj.length + 6 + forScope.length);
+ } else {
+ ZenCode = "";
+ }
+ } else if (ZenCode.substring(0, 4) === "!if:") {
+ result = parseContents("!" + obj + "!", data, indexes);
+ if (result !== "undefined" || result !== "false" || result !== "") {
+ el = createHTMLBlock($, zo, data, functions, indexes);
+ }
+ ZenCode = ZenCode.substr(obj.length + 5 + forScope.length);
+ }
+ ZenObject.main = ZenCode;
+ } else if (ZenCode.charAt(0) === "(") {
+ paren = parseEnclosure(ZenCode, "(", ")");
+ inner = paren.substring(1, paren.length - 1);
+ ZenCode = ZenCode.substr(paren.length);
+ zo = ZenObject;
+ zo.main = inner;
+ el = createHTMLBlock($, zo, data, functions, indexes);
+ } else {
+ blocks = ZenCode.match(regZenTagDfn);
+ block = blocks[0];
+ if (block.length === 0) {
+ return "";
+ }
+ if (block.indexOf("@") >= 0) {
+ ZenCode = parseReferences(ZenCode, ZenObject);
+ zo = ZenObject;
+ zo.main = ZenCode;
+ return createHTMLBlock($, zo, data, functions, indexes);
+ }
+ block = parseContents(block, data, indexes);
+ blockClasses = parseClasses($, block);
+ if (regId.test(block)) {
+ blockId = regId.exec(block)[1];
+ }
+ blockAttrs = parseAttributes(block, data);
+ blockTag = (block.charAt(0) === "{" ? "span" : "div");
+ if (ZenCode.charAt(0) !== "#" && ZenCode.charAt(0) !== "." && ZenCode.charAt(0) !== "{") {
+ blockTag = regTag.exec(block)[1];
+ }
+ if (block.search(regCBrace) !== -1) {
+ blockHTML = block.match(regCBrace)[1];
+ }
+ blockAttrs = $.extend(blockAttrs, {
+ id: blockId,
+ "class": blockClasses,
+ html: blockHTML
+ });
+ el = $("<" + blockTag + ">", blockAttrs);
+ el.attr(blockAttrs);
+ el = bindEvents(block, el, functions);
+ el = bindData(block, el, data);
+ ZenCode = ZenCode.substr(blocks[0].length);
+ ZenObject.main = ZenCode;
+ }
+ if (ZenCode.length > 0) {
+ if (ZenCode.charAt(0) === ">") {
+ if (ZenCode.charAt(1) === "(") {
+ zc = parseEnclosure(ZenCode.substr(1), "(", ")");
+ ZenCode = ZenCode.substr(zc.length + 1);
+ } else if (ZenCode.charAt(1) === "!") {
+ obj = parseEnclosure(ZenCode.substr(1), "!");
+ forScope = parseVariableScope(ZenCode.substr(1));
+ zc = obj + forScope;
+ ZenCode = ZenCode.substr(zc.length + 1);
+ } else {
+ len = Math.max(ZenCode.indexOf("+"), ZenCode.length);
+ zc = ZenCode.substring(1, len);
+ ZenCode = ZenCode.substr(len);
+ }
+ zo = ZenObject;
+ zo.main = zc;
+ els = $(createHTMLBlock($, zo, data, functions, indexes));
+ els.appendTo(el);
+ }
+ if (ZenCode.charAt(0) === "+") {
+ zo = ZenObject;
+ zo.main = ZenCode.substr(1);
+ el2 = createHTMLBlock($, zo, data, functions, indexes);
+ $.each(el2, function(index, value) {
+ return el.push(value);
+ });
+ }
+ }
+ ret = el;
+ return ret;
+ };
+ bindData = function(ZenCode, el, data) {
+ var datas, i, split;
+ if (ZenCode.search(regDatas) === 0) {
+ return el;
+ }
+ datas = ZenCode.match(regDatas);
+ if (datas === null) {
+ return el;
+ }
+ i = 0;
+ while (i < datas.length) {
+ split = regData.exec(datas[i]);
+ if (split[3] === undefined) {
+ $(el).data(split[1], data[split[1]]);
+ } else {
+ $(el).data(split[1], data[split[3]]);
+ }
+ i++;
+ }
+ return el;
+ };
+ bindEvents = function(ZenCode, el, functions) {
+ var bindings, fn, i, split;
+ if (ZenCode.search(regEvents) === 0) {
+ return el;
+ }
+ bindings = ZenCode.match(regEvents);
+ if (bindings === null) {
+ return el;
+ }
+ i = 0;
+ while (i < bindings.length) {
+ split = regEvent.exec(bindings[i]);
+ if (split[2] === undefined) {
+ fn = functions[split[1]];
+ } else {
+ fn = functions[split[2]];
+ }
+ $(el).bind(split[1], fn);
+ i++;
+ }
+ return el;
+ };
+ parseAttributes = function(ZenBlock, data) {
+ var attrStrs, attrs, i, parts;
+ if (ZenBlock.search(regAttrDfn) === -1) {
+ return undefined;
+ }
+ attrStrs = ZenBlock.match(regAttrDfn);
+ attrs = {};
+ i = 0;
+ while (i < attrStrs.length) {
+ parts = regAttr.exec(attrStrs[i]);
+ attrs[parts[1]] = "";
+ if (parts[3] !== undefined) {
+ attrs[parts[1]] = parseContents(parts[3], data);
+ }
+ i++;
+ }
+ return attrs;
+ };
+ parseClasses = function($, ZenBlock) {
+ var classes, clsString, i;
+ ZenBlock = ZenBlock.match(regTagNotContent)[0];
+ if (ZenBlock.search(regClasses) === -1) {
+ return undefined;
+ }
+ classes = ZenBlock.match(regClasses);
+ clsString = "";
+ i = 0;
+ while (i < classes.length) {
+ clsString += " " + regClass.exec(classes[i])[1];
+ i++;
+ }
+ return $.trim(clsString);
+ };
+ parseContents = function(ZenBlock, data, indexes) {
+ var html;
+ if (indexes === undefined) {
+ indexes = {};
+ }
+ html = ZenBlock;
+ if (data === undefined) {
+ return html;
+ }
+ while (regExclamation.test(html)) {
+ html = html.replace(regExclamation, function(str, str2) {
+ var begChar, fn, val;
+ begChar = "";
+ if (str.indexOf("!for:") > 0 || str.indexOf("!if:") > 0) {
+ return str;
+ }
+ if (str.charAt(0) !== "!") {
+ begChar = str.charAt(0);
+ str = str.substring(2, str.length - 1);
+ }
+ fn = new Function("data", "indexes", "var r=undefined;" + "with(data){try{r=" + str + ";}catch(e){}}" + "with(indexes){try{if(r===undefined)r=" + str + ";}catch(e){}}" + "return r;");
+ val = unescape(fn(data, indexes));
+ return begChar + val;
+ });
+ }
+ html = html.replace(/\\./g, function(str) {
+ return str.charAt(1);
+ });
+ return unescape(html);
+ };
+ parseEnclosure = function(ZenCode, open, close, count) {
+ var index, ret;
+ if (close === undefined) {
+ close = open;
+ }
+ index = 1;
+ if (count === undefined) {
+ count = (ZenCode.charAt(0) === open ? 1 : 0);
+ }
+ if (count === 0) {
+ return;
+ }
+ while (count > 0 && index < ZenCode.length) {
+ if (ZenCode.charAt(index) === close && ZenCode.charAt(index - 1) !== "\\") {
+ count--;
+ } else {
+ if (ZenCode.charAt(index) === open && ZenCode.charAt(index - 1) !== "\\") {
+ count++;
+ }
+ }
+ index++;
+ }
+ ret = ZenCode.substring(0, index);
+ return ret;
+ };
+ parseReferences = function(ZenCode, ZenObject) {
+ ZenCode = ZenCode.replace(regReference, function(str) {
+ var fn;
+ str = str.substr(1);
+ fn = new Function("objs", "var r=\"\";" + "with(objs){try{" + "r=" + str + ";" + "}catch(e){}}" + "return r;");
+ return fn(ZenObject, parseReferences);
+ });
+ return ZenCode;
+ };
+ parseVariableScope = function(ZenCode) {
+ var forCode, rest, tag;
+ if (ZenCode.substring(0, 5) !== "!for:" && ZenCode.substring(0, 4) !== "!if:") {
+ return undefined;
+ }
+ forCode = parseEnclosure(ZenCode, "!");
+ ZenCode = ZenCode.substr(forCode.length);
+ if (ZenCode.charAt(0) === "(") {
+ return parseEnclosure(ZenCode, "(", ")");
+ }
+ tag = ZenCode.match(regZenTagDfn)[0];
+ ZenCode = ZenCode.substr(tag.length);
+ if (ZenCode.length === 0 || ZenCode.charAt(0) === "+") {
+ return tag;
+ } else if (ZenCode.charAt(0) === ">") {
+ rest = "";
+ rest = parseEnclosure(ZenCode.substr(1), "(", ")", 1);
+ return tag + ">" + rest;
+ }
+ return undefined;
+ };
+ regZenTagDfn = /([#\.\@]?[\w-]+|\[([\w-!?=:"']+(="([^"]|\\")+")? {0,})+\]|\~[\w$]+=[\w$]+|&[\w$]+(=[\w$]+)?|[#\.\@]?!([^!]|\\!)+!){0,}(\{([^\}]|\\\})+\})?/i;
+ regTag = /(\w+)/i;
+ regId = /(?:^|\b)#([\w-!]+)/i;
+ regTagNotContent = /((([#\.]?[\w-]+)?(\[([\w!]+(="([^"]|\\")+")? {0,})+\])?)+)/i;
+ /*
+ See lookahead syntax (?!) at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
+ */
+
+ regClasses = /(\.[\w-]+)(?!["\w])/g;
+ regClass = /\.([\w-]+)/i;
+ regReference = /(@[\w$_][\w$_\d]+)/i;
+ regAttrDfn = /(\[([\w-!]+(="?([^"]|\\")+"?)? {0,})+\])/ig;
+ regAttrs = /([\w-!]+(="([^"]|\\")+")?)/g;
+ regAttr = /([\w-!]+)(="?((([\w]+(\[.*?\])+)|[^"\]]|\\")+)"?)?/i;
+ regCBrace = /\{(([^\}]|\\\})+)\}/i;
+ regExclamation = /(?:([^\\]|^))!([^!]|\\!)+!/g;
+ regEvents = /\~[\w$]+(=[\w$]+)?/g;
+ regEvent = /\~([\w$]+)=([\w$]+)/i;
+ regDatas = /&[\w$]+(=[\w$]+)?/g;
+ regData = /&([\w$]+)(=([\w$]+))?/i;
+ return createHTMLBlock;
+ })();
+
+}).call(this);