Merge Request #37

Merged
noosfero-themes/angular-theme!37
Created by Victor Costa

Component to search for articles in the environment

Issue #49

Assignee: Ábner Oliveira
Milestone: 2016.06

Merged by Ábner Oliveira

Source branch has been removed
Commits (3)
3 participants
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/layout/navbar/navbar.html
@@ -43,6 +43,9 @@ @@ -43,6 +43,9 @@
43 <language-selector class="nav navbar-nav navbar-right"></language-selector> 43 <language-selector class="nav navbar-nav navbar-right"></language-selector>
44 </ul> 44 </ul>
45 <div ui-view="actions"></div> 45 <div ui-view="actions"></div>
  46 + <div class="nav navbar-nav search navbar-right">
  47 + <search-form></search-form>
  48 + </div>
46 </div> 49 </div>
47 </div> 50 </div>
48 </nav> 51 </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/skins/_whbl.scss
@@ -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 }
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 {
2
  • Me
    Michel Felipe @mfdeveloper (Edited )

    Esse componente é realmente necessário? Talvez utilizar somente o componente SearchComponent com condições para renderizar o form ou a lista de resultados. É apenas uma sugestão, avalie com calma o que seria mais interessante.

    Me peguei a pensar sobre isso pq esse componente só renderiza um HTML diferente, e um único método que na verdade, redireciona para uma rota! :)

    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    É necessário pois o form fica na navbar e o resultado da busca responde por uma rota específica.

    Choose File ...   File name...
    Cancel
  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[];
2
  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
1
  1 +<form ng-submit="ctrl.search()">
1
  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">
3
  • Me
    Michel Felipe @mfdeveloper (Edited )

    Para ficar mais claro e limpa essas condições, não seria melhor adicionar isso a um método do SearchComponent ? Pensando que um designer qualquer possa sobreescrever essa view em temas customizados, minimizar a quantidade de condições + especificas pode melhorar a legibilidade!

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper (Edited )

    Algo nessa linha:

    <div class="summary">
       {{ ctrl.resultsText }}
    </div>
    
    

    Onde ctrl.resultsText é um atributo ou método get do typescript que já retorna o texto traduzido a depender da condição que você adicionou acima, entende?

    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Não entendi o que você quis dizer nesse comentário.

    Choose File ...   File name...
    Cancel
  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;
1
  • Me
    Michel Felipe @mfdeveloper

    Essas cores não poderiam ser variáveis para serem reutilizadas e/ou sobrescritas em outros temas?

    Choose File ...   File name...
    Cancel
  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/languages/en.json
@@ -74,5 +74,8 @@ @@ -74,5 +74,8 @@
74 "profile.content.success.message": "Profile saved!", 74 "profile.content.success.message": "Profile saved!",
75 "custom_content.title": "Edit content", 75 "custom_content.title": "Edit content",
76 "profile.custom_header.label": "Header", 76 "profile.custom_header.label": "Header",
77 - "profile.custom_footer.label": "Footer" 77 + "profile.custom_footer.label": "Footer",
  78 + "search.results.summary": "{results, plural, one{result} other{# results}}",
  79 + "search.results.query.label": "Search for:",
  80 + "search.results.query.placeholder": "Search"
78 } 81 }
src/languages/pt.json
@@ -58,7 +58,7 @@ @@ -58,7 +58,7 @@
58 "article.remove.success.title": "Bom trabalho!", 58 "article.remove.success.title": "Bom trabalho!",
59 "article.remove.success.message": "Artigo removido!", 59 "article.remove.success.message": "Artigo removido!",
60 "article.remove.confirmation.title": "Tem certeza?", 60 "article.remove.confirmation.title": "Tem certeza?",
61 - "article.remove.confirmation.message": "Não será possível recuperar este artigo!", 61 + "article.remove.confirmation.message": "Não será possível recuperar este artigo!",
62 "article.basic_editor.visibility": "Visibilidade", 62 "article.basic_editor.visibility": "Visibilidade",
63 "article.basic_editor.visibility.public": "Público", 63 "article.basic_editor.visibility.public": "Público",
64 "article.basic_editor.visibility.private": "Privado", 64 "article.basic_editor.visibility.private": "Privado",
@@ -74,5 +74,8 @@ @@ -74,5 +74,8 @@
74 "profile.content.success.message": "Perfil salvo!", 74 "profile.content.success.message": "Perfil salvo!",
75 "custom_content.title": "Editar conteúdo", 75 "custom_content.title": "Editar conteúdo",
76 "profile.custom_header.label": "Cabeçalho", 76 "profile.custom_header.label": "Cabeçalho",
77 - "profile.custom_footer.label": "Rodapé" 77 + "profile.custom_footer.label": "Rodapé",
  78 + "search.results.summary": "{results, plural, one{# resultado} other{# resultados}}",
3
  • Me
    Michel Felipe @mfdeveloper (Edited )

    Perfeito @vfcosta . Era isso q estava em dúvida !!

    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Está traduzido, os termos results e plural pertencem a forma de traduzir com o messageformat (para especificar termos em plural)

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Não deveria ser traduzido para o pt-br esse texto? (results, plural...). É alguma restrição da paginação?

    Choose File ...   File name...
    Cancel
  79 + "search.results.query.label": "Buscar:",
  80 + "search.results.query.placeholder": "Informe aqui sua busca"
78 } 81 }
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));
1
  • Me
    Michel Felipe @mfdeveloper

    Se o resultado da pesquisa retornar 404 do endpoint do backend, é exibida uma página de Not found específica? Como é tratado os diferentes códigos de erro do servidor?

    Choose File ...   File name...
    Cancel
  125 + return deferred.promise;
  126 + }
