[#369] add templates; add codemirror

This commit is contained in:
Alexander Machehin 2014-04-11 14:57:00 +06:00
parent 09b75e4110
commit 1a1838a363
76 changed files with 347 additions and 68 deletions

View File

@ -75,6 +75,7 @@ gem 'angular-i18n', '0.1.2'
gem 'js-routes'
gem 'soundmanager-rails'
gem 'angular-ui-bootstrap-rails'
gem 'angular-rails-templates'
gem 'time_diff'
@ -88,6 +89,8 @@ gem 'therubyrhino', '~> 1.73.1', platforms: :jruby
gem 'bootstrap-sass', '~> 3.1.1'
gem 'font-awesome-rails'
gem 'codemirror-rails'
group :production do
gem "airbrake", '~> 3.1.2'
gem 'bluepill', '~> 0.0.60', require: false

View File

@ -64,6 +64,9 @@ GEM
ancestry (2.0.0)
activerecord (>= 3.0.0)
angular-i18n (0.1.2)
angular-rails-templates (0.0.7)
railties (>= 3.1)
sprockets
angular-ui-bootstrap-rails (0.10.0)
angularjs-rails (1.2.15)
arel (4.0.2)
@ -102,6 +105,8 @@ GEM
activesupport (>= 3.0)
cocaine (0.5.3)
climate_control (>= 0.0.3, < 1.0)
codemirror-rails (3.22)
railties (>= 3.0, < 5)
coderay (1.1.0)
coffee-rails (4.0.1)
coffee-script (>= 2.2.0)
@ -468,6 +473,7 @@ DEPENDENCIES
airbrake (~> 3.1.2)
ancestry (~> 2.0.0)
angular-i18n (= 0.1.2)
angular-rails-templates
angular-ui-bootstrap-rails
angularjs-rails (~> 1.2.15)
attr_encrypted (~> 1.3.2)
@ -480,6 +486,7 @@ DEPENDENCIES
capistrano
capistrano_colors
charlock_holmes (~> 0.6.9)
codemirror-rails
coffee-rails (~> 4.0.1)
compass-rails (~> 1.1.6)
creole

View File

@ -1,6 +1,7 @@
//var RosaABF = angular.module('RosaABF', ['ngResource', 'ng-rails-csrf', 'angular-i18n', 'angularMoment']);
var RosaABF = angular.module('RosaABF', ['ui.bootstrap', 'angular-i18n', 'angularMoment',
'chieffancypants.loadingBar', 'ngSanitize']);
'chieffancypants.loadingBar', 'ngSanitize', 'templates',
'ui.codemirror']);
var LocalesHelper = function($locale) {
var locales = {

View File

@ -55,4 +55,12 @@ RosaABF.controller('ActivityCtrl', ['$scope', '$http', '$timeout', '$q', '$filte
return content.kind === 'new_comment_notification' ||
content.kind === 'new_comment_commit_notification';
};
$scope.getTemplate = function(content) {
if(content.kind == 'new_commit_notification' || content.kind == 'git_new_push_notification' ||
content.kind == 'git_delete_branch_notification' || content.kind == 'new_issue_notification') {
return content.kind + '.html';}
else return 'new_comment_notification.html';
};
}]);

View File

@ -0,0 +1,16 @@
%i.img-circle.btn-danger.fa.fa-times
.timeline-item{ 'ng-cloak' => true }
%h3.timeline-header
%a{ 'ng-href' => "{{item.user.link}}" }
%img{ 'ng-src' => "{{item.user.image}}" }
{{item.user.uname}}
%span.time{ popover: "{{item.date | amDateFormat:'ddd, LLL'}}",
"popover-trigger" => "mouseenter", 'popover-append-to-body' => 'true' }
%span.glyphicon.glyphicon-time
%span {{item.date | amDateFormat:'HH:mm'}}
.clearfix
.timeline-body
%p
{{'notification.push.delete_branch' | i18n}}
{{ item.branch_name + ('notification.in_project' | i18n)}}
%a{ 'ng-href' => "{{item.project_link}}" } {{item.project_name_with_owner}}

View File

