Compare View

switch
from
...
to
 
Commits (14)
@@ -50,3 +50,31 @@ See some important folders bellow: @@ -50,3 +50,31 @@ See some important folders bellow:
50 1. Create an app folder inside custom-theme and add your custom scss files 50 1. Create an app folder inside custom-theme and add your custom scss files
51 1. Put the templates that you want to override in the same structure from the main application source, e.g.: 51 1. Put the templates that you want to override in the same structure from the main application source, e.g.:
52 `src/app/profile/profile.html` will be overriden by `themes/custom-theme/app/profile/profile.html` 52 `src/app/profile/profile.html` will be overriden by `themes/custom-theme/app/profile/profile.html`
  53 +
  54 +## Change skin
  55 +
  56 +- Create a any scss file into your theme folder structure
  57 +- Extend your skin css class from `%skin-base` scss placeholder selector. Something like this:
  58 +
  59 +```sass
  60 +.skin-custom {
  61 + @extend %skin-base
  62 +}
  63 +```
  64 +- Configure application to use the new theme, e.g.:
  65 +`npm config set angular-theme:skin custom-skin`
  66 +
  67 +- Start the application with `npm start` scripts ou make a build
  68 +
  69 +## Development environment
  70 +
  71 +## Known Issues
  72 +
  73 +### Message Translation: angular-i18n
  74 +
  75 + - Plural Interpolation only working when current language is En (English)
  76 +
  77 + `Plural Function not found for locale`
  78 +
  79 + For some reason the messageformat installed on bower_component directory was an older version. Removing the bower_components directory
  80 +and runing `bower install` solved the problem
