├── .gitignore ├── CNAME ├── README.md ├── angular-cli.json ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.json ├── package.json ├── protractor.conf.js ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── footer │ │ ├── footer.component.html │ │ ├── footer.component.scss │ │ ├── footer.component.spec.ts │ │ └── footer.component.ts │ ├── get-involved │ │ ├── get-involved.component.html │ │ ├── get-involved.component.scss │ │ ├── get-involved.component.spec.ts │ │ └── get-involved.component.ts │ ├── header │ │ ├── header.component.html │ │ ├── header.component.scss │ │ ├── header.component.spec.ts │ │ └── header.component.ts │ ├── how-we-work │ │ ├── how-we-work.component.html │ │ ├── how-we-work.component.scss │ │ ├── how-we-work.component.spec.ts │ │ ├── how-we-work.component.ts │ │ ├── tools │ │ │ ├── tools.component.html │ │ │ ├── tools.component.scss │ │ │ ├── tools.component.spec.ts │ │ │ └── tools.component.ts │ │ └── tracks │ │ │ ├── tracks.component.html │ │ │ ├── tracks.component.scss │ │ │ ├── tracks.component.spec.ts │ │ │ └── tracks.component.ts │ ├── index.ts │ ├── shared │ │ ├── convertService │ │ │ ├── convert.service.spec.ts │ │ │ └── convert.service.ts │ │ ├── dataService │ │ │ ├── data-service.service.spec.ts │ │ │ └── data-service.service.ts │ │ ├── datePipe │ │ │ ├── date-pipe.pipe.spec.ts │ │ │ └── date-pipe.pipe.ts │ │ ├── discourseService │ │ │ ├── discourse.service.spec.ts │ │ │ └── discourse.service.ts │ │ ├── jsonPipes │ │ │ ├── jsonpipes.pipe.spec.ts │ │ │ └── jsonpipes.pipe.ts │ │ ├── member-service │ │ │ ├── member.service.spec.ts │ │ │ └── member.service.ts │ │ └── replacePipe.ts │ ├── what-we-do │ │ ├── detail │ │ │ ├── detail.component.html │ │ │ ├── detail.component.scss │ │ │ ├── detail.component.spec.ts │ │ │ └── detail.component.ts │ │ ├── what-we-do.component.html │ │ ├── what-we-do.component.scss │ │ ├── what-we-do.component.spec.ts │ │ ├── what-we-do.component.ts │ │ ├── work │ │ │ ├── work.component.html │ │ │ ├── work.component.scss │ │ │ ├── work.component.spec.ts │ │ │ └── work.component.ts │ │ └── worklist │ │ │ ├── worklist.component.html │ │ │ ├── worklist.component.scss │ │ │ ├── worklist.component.spec.ts │ │ │ └── worklist.component.ts │ └── who-we-are │ │ ├── who-we-are.component.html │ │ ├── who-we-are.component.scss │ │ ├── who-we-are.component.spec.ts │ │ └── who-we-are.component.ts ├── assets │ ├── .gitkeep │ ├── css │ │ ├── bootstrap.min.css │ │ └── semantic.min.css │ ├── discourselink.ts │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── img │ │ ├── civic-participation.jpeg │ │ ├── open-api.jpeg │ │ ├── pdis-logo-final-clear.png │ │ ├── pdis-logo-final-inverse.png │ │ ├── pdis-logo-final.png │ │ └── pdis-team.JPG │ ├── js │ │ ├── bootstrap.min.js │ │ ├── jquery.min.js │ │ ├── particles.min.js │ │ ├── semantic.min.js │ │ └── wow.min.js │ └── particles.json ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.json └── typings.d.ts ├── tslint.json └── typings └── yamljs └── yamljs.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # IDEs and editors 12 | /.idea 13 | /.vscode 14 | .project 15 | .classpath 16 | *.launch 17 | .settings/ 18 | 19 | # misc 20 | /.sass-cache 21 | /connect.lock 22 | /coverage/* 23 | /libpeerconnection.log 24 | npm-debug.log 25 | testem.log 26 | #/typings 27 | 28 | # e2e 29 | /e2e/*.js 30 | /e2e/*.map 31 | 32 | #System Files 33 | .DS_Store 34 | Thumbs.db 35 | 36 | #yarn 37 | yarn.lock 38 | yarn-error.log -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | pdis.nat.gov.tw 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PDIS [Land] 2 | =========== 3 | 4 | Function 5 | -------- 6 | 7 | - a landing page 8 | - show people what PDIS do 9 | - open a channel for thoughts contribution 10 | - team building 11 | - mainly in english 12 | - anyone can subscribe to PDIS 13 | - anyone can leave their thoughts 14 | - [12/25] about us page 15 | - [12/25] lazy-load pages || images? 16 | 17 | Design Mockup 18 | ------------- 19 | 20 | https://xd.adobe.com/view/2299981f-e5c3-462e-be4f-286d50721151/ (xd) 21 | 22 | Comment 23 | ------- 24 | 25 | - please help :) 26 | - quick in, quick out (easy to read, understand) 27 | - more attractive & fun 28 | 29 | Development Environment Setup 30 | ----------------------------- 31 | 32 | - fork this project on github 33 | - install [nodejs 6.x.x](https://nodejs.org/en/) [npm](https://www.npmjs.com/) 34 | - install [angular-cli](https://cli.angular.io/) 35 | - ```npm install``` 36 | - ```npm start``` 37 | - Navigate to [http://localhost:4200/](http://localhost:4200/). The app will automatically reload if you change any of the source files. 38 | 39 | Deploy 40 | ------ 41 | 42 | - ```npm run deploy``` 43 | - ```npm run deploy-win``` -------------------------------------------------------------------------------- /angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "version": "2.0.0", 4 | "name": "pdis-web" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "test": "test.ts", 17 | "tsconfig": "tsconfig.json", 18 | "prefix": "app", 19 | "mobile": false, 20 | "styles": [ 21 | "styles.scss" 22 | ], 23 | "scripts": [], 24 | "environmentSource": "environments/environment.ts", 25 | "environments": { 26 | "dev": "environments/environment.ts", 27 | "prod": "environments/environment.prod.ts" 28 | } 29 | } 30 | ], 31 | "addons": [], 32 | "packages": [], 33 | "e2e": { 34 | "protractor": { 35 | "config": "./protractor.conf.js" 36 | } 37 | }, 38 | "test": { 39 | "karma": { 40 | "config": "./karma.conf.js" 41 | } 42 | }, 43 | "defaults": { 44 | "styleExt": "scss", 45 | "prefixInterfaces": false, 46 | "inline": { 47 | "style": false, 48 | "template": false 49 | }, 50 | "spec": { 51 | "class": false, 52 | "component": true, 53 | "directive": true, 54 | "module": false, 55 | "pipe": true, 56 | "service": true 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { PDISWEBv2Page } from './app.po'; 2 | 3 | describe('pdis-webv2 App', function() { 4 | let page: PDISWEBv2Page; 5 | 6 | beforeEach(() => { 7 | page = new PDISWEBv2Page(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class PDISWEBv2Page { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "outDir": "../dist/out-tsc-e2e", 10 | "sourceMap": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "../node_modules/@types" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pdis-web", 3 | "version": "2.0.0", 4 | "license": "MIT", 5 | "angular-cli": {}, 6 | "scripts": { 7 | "start": "ng serve --host 0.0.0.0", 8 | "lint": "tslint \"src/**/*.ts\"", 9 | "test": "ng test", 10 | "pree2e": "webdriver-manager update", 11 | "e2e": "protractor", 12 | "deploy": "rm -rf dist/* ; ng build -prod; echo pdis.nat.gov.tw > dist/CNAME ; gh-pages -d dist", 13 | "deploy-win": "rmdir /s /q dist & ng build -prod & echo pdis.nat.gov.tw > dist/CNAME & gh-pages -d dist" 14 | }, 15 | "private": true, 16 | "dependencies": { 17 | "@angular/common": "^2.1.0", 18 | "@angular/compiler": "^2.1.0", 19 | "@angular/compiler-cli": "^2.1.0", 20 | "@angular/core": "^2.1.0", 21 | "@angular/forms": "^2.1.0", 22 | "@angular/http": "^2.1.0", 23 | "@angular/platform-browser": "^2.1.0", 24 | "@angular/platform-browser-dynamic": "^2.1.0", 25 | "@angular/router": "^3.1.0", 26 | "angular-tag-cloud-module": "0.0.2", 27 | "core-js": "^2.4.1", 28 | "ng2-order-pipe": "^0.1.5", 29 | "node-sass": "^4.3.0", 30 | "rxjs": "^5.0.1", 31 | "ts-helpers": "^1.1.1", 32 | "yamljs": "^0.3.0", 33 | "zone.js": "^0.7.2" 34 | }, 35 | "devDependencies": { 36 | "@angular/cli": "^1.2.6", 37 | "@types/jasmine": "^2.5.53", 38 | "@types/node": "^6.0.42", 39 | "codelyzer": "~1.0.0-beta.3", 40 | "extract-text-webpack-plugin": "2.0.0-beta.4", 41 | "gh-pages": "^0.12.0", 42 | "jasmine-core": "2.4.1", 43 | "jasmine-spec-reporter": "2.5.0", 44 | "karma": "1.2.0", 45 | "karma-chrome-launcher": "^2.0.0", 46 | "karma-cli": "^1.0.1", 47 | "karma-jasmine": "^1.0.2", 48 | "karma-remap-istanbul": "^0.2.1", 49 | "protractor": "^5.1.2", 50 | "ts-node": "1.2.1", 51 | "tslint": "3.13.0", 52 | "typescript": "^2.4.2", 53 | "webdriver-manager": "10.2.5" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/docs/referenceConf.js 3 | 4 | /*global jasmine */ 5 | var SpecReporter = require('jasmine-spec-reporter'); 6 | 7 | exports.config = { 8 | allScriptsTimeout: 11000, 9 | specs: [ 10 | './e2e/**/*.e2e-spec.ts' 11 | ], 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | directConnect: true, 16 | baseUrl: 'http://localhost:4200/', 17 | framework: 'jasmine', 18 | jasmineNodeOpts: { 19 | showColors: true, 20 | defaultTimeoutInterval: 30000, 21 | print: function() {} 22 | }, 23 | useAllAngular2AppRoots: true, 24 | beforeLaunch: function() { 25 | require('ts-node').register({ 26 | project: 'e2e' 27 | }); 28 | }, 29 | onPrepare: function() { 30 | jasmine.getEnv().addReporter(new SpecReporter()); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { DetailComponent } from './what-we-do/detail/detail.component'; 2 | import { GetInvolvedComponent } from './get-involved/get-involved.component'; 3 | import { ToolsComponent } from './how-we-work/tools/tools.component'; 4 | import { TracksComponent } from './how-we-work/tracks/tracks.component'; 5 | import { HowWeWorkComponent } from './how-we-work/how-we-work.component'; 6 | import { WhatWeDoComponent } from './what-we-do/what-we-do.component'; 7 | import { NgModule } from '@angular/core'; 8 | import { Routes, RouterModule } from '@angular/router'; 9 | import { WorkComponent } from "./what-we-do/work/work.component"; 10 | import { WorklistComponent } from "./what-we-do/worklist/worklist.component"; 11 | import { WhoWeAreComponent } from "./who-we-are/who-we-are.component"; 12 | // import { NotFoundComponent } from "./404/404.component"; 13 | 14 | const routes: Routes = [ 15 | { path: 'what-we-do/works', component: WorklistComponent }, 16 | { path: 'what-we-do/values', component: WhatWeDoComponent }, 17 | { path: 'what-we-do/:id', component: DetailComponent }, 18 | { path: 'what-we-do', component: WhatWeDoComponent }, 19 | { path: 'how-we-work/tools', component: ToolsComponent }, 20 | { path: 'how-we-work/tracks', component: TracksComponent }, 21 | { path: 'how-we-work', component: HowWeWorkComponent }, 22 | { path: 'get-involved', component: GetInvolvedComponent }, 23 | { path: 'who-we-are', component: WhoWeAreComponent }, 24 | { path: '', redirectTo:'what-we-do', pathMatch:'full' }, 25 | // { path: '**', component: NotFoundComponent } 26 | ]; 27 | 28 | @NgModule({ 29 | imports: [RouterModule.forRoot(routes)], 30 | exports: [RouterModule], 31 | providers: [] 32 | }) 33 | export class AppRoutingModule { } 34 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PDIS/web/9fe094d88961ff11174b17529113fb16f2b48431/src/app/app.component.scss -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { AppComponent } from './app.component'; 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | declarations: [ 10 | AppComponent 11 | ], 12 | }); 13 | }); 14 | 15 | it('should create the app', async(() => { 16 | let fixture = TestBed.createComponent(AppComponent); 17 | let app = fixture.debugElement.componentInstance; 18 | expect(app).toBeTruthy(); 19 | })); 20 | 21 | it(`should have as title 'app works!'`, async(() => { 22 | let fixture = TestBed.createComponent(AppComponent); 23 | let app = fixture.debugElement.componentInstance; 24 | expect(app.title).toEqual('app works!'); 25 | })); 26 | 27 | it('should render title in a h1 tag', async(() => { 28 | let fixture = TestBed.createComponent(AppComponent); 29 | fixture.detectChanges(); 30 | let compiled = fixture.debugElement.nativeElement; 31 | expect(compiled.querySelector('h1').textContent).toContain('app works!'); 32 | })); 33 | }); 34 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | // import {enableProdMode} from '@angular/core'; 3 | // enableProdMode(); 4 | 5 | @Component({ 6 | selector: 'app-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.scss'] 9 | }) 10 | 11 | export class AppComponent { 12 | title = 'app works!'; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { replacePipe } from './shared/replacePipe'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { NgModule } from '@angular/core'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { HttpModule } from '@angular/http'; 6 | import { AppRoutingModule } from './app-routing.module'; 7 | import { ConvertService } from './shared/convertService/convert.service'; 8 | import { DataService } from './shared/dataService/data-service.service'; 9 | import { DiscourseService } from './shared/discourseService/discourse.service'; 10 | 11 | import { AppComponent } from './app.component'; 12 | import { WhatWeDoComponent } from './what-we-do/what-we-do.component'; 13 | import { HowWeWorkComponent } from './how-we-work/how-we-work.component'; 14 | import { ToolsComponent } from './how-we-work/tools/tools.component'; 15 | import { TracksComponent } from './how-we-work/tracks/tracks.component'; 16 | import { DetailComponent } from './what-we-do/detail/detail.component'; 17 | import { GetInvolvedComponent } from './get-involved/get-involved.component'; 18 | import { HeaderComponent } from './header/header.component'; 19 | import { FooterComponent } from './footer/footer.component'; 20 | import { JSONPipesPipe } from './shared/jsonPipes/jsonpipes.pipe'; 21 | import { DatePipePipe } from './shared/datePipe/date-pipe.pipe'; 22 | 23 | import { Discourselink } from './../assets/discourselink'; 24 | import { WorkComponent } from "app/what-we-do/work/work.component"; 25 | import { WorklistComponent } from "app/what-we-do/worklist/worklist.component"; 26 | import { WhoWeAreComponent } from "./who-we-are/who-we-are.component"; 27 | import { MemberService } from "./shared/member-service/member.service" 28 | 29 | 30 | @NgModule({ 31 | declarations: [ 32 | AppComponent, 33 | WhatWeDoComponent, 34 | HowWeWorkComponent, 35 | ToolsComponent, 36 | TracksComponent, 37 | DetailComponent, 38 | GetInvolvedComponent, 39 | HeaderComponent, 40 | FooterComponent, 41 | JSONPipesPipe, 42 | DatePipePipe, 43 | replacePipe, 44 | WorklistComponent, 45 | WorkComponent, 46 | WhoWeAreComponent 47 | ], 48 | imports: [ 49 | BrowserModule, 50 | FormsModule, 51 | HttpModule, 52 | AppRoutingModule 53 | ], 54 | providers: [ 55 | DataService, 56 | DiscourseService, 57 | ConvertService, 58 | Discourselink, 59 | MemberService 60 | ], 61 | bootstrap: [AppComponent] 62 | }) 63 | export class AppModule { } 64 | -------------------------------------------------------------------------------- /src/app/footer/footer.component.html: -------------------------------------------------------------------------------- 1 | 58 | -------------------------------------------------------------------------------- /src/app/footer/footer.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles.scss"; 2 | 3 | footer { 4 | 5 | max-width: $max_width; 6 | margin: 1em auto 0 auto; 7 | background: $sheet_color; 8 | 9 | .container { 10 | 11 | padding: 0; 12 | width: 100%; 13 | display: flex; 14 | flex-flow: row nowrap; 15 | @media screen and (max-width: $breakpoint_tablet) { 16 | flex-flow: column nowrap; 17 | } 18 | justify-content: center; 19 | align-items: stretch; 20 | 21 | .content { 22 | flex: 1 1 0px; 23 | padding: 0; 24 | 25 | .header { 26 | margin-top: 20px; 27 | text-align: center; 28 | //background: $sheet_color; 29 | //color: $back_color; 30 | // font-size: 3rem; 31 | // font-weight: 700; 32 | border-right: 1px solid $back_color; 33 | @media screen and (max-width: $breakpoint_tablet) { 34 | border-right: 0; 35 | } 36 | } 37 | &:last-child { 38 | h1 { 39 | border-right: 0; 40 | } 41 | } 42 | 43 | ul { 44 | // margin: 0 0 1em 0; 45 | list-style: none; 46 | padding: 0; 47 | 48 | li { 49 | line-height: 2em; 50 | position: relative; 51 | text-align: center; 52 | padding: 0 1em; 53 | a { 54 | display: block; 55 | //color: $sheet_color; 56 | } 57 | } 58 | } 59 | 60 | &.feed { 61 | flex: 2 1 0px; 62 | .rss { 63 | padding: 0 1em; 64 | // height: 20em; 65 | // @media screen and (max-width: $breakpoint_tablet) { 66 | // height: auto; 67 | // } 68 | overflow: auto; 69 | .item{ 70 | margin: 0 0 1em 0; 71 | display: inline-block; 72 | border-bottom: 1px solid lightgray; 73 | text-align: left; 74 | &:last-of-type{ 75 | border-bottom: 0; 76 | } 77 | .title{ 78 | font-weight: normal; 79 | } 80 | .content { 81 | color: $text_color; 82 | } 83 | .date{ 84 | color: $text_color; 85 | font-family: monospace; 86 | font-size: 80%; 87 | text-align: right; 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | 95 | .footer{ 96 | padding: 1em 0; 97 | text-align: center; 98 | a { 99 | color: black; 100 | opacity: 0.5; 101 | text-decoration: none; 102 | padding: 0 .5em; 103 | &:hover { 104 | opacity: 1; 105 | } 106 | img { 107 | width: 3em; 108 | vertical-align: bottom; 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/app/footer/footer.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { FooterComponent } from './footer.component'; 7 | 8 | describe('FooterComponent', () => { 9 | let component: FooterComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ FooterComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(FooterComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Http } from '@angular/http'; 2 | import { Component, OnInit } from '@angular/core'; 3 | 4 | @Component({ 5 | selector: 'app-footer', 6 | templateUrl: './footer.component.html', 7 | styleUrls: ['./footer.component.scss'] 8 | }) 9 | export class FooterComponent implements OnInit { 10 | 11 | constructor(private http: Http) { } 12 | 13 | rss = [] 14 | 15 | ngOnInit() { 16 | /* let each listed external link append with an icon */ 17 | let list = Array.from(document.querySelectorAll("li a[target='_blank']")) 18 | for(let e of list) { 19 | let icon = document.createElement("i") 20 | icon.className = "fa fa-external-link" 21 | icon.style.paddingLeft = "1ex" 22 | icon.style.fontSize = "50%" 23 | e.appendChild(icon) 24 | } 25 | 26 | /* api for rss */ 27 | /* https://www.google.com.tw/alerts/feeds/11419317490390774846/8364829402486342759 */ 28 | let query = 'https://api.rss2json.com/v1/api.json?rss_url=https%3A%2F%2Fwww.google.com.tw%2Falerts%2Ffeeds%2F11419317490390774846%2F8364829402486342759' 29 | this.http 30 | .get(query) 31 | .map(res => res.json()) 32 | .subscribe(data => this.rss = data.items) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/app/get-involved/get-involved.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Come to our forum at 6 | talk.pdis.tw 7 | 8 |

