├── src ├── app │ ├── app.component.scss │ ├── app.component.html │ ├── helpers.ts │ ├── app.component.ts │ ├── constants.ts │ ├── backward-compatibility.ts │ ├── pipes │ │ ├── sanitize-html.pipe.ts │ │ └── count.pipe.ts │ ├── app-routing.module.ts │ ├── post │ │ ├── post.component.ts │ │ └── post.component.html │ ├── models.ts │ ├── app.component.spec.ts │ ├── feed │ │ ├── feed.component.html │ │ └── feed.component.ts │ ├── core.service.ts │ ├── app.module.ts │ └── home │ │ ├── home.component.ts │ │ └── home.component.html ├── favicon.ico ├── assets │ ├── favicon.ico │ ├── skulls.png │ ├── icon-128x128.png │ ├── icon-144x144.png │ ├── icon-152x152.png │ ├── icon-72x72.png │ ├── icon-96x96.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── mstile-150x150.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── browserconfig.xml │ ├── share.svg │ ├── unexpand.svg │ ├── expand.svg │ ├── back.svg │ ├── safari-pinned-tab.svg │ └── fur-boot-green.svg ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── main.ts ├── test.ts ├── manifest.json ├── index.html ├── polyfills.ts ├── styles.scss └── pure.css ├── docs ├── favicon.ico ├── assets │ ├── skulls.png │ ├── favicon.ico │ ├── icon-72x72.png │ ├── icon-96x96.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── icon-128x128.png │ ├── icon-144x144.png │ ├── icon-152x152.png │ ├── apple-touch-icon.png │ ├── mstile-150x150.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── browserconfig.xml │ ├── share.svg │ ├── unexpand.svg │ ├── expand.svg │ ├── back.svg │ ├── safari-pinned-tab.svg │ └── fur-boot-green.svg ├── safety-worker.js ├── worker-basic.min.js ├── runtime.7b63b9fd40098a2e8207.js ├── manifest.json ├── index.html ├── ngsw.json ├── styles.8d877fb47cb501c4832a.css ├── 3rdpartylicenses.txt └── polyfills.00096ed7d93ed26ee6df.js ├── e2e ├── src │ ├── app.po.ts │ └── app.e2e-spec.ts ├── tsconfig.json └── protractor.conf.js ├── .editorconfig ├── tsconfig.app.json ├── tsconfig.spec.json ├── tsconfig.json ├── .browserslistrc ├── .gitignore ├── LICENSE ├── README.md ├── ngsw-config.json ├── karma.conf.js ├── package.json ├── tslint.json └── angular.json /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /docs/assets/skulls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/docs/assets/skulls.png -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/assets/skulls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/src/assets/skulls.png -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /docs/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/docs/assets/favicon.ico -------------------------------------------------------------------------------- /docs/assets/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/docs/assets/icon-72x72.png -------------------------------------------------------------------------------- /docs/assets/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/docs/assets/icon-96x96.png -------------------------------------------------------------------------------- /src/assets/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/src/assets/icon-128x128.png -------------------------------------------------------------------------------- /src/assets/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/src/assets/icon-144x144.png -------------------------------------------------------------------------------- /src/assets/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/src/assets/icon-152x152.png -------------------------------------------------------------------------------- /src/assets/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/src/assets/icon-72x72.png -------------------------------------------------------------------------------- /src/assets/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/src/assets/icon-96x96.png -------------------------------------------------------------------------------- /docs/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/docs/assets/favicon-16x16.png -------------------------------------------------------------------------------- /docs/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/docs/assets/favicon-32x32.png -------------------------------------------------------------------------------- /docs/assets/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/docs/assets/icon-128x128.png -------------------------------------------------------------------------------- /docs/assets/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/docs/assets/icon-144x144.png -------------------------------------------------------------------------------- /docs/assets/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/docs/assets/icon-152x152.png -------------------------------------------------------------------------------- /src/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/src/assets/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/src/assets/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/src/assets/mstile-150x150.png -------------------------------------------------------------------------------- /docs/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/docs/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/assets/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/docs/assets/mstile-150x150.png -------------------------------------------------------------------------------- /src/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/src/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/src/assets/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/assets/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/src/assets/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/assets/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/docs/assets/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/assets/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gurov/stupid-rss-reader/HEAD/docs/assets/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/app/helpers.ts: -------------------------------------------------------------------------------- 1 | export const isValidHttpUrl = (s: string) => { 2 | let url; 3 | 4 | try { 5 | url = new URL(s); 6 | } catch (_) { 7 | return false; 8 | } 9 | 10 | return url.protocol === "http:" || url.protocol === "https:"; 11 | } -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.scss'] 8 | }) 9 | export class AppComponent { 10 | title = 'stupid-rss-reader'; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/constants.ts: -------------------------------------------------------------------------------- 1 | export enum TABLES { 2 | FEEDS = 'feeds', 3 | POSTS = 'posts' 4 | } 5 | 6 | export const RSS2JSON = 'https://api.rss2json.com/v1/api.json?rss_url='; 7 | 8 | export const DELAY200 = 200; 9 | export const DELAY100 = 200; 10 | 11 | export const MAX_POSTS_COUNT = 99; 12 | -------------------------------------------------------------------------------- /docs/assets/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #00a300 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #00a300 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/app/backward-compatibility.ts: -------------------------------------------------------------------------------- 1 | export function importFeedsFromVersion3(): string { 2 | const feedListSource = localStorage.getItem('feedList'); 3 | localStorage.removeItem('feedList'); 4 | try { 5 | return JSON.parse(feedListSource).join('\n'); 6 | } catch (error) { 7 | return ''; 8 | } 9 | } -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | async navigateTo(): Promise { 5 | return browser.get(browser.baseUrl); 6 | } 7 | 8 | async getTitleText(): Promise { 9 | return element(by.css('app-root .content span')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../out-tsc/e2e", 6 | "module": "commonjs", 7 | "target": "es2018", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /docs/assets/share.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/share.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import {enableProdMode} from '@angular/core'; 2 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 3 | 4 | import {AppModule} from './app/app.module'; 5 | import {environment} from './environments/environment'; 6 | 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule) 13 | .catch(err => console.error(err)); 14 | -------------------------------------------------------------------------------- /src/app/pipes/sanitize-html.pipe.ts: -------------------------------------------------------------------------------- 1 | import {Pipe, PipeTransform} from '@angular/core'; 2 | import {DomSanitizer, SafeHtml} from '@angular/platform-browser'; 3 | 4 | 5 | @Pipe({ 6 | name: 'sanitizeHtml' 7 | }) 8 | export class SanitizeHtmlPipe implements PipeTransform { 9 | 10 | constructor(private domSanitizer: DomSanitizer) { 11 | } 12 | 13 | transform(v: string): SafeHtml { 14 | return this.domSanitizer.bypassSecurityTrustHtml(v); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/safety-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.io/license 7 | */ 8 | 9 | // tslint:disable:no-console 10 | 11 | self.addEventListener('install', event => { 12 | self.skipWaiting(); 13 | }); 14 | 15 | self.addEventListener('activate', event => { 16 | event.waitUntil(self.clients.claim()); 17 | self.registration.unregister().then(() => { 18 | console.log('NGSW Safety Worker - unregistered old service worker'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /docs/worker-basic.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.io/license 7 | */ 8 | 9 | // tslint:disable:no-console 10 | 11 | self.addEventListener('install', event => { 12 | self.skipWaiting(); 13 | }); 14 | 15 | self.addEventListener('activate', event => { 16 | event.waitUntil(self.clients.claim()); 17 | self.registration.unregister().then(() => { 18 | console.log('NGSW Safety Worker - unregistered old service worker'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {RouterModule, Routes} from '@angular/router'; 3 | import {FeedComponent} from './feed/feed.component'; 4 | import {HomeComponent} from './home/home.component'; 5 | 6 | 7 | const routes: Routes = [ 8 | { 9 | path: ':id', 10 | component: FeedComponent 11 | }, 12 | { 13 | path: '', 14 | component: HomeComponent 15 | } 16 | ]; 17 | 18 | @NgModule({ 19 | imports: [RouterModule.forRoot(routes, {useHash: true})], 20 | exports: [RouterModule] 21 | }) 22 | export class AppRoutingModule { 23 | } 24 | -------------------------------------------------------------------------------- /docs/assets/unexpand.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/unexpand.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "downlevelIteration": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "module": "es2020", 15 | "lib": [ 16 | "es2018", 17 | "dom" 18 | ] 19 | }, 20 | "angularCompilerOptions": { 21 | "enableI18nLegacyMessageIdFormat": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/assets/expand.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/expand.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 18 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { browser, logging } from 'protractor'; 2 | import { AppPage } from './app.po'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', async () => { 12 | await page.navigateTo(); 13 | expect(await page.getTitleText()).toEqual('stupid-rss-reader app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/pipes/count.pipe.ts: -------------------------------------------------------------------------------- 1 | import {Pipe, PipeTransform} from '@angular/core'; 2 | import {NgxIndexedDBService} from 'ngx-indexed-db'; 3 | import {map} from 'rxjs/operators'; 4 | import {TABLES} from '../constants'; 5 | import {Observable} from 'rxjs'; 6 | import {Post} from '../models'; 7 | 8 | 9 | @Pipe({ 10 | name: 'count' 11 | }) 12 | export class CountPipe implements PipeTransform { 13 | 14 | constructor(private dbService: NgxIndexedDBService) { 15 | } 16 | 17 | transform(feedId: number, isNew?: boolean): Observable { 18 | const filterPostFn = (p: Post) => isNew === undefined || p.isNew === isNew; 19 | return this.dbService.getAllByIndex(TABLES.POSTS, 'feedId', IDBKeyRange.only(feedId)) 20 | .pipe(map(posts => posts.filter(filterPostFn).length)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | 6 | /tmp 7 | /out-tsc 8 | # Only exists if Bazel was run 9 | /bazel-out 10 | 11 | # dependencies 12 | /node_modules 13 | 14 | # profiling files 15 | chrome-profiler-events*.json 16 | speed-measure-plugin*.json 17 | 18 | # IDEs and editors 19 | /.idea 20 | .project 21 | .classpath 22 | .c9/ 23 | *.launch 24 | .settings/ 25 | *.sublime-workspace 26 | 27 | # IDE - VSCode 28 | .vscode/* 29 | !.vscode/settings.json 30 | !.vscode/tasks.json 31 | !.vscode/launch.json 32 | !.vscode/extensions.json 33 | .history/* 34 | 35 | # misc 36 | /.sass-cache 37 | /connect.lock 38 | /coverage 39 | /libpeerconnection.log 40 | npm-debug.log 41 | yarn-error.log 42 | yarn.lock 43 | testem.log 44 | /typings 45 | 46 | # System Files 47 | .DS_Store 48 | Thumbs.db 49 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import {getTestBed} from '@angular/core/testing'; 5 | import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing'; 6 | 7 | 8 | declare const require: { 9 | context(path: string, deep?: boolean, filter?: RegExp): { 10 | keys(): string[]; 11 | (id: string): T; 12 | }; 13 | }; 14 | 15 | // First, initialize the Angular testing environment. 16 | getTestBed().initTestEnvironment( 17 | BrowserDynamicTestingModule, 18 | platformBrowserDynamicTesting() 19 | ); 20 | // Then we find all the tests. 21 | const context = require.context('./', true, /\.spec\.ts$/); 22 | // And load the modules. 23 | context.keys().map(context); 24 | -------------------------------------------------------------------------------- /src/app/post/post.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input} from '@angular/core'; 2 | import {Post} from '../models'; 3 | 4 | 5 | @Component({ 6 | selector: 'app-post', 7 | templateUrl: './post.component.html' 8 | }) 9 | export class PostComponent { 10 | 11 | @Input() post: Post = new Post(); 12 | 13 | 14 | hidden = true; 15 | 16 | constructor() { 17 | } 18 | 19 | get isImage(): boolean { 20 | const typeAsString = this.post?.enclosure?.type || ''; 21 | return typeAsString?.split('/')?.[0] === 'image'; 22 | } 23 | 24 | get shortInfo(): string { 25 | return [ 26 | this.post.author, 27 | this.post.pubDate, 28 | this.readTime 29 | ].filter(a => a).join(', '); 30 | } 31 | 32 | get readTime(): string { 33 | const minutes = Math.round(this.post.content.toString().length / 1500); 34 | 35 | return minutes < 1 ? '' : `⏱ ~${minutes}m`; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | SELENIUM_PROMISE_MANAGER: false, 20 | baseUrl: 'http://localhost:4200/', 21 | framework: 'jasmine', 22 | jasmineNodeOpts: { 23 | showColors: true, 24 | defaultTimeoutInterval: 30000, 25 | print: function() {} 26 | }, 27 | onPrepare() { 28 | require('ts-node').register({ 29 | project: require('path').join(__dirname, './tsconfig.json') 30 | }); 31 | jasmine.getEnv().addReporter(new SpecReporter({ 32 | spec: { 33 | displayStacktrace: StacktraceOption.PRETTY 34 | } 35 | })); 36 | } 37 | }; -------------------------------------------------------------------------------- /src/app/models.ts: -------------------------------------------------------------------------------- 1 | export interface Enclosure { 2 | link?: string; 3 | type?: string; 4 | thumbnail?: string; 5 | url?: string; 6 | length?: number; 7 | } 8 | 9 | export type FeedLoading = Record; 10 | 11 | export type FeedError = Record; 12 | 13 | 14 | export class FeedItem { 15 | url: string; 16 | id: number; 17 | newCount?: number; 18 | count?: number 19 | about?: SiteFeedAbout; 20 | } 21 | 22 | export class SiteFeedAbout { 23 | author: string; 24 | description: string; 25 | image: string; 26 | link: string; 27 | title: string; 28 | url: string; 29 | } 30 | 31 | export class SiteFeed { 32 | static: string; 33 | feed: SiteFeedAbout; 34 | items: Post[]; 35 | } 36 | 37 | export class Post { 38 | id?: number; 39 | title: string; 40 | pubDate?: string; 41 | link: string; 42 | guid: string; 43 | author: string; 44 | thumbnail: string; 45 | description: string; 46 | content: string; 47 | categories: string[] | any; 48 | enclosure: Enclosure; 49 | isNew?: boolean; 50 | } 51 | 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Pavel Gurov 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 | -------------------------------------------------------------------------------- /docs/assets/back.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/back.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stupid-rss-reader 2 | 3 | I'm tired of different stupid RSS clients and I created own Stupid RSS Reader. 4 | 5 | Try it [here](https://gurov.github.io/stupid-rss-reader/) 6 | 7 | 8 | ## Proxy for RSS 9 | 10 | The application must use the proxy [rss2json.com](https://rss2json.com/) because CORS restrictions on cross-site requests. 11 | 12 | ## Features 13 | * Stupid RSS is [Progressive Web App](https://developers.google.com/web/progressive-web-apps/) 14 | * You can install it to your smartphone 15 | * Very simple interface 16 | * Offline mode 17 | * All user data are stored locally only ([IndexedDB](https://developer.mozilla.org/ru/docs/Web/API/IndexedDB_API)) 18 | * Open source: [GitHub](https://github.com/gurov/stupid-rss-reader) 19 | * Proxy for RSS: [rss2json.com](https://rss2json.com/) 20 | 21 | ## Philosophy 22 | The application does not impose anything, but only executes orders. 23 | 24 | 25 | ## Interface (click to watch the video) 26 | 27 | [![Watch the video](https://user-images.githubusercontent.com/2802420/111161788-cbc45980-859b-11eb-99f7-280724bf0590.png)](https://user-images.githubusercontent.com/2802420/111161791-cc5cf000-859b-11eb-91d4-b15bbaf68d7e.mp4) 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /ngsw-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/service-worker/config/schema.json", 3 | "index": "/index.html", 4 | "assetGroups": [ 5 | { 6 | "name": "app", 7 | "installMode": "prefetch", 8 | "resources": { 9 | "files": [ 10 | "/favicon.ico", 11 | "/index.html", 12 | "/manifest.json", 13 | "/*.css", 14 | "/*.js" 15 | ] 16 | } 17 | }, 18 | { 19 | "name": "assets", 20 | "installMode": "lazy", 21 | "updateMode": "prefetch", 22 | "resources": { 23 | "files": [ 24 | "/assets/**", 25 | "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)" 26 | ] 27 | } 28 | } 29 | ], 30 | "dataGroups": [ 31 | { 32 | "name": "feedRequests", 33 | "urls": [ 34 | "https://api.rss2json.com/v1/api.json" 35 | ], 36 | "cacheConfig": { 37 | "maxSize": 100, 38 | "maxAge": "15m" 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | import {RouterTestingModule} from '@angular/router/testing'; 3 | import {AppComponent} from './app.component'; 4 | 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(async () => { 8 | await TestBed.configureTestingModule({ 9 | imports: [ 10 | RouterTestingModule 11 | ], 12 | declarations: [ 13 | AppComponent 14 | ], 15 | }).compileComponents(); 16 | }); 17 | 18 | it('should create the app', () => { 19 | const fixture = TestBed.createComponent(AppComponent); 20 | const app = fixture.componentInstance; 21 | expect(app).toBeTruthy(); 22 | }); 23 | 24 | it(`should have as title 'stupid-rss-reader'`, () => { 25 | const fixture = TestBed.createComponent(AppComponent); 26 | const app = fixture.componentInstance; 27 | expect(app.title).toEqual('stupid-rss-reader'); 28 | }); 29 | 30 | it('should render title', () => { 31 | const fixture = TestBed.createComponent(AppComponent); 32 | fixture.detectChanges(); 33 | const compiled = fixture.nativeElement; 34 | expect(compiled.querySelector('.content span').textContent).toContain('stupid-rss-reader app is running!'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /docs/runtime.7b63b9fd40098a2e8207.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,l,f=r[0],i=r[1],p=r[2],c=0,s=[];c 2 | 3 | 4 | 5 | Stupid RSS Reader 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 51 | 52 | 53 | 54 |
Stupid RSS Reader is loading ...
55 |
56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Stupid RSS Reader 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 51 | 52 | 53 | 54 |
Stupid RSS Reader is loading ...
55 |
56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/app/post/post.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | thumbnail 6 |
7 | 8 | {{shortInfo}} 9 | 10 |