121 127
122 } 128 }
123 -  
  • Me
    Michel Felipe @mfdeveloper

    Reassigned to @mfdeveloper

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe started a discussion on the diff
    last updated by Victor Costa
    src/app/search/search-form/search-form.component.ts 0 → 100644
      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 {
    2
    • Me
      Michel Felipe @mfdeveloper (Edited )

      Esse componente é realmente necessário? Talvez utilizar somente o componente SearchComponent com condições para renderizar o form ou a lista de resultados. É apenas uma sugestão, avalie com calma o que seria mais interessante.

      Me peguei a pensar sobre isso pq esse componente só renderiza um HTML diferente, e um único método que na verdade, redireciona para uma rota! :)

      Choose File ...   File name...
      Cancel
    • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
      Victor Costa @vfcosta

      É necessário pois o form fica na navbar e o resultado da busca responde por uma rota específica.

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the diff
    last updated by Victor Costa
    src/app/search/search.component.ts 0 → 100644
      1 +import {Component, Inject} from "ng-forward";
      2 +import {ArticleService} from "./../../lib/ng-noosfero-api/http/article.service";
      3 +
      4 +@Component({
      5 + selector: 'search',
      6 + templateUrl: 'app/search/search.html'
      7 +})
      8 +@Inject(ArticleService, "$stateParams")
      9 +export class SearchComponent {
      10 +
      11 + articles: noosfero.Article[];
      12 + query: string;
      13 + totalResults = 0;
      14 + perPage = 10;
    2
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/search/search.html 0 → 100644
      1 +<h2>Search</h2>
      2 +
      3 +<div class="search-results">
      4 + <div class="summary">
      5 + {{"search.results.summary" | translate:{results: ctrl.totalResults}:"messageformat"}}
    3
    • Me
      Michel Felipe @mfdeveloper (Edited )

      Para ficar mais claro e limpa essas condições, não seria melhor adicionar isso a um método do SearchComponent ? Pensando que um designer qualquer possa sobreescrever essa view em temas customizados, minimizar a quantidade de condições + especificas pode melhorar a legibilidade!

      Choose File ...   File name...
      Cancel
    • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
      Victor Costa @vfcosta

      Não entendi o que você quis dizer nesse comentário.

      Choose File ...   File name...
      Cancel
    • Me
      Michel Felipe @mfdeveloper (Edited )

      Algo nessa linha:

      <div class="summary">
         {{ ctrl.resultsText }}
      </div>
      
      

      Onde ctrl.resultsText é um atributo ou método get do typescript que já retorna o texto traduzido a depender da condição que você adicionou acima, entende?

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/languages/pt.json
    74 74 "profile.content.success.message": "Perfil salvo!",
    75 75 "custom_content.title": "Editar conteúdo",
    76 76 "profile.custom_header.label": "Cabeçalho",
    77   - "profile.custom_footer.label": "Rodapé"
      77 + "profile.custom_footer.label": "Rodapé",
      78 + "search.results.summary": "{results, plural, one{result} other{# resultados}}"
    3
    • Me
      Michel Felipe @mfdeveloper

      Não deveria ser traduzido para o pt-br esse texto? (results, plural...). É alguma restrição da paginação?

      Choose File ...   File name...
      Cancel
    • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
      Victor Costa @vfcosta

      Está traduzido, os termos results e plural pertencem a forma de traduzir com o messageformat (para especificar termos em plural)

      Choose File ...   File name...
      Cancel
    • Me
      Michel Felipe @mfdeveloper (Edited )

      Perfeito @vfcosta . Era isso q estava em dúvida !!

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/lib/ng-noosfero-api/http/article.service.ts
    118 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));
    1
    • Me
      Michel Felipe @mfdeveloper

      Se o resultado da pesquisa retornar 404 do endpoint do backend, é exibida uma página de Not found específica? Como é tratado os diferentes códigos de erro do servidor?

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the diff
    last updated by Michel Felipe
    src/app/search/search.scss 0 → 100644
      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;
    1
    • Me
      Michel Felipe @mfdeveloper

      Essas cores não poderiam ser variáveis para serem reutilizadas e/ou sobrescritas em outros temas?

      Choose File ...   File name...
      Cancel
    Me
    Michel Felipe started a discussion on the outdated diff
    last updated by Michel Felipe
    src/app/search/search.html 0 → 100644
    ... ... @@ -0,0 +1,26 @@
  • Me
    Michel Felipe @mfdeveloper

    O campo de busca está desaparecendo no responsivo. O comportamento é este mesmo?

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper (Edited )

    O campo de busca está tomando muito espaço na navbar. Associado a mais botões, com o que @abner está fazendo na issue #91, o espaço pode ficar bastante prejudicado. Não seria melhor utilizar uma abordagem "retrátil" como em um desses dois temas abaixo?

    Cube Bootstrap

    Material Admin

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    O texto results logo abaixo do titulo no resultado da busca, não está traduzindo para pt-br quando modifico o idioma

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Assignee removed

    Choose File ...   File name...
    Cancel
  • 0deafa1501ec8dd687ee70f90488d592?s=40&d=identicon
    Ábner Oliveira @abner

    Reassigned to @abner

    Choose File ...   File name...
    Cancel
  • 0deafa1501ec8dd687ee70f90488d592?s=40&d=identicon
    Ábner Oliveira @abner

    Added 1 new commit:

    Choose File ...   File name...
    Cancel
  • 0deafa1501ec8dd687ee70f90488d592?s=40&d=identicon
    Ábner Oliveira @abner

    Added 1 new commit:

    • 575daccf - changed search.component and the search-form.component to fill the query field w…
    Choose File ...   File name...
    Cancel
  • 0deafa1501ec8dd687ee70f90488d592?s=40&d=identicon
    Ábner Oliveira @abner
    Choose File ...   File name...
    Cancel