@ -0,0 +1,27 @@
%i.img-circle.bg-primary.fa.fa-sign-in
.timeline-item{ 'ng-cloak' => true }
%h3.timeline-header
%a{ 'ng-href' => "{{item.user.link}}" }
%img{ 'ng-src' => "{{item.user.image}}" }
{{item.user.uname}}
%span.time{ popover: "{{item.date | amDateFormat:'ddd, LLL'}}",
"popover-trigger" => "mouseenter", 'popover-append-to-body' => 'true' }
%span.glyphicon.glyphicon-time
%span {{item.date | amDateFormat:'HH:mm'}}
.clearfix
.timeline-body
%p
{{'notification.push.' + item.change_type + '_branch' | i18n}}
{{ item.branch_name + ('notification.in_project' | i18n)}}
%a{ 'ng-href' => "{{item.project_link}}" } {{item.project_name_with_owner}}
.timeline-footer
.row
.container-fluid{ 'ng-repeat' => 'commit in item.last_commits' }
.col-sm-3.col-md-2
%a{ 'ng-href' => "{{commit.commit_path}}" } {{commit.hash}}
.col-sm-8.col-md-9{ 'ng-bind-html' => "commit.message" }
.clearfix
%br{ 'ng-if' => "item.other_commits" }
%a{ 'ng-if' => "item.other_commits", 'ng-href' => "{{item.other_commits_path}}" } {{item.other_commits}}

View File

@ -0,0 +1,22 @@
%i.img-circle.btn-warning.fa.fa-comment
.timeline-item{ 'ng-cloak' => true }
%h3.timeline-header
%a{ 'ng-href' => "{{item.user.link}}" }
%img{ 'ng-src' => "{{item.user.image}}" }
{{item.user.uname}}
%span.time{ popover: "{{item.date | amDateFormat:'ddd, LLL'}}",
"popover-trigger" => "mouseenter", 'popover-append-to-body' => 'true' }
%span.glyphicon.glyphicon-time
%span {{item.date | amDateFormat:'HH:mm'}}
.clearfix
.timeline-body
%p
{{'notification.new_comment.title' | i18n}}
%a{ 'ng-href' => "{{item.issue.link}}" } {{item.issue.title}}
%blockquote{ 'ng-bind-html' => "item.body" }
.timeline-footer
%a.btn.btn-primary.btn-xs{ 'ng-href' => "{{item.issue.link + '#comment' + item.issue.read_more}}" }
{{'read_more' | i18n}}
{{item.kind}}

View File

@ -0,0 +1,16 @@
%i.img-circle.btn-success.fa.fa-check
.timeline-item{ 'ng-cloak' => true }
%h3.timeline-header
%a{ 'ng-href' => "{{item.user.link}}" }
%img{ 'ng-src' => "{{item.user.image}}" }
{{item.user.uname}}
%span.time{ popover: "{{item.date | amDateFormat:'ddd, LLL'}}",
"popover-trigger" => "mouseenter", 'popover-append-to-body' => 'true' }
%span.glyphicon.glyphicon-time
%span {{item.date | amDateFormat:'HH:mm'}}
.clearfix
.timeline-body
%p
{{('notification.new_issue' | i18n) + ('notification.in_project' | i18n)}}
%a{ 'ng-href' => "{{item.project_link}}" } {{item.project_name_with_owner}}
%a{ 'ng-href' => "{{item.issue.link}}" } {{item.issue.title}}

View File

@ -17,7 +17,19 @@ var _locales = {
'autostart_statuses.1': 'Раз в день',
'autostart_statuses.2': 'Раз в неделю',
<%= BuildList::STATUSES.map{|s| "'build_list.status.#{s}': '#{BuildList.human_status(s)}'"}.join(',') %>,
<%= I18n.t('activity_menu').map{ |k,v| "'activity_menu.#{k}': '#{v}'" }.join(',') %>
//
<%= I18n.t('activity_menu').map{ |k,v| "'activity_menu.#{k}': '#{v}'" }.join(',') %>,
'notification.new_comment.title': 'добавил новый комментарий к задаче ',
'notification.push.delete_branch': 'удалил ветку ',
'notification.push.create_branch': 'создал новую ветку ',
'notification.push.update_branch': 'внес изменения в ветку ',
'notification.in_project': ' в проекте ',
'notification.new_issue': 'добавил новую задачу',
'read_more': 'Читать дальше',
},
<%I18n.locale = :en%>
'en-us': {
@ -35,6 +47,18 @@ var _locales = {
'autostart_statuses.1': 'Once a day',
'autostart_statuses.2': 'Once a week',
<%= BuildList::STATUSES.map{|s| "'build_list.status.#{s}': '#{BuildList.human_status(s)}'"}.join(',') %>,
<%= I18n.t('activity_menu').map{ |k,v| "'activity_menu.#{k}': '#{v}'" }.join(',') %>
//
<%= I18n.t('activity_menu').map{ |k,v| "'activity_menu.#{k}': '#{v}'" }.join(',') %>,
'notification.new_comment.title': 'added a new comment in issue ',
'notification.push.delete_branch': 'deleted a branch ',
'notification.push.create_branch': 'created a new branch ',
'notification.push.update_branch': 'pushed to branch ',
'notification.in_project': ' in project ',
'notification.new_issue': 'added a new issue ',
'read_more': 'Read more',
}
};