9 |

Or, tell us what you think about PDIS:

10 | 11 |
12 |
13 |
14 |
15 | SUBMIT 16 |
17 | 20 |
21 |
-------------------------------------------------------------------------------- /src/app/get-involved/get-involved.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles.scss"; 2 | 3 | .main-container{ 4 | margin-bottom: 2.5vw; 5 | } 6 | 7 | .get-involved-container{ 8 | text-align: center; 9 | padding-top: 2.5vw; 10 | margin-bottom: 2.5vw; 11 | } 12 | 13 | // .think-area{ 14 | // display: block; 15 | // width: 80%; 16 | // max-width: $max_width / 2; 17 | // margin: 3rem auto; 18 | // border: solid 1px lightgray; 19 | // background: rgba(255,255,255,0.5); 20 | // } 21 | 22 | /*.btn-pdis { 23 | position: relative; 24 | padding: 15px 20px; 25 | border-radius: 0px; 26 | border-color: black; 27 | border: 2px solid; 28 | color: #333; 29 | } 30 | 31 | .btn-pdis:hover { 32 | border: 2px solid black; 33 | color: #fff; 34 | background-color: #000; 35 | } 36 | 37 | input { 38 | margin: 10px 0 20px; 39 | width: 90% 40 | } 41 | 42 | .col-centered { 43 | text-align: center; 44 | margin: 0 auto; 45 | float: none; 46 | } 47 | 48 | .get-involved-container { 49 | margin: 0 auto; 50 | float: none; 51 | text-align: center; 52 | position: relative; 53 | z-index: 2; 54 | } 55 | 56 | .get-involved-comments { 57 | float: none; 58 | text-align: justify; 59 | position: relative; 60 | z-index: 2; 61 | } 62 | 63 | h1, h2, h3, h4, h5 { 64 | font-family: "Noto Serif", "NotoSerifRegular", sans-serif; 65 | font-weight: bold; 66 | line-height: normal; 67 | text-align: center; 68 | } 69 | 70 | .desc { 71 | font-family: "Noto Serif", "NotoSerifRegular", sans-serif; 72 | font-weight: normal; 73 | font-style: italic; 74 | line-height: normal; 75 | text-align: center; 76 | } 77 | 78 | .get-involved-button { 79 | font-family: "Franklin Gothic Condensed", "Franklin Gothic Medium", "Franklin Gothic"; 80 | } 81 | 82 | hr { 83 | -moz-border-bottom-colors: none; 84 | -moz-border-image: none; 85 | -moz-border-left-colors: none; 86 | -moz-border-right-colors: none; 87 | -moz-border-top-colors: none; 88 | border-color: #EEEEEE -moz-use-text-color #FFFFFF; 89 | border-style: solid none; 90 | border-width: 1px 0; 91 | margin: 18px 0; 92 | }*/ -------------------------------------------------------------------------------- /src/app/get-involved/get-involved.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { GetInvolvedComponent } from './get-involved.component'; 7 | 8 | describe('GetInvolvedComponent', () => { 9 | let component: GetInvolvedComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ GetInvolvedComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(GetInvolvedComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/get-involved/get-involved.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { DiscourseService } from '../shared/discourseService/discourse.service'; 3 | import { DataService } from '../shared/dataService/data-service.service'; 4 | 5 | @Component({ 6 | selector: 'app-get-involved', 7 | templateUrl: './get-involved.component.html', 8 | styleUrls: ['./get-involved.component.scss'] 9 | }) 10 | export class GetInvolvedComponent implements OnInit { 11 | 12 | tid; 13 | cid; 14 | think; 15 | res; 16 | err; 17 | 18 | constructor(private discoursesvcWwd: DiscourseService, private datasvcGi: DataService) { } 19 | 20 | 21 | ngOnInit() { 22 | } 23 | 24 | postDataToServer(raw: string) { 25 | this.tid = '67'; 26 | this.cid = '12'; 27 | this.res = ''; 28 | this.err = ''; 29 | this.discoursesvcWwd.postDiscoursePostRestful(raw, this.cid, this.tid) 30 | .subscribe( 31 | data => this.res = data, 32 | err => { 33 | this.err = err, 34 | console.log(err) 35 | }, 36 | () => console.log('POST Complete') 37 | ); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/app/header/header.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 42 | 92 | -------------------------------------------------------------------------------- /src/app/header/header.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles.scss"; 2 | 3 | /* to-top button */ 4 | #top { 5 | position: fixed; 6 | bottom: .5em; 7 | left: .5em; 8 | font-size: 2rem; 9 | line-height: 1.4; 10 | width: 1.5em; 11 | height: 1.5em; 12 | text-align: center; 13 | border-radius: 50%; 14 | background: rgba($highlight, 0.8); 15 | // box-shadow: -1px 2px 3px 0 rgba($text_color, 0.3); 16 | z-index: 99999; 17 | &:hover{ 18 | color: $text_color; 19 | background: rgba($highlight, 1); 20 | transform: scale(1.1) 21 | } 22 | } 23 | 24 | /* site-nav */ 25 | $nav-h: $nav-height; 26 | $nav-color: $sheet_color; 27 | $nav-bg-color: $link_color; 28 | $nav-sub-num: 2; 29 | nav.site-nav { 30 | position: fixed; 31 | @media only screen and (max-width: $breakpoint_tablet){ 32 | position: relative; 33 | } 34 | top: 0; 35 | &.hidenav { 36 | top: -100%; 37 | } 38 | transition: all .5s ease-in-out; 39 | z-index: 10000; 40 | width: 100%; 41 | background: $nav-bg-color; 42 | height: $nav-h; 43 | display: flex; 44 | flex-flow: row wrap; 45 | justify-content: space-between; 46 | align-items: stretch; 47 | padding: 0 calc((100% - #{$max_width})/2); 48 | box-shadow: 0 3px 5px 0 rgba($text_color, 0.3); 49 | .logo { 50 | text-align: center; 51 | flex: 0 0 auto; 52 | @media only screen and (max-width: $breakpoint_tablet){ 53 | flex: 1 0 100%; /* take all the width */ 54 | } 55 | height: 100%; 56 | padding: 3px 0; 57 | a { 58 | display: inline-block; 59 | box-shadow: -1px 2px 10px -3px black; 60 | border-radius: 3px; 61 | height: 100%; 62 | img{ 63 | max-height: 100%; 64 | width: auto; 65 | } 66 | &:hover{ 67 | transform: scale(1.05); 68 | } 69 | } 70 | } 71 | .menu { 72 | font-size: 100%; 73 | flex: 0 0 auto; 74 | @media only screen and (max-width: $breakpoint_tablet){ 75 | font-size: 80%; 76 | flex: 1 0 100%; /* take all the width */ 77 | } 78 | ul { 79 | padding: 0; 80 | display: flex; 81 | flex-flow: row nowrap; 82 | margin: 0 0 0 0; 83 | } 84 | ul>li { /* items */ 85 | background: $nav-bg-color; 86 | display: inline-block; 87 | position: relative; 88 | vertical-align: top; 89 | flex: 1 0 0px; 90 | a { /* item links */ 91 | font-family: $btn_font; 92 | color: $nav-color; 93 | height: $nav-h; 94 | padding: 1ch; 95 | display: flex; 96 | align-items: center; 97 | justify-content: center; 98 | text-align: center; 99 | @media only screen and (max-width: $breakpoint_tablet){ 100 | height: 100%; 101 | /* show sub menus */ 102 | &.active { 103 | color: $nav-bg-color; 104 | background: $nav-color; 105 | +ul { 106 | max-height: $nav-h * $nav-sub-num; 107 | } 108 | } 109 | } 110 | @media only screen and (min-width: $breakpoint_tablet){ 111 | /* show sub menus */ 112 | &:hover { 113 | color: $nav-bg-color; 114 | background: $nav-color; 115 | +ul { 116 | max-height: $nav-h * $nav-sub-num; 117 | } 118 | } 119 | } 120 | } 121 | /* hide sub menus */ 122 | ul { 123 | position: absolute; 124 | top: $nav-h; 125 | left: 0; 126 | max-height: 0; 127 | width: 100%; 128 | overflow: hidden; 129 | transition: all .3s ease; 130 | flex-flow: column; 131 | box-shadow: 0 3px 5px 0 rgba($text_color, 0.3); 132 | @media only screen and (max-width: $breakpoint_tablet){ 133 | top: 100%; 134 | } 135 | @media only screen and (min-width: $breakpoint_tablet){ 136 | /* show sub menus */ 137 | &:hover { 138 | max-height: $nav-h * $nav-sub-num; 139 | } 140 | } 141 | } 142 | } 143 | } 144 | } 145 | // *************************** LOGO section 146 | 147 | // .logo-container { 148 | // height: $nav-height; 149 | // // overflow: hidden; 150 | // .logo { 151 | // transition: all 0.3s ease; 152 | // width: $nav-height; 153 | // margin: 10px 0 0 15px; 154 | // box-shadow: 1px 5px 15px 2px rgba(20, 20, 20, 0.4); 155 | // } 156 | // } 157 | 158 | 159 | // @media screen and (max-width: $breakpoint_tablet) { 160 | // .logo-container { 161 | // height: $nav-height-mobile; 162 | // // overflow: hidden; 163 | // .logo { 164 | // // transition: all 0.3s ease; 165 | // width: $nav-height-mobile; 166 | // margin: 0 0 0 15px; 167 | // box-shadow: 0; 168 | // // margin-top: $nav-hight-mobile / 3; 169 | // } 170 | // } 171 | // } 172 | 173 | // *************************** nav bar section 174 | 175 | // .navbar-pdis { 176 | // background: linear-gradient(to bottom, #FFFFFF, #EFEFEF); 177 | // background: -moz-linear-gradient(to bottom, #FFFFFF, #EFEFEF); 178 | // background: -webkit-linear-gradient(to bottom, #FFFFFF, #EFEFEF); 179 | // border: 0; 180 | // // top: 5px; 181 | // box-shadow: 0 5px 15px 0px rgba(0, 0, 0, 0.5); 182 | // transition: top 0.3s ease; 183 | // } 184 | 185 | // .header-line { 186 | // background-color: $back_color; 187 | // height: 5px; 188 | // width: 100%; 189 | // position: fixed; 190 | // top: 0px; 191 | // z-index: 1030; 192 | // } 193 | 194 | // .nav-item { 195 | // border-left: solid 3px; 196 | // color: $back_color; 197 | // margin: 20px 0; 198 | // padding: 0 30px 0 5px; 199 | // font-size: 1.25rem; 200 | // font-family: $btn_font; 201 | // white-space: pre-line; 202 | // } 203 | 204 | // .nav-item:hover, .nav-item:focus, .nav-item:active { 205 | // color: $highlight; 206 | // background-color: transparent; 207 | // } 208 | 209 | // .nav-container { 210 | // max-width: $max_width; 211 | // margin: 0 auto; 212 | // } 213 | 214 | // #navbar li{ 215 | // // ************************* sub layer nav list 216 | // position: relative; 217 | 218 | // .nav-item+ul.nav-child{ 219 | // // display: none; 220 | // max-height: 0; 221 | // overflow: hidden; 222 | // transition: all 0.5s ease; 223 | 224 | // position: absolute; 225 | // top: 80%; 226 | // padding: 0; 227 | // list-style: none; 228 | // color: $back_color; 229 | // background-color: #F4F4F4; 230 | 231 | // a.nav-item{ 232 | // display: block; 233 | // margin: 0; 234 | // } 235 | // } 236 | 237 | // .nav-item:hover+ul.nav-child, .nav-item+ul.nav-child:hover{ 238 | // // display: block; 239 | // max-height: 8em; /* used to hide ul instead of display:none */ 240 | // color: $highlight; 241 | // } 242 | // } 243 | 244 | 245 | 246 | @media screen and (max-width: $breakpoint_tablet) { 247 | 248 | .icon-bar { 249 | background-color: $back_color; 250 | } 251 | .navbar-toggle { 252 | margin-top: ($nav-height-mobile - 34px) / 2; 253 | } 254 | .navbar-nav { 255 | margin: 0 -15px; 256 | } 257 | .nav>li { 258 | border-top: solid 1px #999 259 | } 260 | .nav-item { 261 | margin: 20px; 262 | white-space: nowrap; 263 | } 264 | .navbar-collapse { 265 | border: 0px; 266 | } 267 | 268 | #navbar li{ 269 | // ************************* sub layer nav list 270 | // position: relative; 271 | 272 | .nav-item+ul.nav-child{ 273 | // display: block; 274 | max-height: 8em; /* used to hide ul instead of display:none */ 275 | // position: absolute; 276 | top: initial; 277 | bottom: 0; 278 | left: 10em; 279 | width: 50%; 280 | // padding: 0; 281 | // list-style: none; 282 | // color: $back_color; 283 | background-color: transparent; 284 | 285 | li{ 286 | display: inline-block; 287 | } 288 | } 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/app/header/header.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { HeaderComponent } from './header.component'; 7 | 8 | describe('HeaderComponent', () => { 9 | let component: HeaderComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ HeaderComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(HeaderComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import {Renderer} from '@angular/core/src/render/api'; 2 | import {ViewChild} from '@angular/core/src/metadata/di'; 3 | import { NavigationStart } from '@angular/router/src/router'; 4 | import { Router } from '@angular/router/src/router'; 5 | import 'rxjs/add/operator/pairwise'; 6 | import { HostListener } from '@angular/core/src/metadata/directives'; 7 | import { Component, OnInit } from '@angular/core'; 8 | import {ElementRef} from '@angular/core'; 9 | declare var $: any; 10 | 11 | @Component({ 12 | selector: 'app-header', 13 | templateUrl: './header.component.html', 14 | styleUrls: ['./header.component.scss'] 15 | }) 16 | 17 | export class HeaderComponent { 18 | 19 | topPos = 30; 20 | isTop: boolean = true; 21 | lastDirection: string; 22 | currentDirection: string; 23 | showNav: boolean = true; 24 | currentPosition: number; 25 | moveStart: number; 26 | moveLength: number; 27 | // bigLogo: boolean = true; 28 | 29 | @HostListener('window:scroll', ['$event']) 30 | doSomething(event) { 31 | var scrollY = window.scrollY; 32 | this.isTop = scrollY < this.topPos; 33 | this.currentDirection = (scrollY > this.currentPosition) ? 'down' : 'up'; 34 | this.currentPosition = scrollY; 35 | if (this.currentDirection != this.lastDirection) { 36 | this.moveStart = scrollY; 37 | this.moveLength = 0; 38 | this.lastDirection = this.currentDirection; 39 | } 40 | else { 41 | this.moveLength = Math.abs(this.moveStart - scrollY); 42 | } 43 | if (this.currentDirection == 'down') { 44 | if (this.isTop) { 45 | } 46 | else { 47 | if (this.moveLength > 2*this.topPos) { 48 | this.showNav = false; 49 | } 50 | } 51 | } 52 | /* this.currentDirection == 'up' */ 53 | else { 54 | if (this.isTop) { 55 | this.showNav = true; 56 | } 57 | else { 58 | if (this.moveLength > 2*this.topPos) { 59 | this.showNav = true; 60 | } 61 | } 62 | } 63 | } 64 | 65 | /* an event handler to go #anchor scroll position */ 66 | goAnchor(anchor){ 67 | if(anchor == "top"){ 68 | /* go to top */ 69 | $('html, body').animate({ 70 | scrollTop: 0, 71 | }, 1000) 72 | } 73 | else if(anchor){ 74 | /* get the top position of anchor */ 75 | let anchor_y = $(anchor).offset().top 76 | /* go to anchor (animation to do) */ 77 | $('html, body').animate({ 78 | scrollTop: anchor_y, 79 | }, 1000) 80 | } 81 | return false 82 | } 83 | 84 | /* add 'hide' class when nav leave top */ 85 | // $('.site-nav').addClass('hidenav') 86 | 87 | toggle (event: any) { 88 | // event.target.nextElementSibling.className += 'active' 89 | $(event.target).toggleClass('active') 90 | } 91 | 92 | //@ViewChild('mobileBtn') el:ElementRef; 93 | //@ViewChild('navbar') navbar:ElementRef; 94 | //constructor(private router: Router, private rd: Renderer) { 95 | // this.router.events.pairwise().subscribe((e) => { 96 | // if (e[1] instanceof NavigationStart && this.navbar.nativeElement.classList.contains("in")) { 97 | // this.rd.invokeElementMethod(this.el.nativeElement,'click'); 98 | // } 99 | // }) 100 | //} 101 | } 102 | -------------------------------------------------------------------------------- /src/app/how-we-work/how-we-work.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/app/how-we-work/how-we-work.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PDIS/web/9fe094d88961ff11174b17529113fb16f2b48431/src/app/how-we-work/how-we-work.component.scss -------------------------------------------------------------------------------- /src/app/how-we-work/how-we-work.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { HowWeWorkComponent } from './how-we-work.component'; 7 | 8 | describe('HowWeWorkComponent', () => { 9 | let component: HowWeWorkComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ HowWeWorkComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(HowWeWorkComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/how-we-work/how-we-work.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-how-we-work', 5 | templateUrl: './how-we-work.component.html', 6 | styleUrls: ['./how-we-work.component.scss'] 7 | }) 8 | export class HowWeWorkComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/how-we-work/tools/tools.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

