├── .gitignore ├── .npmignore ├── .travis.yml ├── .yo-rc.json ├── LICENSE ├── README.MD ├── bs-config.json ├── gulpfile.js ├── package.json ├── playground ├── index.html ├── index.ts ├── systemjs-angular-loader.js ├── systemjs.config.js └── tsconfig.json ├── src ├── index.ts ├── package.json ├── timezone-picker.component.spec.ts ├── timezone-picker.component.ts ├── timezone-picker.pipe.ts ├── timezone-picker.service.ts ├── tsconfig.es5.json └── tsconfig.spec.json ├── tools └── gulp │ └── inline-resources.js ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Node 2 | node_modules/* 3 | npm-debug.log 4 | 5 | # TypeScript 6 | src/*.js 7 | src/*.map 8 | src/*.d.ts 9 | 10 | # JetBrains 11 | .idea 12 | .project 13 | .settings 14 | .idea/* 15 | *.iml 16 | 17 | # VS Code 18 | .vscode/* 19 | 20 | # Windows 21 | Thumbs.db 22 | Desktop.ini 23 | 24 | # Mac 25 | .DS_Store 26 | **/.DS_Store 27 | 28 | # Ngc generated files 29 | **/*.ngfactory.ts 30 | 31 | # Build files 32 | dist/* 33 | docs/* 34 | ng2-demo-app/* 35 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Node 2 | node_modules/* 3 | npm-debug.log 4 | docs/* 5 | # DO NOT IGNORE TYPESCRIPT FILES FOR NPM 6 | # TypeScript 7 | # *.js 8 | # *.map 9 | # *.d.ts 10 | 11 | # JetBrains 12 | .idea 13 | .project 14 | .settings 15 | .idea/* 16 | *.iml 17 | 18 | # VS Code 19 | .vscode/* 20 | 21 | # Windows 22 | Thumbs.db 23 | Desktop.ini 24 | 25 | # Mac 26 | .DS_Store 27 | **/.DS_Store 28 | 29 | # Ngc generated files 30 | **/*.ngfactory.ts 31 | 32 | # Library files 33 | src/* 34 | build/* 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - '4.2.1' 5 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-angular2-library": { 3 | "promptValues": { 4 | "gitRepositoryUrl": "https://github.com/samuelnygaard/ng2-timezone-selector" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Samuel Nygaard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # ng2-timezone-selector 2 | 3 | A simple Angular module to create a timezone selector using [moment-timezones](https://github.com/moment/moment-timezone). 4 | 5 | ## [Demo](https://samuelnygaard.github.io/ng2-timezone-selector/) | [Documentation](https://samuelnygaard.github.io/ng2-timezone-selector/docs/) 6 | 7 | ## New features (0.2.3) 8 | 9 | * Guess the timezone based on browser settings, usage: 10 | * `` 11 | * Show GMT offset (equivalent with UTC offset), e.g.: `Denmark (GTM+01:00)`, usage: 12 | * `` 13 | * Select timezone based on country iso code and outputs country iso code based on timezone, usage: 14 | * `` 15 | 16 | ## Installation 17 | 18 | To install this library, run: 19 | 20 | ```bash 21 | $ npm install ng2-timezone-selector --save 22 | ``` 23 | 24 | ### Requirements (IMPORTANT, use one of the following methods) 25 | 26 | The library depent on [jQuery](https://github.com/jquery/jquery) and [select2](https://github.com/select2/select2) and [moment-timezone](https://github.com/moment/moment-timezone) 27 | 28 | The only file required is the select2 `select2.min.css` file: 29 | 30 | #### 1. Method (should work for all) 31 | 32 | Include the `select2.min.css` file in the `angular-cli.json` file (remember to re-run `ng serve`, after editing `angular-cli.json`): 33 | 34 | ```json 35 | { 36 | "project": { 37 | ... 38 | }, 39 | "apps": [ 40 | { 41 | ... 42 | "styles": [ 43 | "styles.scss", 44 | "../node_modules/select2/dist/css/select2.min.css" 45 | ], 46 | ... 47 | } 48 | ``` 49 | 50 | #### 2. Method (simplest) 51 | 52 | If the angular project is setup to use `*.scss` instead of `*.css`, then you can add the following `@import` to the default `*.scss` file, othen called: `styles.scss`: 53 | 54 | ```scss 55 | @import '~select2/dist/css/select2.min.css'; 56 | ``` 57 | 58 | #### 3. Method (HTML-link) 59 | 60 | Include the `css` resource from a CDN link in the `index.html` file: 61 | 62 | ```xml 63 | 64 | ... 65 | 66 | ... 67 | 68 | ``` 69 | 70 | ### Importing 71 | 72 | Import the module in `app.module.ts`: 73 | 74 | ```typescript 75 | import { BrowserModule } from '@angular/platform-browser'; 76 | import { NgModule } from '@angular/core'; 77 | 78 | import { AppComponent } from './app.component'; 79 | 80 | // Import the library 81 | import { TimezonePickerModule } from 'ng2-timezone-selector'; 82 | 83 | @NgModule({ 84 | declarations: [ 85 | AppComponent 86 | ], 87 | imports: [ 88 | BrowserModule, 89 | ..., 90 | // Include the library in the imports section 91 | TimezonePickerModule 92 | ], 93 | providers: [], 94 | bootstrap: [AppComponent] 95 | }) 96 | export class AppModule { } 97 | ``` 98 | 99 | ## Usage 100 | 101 | Once the library is imported, you can use the component in your Angular application: 102 | 103 | ```xml 104 | 106 | 109 | 110 | ``` 111 | 112 | ```xml 113 | 115 | 121 | 122 | ``` 123 | 124 | ```typescript 125 | // Example of usage in app.component.ts: 126 | user = {}; 127 | placeholderString = 'Select timezone'; 128 | 129 | changeTimezone(timezone) { 130 | this.user.timezone = timezone; 131 | } 132 | ``` 133 | 134 | ## Parameters 135 | 136 | ### Component configuration 137 | 138 | You can configure component with the following input attributes: 139 | 140 | * `timezone: string` - Required: must be defined. The timezone string. If you are using the Two-Way Data Binding `[(timezone)]="timezoneString"` it will change on selection of timezone. 141 | * `placeholder: string` - Optional: default = `''`. The placeholder string to show when no timezone is selected. 142 | * `disabled: boolean` - Optional: default = `false`. Disable the the component. 143 | * `showOffset: boolean` - Optional: default = `false`. Condition to show GMT offset in the dropdown. 144 | * `guess: boolean` - Optional: default = `false`. If set to `true` and if the `timezone` parameters is `null` or `undefined` then guesses the users timezone. 145 | * `country: string` - Optional. The country iso-string (e.g. `'US'` / `'GB'` / `'AU'`). If you are using the Two-Way Data Binding e.g.: `[(country)]="countryIsoCode"` it will change the timezone to the provided iso-code (if the iso code is valid), and if the timezone is changed it changes the value of `countryIsoCode` to the iso of the country. 146 | * `allowClear: boolean` - Optional: default = `false`. Enabled you to clear the selection of a timezone. 147 | 148 | You can configure component with the `(change)="changeFunction($event)` or `(countryChange)="countryIsoCode = $event` output attributes: 149 | 150 | * `change: function($event)` - Trigger-function when timezone is selected or changed, the `$event` parameter is the timezone string. 151 | * `countryChange: function($event: string)` - Trigger-function when timezone is changed, the `$event` parameter is the country iso-code. 152 | 153 | ## License 154 | 155 | MIT © [Samuel Nygaard](mailto:teamnygaard@gmail.com) 156 | -------------------------------------------------------------------------------- /bs-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "baseDir": "src", 4 | "routes": { 5 | "/": "playground", 6 | "/node_modules/": "node_modules", 7 | "/dist/": "dist", 8 | "/.playground": ".playground" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var gulp = require('gulp'), 3 | path = require('path'), 4 | ngc = require('@angular/compiler-cli/src/main').main, 5 | rollup = require('gulp-rollup'), 6 | rename = require('gulp-rename'), 7 | del = require('del'), 8 | runSequence = require('run-sequence'), 9 | inlineResources = require('./tools/gulp/inline-resources'); 10 | 11 | const rootFolder = path.join(__dirname); 12 | const srcFolder = path.join(rootFolder, 'src'); 13 | const tmpFolder = path.join(rootFolder, '.tmp'); 14 | const buildFolder = path.join(rootFolder, 'build'); 15 | const distFolder = path.join(rootFolder, 'dist'); 16 | 17 | /** 18 | * 1. Delete /dist folder 19 | */ 20 | gulp.task('clean:dist', function () { 21 | 22 | // Delete contents but not dist folder to avoid broken npm links 23 | // when dist directory is removed while npm link references it. 24 | return deleteFolders([distFolder + '/**', '!' + distFolder]); 25 | }); 26 | 27 | /** 28 | * 2. Clone the /src folder into /.tmp. If an npm link inside /src has been made, 29 | * then it's likely that a node_modules folder exists. Ignore this folder 30 | * when copying to /.tmp. 31 | */ 32 | gulp.task('copy:source', function () { 33 | return gulp.src([`${srcFolder}/**/*`, `!${srcFolder}/node_modules`]) 34 | .pipe(gulp.dest(tmpFolder)); 35 | }); 36 | 37 | /** 38 | * 3. Inline template (.html) and style (.css) files into the the component .ts files. 39 | * We do this on the /.tmp folder to avoid editing the original /src files 40 | */ 41 | gulp.task('inline-resources', function () { 42 | return Promise.resolve() 43 | .then(() => inlineResources(tmpFolder)); 44 | }); 45 | 46 | 47 | /** 48 | * 4. Run the Angular compiler, ngc, on the /.tmp folder. This will output all 49 | * compiled modules to the /build folder. 50 | * 51 | * As of Angular 5, ngc accepts an array and no longer returns a promise. 52 | */ 53 | gulp.task('ngc', function () { 54 | ngc([ '--project', `${tmpFolder}/tsconfig.es5.json` ]); 55 | return Promise.resolve() 56 | }); 57 | 58 | /** 59 | * 5. Run rollup inside the /build folder to generate our Flat ES module and place the 60 | * generated file into the /dist folder 61 | */ 62 | gulp.task('rollup:fesm', function () { 63 | return gulp.src(`${buildFolder}/**/*.js`) 64 | // transform the files here. 65 | .pipe(rollup({ 66 | 67 | // Bundle's entry point 68 | // See "input" in https://rollupjs.org/#core-functionality 69 | input: `${buildFolder}/index.js`, 70 | 71 | // Allow mixing of hypothetical and actual files. "Actual" files can be files 72 | // accessed by Rollup or produced by plugins further down the chain. 73 | // This prevents errors like: 'path/file' does not exist in the hypothetical file system 74 | // when subdirectories are used in the `src` directory. 75 | allowRealFiles: true, 76 | 77 | // A list of IDs of modules that should remain external to the bundle 78 | // See "external" in https://rollupjs.org/#core-functionality 79 | external: [ 80 | '@angular/core', 81 | '@angular/common' 82 | ], 83 | 84 | // Format of generated bundle 85 | // See "format" in https://rollupjs.org/#core-functionality 86 | format: 'es' 87 | })) 88 | .pipe(gulp.dest(distFolder)); 89 | }); 90 | 91 | /** 92 | * 6. Run rollup inside the /build folder to generate our UMD module and place the 93 | * generated file into the /dist folder 94 | */ 95 | gulp.task('rollup:umd', function () { 96 | return gulp.src(`${buildFolder}/**/*.js`) 97 | // transform the files here. 98 | .pipe(rollup({ 99 | 100 | // Bundle's entry point 101 | // See "input" in https://rollupjs.org/#core-functionality 102 | input: `${buildFolder}/index.js`, 103 | 104 | // Allow mixing of hypothetical and actual files. "Actual" files can be files 105 | // accessed by Rollup or produced by plugins further down the chain. 106 | // This prevents errors like: 'path/file' does not exist in the hypothetical file system 107 | // when subdirectories are used in the `src` directory. 108 | allowRealFiles: true, 109 | 110 | // A list of IDs of modules that should remain external to the bundle 111 | // See "external" in https://rollupjs.org/#core-functionality 112 | external: [ 113 | '@angular/core', 114 | '@angular/common' 115 | ], 116 | 117 | // Format of generated bundle 118 | // See "format" in https://rollupjs.org/#core-functionality 119 | format: 'umd', 120 | 121 | // Export mode to use 122 | // See "exports" in https://rollupjs.org/#danger-zone 123 | exports: 'named', 124 | 125 | // The name to use for the module for UMD/IIFE bundles 126 | // (required for bundles with exports) 127 | // See "name" in https://rollupjs.org/#core-functionality 128 | name: 'ng2-timezone-selector', 129 | 130 | // See "globals" in https://rollupjs.org/#core-functionality 131 | globals: { 132 | typescript: 'ts' 133 | } 134 | 135 | })) 136 | .pipe(rename('ng2-timezone-selector.umd.js')) 137 | .pipe(gulp.dest(distFolder)); 138 | }); 139 | 140 | /** 141 | * 7. Copy all the files from /build to /dist, except .js files. We ignore all .js from /build 142 | * because with don't need individual modules anymore, just the Flat ES module generated 143 | * on step 5. 144 | */ 145 | gulp.task('copy:build', function () { 146 | return gulp.src([`${buildFolder}/**/*`, `!${buildFolder}/**/*.js`]) 147 | .pipe(gulp.dest(distFolder)); 148 | }); 149 | 150 | /** 151 | * 8. Copy package.json from /src to /dist 152 | */ 153 | gulp.task('copy:manifest', function () { 154 | return gulp.src([`${srcFolder}/package.json`]) 155 | .pipe(gulp.dest(distFolder)); 156 | }); 157 | 158 | /** 159 | * 9. Copy README.md from / to /dist 160 | */ 161 | gulp.task('copy:readme', function () { 162 | return gulp.src([path.join(rootFolder, 'README.MD')]) 163 | .pipe(gulp.dest(distFolder)); 164 | }); 165 | 166 | /** 167 | * 10. Delete /.tmp folder 168 | */ 169 | gulp.task('clean:tmp', function () { 170 | return deleteFolders([tmpFolder]); 171 | }); 172 | 173 | /** 174 | * 11. Delete /build folder 175 | */ 176 | gulp.task('clean:build', function () { 177 | return deleteFolders([buildFolder]); 178 | }); 179 | 180 | gulp.task('compile', function () { 181 | runSequence( 182 | 'clean:dist', 183 | 'copy:source', 184 | 'inline-resources', 185 | 'ngc', 186 | 'rollup:fesm', 187 | 'rollup:umd', 188 | 'copy:build', 189 | 'copy:manifest', 190 | 'copy:readme', 191 | 'clean:build', 192 | 'clean:tmp', 193 | function (err) { 194 | if (err) { 195 | console.log('ERROR:', err.message); 196 | deleteFolders([distFolder, tmpFolder, buildFolder]); 197 | } else { 198 | console.log('Compilation finished succesfully'); 199 | } 200 | }); 201 | }); 202 | 203 | /** 204 | * Watch for any change in the /src folder and compile files 205 | */ 206 | gulp.task('watch', function () { 207 | gulp.watch(`${srcFolder}/**/*`, ['compile']); 208 | }); 209 | 210 | gulp.task('clean', ['clean:dist', 'clean:tmp', 'clean:build']); 211 | 212 | gulp.task('build', ['clean', 'compile']); 213 | gulp.task('build:watch', ['build', 'watch']); 214 | gulp.task('default', ['build:watch']); 215 | 216 | /** 217 | * Deletes the specified folder 218 | */ 219 | function deleteFolders(folders) { 220 | return del(folders); 221 | } 222 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng2-timezone-selector", 3 | "version": "0.1.0", 4 | "scripts": { 5 | "build": "gulp build", 6 | "build:watch": "gulp", 7 | "docs": "npm run docs:build", 8 | "docs:build": 9 | "compodoc -p tsconfig.json -n ng2-timezone-selector -d docs --hideGenerator", 10 | "docs:serve": "npm run docs:build -- -s", 11 | "docs:watch": "npm run docs:build -- -s -w", 12 | "lint": "tslint --type-check --project tsconfig.json src/**/*.ts", 13 | "lite": "lite-server", 14 | "playground:build": "tsc -p playground -w", 15 | "playground": 16 | "concurrently \"npm run build:watch\" \"npm run playground:build\" \"npm run lite\"", 17 | "test": "tsc && karma start" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/samuelnygaard/ng2-timezone-selector" 22 | }, 23 | "author": { 24 | "name": "Samuel Nygaard", 25 | "email": "teamnygaard@gmail.com" 26 | }, 27 | "keywords": ["angular"], 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/samuelnygaard/ng2-timezone-selector/issues" 31 | }, 32 | "devDependencies": { 33 | "@angular/common": "^5.0.0", 34 | "@angular/compiler": "^5.0.0", 35 | "@angular/compiler-cli": "^5.0.0", 36 | "@angular/core": "^5.0.0", 37 | "@angular/platform-browser": "^5.0.0", 38 | "@angular/platform-browser-dynamic": "^5.0.0", 39 | "@compodoc/compodoc": "^1.0.0-beta.10", 40 | "@types/jasmine": "2.5.53", 41 | "@types/node": "~6.0.60", 42 | "angular-in-memory-web-api": "^0.3.2", 43 | "codelyzer": "~3.2.0", 44 | "concurrently": "^3.4.0", 45 | "core-js": "^2.4.1", 46 | "del": "^2.2.2", 47 | "gulp": "^3.9.1", 48 | "gulp-rename": "^1.2.2", 49 | "gulp-rollup": "^2.15.0", 50 | "jasmine-core": "~2.6.2", 51 | "jasmine-spec-reporter": "~4.1.0", 52 | "karma": "~1.7.0", 53 | "karma-chrome-launcher": "~2.1.1", 54 | "karma-cli": "~1.0.1", 55 | "karma-coverage-istanbul-reporter": "^1.2.1", 56 | "karma-jasmine": "~1.1.0", 57 | "karma-jasmine-html-reporter": "^0.2.2", 58 | "lite-server": "^2.3.0", 59 | "node-sass": "^4.5.2", 60 | "node-sass-tilde-importer": "^1.0.0", 61 | "node-watch": "^0.5.2", 62 | "protractor": "~5.1.2", 63 | "rollup": "^0.49.3", 64 | "run-sequence": "^1.2.2", 65 | "rxjs": "^5.5.2", 66 | "systemjs": "^0.20.12", 67 | "ts-node": "~3.2.0", 68 | "tslint": "~5.7.0", 69 | "typescript": "~2.4.2", 70 | "zone.js": "^0.8.14" 71 | }, 72 | "engines": { 73 | "node": ">=6.0.0" 74 | }, 75 | "dependencies": { 76 | "jquery": "^3.3.1", 77 | "moment-timezone": "^0.5.14", 78 | "select2": "^4.0.6-rc.1" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ng2-timezone-selector Playground 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | Loading AppComponent content here ... 31 | 32 | 33 | -------------------------------------------------------------------------------- /playground/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is only for local test 3 | */ 4 | import { BrowserModule } from '@angular/platform-browser'; 5 | import { NgModule } from '@angular/core'; 6 | import { Component } from '@angular/core'; 7 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 8 | 9 | import { TimezonePickerModule } from '../dist'; 10 | 11 | @Component({ 12 | selector: 'app', 13 | template: `` 14 | }) 15 | class AppComponent {} 16 | 17 | @NgModule({ 18 | bootstrap: [AppComponent], 19 | declarations: [AppComponent], 20 | imports: [BrowserModule, TimezonePickerModule] 21 | }) 22 | class AppModule {} 23 | 24 | platformBrowserDynamic().bootstrapModule(AppModule); 25 | -------------------------------------------------------------------------------- /playground/systemjs-angular-loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm; 4 | var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g; 5 | var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g; 6 | 7 | module.exports.translate = function (load){ 8 | if (load.source.indexOf('moduleId') !== -1) { 9 | return load; 10 | } 11 | 12 | // eslint-disable-next-line 13 | var url = document.createElement('a'); 14 | url.href = load.address; 15 | 16 | var basePathParts = url.pathname.split('/'); 17 | 18 | basePathParts.pop(); 19 | var basePath = basePathParts.join('/'); 20 | 21 | // eslint-disable-next-line 22 | var baseHref = document.createElement('a'); 23 | baseHref.href = this.baseURL; 24 | baseHref = baseHref.pathname; 25 | 26 | if (!baseHref.startsWith('/base/')) { // it is not karma 27 | basePath = basePath.replace(baseHref, ''); 28 | } 29 | 30 | load.source = load.source 31 | .replace(templateUrlRegex, function (match, quote, sourceUrl){ 32 | var resolvedUrl = sourceUrl; 33 | 34 | if (sourceUrl.startsWith('.')) { 35 | resolvedUrl = basePath + sourceUrl.substr(1); 36 | } 37 | 38 | return 'templateUrl: "' + resolvedUrl + '"'; 39 | }) 40 | .replace(stylesRegex, function (match, relativeUrls) { 41 | var urls = []; 42 | 43 | while ((match = stringRegex.exec(relativeUrls)) !== null) { 44 | if (match[2].startsWith('.')) { 45 | urls.push('"' + basePath + match[2].substr(1) + '"'); 46 | } else { 47 | urls.push('"' + match[2] + '"'); 48 | } 49 | } 50 | 51 | return 'styleUrls: [' + urls.join(', ') + ']'; 52 | }); 53 | 54 | return load; 55 | }; 56 | -------------------------------------------------------------------------------- /playground/systemjs.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * System configuration for Angular samples 4 | * Adjust as necessary for your application needs. 5 | */ 6 | (function () { 7 | System.config({ 8 | paths: { 9 | // paths serve as alias 10 | 'npm:': '../node_modules/' 11 | }, 12 | // map tells the System loader where to look for things 13 | map: { 14 | // our app is within the app folder 15 | app: 'app', 16 | 17 | // angular bundles 18 | '@angular/core': 'npm:@angular/core/bundles/core.umd.js', 19 | '@angular/common': 'npm:@angular/common/bundles/common.umd.js', 20 | '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', 21 | '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', 22 | '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', 23 | '@angular/http': 'npm:@angular/http/bundles/http.umd.js', 24 | '@angular/router': 'npm:@angular/router/bundles/router.umd.js', 25 | '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', 26 | 27 | // other libraries 28 | rxjs: 'npm:rxjs', 29 | 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js', 30 | 'ng2-timezone-selector': '../dist' 31 | }, 32 | // packages tells the System loader how to load when no filename and/or no extension 33 | packages: { 34 | app: { 35 | defaultExtension: 'js', 36 | meta: { 37 | './*.js': { 38 | loader: 'systemjs-angular-loader.js' 39 | } 40 | } 41 | }, 42 | rxjs: { 43 | defaultExtension: 'js' 44 | }, 45 | 'ng2-timezone-selector': { 46 | main: 'ng2-timezone-selector.umd.js', 47 | defaultExtension: 'js' 48 | } 49 | } 50 | }); 51 | })(this); 52 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "../.playground", 4 | "target": "es5", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "lib": [ "es2015", "dom" ], 11 | "noImplicitAny": true, 12 | "suppressImplicitAnyIndexErrors": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { TimezonePickerService } from './timezone-picker.service'; 2 | import { NgModule, ModuleWithProviders } from '@angular/core'; 3 | import { CommonModule } from '@angular/common'; 4 | import { TimezonePickerComponent } from './timezone-picker.component'; 5 | import { TimezonePickerPipe } from './timezone-picker.pipe'; 6 | 7 | export * from './timezone-picker.component'; 8 | export * from './timezone-picker.pipe'; 9 | export * from './timezone-picker.service'; 10 | 11 | @NgModule({ 12 | imports: [CommonModule], 13 | declarations: [TimezonePickerComponent, TimezonePickerPipe], 14 | providers: [TimezonePickerService], 15 | exports: [TimezonePickerComponent, TimezonePickerPipe] 16 | }) 17 | export class TimezonePickerModule { 18 | static forRoot(): ModuleWithProviders { 19 | return { 20 | ngModule: TimezonePickerModule, 21 | providers: [TimezonePickerService] 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng2-timezone-selector", 3 | "version": "0.2.4", 4 | "description": 5 | "A simple Angular module to create a timezone selector using moment-timezone", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/samuelnygaard/ng2-timezone-selector" 9 | }, 10 | "author": { 11 | "name": "Samuel Nygaard", 12 | "email": "teamnygaard@gmail.com" 13 | }, 14 | "keywords": [ 15 | "angular", 16 | "angular 4", 17 | "angular 5", 18 | "ng2", 19 | "timezone", 20 | "timezones", 21 | "selector", 22 | "picker", 23 | "moment", 24 | "moment-js", 25 | "moment-timezone", 26 | "timezone selector" 27 | ], 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/samuelnygaard/ng2-timezone-selector/issues" 31 | }, 32 | "main": "ng2-timezone-selector.umd.js", 33 | "module": "ng2-timezone-selector.js", 34 | "jsnext:main": "ng2-timezone-selector.js", 35 | "typings": "ng2-timezone-selector.d.ts", 36 | "peerDependencies": { 37 | "@angular/core": "^4.0.0 || ^5.0.0", 38 | "rxjs": "^5.1.0", 39 | "zone.js": "^0.8.4" 40 | }, 41 | "dependencies": { 42 | "jquery": "^3.3.1", 43 | "moment-timezone": "^0.5.14", 44 | "select2": "^4.0.6-rc.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/timezone-picker.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { By } from '@angular/platform-browser'; 3 | import { DebugElement } from '@angular/core'; 4 | 5 | import { TimezonePickerComponent } from './timezone-picker.component'; 6 | 7 | describe('TimezonePickerComponent', () => { 8 | let comp: TimezonePickerComponent; 9 | let fixture: ComponentFixture; 10 | let de: DebugElement; 11 | let el: HTMLElement; 12 | 13 | beforeEach(() => { 14 | TestBed.configureTestingModule({ 15 | declarations: [TimezonePickerComponent] // declare the test component 16 | }); 17 | 18 | fixture = TestBed.createComponent(TimezonePickerComponent); 19 | 20 | comp = fixture.componentInstance; // BannerComponent test instance 21 | 22 | // query for the title