View File

@ -1,4 +1,5 @@
//= require jquery
//= require jquery_ujs
//= require js-routes
// Loads all Bootstrap javascripts
@ -8,6 +9,7 @@
//= require angular
//= require angular-sanitize
//= require angular-ui-bootstrap-tpls
//= require ui-codemirror
//= require angular-i18n
//= require angularjs/locales
@ -16,3 +18,19 @@
//= require_tree ./angular-new
//= require loading-bar
//= require codemirror
// ### TODO require all files in codemirror/modes ###
//= require codemirror/modes/ruby
//= require codemirror/modes/javascript
//= require codemirror/modes/markdown
$(document).ready(function() {
window.CodeMirrorRun = function(code) {
//CodeMirror.runMode(code.innerHTML.replace(/&amp;/gi, '&').replace(/&lt;/gi, '<').replace(/&gt;/gi, '>'), code.className, code);
CodeMirror.runMode(code.innerHTML, 'markdown', code);
}
$('.md_and_cm').each(function (code) { CodeMirrorRun(this); });
});

View File

@ -9,4 +9,5 @@ $navbar-default-link-hover-color: #CEE7FF;
@import "custom_bootstrap";
@import "timeline";
@import "font-awesome";
@import "loading-bar";
@import "loading-bar";
@import "codemirror";

View File

@ -11,6 +11,7 @@ class HomeController < ApplicationController
@activity_feeds = current_user.activity_feeds
@activity_feeds = @activity_feeds.where(kind: "ActivityFeed::#{@filter.upcase}".constantize) unless @filter == :all
@activity_feeds = @activity_feeds.paginate page: params[:page]
respond_to do |format|
format.html { request.xhr? ? render('_list', layout: false) : render('activity') }
format.json {}

View File

@ -18,30 +18,4 @@ module ActivityFeedsHelper
def user_link(user, user_name, full_url = false)
user.persisted? ? link_to(user_name, full_url ? user_url(user) : user_path(user)) : user_name
end
def get_title_from_activity_item(item, opts = {})
case item.kind
when 'new_comment_notification'
res = t('notifications.bodies.new_comment_notification.title', { user_link: nil })
res << ' ' << t('notifications.bodies.new_comment_notification.content',
{ issue_link: link_to(item.data[:issue_title], opts[:path]) })
when 'git_new_push_notification'
res = t("notifications.bodies.#{item.data[:change_type]}_branch",
{ branch_name: item.data[:branch_name], user_link: nil })
res << ' ' << t('notifications.bodies.project', project_link: link_to(opts[:project_name_with_owner], opts[:path]))
else nil
end
raw res
end
def get_path_from_activity_item(item, opts = {})
case item.kind
when 'new_comment_notification'
project_issue_path(opts[:project_name_with_owner], item.data[:issue_serial_id])
when 'git_new_push_notification'
project_path(opts[:project_name_with_owner])
else
'?'
end
end
end

View File

@ -23,7 +23,7 @@ module Feed::Comment
user_name: user.name,
user_email: user.email,
user_id: user_id,
comment_body: truncate(body, length: 1000, omission: '…'),
comment_body: truncate(body, length: 100, omission: '…'),
issue_title: commentable.title,
issue_serial_id: commentable.serial_id,
project_id: commentable.project.id,

View File

