├── 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 | [](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 |
26 |
27 |
28 |
29 |
32 |
33 | {{cat}},
34 |
35 |
36 |
37 |
38 |
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 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
38 | |
39 |
40 |
41 | Title:
42 | {{about.title}}
43 |
44 |
45 | Author:
46 | {{about.author}}
47 |
48 |
49 | Description:
50 | {{about.description}}
51 |
52 |
56 | |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
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 |
47 |
--------------------------------------------------------------------------------
/src/assets/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
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 |
9 |
--------------------------------------------------------------------------------
/src/assets/fur-boot-green.svg:
--------------------------------------------------------------------------------
1 |
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 |
7 |
8 |
9 |
10 | |
11 |
12 | Stupid RSS Reader
13 | |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
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 |
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]]]);
--------------------------------------------------------------------------------