by CSS element selector 23 | de = fixture.debugElement.query(By.css('h1')); 24 | el = de.nativeElement; 25 | }); 26 | 27 | it('Should be false', () => { 28 | expect(false).toBe(true); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/timezone-picker.component.ts: -------------------------------------------------------------------------------- 1 | import { TimezonePickerService, Timezone } from './timezone-picker.service'; 2 | import { 3 | Component, 4 | AfterViewInit, 5 | Input, 6 | ViewChild, 7 | ElementRef, 8 | EventEmitter, 9 | Output 10 | } from '@angular/core'; 11 | import * as moment from 'moment-timezone'; 12 | import $ from 'jquery'; 13 | import 'select2'; 14 | 15 | @Component({ 16 | selector: 'ng2-timezone-picker', 17 | template: ` 18 | ` 31 | }) 32 | export class TimezonePickerComponent implements AfterViewInit { 33 | /** 34 | * all time zones combined in one array, for each country 35 | */ 36 | allTimezones: Timezone[]; 37 | /** 38 | * ElementRef for the select element 39 | */ 40 | @ViewChild('select') select: ElementRef; 41 | 42 | /** 43 | * Input (optional) bound to [allowClear] 44 | */ 45 | @Input() allowClear = false; 46 | 47 | @Input() showOffset = false; 48 | 49 | @Input() guess = false; 50 | 51 | /** 52 | * Input (optional) bound to [disabled] 53 | */ 54 | @Input() disabled = false; 55 | 56 | placeholderString = 'Select timezone'; 57 | 58 | /** 59 | * Input (optional) bound to [placeholder] 60 | */ 61 | @Input() 62 | set placeholder(placeholder: string) { 63 | if (placeholder) { 64 | this.placeholderString = placeholder; 65 | const placeholderElem = $(this.select.nativeElement.parentElement).find( 66 | '.select2-selection__placeholder' 67 | ); 68 | if (placeholderElem.length > 0) { 69 | placeholderElem[0].innerText = placeholder; 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * The current selected timezone. 76 | */ 77 | currentTimezone: string; 78 | /** 79 | * Input: string (required) bound to [timezone] 80 | */ 81 | @Input() 82 | set timezone(timezone: string) { 83 | if (timezone) { 84 | this.currentTimezone = timezone; 85 | this.triggerChangeEvent(); 86 | } else if (this.guess) { 87 | this.currentTimezone = moment.tz.guess(); 88 | this.triggerChangeEvent(); 89 | } 90 | } 91 | 92 | @Input() 93 | set country(isoCode: string) { 94 | if (isoCode && !this.currentTimezone && !this.guess) { 95 | const res = this.allTimezones.find(x => x.iso === isoCode); 96 | if (res) { 97 | this.currentTimezone = res.zones[0]; 98 | this.triggerChangeEvent(); 99 | } 100 | } 101 | } 102 | 103 | /** 104 | * Output event bound to (timezone) 105 | */ 106 | @Output() timezoneChange = new EventEmitter(); 107 | 108 | /** 109 | * Output event bound to (change) 110 | */ 111 | @Output() change = new EventEmitter(); 112 | @Output() countryChange = new EventEmitter(); 113 | 114 | /** 115 | * Contructor function to define all the timezones 116 | */ 117 | constructor(public service: TimezonePickerService) { 118 | this.allTimezones = service.getZones(); 119 | } 120 | 121 | /** 122 | * $ bounding of select2 framework in the selectElement 123 | */ 124 | ngAfterViewInit() { 125 | const selectElement = $(this.select.nativeElement); 126 | 127 | selectElement.select2({ 128 | placeholder: this.placeholderString, 129 | allowClear: this.allowClear, 130 | matcher: (term, text) => this.matcher(term, text) 131 | }); 132 | 133 | if (this.currentTimezone) { 134 | $(selectElement) 135 | .val(this.currentTimezone) 136 | .trigger('change'); 137 | } 138 | 139 | selectElement.on('change', (e: any) => { 140 | this.onChange($(e.target).val()); 141 | }); 142 | } 143 | 144 | private triggerChangeEvent(): void { 145 | $(this.select.nativeElement) 146 | .val(this.currentTimezone) 147 | .trigger('change'); 148 | this.timezoneChange.emit(this.currentTimezone); 149 | this.change.emit(this.currentTimezone); 150 | this.countryChange.emit( 151 | this.allTimezones.find(x => x.zones.indexOf(this.currentTimezone) >= 0) 152 | .iso 153 | ); 154 | } 155 | 156 | formatTimezoneString(zone: string): string { 157 | const arr = zone.split('/'); 158 | return arr[arr.length - 1].replace('_', ' '); 159 | } 160 | 161 | offsetOfTimezone(zone: string): string { 162 | let offset = moment.tz(zone).utcOffset(); 163 | const neg = offset < 0; 164 | if (neg) { 165 | offset = -1 * offset; 166 | } 167 | const hours = Math.floor(offset / 60); 168 | const minutes = (offset / 60 - hours) * 60; 169 | return `(GMT${neg ? '-' : '+'}${this.rjust( 170 | hours.toString(), 171 | 2 172 | )}:${this.rjust(minutes.toString(), 2)})`; 173 | } 174 | 175 | /** 176 | * onChange function called by the "select" element 177 | * @param timezone The timezone string selected 178 | */ 179 | private onChange(timezone) { 180 | this.currentTimezone = timezone; 181 | this.timezoneChange.emit(timezone); 182 | this.change.emit(timezone); 183 | } 184 | 185 | /** 186 | * Matcher function to search in the select options 187 | * @param params contains the search term 188 | * @param data contains the data of each row 189 | */ 190 | private matcher(params, data) { 191 | // Always return the object if there is nothing to compare 192 | if ($.trim(params.term) === '') { 193 | return data; 194 | } 195 | 196 | let original = data.text.toUpperCase(); 197 | const term = params.term.toUpperCase(); 198 | 199 | // Replace '_' with ' ' to be able to search for 'New York' 200 | if (original.indexOf('_') !== -1) { 201 | original += original.replace('_', ' '); 202 | } 203 | 204 | // Check if the text contains the term 205 | if (original.indexOf(term) > -1) { 206 | return data; 207 | } 208 | 209 | // Do a recursive check for options with children 210 | if (data.children && data.children.length > 0) { 211 | // Clone the data object if there are children 212 | // This is required as we modify the object to remove any non-matches 213 | const match = $.extend(true, {}, data); 214 | // Check each child of the option 215 | for (let c = data.children.length - 1; c >= 0; c--) { 216 | const child = data.children[c]; 217 | const matches = this.matcher(params, child); 218 | // If there wasn't a match, remove the object in the array 219 | if (matches == null) { 220 | match.children.splice(c, 1); 221 | } 222 | } 223 | // If any children matched, return the new object 224 | if (match.children.length > 0) { 225 | return match; 226 | } 227 | // If there were no matching children, check just the plain object 228 | return this.matcher(params, match); 229 | } 230 | // If it doesn't contain the term, don't return anything 231 | return null; 232 | } 233 | 234 | private rjust(string: string, width: number, padding = '0'): string { 235 | padding = padding || ' '; 236 | padding = padding.substr(0, 1); 237 | if (string.length < width) { 238 | return padding.repeat(width - string.length) + string; 239 | } else { 240 | return string; 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/timezone-picker.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, PipeTransform, Pipe } from '@angular/core'; 2 | import { TimezonePickerService } from './timezone-picker.service'; 3 | 4 | /** 5 | * Transforms any input value 6 | */ 7 | @Pipe({ 8 | name: 'iso2CountryPipe' 9 | }) 10 | @Injectable() 11 | export class TimezonePickerPipe implements PipeTransform { 12 | constructor(private service: TimezonePickerService) {} 13 | transform(value: string): string { 14 | return this.service.iso2country(value); 15 | // return countryList[value] ? countryList[value] : value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/timezone-picker.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | export interface Timezone { 4 | iso: string; 5 | zones: string[]; 6 | } 7 | 8 | @Injectable() 9 | export class TimezonePickerService { 10 | 11 | /** 12 | * Convert country ISO code to country name (in english) 13 | */ 14 | iso2country(iso: string): string { 15 | return countryList[iso] ? countryList[iso] : iso; 16 | } 17 | 18 | /** 19 | * Gets the list of ISO-codes for all countries 20 | */ 21 | getCountries(): string[] { 22 | const res: string[] = []; 23 | for (const prop of Object.keys(countryList)) { 24 | res.push(prop); 25 | } 26 | return res; 27 | } 28 | 29 | /** 30 | * Get the timezones for each country 31 | */ 32 | getZones(): Timezone[] { 33 | const res = []; 34 | for (const prop of Object.keys(zones)) { 35 | res.push({ 36 | iso: prop, 37 | zones: zones[prop] 38 | }); 39 | } 40 | return res; 41 | } 42 | } 43 | 44 | const zones = { 45 | AD: ['Europe/Andorra'], 46 | AE: ['Asia/Dubai'], 47 | AF: ['Asia/Kabul'], 48 | AG: ['America/Antigua'], 49 | AI: ['America/Anguilla'], 50 | AL: ['Europe/Tirane'], 51 | AM: ['Asia/Yerevan'], 52 | AO: ['Africa/Luanda'], 53 | AQ: [ 54 | 'Antarctica/McMurdo', 55 | 'Antarctica/Rothera', 56 | 'Antarctica/Palmer', 57 | 'Antarctica/Mawson', 58 | 'Antarctica/Davis', 59 | 'Antarctica/Casey', 60 | 'Antarctica/Vostok', 61 | 'Antarctica/DumontDUrville', 62 | 'Antarctica/Syowa', 63 | 'Antarctica/Troll' 64 | ], 65 | AR: [ 66 | 'America/Argentina/Buenos_Aires', 67 | 'America/Argentina/Cordoba', 68 | 'America/Argentina/Salta', 69 | 'America/Argentina/Jujuy', 70 | 'America/Argentina/Tucuman', 71 | 'America/Argentina/Catamarca', 72 | 'America/Argentina/La_Rioja', 73 | 'America/Argentina/San_Juan', 74 | 'America/Argentina/Mendoza', 75 | 'America/Argentina/San_Luis', 76 | 'America/Argentina/Rio_Gallegos', 77 | 'America/Argentina/Ushuaia' 78 | ], 79 | AS: ['Pacific/Pago_Pago', 'Pacific/Samoa'], 80 | AT: ['Europe/Vienna'], 81 | AU: [ 82 | 'Australia/Lord_Howe', 83 | 'Antarctica/Macquarie', 84 | 'Australia/Hobart', 85 | 'Australia/Currie', 86 | 'Australia/Melbourne', 87 | 'Australia/Sydney', 88 | 'Australia/Broken_Hill', 89 | 'Australia/Brisbane', 90 | 'Australia/Lindeman', 91 | 'Australia/Adelaide', 92 | 'Australia/Darwin', 93 | 'Australia/Perth', 94 | 'Australia/Eucla', 95 | 'Australia/Canberra', 96 | 'Australia/Queensland', 97 | 'Australia/Tasmania', 98 | 'Australia/Victoria' 99 | ], 100 | AW: ['America/Aruba'], 101 | AX: ['Europe/Mariehamn'], 102 | AZ: ['Asia/Baku'], 103 | BA: ['Europe/Sarajevo'], 104 | BB: ['America/Barbados'], 105 | BD: ['Asia/Dhaka'], 106 | BE: ['Europe/Brussels'], 107 | BF: ['Africa/Ouagadougou'], 108 | BG: ['Europe/Sofia'], 109 | BH: ['Asia/Bahrain'], 110 | BI: ['Africa/Bujumbura'], 111 | BJ: ['Africa/Porto-Novo'], 112 | BL: ['America/St_Barthelemy'], 113 | BM: ['Atlantic/Bermuda'], 114 | BN: ['Asia/Brunei'], 115 | BO: ['America/La_Paz'], 116 | BQ: ['America/Kralendijk'], 117 | BR: [ 118 | 'America/Noronha', 119 | 'America/Belem', 120 | 'America/Fortaleza', 121 | 'America/Recife', 122 | 'America/Araguaina', 123 | 'America/Maceio', 124 | 'America/Bahia', 125 | 'America/Sao_Paulo', 126 | 'America/Campo_Grande', 127 | 'America/Cuiaba', 128 | 'America/Santarem', 129 | 'America/Porto_Velho', 130 | 'America/Boa_Vista', 131 | 'America/Manaus', 132 | 'America/Eirunepe', 133 | 'America/Rio_Branco' 134 | ], 135 | BS: ['America/Nassau'], 136 | BT: ['Asia/Thimphu'], 137 | BW: ['Africa/Gaborone'], 138 | BY: ['Europe/Minsk'], 139 | BZ: ['America/Belize'], 140 | CA: [ 141 | 'America/St_Johns', 142 | 'America/Halifax', 143 | 'America/Glace_Bay', 144 | 'America/Moncton', 145 | 'America/Goose_Bay', 146 | 'America/Blanc-Sablon', 147 | 'America/Toronto', 148 | 'America/Nipigon', 149 | 'America/Thunder_Bay', 150 | 'America/Iqaluit', 151 | 'America/Pangnirtung', 152 | 'America/Resolute', 153 | 'America/Atikokan', 154 | 'America/Rankin_Inlet', 155 | 'America/Winnipeg', 156 | 'America/Rainy_River', 157 | 'America/Regina', 158 | 'America/Swift_Current', 159 | 'America/Edmonton', 160 | 'America/Cambridge_Bay', 161 | 'America/Yellowknife', 162 | 'America/Inuvik', 163 | 'America/Creston', 164 | 'America/Dawson_Creek', 165 | 'America/Vancouver', 166 | 'America/Whitehorse', 167 | 'America/Dawson', 168 | 'America/Montreal', 169 | 'Canada/Atlantic', 170 | 'Canada/Central', 171 | 'Canada/Eastern', 172 | 'Canada/Mountain', 173 | 'Canada/Newfoundland', 174 | 'Canada/Pacific', 175 | 'Canada/Saskatchewan', 176 | 'Canada/Yukon' 177 | ], 178 | CC: ['Indian/Cocos'], 179 | CD: ['Africa/Kinshasa', 'Africa/Lubumbashi'], 180 | CF: ['Africa/Bangui'], 181 | CG: ['Africa/Brazzaville'], 182 | CH: ['Europe/Zurich'], 183 | CI: ['Africa/Abidjan'], 184 | CK: ['Pacific/Rarotonga'], 185 | CL: [ 186 | 'America/Santiago', 187 | 'Pacific/Easter', 188 | 'Chile/Continental', 189 | 'Chile/EasterIsland' 190 | ], 191 | CM: ['Africa/Douala'], 192 | CN: [ 193 | 'Asia/Shanghai', 194 | 'Asia/Harbin', 195 | 'Asia/Chongqing', 196 | 'Asia/Urumqi', 197 | 'Asia/Kashgar' 198 | ], 199 | CO: ['America/Bogota'], 200 | CR: ['America/Costa_Rica'], 201 | CU: ['America/Havana'], 202 | CV: ['Atlantic/Cape_Verde'], 203 | CW: ['America/Curacao'], 204 | CX: ['Indian/Christmas'], 205 | CY: ['Asia/Nicosia'], 206 | CZ: ['Europe/Prague'], 207 | DE: ['Europe/Berlin'], 208 | DJ: ['Africa/Djibouti'], 209 | DK: ['Europe/Copenhagen'], 210 | DM: ['America/Dominica'], 211 | DO: ['America/Santo_Domingo'], 212 | DZ: ['Africa/Algiers'], 213 | EC: ['America/Guayaquil', 'Pacific/Galapagos'], 214 | EE: ['Europe/Tallinn'], 215 | EG: ['Egypt'], 216 | EH: ['Africa/El_Aaiun'], 217 | ER: ['Africa/Asmara'], 218 | ES: ['Europe/Madrid', 'Africa/Ceuta', 'Atlantic/Canary'], 219 | ET: ['Africa/Addis_Ababa'], 220 | FI: ['Europe/Helsinki'], 221 | FJ: ['Pacific/Fiji'], 222 | FK: ['Atlantic/Stanley'], 223 | FM: ['Pacific/Chuuk', 'Pacific/Pohnpei', 'Pacific/Kosrae'], 224 | FO: ['Atlantic/Faroe'], 225 | FR: ['Europe/Paris'], 226 | GA: ['Africa/Libreville'], 227 | GB: ['Europe/London'], 228 | GD: ['America/Grenada'], 229 | GE: ['Asia/Tbilisi'], 230 | GF: ['America/Cayenne'], 231 | GG: ['Europe/Guernsey'], 232 | GH: ['Africa/Accra'], 233 | GI: ['Europe/Gibraltar'], 234 | GL: [ 235 | 'America/Godthab', 236 | 'America/Danmarkshavn', 237 | 'America/Scoresbysund', 238 | 'America/Thule' 239 | ], 240 | GM: ['Africa/Banjul'], 241 | GN: ['Africa/Conakry'], 242 | GP: ['America/Guadeloupe'], 243 | GQ: ['Africa/Malabo'], 244 | GR: ['Europe/Athens'], 245 | GS: ['Atlantic/South_Georgia'], 246 | GT: ['America/Guatemala'], 247 | GU: ['Pacific/Guam'], 248 | GW: ['Africa/Bissau'], 249 | GY: ['America/Guyana'], 250 | HK: ['Asia/Hong_Kong'], 251 | HN: ['America/Tegucigalpa'], 252 | HR: ['Europe/Zagreb'], 253 | HT: ['America/Port-au-Prince'], 254 | HU: ['Europe/Budapest'], 255 | ID: ['Asia/Jakarta', 'Asia/Pontianak', 'Asia/Makassar', 'Asia/Jayapura'], 256 | IE: ['Europe/Dublin'], 257 | IL: ['Asia/Jerusalem'], 258 | IM: ['Europe/Isle_of_Man'], 259 | IN: ['Asia/Kolkata'], 260 | IO: ['Indian/Chagos'], 261 | IQ: ['Asia/Baghdad'], 262 | IR: ['Asia/Tehran'], 263 | IS: ['Atlantic/Reykjavik'], 264 | IT: ['Europe/Rome'], 265 | JE: ['Europe/Jersey'], 266 | JM: ['America/Jamaica'], 267 | JO: ['Asia/Amman'], 268 | JP: ['Asia/Tokyo'], 269 | KE: ['Africa/Nairobi'], 270 | KG: ['Asia/Bishkek'], 271 | KH: ['Asia/Phnom_Penh'], 272 | KI: ['Pacific/Tarawa', 'Pacific/Enderbury', 'Pacific/Kiritimati'], 273 | KM: ['Indian/Comoro'], 274 | KN: ['America/St_Kitts'], 275 | KP: ['Asia/Pyongyang'], 276 | KR: ['Asia/Seoul'], 277 | KW: ['Asia/Kuwait'], 278 | KY: ['America/Cayman'], 279 | KZ: [ 280 | 'Asia/Almaty', 281 | 'Asia/Qyzylorda', 282 | 'Asia/Aqtobe', 283 | 'Asia/Aqtau', 284 | 'Asia/Oral' 285 | ], 286 | LA: ['Asia/Vientiane'], 287 | LB: ['Asia/Beirut'], 288 | LC: ['America/St_Lucia'], 289 | LI: ['Europe/Vaduz'], 290 | LK: ['Asia/Colombo'], 291 | LR: ['Africa/Monrovia'], 292 | LS: ['Africa/Maseru'], 293 | LT: ['Europe/Vilnius'], 294 | LU: ['Europe/Luxembourg'], 295 | LV: ['Europe/Riga'], 296 | LY: ['Africa/Tripoli'], 297 | MA: ['Africa/Casablanca'], 298 | MC: ['Europe/Monaco'], 299 | MD: ['Europe/Chisinau'], 300 | ME: ['Europe/Podgorica'], 301 | MF: ['America/Marigot'], 302 | MG: ['Indian/Antananarivo'], 303 | MH: ['Pacific/Majuro', 'Pacific/Kwajalein'], 304 | MK: ['Europe/Skopje'], 305 | ML: ['Africa/Bamako'], 306 | MM: ['Asia/Rangoon'], 307 | MN: ['Asia/Ulaanbaatar', 'Asia/Hovd', 'Asia/Choibalsan'], 308 | MO: ['Asia/Macau'], 309 | MP: ['Pacific/Saipan'], 310 | MQ: ['America/Martinique'], 311 | MR: ['Africa/Nouakchott'], 312 | MS: ['America/Montserrat'], 313 | MT: ['Europe/Malta'], 314 | MU: ['Indian/Mauritius'], 315 | MV: ['Indian/Maldives'], 316 | MW: ['Africa/Blantyre'], 317 | MX: [ 318 | 'America/Mexico_City', 319 | 'America/Cancun', 320 | 'America/Merida', 321 | 'America/Monterrey', 322 | 'America/Matamoros', 323 | 'America/Mazatlan', 324 | 'America/Chihuahua', 325 | 'America/Ojinaga', 326 | 'America/Hermosillo', 327 | 'America/Tijuana', 328 | 'America/Santa_Isabel', 329 | 'America/Bahia_Banderas' 330 | ], 331 | MY: ['Asia/Kuala_Lumpur', 'Asia/Kuching'], 332 | MZ: ['Africa/Maputo'], 333 | NA: ['Africa/Windhoek'], 334 | NC: ['Pacific/Noumea'], 335 | NE: ['Africa/Niamey'], 336 | NF: ['Pacific/Norfolk'], 337 | NG: ['Africa/Lagos'], 338 | NI: ['America/Managua'], 339 | NL: ['Europe/Amsterdam'], 340 | NO: ['Europe/Oslo'], 341 | NP: ['Asia/Kathmandu'], 342 | NR: ['Pacific/Nauru'], 343 | NU: ['Pacific/Niue'], 344 | NZ: ['Pacific/Auckland', 'Pacific/Chatham'], 345 | OM: ['Asia/Muscat'], 346 | PA: ['America/Panama'], 347 | PE: ['America/Lima'], 348 | PF: ['Pacific/Tahiti', 'Pacific/Marquesas', 'Pacific/Gambier'], 349 | PG: ['Pacific/Port_Moresby'], 350 | PH: ['Asia/Manila'], 351 | PK: ['Asia/Karachi'], 352 | PL: ['Europe/Warsaw', 'Poland'], 353 | PM: ['America/Miquelon'], 354 | PN: ['Pacific/Pitcairn'], 355 | PR: ['America/Puerto_Rico'], 356 | PS: ['Asia/Gaza', 'Asia/Hebron'], 357 | PT: ['Europe/Lisbon', 'Atlantic/Madeira', 'Atlantic/Azores'], 358 | PW: ['Pacific/Palau'], 359 | PY: ['America/Asuncion'], 360 | QA: ['Asia/Qatar'], 361 | RE: ['Indian/Reunion'], 362 | RO: ['Europe/Bucharest'], 363 | RS: ['Europe/Belgrade'], 364 | RU: [ 365 | 'Europe/Kaliningrad', 366 | 'Europe/Moscow', 367 | 'Europe/Volgograd', 368 | 'Europe/Samara', 369 | 'Europe/Simferopol', 370 | 'Asia/Yekaterinburg', 371 | 'Asia/Omsk', 372 | 'Asia/Novosibirsk', 373 | 'Asia/Novokuznetsk', 374 | 'Asia/Krasnoyarsk', 375 | 'Asia/Irkutsk', 376 | 'Asia/Yakutsk', 377 | 'Asia/Khandyga', 378 | 'Asia/Vladivostok', 379 | 'Asia/Sakhalin', 380 | 'Asia/Ust-Nera', 381 | 'Asia/Magadan', 382 | 'Asia/Kamchatka', 383 | 'Asia/Anadyr' 384 | ], 385 | RW: ['Africa/Kigali'], 386 | SA: ['Asia/Riyadh'], 387 | SB: ['Pacific/Guadalcanal'], 388 | SC: ['Indian/Mahe'], 389 | SD: ['Africa/Khartoum'], 390 | SE: ['Europe/Stockholm'], 391 | SG: ['Asia/Singapore'], 392 | SH: ['Atlantic/St_Helena'], 393 | SI: ['Europe/Ljubljana'], 394 | SJ: ['Arctic/Longyearbyen'], 395 | SK: ['Europe/Bratislava'], 396 | SL: ['Africa/Freetown'], 397 | SM: ['Europe/San_Marino'], 398 | SN: ['Africa/Dakar'], 399 | SO: ['Africa/Mogadishu'], 400 | SR: ['America/Paramaribo'], 401 | SS: ['Africa/Juba'], 402 | ST: ['Africa/Sao_Tome'], 403 | SV: ['America/El_Salvador'], 404 | SX: ['America/Lower_Princes'], 405 | SY: ['Asia/Damascus'], 406 | SZ: ['Africa/Mbabane'], 407 | TC: ['America/Grand_Turk'], 408 | TD: ['Africa/Ndjamena'], 409 | TF: ['Indian/Kerguelen'], 410 | TG: ['Africa/Lome'], 411 | TH: ['Asia/Bangkok'], 412 | TJ: ['Asia/Dushanbe'], 413 | TK: ['Pacific/Fakaofo'], 414 | TL: ['Asia/Dili'], 415 | TM: ['Asia/Ashgabat'], 416 | TN: ['Africa/Tunis'], 417 | TO: ['Pacific/Tongatapu'], 418 | TR: ['Europe/Istanbul'], 419 | TT: ['America/Port_of_Spain'], 420 | TV: ['Pacific/Funafuti'], 421 | TW: ['Asia/Taipei'], 422 | TZ: ['Africa/Dar_es_Salaam'], 423 | UA: ['Europe/Kiev', 'Europe/Uzhgorod', 'Europe/Zaporozhye'], 424 | UG: ['Africa/Kampala'], 425 | UM: ['Pacific/Johnston', 'Pacific/Midway', 'Pacific/Wake'], 426 | US: [ 427 | 'America/New_York', 428 | 'America/Detroit', 429 | 'America/Kentucky/Louisville', 430 | 'America/Kentucky/Monticello', 431 | 'America/Indiana/Indianapolis', 432 | 'America/Indiana/Vincennes', 433 | 'America/Indiana/Winamac', 434 | 'America/Indiana/Marengo', 435 | 'America/Indiana/Petersburg', 436 | 'America/Indiana/Vevay', 437 | 'America/Chicago', 438 | 'America/Indiana/Tell_City', 439 | 'America/Indiana/Knox', 440 | 'America/Menominee', 441 | 'America/North_Dakota/Center', 442 | 'America/North_Dakota/New_Salem', 443 | 'America/North_Dakota/Beulah', 444 | 'America/Denver', 445 | 'America/Boise', 446 | 'America/Phoenix', 447 | 'America/Los_Angeles', 448 | 'America/Anchorage', 449 | 'America/Juneau', 450 | 'America/Sitka', 451 | 'America/Yakutat', 452 | 'America/Nome', 453 | 'America/Adak', 454 | 'America/Metlakatla', 455 | 'Pacific/Honolulu' 456 | ], 457 | UY: ['America/Montevideo'], 458 | UZ: ['Asia/Samarkand', 'Asia/Tashkent'], 459 | VA: ['Europe/Vatican'], 460 | VC: ['America/St_Vincent'], 461 | VE: ['America/Caracas'], 462 | VG: ['America/Tortola'], 463 | VI: ['America/St_Thomas'], 464 | VN: ['Asia/Ho_Chi_Minh'], 465 | VU: ['Pacific/Efate'], 466 | WF: ['Pacific/Wallis'], 467 | WS: ['Pacific/Apia'], 468 | YE: ['Asia/Aden'], 469 | YT: ['Indian/Mayotte'], 470 | ZA: ['Africa/Johannesburg'], 471 | ZM: ['Africa/Lusaka'], 472 | ZW: ['Africa/Harare'] 473 | }; 474 | 475 | const countryList = { 476 | AF: 'Afghanistan', 477 | AX: 'Aland Islands', 478 | AL: 'Albania', 479 | DZ: 'Algeria', 480 | AS: 'American Samoa', 481 | AD: 'Andorra', 482 | AO: 'Angola', 483 | AI: 'Anguilla', 484 | AQ: 'Antarctica', 485 | AG: 'Antigua and Barbuda', 486 | AR: 'Argentina', 487 | AM: 'Armenia', 488 | AW: 'Aruba', 489 | AU: 'Australia', 490 | AT: 'Austria', 491 | AZ: 'Azerbaijan', 492 | BS: 'Bahamas', 493 | BH: 'Bahrain', 494 | BD: 'Bangladesh', 495 | BB: 'Barbados', 496 | BY: 'Belarus', 497 | BE: 'Belgium', 498 | BZ: 'Belize', 499 | BJ: 'Benin', 500 | BM: 'Bermuda', 501 | BT: 'Bhutan', 502 | BO: 'Bolivia', 503 | BA: 'Bosnia and Herzegovina', 504 | BW: 'Botswana', 505 | BV: 'Bouvet Island', 506 | BR: 'Brazil', 507 | VG: 'British Virgin Islands', 508 | IO: 'British Indian Ocean Territory', 509 | BN: 'Brunei Darussalam', 510 | BG: 'Bulgaria', 511 | BF: 'Burkina Faso', 512 | BI: 'Burundi', 513 | KH: 'Cambodia', 514 | CM: 'Cameroon', 515 | CA: 'Canada', 516 | CV: 'Cape Verde', 517 | KY: 'Cayman Islands', 518 | CF: 'Central African Republic', 519 | TD: 'Chad', 520 | CL: 'Chile', 521 | CN: 'China', 522 | HK: 'Hong Kong', 523 | MO: 'Macao', 524 | CX: 'Christmas Island', 525 | CC: 'Cocos (Keeling) Islands', 526 | CO: 'Colombia', 527 | KM: 'Comoros', 528 | CG: 'Congo (Brazzaville)', 529 | CD: 'Congo, (Kinshasa)', 530 | CK: 'Cook Islands', 531 | CR: 'Costa Rica', 532 | CI: "Côte d'Ivoire", 533 | HR: 'Croatia', 534 | CU: 'Cuba', 535 | CY: 'Cyprus', 536 | CZ: 'Czech Republic', 537 | DK: 'Denmark', 538 | DJ: 'Djibouti', 539 | DM: 'Dominica', 540 | DO: 'Dominican Republic', 541 | EC: 'Ecuador', 542 | EG: 'Egypt', 543 | SV: 'El Salvador', 544 | GQ: 'Equatorial Guinea', 545 | ER: 'Eritrea', 546 | EE: 'Estonia', 547 | ET: 'Ethiopia', 548 | FK: 'Falkland Islands (Malvinas)', 549 | FO: 'Faroe Islands', 550 | FJ: 'Fiji', 551 | FI: 'Finland', 552 | FR: 'France', 553 | GF: 'French Guiana', 554 | PF: 'French Polynesia', 555 | TF: 'French Southern Territories', 556 | GA: 'Gabon', 557 | GM: 'Gambia', 558 | GE: 'Georgia', 559 | DE: 'Germany', 560 | GH: 'Ghana', 561 | GI: 'Gibraltar', 562 | GR: 'Greece', 563 | GL: 'Greenland', 564 | GD: 'Grenada', 565 | GP: 'Guadeloupe', 566 | GU: 'Guam', 567 | GT: 'Guatemala', 568 | GG: 'Guernsey', 569 | GN: 'Guinea', 570 | GW: 'Guinea-Bissau', 571 | GY: 'Guyana', 572 | HT: 'Haiti', 573 | HM: 'Heard and Mcdonald Islands', 574 | VA: 'Vatican City State', 575 | HN: 'Honduras', 576 | HU: 'Hungary', 577 | IS: 'Iceland', 578 | IN: 'India', 579 | ID: 'Indonesia', 580 | IR: 'Iran', 581 | IQ: 'Iraq', 582 | IE: 'Ireland', 583 | IM: 'Isle of Man', 584 | IL: 'Israel', 585 | IT: 'Italy', 586 | JM: 'Jamaica', 587 | JP: 'Japan', 588 | JE: 'Jersey', 589 | JO: 'Jordan', 590 | KZ: 'Kazakhstan', 591 | KE: 'Kenya', 592 | KI: 'Kiribati', 593 | KP: 'Korea (North)', 594 | KR: 'Korea (South)', 595 | KW: 'Kuwait', 596 | KG: 'Kyrgyzstan', 597 | LA: 'Lao PDR', 598 | LV: 'Latvia', 599 | LB: 'Lebanon', 600 | LS: 'Lesotho', 601 | LR: 'Liberia', 602 | LY: 'Libya', 603 | LI: 'Liechtenstein', 604 | LT: 'Lithuania', 605 | LU: 'Luxembourg', 606 | MK: 'Macedonia', 607 | MG: 'Madagascar', 608 | MW: 'Malawi', 609 | MY: 'Malaysia', 610 | MV: 'Maldives', 611 | ML: 'Mali', 612 | MT: 'Malta', 613 | MH: 'Marshall Islands', 614 | MQ: 'Martinique', 615 | MR: 'Mauritania', 616 | MU: 'Mauritius', 617 | YT: 'Mayotte', 618 | MX: 'Mexico', 619 | FM: 'Micronesia', 620 | MD: 'Moldova', 621 | MC: 'Monaco', 622 | MN: 'Mongolia', 623 | ME: 'Montenegro', 624 | MS: 'Montserrat', 625 | MA: 'Morocco', 626 | MZ: 'Mozambique', 627 | MM: 'Myanmar', 628 | NA: 'Namibia', 629 | NR: 'Nauru', 630 | NP: 'Nepal', 631 | NL: 'Netherlands', 632 | AN: 'Netherlands Antilles', 633 | NC: 'New Caledonia', 634 | NZ: 'New Zealand', 635 | NI: 'Nicaragua', 636 | NE: 'Niger', 637 | NG: 'Nigeria', 638 | NU: 'Niue', 639 | NF: 'Norfolk Island', 640 | MP: 'Northern Mariana Islands', 641 | NO: 'Norway', 642 | OM: 'Oman', 643 | PK: 'Pakistan', 644 | PW: 'Palau', 645 | PS: 'Palestinian Territory', 646 | PA: 'Panama', 647 | PG: 'Papua New Guinea', 648 | PY: 'Paraguay', 649 | PE: 'Peru', 650 | PH: 'Philippines', 651 | PN: 'Pitcairn', 652 | PL: 'Poland', 653 | PT: 'Portugal', 654 | PR: 'Puerto Rico', 655 | QA: 'Qatar', 656 | RE: 'Réunion', 657 | RO: 'Romania', 658 | RU: 'Russian Federation', 659 | RW: 'Rwanda', 660 | BL: 'Saint-Barthélemy', 661 | SH: 'Saint Helena', 662 | KN: 'Saint Kitts and Nevis', 663 | LC: 'Saint Lucia', 664 | MF: 'Saint-Martin (French part)', 665 | PM: 'Saint Pierre and Miquelon', 666 | VC: 'Saint Vincent and Grenadines', 667 | WS: 'Samoa', 668 | SM: 'San Marino', 669 | ST: 'Sao Tome and Principe', 670 | SA: 'Saudi Arabia', 671 | SN: 'Senegal', 672 | RS: 'Serbia', 673 | SC: 'Seychelles', 674 | SL: 'Sierra Leone', 675 | SG: 'Singapore', 676 | SK: 'Slovakia', 677 | SI: 'Slovenia', 678 | SB: 'Solomon Islands', 679 | SO: 'Somalia', 680 | ZA: 'South Africa', 681 | GS: 'South Georgia and the South Sandwich Islands', 682 | SS: 'South Sudan', 683 | ES: 'Spain', 684 | LK: 'Sri Lanka', 685 | SD: 'Sudan', 686 | SR: 'Suriname', 687 | SJ: 'Svalbard and Jan Mayen Islands', 688 | SZ: 'Swaziland', 689 | SE: 'Sweden', 690 | CH: 'Switzerland', 691 | SY: 'Syria', 692 | TW: 'Taiwan', 693 | TJ: 'Tajikistan', 694 | TZ: 'Tanzania', 695 | TH: 'Thailand', 696 | TL: 'Timor-Leste', 697 | TG: 'Togo', 698 | TK: 'Tokelau', 699 | TO: 'Tonga', 700 | TT: 'Trinidad and Tobago', 701 | TN: 'Tunisia', 702 | TR: 'Turkey', 703 | TM: 'Turkmenistan', 704 | TC: 'Turks and Caicos Islands', 705 | TV: 'Tuvalu', 706 | UG: 'Uganda', 707 | UA: 'Ukraine', 708 | AE: 'United Arab Emirates', 709 | GB: 'United Kingdom (GB)', 710 | US: 'United States of America (USA)', 711 | UM: 'US Minor Outlying Islands', 712 | UY: 'Uruguay', 713 | UZ: 'Uzbekistan', 714 | VU: 'Vanuatu', 715 | VE: 'Venezuela', 716 | VN: 'Viet Nam', 717 | VI: 'Virgin Islands, US', 718 | WF: 'Wallis and Futuna Islands', 719 | EH: 'Western Sahara', 720 | YE: 'Yemen', 721 | ZM: 'Zambia', 722 | ZW: 'Zimbabwe' 723 | }; 724 | -------------------------------------------------------------------------------- /src/tsconfig.es5.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "module": "es2015", 5 | "target": "es5", 6 | "baseUrl": ".", 7 | "stripInternal": true, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "moduleResolution": "node", 11 | "outDir": "../build", 12 | "rootDir": ".", 13 | "lib": [ 14 | "es2015", 15 | "dom" 16 | ], 17 | "skipLibCheck": true, 18 | "types": [] 19 | }, 20 | "angularCompilerOptions": { 21 | "annotateForClosureCompiler": true, 22 | "strictMetadataEmit": true, 23 | "skipTemplateCodegen": true, 24 | "flatModuleOutFile": "ng2-timezone-selector.js", 25 | "flatModuleId": "ng2-timezone-selector" 26 | }, 27 | "files": [ 28 | "./index.ts" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.es5.json", 3 | "compilerOptions": { 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "outDir": "../out-tsc/spec", 7 | "module": "commonjs", 8 | "target": "es6", 9 | "baseUrl": "", 10 | "types": [ 11 | "jest", 12 | "node" 13 | ] 14 | }, 15 | "files": [ 16 | "**/*.spec.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tools/gulp/inline-resources.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // https://github.com/filipesilva/angular-quickstart-lib/blob/master/inline-resources.js 3 | 'use strict'; 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const glob = require('glob'); 8 | const sass = require('node-sass'); 9 | const tildeImporter = require('node-sass-tilde-importer'); 10 | 11 | /** 12 | * Simple Promiseify function that takes a Node API and return a version that supports promises. 13 | * We use promises instead of synchronized functions to make the process less I/O bound and 14 | * faster. It also simplifies the code. 15 | */ 16 | function promiseify(fn) { 17 | return function () { 18 | const args = [].slice.call(arguments, 0); 19 | return new Promise((resolve, reject) => { 20 | fn.apply(this, args.concat([function (err, value) { 21 | if (err) { 22 | reject(err); 23 | } else { 24 | resolve(value); 25 | } 26 | }])); 27 | }); 28 | }; 29 | } 30 | 31 | const readFile = promiseify(fs.readFile); 32 | const writeFile = promiseify(fs.writeFile); 33 | 34 | /** 35 | * Inline resources in a tsc/ngc compilation. 36 | * @param projectPath {string} Path to the project. 37 | */ 38 | function inlineResources(projectPath) { 39 | 40 | // Match only TypeScript files in projectPath. 41 | const files = glob.sync('**/*.ts', {cwd: projectPath}); 42 | 43 | // For each file, inline the templates and styles under it and write the new file. 44 | return Promise.all(files.map(filePath => { 45 | const fullFilePath = path.join(projectPath, filePath); 46 | return readFile(fullFilePath, 'utf-8') 47 | .then(content => inlineResourcesFromString(content, url => { 48 | // Resolve the template url. 49 | return path.join(path.dirname(fullFilePath), url); 50 | })) 51 | .then(content => writeFile(fullFilePath, content)) 52 | .catch(err => { 53 | console.error('An error occured: ', err); 54 | }); 55 | })); 56 | } 57 | 58 | /** 59 | * Inline resources from a string content. 60 | * @param content {string} The source file's content. 61 | * @param urlResolver {Function} A resolver that takes a URL and return a path. 62 | * @returns {string} The content with resources inlined. 63 | */ 64 | function inlineResourcesFromString(content, urlResolver) { 65 | // Curry through the inlining functions. 66 | return [ 67 | inlineTemplate, 68 | inlineStyle, 69 | removeModuleId 70 | ].reduce((content, fn) => fn(content, urlResolver), content); 71 | } 72 | 73 | /** 74 | * Inline the templates for a source file. Simply search for instances of `templateUrl: ...` and 75 | * replace with `template: ...` (with the content of the file included). 76 | * @param content {string} The source file's content. 77 | * @param urlResolver {Function} A resolver that takes a URL and return a path. 78 | * @return {string} The content with all templates inlined. 79 | */ 80 | function inlineTemplate(content, urlResolver) { 81 | return content.replace(/templateUrl:\s*'([^']+?\.html)'/g, function (m, templateUrl) { 82 | const templateFile = urlResolver(templateUrl); 83 | const templateContent = fs.readFileSync(templateFile, 'utf-8'); 84 | const shortenedTemplate = templateContent 85 | .replace(/([\n\r]\s*)+/gm, ' ') 86 | .replace(/"/g, '\\"'); 87 | return `template: "${shortenedTemplate}"`; 88 | }); 89 | } 90 | 91 | 92 | /** 93 | * Inline the styles for a source file. Simply search for instances of `styleUrls: [...]` and 94 | * replace with `styles: [...]` (with the content of the file included). 95 | * @param urlResolver {Function} A resolver that takes a URL and return a path. 96 | * @param content {string} The source file's content. 97 | * @return {string} The content with all styles inlined. 98 | */ 99 | function inlineStyle(content, urlResolver) { 100 | return content.replace(/styleUrls\s*:\s*(\[[\s\S]*?\])/gm, function (m, styleUrls) { 101 | const urls = eval(styleUrls); 102 | return 'styles: [' 103 | + urls.map(styleUrl => { 104 | const styleFile = urlResolver(styleUrl); 105 | const originContent = fs.readFileSync(styleFile, 'utf-8'); 106 | const styleContent = styleFile.endsWith('.scss') ? buildSass(originContent, styleFile) : originContent; 107 | const shortenedStyle = styleContent 108 | .replace(/([\n\r]\s*)+/gm, ' ') 109 | .replace(/"/g, '\\"'); 110 | return `"${shortenedStyle}"`; 111 | }) 112 | .join(',\n') 113 | + ']'; 114 | }); 115 | } 116 | 117 | /** 118 | * build sass content to css 119 | * @param content {string} the css content 120 | * @param sourceFile {string} the scss file sourceFile 121 | * @return {string} the generated css, empty string if error occured 122 | */ 123 | function buildSass(content, sourceFile) { 124 | try { 125 | const result = sass.renderSync({ 126 | data: content, 127 | file: sourceFile, 128 | importer: tildeImporter 129 | }); 130 | return result.css.toString() 131 | } catch (e) { 132 | console.error('\x1b[41m'); 133 | console.error('at ' + sourceFile + ':' + e.line + ":" + e.column); 134 | console.error(e.formatted); 135 | console.error('\x1b[0m'); 136 | return ""; 137 | } 138 | } 139 | 140 | /** 141 | * Remove every mention of `moduleId: module.id`. 142 | * @param content {string} The source file's content. 143 | * @returns {string} The content with all moduleId: mentions removed. 144 | */ 145 | function removeModuleId(content) { 146 | return content.replace(/\s*moduleId:\s*module\.id\s*,?\s*/gm, ''); 147 | } 148 | 149 | module.exports = inlineResources; 150 | module.exports.inlineResourcesFromString = inlineResourcesFromString; 151 | 152 | // Run inlineResources if module is being called directly from the CLI with arguments. 153 | if (require.main === module && process.argv.length > 2) { 154 | console.log('Inlining resources from project:', process.argv[2]); 155 | return inlineResources(process.argv[2]); 156 | } 157 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src", 4 | "experimentalDecorators": true, 5 | "moduleResolution": "node", 6 | "rootDir": "./src", 7 | "lib": [ 8 | "es2015", 9 | "dom" 10 | ], 11 | "skipLibCheck": true, 12 | "types": [] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "class-name": true, 7 | "comment-format": [ 8 | true, 9 | "check-space" 10 | ], 11 | "curly": true, 12 | "eofline": true, 13 | "forin": true, 14 | "indent": [ 15 | true, 16 | "spaces" 17 | ], 18 | "label-position": true, 19 | "max-line-length": [ 20 | true, 21 | 140 22 | ], 23 | "member-access": false, 24 | "member-ordering": [ 25 | true, 26 | "static-before-instance", 27 | "variables-before-functions" 28 | ], 29 | "no-arg": true, 30 | "no-bitwise": true, 31 | "no-console": [ 32 | true, 33 | "debug", 34 | "info", 35 | "time", 36 | "timeEnd", 37 | "trace" 38 | ], 39 | "no-construct": true, 40 | "no-debugger": true, 41 | "no-duplicate-variable": true, 42 | "no-empty": false, 43 | "no-eval": true, 44 | "no-inferrable-types": true, 45 | "no-shadowed-variable": true, 46 | "no-string-literal": false, 47 | "no-switch-case-fall-through": true, 48 | "no-trailing-whitespace": true, 49 | "no-unused-expression": true, 50 | "no-unused-variable": true, 51 | "no-use-before-declare": true, 52 | "no-var-keyword": true, 53 | "object-literal-sort-keys": false, 54 | "one-line": [ 55 | true, 56 | "check-open-brace", 57 | "check-catch", 58 | "check-else", 59 | "check-whitespace" 60 | ], 61 | "quotemark": [ 62 | true, 63 | "single" 64 | ], 65 | "radix": true, 66 | "semicolon": [ 67 | "always" 68 | ], 69 | "triple-equals": [ 70 | true, 71 | "allow-null-check" 72 | ], 73 | "typedef-whitespace": [ 74 | true, 75 | { 76 | "call-signature": "nospace", 77 | "index-signature": "nospace", 78 | "parameter": "nospace", 79 | "property-declaration": "nospace", 80 | "variable-declaration": "nospace" 81 | } 82 | ], 83 | "variable-name": false, 84 | "whitespace": [ 85 | true, 86 | "check-branch", 87 | "check-decl", 88 | "check-operator", 89 | "check-separator", 90 | "check-type" 91 | ], 92 | "directive-selector": [true, "attribute", "", "camelCase"], 93 | "component-selector": [true, "element", "", "kebab-case"], 94 | "use-input-property-decorator": true, 95 | "use-output-property-decorator": true, 96 | "use-host-property-decorator": true, 97 | "no-input-rename": true, 98 | "no-output-rename": true, 99 | "use-life-cycle-interface": true, 100 | "use-pipe-transform-interface": true, 101 | "component-class-suffix": true, 102 | "directive-class-suffix": true 103 | } 104 | } 105 | --------------------------------------------------------------------------------