11 |

12 | 13 | 14 |

15 |
16 | 17 | 21 | 25 |
26 | 27 | 28 |
29 |
30 |
31 |
32 |
33 | {{cat}}, 34 |
35 | 36 |
37 | 38 | thumbnail 39 | 40 |
Type: {{post.enclosure?.type}}
41 |
42 | 43 |
44 | thumbnail 45 |
Type: {{post.enclosure?.type}}
46 |
47 | 48 |
49 | Enclosure↗️ 50 |
Type: {{post.enclosure?.type}}
51 |
Length: {{post.enclosure?.length}}
52 |
53 |
54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/app/feed/feed.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 8 | 9 | Back to feeds Back 10 | 11 | 14 |
15 | 16 |
17 | 18 | 21 |
22 | 23 |
24 |
25 |
26 | 27 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 39 | 57 | 58 | 59 |
35 | image 38 | 40 |
41 | Title: 42 | {{about.title}} 43 |
44 |
45 | Author: 46 | {{about.author}} 47 |
48 |
49 | Description: 50 | {{about.description}} 51 |
52 |
53 | Link: 54 | {{about.link}} 55 |
56 |
60 | 61 | 62 | 63 |
64 | 65 |
66 | 67 |
68 | 71 |
72 |
73 | 74 |
75 | 76 |
77 | 78 |
79 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * IE11 requires the following for NgClass support on SVG elements 23 | */ 24 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 25 | 26 | /** 27 | * Web Animations `@angular/platform-browser/animations` 28 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 29 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 30 | */ 31 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 32 | 33 | /** 34 | * By default, zone.js will patch all possible macroTask and DomEvents 35 | * user can disable parts of macroTask/DomEvents patch by setting following flags 36 | * because those flags need to be set before `zone.js` being loaded, and webpack 37 | * will put import in the top of bundle, so user need to create a separate file 38 | * in this directory (for example: zone-flags.ts), and put the following flags 39 | * into that file, and then add the following code before importing zone.js. 40 | * import './zone-flags'; 41 | * 42 | * The flags allowed in zone-flags.ts are listed here. 43 | * 44 | * The following flags will work for all browsers. 45 | * 46 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 47 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 48 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 49 | * 50 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 51 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 52 | * 53 | * (window as any).__Zone_enable_cross_context_check = true; 54 | * 55 | */ 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by default for Angular itself. 59 | */ 60 | import 'zone.js/dist/zone'; // Included with Angular CLI. 61 | 62 | 63 | /*************************************************************************************************** 64 | * APPLICATION IMPORTS 65 | */ 66 | -------------------------------------------------------------------------------- /docs/assets/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/assets/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/app/core.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject, combineLatest, concat, Observable, of, Subject } from 'rxjs'; 3 | import { HttpClient } from '@angular/common/http'; 4 | import { catchError, delay, map, switchMap, tap, toArray } from 'rxjs/operators'; 5 | import { FeedError, FeedItem, FeedLoading, Post, SiteFeed, SiteFeedAbout } from './models'; 6 | import { RSS2JSON, TABLES } from './constants'; 7 | import { NgxIndexedDBService } from 'ngx-indexed-db'; 8 | 9 | 10 | @Injectable({ 11 | providedIn: 'root' 12 | }) 13 | export class CoreService { 14 | 15 | feedLoading$ = new BehaviorSubject({}); 16 | feedError$ = new BehaviorSubject({}); 17 | 18 | constructor(private http: HttpClient, private dbService: NgxIndexedDBService) { 19 | } 20 | 21 | setFeedLoading(id: number, isLoadind: boolean) { 22 | const feedLoading = this.feedLoading$.getValue(); 23 | this.feedLoading$.next({...feedLoading, [id]: isLoadind}); 24 | } 25 | 26 | setFeedError(id: number, isLoadind: string) { 27 | const feedError = this.feedError$.getValue(); 28 | this.feedError$.next({...feedError, [id]: isLoadind}); 29 | } 30 | 31 | updateFeed(url: string, id: number, about: SiteFeedAbout): void { 32 | this.dbService.update(TABLES.FEEDS, { url, id, about }); 33 | } 34 | 35 | addPosts(posts: Post[], feedId: number, url: string, about: SiteFeedAbout): void { 36 | const add$ = (post: Post): Observable => this.dbService 37 | .add(TABLES.POSTS, { ...post, feedId, isNew: true }) 38 | .pipe(catchError(() => of(-1))); 39 | 40 | combineLatest(posts.map(add$)) 41 | .pipe( 42 | switchMap(() => this.dbService.getAllByIndex(TABLES.POSTS, 'feedId', IDBKeyRange.only(feedId))), 43 | map(posts => [posts.length, posts.filter(p => p.isNew).length]), 44 | map(([count, newCount]) => ({ url, id: feedId, about, count, newCount } as FeedItem)) 45 | ) 46 | .subscribe(feedItem => this.dbService.update(TABLES.FEEDS, feedItem)); 47 | } 48 | 49 | getNewPostsAndUpdateStore(url: string, id: number): Observable { 50 | 51 | this.setFeedLoading(id, true); 52 | 53 | return this.http.get(encodeURI(`${RSS2JSON}${url}`)) 54 | .pipe( 55 | tap(siteFeed => { 56 | this.addPosts(siteFeed.items.reverse(), id, url, siteFeed.feed); 57 | this.setFeedLoading(id, false); 58 | }), 59 | catchError(error => { 60 | this.setFeedLoading(id, false); 61 | this.setFeedError(id, error.message || 'Error'); 62 | return of(null); 63 | }) 64 | ); 65 | } 66 | 67 | refreshFeeds(feedItems: FeedItem[]): Observable { 68 | const requests$ = feedItems 69 | .map(feedItem => this.getNewPostsAndUpdateStore(feedItem.url, feedItem.id)); 70 | 71 | return combineLatest(requests$); 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /docs/assets/fur-boot-green.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/fur-boot-green.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/feed/feed.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {debounceTime, map, switchMap, tap, toArray} from 'rxjs/operators'; 3 | import {NgxIndexedDBService} from 'ngx-indexed-db'; 4 | import {ActivatedRoute, Router} from '@angular/router'; 5 | import {FeedItem, Post, SiteFeedAbout} from '../models'; 6 | import {concat} from 'rxjs'; 7 | import {MAX_POSTS_COUNT, TABLES} from '../constants'; 8 | 9 | 10 | @Component({ 11 | selector: 'app-feed', 12 | templateUrl: './feed.component.html' 13 | }) 14 | export class FeedComponent implements OnInit { 15 | 16 | posts: Post[] = []; 17 | displayActions = false; 18 | viewCount: number = 10; 19 | about: SiteFeedAbout = new SiteFeedAbout(); 20 | private feedId: number; 21 | private feed: FeedItem = null; 22 | 23 | identify = (index: number, post: Post) => post.id; 24 | 25 | constructor(private dbService: NgxIndexedDBService, 26 | private router: Router, 27 | private route: ActivatedRoute) { 28 | } 29 | 30 | get viewPosts(): Post[] { 31 | return this.posts.slice(0, this.viewCount); 32 | } 33 | 34 | showMore():void { 35 | this.viewCount += 10; 36 | } 37 | 38 | 39 | markAsRead() { 40 | this.markAsReadFeed(this.feed); 41 | this.markAsReadPosts(); 42 | } 43 | 44 | markAsReadFeed(feed: FeedItem): void { 45 | this.dbService.update(TABLES.FEEDS, {...feed, newCount: 0}).subscribe(); 46 | } 47 | 48 | markAsReadPosts(): void { 49 | const newPosts$ = this.posts.filter(p => p.isNew) 50 | .map(post => this.dbService.update(TABLES.POSTS, {...post, isNew: false})); 51 | 52 | concat(...newPosts$).pipe(toArray()).subscribe(); 53 | 54 | this.posts.forEach(p => { 55 | p.isNew = false; 56 | }); 57 | } 58 | 59 | deleteTail(): void { 60 | const postForDelete$ = this.posts.slice(MAX_POSTS_COUNT) 61 | .map(post => this.dbService.delete(TABLES.POSTS, post.id)); 62 | 63 | concat(...postForDelete$).pipe(toArray()).subscribe(); 64 | 65 | } 66 | 67 | ngOnInit(): void { 68 | this.route.params 69 | .pipe( 70 | map(params => params.id), 71 | tap(id => this.feedId = +id), 72 | switchMap(id => this.dbService.getByID(TABLES.FEEDS, +id)), 73 | tap((feed: FeedItem) => { 74 | this.feed = feed; 75 | this.about = feed.about; 76 | }), 77 | switchMap(feed => this.dbService.getAllByIndex(TABLES.POSTS, 'feedId', IDBKeyRange.only(this.feedId))), 78 | tap((posts) => this.posts = posts?.reverse() || []) 79 | ) 80 | .subscribe(); 81 | } 82 | 83 | deleteFeed(): void { 84 | const result = confirm('Remove the feed?'); 85 | if (result === true) { 86 | 87 | const forDelete$ = [ 88 | this.dbService.delete(TABLES.FEEDS, this.feedId), 89 | ...this.posts.map(post => this.dbService.delete(TABLES.POSTS, post.id)) 90 | ]; 91 | concat(...forDelete$).pipe(toArray()) 92 | .subscribe(() => this.router.navigate(['/'])); 93 | } 94 | } 95 | 96 | removeAllPosts(): void { 97 | const result = confirm('Remove all the posts?'); 98 | if (result === true) { 99 | const postForDelete$ = this.posts 100 | .map(post => this.dbService.delete(TABLES.POSTS, post.id)); 101 | concat(...postForDelete$).pipe(toArray()) 102 | .subscribe(() => this.ngOnInit()); 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { DBConfig, NgxIndexedDBModule } from 'ngx-indexed-db'; 4 | 5 | import { AppRoutingModule } from './app-routing.module'; 6 | import { AppComponent } from './app.component'; 7 | import { FeedComponent } from './feed/feed.component'; 8 | import { HomeComponent } from './home/home.component'; 9 | import { HttpClientModule } from '@angular/common/http'; 10 | import { RouterModule } from '@angular/router'; 11 | import { SanitizeHtmlPipe } from './pipes/sanitize-html.pipe'; 12 | import { FormsModule } from '@angular/forms'; 13 | import { PostComponent } from './post/post.component'; 14 | import { CountPipe } from './pipes/count.pipe'; 15 | import { ServiceWorkerModule } from '@angular/service-worker'; 16 | import { environment } from '../environments/environment'; 17 | import { TABLES } from './constants'; 18 | 19 | export function migrationFactory() { 20 | return { 21 | 2: (db, transaction) => { 22 | const store = transaction.objectStore(TABLES.FEEDS); 23 | store.createIndex('newPostIds', 'newPostIds', { unique: false }); 24 | store.createIndex('postIds', 'postIds', { unique: false }); 25 | } 26 | }; 27 | }; 28 | 29 | const dbConfig: DBConfig = { 30 | name: 'Stupid-RSS-Reader', 31 | version: 2, 32 | objectStoresMeta: [ 33 | { 34 | store: TABLES.POSTS, 35 | storeConfig: { keyPath: 'id', autoIncrement: true }, 36 | storeSchema: [ 37 | { name: 'title', keypath: 'title', options: { unique: false } }, 38 | { name: 'feedId', keypath: 'feedId', options: { unique: false } }, 39 | { name: 'pubDate', keypath: 'pubDate', options: { unique: false } }, 40 | { name: 'link', keypath: 'link', options: { unique: false } }, 41 | { name: 'guid', keypath: 'guid', options: { unique: true } }, 42 | { name: 'author', keypath: 'author', options: { unique: false } }, 43 | { name: 'thumbnail', keypath: 'thumbnail', options: { unique: false } }, 44 | { name: 'description', keypath: 'description', options: { unique: false } }, 45 | { name: 'content', keypath: 'content', options: { unique: false } }, 46 | { name: 'categories', keypath: 'categories', options: { unique: false } }, 47 | { name: 'enclosure', keypath: 'enclosure', options: { unique: false } }, 48 | { name: 'isNew', keypath: 'isNew', options: { unique: false } } 49 | ] 50 | }, 51 | { 52 | store: TABLES.FEEDS, 53 | storeConfig: { keyPath: 'id', autoIncrement: true }, 54 | storeSchema: [ 55 | { name: 'url', keypath: 'url', options: { unique: true } }, 56 | { name: 'about', keypath: 'about', options: { unique: false } }, 57 | { name: 'newCount', keypath: 'newCount', options: { unique: false } }, 58 | { name: 'count', keypath: 'count', options: { unique: false } }, 59 | ] 60 | } 61 | ], 62 | migrationFactory 63 | }; 64 | 65 | @NgModule({ 66 | declarations: [ 67 | AppComponent, 68 | HomeComponent, 69 | FeedComponent, 70 | PostComponent, 71 | CountPipe, 72 | SanitizeHtmlPipe 73 | ], 74 | imports: [ 75 | BrowserModule, 76 | FormsModule, 77 | HttpClientModule, 78 | RouterModule, 79 | AppRoutingModule, 80 | NgxIndexedDBModule.forRoot(dbConfig), 81 | ServiceWorkerModule.register('/stupid-rss-reader/ngsw-worker.js', { enabled: environment.production }) 82 | ], 83 | providers: [], 84 | bootstrap: [AppComponent] 85 | }) 86 | export class AppModule { 87 | } 88 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "codelyzer" 5 | ], 6 | "rules": { 7 | "align": { 8 | "options": [ 9 | "parameters", 10 | "statements" 11 | ] 12 | }, 13 | "array-type": false, 14 | "arrow-return-shorthand": true, 15 | "curly": true, 16 | "deprecation": { 17 | "severity": "warning" 18 | }, 19 | "eofline": true, 20 | "import-blacklist": [ 21 | true, 22 | "rxjs/Rx" 23 | ], 24 | "import-spacing": true, 25 | "indent": { 26 | "options": [ 27 | "spaces" 28 | ] 29 | }, 30 | "max-classes-per-file": false, 31 | "max-line-length": [ 32 | true, 33 | 140 34 | ], 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-console": [ 47 | true, 48 | "debug", 49 | "info", 50 | "time", 51 | "timeEnd", 52 | "trace" 53 | ], 54 | "no-empty": false, 55 | "no-inferrable-types": false, 56 | "no-non-null-assertion": true, 57 | "no-redundant-jsdoc": true, 58 | "no-switch-case-fall-through": true, 59 | "no-var-requires": false, 60 | "object-literal-key-quotes": [ 61 | true, 62 | "as-needed" 63 | ], 64 | "quotemark": [ 65 | true, 66 | "single" 67 | ], 68 | "semicolon": { 69 | "options": [ 70 | "always" 71 | ] 72 | }, 73 | "space-before-function-paren": { 74 | "options": { 75 | "anonymous": "never", 76 | "asyncArrow": "always", 77 | "constructor": "never", 78 | "method": "never", 79 | "named": "never" 80 | } 81 | }, 82 | "typedef": [ 83 | true, 84 | "call-signature" 85 | ], 86 | "typedef-whitespace": { 87 | "options": [ 88 | { 89 | "call-signature": "nospace", 90 | "index-signature": "nospace", 91 | "parameter": "nospace", 92 | "property-declaration": "nospace", 93 | "variable-declaration": "nospace" 94 | }, 95 | { 96 | "call-signature": "onespace", 97 | "index-signature": "onespace", 98 | "parameter": "onespace", 99 | "property-declaration": "onespace", 100 | "variable-declaration": "onespace" 101 | } 102 | ] 103 | }, 104 | "variable-name": { 105 | "options": [ 106 | "ban-keywords", 107 | "check-format", 108 | "allow-pascal-case" 109 | ] 110 | }, 111 | "whitespace": { 112 | "options": [ 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type", 118 | "check-typecast" 119 | ] 120 | }, 121 | "component-class-suffix": true, 122 | "contextual-lifecycle": true, 123 | "directive-class-suffix": true, 124 | "no-conflicting-lifecycle": true, 125 | "no-host-metadata-property": true, 126 | "no-input-rename": true, 127 | "no-inputs-metadata-property": true, 128 | "no-output-native": true, 129 | "no-output-on-prefix": true, 130 | "no-output-rename": true, 131 | "no-outputs-metadata-property": true, 132 | "template-banana-in-box": true, 133 | "template-no-negated-async": true, 134 | "use-lifecycle-interface": true, 135 | "use-pipe-transform-interface": true, 136 | "directive-selector": [ 137 | true, 138 | "attribute", 139 | "app", 140 | "camelCase" 141 | ], 142 | "component-selector": [ 143 | true, 144 | "element", 145 | "app", 146 | "kebab-case" 147 | ] 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnDestroy, OnInit} from '@angular/core'; 2 | import {NgxIndexedDBService} from 'ngx-indexed-db'; 3 | import {combineLatest, Subject} from 'rxjs'; 4 | import {isValidHttpUrl} from '../helpers'; 5 | import {FeedError, FeedItem, FeedLoading} from '../models'; 6 | import {DELAY100, TABLES} from '../constants'; 7 | import {CoreService} from '../core.service'; 8 | import {debounceTime, switchMap, takeUntil} from 'rxjs/operators'; 9 | import {importFeedsFromVersion3} from '../backward-compatibility'; 10 | import { cloneDeep } from 'lodash-es'; 11 | 12 | 13 | @Component({ 14 | selector: 'app-home', 15 | templateUrl: './home.component.html', 16 | styles: [] 17 | }) 18 | export class HomeComponent implements OnInit, OnDestroy { 19 | 20 | feeds: FeedItem[] = []; 21 | addFeedMode: boolean = false; 22 | godMode: boolean = false; 23 | rawFeedURLs: string = ''; 24 | feedLoading: FeedLoading = {}; 25 | feedError: FeedError = {}; 26 | loading: boolean = false; 27 | private ngUnsubscribe$ = new Subject(); 28 | private load$ = new Subject(); 29 | 30 | identify = (index: number, feed: FeedItem) => feed.id; 31 | 32 | constructor(private dbService: NgxIndexedDBService, 33 | private coreService: CoreService) { 34 | } 35 | 36 | get shareIsSuported(): boolean { 37 | return !!navigator.share; 38 | } 39 | 40 | addFeeds(rawFeedStrings: string): void { 41 | this.addFeedMode = false; 42 | this.rawFeedURLs = ''; 43 | const newFeeds$ = rawFeedStrings 44 | .split('\n') 45 | .map(s => s.trim()) 46 | .filter(s => s) 47 | .filter(s => isValidHttpUrl(s)) 48 | .map(url => this.dbService.add(TABLES.FEEDS, {url})); 49 | 50 | combineLatest(newFeeds$) 51 | .subscribe(() => this.load$.next()); 52 | } 53 | 54 | refreshFeeds(): void { 55 | this.loading = true; 56 | this.feedError = {}; 57 | this.coreService.refreshFeeds(this.feeds) 58 | .subscribe(() => { 59 | this.loading = false; 60 | this.load$.next(); 61 | }); 62 | } 63 | 64 | share(): void { 65 | navigator.share?.({ 66 | title: 'Stupid RSS', 67 | text: this.feeds.map(f => f.url).join(' \n'), 68 | url: location.href, 69 | }); // share the URL of MDN 70 | } 71 | 72 | ngOnInit(): void { 73 | 74 | this.coreService.feedLoading$ 75 | .pipe(takeUntil(this.ngUnsubscribe$)) 76 | .subscribe(feedLoading => { 77 | this.feedLoading = feedLoading; 78 | this.load$.next(); 79 | }); 80 | 81 | this.coreService.feedError$ 82 | .pipe(takeUntil(this.ngUnsubscribe$)) 83 | .subscribe(feedError => { 84 | this.feedError = feedError; 85 | this.load$.next(); 86 | }); 87 | 88 | 89 | this.load$ 90 | .pipe( 91 | takeUntil(this.ngUnsubscribe$), 92 | debounceTime(DELAY100), 93 | switchMap(() => this.dbService.getAll(TABLES.FEEDS)) 94 | ) 95 | .subscribe(feeds => { 96 | this.feeds = cloneDeep(feeds); 97 | 98 | }); 99 | 100 | 101 | this.load$.next(); 102 | 103 | // import from the old version 104 | try { 105 | this.addFeeds(importFeedsFromVersion3()); 106 | } catch (error) { 107 | } 108 | 109 | 110 | } 111 | 112 | unregister(): void { 113 | const result = confirm('Unregister the Service Worker?'); 114 | if (result === true) { 115 | navigator.serviceWorker?.getRegistrations().then((registrations) => { 116 | for (const registration of registrations) { 117 | registration.unregister(); 118 | } 119 | }); 120 | } 121 | } 122 | 123 | ngOnDestroy(): void { 124 | this.ngUnsubscribe$.next(); 125 | this.ngUnsubscribe$.complete(); 126 | } 127 | 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 11 | 14 | 15 | 16 |
7 | 8 | Stupid RSS Reader logo 9 | 10 | 12 |