@ -43,28 +43,7 @@
%span{ 'ng-show' => "needShowTimeLabel($index)", 'ng-cloak' => true }
{{item.date | amDateFormat:'ll'}}
/ timeline item
%li
%i.img-circle.fa{ 'ng-class' => "getTimeLinefaClass(item)" }
.timeline-item
%h3.timeline-header
%a{ 'ng-href' => "{{item.user.link}}" }
%img{ 'ng-src' => "{{item.user.image}}" }
{{item.user.uname}}
%span.time{ 'ng-cloak' => true, popover: "{{item.date | amDateFormat:'ddd, LLL'}}",
"popover-trigger" => "mouseenter", 'popover-append-to-body' => 'true' }
%span.glyphicon.glyphicon-time
%span {{item.date | amDateFormat:'HH:mm'}}
.clearfix
.timeline-body
-#%p{ 'ng-bind-html' => "item.title" }
%p{ 'ng-bind-html' => "item.title" }
%blockquote{ 'ng-if' => "isComment(item)" }
{{item.body}}
%p{ 'ng-if' => "!isComment(item)", 'ng-bind-html' => "item.body" }
.timeline-footer
%a.btn.btn-primary.btn-xs{ 'ng-href' => "{{item.read_more}}", 'ng-if' => "isComment(item)" }
= t('layout.read_more')
%li{ 'ng-include' => "getTemplate(item)" }
.hide{ 'ng-repeat-end' => true }
%li
%i.img-circle.bg-primary.fa.fa-clock-o

View File

@ -1,5 +1,5 @@
json.array!(@activity_feeds) do |item|
json.cache! item do
#json.cache! item, expires_in: 10.minutes do
json.date item.created_at
json.kind item.kind
user = get_user_from_activity_item(item)
@ -8,18 +8,50 @@ json.array!(@activity_feeds) do |item|
json.image avatar_url(user, :small) if user.persisted?
json.uname (user.fullname || user.email)
end if user
project_name_with_owner = "#{item.data[:project_owner]}/#{item.data[:project_name]}"
@project = Project.find_by_owner_and_name(item.data[:project_owner], item.data[:project_name])
json.project_name_with_owner project_name_with_owner
path = get_path_from_activity_item(item, project_name_with_owner: project_name_with_owner)
json.title get_title_from_activity_item(item, user: user, project_name_with_owner: project_name_with_owner, path: path)
case item.kind
when 'new_comment_notification'
json.read_more path + "#comment#{item.data[:comment_id]}"
json.body short_message(item.data[:comment_body], 1000)
json.issue do
json.link project_issue_path(project_name_with_owner, item.data[:issue_serial_id]) if item.data[:issue_serial_id].present?
json.title short_message(item.data[:issue_title], 50)
json.read_more item.data[:comment_id]
end
json.body markdown(short_message(item.data[:comment_body], 100))
when 'git_new_push_notification'
json.body render('commits_list', item: item, project_name_with_owner: project_name_with_owner).html_safe
json.change_type item.data[:change_type]
json.project_link project_path(project_name_with_owner)
json.branch_name item.data[:branch_name]
json.last_commits do
json.array! item.data[:last_commits] do |commit|
json.hash shortest_hash_id(commit[0])
json.message markdown(short_message(commit[1], 70))
json.commit_path commit_path(project_name_with_owner, commit[0])
end
end
if item.data[:other_commits].present?
json.other_commits t('notifications.bodies.more_commits', count: item.data[:other_commits_count], commits: commits_pluralize(item.data[:other_commits_count]))
json.other_commits_path diff_path(project_name_with_owner, diff: item.data[:other_commits])
end
when 'git_delete_branch_notification'
json.branch_name item.data[:branch_name]
json.project_link project_path(project_name_with_owner)
when 'new_issue_notification'
json.project_link project_path(project_name_with_owner)
json.issue do
json.title item.data[:issue_title]
json.link project_issue_path(project_name_with_owner, item.data[:issue_serial_id]) if item.data[:issue_serial_id].present?
end
end
json.id item.id
end
#end
end

View File

@ -56,5 +56,7 @@ module Rosa
config.assets.version = '1.0'
config.log_redis = false
config.angular_templates.ignore_prefix = 'angular-new/templates/'
end
end

View File