@@ -47,9 +47,7 @@ @@ -47,9 +47,7 @@
47 "ng-ckeditor": { 47 "ng-ckeditor": {
48 "main": [ 48 "main": [
49 "ng-ckeditor.js", 49 "ng-ckeditor.js",
50 - "libs/ckeditor/lang",  
51 - "libs/ckeditor/ckeditor.js",  
52 - "libs/ckeditor/config.js" 50 + "libs/ckeditor/ckeditor.js"
53 ] 51 ]
54 }, 52 },
55 "bootstrap-sass": { 53 "bootstrap-sass": {
@@ -30,7 +30,8 @@ gulp.task('partials', function () { @@ -30,7 +30,8 @@ gulp.task('partials', function () {
30 quotes: true 30 quotes: true
31 })) 31 }))
32 .pipe($.angularTemplatecache('templateCacheHtml-'+partialPath+'.js', { 32 .pipe($.angularTemplatecache('templateCacheHtml-'+partialPath+'.js', {
33 - module: 'noosferoApp', 33 + module: 'noosfero.templates.' + partialPath,
  34 + standalone: true,
34 root: partialPath 35 root: partialPath
35 })) 36 }))
36 .pipe(gulp.dest(conf.paths.tmp + '/partials/'))); 37 .pipe(gulp.dest(conf.paths.tmp + '/partials/')));
@@ -81,7 +82,6 @@ gulp.task('html', ['inject', 'partials'], function () { @@ -81,7 +82,6 @@ gulp.task('html', ['inject', 'partials'], function () {
81 .pipe($.revReplace({prefix: noosferoThemePrefix})) 82 .pipe($.revReplace({prefix: noosferoThemePrefix}))
82 .pipe(htmlFilter) 83 .pipe(htmlFilter)
83 .pipe($.replace('/bower_components/ng-ckeditor/libs/ckeditor/', noosferoThemePrefix + 'ng-ckeditor/libs/ckeditor/')) 84 .pipe($.replace('/bower_components/ng-ckeditor/libs/ckeditor/', noosferoThemePrefix + 'ng-ckeditor/libs/ckeditor/'))
84 - .pipe($.replace('/bower_components/ng-ckeditor/ng-ckeditor.min.js', noosferoThemePrefix + 'ng-ckeditor/ng-ckeditor.min.js'))  
85 .pipe($.minifyHtml({ 85 .pipe($.minifyHtml({
86 empty: true, 86 empty: true,
87 spare: true, 87 spare: true,
@@ -103,7 +103,7 @@ gulp.task('fonts', function () { @@ -103,7 +103,7 @@ gulp.task('fonts', function () {
103 }); 103 });
104 104
105 gulp.task('ckeditor', function () { 105 gulp.task('ckeditor', function () {
106 - conf.wiredep.exclude.push(/ckeditor/); // exclude ckeditor from build to improve performance 106 + conf.wiredep.exclude.push(/bower_components\/ng-ckeditor\/libs\/ckeditor/); // exclude ckeditor from build to improve performance
107 return gulp.src(['bower_components/ng-ckeditor/**/*']).pipe(gulp.dest(path.join(conf.paths.dist, '/ng-ckeditor'))); 107 return gulp.src(['bower_components/ng-ckeditor/**/*']).pipe(gulp.dest(path.join(conf.paths.dist, '/ng-ckeditor')));
108 }); 108 });
109 109
@@ -27,6 +27,10 @@ exports.configTheme = function(theme) { @@ -27,6 +27,10 @@ exports.configTheme = function(theme) {
27 exports.paths.theme = theme || "angular-default"; 27 exports.paths.theme = theme || "angular-default";
28 exports.paths.allSources = [exports.paths.src, path.join(exports.paths.themes, exports.paths.theme)]; 28 exports.paths.allSources = [exports.paths.src, path.join(exports.paths.themes, exports.paths.theme)];
29 exports.paths.dist = path.join("dist", exports.paths.theme); 29 exports.paths.dist = path.join("dist", exports.paths.theme);
  30 +
  31 + if(argv.skin) {
  32 + exports.paths.skin = argv.skin;
  33 + }
30 } 34 }
31 exports.configTheme(argv.theme); 35 exports.configTheme(argv.theme);
32 36
gulp/inject.js
@@ -3,6 +3,8 @@ @@ -3,6 +3,8 @@
3 var path = require('path'); 3 var path = require('path');
4 var gulp = require('gulp'); 4 var gulp = require('gulp');
5 var conf = require('./conf'); 5 var conf = require('./conf');
  6 +var map = require('map-stream');
  7 +var transform = require('vinyl-transform');
6 8
7 var $ = require('gulp-load-plugins')(); 9 var $ = require('gulp-load-plugins')();
8 10
@@ -40,3 +42,34 @@ gulp.task('inject', ['scripts', 'styles'], function () { @@ -40,3 +42,34 @@ gulp.task('inject', ['scripts', 'styles'], function () {
40 .pipe(wiredep(_.extend({}, conf.wiredep))) 42 .pipe(wiredep(_.extend({}, conf.wiredep)))
41 .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve'))); 43 .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve')));
42 }); 44 });
  45 +
  46 +/**
  47 +* Replace the default theme skin to a npm config
  48 +*
  49 +* Uses "vinyl-transform" + "map-stream" to open the
  50 +* js file and rewrite the source file into the same
  51 +* destination
  52 +*
  53 +* @see https://www.npmjs.com/package/vinyl-transform
  54 +* @see https://www.npmjs.com/package/map-stream
  55 +*/
  56 +gulp.task('inject-skin', function () {
  57 +
  58 + if(conf.paths.skin) {
  59 +
  60 + $.util.log('Configured theme skin:', conf.paths.skin);
  61 +
  62 + var replaceSkin = transform(function(filename) {
  63 + return map(function(file, next) {
  64 + var contents = file.toString();
  65 + contents = contents.replace('skin-whbl', conf.paths.skin);
  66 + return next(null, contents);
  67 + });
  68 + });
  69 +
  70 + gulp.src(path.join(conf.paths.src,'./noosfero.js'))
  71 + .pipe(replaceSkin)
  72 + .pipe(gulp.dest(conf.paths.src));
  73 + }
  74 +
  75 +});
gulp/scripts.js
@@ -9,7 +9,7 @@ var browserSync = require('browser-sync'); @@ -9,7 +9,7 @@ var browserSync = require('browser-sync');
9 var $ = require('gulp-load-plugins')(); 9 var $ = require('gulp-load-plugins')();
10 10
11 11
12 -gulp.task('scripts-reload', function() { 12 +gulp.task('scripts-reload', ['inject-skin'], function() {
13 return buildScripts() 13 return buildScripts()
14 .pipe(browserSync.stream()); 14 .pipe(browserSync.stream());
15 }); 15 });
@@ -8,10 +8,11 @@ @@ -8,10 +8,11 @@
8 "ng-forward": "0.0.1-alpha.12" 8 "ng-forward": "0.0.1-alpha.12"
9 }, 9 },
10 "config": { 10 "config": {
11 - "theme": "angular-default" 11 + "theme": "angular-default",
  12 + "skin": "skin-whbl"
12 }, 13 },
13 "scripts": { 14 "scripts": {
14 - "build": "webpack; gulp clean && gulp --theme=$npm_package_config_theme build", 15 + "build": "webpack; gulp clean && gulp --theme=$npm_package_config_theme --skin=$npm_package_config_skin build",
15 "build-all": "gulp clean && for d in ./themes/* ; do (gulp --theme=`basename $d` build); done", 16 "build-all": "gulp clean && for d in ./themes/* ; do (gulp --theme=`basename $d` build); done",
16 "webpack": "webpack", 17 "webpack": "webpack",
17 "karma": "concurrently \"webpack -w\" \"karma start\"", 18 "karma": "concurrently \"webpack -w\" \"karma start\"",
@@ -23,7 +24,7 @@ @@ -23,7 +24,7 @@
23 "test": "webpack -w --test", 24 "test": "webpack -w --test",
24 "jenkins": "webpack && karma start --single-run", 25 "jenkins": "webpack && karma start --single-run",
25 "postinstall": "bower install; typings install; cd dev-scripts; typings install; cd ../; npm run fix-jqlite", 26 "postinstall": "bower install; typings install; cd dev-scripts; typings install; cd ../; npm run fix-jqlite",
26 - "start": "concurrently \"webpack -w\" \"gulp --theme=$npm_package_config_theme serve\"", 27 + "start": "concurrently \"webpack -w\" \"gulp --theme=$npm_package_config_theme --skin=$npm_package_config_skin serve\"",
27 "generate-indexes": "ts-node --project ./dev-scripts ./dev-scripts/generate-index-modules.ts", 28 "generate-indexes": "ts-node --project ./dev-scripts ./dev-scripts/generate-index-modules.ts",
28 "fix-jqlite": "ts-node --project ./dev-scripts dev-scripts/fix-jqlite.ts" 29 "fix-jqlite": "ts-node --project ./dev-scripts dev-scripts/fix-jqlite.ts"
29 }, 30 },
@@ -46,8 +47,8 @@ @@ -46,8 +47,8 @@
46 "gulp-eslint": "~1.0.0", 47 "gulp-eslint": "~1.0.0",
47 "gulp-filter": "~3.0.1", 48 "gulp-filter": "~3.0.1",
48 "gulp-flatten": "~0.2.0", 49 "gulp-flatten": "~0.2.0",
49 - "gulp-insert": "^0.5.0",  
50 "gulp-inject": "~3.0.0", 50 "gulp-inject": "~3.0.0",
  51 + "gulp-insert": "^0.5.0",
51 "gulp-load-plugins": "~0.10.0", 52 "gulp-load-plugins": "~0.10.0",
52 "gulp-merge-json": "^0.4.0", 53 "gulp-merge-json": "^0.4.0",
53 "gulp-minify-css": "~1.2.1", 54 "gulp-minify-css": "~1.2.1",
@@ -81,6 +82,7 @@ @@ -81,6 +82,7 @@
81 "karma-webpack": "^1.7.0", 82 "karma-webpack": "^1.7.0",
82 "lodash": "~3.10.1", 83 "lodash": "~3.10.1",
83 "main-bower-files": "~2.9.0", 84 "main-bower-files": "~2.9.0",
  85 + "map-stream": "0.0.6",
84 "merge-stream": "^1.0.0", 86 "merge-stream": "^1.0.0",
85 "on-build-webpack": "^0.1.0", 87 "on-build-webpack": "^0.1.0",
86 "phantomjs": "~1.9.18", 88 "phantomjs": "~1.9.18",
@@ -96,6 +98,7 @@ @@ -96,6 +98,7 @@
96 "typescript": "^1.8.10", 98 "typescript": "^1.8.10",
97 "typings": "^0.6.8", 99 "typings": "^0.6.8",
98 "uglify-save-license": "~0.4.1", 100 "uglify-save-license": "~0.4.1",
  101 + "vinyl-transform": "^1.0.0",
99 "webpack": "^1.12.14", 102 "webpack": "^1.12.14",
100 "wiredep": "~2.2.2", 103 "wiredep": "~2.2.2",
101 "wrench": "~1.5.8", 104 "wrench": "~1.5.8",
src/app/environment/environment.component.ts
@@ -2,6 +2,7 @@ import {StateConfig, Component, Inject, provide} from 'ng-forward'; @@ -2,6 +2,7 @@ import {StateConfig, Component, Inject, provide} from 'ng-forward';
2 import {EnvironmentService} from "../../lib/ng-noosfero-api/http/environment.service"; 2 import {EnvironmentService} from "../../lib/ng-noosfero-api/http/environment.service";
3 import {NotificationService} from "../shared/services/notification.service"; 3 import {NotificationService} from "../shared/services/notification.service";
4 import {EnvironmentHomeComponent} from "./environment-home.component"; 4 import {EnvironmentHomeComponent} from "./environment-home.component";
  5 +import {SearchComponent} from "../search/search.component";
5 6
6 /** 7 /**
7 * @ngdoc controller 8 * @ngdoc controller
@@ -29,6 +30,18 @@ import {EnvironmentHomeComponent} from "./environment-home.component"; @@ -29,6 +30,18 @@ import {EnvironmentHomeComponent} from "./environment-home.component";
29 controllerAs: "vm" 30 controllerAs: "vm"
30 } 31 }
31 } 32 }
  33 + },
  34 + {
  35 + url: '^/search?query',
  36 + component: SearchComponent,
  37 + name: 'main.environment.search',
  38 + views: {
  39 + "mainBlockContent": {
  40 + templateUrl: "app/search/search.html",
  41 + controller: SearchComponent,
  42 + controllerAs: "ctrl"
  43 + }
  44 + }
32 } 45 }
33 ]) 46 ])
34 @Inject(EnvironmentService, "$state", "currentEnvironment") 47 @Inject(EnvironmentService, "$state", "currentEnvironment")
src/app/index.ts
@@ -6,7 +6,19 @@ import {AuthEvents} from "./login/auth-events"; @@ -6,7 +6,19 @@ import {AuthEvents} from "./login/auth-events";
6 6
7 declare var moment: any; 7 declare var moment: any;
8 8
9 -angular.module('noosfero.init', []).config(noosferoModuleConfig). 9 +// FIXME see a better way to declare template modules for dev mode
  10 +try {
  11 + angular.module('noosfero.templates.app');
  12 +} catch (error) {
  13 + angular.module('noosfero.templates.app', []);
  14 +}
  15 +try {
  16 + angular.module('noosfero.templates.plugins');
  17 +} catch (error) {
  18 + angular.module('noosfero.templates.plugins', []);
  19 +}
  20 +angular.module('noosfero.init', ['noosfero.templates.app', 'noosfero.templates.plugins']).
  21 + config(noosferoModuleConfig).
10 run(noosferoAngularRunBlock). 22 run(noosferoAngularRunBlock).
11 constant("moment", moment). 23 constant("moment", moment).
12 constant("AuthEvents", AuthEvents); 24 constant("AuthEvents", AuthEvents);
src/app/layout/navbar/navbar.html
@@ -44,6 +44,9 @@ @@ -44,6 +44,9 @@
44 <language-selector class="nav navbar-nav navbar-right"></language-selector> 44 <language-selector class="nav navbar-nav navbar-right"></language-selector>
45 </ul> 45 </ul>
46 <div ui-view="actions"></div> 46 <div ui-view="actions"></div>
  47 + <div class="nav navbar-nav search navbar-right">
  48 + <search-form></search-form>
  49 + </div>
47 </div> 50 </div>
48 </div> 51 </div>
49 </nav> 52 </nav>
src/app/layout/navbar/navbar.scss
@@ -34,6 +34,16 @@ @@ -34,6 +34,16 @@
34 text-align: center; 34 text-align: center;
35 } 35 }
36 36
  37 + .search {
  38 + .search-input {
  39 + height: 45px;
  40 + margin-top: 10px;
  41 + }
  42 + .input-group-btn {
  43 + padding-top: 10px;
  44 + }
  45 + }
  46 +
37 @media (max-width: $break-sm-max) { 47 @media (max-width: $break-sm-max) {
38 .navbar-toggle .fa-bars{ 48 .navbar-toggle .fa-bars{
39 font-size: 14pt; 49 font-size: 14pt;
src/app/layout/scss/_layout.scss
@@ -54,6 +54,5 @@ ul.nav &gt; li.toggler-container { @@ -54,6 +54,5 @@ ul.nav &gt; li.toggler-container {
54 @include make-row(); 54 @include make-row();
55 margin-left: 0px; 55 margin-left: 0px;
56 margin-right: 0px; 56 margin-right: 0px;
57 - background-color: #eeeeee;  
58 - background: linear-gradient(top, #eeeeee 0%, #c9d4e4 100%); 57 + background-color: #edecec;
59 } 58 }
60 \ No newline at end of file 59 \ No newline at end of file
src/app/layout/scss/skins/_whbl.scss
@@ -3,7 +3,7 @@ $whbl-sidebar-linkColor: #484848; @@ -3,7 +3,7 @@ $whbl-sidebar-linkColor: #484848;
3 $whbl-primary-color: #1E96D0; //blue 3 $whbl-primary-color: #1E96D0; //blue
4 $whbl-font-color: #16191c; 4 $whbl-font-color: #16191c;
5 5
6 -.skin-whbl { 6 +%skin-base {
7 #header-navbar { 7 #header-navbar {
8 background-color: $whbl-primary-color; 8 background-color: $whbl-primary-color;
9 } 9 }
@@ -180,27 +180,19 @@ $whbl-font-color: #16191c; @@ -180,27 +180,19 @@ $whbl-font-color: #16191c;
180 } 180 }
181 .pagination { 181 .pagination {
182 > li { 182 > li {
183 - > a,  
184 - > span,  
185 - > a:hover,  
186 - > span:hover,  
187 - > a:focus,  
188 - > span:focus,  
189 - > a:active,  
190 - > span:active {  
191 - color: $whbl-primary-color; 183 + &.active {
  184 + a {
  185 + background-color: $whbl-primary-color;
  186 + color: #FFF;
  187 + }
192 } 188 }
193 - }  
194 - > .active {  
195 - > a,  
196 - > span,  
197 - > a:hover,  
198 - > span:hover,  
199 - > a:focus,  
200 - > span:focus {  
201 - background-color: $whbl-primary-color;  
202 - border-color: $whbl-primary-color;  
203 - color: #fff; 189 + > a {
  190 + &:hover {
  191 + background-color: $whbl-primary-color;
  192 + color: #FFF;
  193 + }
  194 + background-color: #FFF;
  195 + color: $whbl-primary-color;
204 } 196 }
205 } 197 }
206 } 198 }
@@ -321,7 +313,7 @@ $whbl-font-color: #16191c; @@ -321,7 +313,7 @@ $whbl-font-color: #16191c;
321 } 313 }
322 314
323 @media (max-width: $break-sm-max) { 315 @media (max-width: $break-sm-max) {
324 - .skin-whbl { 316 + %skin-base {
325 #logo.navbar-brand > img.normal-logo.logo-white { 317 #logo.navbar-brand > img.normal-logo.logo-white {
326 display: block; 318 display: block;
327 } 319 }
@@ -333,3 +325,7 @@ $whbl-font-color: #16191c; @@ -333,3 +325,7 @@ $whbl-font-color: #16191c;
333 } 325 }
334 } 326 }
335 } 327 }
  328 +
  329 +.skin-whbl {
  330 + @extend %skin-base;
  331 +}
src/app/main/main.component.ts
@@ -40,6 +40,8 @@ import {SidebarComponent} from &quot;../layout/sidebar/sidebar.component&quot;; @@ -40,6 +40,8 @@ import {SidebarComponent} from &quot;../layout/sidebar/sidebar.component&quot;;
40 import {MainBlockComponent} from "../layout/blocks/main/main-block.component"; 40 import {MainBlockComponent} from "../layout/blocks/main/main-block.component";
41 import {HtmlEditorComponent} from "../shared/components/html-editor/html-editor.component"; 41 import {HtmlEditorComponent} from "../shared/components/html-editor/html-editor.component";
42 import {PermissionDirective} from "../shared/components/permission/permission.directive"; 42 import {PermissionDirective} from "../shared/components/permission/permission.directive";
  43 +import {SearchComponent} from "../search/search.component";
  44 +import {SearchFormComponent} from "../search/search-form/search-form.component";
43 45
44 /** 46 /**
45 * @ngdoc controller 47 * @ngdoc controller
@@ -100,7 +102,7 @@ export class EnvironmentContent { @@ -100,7 +102,7 @@ export class EnvironmentContent {
100 LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent, ProfileComponent, 102 LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent, ProfileComponent,
101 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent, 103 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent,
102 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, 104 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent,
103 - LoginBlockComponent, CustomContentComponent, PermissionDirective 105 + LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent
104 ].concat(plugins.mainComponents).concat(plugins.hotspots), 106 ].concat(plugins.mainComponents).concat(plugins.hotspots),
105 providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, 107 providers: [AuthService, SessionService, NotificationService, BodyStateClassesService,
106 "ngAnimate", "ngCookies", "ngStorage", "ngTouch", 108 "ngAnimate", "ngCookies", "ngStorage", "ngTouch",
src/app/search/search-form/search-form.component.spec.ts 0 → 100644
@@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
  1 +import {ComponentTestHelper, createClass} from "../../../spec/component-test-helper";
  2 +import {SearchFormComponent} from "./search-form.component";
  3 +import * as helpers from "../../../spec/helpers";
  4 +
  5 +const htmlTemplate: string = '<search-form></search-form>';
  6 +
  7 +describe("Components", () => {
  8 + describe("Search Form Component", () => {
  9 +
  10 + let helper: ComponentTestHelper<SearchFormComponent>;
  11 + let stateMock = jasmine.createSpyObj("$state", ["go"]);
  12 +
  13 + beforeEach(angular.mock.module("templates"));
  14 +
  15 + beforeEach((done) => {
  16 + let cls = createClass({
  17 + template: htmlTemplate,
  18 + directives: [SearchFormComponent],
  19 + providers: [helpers.createProviderToValue("$state", stateMock)]
  20 + });
  21 + helper = new ComponentTestHelper<SearchFormComponent>(cls, done);
  22 + });
  23 +
  24 + it("render a input for search query", () => {
  25 + expect(helper.find(".search-input").length).toEqual(1);
  26 + });
  27 +
  28 + it("go to search page when click on search button", () => {
  29 + helper.component.query = 'query';
  30 + helper.component.search();
  31 + expect(stateMock.go).toHaveBeenCalledWith('main.environment.search', { query: 'query' });
  32 + });
  33 + });
  34 +});
src/app/search/search-form/search-form.component.ts 0 → 100644
@@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
  1 +import {Component, Inject} from "ng-forward";
  2 +
  3 +@Component({
  4 + selector: 'search-form',
  5 + templateUrl: 'app/search/search-form/search-form.html'
  6 +})
  7 +@Inject("$state")
  8 +export class SearchFormComponent {
  9 +
  10 + query: string;
  11 +
  12 + constructor(private $state: ng.ui.IStateService) {
  13 + }
  14 +
  15 + ngOnInit() {
  16 + this.query = this.$state.params['query'];
  17 + }
  18 +
  19 + search() {
  20 + this.$state.go('main.environment.search', { query: this.query });
  21 + }
  22 +
  23 + isSearchPage() {
  24 + return "main.environment.search" === this.$state.current.name;
  25 + }
  26 +}
src/app/search/search-form/search-form.html 0 → 100644
@@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
  1 +<form class="navbar-form search-form" role="search" ng-if="!ctrl.isSearchPage()">
  2 + <div class="input-group">
  3 + <input type="text" class="search-input form-control" placeholder="Search" name="q" ng-model="ctrl.query">
  4 + <div class="input-group-btn">
  5 + <button class="btn btn-default" type="submit" (click)="ctrl.search()"><i class="fa fa-search fa-fw"></i></button>
  6 + </div>
  7 + </div>
  8 +</form>
src/app/search/search.component.spec.ts 0 → 100644
@@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
  1 +import {ComponentTestHelper, createClass} from "../../spec/component-test-helper";
  2 +import {SearchComponent} from "./search.component";
  3 +import * as helpers from "../../spec/helpers";
  4 +
  5 +const htmlTemplate: string = '<search></search>';
  6 +
  7 +describe("Components", () => {
  8 + describe("Search Component", () => {
  9 +
  10 + let helper: ComponentTestHelper<SearchComponent>;
  11 + let stateParams = { query: 'query' };
  12 + let articleService = jasmine.createSpyObj("ArticleService", ["search"]);
  13 + let result = Promise.resolve({ data: [{ id: 1 }], headers: (param: string) => { return 1; } });
  14 + articleService.search = jasmine.createSpy("search").and.returnValue(result);
  15 +
  16 + beforeEach(angular.mock.module("templates"));
  17 +
  18 + beforeEach((done) => {
  19 + let cls = createClass({
  20 + template: htmlTemplate,
  21 + directives: [SearchComponent],
  22 + providers: [
  23 + helpers.createProviderToValue("$stateParams", stateParams),
  24 + helpers.createProviderToValue("ArticleService", articleService)
  25 + ].concat(helpers.provideFilters("truncateFilter", "stripTagsFilter"))
  26 + });
  27 + helper = new ComponentTestHelper<SearchComponent>(cls, done);
  28 + });
  29 +
  30 + it("load first page with search results", () => {
  31 + expect(articleService.search).toHaveBeenCalledWith({ query: 'query', per_page: 10, page: 0 });
  32 + });
  33 +
  34 + it("display search results", () => {
  35 + expect(helper.all(".result").length).toEqual(1);
  36 + });
  37 + });
  38 +});
src/app/search/search.component.ts 0 → 100644
@@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
  1 +import {Component, Inject} from "ng-forward";
  2 +import {ArticleService} from "./../../lib/ng-noosfero-api/http/article.service";
  3 +
  4 +import {SearchFormComponent} from "./search-form/search-form.component";
  5 +
  6 +@Component({
  7 + selector: 'search',
  8 + templateUrl: 'app/search/search.html',
  9 + directives: [SearchFormComponent]
  10 +})
  11 +@Inject(ArticleService, "$stateParams", "$state")
  12 +export class SearchComponent {
  13 +
  14 + articles: noosfero.Article[];
  15 + query: string;
  16 + totalResults = 0;
  17 + perPage = 10;
  18 + currentPage: number = 0;
  19 +
  20 + constructor(private articleService: ArticleService, private $stateParams: ng.ui.IStateParamsService, private $state: ng.ui.IStateService) {
  21 + this.query = this.$stateParams['query'];
  22 + this.loadPage();
  23 + }
  24 +
  25 + search() {
  26 + this.$state.go('main.environment.search', { query: this.query });
  27 + }
  28 +
  29 + loadPage() {
  30 + let filters = {
  31 + query: this.query,
  32 + per_page: this.perPage,
  33 + page: this.currentPage
  34 + };
  35 + this.articleService.search(filters).then((result: noosfero.RestResult<noosfero.Article[]>) => {
  36 + this.totalResults = <number>result.headers("total");
  37 + this.articles = result.data;
  38 + });
  39 + }
  40 +}
src/app/search/search.html 0 → 100644
@@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
  1 +<form ng-submit="ctrl.search()">
  2 +<label for="query" ng-bind-html="'search.results.query.label' | translate"></label>
  3 +<input id="query" placeholder="{{'search.results.query.placeholder' | translate}}" type="search" class="search-box-title" ng-model="ctrl.query">
  4 +</form>
  5 +<div class="search-results">
  6 + <div class="summary">
  7 + {{"search.results.summary" | translate:{results: ctrl.totalResults}:"messageformat"}}
  8 + </div>
  9 + <div ng-repeat="article in ctrl.articles | orderBy: 'created_at':true" class="result">
  10 + <a class="title" ui-sref="main.profile.page({profile: article.profile.identifier, page: article.path})">
  11 + <h4 ng-bind="article.title"></h4>
  12 + </a>
  13 + <div class="info">
  14 + <a class="profile" ui-sref="main.profile.home({profile: article.profile.identifier})">
  15 + {{article.profile.name}}
  16 + </a>
  17 + <span class="bullet-separator">•</span>
  18 + <span class="time">
  19 + <span am-time-ago="article.created_at | dateFormat"></span>
  20 + </span>
  21 + </div>
  22 + <div class="post-lead" ng-bind-html="article.body | stripTags | truncate: 250: '...': true"></div>
  23 + </div>
  24 + <uib-pagination ng-model="ctrl.currentPage" total-items="ctrl.totalResults" class="pagination-sm center-block"
  25 + boundary-links="true" items-per-page="ctrl.perPage" ng-change="ctrl.loadPage()"
  26 + first-text="«" last-text="»" previous-text="‹" next-text="›">
  27 + </uib-pagination>
  28 +</div>
src/app/search/search.scss 0 → 100644
@@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
  1 +.search-results {
  2 + .summary {
  3 + color: #bbbbbb;
  4 + font-size: 13px;
  5 + border-top: 1px solid #ececec;
  6 + }
  7 + .result {
  8 + margin: 25px 0;
  9 +
  10 + .title {
  11 + h4 {
  12 + margin: 0;
  13 + }
  14 + }
  15 + .info {
  16 + .profile {
  17 + color: #6e9e7b;
  18 + }
  19 + .time {
  20 + color: #6e9e7b;
  21 + font-size: 12px;
  22 + }
  23 + .bullet-separator {
  24 + margin: 0 2px;
  25 + font-size: 10px;
  26 + color: #afd6ba;
  27 + }
  28 + }
  29 + }
  30 +}
  31 +
  32 +.search-box-title {
  33 + border: 0;
  34 + border-bottom: 1px solid rgba(0,0,0,.15);
  35 + border-radius: 0;
  36 + font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif;
  37 + letter-spacing: 0;
  38 + font-weight: 300;
  39 + font-style: normal;
  40 + font-size: 50px;
  41 + height: 80px;
  42 + padding: 0;
  43 + width: 100%;
  44 +}
  45 +
  46 +.search-box-title:focus {
  47 + outline: none;
  48 +}
0 \ No newline at end of file 49 \ No newline at end of file
src/app/shared/components/bootstrap-switcher/bootstrap-switcher.component.ts
@@ -17,16 +17,16 @@ export interface BootstrapSwitcherItem { @@ -17,16 +17,16 @@ export interface BootstrapSwitcherItem {
17 </button> 17 </button>
18 </div> 18 </div>
19 `, 19 `,
20 - inputs: ['label', 'options', 'defaultOption'], 20 + inputs: ['activeClass', 'defaultClass', 'label', 'options', 'defaultOption'],
21 outputs: ['onSwitch'] 21 outputs: ['onSwitch']
22 }) 22 })
23 export class BootstrapSwitcherComponent { 23 export class BootstrapSwitcherComponent {
24 - @Input('activeClass') activeClass: string = 'active btn-danger';  
25 - @Input('defaultClass') defaultClass: string = 'btn-default';  
26 - @Input('label') label: string;  
27 - @Input('options') options: BootstrapSwitcherItem[];  
28 - @Input('defaultOption') defaultOption: BootstrapSwitcherItem;  
29 - @Output('onSwitch') onSwitch: EventEmitter<BootstrapSwitcherItem> = new EventEmitter<BootstrapSwitcherItem>(); 24 + @Input() activeClass: string = 'active btn-danger';
  25 + @Input() defaultClass: string = 'btn-default';
  26 + @Input() label: string;
  27 + @Input() options: BootstrapSwitcherItem[];
  28 + @Input() defaultOption: BootstrapSwitcherItem;
  29 + @Output() onSwitch: EventEmitter<BootstrapSwitcherItem> = new EventEmitter<BootstrapSwitcherItem>();
30 30
31 selectedOption: BootstrapSwitcherItem = null; 31 selectedOption: BootstrapSwitcherItem = null;
32 32
src/index.html
@@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@
30 <script> 30 <script>
31 CKEDITOR_BASEPATH='/bower_components/ng-ckeditor/libs/ckeditor/'; 31 CKEDITOR_BASEPATH='/bower_components/ng-ckeditor/libs/ckeditor/';
32 </script> 32 </script>
  33 + <script src="/bower_components/ng-ckeditor/libs/ckeditor/ckeditor.js"></script>
33 34
34 <!-- build:js(src) scripts/vendor.js --> 35 <!-- build:js(src) scripts/vendor.js -->
35 <!-- bower:js --> 36 <!-- bower:js -->
@@ -38,15 +39,12 @@ @@ -38,15 +39,12 @@
38 <!-- endbuild --> 39 <!-- endbuild -->
39 40
40 <!-- build:js({.tmp/serve,.tmp/partials,src}) scripts/app.js --> 41 <!-- build:js({.tmp/serve,.tmp/partials,src}) scripts/app.js -->
41 - <script src="commons.js"></script>  
42 - <script src="vendor.bundle.js"></script>  
43 - <script src="noosfero.js"></script>  
44 <!-- inject:partials --> 42 <!-- inject:partials -->
45 <!-- angular templates will be automatically converted in js and inserted here --> 43 <!-- angular templates will be automatically converted in js and inserted here -->
46 <!-- endinject --> 44 <!-- endinject -->
  45 + <script src="commons.js"></script>
  46 + <script src="vendor.bundle.js"></script>
  47 + <script src="noosfero.js"></script>
47 <!-- endbuild --> 48 <!-- endbuild -->
48 -  
49 - <script src="/bower_components/ng-ckeditor/libs/ckeditor/ckeditor.js"></script>  
50 - <script src="/bower_components/ng-ckeditor/ng-ckeditor.min.js"></script>  
51 </body> 49 </body>
52 </html> 50 </html>
src/languages/en.json
@@ -77,5 +77,8 @@ @@ -77,5 +77,8 @@
77 "profile.custom_footer.label": "Footer", 77 "profile.custom_footer.label": "Footer",
78 "designMode.label": "In Design", 78 "designMode.label": "In Design",
79 "designMode.toggle.ON": "ON", 79 "designMode.toggle.ON": "ON",
80 - "designMode.toggle.OFF": "OFF" 80 + "designMode.toggle.OFF": "OFF",
  81 + "search.results.summary": "{results, plural, one{result} other{# results}}",
  82 + "search.results.query.label": "Search for:",
  83 + "search.results.query.placeholder": "Search"
81 } 84 }
src/languages/pt.json
@@ -77,5 +77,8 @@ @@ -77,5 +77,8 @@
77 "profile.custom_footer.label": "Rodapé", 77 "profile.custom_footer.label": "Rodapé",
78 "designMode.label": "Modo de Edição", 78 "designMode.label": "Modo de Edição",
79 "designMode.toggle.ON": "Ligado", 79 "designMode.toggle.ON": "Ligado",
80 - "designMode.toggle.OFF": "Desligado" 80 + "designMode.toggle.OFF": "Desligado",
  81 + "search.results.summary": "{results, plural, one{# resultado} other{# resultados}}",
  82 + "search.results.query.label": "Buscar:",
  83 + "search.results.query.placeholder": "Informe aqui sua busca"
81 } 84 }
src/lib/ng-noosfero-api/http/article.service.spec.ts
@@ -23,7 +23,7 @@ describe(&quot;Services&quot;, () =&gt; { @@ -23,7 +23,7 @@ describe(&quot;Services&quot;, () =&gt; {
23 it("should remove article", (done) => { 23 it("should remove article", (done) => {
24 let articleId = 1; 24 let articleId = 1;
25 $httpBackend.expectDELETE(`/api/v1/articles/${articleId}`).respond(200, { success: "true" }); 25 $httpBackend.expectDELETE(`/api/v1/articles/${articleId}`).respond(200, { success: "true" });
26 - articleService.remove(<noosfero.Article>{id: articleId}); 26 + articleService.remove(<noosfero.Article>{ id: articleId });
27 $httpBackend.flush(); 27 $httpBackend.flush();
28 $httpBackend.verifyNoOutstandingExpectation(); 28 $httpBackend.verifyNoOutstandingExpectation();
29 done(); 29 done();
@@ -32,7 +32,7 @@ describe(&quot;Services&quot;, () =&gt; { @@ -32,7 +32,7 @@ describe(&quot;Services&quot;, () =&gt; {
32 it("should return article children", (done) => { 32 it("should return article children", (done) => {
33 let articleId = 1; 33 let articleId = 1;
34 $httpBackend.expectGET(`/api/v1/articles/${articleId}/children`).respond(200, { articles: [{ name: "article1" }] }); 34 $httpBackend.expectGET(`/api/v1/articles/${articleId}/children`).respond(200, { articles: [{ name: "article1" }] });
35 - articleService.getChildren(<noosfero.Article>{id: articleId}).then((result: noosfero.RestResult<noosfero.Article[]>) => { 35 + articleService.getChildren(<noosfero.Article>{ id: articleId }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
36 expect(result.data).toEqual([{ name: "article1" }]); 36 expect(result.data).toEqual([{ name: "article1" }]);
37 done(); 37 done();
38 }); 38 });
@@ -42,7 +42,7 @@ describe(&quot;Services&quot;, () =&gt; { @@ -42,7 +42,7 @@ describe(&quot;Services&quot;, () =&gt; {
42 it("should get articles by profile", (done) => { 42 it("should get articles by profile", (done) => {
43 let profileId = 1; 43 let profileId = 1;
44 $httpBackend.expectGET(`/api/v1/profiles/${profileId}/articles`).respond(200, { articles: [{ name: "article1" }] }); 44 $httpBackend.expectGET(`/api/v1/profiles/${profileId}/articles`).respond(200, { articles: [{ name: "article1" }] });
45 - articleService.getByProfile(<noosfero.Profile>{id: profileId}).then((result: noosfero.RestResult<noosfero.Article[]>) => { 45 + articleService.getByProfile(<noosfero.Profile>{ id: profileId }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
46 expect(result.data).toEqual([{ name: "article1" }]); 46 expect(result.data).toEqual([{ name: "article1" }]);
47 done(); 47 done();
48 }); 48 });
@@ -52,7 +52,7 @@ describe(&quot;Services&quot;, () =&gt; { @@ -52,7 +52,7 @@ describe(&quot;Services&quot;, () =&gt; {
52 it("should get articles by profile with additional filters", (done) => { 52 it("should get articles by profile with additional filters", (done) => {
53 let profileId = 1; 53 let profileId = 1;
54 $httpBackend.expectGET(`/api/v1/profiles/${profileId}/articles?path=test`).respond(200, { articles: [{ name: "article1" }] }); 54 $httpBackend.expectGET(`/api/v1/profiles/${profileId}/articles?path=test`).respond(200, { articles: [{ name: "article1" }] });
55 - articleService.getByProfile(<noosfero.Profile>{id: profileId}, { path: 'test' }).then((result: noosfero.RestResult<noosfero.Article[]>) => { 55 + articleService.getByProfile(<noosfero.Profile>{ id: profileId }, { path: 'test' }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
56 expect(result.data).toEqual([{ name: "article1" }]); 56 expect(result.data).toEqual([{ name: "article1" }]);
57 done(); 57 done();
58 }); 58 });
@@ -62,7 +62,7 @@ describe(&quot;Services&quot;, () =&gt; { @@ -62,7 +62,7 @@ describe(&quot;Services&quot;, () =&gt; {
62 it("should get article children with additional filters", (done) => { 62 it("should get article children with additional filters", (done) => {
63 let articleId = 1; 63 let articleId = 1;
64 $httpBackend.expectGET(`/api/v1/articles/${articleId}/children?path=test`).respond(200, { articles: [{ name: "article1" }] }); 64 $httpBackend.expectGET(`/api/v1/articles/${articleId}/children?path=test`).respond(200, { articles: [{ name: "article1" }] });
65 - articleService.getChildren(<noosfero.Article>{id: articleId}, { path: 'test' }).then((result: noosfero.RestResult<noosfero.Article[]>) => { 65 + articleService.getChildren(<noosfero.Article>{ id: articleId }, { path: 'test' }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
66 expect(result.data).toEqual([{ name: "article1" }]); 66 expect(result.data).toEqual([{ name: "article1" }]);
67 done(); 67 done();
68 }); 68 });
@@ -71,14 +71,25 @@ describe(&quot;Services&quot;, () =&gt; { @@ -71,14 +71,25 @@ describe(&quot;Services&quot;, () =&gt; {
71 71
72 it("should create an article in a profile", (done) => { 72 it("should create an article in a profile", (done) => {
73 let profileId = 1; 73 let profileId = 1;
74 - let article: noosfero.Article = <any>{ id: null};  
75 - $httpBackend.expectPOST(`/api/v1/profiles/${profileId}/articles`, { article: article }).respond(200, {article: { id: 2 }});  
76 - articleService.createInProfile(<noosfero.Profile>{id: profileId}, article).then((result: noosfero.RestResult<noosfero.Article>) => { 74 + let article: noosfero.Article = <any>{ id: null };
  75 + $httpBackend.expectPOST(`/api/v1/profiles/${profileId}/articles`, { article: article }).respond(200, { article: { id: 2 } });
  76 + articleService.createInProfile(<noosfero.Profile>{ id: profileId }, article).then((result: noosfero.RestResult<noosfero.Article>) => {
77 expect(result.data).toEqual({ id: 2 }); 77 expect(result.data).toEqual({ id: 2 });
78 done(); 78 done();
79 }); 79 });
80 $httpBackend.flush(); 80 $httpBackend.flush();
81 }); 81 });
  82 +
  83 + it("should search for articles in environment", (done) => {
  84 + let profileId = 1;
  85 + let article: noosfero.Article = <any>{ id: null };
  86 + $httpBackend.expectGET(`/api/v1/search/article?query=query`).respond(200, { articles: [{ id: 2 }] });
  87 + articleService.search({ query: 'query' }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
  88 + expect(result.data).toEqual([{ id: 2 }]);
  89 + done();
  90 + });
  91 + $httpBackend.flush();
  92 + });
82 }); 93 });
83 94
84 95
src/lib/ng-noosfero-api/http/article.service.ts
@@ -118,6 +118,11 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; { @@ -118,6 +118,11 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; {
118 return this.listSubElements(<noosfero.Article>articleElement, "children", params); 118 return this.listSubElements(<noosfero.Article>articleElement, "children", params);
119 } 119 }
120 120
  121 + search(params: any): ng.IPromise<noosfero.RestResult<noosfero.Article[]>> {
  122 + let deferred = this.$q.defer<noosfero.RestResult<noosfero.Article[]>>();
  123 + let restRequest = this.restangularService.all("search").customGET('article', params);
  124 + restRequest.then(this.getHandleSuccessFunction(deferred)).catch(this.getHandleErrorFunction(deferred));
  125 + return deferred.promise;
  126 + }
121 127
122 } 128 }
123 -  
src/plugins/comment_paragraph/hotspot/export-comment-button.component.spec.ts 0 → 100644
@@ -0,0 +1,58 @@ @@ -0,0 +1,58 @@
  1 +import {ExportCommentButtonHotspotComponent} from "./export-comment-button.component";
  2 +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper';
  3 +import * as helpers from "../../../spec/helpers";
  4 +import {Provider} from 'ng-forward';
  5 +import {ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder';
  6 +import {PermissionDirective} from '../../../app/shared/components/permission/permission.directive';
  7 +
  8 +let htmlTemplate = '<export-comment-button-hotspot [article]="ctrl.article"></export-comment-button-hotspot>';
  9 +
  10 +describe("Components", () => {
  11 + describe("Export Comment Button Hotspot Component", () => {
  12 +
  13 + let serviceMock = jasmine.createSpyObj("CommentParagraphService", ["getArticle"]);
  14 +
  15 + let providers = [new Provider('CommentParagraphService', { useValue: serviceMock })]
  16 + .concat(helpers.provideFilters('translateFilter'));
  17 + let helper: ComponentTestHelper<ExportCommentButtonHotspotComponent>;
  18 +
  19 + beforeEach(angular.mock.module("templates"));
  20 +
  21 + beforeEach((done) => {
  22 + let cls = createClass({
  23 + template: htmlTemplate,
  24 + directives: [ExportCommentButtonHotspotComponent, PermissionDirective],
  25 + providers: providers,
  26 + properties: {
  27 + article: {}
  28 + }
  29 + });
  30 + helper = new ComponentTestHelper<ExportCommentButtonHotspotComponent>(cls, done);
  31 + });
  32 +
  33 + it('return true when comment paragraph is active', () => {
  34 + helper.component.article = <noosfero.Article>{ setting: { comment_paragraph_plugin_activate: true } };
  35 + helper.detectChanges();
  36 + expect(helper.component.isActivated()).toBeTruthy();
  37 + expect(helper.all('.export-comment-button').length).toEqual(1);
  38 + });
  39 +
  40 + it('return false when comment paragraph is not active', () => {
  41 + expect(helper.component.isActivated()).toBeFalsy();
  42 + expect(helper.all('.export-comment-button').length).toEqual(0);
  43 + });
  44 +
  45 + it('return false when article has no setting attribute', () => {
  46 + helper.component.article = <noosfero.Article>{};
  47 + helper.detectChanges();
  48 + expect(helper.component.isActivated()).toBeFalsy();
  49 + expect(helper.all('.export-comment-button').length).toEqual(0);
  50 + });
  51 +
  52 + it('not display export comment button when user does not have permission', () => {
  53 + helper.component.article = <noosfero.Article>{ setting: { comment_paragraph_plugin_activate: true } };
  54 + helper.detectChanges();
  55 + expect(helper.find('.export-comment-button').attr('style')).toEqual("display: none; ");
  56 + });
  57 + });
  58 +});
src/plugins/comment_paragraph/hotspot/export-comment-button.component.ts 0 → 100644
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +import { Input, Inject, Component } from "ng-forward";
  2 +import {Hotspot} from "../../../app/hotspot/hotspot.decorator";
  3 +import {CommentParagraphService} from "../http/comment-paragraph.service";
  4 +
  5 +@Component({
  6 + selector: "export-comment-button-hotspot",
  7 + templateUrl: "plugins/comment_paragraph/hotspot/export-comment-button.html",
  8 +})
  9 +@Inject(CommentParagraphService)
  10 +@Hotspot("article_extra_toolbar_buttons")
  11 +export class ExportCommentButtonHotspotComponent {
  12 +
  13 + @Input() article: noosfero.Article;
  14 + exportCommentPath: any;
  15 +
  16 + constructor(private commentParagraphService: CommentParagraphService) { }
  17 +
  18 + isActivated() {
  19 + this.exportCommentPath = ["/api/v1/articles/", this.article.id, "/comment_paragraph_plugin/export"].join("");
  20 + return this.article && this.article.setting && this.article.setting.comment_paragraph_plugin_activate;
  21 + }
  22 +
  23 +}
src/plugins/comment_paragraph/hotspot/export-comment-button.html 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +<a href='{{ctrl.exportCommentPath}}' target="_self"
  2 + class="btn btn-default btn-xs export-comment-button" ng-if="ctrl.isActivated()"
  3 + permission="ctrl.article.permissions" permission-action="allow_edit">
  4 + <i class="fa fa-fw fa-download"></i> {{"comment-paragraph-plugin.export" | translate}}
  5 +</a>
src/plugins/comment_paragraph/index.ts
1 import {AllowCommentComponent} from "./allow-comment/allow-comment.component"; 1 import {AllowCommentComponent} from "./allow-comment/allow-comment.component";
  2 +import {ExportCommentButtonHotspotComponent} from "./hotspot/export-comment-button.component";
2 import {CommentParagraphFormHotspotComponent} from "./hotspot/comment-paragraph-form.component"; 3 import {CommentParagraphFormHotspotComponent} from "./hotspot/comment-paragraph-form.component";
3 import {DiscussionEditorComponent} from "./article/cms/discussion-editor/discussion-editor.component"; 4 import {DiscussionEditorComponent} from "./article/cms/discussion-editor/discussion-editor.component";
4 import {CommentParagraphArticleContentHotspotComponent} from "./hotspot/article-content/article-content.component"; 5 import {CommentParagraphArticleContentHotspotComponent} from "./hotspot/article-content/article-content.component";
5 import {DiscussionBlockComponent} from "./block/discussion/discussion-block.component"; 6 import {DiscussionBlockComponent} from "./block/discussion/discussion-block.component";
6 7
7 export let mainComponents: any = [AllowCommentComponent, DiscussionEditorComponent, DiscussionBlockComponent]; 8 export let mainComponents: any = [AllowCommentComponent, DiscussionEditorComponent, DiscussionBlockComponent];
8 -export let hotspots: any = [CommentParagraphFormHotspotComponent, CommentParagraphArticleContentHotspotComponent]; 9 +export let hotspots: any = [ExportCommentButtonHotspotComponent, CommentParagraphFormHotspotComponent, CommentParagraphArticleContentHotspotComponent];
src/plugins/comment_paragraph/languages/en.json
1 { 1 {
2 "comment-paragraph-plugin.title": "Paragraph Comments", 2 "comment-paragraph-plugin.title": "Paragraph Comments",
  3 + "comment-paragraph-plugin.export": "Export Comments",
3 "comment-paragraph-plugin.discussion.editor.start_date.label": "From", 4 "comment-paragraph-plugin.discussion.editor.start_date.label": "From",
4 "comment-paragraph-plugin.discussion.editor.end_date.label": "To", 5 "comment-paragraph-plugin.discussion.editor.end_date.label": "To",
5 "comment-paragraph-plugin.discussion.header": "Open for comments", 6 "comment-paragraph-plugin.discussion.header": "Open for comments",
src/plugins/comment_paragraph/languages/pt.json
1 { 1 {
2 "comment-paragraph-plugin.title": "Comentários por Parágrafo", 2 "comment-paragraph-plugin.title": "Comentários por Parágrafo",
  3 + "comment-paragraph-plugin.export": "Exportar Comentários",
3 "comment-paragraph-plugin.discussion.editor.start_date.label": "De", 4 "comment-paragraph-plugin.discussion.editor.start_date.label": "De",
4 "comment-paragraph-plugin.discussion.editor.end_date.label": "Até", 5 "comment-paragraph-plugin.discussion.editor.end_date.label": "Até",
5 "comment-paragraph-plugin.discussion.header": "Aberto para comentários", 6 "comment-paragraph-plugin.discussion.header": "Aberto para comentários",
themes/angular-participa-consulta/app/blocks.scss
  1 +%panel-head-theme {
  2 + border: none;
  3 + border-radius: 3px 3px 0 0;
  4 + font-family: "Ubuntu Medium";
  5 + font-size: 15px;
  6 + font-variant: normal;
  7 + font-weight: normal;
  8 + line-height: 30px;
  9 + padding: 15px 15px 15px 15px;
  10 + text-transform: capitalize;
  11 + background-size: 30px 30px;
  12 + background: #DAE1C4 !important;
  13 + color: #4F9CAC;
  14 +}
  15 +
1 .block-head-with-icon { 16 .block-head-with-icon {
2 padding: 15px 15px 15px 55px; 17 padding: 15px 15px 15px 55px;
3 } 18 }
4 19
  20 +.content-wrapper .block .panel-heading,
  21 +.content-wrapper .side-options .panel-heading {
  22 + @extend %panel-head-theme;
  23 +}
  24 +
5 .content-wrapper .block { 25 .content-wrapper .block {
6 - .panel-heading {  
7 - border: none;  
8 - border-radius: 3px 3px 0 0;  
9 - font-family: "Ubuntu Medium";  
10 - font-size: 15px;  
11 - font-variant: normal;  
12 - font-weight: normal;  
13 - line-height: 30px;  
14 - padding: 15px 15px 15px 15px;  
15 - text-transform: capitalize;  
16 - background-size: 30px 30px;  
17 - background: #DAE1C4;  
18 - color: #4F9CAC;  
19 - }  
20 26
21 &.membersblock { 27 &.membersblock {
22 .panel-heading { 28 .panel-heading {
themes/angular-participa-consulta/app/navbar.scss
  1 +.skin-yellow {
  2 + .navbar-inverse .navbar-toggle {
  3 + border-color: #D49F18;
  4 + }
  5 +
  6 + .container-fluid .navbar-header .navbar-toggle {
  7 + &:hover, &:focus {
  8 + background-color: #f5b025;
  9 + }
  10 + }
  11 +}
  12 +
1 .navbar { 13 .navbar {
2 min-height: 123px; 14 min-height: 123px;
3 background-color: #f9c404; 15 background-color: #f9c404;
themes/angular-participa-consulta/app/participa-consulta.scss
  1 +$selected-color: #f6c445;
  2 +
1 @font-face { 3 @font-face {
2 font-family: 'Ubuntu'; 4 font-family: 'Ubuntu';
3 font-weight: 300; 5 font-weight: 300;
@@ -19,9 +21,51 @@ @@ -19,9 +21,51 @@
19 src: url('../assets/fonts/participa-consulta/Ubuntu-RI.ttf'); 21 src: url('../assets/fonts/participa-consulta/Ubuntu-RI.ttf');
20 } 22 }
21 23
22 -.skin-whbl .notifications-list .item-footer {  
23 - background: #DAE1C4;  
24 - color: #4F9CAC; 24 +.skin-yellow {
  25 + @extend %skin-base;
  26 +
  27 + .notifications-list .item-footer {
  28 + background: #DAE1C4;
  29 + color: #4F9CAC;
  30 + }
  31 +
  32 + .navbar-nav .open > a,
  33 + .navbar-nav .open > a:hover,
  34 + .navbar-nav .open > a:focus {
  35 + background-color: $selected-color;
  36 + color: #000;
  37 + }
  38 +
  39 + .nav .open > a,
  40 + .nav .open > a:hover,
  41 + .nav .open > a:focus {
  42 + border: none;
  43 + }
  44 +
  45 + .dropdown-menu {
  46 + li > a:hover {
  47 + color: #555;
  48 + background-color: #F7F7F7;
  49 + }
  50 +
  51 + .active > a,
  52 + .active > a:hover,
  53 + .active > a:focus {
  54 + color: #000;
  55 + background-color: #CCC;
  56 + }
  57 + }
  58 +
  59 + .nav .caret {
  60 + border-bottom-color: #fff !important;
  61 + border-top-color: #fff !important;
  62 + }
  63 +
  64 + .nav .open .caret {
  65 + border-bottom-color: #000 !important;
  66 + border-top-color: #000 !important;
  67 + }
  68 +
25 } 69 }
26 70
27 .profile-header, .profile-footer{ 71 .profile-header, .profile-footer{