Compare View
Commits (14)
-
The use of bootstrap to start the application broke the startup process.
-
The use of bootstrap to start the application broke the startup process.
-
Export comments Incorporado o botão de exportar comentários. See merge request !40
-
* theme-skin-yellow: New npm config 'skin' to allow sass by theme skins
-
…ith the query value in the current query param. changed the search.component to have a search text box to show then current query and allow the user query for new terms.
Showing
36 changed files
Show diff stats
README.md
@@ -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 |
bower.json
@@ -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": { |
gulp/build.js
@@ -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 |
gulp/conf.js
@@ -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 | }); |
package.json
@@ -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 > li.toggler-container { | @@ -54,6 +54,5 @@ ul.nav > 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 "../layout/sidebar/sidebar.component"; | @@ -40,6 +40,8 @@ import {SidebarComponent} from "../layout/sidebar/sidebar.component"; | ||
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 | +}); |
@@ -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 | +} |
@@ -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> |
@@ -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 | +}); |
@@ -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 | +} |
@@ -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> |
@@ -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("Services", () => { | @@ -23,7 +23,7 @@ describe("Services", () => { | ||
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("Services", () => { | @@ -32,7 +32,7 @@ describe("Services", () => { | ||
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("Services", () => { | @@ -42,7 +42,7 @@ describe("Services", () => { | ||
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("Services", () => { | @@ -52,7 +52,7 @@ describe("Services", () => { | ||
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("Services", () => { | @@ -62,7 +62,7 @@ describe("Services", () => { | ||
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("Services", () => { | @@ -71,14 +71,25 @@ describe("Services", () => { | ||
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<noosfero.Article> { | @@ -118,6 +118,11 @@ export class ArticleService extends RestangularService<noosfero.Article> { | ||
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{ |