@ -0,0 +1,148 @@
/*
* UI.Codemirror directive
* https://github.com/angular-ui/ui-codemirror
*
* This directive allows you to add CodeMirror to your textarea elements.
*
* The MIT License
*
* Copyright (c) 2012 the AngularUI Team, http://angular-ui.github.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
'use strict';
/**
* Binds a CodeMirror widget to a <textarea> element.
*/
angular.module('ui.codemirror', [])
.constant('uiCodemirrorConfig', {})
.directive('uiCodemirror', ['uiCodemirrorConfig', function (uiCodemirrorConfig) {
return {
restrict: 'EA',
require: '?ngModel',
priority: 1,
compile: function compile(tElement) {
// Require CodeMirror
if (angular.isUndefined(window.CodeMirror)) {
throw new Error('ui-codemirror need CodeMirror to work... (o rly?)');
}
// Create a codemirror instance with
// - the function that will to place the editor into the document.
// - the initial content of the editor.
// see http://codemirror.net/doc/manual.html#api_constructor
var value = tElement.text();
var codeMirror = new window.CodeMirror(function (cm_el) {
angular.forEach(tElement.prop('attributes'), function (a) {
if (a.name === 'ui-codemirror') {
cm_el.setAttribute('ui-codemirror-opts', a.textContent);
} else {
cm_el.setAttribute(a.name, a.textContent);
}
});
// FIX replaceWith throw not parent Error !
if (tElement.parent().length <= 0) {
tElement.wrap('<div>');
}
tElement.replaceWith(cm_el);
}, {value: value});
return function postLink(scope, iElement, iAttrs, ngModel) {
var options, opts;
options = uiCodemirrorConfig.codemirror || {};
opts = angular.extend({}, options, scope.$eval(iAttrs.uiCodemirror), scope.$eval(iAttrs.uiCodemirrorOpts));
function updateOptions(newValues) {
for (var key in newValues) {
if (newValues.hasOwnProperty(key)) {
codeMirror.setOption(key, newValues[key]);
}
}
}
updateOptions(opts);
if (angular.isDefined(scope.$eval(iAttrs.uiCodemirror))) {
scope.$watch(iAttrs.uiCodemirror, updateOptions, true);
}
// Specialize change event
codeMirror.on('change', function (instance) {
var newValue = instance.getValue();
if (ngModel && newValue !== ngModel.$viewValue) {
ngModel.$setViewValue(newValue);
}
if (!scope.$$phase) {
scope.$apply();
}
});
if (ngModel) {
// CodeMirror expects a string, so make sure it gets one.
// This does not change the model.
ngModel.$formatters.push(function (value) {
if (angular.isUndefined(value) || value === null) {
return '';
}
else if (angular.isObject(value) || angular.isArray(value)) {
throw new Error('ui-codemirror cannot use an object or an array as a model');
}
return value;
});
// Override the ngModelController $render method, which is what gets called when the model is updated.
// This takes care of the synchronizing the codeMirror element with the underlying model, in the case that it is changed by something else.
ngModel.$render = function () {
//Code mirror expects a string so make sure it gets one
//Although the formatter have already done this, it can be possible that another formatter returns undefined (for example the required directive)
var safeViewValue = ngModel.$viewValue || '';
codeMirror.setValue(safeViewValue);
};
}
// Watch ui-refresh and refresh the directive
if (iAttrs.uiRefresh) {
scope.$watch(iAttrs.uiRefresh, function (newVal, oldVal) {
// Skip the initial watch firing
if (newVal !== oldVal) {
codeMirror.refresh();
}
});
}
// onLoad callback
if (angular.isFunction(opts.onLoad)) {
opts.onLoad(codeMirror);
}
};
}
};
}]);

View File

@ -3,9 +3,9 @@
//= require gollum/gollum.placeholder
//= require gollum/editor/gollum.editor
//= require jquery.dataTables
//= require codemirror
//= require codemirror/runmode
//= require_tree ./codemirror/modes
//= require old-codemirror
//= require old-codemirror/runmode
//= require_tree ./old-codemirror/modes
//= require cusel
//= require bootstrap-modal
//= require bootstrap-button

View File

@ -7,11 +7,11 @@
//@import "gollum/template";
@import "gollum/editor";
@import "codemirror";
@import "codemirror/themes/eclipse";
@import "codemirror/modes/diff";
@import "codemirror/modes/rpm-spec";
@import "codemirror/modes/tiddlywiki";
@import "old-codemirror";
@import "old-codemirror/themes/eclipse";
@import "old-codemirror/modes/diff";
@import "old-codemirror/modes/rpm-spec";
@import "old-codemirror/modes/tiddlywiki";
@import "old-bootstrap";
@import "chosen.scss";