5 | These are the tools we use everyday. 6 |

7 |
8 | 9 | 10 |
    11 |
  • 12 |
13 | 14 |
15 | 16 | 17 |
18 | 19 | 40 | 41 |
42 | 43 |
44 |
45 | -------------------------------------------------------------------------------- /src/app/how-we-work/tools/tools.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../../styles.scss"; 2 | 3 | img{ 4 | max-width:100%; 5 | // max-height:100%; 6 | height: auto; 7 | // width:auto; 8 | } 9 | 10 | // ******************** slogan section 11 | 12 | .slogan-container { 13 | position: relative; 14 | text-align: center; 15 | .slogan { 16 | font-size: 4rem; 17 | @media screen and (max-width: $breakpoint_tablet) { 18 | font-size: 2rem; 19 | } 20 | font-weight: normal; 21 | font-family: $slogan_font; 22 | padding: 2em 0; 23 | } 24 | } 25 | 26 | /*-----------------thumb------------------*/ 27 | 28 | ul.thumb-container{ 29 | margin: auto; 30 | padding: 0; 31 | width: 60%; 32 | text-align: center; 33 | 34 | li.thumb{ 35 | display: inline-block; 36 | margin: 3px; 37 | padding: 10px; 38 | width: 30%; 39 | height: 14em; /* not sure */ 40 | text-align: center; 41 | vertical-align: top; 42 | background-color: #FFFFFF; 43 | // border-radius: 10px; 44 | overflow: hidden; 45 | } 46 | } 47 | 48 | @media screen and (max-width: $breakpoint_tablet) { 49 | 50 | ul.thumb-container{ 51 | width: 100%; 52 | 53 | li.thumb{ 54 | display: block; 55 | width: 100%; 56 | height: auto; 57 | } 58 | } 59 | } 60 | 61 | /*-----------------topic------------------*/ 62 | 63 | .topic-container { 64 | position: relative; 65 | margin: 4em 0; 66 | .text { 67 | position: relative; 68 | // width: 40vw; 69 | max-width: $max_width * 0.5; 70 | display: inline-block; 71 | margin: 0; 72 | z-index: 100; 73 | background-color: #FFFFFF; 74 | opacity: 0.9; 75 | padding: 2.5vw; 76 | text-align: left; 77 | } 78 | 79 | .image { 80 | position: absolute; 81 | top: 0; 82 | bottom: 0; 83 | width: 40vw; 84 | max-width: $max_width * 0.5; 85 | margin: -2.5vw 0; 86 | z-index: 10; 87 | overflow: hidden; 88 | display: inline-flex; 89 | align-items: center; 90 | 91 | img{ 92 | margin: 0 auto; 93 | } 94 | } 95 | } 96 | 97 | .topic-left { 98 | text-align: left; 99 | position: relative; 100 | 101 | .image { 102 | right: 0; 103 | } 104 | } 105 | 106 | .topic-right { 107 | text-align: right; 108 | position: relative; 109 | 110 | .image { 111 | left: 0; 112 | } 113 | } 114 | 115 | @media screen and (max-width: $breakpoint_tablet) { 116 | 117 | .topic-container { 118 | width: 100%; 119 | display: flex; 120 | .text { 121 | width: 100%; 122 | margin: 0; 123 | max-width: $max_width; 124 | } 125 | .image { 126 | width: 100%; 127 | height: 100%; 128 | margin: 0; 129 | max-width: $max_width; 130 | } 131 | } 132 | 133 | .topic-right { 134 | text-align: left; 135 | } 136 | } 137 | /*-----------------------------------*/ -------------------------------------------------------------------------------- /src/app/how-we-work/tools/tools.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { ToolsComponent } from './tools.component'; 7 | 8 | describe('ToolsComponent', () => { 9 | let component: ToolsComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ ToolsComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(ToolsComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/how-we-work/tools/tools.component.ts: -------------------------------------------------------------------------------- 1 | import { DataService } from './../../shared/dataService/data-service.service'; 2 | import { Component, OnInit } from '@angular/core'; 3 | import { DomSanitizer } from '@angular/platform-browser/src/security/dom_sanitization_service'; 4 | 5 | // declare var particlesJS: any; 6 | // declare var $; 7 | declare var WOW: any; 8 | 9 | @Component({ 10 | selector: 'app-tools', 11 | templateUrl: './tools.component.html', 12 | styleUrls: ['./tools.component.scss'] 13 | }) 14 | export class ToolsComponent implements OnInit { 15 | 16 | tools_list; 17 | tools_detail_list = []; 18 | 19 | constructor(private datasvcHww: DataService, private sanitizer: DomSanitizer) { 20 | } 21 | 22 | ngOnInit() { 23 | /* WOW for animateCSS */ 24 | new WOW().init(); 25 | 26 | // how-we-work-tools = 54 27 | // fetch the title & thumb of tools 28 | this.datasvcHww.getData("54").subscribe(value => { 29 | // console.log(this.tools_list); 30 | this.tools_list = JSON.parse(value.text()); 31 | }); 32 | 33 | // how-we-work-tools-detail-version = 208 34 | this.datasvcHww.getData("208").subscribe(value => { 35 | let jsdata = JSON.parse(value.text()); 36 | // parsing raw html into param 37 | jsdata['post_stream']['posts'].forEach(data =>{ 38 | let post = {}; 39 | let dom = (new DOMParser()).parseFromString(data["cooked"], "text/html"); 40 | 41 | post['title'] = (dom.querySelector("h1,h2,h3,h4,h5,h6")).innerText; 42 | 43 | let imgs = dom.querySelectorAll("img"); 44 | console.log(imgs) 45 | post['img'] = imgs.length && ( 46 | imgs[imgs.length - 1].src || imgs[imgs.length - 1].getAttribute("src") 47 | ) || "http://via.placeholder.com/350x350"; 48 | 49 | post['text'] = dom.querySelector("p").innerHTML; 50 | 51 | post['link'] = dom.querySelector("aside header a") && (dom.querySelector("aside header a")).outerHTML; 52 | 53 | let ytdata; 54 | // if lazyYT exist, then return its converted iframe 55 | post['yt'] = dom.querySelector(".lazyYT") && ( 56 | ytdata = (dom.querySelector(".lazyYT")).dataset 57 | ) && ( 58 | /*** sanitized from xss ***/ 59 | this.sanitizer.bypassSecurityTrustResourceUrl("https://www.youtube.com/embed/" + ytdata["youtubeId"]) 60 | ); 61 | 62 | this.tools_detail_list.push(post); 63 | }); 64 | // this.tools_detail_list['post_stream']['posts'][0]['cooked'] 65 | // console.log(this.tools_detail_list); 66 | }); 67 | 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/app/how-we-work/tracks/tracks.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