Stupid RSS Reader

13 |
17 |
18 | 19 |
20 |
21 |

Feeds

22 | 25 | 30 |
31 | 32 |
35 |
36 | {{feed.about?.title || 'noname'}}
37 | {{feed.url}} 38 |
39 | 💢 {{feedError[feed.id]}} 40 |
41 |
42 |
43 | 58 |
59 |
60 | 61 |
62 | 63 |
64 |
65 |
66 |
67 | 68 | 73 | 74 | 77 |
78 |
79 |
80 |
81 | 82 | 83 | 84 |
85 |

About

86 |

I'm tired of different stupid RSS clients and I created own Stupid RSS Reader.

87 |

Proxy for RSS

88 |

89 | The application must use the proxy rss2json.com because CORS restrictions on cross-site requests. 90 |

91 | 92 |

Features

93 |
    94 |
  • Stupid RSS is Progressive 95 | Web App 96 |
  • 97 |
  • You can install it to your smartphone
  • 98 |
  • Very simple interface
  • 99 |
  • Offline mode
  • 100 |
  • All user data are stored locally only (IndexedDB)
  • 101 |
  • Open source: Github
  • 102 |
103 | 104 |
105 | 106 | 107 |
108 |

Philosophy

109 |

The application does not impose anything, but only executes orders.

