gulp minify & concat JS

This commit is contained in:
nitely 2015-11-08 18:09:20 -03:00
parent b34194981a
commit 84e875edb9
49 changed files with 783 additions and 27 deletions

View File

@ -7,6 +7,7 @@ var Server = require('karma').Server;
var minifyCss = require('gulp-minify-css'); var minifyCss = require('gulp-minify-css');
var rename = require("gulp-rename"); var rename = require("gulp-rename");
var concat = require('gulp-concat'); var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var assetsPath = 'spirit/core/static/spirit/'; var assetsPath = 'spirit/core/static/spirit/';
var cssPath = assetsPath + 'stylesheets/'; var cssPath = assetsPath + 'stylesheets/';
@ -29,11 +30,9 @@ gulp.task('_css-minify', ['_sass'], function () {
gulp.task('_css-concat', ['_css-minify'], function() { gulp.task('_css-concat', ['_css-minify'], function() {
var path = cssPath + 'vendors/' var path = cssPath + 'vendors/';
return gulp.src([ return gulp.src([
path + 'font-awesome.min.css', path + '*.min.css',
path + 'github.min.css',
path + 'jquery.atwho.min.css',
cssPath + 'styles.min.css', cssPath + 'styles.min.css',
]) ])
.pipe(concat('styles.all.min.css')) .pipe(concat('styles.all.min.css'))
@ -57,7 +56,7 @@ var coffeeTask = function coffeeTask(opts) {
gulp.task('coffee', function(done) { gulp.task('coffee', function(done) {
coffeeTask({ coffeeTask({
srcPath: jsPath + 'src/*.coffee', srcPath: jsPath + 'src/*.coffee',
destPath: jsPath, destPath: jsPath + 'js',
}).on('end', done) }).on('end', done)
}); });
@ -81,3 +80,36 @@ gulp.task('_test', ['coffee', '_coffee-test'], function (done) {
gulp.task('test', ['coffee', '_coffee-test', '_test']); gulp.task('test', ['coffee', '_coffee-test', '_test']);
gulp.task('_js-uglify', ['coffee'], function() {
var path = jsPath + 'js/';
return gulp.src([
'!' + path + '*.min.js', // Exclude
path + '*.js'
])
.pipe(uglify({mangle: false}))
.pipe(rename({suffix: ".min"}))
.pipe(gulp.dest(path));
});
gulp.task('_js-concat', ['_js-uglify'], function() {
var path = jsPath + 'js/';
var pathVendors = jsPath + 'vendors/';
return gulp.src([
pathVendors + 'jquery.min.js',
pathVendors + 'atwho/jquery.caret.min.js',
pathVendors + 'atwho/jquery.atwho.min.js',
pathVendors + '**/*.js',
path + 'util.min.js',
path + 'tab.min.js',
path + 'editor_image_upload.min.js',
path + '*.min.js'
])
.pipe(concat('all.min.js'))
.pipe(gulp.dest(jsPath));
});
gulp.task('js', ['_js-concat']);

View File

@ -8,6 +8,7 @@
"gulp-rename": "^1.2.2", "gulp-rename": "^1.2.2",
"gulp-ruby-sass": "^1.4.0", "gulp-ruby-sass": "^1.4.0",
"gulp-sourcemaps": "^1.6.0", "gulp-sourcemaps": "^1.6.0",
"gulp-uglify": "^1.4.2",
"gulp-util": "^3.0.6", "gulp-util": "^3.0.6",
"karma": "^0.13.10", "karma": "^0.13.10",
"karma-cli": "^0.1.1", "karma-cli": "^0.1.1",

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
(function(){var $,Bookmark,Mark,bind=function(fn,me){return function(){return fn.apply(me,arguments)}};$=jQuery,Mark=function(){function Mark(){this.isSending=!1,this.commentNumber=this._getCommentNumber()}return Mark.prototype._getCommentNumber=function(){var commentNumber;return commentNumber=window.location.hash.split("#c")[1],commentNumber=parseInt(commentNumber,10),isNaN(commentNumber)?commentNumber=0:commentNumber-=1,commentNumber},Mark}(),Bookmark=function(){function Bookmark(el,mark,options){this.sendCommentNumber=bind(this.sendCommentNumber,this),this.onWaypoint=bind(this.onWaypoint,this),this.el=$(el),this.mark=mark,this.options=$.extend({},this.defaults,options),this.setUp()}return Bookmark.prototype.defaults={csrfToken:"csrf_token",target:"target url"},Bookmark.prototype.setUp=function(){return this.el.waypoint(this.onWaypoint,{offset:"100%"})},Bookmark.prototype.onWaypoint=function(){var newCommentNumber;newCommentNumber=this.el.data("number"),newCommentNumber>this.mark.commentNumber&&(this.mark.commentNumber=newCommentNumber,this.sendCommentNumber())},Bookmark.prototype.sendCommentNumber=function(){var post,sentCommentNumber;if(!this.mark.isSending)return this.mark.isSending=!0,sentCommentNumber=this.mark.commentNumber,post=$.post(this.options.target,{csrfmiddlewaretoken:this.options.csrfToken,comment_number:this.mark.commentNumber}),post.always(function(_this){return function(){return _this.mark.isSending=!1,_this.mark.commentNumber>sentCommentNumber?_this.sendCommentNumber():void 0}}(this))},Bookmark}(),$.fn.extend({bookmark:function(options){var mark;return mark=new Mark,this.each(function(){return $(this).data("plugin_bookmark")?void 0:$(this).data("plugin_bookmark",new Bookmark(this,mark,options))})}}),$.fn.bookmark.Bookmark=Bookmark,$.fn.bookmark.Mark=Mark}).call(this);

View File

@ -0,0 +1 @@
(function(){var $,Editor,bind=function(fn,me){return function(){return fn.apply(me,arguments)}};$=jQuery,Editor=function(){function Editor(el,options){this.togglePreview=bind(this.togglePreview,this),this.addPoll=bind(this.addPoll,this),this.addImage=bind(this.addImage,this),this.addUrl=bind(this.addUrl,this),this.addList=bind(this.addList,this),this.addItalic=bind(this.addItalic,this),this.addBold=bind(this.addBold,this),this.wrapSelection=bind(this.wrapSelection,this),this.el=$(el),this.options=$.extend({},this.defaults,options),this.pollCounter=1,this.setUp()}return Editor.prototype.defaults={boldedText:"bolded text",italicisedText:"italicised text",listItemText:"list item",linkText:"link text",linkUrlText:"link url",imageText:"image text",imageUrlText:"image url",pollTitleText:"Title",pollChoiceText:"Description"},Editor.prototype.setUp=function(){return $(".js-box-bold").on("click",this.addBold),$(".js-box-italic").on("click",this.addItalic),$(".js-box-list").on("click",this.addList),$(".js-box-url").on("click",this.addUrl),$(".js-box-image").on("click",this.addImage),$(".js-box-poll").on("click",this.addPoll),$(".js-box-preview").on("click",this.togglePreview)},Editor.prototype.wrapSelection=function(preTxt,postTxt,defaultTxt){var postSelection,preSelection,selection;return preSelection=this.el.val().substring(0,this.el[0].selectionStart),selection=this.el.val().substring(this.el[0].selectionStart,this.el[0].selectionEnd),postSelection=this.el.val().substring(this.el[0].selectionEnd),selection||(selection=defaultTxt),this.el.val(preSelection+preTxt+selection+postTxt+postSelection)},Editor.prototype.addBold=function(){return this.wrapSelection("**","**",this.options.boldedText),!1},Editor.prototype.addItalic=function(){return this.wrapSelection("*","*",this.options.italicisedText),!1},Editor.prototype.addList=function(){return this.wrapSelection("\n* ","",this.options.listItemText),!1},Editor.prototype.addUrl=function(){return this.wrapSelection("[","]("+this.options.linkUrlText+")",this.options.linkText),!1},Editor.prototype.addImage=function(){return this.wrapSelection("![","]("+this.options.imageUrlText+")",this.options.imageText),!1},Editor.prototype.addPoll=function(){var poll;return poll="\n\n[poll name="+this.pollCounter+"]\n"+("# "+this.options.pollTitleText+"\n")+("1. "+this.options.pollChoiceText+"\n")+("2. "+this.options.pollChoiceText+"\n")+"[/poll]\n",this.wrapSelection("",poll,""),this.pollCounter++,!1},Editor.prototype.togglePreview=function(){var $preview;return $preview=$(".js-box-preview-content"),this.el.toggle(),$preview.toggle(),$preview.html(marked(this.el.val())),!1},Editor}(),$.fn.extend({editor:function(options){return this.each(function(){return $(this).data("plugin_editor")?void 0:$(this).data("plugin_editor",new Editor(this,options))})}}),$.fn.editor.Editor=Editor}).call(this);

View File

@ -0,0 +1 @@
(function(){var $,EditorImageUpload,bind=function(fn,me){return function(){return fn.apply(me,arguments)}};$=jQuery,EditorImageUpload=function(){function EditorImageUpload(el,options){this.openFileDialog=bind(this.openFileDialog,this),this.textReplace=bind(this.textReplace,this),this.addStatusError=bind(this.addStatusError,this),this.addError=bind(this.addError,this),this.addImage=bind(this.addImage,this),this.buildFormData=bind(this.buildFormData,this),this.addPlaceholder=bind(this.addPlaceholder,this),this.sendFile=bind(this.sendFile,this),this.el=$(el),this.options=$.extend({},this.defaults,options),this.inputFile=$("<input/>",{type:"file",accept:"image/*"}),this.setUp()}return EditorImageUpload.prototype.defaults={csrfToken:"csrf_token",target:"target url",placeholderText:"uploading {image_name}"},EditorImageUpload.prototype.setUp=function(){var $boxImage;if(null!=window.FormData)return this.inputFile.on("change",this.sendFile),$boxImage=$(".js-box-image"),$boxImage.on("click",this.openFileDialog),$boxImage.on("click",this.stopClick)},EditorImageUpload.prototype.sendFile=function(){var file,formData,placeholder,post;file=this.inputFile.get(0).files[0],placeholder=this.addPlaceholder(file),formData=this.buildFormData(file),post=$.ajax({url:this.options.target,data:formData,processData:!1,contentType:!1,type:"POST"}),post.done(function(_this){return function(data){return"url"in data?_this.addImage(data,file,placeholder):_this.addError(data,placeholder)}}(this)),post.fail(function(_this){return function(jqxhr,textStatus,error){return _this.addStatusError(textStatus,error,placeholder)}}(this))},EditorImageUpload.prototype.addPlaceholder=function(file){var placeholder;return placeholder=$.format("!["+this.options.placeholderText+"]()",{image_name:file.name}),this.el.val(this.el.val()+placeholder),placeholder},EditorImageUpload.prototype.buildFormData=function(file){var formData;return formData=new FormData,formData.append("csrfmiddlewaretoken",this.options.csrfToken),formData.append("image",file),formData},EditorImageUpload.prototype.addImage=function(data,file,placeholder){var imageTag;return imageTag=$.format("![{name}]({url})",{name:file.name,url:data.url}),this.textReplace(placeholder,imageTag)},EditorImageUpload.prototype.addError=function(data,placeholder){var error;return error=JSON.stringify(data),this.textReplace(placeholder,"!["+error+"]()")},EditorImageUpload.prototype.addStatusError=function(textStatus,error,placeholder){var errorTag;return errorTag=$.format("![error: {code} {error}]()",{code:textStatus,error:error}),this.textReplace(placeholder,errorTag)},EditorImageUpload.prototype.textReplace=function(find,replace){this.el.val(this.el.val().replace(find,replace))},EditorImageUpload.prototype.openFileDialog=function(){this.inputFile.trigger("click")},EditorImageUpload.prototype.stopClick=function(e){e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation()},EditorImageUpload}(),$.fn.extend({editor_image_upload:function(options){return this.each(function(){return $(this).data("plugin_editor_image_upload")?void 0:$(this).data("plugin_editor_image_upload",new EditorImageUpload(this,options))})}}),$.fn.editor_image_upload.EditorImageUpload=EditorImageUpload}).call(this);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,371 @@
(function() {
var Match, calculate_operations, consecutive_where, create_index, diff, find_match, find_matching_blocks, html_to_tokens, is_end_of_tag, is_start_of_tag, is_tag, is_whitespace, isnt_tag, op_map, recursively_find_matching_blocks, render_operations, wrap;
is_end_of_tag = function(char) {
return char === '>';
};
is_start_of_tag = function(char) {
return char === '<';
};
is_whitespace = function(char) {
return /^\s+$/.test(char);
};
is_tag = function(token) {
return /^\s*<[^>]+>\s*$/.test(token);
};
isnt_tag = function(token) {
return !is_tag(token);
};
Match = (function() {
function Match(start_in_before1, start_in_after1, length1) {
this.start_in_before = start_in_before1;
this.start_in_after = start_in_after1;
this.length = length1;
this.end_in_before = (this.start_in_before + this.length) - 1;
this.end_in_after = (this.start_in_after + this.length) - 1;
}
return Match;
})();
html_to_tokens = function(html) {
var char, current_word, i, len, mode, words;
mode = 'char';
current_word = '';
words = [];
for (i = 0, len = html.length; i < len; i++) {
char = html[i];
switch (mode) {
case 'tag':
if (is_end_of_tag(char)) {
current_word += '>';
words.push(current_word);
current_word = '';
if (is_whitespace(char)) {
mode = 'whitespace';
} else {
mode = 'char';
}
} else {
current_word += char;
}
break;
case 'char':
if (is_start_of_tag(char)) {
if (current_word) {
words.push(current_word);
}
current_word = '<';
mode = 'tag';
} else if (/\s/.test(char)) {
if (current_word) {
words.push(current_word);
}
current_word = char;
mode = 'whitespace';
} else if (/[\w\#@]+/i.test(char)) {
current_word += char;
} else {
if (current_word) {
words.push(current_word);
}
current_word = char;
}
break;
case 'whitespace':
if (is_start_of_tag(char)) {
if (current_word) {
words.push(current_word);
}
current_word = '<';
mode = 'tag';
} else if (is_whitespace(char)) {
current_word += char;
} else {
if (current_word) {
words.push(current_word);
}
current_word = char;
mode = 'char';
}
break;
default:
throw new Error("Unknown mode " + mode);
}
}
if (current_word) {
words.push(current_word);
}
return words;
};
find_match = function(before_tokens, after_tokens, index_of_before_locations_in_after_tokens, start_in_before, end_in_before, start_in_after, end_in_after) {
var best_match_in_after, best_match_in_before, best_match_length, i, index_in_after, index_in_before, j, len, locations_in_after, looking_for, match, match_length_at, new_match_length, new_match_length_at, ref, ref1;
best_match_in_before = start_in_before;
best_match_in_after = start_in_after;
best_match_length = 0;
match_length_at = {};
for (index_in_before = i = ref = start_in_before, ref1 = end_in_before; ref <= ref1 ? i < ref1 : i > ref1; index_in_before = ref <= ref1 ? ++i : --i) {
new_match_length_at = {};
looking_for = before_tokens[index_in_before];
locations_in_after = index_of_before_locations_in_after_tokens[looking_for];
for (j = 0, len = locations_in_after.length; j < len; j++) {
index_in_after = locations_in_after[j];
if (index_in_after < start_in_after) {
continue;
}
if (index_in_after >= end_in_after) {
break;
}
if (match_length_at[index_in_after - 1] == null) {
match_length_at[index_in_after - 1] = 0;
}
new_match_length = match_length_at[index_in_after - 1] + 1;
new_match_length_at[index_in_after] = new_match_length;
if (new_match_length > best_match_length) {
best_match_in_before = index_in_before - new_match_length + 1;
best_match_in_after = index_in_after - new_match_length + 1;
best_match_length = new_match_length;
}
}
match_length_at = new_match_length_at;
}
if (best_match_length !== 0) {
match = new Match(best_match_in_before, best_match_in_after, best_match_length);
}
return match;
};
recursively_find_matching_blocks = function(before_tokens, after_tokens, index_of_before_locations_in_after_tokens, start_in_before, end_in_before, start_in_after, end_in_after, matching_blocks) {
var match;
match = find_match(before_tokens, after_tokens, index_of_before_locations_in_after_tokens, start_in_before, end_in_before, start_in_after, end_in_after);
if (match != null) {
if (start_in_before < match.start_in_before && start_in_after < match.start_in_after) {
recursively_find_matching_blocks(before_tokens, after_tokens, index_of_before_locations_in_after_tokens, start_in_before, match.start_in_before, start_in_after, match.start_in_after, matching_blocks);
}
matching_blocks.push(match);
if (match.end_in_before <= end_in_before && match.end_in_after <= end_in_after) {
recursively_find_matching_blocks(before_tokens, after_tokens, index_of_before_locations_in_after_tokens, match.end_in_before + 1, end_in_before, match.end_in_after + 1, end_in_after, matching_blocks);
}
}
return matching_blocks;
};
create_index = function(p) {
var i, idx, index, len, ref, token;
if (p.find_these == null) {
throw new Error('params must have find_these key');
}
if (p.in_these == null) {
throw new Error('params must have in_these key');
}
index = {};
ref = p.find_these;
for (i = 0, len = ref.length; i < len; i++) {
token = ref[i];
index[token] = [];
idx = p.in_these.indexOf(token);
while (idx !== -1) {
index[token].push(idx);
idx = p.in_these.indexOf(token, idx + 1);
}
}
return index;
};
find_matching_blocks = function(before_tokens, after_tokens) {
var index_of_before_locations_in_after_tokens, matching_blocks;
matching_blocks = [];
index_of_before_locations_in_after_tokens = create_index({
find_these: before_tokens,
in_these: after_tokens
});
return recursively_find_matching_blocks(before_tokens, after_tokens, index_of_before_locations_in_after_tokens, 0, before_tokens.length, 0, after_tokens.length, matching_blocks);
};
calculate_operations = function(before_tokens, after_tokens) {
var action_map, action_up_to_match_positions, i, index, is_single_whitespace, j, last_op, len, len1, match, match_starts_at_current_position_in_after, match_starts_at_current_position_in_before, matches, op, operations, position_in_after, position_in_before, post_processed;
if (before_tokens == null) {
throw new Error('before_tokens?');
}
if (after_tokens == null) {
throw new Error('after_tokens?');
}
position_in_before = position_in_after = 0;
operations = [];
action_map = {
'false,false': 'replace',
'true,false': 'insert',
'false,true': 'delete',
'true,true': 'none'
};
matches = find_matching_blocks(before_tokens, after_tokens);
matches.push(new Match(before_tokens.length, after_tokens.length, 0));
for (index = i = 0, len = matches.length; i < len; index = ++i) {
match = matches[index];
match_starts_at_current_position_in_before = position_in_before === match.start_in_before;
match_starts_at_current_position_in_after = position_in_after === match.start_in_after;
action_up_to_match_positions = action_map[[match_starts_at_current_position_in_before, match_starts_at_current_position_in_after].toString()];
if (action_up_to_match_positions !== 'none') {
operations.push({
action: action_up_to_match_positions,
start_in_before: position_in_before,
end_in_before: (action_up_to_match_positions !== 'insert' ? match.start_in_before - 1 : void 0),
start_in_after: position_in_after,
end_in_after: (action_up_to_match_positions !== 'delete' ? match.start_in_after - 1 : void 0)
});
}
if (match.length !== 0) {
operations.push({
action: 'equal',
start_in_before: match.start_in_before,
end_in_before: match.end_in_before,
start_in_after: match.start_in_after,
end_in_after: match.end_in_after
});
}
position_in_before = match.end_in_before + 1;
position_in_after = match.end_in_after + 1;
}
post_processed = [];
last_op = {
action: 'none'
};
is_single_whitespace = function(op) {
if (op.action !== 'equal') {
return false;
}
if (op.end_in_before - op.start_in_before !== 0) {
return false;
}
return /^\s$/.test(before_tokens.slice(op.start_in_before, +op.end_in_before + 1 || 9e9));
};
for (j = 0, len1 = operations.length; j < len1; j++) {
op = operations[j];
if (((is_single_whitespace(op)) && last_op.action === 'replace') || (op.action === 'replace' && last_op.action === 'replace')) {
last_op.end_in_before = op.end_in_before;
last_op.end_in_after = op.end_in_after;
} else {
post_processed.push(op);
last_op = op;
}
}
return post_processed;
};
consecutive_where = function(start, content, predicate) {
var answer, i, index, last_matching_index, len, token;
content = content.slice(start, +content.length + 1 || 9e9);
last_matching_index = void 0;
for (index = i = 0, len = content.length; i < len; index = ++i) {
token = content[index];
answer = predicate(token);
if (answer === true) {
last_matching_index = index;
}
if (answer === false) {
break;
}
}
if (last_matching_index != null) {
return content.slice(0, +last_matching_index + 1 || 9e9);
}
return [];
};
wrap = function(tag, content) {
var length, non_tags, position, rendering, tags;
rendering = '';
position = 0;
length = content.length;
while (true) {
if (position >= length) {
break;
}
non_tags = consecutive_where(position, content, isnt_tag);
position += non_tags.length;
if (non_tags.length !== 0) {
rendering += "<" + tag + ">" + (non_tags.join('')) + "</" + tag + ">";
}
if (position >= length) {
break;
}
tags = consecutive_where(position, content, is_tag);
position += tags.length;
rendering += tags.join('');
}
return rendering;
};
op_map = {
equal: function(op, before_tokens, after_tokens) {
return before_tokens.slice(op.start_in_before, +op.end_in_before + 1 || 9e9).join('');
},
insert: function(op, before_tokens, after_tokens) {
var val;
val = after_tokens.slice(op.start_in_after, +op.end_in_after + 1 || 9e9);
return wrap('ins', val);
},
"delete": function(op, before_tokens, after_tokens) {
var val;
val = before_tokens.slice(op.start_in_before, +op.end_in_before + 1 || 9e9);
return wrap('del', val);
}
};
op_map.replace = function(op, before_tokens, after_tokens) {
return (op_map["delete"](op, before_tokens, after_tokens)) + (op_map.insert(op, before_tokens, after_tokens));
};
render_operations = function(before_tokens, after_tokens, operations) {
var i, len, op, rendering;
rendering = '';
for (i = 0, len = operations.length; i < len; i++) {
op = operations[i];
rendering += op_map[op.action](op, before_tokens, after_tokens);
}
return rendering;
};
diff = function(before, after) {
var ops;
if (before === after) {
return before;
}
before = html_to_tokens(before);
after = html_to_tokens(after);
ops = calculate_operations(before, after);
return render_operations(before, after, ops);
};
diff.html_to_tokens = html_to_tokens;
diff.find_matching_blocks = find_matching_blocks;
find_matching_blocks.find_match = find_match;
find_matching_blocks.create_index = create_index;
diff.calculate_operations = calculate_operations;
diff.render_operations = render_operations;
if (typeof define === 'function') {
define([], function() {
return diff;
});
} else if (typeof module !== "undefined" && module !== null) {
module.exports = diff;
} else {
this.htmldiff = diff;
}
}).call(this);
//# sourceMappingURL=htmldiff.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
(function(){var $,Like,bind=function(fn,me){return function(){return fn.apply(me,arguments)}};$=jQuery,Like=function(){function Like(el,options){this.apiError=bind(this.apiError,this),this.removeLike=bind(this.removeLike,this),this.addLike=bind(this.addLike,this),this.sendLike=bind(this.sendLike,this),this.el=$(el),this.options=$.extend({},this.defaults,options),this.isSending=!1,this.setUp()}return Like.prototype.defaults={csrfToken:"csrf_token",likeText:"like ({count})",removeLikeText:"remove like ({count})"},Like.prototype.setUp=function(){return this.el.on("click",this.sendLike),this.el.on("click",this.stopClick)},Like.prototype.sendLike=function(){var post;this.isSending||(this.isSending=!0,post=$.post(this.el.attr("href"),{csrfmiddlewaretoken:this.options.csrfToken}),post.done(function(_this){return function(data){return data.url_delete?_this.addLike(data):data.url_create?_this.removeLike(data):_this.apiError()}}(this)),post.always(function(_this){return function(){return _this.isSending=!1}}(this)))},Like.prototype.addLike=function(data){var count,removeLikeText;return this.el.attr("href",data.url_delete),count=this.el.data("count"),count+=1,this.el.data("count",count),removeLikeText=$.format(this.options.removeLikeText,{count:count}),this.el.text(removeLikeText)},Like.prototype.removeLike=function(data){var count,likeText;return this.el.attr("href",data.url_create),count=this.el.data("count"),count-=1,this.el.data("count",count),likeText=$.format(this.options.likeText,{count:count}),this.el.text(likeText)},Like.prototype.apiError=function(){return this.el.text("api error")},Like.prototype.stopClick=function(e){e.preventDefault(),e.stopPropagation()},Like}(),$.fn.extend({like:function(options){return this.each(function(){return $(this).data("plugin_like")?void 0:$(this).data("plugin_like",new Like(this,options))})}}),$.fn.like.Like=Like}).call(this);

View File

@ -0,0 +1 @@
(function(){var $,Messages,bind=function(fn,me){return function(){return fn.apply(me,arguments)}};$=jQuery,Messages=function(){function Messages(el){this.hasVisibleMessages=bind(this.hasVisibleMessages,this),this.hideMessage=bind(this.hideMessage,this),this.showAllCloseButtons=bind(this.showAllCloseButtons,this),this.placeMessages=bind(this.placeMessages,this),this.el=$(el),this.allCloseButtons=this.el.find(".js-messages-close-button"),this.setUp()}return Messages.prototype.setUp=function(){return this.placeMessages(),this.showAllCloseButtons(),this.allCloseButtons.on("click",this.hideMessage),this.allCloseButtons.on("click",this.stopClick)},Messages.prototype.placeMessages=function(){return this.hasHash()?this.el.addClass("is-fixed"):void 0},Messages.prototype.showAllCloseButtons=function(){return this.hasHash()?this.el.find(".js-messages-close").show():void 0},Messages.prototype.hideMessage=function(e){$(e.currentTarget).closest(".js-messages-set").hide(),this.hasVisibleMessages()||(this.el.hide(),this.el.removeClass("is-fixed"))},Messages.prototype.hasVisibleMessages=function(){return this.el.find(".js-messages-set").is(":visible")},Messages.prototype.stopClick=function(e){e.preventDefault(),e.stopPropagation()},Messages.prototype.hasHash=function(){var hash;return hash=window.location.hash.split("#")[1],null!=hash&&hash.length>0},Messages}(),$.fn.extend({messages:function(){return this.each(function(){return $(this).data("plugin_messages")?void 0:$(this).data("plugin_messages",new Messages(this))})}}),$.fn.messages.Messages=Messages}).call(this);

View File

@ -0,0 +1 @@
(function(){var $,MoveComment,bind=function(fn,me){return function(){return fn.apply(me,arguments)}};$=jQuery,MoveComment=function(){function MoveComment(el,options){this.moveComments=bind(this.moveComments,this),this.addCommentSelection=bind(this.addCommentSelection,this),this.showMoveComments=bind(this.showMoveComments,this),this.el=$(el),this.options=$.extend({},this.defaults,options),this.setUp()}return MoveComment.prototype.defaults={csrfToken:"csrf_token",target:"#post_url"},MoveComment.prototype.setUp=function(){var $move_comments;return this.el.on("click",this.showMoveComments),this.el.on("click",this.stopClick),$move_comments=$(".js-move-comments"),$move_comments.on("click",this.moveComments),$move_comments.on("click",this.stopClick)},MoveComment.prototype.showMoveComments=function(){$(".move-comments").is(":hidden")&&($(".move-comments").show(),this.addCommentSelection())},MoveComment.prototype.addCommentSelection=function(){var $checkbox,$li;return $li=$("<li/>").appendTo(".comment-date"),$checkbox=$("<input/>",{"class":"move-comment-checkbox",name:"comments",type:"checkbox",value:""}).appendTo($li),$checkbox.each(function(){var $commentId;return $commentId=$(this).closest(".comment").data("pk"),$(this).val($commentId)})},MoveComment.prototype.moveComments=function(){var $form,topicId;$form=$("<form/>",{action:this.options.target,method:"post"}).hide().appendTo($("body")),$("<input/>",{name:"csrfmiddlewaretoken",type:"hidden",value:this.options.csrfToken}).appendTo($form),topicId=$("#id_move_comments_topic").val(),$("<input/>",{name:"topic",type:"text",value:topicId}).appendTo($form),$(".move-comment-checkbox").clone().appendTo($form),this.formSubmit($form)},MoveComment.prototype.formSubmit=function($form){return $form.submit()},MoveComment.prototype.stopClick=function(e){e.preventDefault(),e.stopPropagation()},MoveComment}(),$.fn.extend({move_comments:function(options){return this.each(function(){return $(this).data("plugin_move_comments")?void 0:$(this).data("plugin_move_comments",new MoveComment(this,options))})}}),$.fn.move_comments.MoveComment=MoveComment}).call(this);

View File

@ -0,0 +1 @@
(function(){var $,Notification,bind=function(fn,me){return function(){return fn.apply(me,arguments)}};$=jQuery,Notification=function(){function Notification(el,options){this.ajaxDone=bind(this.ajaxDone,this),this.addErrorTxt=bind(this.addErrorTxt,this),this.addIsEmptyTxt=bind(this.addIsEmptyTxt,this),this.addNotifications=bind(this.addNotifications,this),this.tabSwitch=bind(this.tabSwitch,this),this.el=$(el),this.options=$.extend({},this.defaults,options),this.tabNotificationContent=$(this.el.data("related")),this.setUp()}return Notification.prototype.defaults={notificationUrl:"#ajax",notificationListUrl:"#show-all",mentionTxt:"{user} mention you on {topic}",commentTxt:"{user} has commented on {topic}",showAll:"Show all",empty:"Nothing to show",unread:"unread"},Notification.prototype.setUp=function(){return this.el.one("click",this.tabSwitch),this.el.one("click",this.stopClick)},Notification.prototype.tabSwitch=function(){var get;get=$.getJSON(this.options.notificationUrl),get.done(function(_this){return function(data,status,jqXHR){return data.n.length>0?_this.addNotifications(data):_this.addIsEmptyTxt()}}(this)),get.fail(function(_this){return function(jqxhr,textStatus,error){return _this.addErrorTxt(textStatus,error)}}(this)),get.always(function(_this){return function(){return _this.ajaxDone()}}(this))},Notification.prototype.addNotifications=function(data){var showAllLink,unread;return unread='<span class="row-unread">'+this.options.unread+"</span>",$.each(data.n,function(_this){return function(i,obj){var link,txt;return txt=1===obj.action?_this.options.mentionTxt:_this.options.commentTxt,obj.is_read||(txt=txt+" "+unread),link='<a href="'+obj.url+'">'+obj.title+"</a>",txt=$.format(txt,{user:obj.user,topic:link}),_this.tabNotificationContent.append("<div>"+txt+"</div>")}}(this)),showAllLink='<a href="'+this.options.notificationListUrl+'">'+this.options.showAll+"</a>",this.tabNotificationContent.append("<div>"+showAllLink+"</div>")},Notification.prototype.addIsEmptyTxt=function(){return this.tabNotificationContent.append("<div>"+this.options.empty+"</div>")},Notification.prototype.addErrorTxt=function(textStatus,error){return this.tabNotificationContent.append("<div>Error: "+textStatus+", "+error+"</div>")},Notification.prototype.ajaxDone=function(){return this.el.addClass("js-tab"),$.tab(),this.el.trigger("click")},Notification.prototype.stopClick=function(e){e.preventDefault(),e.stopPropagation()},Notification}(),$.extend({notification:function(options){return $(".js-tab-notification").each(function(){return $(this).data("plugin_notification")?void 0:$(this).data("plugin_notification",new Notification(this,options))})}}),$.notification.Notification=Notification}).call(this);

View File

@ -0,0 +1 @@
(function(){var $,Postify,bind=function(fn,me){return function(){return fn.apply(me,arguments)}};$=jQuery,Postify=function(){function Postify(el,options){this.makePost=bind(this.makePost,this),this.el=$(el),this.options=$.extend({},this.defaults,options),this.setUp()}return Postify.prototype.defaults={csrfToken:"csrf_token"},Postify.prototype.setUp=function(){return this.el.on("click",this.makePost),this.el.on("click",this.stopClick)},Postify.prototype.makePost=function(){var $form;$form=$("<form/>",{action:this.el.attr("href"),method:"post"}).hide().appendTo($("body")),$("<input/>",{name:"csrfmiddlewaretoken",type:"hidden",value:this.options.csrfToken}).appendTo($form),this.formSubmit($form)},Postify.prototype.formSubmit=function($form){return $form.submit()},Postify.prototype.stopClick=function(e){e.preventDefault(),e.stopPropagation()},Postify}(),$.fn.extend({postify:function(options){return this.each(function(){return $(this).data("plugin_postify")?void 0:$(this).data("plugin_postify",new Postify(this,options))})}}),$.fn.postify.Postify=Postify}).call(this);

View File

@ -0,0 +1 @@
(function(){var $,SocialShare,bind=function(fn,me){return function(){return fn.apply(me,arguments)}};$=jQuery,SocialShare=function(){function SocialShare(el){this.closeDialog=bind(this.closeDialog,this),this.showDialog=bind(this.showDialog,this),this.el=$(el),this.dialog=$(this.el.data("dialog")),this.setUp()}return SocialShare.prototype.setUp=function(){var $shareClose,$shareInput;return this.el.on("click",this.showDialog),this.el.on("click",this.stopClick),$shareClose=this.dialog.find(".share-close"),$shareClose.on("click",this.closeDialog),$shareClose.on("click",this.stopClick),$shareInput=this.dialog.find(".share-url"),$shareInput.on("focus",this.select),$shareInput.on("mouseup",this.stopClick)},SocialShare.prototype.showDialog=function(){$(".share").hide(),this.dialog.show()},SocialShare.prototype.closeDialog=function(){this.dialog.hide()},SocialShare.prototype.select=function(){$(this).select()},SocialShare.prototype.stopClick=function(e){e.preventDefault(),e.stopPropagation()},SocialShare}(),$.fn.extend({social_share:function(){return this.each(function(){return $(this).data("plugin_social_share")?void 0:$(this).data("plugin_social_share",new SocialShare(this))})}}),$.fn.social_share.SocialShare=SocialShare}).call(this);

View File

@ -0,0 +1 @@
(function(){var $,Storage,bind=function(fn,me){return function(){return fn.apply(me,arguments)}};$=jQuery,Storage=function(){function Storage(el,lsKey){this.clearStorage=bind(this.clearStorage,this),this.updateField=bind(this.updateField,this),this.updateStorage=bind(this.updateStorage,this),this.el=$(el),this.lsKey=lsKey,this.setUp()}return Storage.prototype.setUp=function(){var $form;if("undefined"!=typeof localStorage&&null!==localStorage)return this.lsKey in localStorage&&this.updateField(),$(window).on("storage",this.updateField),this.el.on("input change propertychange",function(_this){return function(){$(window).off("storage",_this.updateField),_this.updateStorage(),$(window).on("storage",_this.updateField)}}(this)),$form=this.el.closest("form"),$form.on("submit",this.clearStorage)},Storage.prototype.updateStorage=function(){var err,error,value;value=this.el.val();try{localStorage[this.lsKey]=value}catch(error){err=error,localStorage.clear()}},Storage.prototype.updateField=function(){this.el.val(localStorage[this.lsKey])},Storage.prototype.clearStorage=function(){delete localStorage[this.lsKey]},Storage}(),$.fn.extend({store:function(lsKey){return this.each(function(){return $(this).data("plugin_store")?void 0:$(this).data("plugin_store",new Storage(this,lsKey))})}}),$.fn.store.Storage=Storage}).call(this);

View File

@ -0,0 +1 @@
(function(){var $,Tab,bind=function(fn,me){return function(){return fn.apply(me,arguments)}};$=jQuery,Tab=function(){function Tab(el){this.showTabContent=bind(this.showTabContent,this),this.selectTab=bind(this.selectTab,this),this.unselectAllTabs=bind(this.unselectAllTabs,this),this.hideAllTabsContent=bind(this.hideAllTabsContent,this),this.tabSwitch=bind(this.tabSwitch,this),this.el=$(el),this.setUp()}return Tab.prototype.setUp=function(){return this.el.on("click",this.tabSwitch),this.el.on("click",this.stopClick)},Tab.prototype.tabSwitch=function(){this.hideAllTabsContent(),this.el.hasClass("is-selected")?this.el.removeClass("is-selected"):(this.unselectAllTabs(),this.selectTab(),this.showTabContent())},Tab.prototype.hideAllTabsContent=function(){var $tabs_container,$tabs_content;return $tabs_container=this.el.closest(".js-tabs-container"),$tabs_content=$tabs_container.find(".js-tab-content"),$tabs_content.hide()},Tab.prototype.unselectAllTabs=function(){var $tabs,$tabs_container;return $tabs_container=this.el.closest(".js-tabs-container"),$tabs=$tabs_container.find(".js-tab"),$tabs.removeClass("is-selected")},Tab.prototype.selectTab=function(){return this.el.addClass("is-selected")},Tab.prototype.showTabContent=function(){var tab_content;return tab_content=this.el.data("related"),$(tab_content).show()},Tab.prototype.stopClick=function(e){e.preventDefault(),e.stopPropagation()},Tab}(),$.extend({tab:function(){return $(".js-tab").each(function(){return $(this).data("plugin_tab")?void 0:$(this).data("plugin_tab",new Tab(this))})}}),$.tab.Tab=Tab}).call(this);

View File

@ -0,0 +1 @@
(function(){var $;$=jQuery,$.extend({format:function(str,kwargs){var key,value;for(key in kwargs)value=kwargs[key],str=str.replace("{"+key+"}",String(value));return str}})}).call(this);

View File

@ -15,5 +15,11 @@ $ npm install -y .
## Build ## Build
``` ```
$ npm run gulp coffee $ npm run gulp js
```
## Test
```
$ npm run gulp test
``` ```

View File

@ -0,0 +1,298 @@
is_end_of_tag = (char)-> char is '>'
is_start_of_tag = (char)-> char is '<'
is_whitespace = (char)-> /^\s+$/.test char
is_tag = (token)-> /^\s*<[^>]+>\s*$/.test token
isnt_tag = (token)-> not is_tag token
class Match
constructor: (@start_in_before, @start_in_after, @length)->
@end_in_before = (@start_in_before + @length) - 1
@end_in_after = (@start_in_after + @length) - 1
html_to_tokens = (html)->
mode = 'char'
current_word = ''
words = []
for char in html
switch mode
when 'tag'
if is_end_of_tag char
current_word += '>'
words.push current_word
current_word = ''
if is_whitespace char
mode = 'whitespace'
else
mode = 'char'
else
current_word += char
when 'char'
if is_start_of_tag char
words.push current_word if current_word
current_word = '<'
mode = 'tag'
else if /\s/.test char
words.push current_word if current_word
current_word = char
mode = 'whitespace'
else if /[\w\#@]+/i.test char
current_word += char
else
words.push current_word if current_word
current_word = char
when 'whitespace'
if is_start_of_tag char
words.push current_word if current_word
current_word = '<'
mode = 'tag'
else if is_whitespace char
current_word += char
else
words.push current_word if current_word
current_word = char
mode = 'char'
else throw new Error "Unknown mode #{mode}"
words.push current_word if current_word
return words
find_match = (before_tokens, after_tokens,
index_of_before_locations_in_after_tokens,
start_in_before, end_in_before,
start_in_after, end_in_after)->
best_match_in_before = start_in_before
best_match_in_after = start_in_after
best_match_length = 0
match_length_at = {}
for index_in_before in [start_in_before...end_in_before]
new_match_length_at = {}
looking_for = before_tokens[index_in_before]
locations_in_after =
index_of_before_locations_in_after_tokens[looking_for]
for index_in_after in locations_in_after
continue if index_in_after < start_in_after
break if index_in_after >= end_in_after
unless match_length_at[index_in_after - 1]?
match_length_at[index_in_after - 1] = 0
new_match_length = match_length_at[index_in_after - 1] + 1
new_match_length_at[index_in_after] = new_match_length
if new_match_length > best_match_length
best_match_in_before = index_in_before - new_match_length + 1
best_match_in_after = index_in_after - new_match_length + 1
best_match_length = new_match_length
match_length_at = new_match_length_at
unless best_match_length is 0
match = (new Match best_match_in_before, best_match_in_after,\
best_match_length)
return match
recursively_find_matching_blocks = (before_tokens, after_tokens,
index_of_before_locations_in_after_tokens,
start_in_before, end_in_before,
start_in_after, end_in_after,
matching_blocks)->
match = (find_match before_tokens, after_tokens,
index_of_before_locations_in_after_tokens,
start_in_before, end_in_before,
start_in_after, end_in_after)
if match?
if start_in_before < match.start_in_before\
and start_in_after < match.start_in_after
recursively_find_matching_blocks before_tokens, after_tokens,
index_of_before_locations_in_after_tokens,
start_in_before, match.start_in_before,
start_in_after, match.start_in_after,
matching_blocks
matching_blocks.push match
if match.end_in_before <= end_in_before\
and match.end_in_after <= end_in_after
recursively_find_matching_blocks before_tokens, after_tokens,
index_of_before_locations_in_after_tokens,
match.end_in_before + 1, end_in_before,
match.end_in_after + 1, end_in_after,
matching_blocks
return matching_blocks
create_index = (p)->
throw new Error 'params must have find_these key' unless p.find_these?
throw new Error 'params must have in_these key' unless p.in_these?
index = {}
for token in p.find_these
index[token] = []
idx = p.in_these.indexOf token
while idx isnt -1
index[token].push idx
idx = p.in_these.indexOf token, idx+1
return index
find_matching_blocks = (before_tokens, after_tokens)->
matching_blocks = []
index_of_before_locations_in_after_tokens =
create_index
find_these: before_tokens
in_these: after_tokens
recursively_find_matching_blocks before_tokens, after_tokens,
index_of_before_locations_in_after_tokens,
0, before_tokens.length,
0, after_tokens.length,
matching_blocks
calculate_operations = (before_tokens, after_tokens)->
throw new Error 'before_tokens?' unless before_tokens?
throw new Error 'after_tokens?' unless after_tokens?
position_in_before = position_in_after = 0
operations = []
action_map =
'false,false': 'replace'
'true,false' : 'insert'
'false,true' : 'delete'
'true,true' : 'none'
matches = find_matching_blocks before_tokens, after_tokens
matches.push new Match before_tokens.length, after_tokens.length, 0
for match, index in matches
match_starts_at_current_position_in_before =
position_in_before is match.start_in_before
match_starts_at_current_position_in_after =
position_in_after is match.start_in_after
action_up_to_match_positions =
action_map[[match_starts_at_current_position_in_before,\
match_starts_at_current_position_in_after].toString()]
if action_up_to_match_positions isnt 'none'
operations.push
action: action_up_to_match_positions
start_in_before: position_in_before
end_in_before: (match.start_in_before - 1 \
unless action_up_to_match_positions is 'insert')
start_in_after: position_in_after
end_in_after: (match.start_in_after - 1 \
unless action_up_to_match_positions is 'delete')
unless match.length is 0
operations.push
action: 'equal'
start_in_before: match.start_in_before
end_in_before: match.end_in_before
start_in_after: match.start_in_after
end_in_after: match.end_in_after
position_in_before = match.end_in_before + 1
position_in_after = match.end_in_after + 1
post_processed = []
last_op = action: 'none'
is_single_whitespace = (op)->
return no unless op.action is 'equal'
return no unless op.end_in_before - op.start_in_before is 0
return /^\s$/.test before_tokens[op.start_in_before..op.end_in_before]
for op in operations
if ((is_single_whitespace op) and last_op.action is 'replace') or
(op.action is 'replace' and last_op.action is 'replace')
last_op.end_in_before = op.end_in_before
last_op.end_in_after = op.end_in_after
else
post_processed.push op
last_op = op
return post_processed
consecutive_where = (start, content, predicate)->
content = content[start..content.length]
last_matching_index = undefined
for token, index in content
answer = predicate token
last_matching_index = index if answer is yes
break if answer is no
return content[0..last_matching_index] if last_matching_index?
return []
wrap = (tag, content)->
rendering = ''
position = 0
length = content.length
loop
break if position >= length
non_tags = consecutive_where position, content, isnt_tag
position += non_tags.length
if non_tags.length isnt 0
rendering += "<#{tag}>#{non_tags.join ''}</#{tag}>"
break if position >= length
tags = consecutive_where position, content, is_tag
position += tags.length
rendering += tags.join ''
return rendering
op_map =
equal: (op, before_tokens, after_tokens)->
before_tokens[op.start_in_before..op.end_in_before].join ''
insert: (op, before_tokens, after_tokens)->
val = after_tokens[op.start_in_after..op.end_in_after]
wrap 'ins', val
delete: (op, before_tokens, after_tokens)->
val = before_tokens[op.start_in_before..op.end_in_before]
wrap 'del', val
op_map.replace = (op, before_tokens, after_tokens)->
(op_map.delete op, before_tokens, after_tokens) +
(op_map.insert op, before_tokens, after_tokens)
render_operations = (before_tokens, after_tokens, operations)->
rendering = ''
for op in operations
rendering += op_map[op.action] op, before_tokens, after_tokens
return rendering
diff = (before, after)->
return before if before is after
before = html_to_tokens before
after = html_to_tokens after
ops = calculate_operations before, after
render_operations before, after, ops
diff.html_to_tokens = html_to_tokens
diff.find_matching_blocks = find_matching_blocks
find_matching_blocks.find_match = find_match
find_matching_blocks.create_index = create_index
diff.calculate_operations = calculate_operations
diff.render_operations = render_operations
if typeof define is 'function'
define [], ()-> diff
else if module?
module.exports = diff
else
this.htmldiff = diff

View File

@ -14,12 +14,14 @@ module.exports = function(config) {
// list of files / patterns to load in the browser // list of files / patterns to load in the browser
// todo: load the all.min.js instead of individual files
files: [ files: [
'vendors/jquery.min.js', 'vendors/jquery.min.js',
'vendors/**/*.js', 'vendors/**/*.js',
'test/jasmine-jquery.js', 'test/jasmine-jquery.js',
'test/suites/*-spec.js', 'test/suites/*-spec.js',
'*.js', '!js/*.min.js', // Exclude
'js/*.js',
{ {

View File

@ -12,31 +12,30 @@
<link rel="stylesheet" href="{% static "spirit/stylesheets/styles.all.min.css" %}"> <link rel="stylesheet" href="{% static "spirit/stylesheets/styles.all.min.css" %}">
<script src="{% static "spirit/scripts/vendors/jquery.min.js" %}"></script> <script src="{% static "spirit/scripts/all.min.js" %}"></script>
<script src="{% static "spirit/scripts/vendors/highlightjs/highlight.min.js" %}"></script>
<script src="{% static "spirit/scripts/util.js" %}"></script>
<script src="{% static "spirit/scripts/tab.js" %}"></script>
<script src="{% static "spirit/scripts/postify.js" %}"></script>
<script src="{% static "spirit/scripts/social_share.js" %}"></script>
<script src="{% static "spirit/scripts/messages.js" %}"></script>
{% if user.is_authenticated %} {# todo: use this if debug=true #}
{% comment %}
<script src="{% static "spirit/scripts/vendors/jquery.min.js" %}"></script>
<script src="{% static "spirit/scripts/vendors/highlightjs/highlight.min.js" %}"></script>
<script src="{% static "spirit/scripts/vendors/atwho/jquery.caret.min.js" %}"></script> <script src="{% static "spirit/scripts/vendors/atwho/jquery.caret.min.js" %}"></script>
<script src="{% static "spirit/scripts/vendors/atwho/jquery.atwho.min.js" %}"></script> <script src="{% static "spirit/scripts/vendors/atwho/jquery.atwho.min.js" %}"></script>
<script src="{% static "spirit/scripts/vendors/marked/marked.min.js" %}"></script> <script src="{% static "spirit/scripts/vendors/marked/marked.min.js" %}"></script>
<script src="{% static "spirit/scripts/vendors/waypoints/waypoints.min.js" %}"></script> <script src="{% static "spirit/scripts/vendors/waypoints/waypoints.min.js" %}"></script>
<script src="{% static "spirit/scripts/store.js" %}"></script> <script src="{% static "spirit/scripts/js/util.js" %}"></script>
<script src="{% static "spirit/scripts/editor_image_upload.js" %}"></script> <script src="{% static "spirit/scripts/js/tab.js" %}"></script>
<script src="{% static "spirit/scripts/editor.js" %}"></script> <script src="{% static "spirit/scripts/js/postify.js" %}"></script>
<script src="{% static "spirit/scripts/emoji_list.js" %}"></script> <script src="{% static "spirit/scripts/js/social_share.js" %}"></script>
<script src="{% static "spirit/scripts/like.js" %}"></script> <script src="{% static "spirit/scripts/js/messages.js" %}"></script>
<script src="{% static "spirit/scripts/bookmark.js" %}"></script> <script src="{% static "spirit/scripts/js/store.js" %}"></script>
<script src="{% static "spirit/scripts/notification.js" %}"></script> <script src="{% static "spirit/scripts/js/editor_image_upload.js" %}"></script>
{% endif %} <script src="{% static "spirit/scripts/js/editor.js" %}"></script>
<script src="{% static "spirit/scripts/js/emoji_list.js" %}"></script>
{% if user.st.is_moderator %} <script src="{% static "spirit/scripts/js/like.js" %}"></script>
<script src="{% static "spirit/scripts/move_comments.js" %}"></script> <script src="{% static "spirit/scripts/js/bookmark.js" %}"></script>
{% endif %} <script src="{% static "spirit/scripts/js/notification.js" %}"></script>
<script src="{% static "spirit/scripts/js/move_comments.js" %}"></script>
{% endcomment %}
<script> <script>