From 69ff070c1cd62386ad769d7400c3436e37a6d7d9 Mon Sep 17 00:00:00 2001 From: Victor Costa Date: Tue, 31 May 2016 17:55:06 -0300 Subject: [PATCH] Component to search for articles in the environment --- src/app/environment/environment.component.ts | 13 +++++++++++++ src/app/layout/navbar/navbar.html | 3 +++ src/app/layout/navbar/navbar.scss | 10 ++++++++++ src/app/layout/scss/skins/_whbl.scss | 32 ++++++++++++-------------------- src/app/main/main.component.ts | 4 +++- src/app/search/search-form/search-form.component.spec.ts | 34 ++++++++++++++++++++++++++++++++++ src/app/search/search-form/search-form.component.ts | 17 +++++++++++++++++ src/app/search/search-form/search-form.html | 8 ++++++++ src/app/search/search.component.spec.ts | 38 ++++++++++++++++++++++++++++++++++++++ src/app/search/search.component.ts | 33 +++++++++++++++++++++++++++++++++ src/app/search/search.html | 26 ++++++++++++++++++++++++++ src/app/search/search.scss | 30 ++++++++++++++++++++++++++++++ src/languages/en.json | 3 ++- src/languages/pt.json | 3 ++- src/lib/ng-noosfero-api/http/article.service.spec.ts | 27 +++++++++++++++++++-------- src/lib/ng-noosfero-api/http/article.service.ts | 7 ++++++- 16 files changed, 256 insertions(+), 32 deletions(-) create mode 100644 src/app/search/search-form/search-form.component.spec.ts create mode 100644 src/app/search/search-form/search-form.component.ts create mode 100644 src/app/search/search-form/search-form.html create mode 100644 src/app/search/search.component.spec.ts create mode 100644 src/app/search/search.component.ts create mode 100644 src/app/search/search.html create mode 100644 src/app/search/search.scss diff --git a/src/app/environment/environment.component.ts b/src/app/environment/environment.component.ts index 352f3ee..547601b 100644 --- a/src/app/environment/environment.component.ts +++ b/src/app/environment/environment.component.ts @@ -2,6 +2,7 @@ import {StateConfig, Component, Inject, provide} from 'ng-forward'; import {EnvironmentService} from "../../lib/ng-noosfero-api/http/environment.service"; import {NotificationService} from "../shared/services/notification.service"; import {EnvironmentHomeComponent} from "./environment-home.component"; +import {SearchComponent} from "../search/search.component"; /** * @ngdoc controller @@ -29,6 +30,18 @@ import {EnvironmentHomeComponent} from "./environment-home.component"; controllerAs: "vm" } } + }, + { + url: '^/search?query', + component: SearchComponent, + name: 'main.environment.search', + views: { + "mainBlockContent": { + templateUrl: "app/search/search.html", + controller: SearchComponent, + controllerAs: "ctrl" + } + } } ]) @Inject(EnvironmentService, "$state", "currentEnvironment") diff --git a/src/app/layout/navbar/navbar.html b/src/app/layout/navbar/navbar.html index faff949..9b6e155 100644 --- a/src/app/layout/navbar/navbar.html +++ b/src/app/layout/navbar/navbar.html @@ -43,6 +43,9 @@
+ diff --git a/src/app/layout/navbar/navbar.scss b/src/app/layout/navbar/navbar.scss index 4064192..9bb908b 100644 --- a/src/app/layout/navbar/navbar.scss +++ b/src/app/layout/navbar/navbar.scss @@ -34,6 +34,16 @@ text-align: center; } + .search { + .search-input { + height: 45px; + margin-top: 10px; + } + .input-group-btn { + padding-top: 10px; + } + } + @media (max-width: $break-sm-max) { .navbar-toggle .fa-bars{ font-size: 14pt; diff --git a/src/app/layout/scss/skins/_whbl.scss b/src/app/layout/scss/skins/_whbl.scss index 146307f..d32357c 100644 --- a/src/app/layout/scss/skins/_whbl.scss +++ b/src/app/layout/scss/skins/_whbl.scss @@ -180,27 +180,19 @@ $whbl-font-color: #16191c; } .pagination { > li { - > a, - > span, - > a:hover, - > span:hover, - > a:focus, - > span:focus, - > a:active, - > span:active { - color: $whbl-primary-color; + &.active { + a { + background-color: $whbl-primary-color; + color: #FFF; + } } - } - > .active { - > a, - > span, - > a:hover, - > span:hover, - > a:focus, - > span:focus { - background-color: $whbl-primary-color; - border-color: $whbl-primary-color; - color: #fff; + > a { + &:hover { + background-color: $whbl-primary-color; + color: #FFF; + } + background-color: #FFF; + color: $whbl-primary-color; } } } diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts index 16b5a19..38088fe 100644 --- a/src/app/main/main.component.ts +++ b/src/app/main/main.component.ts @@ -40,6 +40,8 @@ import {SidebarComponent} from "../layout/sidebar/sidebar.component"; import {MainBlockComponent} from "../layout/blocks/main/main-block.component"; import {HtmlEditorComponent} from "../shared/components/html-editor/html-editor.component"; import {PermissionDirective} from "../shared/components/permission/permission.directive"; +import {SearchComponent} from "../search/search.component"; +import {SearchFormComponent} from "../search/search-form/search-form.component"; /** * @ngdoc controller @@ -100,7 +102,7 @@ export class EnvironmentContent { LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent, ProfileComponent, MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent, MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, - LoginBlockComponent, CustomContentComponent, PermissionDirective + LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent ].concat(plugins.mainComponents).concat(plugins.hotspots), providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, "ngAnimate", "ngCookies", "ngStorage", "ngTouch", diff --git a/src/app/search/search-form/search-form.component.spec.ts b/src/app/search/search-form/search-form.component.spec.ts new file mode 100644 index 0000000..b737766 --- /dev/null +++ b/src/app/search/search-form/search-form.component.spec.ts @@ -0,0 +1,34 @@ +import {ComponentTestHelper, createClass} from "../../../spec/component-test-helper"; +import {SearchFormComponent} from "./search-form.component"; +import * as helpers from "../../../spec/helpers"; + +const htmlTemplate: string = ''; + +describe("Components", () => { + describe("Search Form Component", () => { + + let helper: ComponentTestHelper; + let stateMock = jasmine.createSpyObj("$state", ["go"]); + + beforeEach(angular.mock.module("templates")); + + beforeEach((done) => { + let cls = createClass({ + template: htmlTemplate, + directives: [SearchFormComponent], + providers: [helpers.createProviderToValue("$state", stateMock)] + }); + helper = new ComponentTestHelper(cls, done); + }); + + it("render a input for search query", () => { + expect(helper.find(".search-input").length).toEqual(1); + }); + + it("go to search page when click on search button", () => { + helper.component.query = 'query'; + helper.component.search(); + expect(stateMock.go).toHaveBeenCalledWith('main.environment.search', { query: 'query' }); + }); + }); +}); diff --git a/src/app/search/search-form/search-form.component.ts b/src/app/search/search-form/search-form.component.ts new file mode 100644 index 0000000..c8978ed --- /dev/null +++ b/src/app/search/search-form/search-form.component.ts @@ -0,0 +1,17 @@ +import {Component, Inject} from "ng-forward"; + +@Component({ + selector: 'search-form', + templateUrl: 'app/search/search-form/search-form.html' +}) +@Inject("$state") +export class SearchFormComponent { + + query: string; + + constructor(private $state: ng.ui.IStateService) { } + + search() { + this.$state.go('main.environment.search', { query: this.query }); + } +} diff --git a/src/app/search/search-form/search-form.html b/src/app/search/search-form/search-form.html new file mode 100644 index 0000000..3c59af3 --- /dev/null +++ b/src/app/search/search-form/search-form.html @@ -0,0 +1,8 @@ + diff --git a/src/app/search/search.component.spec.ts b/src/app/search/search.component.spec.ts new file mode 100644 index 0000000..a3db88b --- /dev/null +++ b/src/app/search/search.component.spec.ts @@ -0,0 +1,38 @@ +import {ComponentTestHelper, createClass} from "../../spec/component-test-helper"; +import {SearchComponent} from "./search.component"; +import * as helpers from "../../spec/helpers"; + +const htmlTemplate: string = ''; + +describe("Components", () => { + describe("Search Component", () => { + + let helper: ComponentTestHelper; + let stateParams = { query: 'query' }; + let articleService = jasmine.createSpyObj("ArticleService", ["search"]); + let result = Promise.resolve({ data: [{ id: 1 }], headers: (param: string) => { return 1; } }); + articleService.search = jasmine.createSpy("search").and.returnValue(result); + + beforeEach(angular.mock.module("templates")); + + beforeEach((done) => { + let cls = createClass({ + template: htmlTemplate, + directives: [SearchComponent], + providers: [ + helpers.createProviderToValue("$stateParams", stateParams), + helpers.createProviderToValue("ArticleService", articleService) + ].concat(helpers.provideFilters("truncateFilter", "stripTagsFilter")) + }); + helper = new ComponentTestHelper(cls, done); + }); + + it("load first page with search results", () => { + expect(articleService.search).toHaveBeenCalledWith({ query: 'query', per_page: 10, page: 0 }); + }); + + it("display search results", () => { + expect(helper.all(".result").length).toEqual(1); + }); + }); +}); diff --git a/src/app/search/search.component.ts b/src/app/search/search.component.ts new file mode 100644 index 0000000..67ee04d --- /dev/null +++ b/src/app/search/search.component.ts @@ -0,0 +1,33 @@ +import {Component, Inject} from "ng-forward"; +import {ArticleService} from "./../../lib/ng-noosfero-api/http/article.service"; + +@Component({ + selector: 'search', + templateUrl: 'app/search/search.html' +}) +@Inject(ArticleService, "$stateParams") +export class SearchComponent { + + articles: noosfero.Article[]; + query: string; + totalResults = 0; + perPage = 10; + currentPage: number = 0; + + constructor(private articleService: ArticleService, private $stateParams: ng.ui.IStateParamsService) { + this.query = this.$stateParams['query']; + this.loadPage(); + } + + loadPage() { + let filters = { + query: this.query, + per_page: this.perPage, + page: this.currentPage + }; + this.articleService.search(filters).then((result: noosfero.RestResult) => { + this.totalResults = result.headers("total"); + this.articles = result.data; + }); + } +} diff --git a/src/app/search/search.html b/src/app/search/search.html new file mode 100644 index 0000000..e829ad9 --- /dev/null +++ b/src/app/search/search.html @@ -0,0 +1,26 @@ +

Search

+ +
+
+ {{"search.results.summary" | translate:{results: ctrl.totalResults}:"messageformat"}} +
+ + + +
diff --git a/src/app/search/search.scss b/src/app/search/search.scss new file mode 100644 index 0000000..8706e04 --- /dev/null +++ b/src/app/search/search.scss @@ -0,0 +1,30 @@ +.search-results { + .summary { + color: #bbbbbb; + font-size: 13px; + border-top: 1px solid #ececec; + } + .result { + margin: 25px 0; + + .title { + h4 { + margin: 0; + } + } + .info { + .profile { + color: #6e9e7b; + } + .time { + color: #6e9e7b; + font-size: 12px; + } + .bullet-separator { + margin: 0 2px; + font-size: 10px; + color: #afd6ba; + } + } + } +} diff --git a/src/languages/en.json b/src/languages/en.json index ffc7034..b5cf2f4 100644 --- a/src/languages/en.json +++ b/src/languages/en.json @@ -74,5 +74,6 @@ "profile.content.success.message": "Profile saved!", "custom_content.title": "Edit content", "profile.custom_header.label": "Header", - "profile.custom_footer.label": "Footer" + "profile.custom_footer.label": "Footer", + "search.results.summary": "{results, plural, one{result} other{# results}}" } diff --git a/src/languages/pt.json b/src/languages/pt.json index bf1d05d..0b0efe6 100644 --- a/src/languages/pt.json +++ b/src/languages/pt.json @@ -74,5 +74,6 @@ "profile.content.success.message": "Perfil salvo!", "custom_content.title": "Editar conteúdo", "profile.custom_header.label": "Cabeçalho", - "profile.custom_footer.label": "Rodapé" + "profile.custom_footer.label": "Rodapé", + "search.results.summary": "{results, plural, one{result} other{# resultados}}" } diff --git a/src/lib/ng-noosfero-api/http/article.service.spec.ts b/src/lib/ng-noosfero-api/http/article.service.spec.ts index 7440c51..f062eb8 100644 --- a/src/lib/ng-noosfero-api/http/article.service.spec.ts +++ b/src/lib/ng-noosfero-api/http/article.service.spec.ts @@ -23,7 +23,7 @@ describe("Services", () => { it("should remove article", (done) => { let articleId = 1; $httpBackend.expectDELETE(`/api/v1/articles/${articleId}`).respond(200, { success: "true" }); - articleService.remove({id: articleId}); + articleService.remove({ id: articleId }); $httpBackend.flush(); $httpBackend.verifyNoOutstandingExpectation(); done(); @@ -32,7 +32,7 @@ describe("Services", () => { it("should return article children", (done) => { let articleId = 1; $httpBackend.expectGET(`/api/v1/articles/${articleId}/children`).respond(200, { articles: [{ name: "article1" }] }); - articleService.getChildren({id: articleId}).then((result: noosfero.RestResult) => { + articleService.getChildren({ id: articleId }).then((result: noosfero.RestResult) => { expect(result.data).toEqual([{ name: "article1" }]); done(); }); @@ -42,7 +42,7 @@ describe("Services", () => { it("should get articles by profile", (done) => { let profileId = 1; $httpBackend.expectGET(`/api/v1/profiles/${profileId}/articles`).respond(200, { articles: [{ name: "article1" }] }); - articleService.getByProfile({id: profileId}).then((result: noosfero.RestResult) => { + articleService.getByProfile({ id: profileId }).then((result: noosfero.RestResult) => { expect(result.data).toEqual([{ name: "article1" }]); done(); }); @@ -52,7 +52,7 @@ describe("Services", () => { it("should get articles by profile with additional filters", (done) => { let profileId = 1; $httpBackend.expectGET(`/api/v1/profiles/${profileId}/articles?path=test`).respond(200, { articles: [{ name: "article1" }] }); - articleService.getByProfile({id: profileId}, { path: 'test' }).then((result: noosfero.RestResult) => { + articleService.getByProfile({ id: profileId }, { path: 'test' }).then((result: noosfero.RestResult) => { expect(result.data).toEqual([{ name: "article1" }]); done(); }); @@ -62,7 +62,7 @@ describe("Services", () => { it("should get article children with additional filters", (done) => { let articleId = 1; $httpBackend.expectGET(`/api/v1/articles/${articleId}/children?path=test`).respond(200, { articles: [{ name: "article1" }] }); - articleService.getChildren({id: articleId}, { path: 'test' }).then((result: noosfero.RestResult) => { + articleService.getChildren({ id: articleId }, { path: 'test' }).then((result: noosfero.RestResult) => { expect(result.data).toEqual([{ name: "article1" }]); done(); }); @@ -71,14 +71,25 @@ describe("Services", () => { it("should create an article in a profile", (done) => { let profileId = 1; - let article: noosfero.Article = { id: null}; - $httpBackend.expectPOST(`/api/v1/profiles/${profileId}/articles`, { article: article }).respond(200, {article: { id: 2 }}); - articleService.createInProfile({id: profileId}, article).then((result: noosfero.RestResult) => { + let article: noosfero.Article = { id: null }; + $httpBackend.expectPOST(`/api/v1/profiles/${profileId}/articles`, { article: article }).respond(200, { article: { id: 2 } }); + articleService.createInProfile({ id: profileId }, article).then((result: noosfero.RestResult) => { expect(result.data).toEqual({ id: 2 }); done(); }); $httpBackend.flush(); }); + + it("should search for articles in environment", (done) => { + let profileId = 1; + let article: noosfero.Article = { id: null }; + $httpBackend.expectGET(`/api/v1/search/article?query=query`).respond(200, { articles: [{ id: 2 }] }); + articleService.search({ query: 'query' }).then((result: noosfero.RestResult) => { + expect(result.data).toEqual([{ id: 2 }]); + done(); + }); + $httpBackend.flush(); + }); }); diff --git a/src/lib/ng-noosfero-api/http/article.service.ts b/src/lib/ng-noosfero-api/http/article.service.ts index 0805339..362dac5 100644 --- a/src/lib/ng-noosfero-api/http/article.service.ts +++ b/src/lib/ng-noosfero-api/http/article.service.ts @@ -118,6 +118,11 @@ export class ArticleService extends RestangularService { return this.listSubElements(articleElement, "children", params); } + search(params: any): ng.IPromise> { + let deferred = this.$q.defer>(); + let restRequest = this.restangularService.all("search").customGET('article', params); + restRequest.then(this.getHandleSuccessFunction(deferred)).catch(this.getHandleErrorFunction(deferred)); + return deferred.promise; + } } - -- libgit2 0.21.2