5 | We are commited to open our work process 6 |
7 | See and track how we work day by day. 8 |

9 |
10 | 11 | 16 | 17 |
18 | You are now tracking with 19 | 20 | {{ q }} 21 | 22 | 23 |
24 | 25 | 26 |
27 | 28 | 33 | 34 | 35 |
36 |
37 | 38 |
    39 |
  • 40 |
    41 |
    42 |
    43 |
    {{post.date | datePipe:"yyyy/MM/dd"}}
    44 |
    45 |

    {{post.title}}

    46 | {{tag}} 47 |
    48 |
    49 |
    50 |

    51 | {{media | JSONpipes:'keys'}} 52 |

    53 |
    54 | 59 |
    60 |
  • 61 |
  • 62 |
63 | 64 |
65 |
66 | 67 |

68 | 69 | 70 | 71 |

72 | 73 |
74 | 75 |
76 | -------------------------------------------------------------------------------- /src/app/how-we-work/tracks/tracks.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../../styles.scss"; 2 | 3 | // ******************** slogan section 4 | 5 | .slogan-container { 6 | position: relative; 7 | text-align: center; 8 | .slogan { 9 | font-size: 4rem; 10 | @media screen and (max-width: $breakpoint_tablet) { 11 | font-size: 2rem; 12 | } 13 | font-weight: normal; 14 | font-family: $slogan_font; 15 | padding: 2em 0; 16 | } 17 | } 18 | 19 | /******************************/ 20 | 21 | .more { 22 | font-family: $btn_font; 23 | display: inline-block; 24 | width: 100%; 25 | line-height: 2.5em; 26 | text-align: center; 27 | background: white; 28 | border: 1px solid $back_color; 29 | box-shadow: 0px 3px 5px -1px $back_color; 30 | } 31 | 32 | /*-----------------------------------*/ 33 | 34 | .cloud { 35 | margin: 0 0 3em 0; 36 | text-align: center; 37 | line-height: 2.5; 38 | .cloud-item { 39 | position: relative; 40 | white-space: nowrap; 41 | display: inline-block; 42 | a { 43 | margin: 1ch; 44 | padding: 0 1ch; 45 | color: $text_color; 46 | background-color: $back_color; 47 | border-radius: .1em; 48 | &:hover { 49 | background-color: $highlight; 50 | } 51 | &::after { 52 | content: attr(data-weight); 53 | // position: absolute; 54 | // right: 1.7ch; 55 | // top: -.9em; 56 | font-size: 80%; 57 | color: gray; 58 | font-family: $btn_font; 59 | } 60 | } 61 | } 62 | } 63 | 64 | /*-----------------------------------*/ 65 | 66 | .tagging { 67 | margin: 1em 0; 68 | font-size: 120%; 69 | .tag { 70 | font-family: $btn_font; 71 | font-size: 120%; 72 | position: relative; 73 | padding: 0 1ch; 74 | background: white; 75 | border: 1px solid $back_color; 76 | box-shadow: 0px 3px 5px -1px $back_color; 77 | .dismiss { 78 | position: absolute; 79 | right: -1ch; 80 | top: -0.5em; 81 | font-size: 80%; 82 | } 83 | } 84 | } 85 | 86 | .card { 87 | padding-top: 10px; 88 | width: 100%; 89 | position: relative; 90 | } 91 | 92 | /************* tabs of categories *****************/ 93 | 94 | .nav-tabs { 95 | border-bottom: 1px solid $back_color; 96 | text-align: center; 97 | li { 98 | float: none; /* suppress bootstrap's float:left */ 99 | display: inline-block; 100 | } 101 | >li>a { 102 | border: none; 103 | color: #666; 104 | &::after { 105 | content: ""; 106 | background: $link_color; 107 | height: 1px; 108 | position: absolute; 109 | width: 100%; 110 | left: 0; 111 | bottom: 0; 112 | transition: all 250ms ease 0s; 113 | transform: scale(0); 114 | } 115 | &.active, 116 | &.active:focus, 117 | &.active:hover { 118 | border-width: 0; 119 | } 120 | } 121 | } 122 | 123 | .nav-tabs>li.active>a, 124 | .nav-tabs>li>a:hover { 125 | border: none; 126 | color: #4B0056 !important; 127 | background: transparent; 128 | &::after { 129 | transform: scale(1); 130 | } 131 | } 132 | 133 | /*----------------- categories highlight ------------------*/ 134 | 135 | $speech: darkcyan; 136 | $meeting: chocolate; 137 | $conference: cornflowerblue; 138 | $interview: indianred; 139 | $other: darkolivegreen; 140 | .nav-tabs li a { 141 | &.Speech { color: $speech } 142 | &.Meeting { color: $meeting } 143 | &.Conference { color: $conference } 144 | &.Interview { color: $interview } 145 | &.Other { color: $other } 146 | } 147 | .timeline li .timeline-badge { 148 | &.Speech { background: $speech } 149 | &.Meeting { background: $meeting } 150 | &.Conference { background: $conference } 151 | &.Interview { background: $interview } 152 | &.Other { background: $other } 153 | } 154 | 155 | /*-----------------------------------*/ 156 | 157 | .timeline-tags { 158 | font-size: 1.4rem; 159 | font-family: Georgia; 160 | color: #4B0056; 161 | text-decoration: underline; 162 | } 163 | 164 | .timeline-tags:hover { 165 | color: #D66FE5; 166 | } 167 | 168 | 169 | /* ----------------------------------------------- 170 | * Timeline 171 | * --------------------------------------------- */ 172 | 173 | .timeline { 174 | list-style: none; 175 | padding: 10px 0; 176 | position: relative; 177 | font-weight: 300; 178 | } 179 | /* straight line */ 180 | .timeline:before { 181 | top: 0; 182 | bottom: 0; 183 | position: absolute; 184 | content: " "; 185 | width: 1px; 186 | background: #DDD; 187 | left: 50%; 188 | margin-left: -1.5px; 189 | } 190 | 191 | .timeline li { 192 | margin-bottom: 30px; 193 | position: relative; 194 | width: 50%; 195 | float: left; 196 | clear: left; 197 | } 198 | 199 | .timeline li:before, 200 | .timeline li:after { 201 | content: " "; 202 | display: table; 203 | } 204 | 205 | .timeline li:after { 206 | clear: both; 207 | } 208 | 209 | .timeline li:before, 210 | .timeline li:after { 211 | content: " "; 212 | display: table; 213 | } 214 | 215 | .timeline li:after { 216 | clear: both; 217 | } 218 | 219 | .timeline li .timeline-panel { 220 | // margin-left: 10px; 221 | // margin-right: 10px; 222 | width: calc(100% - 35px); 223 | float: left; 224 | border: 1px solid #dcdcdc; 225 | background: #ffffff; 226 | position: relative; 227 | // -webkit-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.175); 228 | // box-shadow: 0 1px 6px rgba(0, 0, 0, 0.175); 229 | } 230 | 231 | .timeline li .timeline-body { 232 | // position: relative; 233 | &::before { 234 | position: absolute; 235 | margin: auto 0; 236 | height: 0; 237 | top: 0; 238 | bottom: 0; 239 | right: -14px; 240 | display: inline-block; 241 | border-top: 15px solid transparent; 242 | border-left: 15px solid #dcdcdc; 243 | border-right: 0 solid #dcdcdc; 244 | border-bottom: 15px solid transparent; 245 | content: " "; 246 | } 247 | &::after { 248 | position: absolute; 249 | margin: auto 0; 250 | height: 0; 251 | top: 0; 252 | bottom: 0; 253 | right: -13px; 254 | display: inline-block; 255 | border-top: 14px solid transparent; 256 | border-left: 14px solid #ffffff; 257 | border-right: 0 solid #ffffff; 258 | border-bottom: 14px solid transparent; 259 | content: " "; 260 | } 261 | } 262 | 263 | .timeline li.timeline-inverted .timeline-body { 264 | // position: relative; 265 | &::before { 266 | border-left-width: 0; 267 | border-right-width: 15px; 268 | left: -14px; 269 | right: auto; 270 | } 271 | &::after { 272 | border-left-width: 0; 273 | border-right-width: 14px; 274 | left: -13px; 275 | right: auto; 276 | } 277 | } 278 | 279 | // 時間軸圓圈 280 | .timeline li .timeline-badge { 281 | color: #fff; 282 | width: 15px; 283 | height: 15px; 284 | line-height: 50px; 285 | font-size: 1.4em; 286 | text-align: center; 287 | position: absolute; 288 | // top: 34px; 289 | margin: auto 0; 290 | top: 0; 291 | bottom: 0; 292 | right: -7px; 293 | background-color: lightgray; 294 | z-index: 100; 295 | border-top-right-radius: 50%; 296 | border-top-left-radius: 50%; 297 | border-bottom-right-radius: 50%; 298 | border-bottom-left-radius: 50%; 299 | border-color: white; 300 | } 301 | 302 | .timeline li.timeline-inverted .timeline-panel { 303 | float: right; 304 | } 305 | 306 | .timeline-title { 307 | margin-top: 0; 308 | color: inherit; 309 | } 310 | 311 | .timeline-heading { 312 | position: relative; 313 | padding: .5em 1em; 314 | .title { 315 | // color: black; 316 | // font-size: 2rem; 317 | // font-weight: 500; 318 | // margin: 0; 319 | display: inline-block; 320 | } 321 | .tag { 322 | display: inline-block; 323 | // font-size: .8em; 324 | color: black; 325 | background-color: $back_color; 326 | text-decoration: none; 327 | border-radius: .1em; 328 | padding: .1em .7em; 329 | margin: 0 0 0 1ch; 330 | &:hover { 331 | background-color: $highlight; 332 | } 333 | } 334 | } 335 | 336 | .timeline-footer { 337 | position: absolute; 338 | right: 0; 339 | bottom: 0; 340 | // font-size: 5em; 341 | // color: rgba($back_color, .2); 342 | } 343 | 344 | .timeline-body p, 345 | .timeline-body>ul { 346 | padding: 10px 15px; 347 | margin-top: 10px; 348 | margin-bottom: 10px; 349 | text-align: center; 350 | } 351 | 352 | .timeline li.timeline-inverted { 353 | float: right; 354 | clear: right; 355 | } 356 | 357 | .timeline li:nth-child(1) { 358 | margin-top: 30px; 359 | } 360 | 361 | .timeline li:nth-child(2) { 362 | margin-top: 60px; 363 | } 364 | 365 | .timeline li.timeline-inverted .timeline-badge { 366 | left: -8px; 367 | } 368 | 369 | .no-float { 370 | float: none !important; 371 | } 372 | 373 | @media (max-width: $breakpoint_tablet) { 374 | ul.timeline:before { 375 | left: 7px; 376 | } 377 | ul.timeline li { 378 | margin-bottom: 0px; 379 | position: relative; 380 | width: 100%; 381 | float: left; 382 | clear: left; 383 | } 384 | ul.timeline li>.timeline-panel { 385 | width: calc(100% - 40px); 386 | } 387 | ul.timeline li>.timeline-badge { 388 | left: 0; 389 | margin-left: 0; 390 | width: 12px; 391 | height: 12px; 392 | // top: 16px; 393 | } 394 | ul.timeline li>.timeline-panel { 395 | float: right; 396 | } 397 | ul.timeline li>.timeline-panel .timeline-body::before, 398 | ul.timeline li.timeline-inverted>.timeline-panel .timeline-body:before { 399 | border-left-width: 0; 400 | border-right-width: 14px; 401 | left: -15px; 402 | right: auto; 403 | // top: 25px; 404 | } 405 | ul.timeline li>.timeline-panel .timeline-body::after, 406 | ul.timeline li.timeline-inverted>.timeline-panel .timeline-body:after { 407 | border-left-width: 0; 408 | border-right-width: 14px; 409 | left: -14px; 410 | right: auto; 411 | // top: 25px; 412 | } 413 | .timeline li.timeline-inverted { 414 | float: left; 415 | clear: left; 416 | margin-top: 30px; 417 | margin-bottom: 30px; 418 | } 419 | .timeline li.timeline-inverted>.timeline-badge { 420 | left: 0; 421 | // top:36px; 422 | width: 12px; 423 | height: 12px; 424 | } 425 | } -------------------------------------------------------------------------------- /src/app/how-we-work/tracks/tracks.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { TracksComponent } from './tracks.component'; 7 | 8 | describe('TracksComponent', () => { 9 | let component: TracksComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ TracksComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(TracksComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/how-we-work/tracks/tracks.component.ts: -------------------------------------------------------------------------------- 1 | import { ActivatedRoute } from '@angular/router'; 2 | // import { TagCloudModule } from 'angular-tag-cloud-module'; 3 | // import { element } from 'protractor'; 4 | import { DataService } from './../../shared/dataService/data-service.service'; 5 | import { Http } from '@angular/http'; 6 | import { ConvertService } from './../../shared/convertService/convert.service'; 7 | import { Component, OnInit } from '@angular/core'; 8 | import 'rxjs/add/operator/map'; 9 | import 'rxjs/add/operator/do'; 10 | import 'rxjs/add/operator/mergeMap'; 11 | import 'rxjs/add/operator/concatMap'; 12 | import { Discourselink } from './../../../assets/discourselink'; 13 | 14 | // declare var particlesJS: any; 15 | // declare var WOW: any; 16 | declare var $: any; 17 | 18 | @Component({ 19 | selector: 'app-tracks', 20 | templateUrl: './tracks.component.html', 21 | styleUrls: ['./tracks.component.scss'] 22 | }) 23 | 24 | export class TracksComponent implements OnInit { 25 | 26 | tags = []; 27 | total = []; 28 | q:string = ''; 29 | more_url: string = '' 30 | autoLoad: boolean = true 31 | 32 | constructor( 33 | private dataService: DataService, 34 | private convertService: ConvertService, 35 | private http: Http, 36 | private activatedRoute: ActivatedRoute 37 | ){} 38 | 39 | private getCategories() { //取得分類(置頂文章) 40 | return this.http.get(Discourselink.Host + Discourselink.Text + Discourselink.HOWWEWORKTRACK + "/73.json?include_raw=1") 41 | .map(res => { 42 | let data = res.json() 43 | let raw = data['post_stream']['posts'][0]['raw'] 44 | let categories = this.convertService.convertYAMLtoJSON(raw) 45 | return categories 46 | }) 47 | } 48 | 49 | private getIds(q: string, more_url: string) { //取得討論區每篇文的ID & more_url 50 | /* fetch date base on if 'q' & 'more_url' exist */ 51 | /* 52 | https://talk.pdis.nat.gov.tw/c/pdis-site/how-we-work-track 53 | /c/pdis-site/how-we-work-track/l/latest?page=1 54 | https://talk.pdis.nat.gov.tw/tags/c/pdis-site/how-we-work-track/TAG 55 | /tags/c/pdis-site/how-we-work-track/TAG/l/latest?page=1 56 | */ 57 | let query 58 | if (q) { 59 | if (more_url) { 60 | query = Discourselink.Host + more_url.replace(/latest/,'latest.json') 61 | } 62 | else { 63 | query = Discourselink.Host + Discourselink.Tags + Discourselink.Category + Discourselink.HOWWEWORKTRACK + '/' + q + Discourselink.Filename 64 | } 65 | } 66 | else { 67 | if (more_url) { 68 | query = Discourselink.Host + more_url.replace(/latest/,'latest.json') 69 | } 70 | else { 71 | query = Discourselink.Host + Discourselink.Category + Discourselink.HOWWEWORKTRACK + Discourselink.Filename 72 | } 73 | } 74 | 75 | return this.http 76 | .get(query) 77 | /* to get more_url */ 78 | .do(data => this.more_url = data.json().topic_list.more_topics_url || '') 79 | /* to get ids */ 80 | .map(data => data.json().topic_list.topics.map(topic => topic['id'])) 81 | } 82 | 83 | private getPost(id: {}) { // 取得單篇PO文 84 | return this.http.get(Discourselink.Host + Discourselink.Text + id + ".json?include_raw=1") 85 | .map(res => { 86 | let data = res.json(); 87 | let post = {}; 88 | post['id'] = data['id'] 89 | post['title'] = data['title']; 90 | post['date'] = data['created_at']; 91 | post['tags'] = data['tags']; 92 | // post['content'] = data['post_stream']['posts'][0]['raw']; 93 | let raw = data['post_stream']['posts'][0]['raw']; 94 | post['content'] = this.convertService.convertYAMLtoJSON(raw)['content'] 95 | return post; 96 | }) 97 | } 98 | 99 | private categorizePost(post, categories) { //將每篇PO文與各分類中的關鍵字比對 100 | // categories = {conference:['xx','oo'], ...} 101 | // post = {title:'xxxoo'} 102 | /* set default */ 103 | post['category'] = 'Other'; 104 | Object.keys(categories).forEach(key => { 105 | for (var i = 0; i < categories[key].length; i++) { 106 | if (post['title'].indexOf(categories[key][i]) > -1) { 107 | post['category'] = key; 108 | return post; 109 | } 110 | } 111 | }) 112 | return post; 113 | } 114 | 115 | getMorePosts(q:string, more_url:string) { 116 | /* an event handler to get more ids and then post them */ 117 | let categories 118 | /* init categories */ 119 | this.getCategories() 120 | .do(cats => categories = cats) 121 | /* get the posts' id */ 122 | .mergeMap(() => this.getIds(q, more_url)) 123 | /* seems that first post is duplicated */ 124 | /* concat observable into observable[]<> */ 125 | .mergeMap(ids => ids.slice(1)) 126 | /* get the posts by ids */ 127 | .concatMap(id => this.getPost(id)) 128 | .do(post => { 129 | post = this.categorizePost(post, categories) 130 | /* put in ALL */ 131 | this.total[0]['posts'].push(post); 132 | /* put in respective category */ 133 | let cat_list = this.total.map(cat => cat['category']) 134 | let cat_index = cat_list.indexOf(post['category']) 135 | this.total[cat_index]['posts'].push(post) 136 | }) 137 | .subscribe() 138 | } 139 | 140 | /* an event handler to go #anchor scroll position */ 141 | goAnchor(anchor) { 142 | // console.log(anchor) 143 | if (anchor == "top") { 144 | /* go to top */ 145 | $('html, body').animate({ 146 | scrollTop: 0, 147 | }, 300) 148 | } 149 | else if (anchor) { 150 | /* get the top position of anchor */ 151 | let anchor_y = $(anchor).offset().top 152 | /* go to anchor (animation to do) */ 153 | $('html, body').animate({ 154 | scrollTop: anchor_y, 155 | }, 300) 156 | } 157 | return false 158 | } 159 | 160 | // /* trigger 'load more' when window scroll to bottom */ 161 | // hitLoad () { 162 | // let wBottom = window.pageYOffset + window.innerHeight 163 | // /* cross-browser highest document finding */ 164 | // let dHeight = Math.max( 165 | // document.body.scrollHeight, document.documentElement.scrollHeight, 166 | // document.body.offsetHeight, document.documentElement.offsetHeight, 167 | // document.body.clientHeight, document.documentElement.clientHeight 168 | // ) 169 | // /* check if scroll to bottom */ 170 | // if (wBottom === dHeight && true) { 171 | // this.getMorePosts(this.q, this.more_url) 172 | // } 173 | // } 174 | 175 | ngOnInit() { 176 | /* WOW for animateCSS */ 177 | // new WOW().init(); 178 | 179 | /* bind event 'scroll' to window */ 180 | window.addEventListener('scroll', () => { 181 | /* trigger 'load more' when window scroll to bottom */ 182 | let wBottom = window.pageYOffset + window.innerHeight 183 | /* cross-browser highest document finding */ 184 | let dHeight = Math.max( 185 | document.body.scrollHeight, document.documentElement.scrollHeight, 186 | document.body.offsetHeight, document.documentElement.offsetHeight, 187 | document.body.clientHeight, document.documentElement.clientHeight 188 | ) 189 | /* check if scroll to bottom */ 190 | if (wBottom === dHeight && this.autoLoad) { 191 | this.getMorePosts(this.q, this.more_url) 192 | } 193 | }) 194 | 195 | // tags in pdis-site/how-we-work-track category 196 | // fit discourse admin setting [max tags in filter list] value 197 | this.http.get(Discourselink.Host + "c/pdis-site/how-we-work-track.json") 198 | .map(data => { 199 | data = data.json(); 200 | var tags: [Object] = data['topic_list']['tags']; 201 | return tags; 202 | }) 203 | .subscribe( 204 | tags => { 205 | var topic_tags = tags; 206 | 207 | // Tags Cloud 208 | this.http.get(Discourselink.Host + "tags/filter/search.json") 209 | .map(data => { 210 | data = data.json(); 211 | var tags = []; 212 | var discourseTags: [Object] = data['results']; 213 | for (var i in discourseTags) { 214 | // remove tags not in pdis-site/how-we-work-track 215 | if (topic_tags.indexOf(discourseTags[i]['text']) == -1) { continue; } 216 | if (discourseTags[i]['text'] === '尚未回覆') { continue; } 217 | if (/^(.+)-/.test(discourseTags[i]['text'])) { continue; } 218 | var tag = {}; 219 | tag['text'] = discourseTags[i]['text']; 220 | tag['weight'] = discourseTags[i]['count']; 221 | tag['link'] = "/how-we-work/tracks?q=" + discourseTags[i]['text']; 222 | tags.push(tag); 223 | } 224 | return tags; 225 | }) 226 | // .do(data => { console.log(data); }) 227 | .subscribe( 228 | tags => { this.tags = tags; } 229 | ); 230 | 231 | } 232 | ); 233 | 234 | /* init categories tab header */ 235 | this.getCategories() 236 | .subscribe(cats => { 237 | /* if cate-tab already generated then break */ 238 | if(this.total.length > 0) return 239 | this.total.push({ category: 'All', posts: new Array() }) 240 | Object.keys(cats).forEach(key => this.total.push({ category: key, posts: new Array() })) 241 | this.total.push({ category: 'Other', posts: new Array() }) 242 | }) 243 | 244 | /* Tag Query & Timeline */ 245 | let categories 246 | this.activatedRoute.queryParams 247 | .do(param => this.q = param['q'] || '') 248 | .do(() => { if(this.q) this.goAnchor('#cloud') }) 249 | /* empty all the total[n].posts */ 250 | .do(() => this.total.forEach(t => t.posts = [])) 251 | .mergeMap(() => this.getCategories()) 252 | /* save for later use */ 253 | .do(cats => categories = cats) 254 | /* get the posts' id */ 255 | .mergeMap(() => this.getIds(this.q, '')) 256 | /* discard first post if no tag query (avoid the head post) */ 257 | /* concat observable into observable[]<> */ 258 | .mergeMap(ids => (this.q) ? ids : ids.slice(1)) 259 | /* get the posts by ids */ 260 | /* flatten observable into observable<> */ 261 | /* use concat instead of merge to remain sorted */ 262 | .concatMap(id => this.getPost(id)) 263 | .do(post => { 264 | post = this.categorizePost(post, categories) 265 | /* put in ALL */ 266 | this.total[0]['posts'].push(post); 267 | /* put in respective category */ 268 | let cat_list = this.total.map(cat => cat['category']) 269 | let cat_index = cat_list.indexOf(post['category']) 270 | this.total[cat_index]['posts'].push(post) 271 | }) 272 | .subscribe() 273 | } 274 | 275 | } 276 | -------------------------------------------------------------------------------- /src/app/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app.component'; 2 | export * from './app.module'; 3 | -------------------------------------------------------------------------------- /src/app/shared/convertService/convert.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async, inject } from '@angular/core/testing'; 4 | import { ConvertService } from './convert.service'; 5 | 6 | describe('ConvertService', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | providers: [ConvertService] 10 | }); 11 | }); 12 | 13 | it('should ...', inject([ConvertService], (service: ConvertService) => { 14 | expect(service).toBeTruthy(); 15 | })); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/shared/convertService/convert.service.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import * as YAML from 'yamljs'; 3 | import { Injectable } from '@angular/core'; 4 | 5 | @Injectable() 6 | export class ConvertService { 7 | 8 | constructor() { } 9 | 10 | convertYAMLtoJSON(yaml: string){ 11 | // yaml = yaml.replace(/ *: */g,": "); 12 | return YAML.parse(yaml); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/shared/dataService/data-service.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async, inject } from '@angular/core/testing'; 4 | import { DataService } from './data-service.service'; 5 | 6 | describe('DataServiceService', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | providers: [DataService] 10 | }); 11 | }); 12 | 13 | it('should ...', inject([DataService], (service: DataService) => { 14 | expect(service).toBeTruthy(); 15 | })); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/shared/dataService/data-service.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http } from '@angular/http'; 3 | import { Observable } from 'rxjs/Observable'; 4 | 5 | @Injectable() 6 | export class DataService { 7 | 8 | constructor(private http: Http) { 9 | this.http = http; 10 | } 11 | 12 | base = 'https://talk.pdis.nat.gov.tw' 13 | 14 | getData(id: string) { 15 | // console.log('https://talk.pdis.nat.gov.tw/t/' + id + '.json'); 16 | return this.http.get('https://talk.pdis.nat.gov.tw/t/' + id + '.json'); 17 | } 18 | 19 | getList(category: string) { 20 | // var topics = []; 21 | // var titles = []; 22 | // console.log('https://talk.pdis.nat.gov.tw/c/' + category + '.json'); 23 | 24 | // topics 25 | return this.http 26 | .get('https://talk.pdis.nat.gov.tw/c/' + category + '.json') 27 | .map((value) => { 28 | let topics = JSON.parse(value.text()).topic_list.topics 29 | return topics 30 | }) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/app/shared/datePipe/date-pipe.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { DatePipePipe } from './date-pipe.pipe'; 5 | 6 | describe('DatePipePipe', () => { 7 | it('create an instance', () => { 8 | let pipe = new DatePipePipe(); 9 | expect(pipe).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/shared/datePipe/date-pipe.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'datePipe' 5 | }) 6 | export class DatePipePipe implements PipeTransform { 7 | 8 | transform(value: any, args?: string): string { 9 | 10 | switch (args) { 11 | case "yyyy/MM/dd": 12 | var date: Date = new Date(value); 13 | return date.getFullYear() + "/" + (date.getMonth()+1) + "/" + date.getDate(); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/app/shared/discourseService/discourse.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async, inject } from '@angular/core/testing'; 4 | import { DiscourseService } from './discourse.service'; 5 | 6 | describe('DiscourseService', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | providers: [DiscourseService] 10 | }); 11 | }); 12 | 13 | it('should ...', inject([DiscourseService], (service: DiscourseService) => { 14 | expect(service).toBeTruthy(); 15 | })); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/shared/discourseService/discourse.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http, Response, Headers, RequestOptions } from '@angular/http'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import 'rxjs/add/operator/map'; 5 | import 'rxjs/add/operator/catch'; 6 | import 'rxjs/add/observable/throw'; 7 | 8 | @Injectable() 9 | export class DiscourseService { 10 | private _discourseUrl: string = "https://talk.pdis.nat.gov.tw/posts?api_key=cd1a0c22c71b51dec2b702fbb646df99899c27e32c106a1c3173e1ac5336308c&api_username=talk.about.pdis"; 11 | 12 | constructor(private _http: Http) { } 13 | 14 | serialize(obj) { 15 | // return Object.keys(obj).reduce(function (a, k) { a.push(k + '=' + encodeURIComponent(obj[k])); return a }, []).join('&') 16 | return Object.keys(obj).map(k => k + '=' + encodeURIComponent(obj[k])).join('&') 17 | } 18 | 19 | postDiscourseTopicRestful(title: string, raw: string, category: string) { 20 | let body = this.serialize(JSON.parse(JSON.stringify({ "title": title, "raw": raw, "category": category, "archetype": "regular", "reply_to_post_number": "0" }))); 21 | let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' }); 22 | let options = new RequestOptions({ headers: headers }); 23 | 24 | return this._http.post(this._discourseUrl, body, options) 25 | .map(this.extractData) 26 | .catch(this.handleError); 27 | } 28 | 29 | postDiscoursePostRestful(raw: string, category: string, topic_id: string) { 30 | let body = this.serialize(JSON.parse(JSON.stringify({ "raw": raw, "archetype": "regular", "category": category, "topic_id": topic_id }))); 31 | // console.log(body); 32 | let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' }); 33 | let options = new RequestOptions({ headers: headers }); 34 | 35 | return this._http.post(this._discourseUrl, body, options) 36 | .map(this.extractData) 37 | .catch(this.handleError); 38 | } 39 | 40 | // https://meta.discourse.org/t/private-message-send-api/27593/2 41 | postDiscourseMessageRestful(title: string, raw: string, target_usernames: string) { 42 | let body = this.serialize(JSON.parse(JSON.stringify({ "title": title, "raw": raw, "archetype": "private_message", "target_usernames": target_usernames }))); 43 | // console.log(body); 44 | let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' }); 45 | let options = new RequestOptions({ headers: headers }); 46 | 47 | return this._http.post(this._discourseUrl, body, options) 48 | .map(this.extractData) 49 | .catch(this.handleError); 50 | } 51 | 52 | // {"action":"create_post","errors":["內容 與你最近發表的內容太相似"]} 53 | // {"action":"create_post","errors":["我們非常抱歉,新用戶被臨時限制在同一個主題上,只能回覆 3 次","內容 與你最近發表的內容太相似"]} 54 | // {"action":"create_post","errors":["內容 長度不足 (下限是 10 字)","內容 無效,請再仔細描述","我們非常抱歉,新用戶被臨時限制在同一個主題上,只能回覆 3 次"]} 55 | private extractData(res: Response) { 56 | console.log(res.status); 57 | // let body = res.json(); 58 | let body = JSON.parse(res.text()); 59 | console.log(body); 60 | return body || {}; 61 | } 62 | 63 | private handleError(error: Response | any) { 64 | let errMsg: string; 65 | let crlf: string; 66 | 67 | errMsg = ''; 68 | // crlf = '\n'; 69 | crlf = '
'; 70 | if (error instanceof Response) { 71 | const body = error.json() || ''; 72 | const err = body.error || JSON.stringify(body); 73 | // errMsg = `${error.status} - ${error.statusText || ''} ${err}`; 74 | for (let x of JSON.parse(err).errors) { 75 | errMsg += x + crlf; 76 | } 77 | if (errMsg.length > 0) { 78 | errMsg = errMsg.substr(0, errMsg.length - crlf.length); 79 | } 80 | } else { 81 | errMsg = error.message ? error.message : error.toString(); 82 | } 83 | console.error(error.status + '-' + error.statusText); 84 | console.error(errMsg); 85 | return Observable.throw(errMsg); 86 | } 87 | } -------------------------------------------------------------------------------- /src/app/shared/jsonPipes/jsonpipes.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { JSONPipesPipe } from './jsonpipes.pipe'; 5 | 6 | describe('JSONPipesPipe', () => { 7 | it('create an instance', () => { 8 | let pipe = new JSONPipesPipe(); 9 | expect(pipe).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/shared/jsonPipes/jsonpipes.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'JSONpipes' 5 | }) 6 | export class JSONPipesPipe implements PipeTransform { 7 | 8 | transform(json: any, type: string): any { 9 | 10 | switch (type) { 11 | case "keys": 12 | return Object.keys(json).join(); 13 | 14 | case "not null object of array": 15 | var jsonArray: Array = json; 16 | 17 | return jsonArray.filter(json => { 18 | for (var key in json) { 19 | if (json[key] != null) 20 | return true; 21 | } 22 | return false; 23 | }); 24 | 25 | case "audio only": 26 | var jsonArray: Array = json; 27 | console.log(jsonArray); 28 | return jsonArray.filter(json1 => { 29 | return true; 30 | }); 31 | 32 | case "values": 33 | var values: Array = []; 34 | for (var key in json) { 35 | if (json[key] != null) { 36 | values.push(json[key]); 37 | } 38 | } 39 | return values.join(); 40 | 41 | case "gettag": 42 | // var gettag: Array =[]; 43 | for (var i in json) { 44 | if (['meeting', 'talks', 'speech'].indexOf(json[i]) > -1) { 45 | return json[i]; 46 | } 47 | } 48 | 49 | default: 50 | break; 51 | } 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/app/shared/member-service/member.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { MemberService } from './member.service'; 4 | 5 | describe('MemberService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [MemberService] 9 | }); 10 | }); 11 | 12 | it('should ...', inject([MemberService], (service: MemberService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/shared/member-service/member.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http,Response } from '@angular/http'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { Discourselink } from '../../../assets/discourselink' 5 | 6 | @Injectable() 7 | export class MemberService { 8 | groupUrl: string; 9 | UserUrl: string; 10 | InfoUrl: string; 11 | /* WhoweareUrl: string 12 | profileUrl: string;*/ 13 | constructor(private http: Http) { 14 | this.groupUrl = Discourselink.GroupUrl; 15 | this.UserUrl = Discourselink.UserUrl; 16 | this.InfoUrl = Discourselink.InfoUrl; 17 | /* this.WhoweareUrl = Discourselink.WhoweareUrl; 18 | this.profileUrl = Discourselink.ProfileUrl;*/ 19 | } 20 | 21 | getGroup(): Observable { 22 | return this.http.get(this.groupUrl).map(g => 23 | g.json().members.map(u => u.username) 24 | ); 25 | } 26 | 27 | getUser(name: any): Observable { 28 | return this.http.get(this.UserUrl + name + ".json").map( u => { 29 | var people = {}; 30 | var tmp2 = u.json().user; 31 | people['id'] = tmp2.id; 32 | if (tmp2.name == '') { 33 | people['name'] = tmp2.username; 34 | } 35 | else { 36 | people['name'] = tmp2.name; 37 | } 38 | people['username'] = tmp2.username; 39 | people['image'] = Discourselink.Host + tmp2.avatar_template.replace("{size}", "800"); 40 | people['wiselikelink'] = "https://wiselike.tw/#/user/" + tmp2.username; 41 | return people; 42 | }); 43 | } 44 | 45 | getInfo(name: any,user: any): Observable { 46 | return this.http.get(this.InfoUrl + name.toString().toLowerCase().replace(/_/g,'-') + ".json").map ( u => { 47 | var topic = u.json().topic_list.topics[0]; 48 | var content = topic.excerpt.replace(" :new:", ""); 49 | var new_tags = []; 50 | var tags = topic.tags; 51 | tags.map(p => { 52 | new_tags.push(p.replace("wiselike-", "")); 53 | }); 54 | user['tags'] = new_tags; 55 | if (content.includes("建立一個完整的")) { 56 | user['description'] = ""; 57 | } 58 | else { 59 | user['description'] = content; 60 | } 61 | return user; 62 | }); 63 | } 64 | 65 | /* getID(): Observable { 66 | return this.http.get(this.WhoweareUrl); 67 | } 68 | 69 | getProfile(id: string): Observable { 70 | return this.http.get(this.profileUrl + id + ".json"); 71 | }*/ 72 | } 73 | -------------------------------------------------------------------------------- /src/app/shared/replacePipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'replacePipe' 5 | }) 6 | export class replacePipe implements PipeTransform { 7 | 8 | transform(target: string): string { 9 | // console.log(target.replace(/&/g,'&')) 10 | return target.replace(/&/g,'&') 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/app/what-we-do/detail/detail.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |

6 | {{ post_title }} 7 |

8 | 15 | 16 |
17 | 18 |
19 | 20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 | 28 | 29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | 37 |
38 | 39 |
40 | 41 |
42 | 43 |
-------------------------------------------------------------------------------- /src/app/what-we-do/detail/detail.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../../styles.scss"; 2 | $main-font-size: 22px; 3 | $line-height-size: 35px; 4 | 5 | // 設定 detailcomponent 文字及圖片樣式 6 | 7 | .title { 8 | text-align: center; 9 | } 10 | 11 | // .detail-text { 12 | // font-size: $main-font-size; 13 | // line-height: $line-height-size; 14 | // } 15 | 16 | :host /deep/ .detail-text { 17 | font-size: $main-font-size; 18 | line-height: $line-height-size; 19 | // h1, h2 { 20 | // &::before { 21 | // content: '\f178'; 22 | // font-family: FontAwesome; 23 | // font-size: 1.5em; 24 | // position: absolute; 25 | // left: -2ch; 26 | // } 27 | // } 28 | p { 29 | margin: 1em 0 1.5em 0; 30 | } 31 | img, 32 | iframe { 33 | border: 0; 34 | display: block; 35 | margin: 0 auto; 36 | max-width: 100%; 37 | } 38 | // img float right 39 | em img, 40 | em iframe { 41 | float: right; 42 | max-width: 50%; 43 | height: auto; 44 | margin: 0 0 1em 1em; 45 | } 46 | // img float left 47 | strong img, 48 | strong iframe { 49 | float: left; 50 | max-width: 50%; 51 | height: auto; 52 | margin: 0 1em 1em 0; 53 | } 54 | // clean img float 55 | dd { 56 | clear: left; 57 | } 58 | // blockquote { 59 | // position: relative; 60 | // border-left: 0px; 61 | // } 62 | // // blockquote { style 63 | // blockquote::before { 64 | // content: "\007B"; 65 | // font-size: 8em; 66 | // line-height: 100%; 67 | // color: #DDDDDD; 68 | // position: absolute; 69 | // left: -50px; 70 | // top: 2rem; 71 | // } 72 | // // blockquote } style 73 | // blockquote::after { 74 | // content: "\007D"; 75 | // font-size: 8em; 76 | // line-height: 100%; 77 | // color: #DDDDDD; 78 | // position: absolute; 79 | // right: -30px; 80 | // top: 2rem; 81 | // } 82 | .meta { 83 | display: none; 84 | } 85 | } 86 | 87 | // window size < 1200px clean blockquote style 88 | @media screen and (max-width: $breakpoint_tablet*1.56) { 89 | :host /deep/ .detail-text { 90 | blockquote::after, 91 | blockquote::before { 92 | content: ""; 93 | } 94 | } 95 | } 96 | 97 | // window size < 991px change to mobile 98 | @media screen and (max-width: $breakpoint_tablet*1.29) { 99 | :host /deep/ .detail-text { 100 | p, 101 | h2 { 102 | margin: 1em 0.5em; 103 | word-break: break-word; 104 | } 105 | img, 106 | strong img, 107 | em img { 108 | max-width: 90%; 109 | margin: 0 5%; 110 | } 111 | // the head of author 112 | span img { 113 | width: 40%; 114 | height: 40%; 115 | } 116 | iframe { 117 | width: 90%; 118 | height: 50vw; 119 | } 120 | } 121 | } 122 | 123 | // @media screen and (min-width: 991px) { //window size > 991px img 100% 124 | // :host /deep/ .detail-container{ 125 | // img { 126 | // width: 100%; 127 | // height: 100%; 128 | // border: 0px; 129 | // } 130 | // } 131 | // } -------------------------------------------------------------------------------- /src/app/what-we-do/detail/detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { DetailComponent } from './detail.component'; 7 | 8 | describe('DetailComponent', () => { 9 | let component: DetailComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ DetailComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(DetailComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/what-we-do/detail/detail.component.ts: -------------------------------------------------------------------------------- 1 | import { DomSanitizer } from '@angular/platform-browser/src/security/dom_sanitization_service'; 2 | import { DataService } from './../../shared/dataService/data-service.service'; 3 | import { Router, ActivatedRoute } from '@angular/router'; 4 | import { Component, OnInit } from '@angular/core'; 5 | import { Discourselink } from './../../../assets/discourselink'; 6 | 7 | 8 | @Component({ 9 | selector: 'app-detail', 10 | templateUrl: './detail.component.html', 11 | styleUrls: ['./detail.component.scss'] 12 | }) 13 | export class DetailComponent implements OnInit { 14 | 15 | posts = []; 16 | post_title = ""; 17 | page_id: string; 18 | 19 | constructor(private router: Router, private route: ActivatedRoute, private datadetail: DataService,private sanitizer: DomSanitizer) { 20 | 21 | this.route.params.subscribe(params => { 22 | this.page_id = params['id']; 23 | }); 24 | } 25 | 26 | ngOnInit() { 27 | this.datadetail.getData(this.page_id) 28 | .subscribe((value) => { 29 | 30 | /*** get articles from json ***/ 31 | let posts = JSON.parse(value.text()) 32 | this.post_title = posts.title 33 | var articles = posts.post_stream.posts; 34 | // let each article to be split and converted 35 | articles.forEach(element => { 36 | /*** split an article by
***/ 37 | var article = element['cooked'].split("
"); 38 | // convert lazyYT into iframe 39 | article = this.lazyTY2iframe(article); 40 | // push back into posts 41 | this.posts.push(article); 42 | 43 | }); 44 | }); 45 | } 46 | 47 | lazyTY2iframe(article){ 48 | var dom = new DOMParser(); 49 | var nodes: HTMLCollectionOf; 50 | var regexp_youtube= /
/; 51 | 52 | for (var i = 0; i < article.length; i++) { 53 | var e = article[i]; 54 | var iframe_string; 55 | var doc = dom.parseFromString(e, "text/html"); 56 | nodes = doc.getElementsByClassName("lazyYT"); 57 | 58 | for (var j = 0; j < nodes.length; j++) { 59 | iframe_string = " 26 |
27 | 28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 | 36 | 37 |
38 | 39 |
40 |
41 |
42 |

{{topic.title}}

43 |
44 | 45 | More about {{topic.title}} 46 | 47 |
48 |
49 |
50 | 51 |
52 | 53 |
54 | 55 |
56 |
57 |

Tell us what you think about

58 | 59 | 60 | 61 |
62 |
63 | 64 |
65 | 66 | -------------------------------------------------------------------------------- /src/app/what-we-do/what-we-do.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles.scss"; 2 | 3 | /*-----------------------------------*/ 4 | 5 | :host /deep/ .swiper-pagination-bullet-active{ 6 | background: $link_color; 7 | } 8 | 9 | .swiper-container { 10 | margin: 0 -2.5vw; 11 | padding: 1em 50px; 12 | 13 | .swiper-wrapper{ 14 | align-items: center; 15 | } 16 | 17 | .iframeWrapper { 18 | position: relative; 19 | padding-bottom: 56.25%; /* 16:9 */ 20 | padding-top: 25px; 21 | height: 0; 22 | iframe { 23 | position: absolute; 24 | top: 0; 25 | left: 0; 26 | width: 100%; 27 | height: 100%; 28 | border: 0; 29 | overflow: hidden; 30 | } 31 | } 32 | 33 | .slogan-container { 34 | text-align: center; 35 | .slogan { 36 | font-size: 4rem; 37 | @media screen and (max-width: $breakpoint_tablet) { 38 | font-size: 2rem; 39 | } 40 | font-weight: normal; 41 | font-family: $slogan_font; 42 | padding: 2em 0; 43 | } 44 | .more { 45 | font-size: 120%; 46 | font-family: $btn_font; 47 | display: flex; 48 | flex-flow: column; 49 | align-items: center; 50 | } 51 | } 52 | } 53 | 54 | /*-----------------------------------*/ 55 | 56 | .topic-container { 57 | position: relative; 58 | display: flex; 59 | padding: 0 1em; 60 | &.topic-left { 61 | flex-flow: row; 62 | text-align: left; 63 | } 64 | &.topic-right { 65 | flex-flow: row-reverse; 66 | text-align: right; 67 | .text { 68 | padding-left: 3vw; 69 | } 70 | } 71 | .text { 72 | flex: 3; 73 | max-width: $max_width * 0.5; 74 | display: inline-block; 75 | margin: 0 -1em; 76 | z-index: 2; 77 | background-color: rgba(255,255,255,0.9); 78 | padding: 2.5vw; 79 | } 80 | .image { 81 | flex: 2; 82 | max-width: $max_width * 0.5; 83 | display: inline-block; 84 | margin: 2.5vw -1em; 85 | z-index: 3; 86 | overflow: hidden; 87 | height: calc(100% - 5vw); 88 | display: inline-flex; 89 | align-items: center; 90 | } 91 | .btn-pdis { 92 | margin-top: 15px; 93 | } 94 | } 95 | 96 | @media screen and (max-width: $breakpoint_tablet) { 97 | .topic-container { 98 | width: 100%; 99 | // display: flex; 100 | &.topic-left, 101 | &.topic-right { 102 | flex-flow: column; 103 | } 104 | .text { 105 | // width: 100%; 106 | margin: 0; 107 | // max-width: $max_width; 108 | } 109 | .image { 110 | // width: 100%; 111 | // height: 100%; 112 | margin: 0; 113 | // max-width: $max_width; 114 | } 115 | } 116 | } 117 | 118 | /*-----------------------------------*/ 119 | 120 | .tell-us-container { 121 | background-image: url("/assets/img/pdis-team.JPG"); 122 | background-size: cover; 123 | margin: 0; 124 | padding: 2.5vw; 125 | text-align: center; 126 | } 127 | 128 | .tell-us-content { 129 | position: relative; 130 | background-color: rgba(255,255,255,0.9); 131 | display: inline-block; 132 | padding: 2.5vw; 133 | h2 { 134 | margin-bottom: 1em; 135 | } 136 | img { 137 | width: 30%; 138 | } 139 | } 140 | 141 | @media screen and (max-width: $breakpoint_tablet) { 142 | .tell-us-container { 143 | h2 { 144 | font-size: 2rem; 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /src/app/what-we-do/what-we-do.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { WhatWeDoComponent } from './what-we-do.component'; 7 | 8 | describe('WhatWeDoComponent', () => { 9 | let component: WhatWeDoComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ WhatWeDoComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(WhatWeDoComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/what-we-do/what-we-do.component.ts: -------------------------------------------------------------------------------- 1 | import { Http } from '@angular/http'; 2 | import { DataService } from './../shared/dataService/data-service.service'; 3 | import { Component, OnInit } from '@angular/core'; 4 | import { Discourselink } from './../../assets/discourselink'; 5 | // declare var particlesJS: any; 6 | declare var WOW: any; 7 | declare var Swiper: any; 8 | declare var $: any; 9 | 10 | @Component({ 11 | selector: 'app-what-we-do', 12 | templateUrl: './what-we-do.component.html', 13 | styleUrls: ['./what-we-do.component.scss'] 14 | }) 15 | export class WhatWeDoComponent implements OnInit { 16 | 17 | constructor(private dataService: DataService, private http: Http) { } 18 | 19 | topics = []; 20 | 21 | private getIds() { 22 | return this.http.get(Discourselink.Host+Discourselink.Category+Discourselink.Whatwedo+Discourselink.Filename) 23 | .map(function (data) { 24 | data = data.json(); 25 | var ids = []; 26 | var topics = data['topic_list']['topics']; 27 | topics.forEach(function (topic) { 28 | ids.push(topic['id']); 29 | }); 30 | return ids; 31 | }) 32 | // .do(data => console.log(data)); 33 | } 34 | 35 | private getPost(id: string) { 36 | return this.http.get(Discourselink.Host+Discourselink.Text + id + Discourselink.Filename) 37 | .map(function (data) { 38 | data = data.json(); 39 | var post = {}; 40 | var post_content = data['post_stream']['posts'][0]['cooked'].split("
"); 41 | post['id'] = id; 42 | post['title'] = post_content[0].replace(/<(?:.|\n)*?>/gm, ''); 43 | post['text'] = post_content[1]; 44 | post['image'] = post_content[2]; 45 | return post; 46 | }) 47 | // .do(data => console.log(data)); 48 | } 49 | 50 | /* an event handler to go #anchor scroll position */ 51 | goAnchor(anchor){ 52 | if(anchor == "top"){ 53 | /* go to top */ 54 | $('html, body').animate({ 55 | scrollTop: 0, 56 | }, 1000) 57 | } 58 | else if(anchor){ 59 | /* get the top position of anchor */ 60 | let anchor_y = $(anchor).offset().top 61 | /* go to anchor (animation to do) */ 62 | $('html, body').animate({ 63 | scrollTop: anchor_y, 64 | }, 1000) 65 | } 66 | return false 67 | } 68 | 69 | ngOnInit() { 70 | this.getIds() 71 | .subscribe(ids => { 72 | ids.forEach(id => { 73 | this.getPost(id) 74 | // .do(data => console.log(data)) 75 | .subscribe(post => { 76 | this.topics.push(post); 77 | }) 78 | }) 79 | }); 80 | 81 | /* WOW for animateCSS */ 82 | new WOW().init(); 83 | 84 | /* particlesJS */ 85 | // particlesJS.load("particles", "../../assets/particles.json", function () { 86 | // console.log('callback - particles.js config loaded'); 87 | // }); 88 | 89 | new Swiper('.swiper-container', { 90 | autoplay: 10000, /* 10 sec to change */ 91 | keyboardControl: true, 92 | direction: 'horizontal', 93 | spaceBetween: 50, 94 | grabCursor: true, 95 | nextButton: '.swiper-button-next', 96 | prevButton: '.swiper-button-prev', 97 | pagination: '.swiper-pagination', 98 | // paginationType: 'fraction' 99 | // loop: true, 100 | }) 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/app/what-we-do/work/work.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |

7 |
8 |
9 |
10 |
11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 29 |
30 | 31 |
32 | 33 |
34 | 35 |
-------------------------------------------------------------------------------- /src/app/what-we-do/work/work.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../../styles.scss"; 2 | 3 | $main-font-size: 22px; 4 | $line-height-size:35px; 5 | 6 | -------------------------------------------------------------------------------- /src/app/what-we-do/work/work.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { WorkComponent } from './work.component'; 7 | 8 | describe('WorkComponent', () => { 9 | let component: WorkComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ WorkComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(WorkComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/what-we-do/work/work.component.ts: -------------------------------------------------------------------------------- 1 | import { DomSanitizer } from '@angular/platform-browser/src/security/dom_sanitization_service'; 2 | import { DataService } from './../../shared/dataService/data-service.service'; 3 | import { Router, ActivatedRoute } from '@angular/router'; 4 | import { Component, OnInit } from '@angular/core'; 5 | import { Discourselink } from './../../../assets/discourselink'; 6 | 7 | 8 | @Component({ 9 | selector: 'app-work', 10 | templateUrl: './work.component.html', 11 | styleUrls: ['./work.component.scss'] 12 | }) 13 | export class WorkComponent { 14 | 15 | posts = []; 16 | post_title = "Vegeprice - 從可交付原型、不間斷溝通協調、到菜價資訊整合"; 17 | 18 | constructor(private workdetail: DataService, private sanitizer: DomSanitizer) { 19 | } 20 | 21 | ngOnInit() { 22 | this.workdetail.getData("1251") 23 | .subscribe((value) => { 24 | 25 | /*** get articles from json ***/ 26 | var articles = JSON.parse(value.text()).post_stream.posts; 27 | // let each article to be split and converted 28 | articles.forEach(element => { 29 | /*** split an article by
***/ 30 | var article = element['cooked'].split("
"); 31 | // convert lazyYT into iframe 32 | article = this.lazyTY2iframe(article); 33 | // push back into posts 34 | this.posts.push(article); 35 | 36 | }); 37 | }); 38 | } 39 | 40 | lazyTY2iframe(article) { 41 | var dom = new DOMParser(); 42 | var nodes: HTMLCollectionOf; 43 | var regexp_youtube = /
/; 44 | 45 | for (var i = 0; i < article.length; i++) { 46 | var e = article[i]; 47 | var iframe_string; 48 | var doc = dom.parseFromString(e, "text/html"); 49 | nodes = doc.getElementsByClassName("lazyYT"); 50 | 51 | for (var j = 0; j < nodes.length; j++) { 52 | iframe_string = "