110 |
111 | 112 |
113 | 114 | 117 |
118 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | .w-100 { 4 | width: 100%; 5 | } 6 | .p-1 { 7 | padding: 0.5rem !important; 8 | } 9 | .pb-2 { 10 | padding-bottom: 1rem !important; 11 | } 12 | .mb-2 { 13 | margin-bottom: 1rem !important; 14 | } 15 | .mt-1 { 16 | margin-top: 0.5rem; 17 | } 18 | .ml-1 { 19 | margin-left: 0.5rem; 20 | } 21 | .m-1 { 22 | margin: 0.5rem; 23 | } 24 | .mr-1 { 25 | margin-right: 0.5rem; 26 | } 27 | .ml-auto { 28 | margin-left: auto; 29 | } 30 | .mr-auto { 31 | margin-right: auto; 32 | } 33 | 34 | .lh-20 { 35 | line-height: 1.2rem !important; 36 | } 37 | 38 | .container { 39 | max-width: 960px; 40 | margin: 0 auto; 41 | } 42 | .post-wrapper { 43 | background-color: rgba(255,255,255,0.5); 44 | margin: 0.5rem 0 1rem; 45 | padding: 0.2rem 0.5rem; 46 | border-radius: 0.2rem; 47 | box-shadow: 0 1px 2px rgba(0,0,0,0.1); 48 | border-bottom: 1px solid white; 49 | 50 | .post-header h3 { 51 | margin-top: 0; 52 | margin-bottom: 0; 53 | color: #333;; 54 | } 55 | 56 | .post-header-thumbnail { 57 | max-height: 58px; 58 | max-width: 73px; 59 | margin-right: 0.2rem; 60 | } 61 | 62 | .post-header-text { 63 | margin: 0.2rem 0.3rem 0.3rem 0.3rem; 64 | } 65 | 66 | img { 67 | max-width: 100%; 68 | } 69 | } 70 | .post-box { 71 | overflow-wrap: break-word; 72 | img { 73 | max-width: 100%; 74 | } 75 | svg.spacer { 76 | display: none; 77 | } 78 | } 79 | .feed-image { 80 | width: 80px; 81 | min-width: 80px; 82 | padding: 4px; 83 | vertical-align: top; 84 | img { 85 | max-width: 100%; 86 | } 87 | } 88 | .feed-about { 89 | padding: 4px; 90 | overflow-wrap: break-word; 91 | word-break: break-word; 92 | } 93 | 94 | .post-header, 95 | .feed-line, 96 | .top-top-feed, 97 | .feeds-header { 98 | display: flex; 99 | justify-content:flex-start; 100 | align-items: center; 101 | } 102 | 103 | .fit-text { 104 | overflow-wrap: break-word; 105 | word-break: break-word; 106 | } 107 | 108 | .feed-line { 109 | border-bottom: 1px solid #c6e081; 110 | padding: 0.5rem 0; 111 | } 112 | 113 | .feed-line.loading { 114 | text-shadow: 0px 1px 3px rgba(19, 161, 0, 0.3);; 115 | } 116 | 117 | .post-button { 118 | min-width: 40px; 119 | img { 120 | vertical-align: text-top; 121 | width: 16px; 122 | height: 16px; 123 | } 124 | } 125 | 126 | h1.app-name { 127 | margin: 0; 128 | font-size: 1.8rem; 129 | } 130 | 131 | h1, h2, h3, h4, h5, h6 { 132 | color: #444; 133 | } 134 | 135 | .new-info { 136 | color: #16a006; 137 | font-weight: bold; 138 | } 139 | 140 | .short-info { 141 | color: grey; 142 | } 143 | 144 | .loader { 145 | border-radius: 8px; 146 | } 147 | 148 | hr { 149 | border-top: 0; 150 | } 151 | 152 | /** 153 | * Grey 154 | */ 155 | .a-button { 156 | -moz-box-shadow: inset 0 0 0 1px #63ad0d; 157 | -webkit-box-shadow: inset 0 0 0 1px #63ad0d; 158 | -moz-border-radius: 3px; 159 | -webkit-border-radius: 3px; 160 | background: #eee; 161 | background: linear-gradient(#eee, #e2e2e2); 162 | border: solid 1px #d0d0d0; 163 | border-bottom: solid 1px #b2b1b1; 164 | border-radius: 3px; 165 | box-shadow: inset 0 0 0 1px #f5f5f5; 166 | color: #555; 167 | display: inline-block; 168 | line-height: 2rem; 169 | text-align: center; 170 | text-decoration: none; 171 | text-shadow: 0 1px 0 #fafafa; } 172 | 173 | .a-button:hover { 174 | background: #e4e4e4; 175 | background: linear-gradient(#e4e4e4, #ededed); 176 | border: solid 1px #c2c2c2; 177 | border-bottom: solid 1px #b2b1b1; 178 | box-shadow: inset 0 0 0 1px #efefef; } 179 | 180 | .a-button:active { 181 | background: #dfdfdf; 182 | background: linear-gradient(#dfdfdf, #e3e3e3); 183 | border: solid 1px #959595; 184 | box-shadow: inset 0 10px 15px 0 #c4c4c4; 185 | top:2px;} 186 | 187 | 188 | .a-button.green { 189 | background: #cae285; 190 | background: linear-gradient(#cae285, #a3cd5a); 191 | border: solid 1px #aad063; 192 | border-bottom: solid 1px #799545; 193 | box-shadow: inset 0 0 0 1px #e0eeb6; 194 | color: #444; 195 | text-shadow: 0 1px 0 #d0e5a4; } 196 | 197 | .a-button.green:hover { 198 | background: #abd164; 199 | background: linear-gradient(#abd164, #b9d972); 200 | border: solid 1px #98b85b; 201 | border-bottom: solid 1px #799545; 202 | box-shadow: inset 0 0 0 1px #cce3a1; } 203 | 204 | .a-button.green:active { 205 | background: #a4cb5d; 206 | background: linear-gradient(#cae285, #a3cd5a); 207 | border: solid 1px #6e883f; 208 | box-shadow: inset 0 10px 15px 0 #90b352; } 209 | 210 | .a-button.red { 211 | color: #82280C; 212 | } 213 | 214 | .button-share { 215 | img { 216 | vertical-align: text-top; 217 | width: 16px; 218 | height: 16px; 219 | } 220 | } 221 | 222 | .button-counter { 223 | min-width: 60px; 224 | } 225 | 226 | .active-actions { 227 | background-color: rgba(255, 255, 255, 0.7); 228 | } 229 | 230 | a { 231 | color: #005224; 232 | } 233 | 234 | .bottom-box { 235 | height: 200px; 236 | display: flex; 237 | justify-content: center; 238 | align-items: center; 239 | 240 | img { 241 | opacity: 0.2; 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /docs/ngsw.json: -------------------------------------------------------------------------------- 1 | { 2 | "configVersion": 1, 3 | "timestamp": 1629057504321, 4 | "index": "/stupid-rss-reader/index.html", 5 | "assetGroups": [ 6 | { 7 | "name": "app", 8 | "installMode": "prefetch", 9 | "updateMode": "prefetch", 10 | "cacheQueryOptions": { 11 | "ignoreVary": true 12 | }, 13 | "urls": [ 14 | "/stupid-rss-reader/favicon.ico", 15 | "/stupid-rss-reader/index.html", 16 | "/stupid-rss-reader/main.3abe0f1288fe1afd2bad.js", 17 | "/stupid-rss-reader/manifest.json", 18 | "/stupid-rss-reader/polyfills.00096ed7d93ed26ee6df.js", 19 | "/stupid-rss-reader/runtime.7b63b9fd40098a2e8207.js", 20 | "/stupid-rss-reader/styles.8d877fb47cb501c4832a.css" 21 | ], 22 | "patterns": [] 23 | }, 24 | { 25 | "name": "assets", 26 | "installMode": "lazy", 27 | "updateMode": "prefetch", 28 | "cacheQueryOptions": { 29 | "ignoreVary": true 30 | }, 31 | "urls": [ 32 | "/stupid-rss-reader/assets/android-chrome-192x192.png", 33 | "/stupid-rss-reader/assets/android-chrome-512x512.png", 34 | "/stupid-rss-reader/assets/apple-touch-icon.png", 35 | "/stupid-rss-reader/assets/back.svg", 36 | "/stupid-rss-reader/assets/browserconfig.xml", 37 | "/stupid-rss-reader/assets/expand.svg", 38 | "/stupid-rss-reader/assets/favicon-16x16.png", 39 | "/stupid-rss-reader/assets/favicon-32x32.png", 40 | "/stupid-rss-reader/assets/favicon.ico", 41 | "/stupid-rss-reader/assets/fur-boot-green.svg", 42 | "/stupid-rss-reader/assets/icon-128x128.png", 43 | "/stupid-rss-reader/assets/icon-144x144.png", 44 | "/stupid-rss-reader/assets/icon-152x152.png", 45 | "/stupid-rss-reader/assets/icon-72x72.png", 46 | "/stupid-rss-reader/assets/icon-96x96.png", 47 | "/stupid-rss-reader/assets/mstile-150x150.png", 48 | "/stupid-rss-reader/assets/safari-pinned-tab.svg", 49 | "/stupid-rss-reader/assets/share.svg", 50 | "/stupid-rss-reader/assets/skulls.png", 51 | "/stupid-rss-reader/assets/unexpand.svg" 52 | ], 53 | "patterns": [] 54 | } 55 | ], 56 | "dataGroups": [ 57 | { 58 | "name": "feedRequests", 59 | "patterns": [ 60 | "https:\\/\\/api\\.rss2json\\.com\\/v1\\/api\\.json" 61 | ], 62 | "strategy": "performance", 63 | "maxSize": 100, 64 | "maxAge": 900000, 65 | "cacheQueryOptions": { 66 | "ignoreVary": true 67 | }, 68 | "version": 1 69 | } 70 | ], 71 | "hashTable": { 72 | "/stupid-rss-reader/assets/android-chrome-192x192.png": "ad0fdd1996a3cc34a625499b8fea65f455601a18", 73 | "/stupid-rss-reader/assets/android-chrome-512x512.png": "cc303a2c704964cae1ae4bc28d51913649140b9b", 74 | "/stupid-rss-reader/assets/apple-touch-icon.png": "1afda609406d56f0330c36164f476a12b2e753ae", 75 | "/stupid-rss-reader/assets/back.svg": "7b69cd2a19c6d47272301e5716822fdbb2179b71", 76 | "/stupid-rss-reader/assets/browserconfig.xml": "46fb79e8a556afb1f44ae0e49e95d3df9c2cd3b1", 77 | "/stupid-rss-reader/assets/expand.svg": "f207a0821570ebe8bee71550106e62fa23136cda", 78 | "/stupid-rss-reader/assets/favicon-16x16.png": "6eb39b631ebfe75c3a4d89a395802a0ff62b1639", 79 | "/stupid-rss-reader/assets/favicon-32x32.png": "958bb429907c752fca5edcbf77a90319a2e605fc", 80 | "/stupid-rss-reader/assets/favicon.ico": "51f83552d6bd2f73fb09d202efbb4a903443ca29", 81 | "/stupid-rss-reader/assets/fur-boot-green.svg": "e58e1262d6cd0c1679fe5efc23e26b4908a7437a", 82 | "/stupid-rss-reader/assets/icon-128x128.png": "1de12956e9aba7b57167ab85dfa67a747ee969c5", 83 | "/stupid-rss-reader/assets/icon-144x144.png": "f02157c30d886482777c17e8b05dfd916311f930", 84 | "/stupid-rss-reader/assets/icon-152x152.png": "271fb26295193e00d75fee8315bba62e024278e9", 85 | "/stupid-rss-reader/assets/icon-72x72.png": "3f91af4119bab37b808eab6f3885269dfe89867f", 86 | "/stupid-rss-reader/assets/icon-96x96.png": "99e6a723c6e26226824c7b58bf4a8437fc9ecd67", 87 | "/stupid-rss-reader/assets/mstile-150x150.png": "aba962b9d2e6a5722d02489b9665866b47569bef", 88 | "/stupid-rss-reader/assets/safari-pinned-tab.svg": "78fc344b92e6a0bc5b52fc397302e6f05e88c628", 89 | "/stupid-rss-reader/assets/share.svg": "294413537f5bc98fa18c967a970ce79bde4b3d91", 90 | "/stupid-rss-reader/assets/skulls.png": "3452c09346c73808335623c7dba5ba3af99acbf5", 91 | "/stupid-rss-reader/assets/unexpand.svg": "19cdf930a94983f98eaa1d7209c8540a237be6d2", 92 | "/stupid-rss-reader/favicon.ico": "51f83552d6bd2f73fb09d202efbb4a903443ca29", 93 | "/stupid-rss-reader/index.html": "19a97d536ef9b41ea580941ad95fba26a6fc1e55", 94 | "/stupid-rss-reader/main.3abe0f1288fe1afd2bad.js": "cceefd18a7b22e252f816dc8386ac333043a10ef", 95 | "/stupid-rss-reader/manifest.json": "58fc308bfbd3d29eb32ff5dba40d26df99ed4fc9", 96 | "/stupid-rss-reader/polyfills.00096ed7d93ed26ee6df.js": "cfe7b0b53e96bff9ba384b4d497c08475a617d62", 97 | "/stupid-rss-reader/runtime.7b63b9fd40098a2e8207.js": "a9aafcf49f49145093fc831efd9b8e2f6c71bb9c", 98 | "/stupid-rss-reader/styles.8d877fb47cb501c4832a.css": "07815e1878a5d56850273bfb9f623ba4da965272" 99 | }, 100 | "navigationUrls": [ 101 | { 102 | "positive": true, 103 | "regex": "^\\/.*$" 104 | }, 105 | { 106 | "positive": false, 107 | "regex": "^\\/(?:.+\\/)?[^/]*\\.[^/]*$" 108 | }, 109 | { 110 | "positive": false, 111 | "regex": "^\\/(?:.+\\/)?[^/]*__[^/]*$" 112 | }, 113 | { 114 | "positive": false, 115 | "regex": "^\\/(?:.+\\/)?[^/]*__[^/]*\\/.*$" 116 | } 117 | ], 118 | "navigationRequestStrategy": "performance" 119 | } -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "cli": { 4 | "analytics": false 5 | }, 6 | "version": 1, 7 | "newProjectRoot": "projects", 8 | "projects": { 9 | "stupid-rss-reader": { 10 | "projectType": "application", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "style": "scss" 14 | } 15 | }, 16 | "root": "", 17 | "sourceRoot": "src", 18 | "prefix": "app", 19 | "architect": { 20 | "build": { 21 | "builder": "@angular-devkit/build-angular:browser", 22 | "options": { 23 | "outputPath": "docs", 24 | "index": "src/index.html", 25 | "main": "src/main.ts", 26 | "polyfills": "src/polyfills.ts", 27 | "tsConfig": "tsconfig.app.json", 28 | "aot": true, 29 | "assets": [ 30 | "src/favicon.ico", 31 | "src/assets", 32 | "src/manifest.json" 33 | ], 34 | "styles": [ 35 | "src/pure.css", 36 | "src/styles.scss" 37 | ], 38 | "scripts": [] 39 | }, 40 | "configurations": { 41 | "production": { 42 | "fileReplacements": [ 43 | { 44 | "replace": "src/environments/environment.ts", 45 | "with": "src/environments/environment.prod.ts" 46 | } 47 | ], 48 | "optimization": true, 49 | "outputHashing": "all", 50 | "sourceMap": false, 51 | "namedChunks": false, 52 | "extractLicenses": true, 53 | "vendorChunk": false, 54 | "commonChunk": false, 55 | "buildOptimizer": true, 56 | "budgets": [ 57 | { 58 | "type": "initial", 59 | "maximumWarning": "2mb", 60 | "maximumError": "5mb" 61 | }, 62 | { 63 | "type": "anyComponentStyle", 64 | "maximumWarning": "6kb", 65 | "maximumError": "10kb" 66 | } 67 | ], 68 | "serviceWorker": true, 69 | "ngswConfigPath": "ngsw-config.json" 70 | } 71 | } 72 | }, 73 | "serve": { 74 | "builder": "@angular-devkit/build-angular:dev-server", 75 | "options": { 76 | "browserTarget": "stupid-rss-reader:build" 77 | }, 78 | "configurations": { 79 | "production": { 80 | "browserTarget": "stupid-rss-reader:build:production" 81 | } 82 | } 83 | }, 84 | "extract-i18n": { 85 | "builder": "@angular-devkit/build-angular:extract-i18n", 86 | "options": { 87 | "browserTarget": "stupid-rss-reader:build" 88 | } 89 | }, 90 | "test": { 91 | "builder": "@angular-devkit/build-angular:karma", 92 | "options": { 93 | "main": "src/test.ts", 94 | "polyfills": "src/polyfills.ts", 95 | "tsConfig": "tsconfig.spec.json", 96 | "karmaConfig": "karma.conf.js", 97 | "assets": [ 98 | "src/favicon.ico", 99 | "src/assets", 100 | "src/manifest.json" 101 | ], 102 | "styles": [ 103 | "src/styles.scss" 104 | ], 105 | "scripts": [] 106 | } 107 | }, 108 | "lint": { 109 | "builder": "@angular-devkit/build-angular:tslint", 110 | "options": { 111 | "tsConfig": [ 112 | "tsconfig.app.json", 113 | "tsconfig.spec.json", 114 | "e2e/tsconfig.json" 115 | ], 116 | "exclude": [ 117 | "**/node_modules/**" 118 | ] 119 | } 120 | }, 121 | "e2e": { 122 | "builder": "@angular-devkit/build-angular:protractor", 123 | "options": { 124 | "protractorConfig": "e2e/protractor.conf.js", 125 | "devServerTarget": "stupid-rss-reader:serve" 126 | }, 127 | "configurations": { 128 | "production": { 129 | "devServerTarget": "stupid-rss-reader:serve:production" 130 | } 131 | } 132 | } 133 | } 134 | } 135 | }, 136 | "defaultProject": "stupid-rss-reader" 137 | } 138 | -------------------------------------------------------------------------------- /docs/styles.8d877fb47cb501c4832a.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v2.0.5 3 | Copyright 2013 Yahoo! 4 | Licensed under the BSD License. 5 | https://github.com/pure-css/pure/blob/master/LICENSE 6 | */ 7 | /*! 8 | normalize.css v | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:initial;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:initial}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:initial}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}html{font-family:sans-serif}.hidden,[hidden]{display:none!important}.pure-form input[type=color],.pure-form input[type=date],.pure-form input[type=datetime-local],.pure-form input[type=datetime],.pure-form input[type=email],.pure-form input[type=month],.pure-form input[type=number],.pure-form input[type=password],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=text],.pure-form input[type=time],.pure-form input[type=url],.pure-form input[type=week],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input:not([type]):focus,.pure-form input[type=color]:focus,.pure-form input[type=date]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=email]:focus,.pure-form input[type=month]:focus,.pure-form input[type=number]:focus,.pure-form input[type=password]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=text]:focus,.pure-form input[type=time]:focus,.pure-form input[type=url]:focus,.pure-form input[type=week]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129fea}.pure-form input[type=checkbox]:focus,.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus{outline:thin solid #129fea;outline:1px auto #129fea}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input:not([type])[disabled],.pure-form input[type=color][disabled],.pure-form input[type=date][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=email][disabled],.pure-form input[type=month][disabled],.pure-form input[type=number][disabled],.pure-form input[type=password][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=text][disabled],.pure-form input[type=time][disabled],.pure-form input[type=url][disabled],.pure-form input[type=week][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form select:focus:invalid,.pure-form textarea:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=checkbox]:focus:invalid:focus,.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input:not([type]),.pure-form-stacked input[type=color],.pure-form-stacked input[type=date],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=email],.pure-form-stacked input[type=file],.pure-form-stacked input[type=month],.pure-form-stacked input[type=number],.pure-form-stacked input[type=password],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=text],.pure-form-stacked input[type=time],.pure-form-stacked input[type=url],.pure-form-stacked input[type=week],.pure-form-stacked label,.pure-form-stacked select,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned select,.pure-form-aligned textarea,.pure-form-message-inline{display:inline-block;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form .pure-input-rounded,.pure-form input.pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-3-4{width:75%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width:480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=color],.pure-form input[type=date],.pure-form input[type=datetime-local],.pure-form input[type=datetime],.pure-form input[type=email],.pure-form input[type=month],.pure-form input[type=number],.pure-form input[type=password],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=text],.pure-form input[type=time],.pure-form input[type=url],.pure-form input[type=week],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=color],.pure-group input[type=date],.pure-group input[type=datetime-local],.pure-group input[type=datetime],.pure-group input[type=email],.pure-group input[type=month],.pure-group input[type=number],.pure-group input[type=password],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=text],.pure-group input[type=time],.pure-group input[type=url],.pure-group input[type=week]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form-message,.pure-form-message-inline{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-bottom-width:0;border-right-width:0;border-top-width:0;font-size:inherit;margin:0;overflow:visible;padding:.4rem .5rem}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:initial}.pure-table-odd td,.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb;word-break:break-word}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0}.w-100{width:100%}.p-1{padding:.5rem!important}.pb-2{padding-bottom:1rem!important}.mb-2{margin-bottom:1rem!important}.mt-1{margin-top:.5rem}.ml-1{margin-left:.5rem}.m-1{margin:.5rem}.mr-1{margin-right:.5rem}.ml-auto{margin-left:auto}.mr-auto{margin-right:auto}.lh-20{line-height:1.2rem!important}.container{max-width:960px;margin:0 auto}.post-wrapper{background-color:#ffffff80;margin:.5rem 0 1rem;padding:.2rem .5rem;border-radius:.2rem;box-shadow:0 1px 2px #0000001a;border-bottom:1px solid #fff}.post-wrapper .post-header h3{margin-top:0;margin-bottom:0;color:#333}.post-wrapper .post-header-thumbnail{max-height:58px;max-width:73px;margin-right:.2rem}.post-wrapper .post-header-text{margin:.2rem .3rem .3rem}.post-wrapper img{max-width:100%}.post-box{overflow-wrap:break-word}.post-box img{max-width:100%}.post-box svg.spacer{display:none}.feed-image{width:80px;min-width:80px;padding:4px;vertical-align:top}.feed-image img{max-width:100%}.feed-about{padding:4px;overflow-wrap:break-word;word-break:break-word}.feed-line,.feeds-header,.post-header,.top-top-feed{display:flex;justify-content:flex-start;align-items:center}.fit-text{overflow-wrap:break-word;word-break:break-word}.feed-line{border-bottom:1px solid #c6e081;padding:.5rem 0}.feed-line.loading{text-shadow:0 1px 3px #13a1004d}.post-button{min-width:40px}.post-button img{vertical-align:text-top;width:16px;height:16px}h1.app-name{margin:0;font-size:1.8rem}h1,h2,h3,h4,h5,h6{color:#444}.new-info{color:#16a006;font-weight:700}.short-info{color:grey}.loader{border-radius:8px}hr{border-top:0}.a-button{-moz-box-shadow:inset 0 0 0 1px #63ad0d;-webkit-box-shadow:inset 0 0 0 1px #63ad0d;-moz-border-radius:3px;-webkit-border-radius:3px;background:#eee;background:linear-gradient(#eee,#e2e2e2);border:1px solid #d0d0d0;border-bottom-color:#b2b1b1;border-radius:3px;box-shadow:inset 0 0 0 1px #f5f5f5;color:#555;display:inline-block;line-height:2rem;text-align:center;text-decoration:none;text-shadow:0 1px 0 #fafafa}.a-button:hover{background:#e4e4e4;background:linear-gradient(#e4e4e4,#ededed);border:1px solid #c2c2c2;border-bottom-color:#b2b1b1;box-shadow:inset 0 0 0 1px #efefef}.a-button:active{background:#dfdfdf;background:linear-gradient(#dfdfdf,#e3e3e3);border:1px solid #959595;box-shadow:inset 0 10px 15px 0 #c4c4c4;top:2px}.a-button.green{background:#cae285;background:linear-gradient(#cae285,#a3cd5a);border:1px solid #aad063;border-bottom-color:#799545;box-shadow:inset 0 0 0 1px #e0eeb6;color:#444;text-shadow:0 1px 0 #d0e5a4}.a-button.green:hover{background:#abd164;background:linear-gradient(#abd164,#b9d972);border:1px solid #98b85b;border-bottom-color:#799545;box-shadow:inset 0 0 0 1px #cce3a1}.a-button.green:active{background:#a4cb5d;background:linear-gradient(#cae285,#a3cd5a);border:1px solid #6e883f;box-shadow:inset 0 10px 15px 0 #90b352}.a-button.red{color:#82280c}.button-share img{vertical-align:text-top;width:16px;height:16px}.button-counter{min-width:60px}.active-actions{background-color:#ffffffb3}a{color:#005224}.bottom-box{height:200px;display:flex;justify-content:center;align-items:center}.bottom-box img{opacity:.2} -------------------------------------------------------------------------------- /docs/3rdpartylicenses.txt: -------------------------------------------------------------------------------- 1 | @angular/common 2 | MIT 3 | 4 | @angular/core 5 | MIT 6 | 7 | @angular/forms 8 | MIT 9 | 10 | @angular/platform-browser 11 | MIT 12 | 13 | @angular/router 14 | MIT 15 | 16 | @angular/service-worker 17 | MIT 18 | 19 | lodash-es 20 | MIT 21 | Copyright OpenJS Foundation and other contributors 22 | 23 | Based on Underscore.js, copyright Jeremy Ashkenas, 24 | DocumentCloud and Investigative Reporters & Editors 25 | 26 | This software consists of voluntary contributions made by many 27 | individuals. For exact contribution history, see the revision history 28 | available at https://github.com/lodash/lodash 29 | 30 | The following license applies to all parts of this software except as 31 | documented below: 32 | 33 | ==== 34 | 35 | Permission is hereby granted, free of charge, to any person obtaining 36 | a copy of this software and associated documentation files (the 37 | "Software"), to deal in the Software without restriction, including 38 | without limitation the rights to use, copy, modify, merge, publish, 39 | distribute, sublicense, and/or sell copies of the Software, and to 40 | permit persons to whom the Software is furnished to do so, subject to 41 | the following conditions: 42 | 43 | The above copyright notice and this permission notice shall be 44 | included in all copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 47 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 48 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 49 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 50 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 51 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 52 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 53 | 54 | ==== 55 | 56 | Copyright and related rights for sample code are waived via CC0. Sample 57 | code is defined as all source code displayed within the prose of the 58 | documentation. 59 | 60 | CC0: http://creativecommons.org/publicdomain/zero/1.0/ 61 | 62 | ==== 63 | 64 | Files located in the node_modules and vendor directories are externally 65 | maintained libraries used by this software which have their own 66 | licenses; we recommend you read them, as their terms may differ from the 67 | terms above. 68 | 69 | 70 | ngx-indexed-db 71 | ISC 72 | The MIT License (MIT) 73 | 74 | Permission is hereby granted, free of charge, to any person obtaining a copy 75 | of this software and associated documentation files (the "Software"), to deal 76 | in the Software without restriction, including without limitation the rights 77 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 78 | copies of the Software, and to permit persons to whom the Software is 79 | furnished to do so, subject to the following conditions: 80 | 81 | The above copyright notice and this permission notice shall be included in 82 | all copies or substantial portions of the Software. 83 | 84 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 85 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 86 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 87 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 88 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 89 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 90 | THE SOFTWARE. 91 | 92 | rxjs 93 | Apache-2.0 94 | Apache License 95 | Version 2.0, January 2004 96 | http://www.apache.org/licenses/ 97 | 98 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 99 | 100 | 1. Definitions. 101 | 102 | "License" shall mean the terms and conditions for use, reproduction, 103 | and distribution as defined by Sections 1 through 9 of this document. 104 | 105 | "Licensor" shall mean the copyright owner or entity authorized by 106 | the copyright owner that is granting the License. 107 | 108 | "Legal Entity" shall mean the union of the acting entity and all 109 | other entities that control, are controlled by, or are under common 110 | control with that entity. For the purposes of this definition, 111 | "control" means (i) the power, direct or indirect, to cause the 112 | direction or management of such entity, whether by contract or 113 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 114 | outstanding shares, or (iii) beneficial ownership of such entity. 115 | 116 | "You" (or "Your") shall mean an individual or Legal Entity 117 | exercising permissions granted by this License. 118 | 119 | "Source" form shall mean the preferred form for making modifications, 120 | including but not limited to software source code, documentation 121 | source, and configuration files. 122 | 123 | "Object" form shall mean any form resulting from mechanical 124 | transformation or translation of a Source form, including but 125 | not limited to compiled object code, generated documentation, 126 | and conversions to other media types. 127 | 128 | "Work" shall mean the work of authorship, whether in Source or 129 | Object form, made available under the License, as indicated by a 130 | copyright notice that is included in or attached to the work 131 | (an example is provided in the Appendix below). 132 | 133 | "Derivative Works" shall mean any work, whether in Source or Object 134 | form, that is based on (or derived from) the Work and for which the 135 | editorial revisions, annotations, elaborations, or other modifications 136 | represent, as a whole, an original work of authorship. For the purposes 137 | of this License, Derivative Works shall not include works that remain 138 | separable from, or merely link (or bind by name) to the interfaces of, 139 | the Work and Derivative Works thereof. 140 | 141 | "Contribution" shall mean any work of authorship, including 142 | the original version of the Work and any modifications or additions 143 | to that Work or Derivative Works thereof, that is intentionally 144 | submitted to Licensor for inclusion in the Work by the copyright owner 145 | or by an individual or Legal Entity authorized to submit on behalf of 146 | the copyright owner. For the purposes of this definition, "submitted" 147 | means any form of electronic, verbal, or written communication sent 148 | to the Licensor or its representatives, including but not limited to 149 | communication on electronic mailing lists, source code control systems, 150 | and issue tracking systems that are managed by, or on behalf of, the 151 | Licensor for the purpose of discussing and improving the Work, but 152 | excluding communication that is conspicuously marked or otherwise 153 | designated in writing by the copyright owner as "Not a Contribution." 154 | 155 | "Contributor" shall mean Licensor and any individual or Legal Entity 156 | on behalf of whom a Contribution has been received by Licensor and 157 | subsequently incorporated within the Work. 158 | 159 | 2. Grant of Copyright License. Subject to the terms and conditions of 160 | this License, each Contributor hereby grants to You a perpetual, 161 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 162 | copyright license to reproduce, prepare Derivative Works of, 163 | publicly display, publicly perform, sublicense, and distribute the 164 | Work and such Derivative Works in Source or Object form. 165 | 166 | 3. Grant of Patent License. Subject to the terms and conditions of 167 | this License, each Contributor hereby grants to You a perpetual, 168 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 169 | (except as stated in this section) patent license to make, have made, 170 | use, offer to sell, sell, import, and otherwise transfer the Work, 171 | where such license applies only to those patent claims licensable 172 | by such Contributor that are necessarily infringed by their 173 | Contribution(s) alone or by combination of their Contribution(s) 174 | with the Work to which such Contribution(s) was submitted. If You 175 | institute patent litigation against any entity (including a 176 | cross-claim or counterclaim in a lawsuit) alleging that the Work 177 | or a Contribution incorporated within the Work constitutes direct 178 | or contributory patent infringement, then any patent licenses 179 | granted to You under this License for that Work shall terminate 180 | as of the date such litigation is filed. 181 | 182 | 4. Redistribution. You may reproduce and distribute copies of the 183 | Work or Derivative Works thereof in any medium, with or without 184 | modifications, and in Source or Object form, provided that You 185 | meet the following conditions: 186 | 187 | (a) You must give any other recipients of the Work or 188 | Derivative Works a copy of this License; and 189 | 190 | (b) You must cause any modified files to carry prominent notices 191 | stating that You changed the files; and 192 | 193 | (c) You must retain, in the Source form of any Derivative Works 194 | that You distribute, all copyright, patent, trademark, and 195 | attribution notices from the Source form of the Work, 196 | excluding those notices that do not pertain to any part of 197 | the Derivative Works; and 198 | 199 | (d) If the Work includes a "NOTICE" text file as part of its 200 | distribution, then any Derivative Works that You distribute must 201 | include a readable copy of the attribution notices contained 202 | within such NOTICE file, excluding those notices that do not 203 | pertain to any part of the Derivative Works, in at least one 204 | of the following places: within a NOTICE text file distributed 205 | as part of the Derivative Works; within the Source form or 206 | documentation, if provided along with the Derivative Works; or, 207 | within a display generated by the Derivative Works, if and 208 | wherever such third-party notices normally appear. The contents 209 | of the NOTICE file are for informational purposes only and 210 | do not modify the License. You may add Your own attribution 211 | notices within Derivative Works that You distribute, alongside 212 | or as an addendum to the NOTICE text from the Work, provided 213 | that such additional attribution notices cannot be construed 214 | as modifying the License. 215 | 216 | You may add Your own copyright statement to Your modifications and 217 | may provide additional or different license terms and conditions 218 | for use, reproduction, or distribution of Your modifications, or 219 | for any such Derivative Works as a whole, provided Your use, 220 | reproduction, and distribution of the Work otherwise complies with 221 | the conditions stated in this License. 222 | 223 | 5. Submission of Contributions. Unless You explicitly state otherwise, 224 | any Contribution intentionally submitted for inclusion in the Work 225 | by You to the Licensor shall be under the terms and conditions of 226 | this License, without any additional terms or conditions. 227 | Notwithstanding the above, nothing herein shall supersede or modify 228 | the terms of any separate license agreement you may have executed 229 | with Licensor regarding such Contributions. 230 | 231 | 6. Trademarks. This License does not grant permission to use the trade 232 | names, trademarks, service marks, or product names of the Licensor, 233 | except as required for reasonable and customary use in describing the 234 | origin of the Work and reproducing the content of the NOTICE file. 235 | 236 | 7. Disclaimer of Warranty. Unless required by applicable law or 237 | agreed to in writing, Licensor provides the Work (and each 238 | Contributor provides its Contributions) on an "AS IS" BASIS, 239 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 240 | implied, including, without limitation, any warranties or conditions 241 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 242 | PARTICULAR PURPOSE. You are solely responsible for determining the 243 | appropriateness of using or redistributing the Work and assume any 244 | risks associated with Your exercise of permissions under this License. 245 | 246 | 8. Limitation of Liability. In no event and under no legal theory, 247 | whether in tort (including negligence), contract, or otherwise, 248 | unless required by applicable law (such as deliberate and grossly 249 | negligent acts) or agreed to in writing, shall any Contributor be 250 | liable to You for damages, including any direct, indirect, special, 251 | incidental, or consequential damages of any character arising as a 252 | result of this License or out of the use or inability to use the 253 | Work (including but not limited to damages for loss of goodwill, 254 | work stoppage, computer failure or malfunction, or any and all 255 | other commercial damages or losses), even if such Contributor 256 | has been advised of the possibility of such damages. 257 | 258 | 9. Accepting Warranty or Additional Liability. While redistributing 259 | the Work or Derivative Works thereof, You may choose to offer, 260 | and charge a fee for, acceptance of support, warranty, indemnity, 261 | or other liability obligations and/or rights consistent with this 262 | License. However, in accepting such obligations, You may act only 263 | on Your own behalf and on Your sole responsibility, not on behalf 264 | of any other Contributor, and only if You agree to indemnify, 265 | defend, and hold each Contributor harmless for any liability 266 | incurred by, or claims asserted against, such Contributor by reason 267 | of your accepting any such warranty or additional liability. 268 | 269 | END OF TERMS AND CONDITIONS 270 | 271 | APPENDIX: How to apply the Apache License to your work. 272 | 273 | To apply the Apache License to your work, attach the following 274 | boilerplate notice, with the fields enclosed by brackets "[]" 275 | replaced with your own identifying information. (Don't include 276 | the brackets!) The text should be enclosed in the appropriate 277 | comment syntax for the file format. We also recommend that a 278 | file or class name and description of purpose be included on the 279 | same "printed page" as the copyright notice for easier 280 | identification within third-party archives. 281 | 282 | Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors 283 | 284 | Licensed under the Apache License, Version 2.0 (the "License"); 285 | you may not use this file except in compliance with the License. 286 | You may obtain a copy of the License at 287 | 288 | http://www.apache.org/licenses/LICENSE-2.0 289 | 290 | Unless required by applicable law or agreed to in writing, software 291 | distributed under the License is distributed on an "AS IS" BASIS, 292 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 293 | See the License for the specific language governing permissions and 294 | limitations under the License. 295 | 296 | 297 | 298 | tslib 299 | 0BSD 300 | Copyright (c) Microsoft Corporation. 301 | 302 | Permission to use, copy, modify, and/or distribute this software for any 303 | purpose with or without fee is hereby granted. 304 | 305 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 306 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 307 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 308 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 309 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 310 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 311 | PERFORMANCE OF THIS SOFTWARE. 312 | 313 | webpack 314 | MIT 315 | Copyright JS Foundation and other contributors 316 | 317 | Permission is hereby granted, free of charge, to any person obtaining 318 | a copy of this software and associated documentation files (the 319 | 'Software'), to deal in the Software without restriction, including 320 | without limitation the rights to use, copy, modify, merge, publish, 321 | distribute, sublicense, and/or sell copies of the Software, and to 322 | permit persons to whom the Software is furnished to do so, subject to 323 | the following conditions: 324 | 325 | The above copyright notice and this permission notice shall be 326 | included in all copies or substantial portions of the Software. 327 | 328 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 329 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 330 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 331 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 332 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 333 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 334 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 335 | 336 | 337 | zone.js 338 | MIT 339 | The MIT License 340 | 341 | Copyright (c) 2010-2020 Google LLC. https://angular.io/license 342 | 343 | Permission is hereby granted, free of charge, to any person obtaining a copy 344 | of this software and associated documentation files (the "Software"), to deal 345 | in the Software without restriction, including without limitation the rights 346 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 347 | copies of the Software, and to permit persons to whom the Software is 348 | furnished to do so, subject to the following conditions: 349 | 350 | The above copyright notice and this permission notice shall be included in 351 | all copies or substantial portions of the Software. 352 | 353 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 354 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 355 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 356 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 357 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 358 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 359 | THE SOFTWARE. 360 | -------------------------------------------------------------------------------- /src/pure.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v2.0.5 3 | Copyright 2013 Yahoo! 4 | Licensed under the BSD License. 5 | https://github.com/pure-css/pure/blob/master/LICENSE 6 | */ 7 | /*! 8 | normalize.css v | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 12 | 13 | /* Document 14 | ========================================================================== */ 15 | 16 | /** 17 | * 1. Correct the line height in all browsers. 18 | * 2. Prevent adjustments of font size after orientation changes in iOS. 19 | */ 20 | 21 | html { 22 | line-height: 1.15; /* 1 */ 23 | -webkit-text-size-adjust: 100%; /* 2 */ 24 | } 25 | 26 | /* Sections 27 | ========================================================================== */ 28 | 29 | /** 30 | * Remove the margin in all browsers. 31 | */ 32 | 33 | body { 34 | margin: 0; 35 | } 36 | 37 | /** 38 | * Render the `main` element consistently in IE. 39 | */ 40 | 41 | main { 42 | display: block; 43 | } 44 | 45 | /** 46 | * Correct the font size and margin on `h1` elements within `section` and 47 | * `article` contexts in Chrome, Firefox, and Safari. 48 | */ 49 | 50 | h1 { 51 | font-size: 2em; 52 | margin: 0.67em 0; 53 | } 54 | 55 | /* Grouping content 56 | ========================================================================== */ 57 | 58 | /** 59 | * 1. Add the correct box sizing in Firefox. 60 | * 2. Show the overflow in Edge and IE. 61 | */ 62 | 63 | hr { 64 | -webkit-box-sizing: content-box; 65 | box-sizing: content-box; /* 1 */ 66 | height: 0; /* 1 */ 67 | overflow: visible; /* 2 */ 68 | } 69 | 70 | /** 71 | * 1. Correct the inheritance and scaling of font size in all browsers. 72 | * 2. Correct the odd `em` font sizing in all browsers. 73 | */ 74 | 75 | pre { 76 | font-family: monospace, monospace; /* 1 */ 77 | font-size: 1em; /* 2 */ 78 | } 79 | 80 | /* Text-level semantics 81 | ========================================================================== */ 82 | 83 | /** 84 | * Remove the gray background on active links in IE 10. 85 | */ 86 | 87 | a { 88 | background-color: transparent; 89 | } 90 | 91 | /** 92 | * 1. Remove the bottom border in Chrome 57- 93 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 94 | */ 95 | 96 | abbr[title] { 97 | border-bottom: none; /* 1 */ 98 | text-decoration: underline; /* 2 */ 99 | -webkit-text-decoration: underline dotted; 100 | text-decoration: underline dotted; /* 2 */ 101 | } 102 | 103 | /** 104 | * Add the correct font weight in Chrome, Edge, and Safari. 105 | */ 106 | 107 | b, 108 | strong { 109 | font-weight: bolder; 110 | } 111 | 112 | /** 113 | * 1. Correct the inheritance and scaling of font size in all browsers. 114 | * 2. Correct the odd `em` font sizing in all browsers. 115 | */ 116 | 117 | code, 118 | kbd, 119 | samp { 120 | font-family: monospace, monospace; /* 1 */ 121 | font-size: 1em; /* 2 */ 122 | } 123 | 124 | /** 125 | * Add the correct font size in all browsers. 126 | */ 127 | 128 | small { 129 | font-size: 80%; 130 | } 131 | 132 | /** 133 | * Prevent `sub` and `sup` elements from affecting the line height in 134 | * all browsers. 135 | */ 136 | 137 | sub, 138 | sup { 139 | font-size: 75%; 140 | line-height: 0; 141 | position: relative; 142 | vertical-align: baseline; 143 | } 144 | 145 | sub { 146 | bottom: -0.25em; 147 | } 148 | 149 | sup { 150 | top: -0.5em; 151 | } 152 | 153 | /* Embedded content 154 | ========================================================================== */ 155 | 156 | /** 157 | * Remove the border on images inside links in IE 10. 158 | */ 159 | 160 | img { 161 | border-style: none; 162 | } 163 | 164 | /* Forms 165 | ========================================================================== */ 166 | 167 | /** 168 | * 1. Change the font styles in all browsers. 169 | * 2. Remove the margin in Firefox and Safari. 170 | */ 171 | 172 | button, 173 | input, 174 | optgroup, 175 | select, 176 | textarea { 177 | font-family: inherit; /* 1 */ 178 | font-size: 100%; /* 1 */ 179 | line-height: 1.15; /* 1 */ 180 | margin: 0; /* 2 */ 181 | } 182 | 183 | /** 184 | * Show the overflow in IE. 185 | * 1. Show the overflow in Edge. 186 | */ 187 | 188 | button, 189 | input { /* 1 */ 190 | overflow: visible; 191 | } 192 | 193 | /** 194 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 195 | * 1. Remove the inheritance of text transform in Firefox. 196 | */ 197 | 198 | button, 199 | select { /* 1 */ 200 | text-transform: none; 201 | } 202 | 203 | /** 204 | * Correct the inability to style clickable types in iOS and Safari. 205 | */ 206 | 207 | button, 208 | [type="button"], 209 | [type="reset"], 210 | [type="submit"] { 211 | -webkit-appearance: button; 212 | } 213 | 214 | /** 215 | * Remove the inner border and padding in Firefox. 216 | */ 217 | 218 | button::-moz-focus-inner, 219 | [type="button"]::-moz-focus-inner, 220 | [type="reset"]::-moz-focus-inner, 221 | [type="submit"]::-moz-focus-inner { 222 | border-style: none; 223 | padding: 0; 224 | } 225 | 226 | /** 227 | * Restore the focus styles unset by the previous rule. 228 | */ 229 | 230 | button:-moz-focusring, 231 | [type="button"]:-moz-focusring, 232 | [type="reset"]:-moz-focusring, 233 | [type="submit"]:-moz-focusring { 234 | outline: 1px dotted ButtonText; 235 | } 236 | 237 | /** 238 | * Correct the padding in Firefox. 239 | */ 240 | 241 | fieldset { 242 | padding: 0.35em 0.75em 0.625em; 243 | } 244 | 245 | /** 246 | * 1. Correct the text wrapping in Edge and IE. 247 | * 2. Correct the color inheritance from `fieldset` elements in IE. 248 | * 3. Remove the padding so developers are not caught out when they zero out 249 | * `fieldset` elements in all browsers. 250 | */ 251 | 252 | legend { 253 | -webkit-box-sizing: border-box; 254 | box-sizing: border-box; /* 1 */ 255 | color: inherit; /* 2 */ 256 | display: table; /* 1 */ 257 | max-width: 100%; /* 1 */ 258 | padding: 0; /* 3 */ 259 | white-space: normal; /* 1 */ 260 | } 261 | 262 | /** 263 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 264 | */ 265 | 266 | progress { 267 | vertical-align: baseline; 268 | } 269 | 270 | /** 271 | * Remove the default vertical scrollbar in IE 10+. 272 | */ 273 | 274 | textarea { 275 | overflow: auto; 276 | } 277 | 278 | /** 279 | * 1. Add the correct box sizing in IE 10. 280 | * 2. Remove the padding in IE 10. 281 | */ 282 | 283 | [type="checkbox"], 284 | [type="radio"] { 285 | -webkit-box-sizing: border-box; 286 | box-sizing: border-box; /* 1 */ 287 | padding: 0; /* 2 */ 288 | } 289 | 290 | /** 291 | * Correct the cursor style of increment and decrement buttons in Chrome. 292 | */ 293 | 294 | [type="number"]::-webkit-inner-spin-button, 295 | [type="number"]::-webkit-outer-spin-button { 296 | height: auto; 297 | } 298 | 299 | /** 300 | * 1. Correct the odd appearance in Chrome and Safari. 301 | * 2. Correct the outline style in Safari. 302 | */ 303 | 304 | [type="search"] { 305 | -webkit-appearance: textfield; /* 1 */ 306 | outline-offset: -2px; /* 2 */ 307 | } 308 | 309 | /** 310 | * Remove the inner padding in Chrome and Safari on macOS. 311 | */ 312 | 313 | [type="search"]::-webkit-search-decoration { 314 | -webkit-appearance: none; 315 | } 316 | 317 | /** 318 | * 1. Correct the inability to style clickable types in iOS and Safari. 319 | * 2. Change font properties to `inherit` in Safari. 320 | */ 321 | 322 | ::-webkit-file-upload-button { 323 | -webkit-appearance: button; /* 1 */ 324 | font: inherit; /* 2 */ 325 | } 326 | 327 | /* Interactive 328 | ========================================================================== */ 329 | 330 | /* 331 | * Add the correct display in Edge, IE 10+, and Firefox. 332 | */ 333 | 334 | details { 335 | display: block; 336 | } 337 | 338 | /* 339 | * Add the correct display in all browsers. 340 | */ 341 | 342 | summary { 343 | display: list-item; 344 | } 345 | 346 | /* Misc 347 | ========================================================================== */ 348 | 349 | /** 350 | * Add the correct display in IE 10+. 351 | */ 352 | 353 | template { 354 | display: none; 355 | } 356 | 357 | /** 358 | * Add the correct display in IE 10. 359 | */ 360 | 361 | [hidden] { 362 | display: none; 363 | } 364 | 365 | /*csslint important:false*/ 366 | 367 | /* ========================================================================== 368 | Pure Base Extras 369 | ========================================================================== */ 370 | 371 | /** 372 | * Extra rules that Pure adds on top of Normalize.css 373 | */ 374 | 375 | html { 376 | font-family: sans-serif; 377 | } 378 | 379 | /** 380 | * Always hide an element when it has the `hidden` HTML attribute. 381 | */ 382 | 383 | .hidden, 384 | [hidden] { 385 | display: none !important; 386 | } 387 | 388 | 389 | /*csslint box-model:false*/ 390 | /* 391 | Box-model set to false because we're setting a height on select elements, which 392 | also have border and padding. This is done because some browsers don't render 393 | the padding. We explicitly set the box-model for select elements to border-box, 394 | so we can ignore the csslint warning. 395 | */ 396 | 397 | .pure-form input[type="text"], 398 | .pure-form input[type="password"], 399 | .pure-form input[type="email"], 400 | .pure-form input[type="url"], 401 | .pure-form input[type="date"], 402 | .pure-form input[type="month"], 403 | .pure-form input[type="time"], 404 | .pure-form input[type="datetime"], 405 | .pure-form input[type="datetime-local"], 406 | .pure-form input[type="week"], 407 | .pure-form input[type="number"], 408 | .pure-form input[type="search"], 409 | .pure-form input[type="tel"], 410 | .pure-form input[type="color"], 411 | .pure-form select, 412 | .pure-form textarea { 413 | padding: 0.5em 0.6em; 414 | display: inline-block; 415 | border: 1px solid #ccc; 416 | -webkit-box-shadow: inset 0 1px 3px #ddd; 417 | box-shadow: inset 0 1px 3px #ddd; 418 | border-radius: 4px; 419 | vertical-align: middle; 420 | -webkit-box-sizing: border-box; 421 | box-sizing: border-box; 422 | } 423 | 424 | /* 425 | Need to separate out the :not() selector from the rest of the CSS 2.1 selectors 426 | since IE8 won't execute CSS that contains a CSS3 selector. 427 | */ 428 | .pure-form input:not([type]) { 429 | padding: 0.5em 0.6em; 430 | display: inline-block; 431 | border: 1px solid #ccc; 432 | -webkit-box-shadow: inset 0 1px 3px #ddd; 433 | box-shadow: inset 0 1px 3px #ddd; 434 | border-radius: 4px; 435 | -webkit-box-sizing: border-box; 436 | box-sizing: border-box; 437 | } 438 | 439 | 440 | /* Chrome (as of v.32/34 on OS X) needs additional room for color to display. */ 441 | /* May be able to remove this tweak as color inputs become more standardized across browsers. */ 442 | .pure-form input[type="color"] { 443 | padding: 0.2em 0.5em; 444 | } 445 | 446 | 447 | .pure-form input[type="text"]:focus, 448 | .pure-form input[type="password"]:focus, 449 | .pure-form input[type="email"]:focus, 450 | .pure-form input[type="url"]:focus, 451 | .pure-form input[type="date"]:focus, 452 | .pure-form input[type="month"]:focus, 453 | .pure-form input[type="time"]:focus, 454 | .pure-form input[type="datetime"]:focus, 455 | .pure-form input[type="datetime-local"]:focus, 456 | .pure-form input[type="week"]:focus, 457 | .pure-form input[type="number"]:focus, 458 | .pure-form input[type="search"]:focus, 459 | .pure-form input[type="tel"]:focus, 460 | .pure-form input[type="color"]:focus, 461 | .pure-form select:focus, 462 | .pure-form textarea:focus { 463 | outline: 0; 464 | border-color: #129FEA; 465 | } 466 | 467 | /* 468 | Need to separate out the :not() selector from the rest of the CSS 2.1 selectors 469 | since IE8 won't execute CSS that contains a CSS3 selector. 470 | */ 471 | .pure-form input:not([type]):focus { 472 | outline: 0; 473 | border-color: #129FEA; 474 | } 475 | 476 | .pure-form input[type="file"]:focus, 477 | .pure-form input[type="radio"]:focus, 478 | .pure-form input[type="checkbox"]:focus { 479 | outline: thin solid #129FEA; 480 | outline: 1px auto #129FEA; 481 | } 482 | 483 | .pure-form .pure-checkbox, 484 | .pure-form .pure-radio { 485 | margin: 0.5em 0; 486 | display: block; 487 | } 488 | 489 | .pure-form input[type="text"][disabled], 490 | .pure-form input[type="password"][disabled], 491 | .pure-form input[type="email"][disabled], 492 | .pure-form input[type="url"][disabled], 493 | .pure-form input[type="date"][disabled], 494 | .pure-form input[type="month"][disabled], 495 | .pure-form input[type="time"][disabled], 496 | .pure-form input[type="datetime"][disabled], 497 | .pure-form input[type="datetime-local"][disabled], 498 | .pure-form input[type="week"][disabled], 499 | .pure-form input[type="number"][disabled], 500 | .pure-form input[type="search"][disabled], 501 | .pure-form input[type="tel"][disabled], 502 | .pure-form input[type="color"][disabled], 503 | .pure-form select[disabled], 504 | .pure-form textarea[disabled] { 505 | cursor: not-allowed; 506 | background-color: #eaeded; 507 | color: #cad2d3; 508 | } 509 | 510 | /* 511 | Need to separate out the :not() selector from the rest of the CSS 2.1 selectors 512 | since IE8 won't execute CSS that contains a CSS3 selector. 513 | */ 514 | .pure-form input:not([type])[disabled] { 515 | cursor: not-allowed; 516 | background-color: #eaeded; 517 | color: #cad2d3; 518 | } 519 | 520 | .pure-form input[readonly], 521 | .pure-form select[readonly], 522 | .pure-form textarea[readonly] { 523 | background-color: #eee; /* menu hover bg color */ 524 | color: #777; /* menu text color */ 525 | border-color: #ccc; 526 | } 527 | 528 | .pure-form input:focus:invalid, 529 | .pure-form textarea:focus:invalid, 530 | .pure-form select:focus:invalid { 531 | color: #b94a48; 532 | border-color: #e9322d; 533 | } 534 | 535 | .pure-form input[type="file"]:focus:invalid:focus, 536 | .pure-form input[type="radio"]:focus:invalid:focus, 537 | .pure-form input[type="checkbox"]:focus:invalid:focus { 538 | outline-color: #e9322d; 539 | } 540 | 541 | .pure-form select { 542 | /* Normalizes the height; padding is not sufficient. */ 543 | height: 2.25em; 544 | border: 1px solid #ccc; 545 | background-color: white; 546 | } 547 | 548 | .pure-form select[multiple] { 549 | height: auto; 550 | } 551 | 552 | .pure-form label { 553 | margin: 0.5em 0 0.2em; 554 | } 555 | 556 | .pure-form fieldset { 557 | margin: 0; 558 | padding: 0.35em 0 0.75em; 559 | border: 0; 560 | } 561 | 562 | .pure-form legend { 563 | display: block; 564 | width: 100%; 565 | padding: 0.3em 0; 566 | margin-bottom: 0.3em; 567 | color: #333; 568 | border-bottom: 1px solid #e5e5e5; 569 | } 570 | 571 | .pure-form-stacked input[type="text"], 572 | .pure-form-stacked input[type="password"], 573 | .pure-form-stacked input[type="email"], 574 | .pure-form-stacked input[type="url"], 575 | .pure-form-stacked input[type="date"], 576 | .pure-form-stacked input[type="month"], 577 | .pure-form-stacked input[type="time"], 578 | .pure-form-stacked input[type="datetime"], 579 | .pure-form-stacked input[type="datetime-local"], 580 | .pure-form-stacked input[type="week"], 581 | .pure-form-stacked input[type="number"], 582 | .pure-form-stacked input[type="search"], 583 | .pure-form-stacked input[type="tel"], 584 | .pure-form-stacked input[type="color"], 585 | .pure-form-stacked input[type="file"], 586 | .pure-form-stacked select, 587 | .pure-form-stacked label, 588 | .pure-form-stacked textarea { 589 | display: block; 590 | margin: 0.25em 0; 591 | } 592 | 593 | /* 594 | Need to separate out the :not() selector from the rest of the CSS 2.1 selectors 595 | since IE8 won't execute CSS that contains a CSS3 selector. 596 | */ 597 | .pure-form-stacked input:not([type]) { 598 | display: block; 599 | margin: 0.25em 0; 600 | } 601 | 602 | .pure-form-aligned input, 603 | .pure-form-aligned textarea, 604 | .pure-form-aligned select, 605 | .pure-form-message-inline { 606 | display: inline-block; 607 | vertical-align: middle; 608 | } 609 | 610 | .pure-form-aligned textarea { 611 | vertical-align: top; 612 | } 613 | 614 | /* Aligned Forms */ 615 | .pure-form-aligned .pure-control-group { 616 | margin-bottom: 0.5em; 617 | } 618 | 619 | .pure-form-aligned .pure-control-group label { 620 | text-align: right; 621 | display: inline-block; 622 | vertical-align: middle; 623 | width: 10em; 624 | margin: 0 1em 0 0; 625 | } 626 | 627 | .pure-form-aligned .pure-controls { 628 | margin: 1.5em 0 0 11em; 629 | } 630 | 631 | /* Rounded Inputs */ 632 | .pure-form input.pure-input-rounded, 633 | .pure-form .pure-input-rounded { 634 | border-radius: 2em; 635 | padding: 0.5em 1em; 636 | } 637 | 638 | /* Grouped Inputs */ 639 | .pure-form .pure-group fieldset { 640 | margin-bottom: 10px; 641 | } 642 | 643 | .pure-form .pure-group input, 644 | .pure-form .pure-group textarea { 645 | display: block; 646 | padding: 10px; 647 | margin: 0 0 -1px; 648 | border-radius: 0; 649 | position: relative; 650 | top: -1px; 651 | } 652 | 653 | .pure-form .pure-group input:focus, 654 | .pure-form .pure-group textarea:focus { 655 | z-index: 3; 656 | } 657 | 658 | .pure-form .pure-group input:first-child, 659 | .pure-form .pure-group textarea:first-child { 660 | top: 1px; 661 | border-radius: 4px 4px 0 0; 662 | margin: 0; 663 | } 664 | 665 | .pure-form .pure-group input:first-child:last-child, 666 | .pure-form .pure-group textarea:first-child:last-child { 667 | top: 1px; 668 | border-radius: 4px; 669 | margin: 0; 670 | } 671 | 672 | .pure-form .pure-group input:last-child, 673 | .pure-form .pure-group textarea:last-child { 674 | top: -2px; 675 | border-radius: 0 0 4px 4px; 676 | margin: 0; 677 | } 678 | 679 | .pure-form .pure-group button { 680 | margin: 0.35em 0; 681 | } 682 | 683 | .pure-form .pure-input-1 { 684 | width: 100%; 685 | } 686 | 687 | .pure-form .pure-input-3-4 { 688 | width: 75%; 689 | } 690 | 691 | .pure-form .pure-input-2-3 { 692 | width: 66%; 693 | } 694 | 695 | .pure-form .pure-input-1-2 { 696 | width: 50%; 697 | } 698 | 699 | .pure-form .pure-input-1-3 { 700 | width: 33%; 701 | } 702 | 703 | .pure-form .pure-input-1-4 { 704 | width: 25%; 705 | } 706 | 707 | /* Inline help for forms */ 708 | .pure-form-message-inline { 709 | display: inline-block; 710 | padding-left: 0.3em; 711 | color: #666; 712 | vertical-align: middle; 713 | font-size: 0.875em; 714 | } 715 | 716 | /* Block help for forms */ 717 | .pure-form-message { 718 | display: block; 719 | color: #666; 720 | font-size: 0.875em; 721 | } 722 | 723 | @media only screen and (max-width: 480px) { 724 | .pure-form button[type="submit"] { 725 | margin: 0.7em 0 0; 726 | } 727 | 728 | .pure-form input:not([type]), 729 | .pure-form input[type="text"], 730 | .pure-form input[type="password"], 731 | .pure-form input[type="email"], 732 | .pure-form input[type="url"], 733 | .pure-form input[type="date"], 734 | .pure-form input[type="month"], 735 | .pure-form input[type="time"], 736 | .pure-form input[type="datetime"], 737 | .pure-form input[type="datetime-local"], 738 | .pure-form input[type="week"], 739 | .pure-form input[type="number"], 740 | .pure-form input[type="search"], 741 | .pure-form input[type="tel"], 742 | .pure-form input[type="color"], 743 | .pure-form label { 744 | margin-bottom: 0.3em; 745 | display: block; 746 | } 747 | 748 | .pure-group input:not([type]), 749 | .pure-group input[type="text"], 750 | .pure-group input[type="password"], 751 | .pure-group input[type="email"], 752 | .pure-group input[type="url"], 753 | .pure-group input[type="date"], 754 | .pure-group input[type="month"], 755 | .pure-group input[type="time"], 756 | .pure-group input[type="datetime"], 757 | .pure-group input[type="datetime-local"], 758 | .pure-group input[type="week"], 759 | .pure-group input[type="number"], 760 | .pure-group input[type="search"], 761 | .pure-group input[type="tel"], 762 | .pure-group input[type="color"] { 763 | margin-bottom: 0; 764 | } 765 | 766 | .pure-form-aligned .pure-control-group label { 767 | margin-bottom: 0.3em; 768 | text-align: left; 769 | display: block; 770 | width: 100%; 771 | } 772 | 773 | .pure-form-aligned .pure-controls { 774 | margin: 1.5em 0 0 0; 775 | } 776 | 777 | .pure-form-message-inline, 778 | .pure-form-message { 779 | display: block; 780 | font-size: 0.75em; 781 | /* Increased bottom padding to make it group with its related input element. */ 782 | padding: 0.2em 0 0.8em; 783 | } 784 | } 785 | 786 | 787 | 788 | 789 | .pure-table { 790 | /* Remove spacing between table cells (from Normalize.css) */ 791 | border-collapse: collapse; 792 | border-spacing: 0; 793 | empty-cells: show; 794 | border: 1px solid #cbcbcb; 795 | } 796 | 797 | .pure-table caption { 798 | color: #000; 799 | font: italic 85%/1 arial, sans-serif; 800 | padding: 1em 0; 801 | text-align: center; 802 | } 803 | 804 | .pure-table td, 805 | .pure-table th { 806 | border-left: 1px solid #cbcbcb; /* inner column border */ 807 | border-width: 0 0 0 1px; 808 | font-size: inherit; 809 | margin: 0; 810 | overflow: visible; /*to make ths where the title is really long work*/ 811 | padding: 0.4rem 0.5rem; /* cell padding */ 812 | } 813 | 814 | .pure-table thead { 815 | background-color: #e0e0e0; 816 | color: #000; 817 | text-align: left; 818 | vertical-align: bottom; 819 | } 820 | 821 | /* 822 | striping: 823 | even - #fff (white) 824 | odd - #f2f2f2 (light gray) 825 | */ 826 | .pure-table td { 827 | background-color: transparent; 828 | } 829 | 830 | .pure-table-odd td { 831 | background-color: #f2f2f2; 832 | } 833 | 834 | /* nth-child selector for modern browsers */ 835 | .pure-table-striped tr:nth-child(2n-1) td { 836 | background-color: #f2f2f2; 837 | } 838 | 839 | /* BORDERED TABLES */ 840 | .pure-table-bordered td { 841 | border-bottom: 1px solid #cbcbcb; 842 | } 843 | 844 | .pure-table-bordered tbody > tr:last-child > td { 845 | border-bottom-width: 0; 846 | } 847 | 848 | 849 | /* HORIZONTAL BORDERED TABLES */ 850 | 851 | .pure-table-horizontal td, 852 | .pure-table-horizontal th { 853 | border-width: 0 0 1px 0; 854 | border-bottom: 1px solid #cbcbcb; 855 | word-break: break-word; 856 | } 857 | 858 | .pure-table-horizontal tbody > tr:last-child > td { 859 | border-bottom-width: 0; 860 | } 861 | -------------------------------------------------------------------------------- /docs/polyfills.00096ed7d93ed26ee6df.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[2],{1:function(e,t,n){e.exports=n("hN/g")},"hN/g":function(e,t,n){"use strict";n.r(t),n("pDpN")},pDpN:function(e,t,n){"use strict";!function(e){const t=e.performance;function n(e){t&&t.mark&&t.mark(e)}function o(e,n){t&&t.measure&&t.measure(e,n)}n("Zone");const r=e.__Zone_symbol_prefix||"__zone_symbol__";function s(e){return r+e}const a=!0===e[s("forceDuplicateZoneCheck")];if(e.Zone){if(a||"function"!=typeof e.Zone.__symbol__)throw new Error("Zone already loaded.");return e.Zone}class i{constructor(e,t){this._parent=e,this._name=t?t.name||"unnamed":"",this._properties=t&&t.properties||{},this._zoneDelegate=new l(this,this._parent&&this._parent._zoneDelegate,t)}static assertZonePatched(){if(e.Promise!==O.ZoneAwarePromise)throw new Error("Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten.\nMost likely cause is that a Promise polyfill has been loaded after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. If you must load one, do so before loading zone.js.)")}static get root(){let e=i.current;for(;e.parent;)e=e.parent;return e}static get current(){return z.zone}static get currentTask(){return j}static __load_patch(t,r,s=!1){if(O.hasOwnProperty(t)){if(!s&&a)throw Error("Already loaded patch: "+t)}else if(!e["__Zone_disable_"+t]){const s="Zone:"+t;n(s),O[t]=r(e,i,C),o(s,s)}}get parent(){return this._parent}get name(){return this._name}get(e){const t=this.getZoneWith(e);if(t)return t._properties[e]}getZoneWith(e){let t=this;for(;t;){if(t._properties.hasOwnProperty(e))return t;t=t._parent}return null}fork(e){if(!e)throw new Error("ZoneSpec required!");return this._zoneDelegate.fork(this,e)}wrap(e,t){if("function"!=typeof e)throw new Error("Expecting function got: "+e);const n=this._zoneDelegate.intercept(this,e,t),o=this;return function(){return o.runGuarded(n,this,arguments,t)}}run(e,t,n,o){z={parent:z,zone:this};try{return this._zoneDelegate.invoke(this,e,t,n,o)}finally{z=z.parent}}runGuarded(e,t=null,n,o){z={parent:z,zone:this};try{try{return this._zoneDelegate.invoke(this,e,t,n,o)}catch(r){if(this._zoneDelegate.handleError(this,r))throw r}}finally{z=z.parent}}runTask(e,t,n){if(e.zone!=this)throw new Error("A task can only be run in the zone of creation! (Creation: "+(e.zone||y).name+"; Execution: "+this.name+")");if(e.state===v&&(e.type===P||e.type===D))return;const o=e.state!=E;o&&e._transitionTo(E,b),e.runCount++;const r=j;j=e,z={parent:z,zone:this};try{e.type==D&&e.data&&!e.data.isPeriodic&&(e.cancelFn=void 0);try{return this._zoneDelegate.invokeTask(this,e,t,n)}catch(s){if(this._zoneDelegate.handleError(this,s))throw s}}finally{e.state!==v&&e.state!==Z&&(e.type==P||e.data&&e.data.isPeriodic?o&&e._transitionTo(b,E):(e.runCount=0,this._updateTaskCount(e,-1),o&&e._transitionTo(v,E,v))),z=z.parent,j=r}}scheduleTask(e){if(e.zone&&e.zone!==this){let t=this;for(;t;){if(t===e.zone)throw Error(`can not reschedule task to ${this.name} which is descendants of the original zone ${e.zone.name}`);t=t.parent}}e._transitionTo(T,v);const t=[];e._zoneDelegates=t,e._zone=this;try{e=this._zoneDelegate.scheduleTask(this,e)}catch(n){throw e._transitionTo(Z,T,v),this._zoneDelegate.handleError(this,n),n}return e._zoneDelegates===t&&this._updateTaskCount(e,1),e.state==T&&e._transitionTo(b,T),e}scheduleMicroTask(e,t,n,o){return this.scheduleTask(new u(S,e,t,n,o,void 0))}scheduleMacroTask(e,t,n,o,r){return this.scheduleTask(new u(D,e,t,n,o,r))}scheduleEventTask(e,t,n,o,r){return this.scheduleTask(new u(P,e,t,n,o,r))}cancelTask(e){if(e.zone!=this)throw new Error("A task can only be cancelled in the zone of creation! (Creation: "+(e.zone||y).name+"; Execution: "+this.name+")");e._transitionTo(w,b,E);try{this._zoneDelegate.cancelTask(this,e)}catch(t){throw e._transitionTo(Z,w),this._zoneDelegate.handleError(this,t),t}return this._updateTaskCount(e,-1),e._transitionTo(v,w),e.runCount=0,e}_updateTaskCount(e,t){const n=e._zoneDelegates;-1==t&&(e._zoneDelegates=null);for(let o=0;oe.hasTask(n,o),onScheduleTask:(e,t,n,o)=>e.scheduleTask(n,o),onInvokeTask:(e,t,n,o,r,s)=>e.invokeTask(n,o,r,s),onCancelTask:(e,t,n,o)=>e.cancelTask(n,o)};class l{constructor(e,t,n){this._taskCounts={microTask:0,macroTask:0,eventTask:0},this.zone=e,this._parentDelegate=t,this._forkZS=n&&(n&&n.onFork?n:t._forkZS),this._forkDlgt=n&&(n.onFork?t:t._forkDlgt),this._forkCurrZone=n&&(n.onFork?this.zone:t._forkCurrZone),this._interceptZS=n&&(n.onIntercept?n:t._interceptZS),this._interceptDlgt=n&&(n.onIntercept?t:t._interceptDlgt),this._interceptCurrZone=n&&(n.onIntercept?this.zone:t._interceptCurrZone),this._invokeZS=n&&(n.onInvoke?n:t._invokeZS),this._invokeDlgt=n&&(n.onInvoke?t:t._invokeDlgt),this._invokeCurrZone=n&&(n.onInvoke?this.zone:t._invokeCurrZone),this._handleErrorZS=n&&(n.onHandleError?n:t._handleErrorZS),this._handleErrorDlgt=n&&(n.onHandleError?t:t._handleErrorDlgt),this._handleErrorCurrZone=n&&(n.onHandleError?this.zone:t._handleErrorCurrZone),this._scheduleTaskZS=n&&(n.onScheduleTask?n:t._scheduleTaskZS),this._scheduleTaskDlgt=n&&(n.onScheduleTask?t:t._scheduleTaskDlgt),this._scheduleTaskCurrZone=n&&(n.onScheduleTask?this.zone:t._scheduleTaskCurrZone),this._invokeTaskZS=n&&(n.onInvokeTask?n:t._invokeTaskZS),this._invokeTaskDlgt=n&&(n.onInvokeTask?t:t._invokeTaskDlgt),this._invokeTaskCurrZone=n&&(n.onInvokeTask?this.zone:t._invokeTaskCurrZone),this._cancelTaskZS=n&&(n.onCancelTask?n:t._cancelTaskZS),this._cancelTaskDlgt=n&&(n.onCancelTask?t:t._cancelTaskDlgt),this._cancelTaskCurrZone=n&&(n.onCancelTask?this.zone:t._cancelTaskCurrZone),this._hasTaskZS=null,this._hasTaskDlgt=null,this._hasTaskDlgtOwner=null,this._hasTaskCurrZone=null;const o=n&&n.onHasTask;(o||t&&t._hasTaskZS)&&(this._hasTaskZS=o?n:c,this._hasTaskDlgt=t,this._hasTaskDlgtOwner=this,this._hasTaskCurrZone=e,n.onScheduleTask||(this._scheduleTaskZS=c,this._scheduleTaskDlgt=t,this._scheduleTaskCurrZone=this.zone),n.onInvokeTask||(this._invokeTaskZS=c,this._invokeTaskDlgt=t,this._invokeTaskCurrZone=this.zone),n.onCancelTask||(this._cancelTaskZS=c,this._cancelTaskDlgt=t,this._cancelTaskCurrZone=this.zone))}fork(e,t){return this._forkZS?this._forkZS.onFork(this._forkDlgt,this.zone,e,t):new i(e,t)}intercept(e,t,n){return this._interceptZS?this._interceptZS.onIntercept(this._interceptDlgt,this._interceptCurrZone,e,t,n):t}invoke(e,t,n,o,r){return this._invokeZS?this._invokeZS.onInvoke(this._invokeDlgt,this._invokeCurrZone,e,t,n,o,r):t.apply(n,o)}handleError(e,t){return!this._handleErrorZS||this._handleErrorZS.onHandleError(this._handleErrorDlgt,this._handleErrorCurrZone,e,t)}scheduleTask(e,t){let n=t;if(this._scheduleTaskZS)this._hasTaskZS&&n._zoneDelegates.push(this._hasTaskDlgtOwner),n=this._scheduleTaskZS.onScheduleTask(this._scheduleTaskDlgt,this._scheduleTaskCurrZone,e,t),n||(n=t);else if(t.scheduleFn)t.scheduleFn(t);else{if(t.type!=S)throw new Error("Task is missing scheduleFn.");k(t)}return n}invokeTask(e,t,n,o){return this._invokeTaskZS?this._invokeTaskZS.onInvokeTask(this._invokeTaskDlgt,this._invokeTaskCurrZone,e,t,n,o):t.callback.apply(n,o)}cancelTask(e,t){let n;if(this._cancelTaskZS)n=this._cancelTaskZS.onCancelTask(this._cancelTaskDlgt,this._cancelTaskCurrZone,e,t);else{if(!t.cancelFn)throw Error("Task is not cancelable");n=t.cancelFn(t)}return n}hasTask(e,t){try{this._hasTaskZS&&this._hasTaskZS.onHasTask(this._hasTaskDlgt,this._hasTaskCurrZone,e,t)}catch(n){this.handleError(e,n)}}_updateTaskCount(e,t){const n=this._taskCounts,o=n[e],r=n[e]=o+t;if(r<0)throw new Error("More tasks executed then were scheduled.");0!=o&&0!=r||this.hasTask(this.zone,{microTask:n.microTask>0,macroTask:n.macroTask>0,eventTask:n.eventTask>0,change:e})}}class u{constructor(t,n,o,r,s,a){if(this._zone=null,this.runCount=0,this._zoneDelegates=null,this._state="notScheduled",this.type=t,this.source=n,this.data=r,this.scheduleFn=s,this.cancelFn=a,!o)throw new Error("callback is not defined");this.callback=o;const i=this;this.invoke=t===P&&r&&r.useG?u.invokeTask:function(){return u.invokeTask.call(e,i,this,arguments)}}static invokeTask(e,t,n){e||(e=this),I++;try{return e.runCount++,e.zone.runTask(e,t,n)}finally{1==I&&m(),I--}}get zone(){return this._zone}get state(){return this._state}cancelScheduleRequest(){this._transitionTo(v,T)}_transitionTo(e,t,n){if(this._state!==t&&this._state!==n)throw new Error(`${this.type} '${this.source}': can not transition to '${e}', expecting state '${t}'${n?" or '"+n+"'":""}, was '${this._state}'.`);this._state=e,e==v&&(this._zoneDelegates=null)}toString(){return this.data&&void 0!==this.data.handleId?this.data.handleId.toString():Object.prototype.toString.call(this)}toJSON(){return{type:this.type,state:this.state,source:this.source,zone:this.zone.name,runCount:this.runCount}}}const h=s("setTimeout"),p=s("Promise"),f=s("then");let d,g=[],_=!1;function k(t){if(0===I&&0===g.length)if(d||e[p]&&(d=e[p].resolve(0)),d){let e=d[f];e||(e=d.then),e.call(d,m)}else e[h](m,0);t&&g.push(t)}function m(){if(!_){for(_=!0;g.length;){const t=g;g=[];for(let n=0;nz,onUnhandledError:R,microtaskDrainDone:R,scheduleMicroTask:k,showUncaughtError:()=>!i[s("ignoreConsoleErrorUncaughtError")],patchEventTarget:()=>[],patchOnProperties:R,patchMethod:()=>R,bindArguments:()=>[],patchThen:()=>R,patchMacroTask:()=>R,patchEventPrototype:()=>R,isIEOrEdge:()=>!1,getGlobalObjects:()=>{},ObjectDefineProperty:()=>R,ObjectGetOwnPropertyDescriptor:()=>{},ObjectCreate:()=>{},ArraySlice:()=>[],patchClass:()=>R,wrapWithCurrentZone:()=>R,filterProperties:()=>[],attachOriginToPatched:()=>R,_redefineProperty:()=>R,patchCallbacks:()=>R};let z={parent:null,zone:new i(null,null)},j=null,I=0;function R(){}o("Zone","Zone"),e.Zone=i}("undefined"!=typeof window&&window||"undefined"!=typeof self&&self||global);const o=Object.getOwnPropertyDescriptor,r=Object.defineProperty,s=Object.getPrototypeOf,a=Object.create,i=Array.prototype.slice,c="addEventListener",l="removeEventListener",u=Zone.__symbol__(c),h=Zone.__symbol__(l),p="true",f="false",d=Zone.__symbol__("");function g(e,t){return Zone.current.wrap(e,t)}function _(e,t,n,o,r){return Zone.current.scheduleMacroTask(e,t,n,o,r)}const k=Zone.__symbol__,m="undefined"!=typeof window,y=m?window:void 0,v=m&&y||"object"==typeof self&&self||global,T=[null];function b(e,t){for(let n=e.length-1;n>=0;n--)"function"==typeof e[n]&&(e[n]=g(e[n],t+"_"+n));return e}function E(e){return!e||!1!==e.writable&&!("function"==typeof e.get&&void 0===e.set)}const w="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope,Z=!("nw"in v)&&void 0!==v.process&&"[object process]"==={}.toString.call(v.process),S=!Z&&!w&&!(!m||!y.HTMLElement),D=void 0!==v.process&&"[object process]"==={}.toString.call(v.process)&&!w&&!(!m||!y.HTMLElement),P={},O=function(e){if(!(e=e||v.event))return;let t=P[e.type];t||(t=P[e.type]=k("ON_PROPERTY"+e.type));const n=this||e.target||v,o=n[t];let r;if(S&&n===y&&"error"===e.type){const t=e;r=o&&o.call(this,t.message,t.filename,t.lineno,t.colno,t.error),!0===r&&e.preventDefault()}else r=o&&o.apply(this,arguments),null==r||r||e.preventDefault();return r};function C(e,t,n){let s=o(e,t);if(!s&&n&&o(n,t)&&(s={enumerable:!0,configurable:!0}),!s||!s.configurable)return;const a=k("on"+t+"patched");if(e.hasOwnProperty(a)&&e[a])return;delete s.writable,delete s.value;const i=s.get,c=s.set,l=t.substr(2);let u=P[l];u||(u=P[l]=k("ON_PROPERTY"+l)),s.set=function(t){let n=this;n||e!==v||(n=v),n&&(n[u]&&n.removeEventListener(l,O),c&&c.apply(n,T),"function"==typeof t?(n[u]=t,n.addEventListener(l,O,!1)):n[u]=null)},s.get=function(){let n=this;if(n||e!==v||(n=v),!n)return null;const o=n[u];if(o)return o;if(i){let e=i&&i.call(this);if(e)return s.set.call(this,e),"function"==typeof n.removeAttribute&&n.removeAttribute(t),e}return null},r(e,t,s),e[a]=!0}function z(e,t,n){if(t)for(let o=0;ofunction(t,o){const s=n(t,o);return s.cbIdx>=0&&"function"==typeof o[s.cbIdx]?_(s.name,o[s.cbIdx],s,r):e.apply(t,o)})}function N(e,t){e[k("OriginalDelegate")]=t}let x=!1,L=!1;function A(){try{const e=y.navigator.userAgent;if(-1!==e.indexOf("MSIE ")||-1!==e.indexOf("Trident/"))return!0}catch(e){}return!1}function H(){if(x)return L;x=!0;try{const e=y.navigator.userAgent;-1===e.indexOf("MSIE ")&&-1===e.indexOf("Trident/")&&-1===e.indexOf("Edge/")||(L=!0)}catch(e){}return L}Zone.__load_patch("ZoneAwarePromise",(e,t,n)=>{const o=Object.getOwnPropertyDescriptor,r=Object.defineProperty,s=n.symbol,a=[],i=!0===e[s("DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION")],c=s("Promise"),l=s("then");n.onUnhandledError=e=>{if(n.showUncaughtError()){const t=e&&e.rejection;t?console.error("Unhandled Promise rejection:",t instanceof Error?t.message:t,"; Zone:",e.zone.name,"; Task:",e.task&&e.task.source,"; Value:",t,t instanceof Error?t.stack:void 0):console.error(e)}},n.microtaskDrainDone=()=>{for(;a.length;){const t=a.shift();try{t.zone.runGuarded(()=>{if(t.throwOriginal)throw t.rejection;throw t})}catch(e){h(e)}}};const u=s("unhandledPromiseRejectionHandler");function h(e){n.onUnhandledError(e);try{const n=t[u];"function"==typeof n&&n.call(this,e)}catch(o){}}function p(e){return e&&e.then}function f(e){return e}function d(e){return C.reject(e)}const g=s("state"),_=s("value"),k=s("finally"),m=s("parentPromiseValue"),y=s("parentPromiseState"),v=null,T=!0,b=!1;function E(e,t){return n=>{try{Z(e,t,n)}catch(o){Z(e,!1,o)}}}const w=s("currentTaskTrace");function Z(e,o,s){const c=function(){let e=!1;return function(t){return function(){e||(e=!0,t.apply(null,arguments))}}}();if(e===s)throw new TypeError("Promise resolved with itself");if(e[g]===v){let h=null;try{"object"!=typeof s&&"function"!=typeof s||(h=s&&s.then)}catch(u){return c(()=>{Z(e,!1,u)})(),e}if(o!==b&&s instanceof C&&s.hasOwnProperty(g)&&s.hasOwnProperty(_)&&s[g]!==v)D(s),Z(e,s[g],s[_]);else if(o!==b&&"function"==typeof h)try{h.call(s,c(E(e,o)),c(E(e,!1)))}catch(u){c(()=>{Z(e,!1,u)})()}else{e[g]=o;const c=e[_];if(e[_]=s,e[k]===k&&o===T&&(e[g]=e[y],e[_]=e[m]),o===b&&s instanceof Error){const e=t.currentTask&&t.currentTask.data&&t.currentTask.data.__creationTrace__;e&&r(s,w,{configurable:!0,enumerable:!1,writable:!0,value:e})}for(let t=0;t{try{const o=e[_],r=!!n&&k===n[k];r&&(n[m]=o,n[y]=s);const i=t.run(a,void 0,r&&a!==d&&a!==f?[]:[o]);Z(n,!0,i)}catch(o){Z(n,!1,o)}},n)}const O=function(){};class C{static toString(){return"function ZoneAwarePromise() { [native code] }"}static resolve(e){return Z(new this(null),T,e)}static reject(e){return Z(new this(null),b,e)}static race(e){let t,n,o=new this((e,o)=>{t=e,n=o});function r(e){t(e)}function s(e){n(e)}for(let a of e)p(a)||(a=this.resolve(a)),a.then(r,s);return o}static all(e){return C.allWithCallback(e)}static allSettled(e){return(this&&this.prototype instanceof C?this:C).allWithCallback(e,{thenCallback:e=>({status:"fulfilled",value:e}),errorCallback:e=>({status:"rejected",reason:e})})}static allWithCallback(e,t){let n,o,r=new this((e,t)=>{n=e,o=t}),s=2,a=0;const i=[];for(let l of e){p(l)||(l=this.resolve(l));const e=a;try{l.then(o=>{i[e]=t?t.thenCallback(o):o,s--,0===s&&n(i)},r=>{t?(i[e]=t.errorCallback(r),s--,0===s&&n(i)):o(r)})}catch(c){o(c)}s++,a++}return s-=2,0===s&&n(i),r}constructor(e){const t=this;if(!(t instanceof C))throw new Error("Must be an instanceof Promise.");t[g]=v,t[_]=[];try{e&&e(E(t,T),E(t,b))}catch(n){Z(t,!1,n)}}get[Symbol.toStringTag](){return"Promise"}get[Symbol.species](){return C}then(e,n){let o=this.constructor[Symbol.species];o&&"function"==typeof o||(o=this.constructor||C);const r=new o(O),s=t.current;return this[g]==v?this[_].push(s,r,e,n):P(this,s,r,e,n),r}catch(e){return this.then(null,e)}finally(e){let n=this.constructor[Symbol.species];n&&"function"==typeof n||(n=C);const o=new n(O);o[k]=k;const r=t.current;return this[g]==v?this[_].push(r,o,e,e):P(this,r,o,e,e),o}}C.resolve=C.resolve,C.reject=C.reject,C.race=C.race,C.all=C.all;const z=e[c]=e.Promise;e.Promise=C;const j=s("thenPatched");function I(e){const t=e.prototype,n=o(t,"then");if(n&&(!1===n.writable||!n.configurable))return;const r=t.then;t[l]=r,e.prototype.then=function(e,t){return new C((e,t)=>{r.call(this,e,t)}).then(e,t)},e[j]=!0}return n.patchThen=I,z&&(I(z),R(e,"fetch",e=>{return t=e,function(e,n){let o=t.apply(e,n);if(o instanceof C)return o;let r=o.constructor;return r[j]||I(r),o};var t})),Promise[t.__symbol__("uncaughtPromiseErrors")]=a,C}),Zone.__load_patch("toString",e=>{const t=Function.prototype.toString,n=k("OriginalDelegate"),o=k("Promise"),r=k("Error"),s=function(){if("function"==typeof this){const s=this[n];if(s)return"function"==typeof s?t.call(s):Object.prototype.toString.call(s);if(this===Promise){const n=e[o];if(n)return t.call(n)}if(this===Error){const n=e[r];if(n)return t.call(n)}}return t.call(this)};s[n]=t,Function.prototype.toString=s;const a=Object.prototype.toString;Object.prototype.toString=function(){return"function"==typeof Promise&&this instanceof Promise?"[object Promise]":a.call(this)}});let F=!1;if("undefined"!=typeof window)try{const e=Object.defineProperty({},"passive",{get:function(){F=!0}});window.addEventListener("test",e,e),window.removeEventListener("test",e,e)}catch(de){F=!1}const q={useG:!0},G={},B={},W=new RegExp("^"+d+"(\\w+)(true|false)$"),U=k("propagationStopped");function V(e,t){const n=(t?t(e):e)+f,o=(t?t(e):e)+p,r=d+n,s=d+o;G[e]={},G[e].false=r,G[e].true=s}function $(e,t,n){const o=n&&n.add||c,r=n&&n.rm||l,a=n&&n.listeners||"eventListeners",i=n&&n.rmAll||"removeAllListeners",u=k(o),h="."+o+":",g=function(e,t,n){if(e.isRemoved)return;const o=e.callback;"object"==typeof o&&o.handleEvent&&(e.callback=e=>o.handleEvent(e),e.originalDelegate=o),e.invoke(e,t,[n]);const s=e.options;s&&"object"==typeof s&&s.once&&t[r].call(t,n.type,e.originalDelegate?e.originalDelegate:e.callback,s)},_=function(t){if(!(t=t||e.event))return;const n=this||t.target||e,o=n[G[t.type].false];if(o)if(1===o.length)g(o[0],n,t);else{const e=o.slice();for(let o=0;ofunction(t,n){t[U]=!0,e&&e.apply(t,n)})}function Y(e,t,n,o,r){const s=Zone.__symbol__(o);if(t[s])return;const a=t[s]=t[o];t[o]=function(s,i,c){return i&&i.prototype&&r.forEach(function(t){const r=`${n}.${o}::`+t,s=i.prototype;if(s.hasOwnProperty(t)){const n=e.ObjectGetOwnPropertyDescriptor(s,t);n&&n.value?(n.value=e.wrapWithCurrentZone(n.value,r),e._redefineProperty(i.prototype,t,n)):s[t]&&(s[t]=e.wrapWithCurrentZone(s[t],r))}else s[t]&&(s[t]=e.wrapWithCurrentZone(s[t],r))}),a.call(t,s,i,c)},e.attachOriginToPatched(t[o],a)}const K=["absolutedeviceorientation","afterinput","afterprint","appinstalled","beforeinstallprompt","beforeprint","beforeunload","devicelight","devicemotion","deviceorientation","deviceorientationabsolute","deviceproximity","hashchange","languagechange","message","mozbeforepaint","offline","online","paint","pageshow","pagehide","popstate","rejectionhandled","storage","unhandledrejection","unload","userproximity","vrdisplayconnected","vrdisplaydisconnected","vrdisplaypresentchange"],Q=["encrypted","waitingforkey","msneedkey","mozinterruptbegin","mozinterruptend"],ee=["load"],te=["blur","error","focus","load","resize","scroll","messageerror"],ne=["bounce","finish","start"],oe=["loadstart","progress","abort","error","load","progress","timeout","loadend","readystatechange"],re=["upgradeneeded","complete","abort","success","error","blocked","versionchange","close"],se=["close","error","open","message"],ae=["error","message"],ie=["abort","animationcancel","animationend","animationiteration","auxclick","beforeinput","blur","cancel","canplay","canplaythrough","change","compositionstart","compositionupdate","compositionend","cuechange","click","close","contextmenu","curechange","dblclick","drag","dragend","dragenter","dragexit","dragleave","dragover","drop","durationchange","emptied","ended","error","focus","focusin","focusout","gotpointercapture","input","invalid","keydown","keypress","keyup","load","loadstart","loadeddata","loadedmetadata","lostpointercapture","mousedown","mouseenter","mouseleave","mousemove","mouseout","mouseover","mouseup","mousewheel","orientationchange","pause","play","playing","pointercancel","pointerdown","pointerenter","pointerleave","pointerlockchange","mozpointerlockchange","webkitpointerlockerchange","pointerlockerror","mozpointerlockerror","webkitpointerlockerror","pointermove","pointout","pointerover","pointerup","progress","ratechange","reset","resize","scroll","seeked","seeking","select","selectionchange","selectstart","show","sort","stalled","submit","suspend","timeupdate","volumechange","touchcancel","touchmove","touchstart","touchend","transitioncancel","transitionend","waiting","wheel"].concat(["webglcontextrestored","webglcontextlost","webglcontextcreationerror"],["autocomplete","autocompleteerror"],["toggle"],["afterscriptexecute","beforescriptexecute","DOMContentLoaded","freeze","fullscreenchange","mozfullscreenchange","webkitfullscreenchange","msfullscreenchange","fullscreenerror","mozfullscreenerror","webkitfullscreenerror","msfullscreenerror","readystatechange","visibilitychange","resume"],K,["beforecopy","beforecut","beforepaste","copy","cut","paste","dragstart","loadend","animationstart","search","transitionrun","transitionstart","webkitanimationend","webkitanimationiteration","webkitanimationstart","webkittransitionend"],["activate","afterupdate","ariarequest","beforeactivate","beforedeactivate","beforeeditfocus","beforeupdate","cellchange","controlselect","dataavailable","datasetchanged","datasetcomplete","errorupdate","filterchange","layoutcomplete","losecapture","move","moveend","movestart","propertychange","resizeend","resizestart","rowenter","rowexit","rowsdelete","rowsinserted","command","compassneedscalibration","deactivate","help","mscontentzoom","msmanipulationstatechanged","msgesturechange","msgesturedoubletap","msgestureend","msgesturehold","msgesturestart","msgesturetap","msgotpointercapture","msinertiastart","mslostpointercapture","mspointercancel","mspointerdown","mspointerenter","mspointerhover","mspointerleave","mspointermove","mspointerout","mspointerover","mspointerup","pointerout","mssitemodejumplistitemremoved","msthumbnailclick","stop","storagecommit"]);function ce(e,t,n){if(!n||0===n.length)return t;const o=n.filter(t=>t.target===e);if(!o||0===o.length)return t;const r=o[0].ignoreProperties;return t.filter(e=>-1===r.indexOf(e))}function le(e,t,n,o){e&&z(e,ce(e,t,n),o)}function ue(e,t){if(Z&&!D)return;if(Zone[e.symbol("patchEvents")])return;const n="undefined"!=typeof WebSocket,o=t.__Zone_ignore_on_properties;if(S){const e=window,t=A()?[{target:e,ignoreProperties:["error"]}]:[];le(e,ie.concat(["messageerror"]),o?o.concat(t):o,s(e)),le(Document.prototype,ie,o),void 0!==e.SVGElement&&le(e.SVGElement.prototype,ie,o),le(Element.prototype,ie,o),le(HTMLElement.prototype,ie,o),le(HTMLMediaElement.prototype,Q,o),le(HTMLFrameSetElement.prototype,K.concat(te),o),le(HTMLBodyElement.prototype,K.concat(te),o),le(HTMLFrameElement.prototype,ee,o),le(HTMLIFrameElement.prototype,ee,o);const n=e.HTMLMarqueeElement;n&&le(n.prototype,ne,o);const r=e.Worker;r&&le(r.prototype,ae,o)}const r=t.XMLHttpRequest;r&&le(r.prototype,oe,o);const a=t.XMLHttpRequestEventTarget;a&&le(a&&a.prototype,oe,o),"undefined"!=typeof IDBIndex&&(le(IDBIndex.prototype,re,o),le(IDBRequest.prototype,re,o),le(IDBOpenDBRequest.prototype,re,o),le(IDBDatabase.prototype,re,o),le(IDBTransaction.prototype,re,o),le(IDBCursor.prototype,re,o)),n&&le(WebSocket.prototype,se,o)}Zone.__load_patch("util",(e,t,n)=>{n.patchOnProperties=z,n.patchMethod=R,n.bindArguments=b,n.patchMacroTask=M;const s=t.__symbol__("BLACK_LISTED_EVENTS"),u=t.__symbol__("UNPATCHED_EVENTS");e[u]&&(e[s]=e[u]),e[s]&&(t[s]=t[u]=e[s]),n.patchEventPrototype=J,n.patchEventTarget=$,n.isIEOrEdge=H,n.ObjectDefineProperty=r,n.ObjectGetOwnPropertyDescriptor=o,n.ObjectCreate=a,n.ArraySlice=i,n.patchClass=I,n.wrapWithCurrentZone=g,n.filterProperties=ce,n.attachOriginToPatched=N,n._redefineProperty=Object.defineProperty,n.patchCallbacks=Y,n.getGlobalObjects=()=>({globalSources:B,zoneSymbolEventNames:G,eventNames:ie,isBrowser:S,isMix:D,isNode:Z,TRUE_STR:p,FALSE_STR:f,ZONE_SYMBOL_PREFIX:d,ADD_EVENT_LISTENER_STR:c,REMOVE_EVENT_LISTENER_STR:l})});const he=k("zoneTask");function pe(e,t,n,o){let r=null,s=null;n+=o;const a={};function i(t){const n=t.data;return n.args[0]=function(){return t.invoke.apply(this,arguments)},n.handleId=r.apply(e,n.args),t}function c(t){return s.call(e,t.data.handleId)}r=R(e,t+=o,n=>function(r,s){if("function"==typeof s[0]){const e={isPeriodic:"Interval"===o,delay:"Timeout"===o||"Interval"===o?s[1]||0:void 0,args:s},n=s[0];s[0]=function(){try{return n.apply(this,arguments)}finally{e.isPeriodic||("number"==typeof e.handleId?delete a[e.handleId]:e.handleId&&(e.handleId[he]=null))}};const r=_(t,s[0],e,i,c);if(!r)return r;const l=r.data.handleId;return"number"==typeof l?a[l]=r:l&&(l[he]=r),l&&l.ref&&l.unref&&"function"==typeof l.ref&&"function"==typeof l.unref&&(r.ref=l.ref.bind(l),r.unref=l.unref.bind(l)),"number"==typeof l||l?l:r}return n.apply(e,s)}),s=R(e,n,t=>function(n,o){const r=o[0];let s;"number"==typeof r?s=a[r]:(s=r&&r[he],s||(s=r)),s&&"string"==typeof s.type?"notScheduled"!==s.state&&(s.cancelFn&&s.data.isPeriodic||0===s.runCount)&&("number"==typeof r?delete a[r]:r&&(r[he]=null),s.zone.cancelTask(s)):t.apply(e,o)})}function fe(e,t){if(Zone[t.symbol("patchEventTarget")])return;const{eventNames:n,zoneSymbolEventNames:o,TRUE_STR:r,FALSE_STR:s,ZONE_SYMBOL_PREFIX:a}=t.getGlobalObjects();for(let c=0;c{const t=e[Zone.__symbol__("legacyPatch")];t&&t()}),Zone.__load_patch("queueMicrotask",(e,t,n)=>{n.patchMethod(e,"queueMicrotask",e=>function(e,n){t.current.scheduleMicroTask("queueMicrotask",n[0])})}),Zone.__load_patch("timers",e=>{const t="set",n="clear";pe(e,t,n,"Timeout"),pe(e,t,n,"Interval"),pe(e,t,n,"Immediate")}),Zone.__load_patch("requestAnimationFrame",e=>{pe(e,"request","cancel","AnimationFrame"),pe(e,"mozRequest","mozCancel","AnimationFrame"),pe(e,"webkitRequest","webkitCancel","AnimationFrame")}),Zone.__load_patch("blocking",(e,t)=>{const n=["alert","prompt","confirm"];for(let o=0;ofunction(o,s){return t.current.run(n,e,s,r)})}),Zone.__load_patch("EventTarget",(e,t,n)=>{!function(e,t){t.patchEventPrototype(e,t)}(e,n),fe(e,n);const o=e.XMLHttpRequestEventTarget;o&&o.prototype&&n.patchEventTarget(e,[o.prototype])}),Zone.__load_patch("MutationObserver",(e,t,n)=>{I("MutationObserver"),I("WebKitMutationObserver")}),Zone.__load_patch("IntersectionObserver",(e,t,n)=>{I("IntersectionObserver")}),Zone.__load_patch("FileReader",(e,t,n)=>{I("FileReader")}),Zone.__load_patch("on_property",(e,t,n)=>{ue(n,e)}),Zone.__load_patch("customElements",(e,t,n)=>{!function(e,t){const{isBrowser:n,isMix:o}=t.getGlobalObjects();(n||o)&&e.customElements&&"customElements"in e&&t.patchCallbacks(t,e.customElements,"customElements","define",["connectedCallback","disconnectedCallback","adoptedCallback","attributeChangedCallback"])}(e,n)}),Zone.__load_patch("XHR",(e,t)=>{!function(e){const c=e.XMLHttpRequest;if(!c)return;const l=c.prototype;let p=l[u],f=l[h];if(!p){const t=e.XMLHttpRequestEventTarget;if(t){const e=t.prototype;p=e[u],f=e[h]}}const d="readystatechange",g="scheduled";function m(e){const o=e.data,a=o.target;a[s]=!1,a[i]=!1;const c=a[r];p||(p=a[u],f=a[h]),c&&f.call(a,d,c);const l=a[r]=()=>{if(a.readyState===a.DONE)if(!o.aborted&&a[s]&&e.state===g){const n=a[t.__symbol__("loadfalse")];if(0!==a.status&&n&&n.length>0){const r=e.invoke;e.invoke=function(){const n=a[t.__symbol__("loadfalse")];for(let t=0;tfunction(e,t){return e[o]=0==t[2],e[a]=t[1],T.apply(e,t)}),b=k("fetchTaskAborting"),E=k("fetchTaskScheduling"),w=R(l,"send",()=>function(e,n){if(!0===t.current[E])return w.apply(e,n);if(e[o])return w.apply(e,n);{const t={target:e,url:e[a],isPeriodic:!1,args:n,aborted:!1},o=_("XMLHttpRequest.send",y,t,m,v);e&&!0===e[i]&&!t.aborted&&o.state===g&&o.invoke()}}),Z=R(l,"abort",()=>function(e,o){const r=e[n];if(r&&"string"==typeof r.type){if(null==r.cancelFn||r.data&&r.data.aborted)return;r.zone.cancelTask(r)}else if(!0===t.current[b])return Z.apply(e,o)})}(e);const n=k("xhrTask"),o=k("xhrSync"),r=k("xhrListener"),s=k("xhrScheduled"),a=k("xhrURL"),i=k("xhrErrorBeforeScheduled")}),Zone.__load_patch("geolocation",e=>{e.navigator&&e.navigator.geolocation&&function(e,t){const n=e.constructor.name;for(let r=0;r{const t=function(){return e.apply(this,b(arguments,n+"."+s))};return N(t,e),t})(a)}}}(e.navigator.geolocation,["getCurrentPosition","watchPosition"])}),Zone.__load_patch("PromiseRejectionEvent",(e,t)=>{function n(t){return function(n){X(e,t).forEach(o=>{const r=e.PromiseRejectionEvent;if(r){const e=new r(t,{promise:n.promise,reason:n.rejection});o.invoke(e)}})}}e.PromiseRejectionEvent&&(t[k("unhandledPromiseRejectionHandler")]=n("unhandledrejection"),t[k("rejectionHandledHandler")]=n("rejectionhandled"))})}},[[1,0]]]); --------------------------------------------------------------------------------