├── .dockerignore ├── .editorconfig ├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── vcs.xml └── workspace.xml ├── .travis.yml ├── Dockerfile ├── Makefile ├── README.md ├── build-info.js ├── build.gradle.kts ├── changelog.md ├── chrome.json ├── docker-compose.yml ├── docker ├── loki │ └── loki.yml └── prometheus │ └── prometheus.yml ├── docs ├── js-support.png ├── nginx.conf ├── rssproxy-candidates.png ├── text filter.png └── yahoo pipes.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── packages └── playground │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc.json │ ├── README.md │ ├── angular.json │ ├── browserslist │ ├── build-info.ts │ ├── build.gradle.kts │ ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.json │ ├── karma.conf.js │ ├── package.json │ ├── proxy.conf.json │ ├── src │ ├── ads.txt │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── components │ │ │ ├── convert-format │ │ │ │ ├── convert-format.component.html │ │ │ │ ├── convert-format.component.scss │ │ │ │ ├── convert-format.component.spec.ts │ │ │ │ ├── convert-format.component.ts │ │ │ │ └── convert-format.module.ts │ │ │ ├── dot │ │ │ │ ├── dot.component.html │ │ │ │ ├── dot.component.scss │ │ │ │ ├── dot.component.spec.ts │ │ │ │ ├── dot.component.ts │ │ │ │ └── dot.module.ts │ │ │ ├── export-options │ │ │ │ ├── export-options.component.html │ │ │ │ ├── export-options.component.scss │ │ │ │ ├── export-options.component.spec.ts │ │ │ │ ├── export-options.component.ts │ │ │ │ └── export-options.module.ts │ │ │ ├── feed-url │ │ │ │ ├── feed-url.component.html │ │ │ │ ├── feed-url.component.scss │ │ │ │ ├── feed-url.component.spec.ts │ │ │ │ ├── feed-url.component.ts │ │ │ │ └── feed-url.module.ts │ │ │ ├── feed │ │ │ │ ├── feed.component.html │ │ │ │ ├── feed.component.scss │ │ │ │ ├── feed.component.spec.ts │ │ │ │ ├── feed.component.ts │ │ │ │ └── feed.module.ts │ │ │ ├── footer │ │ │ │ ├── footer.component.html │ │ │ │ ├── footer.component.scss │ │ │ │ ├── footer.component.spec.ts │ │ │ │ ├── footer.component.ts │ │ │ │ └── footer.module.ts │ │ │ ├── generic-feeds │ │ │ │ ├── generic-feeds.component.html │ │ │ │ ├── generic-feeds.component.scss │ │ │ │ ├── generic-feeds.component.spec.ts │ │ │ │ ├── generic-feeds.component.ts │ │ │ │ └── generic-feeds.module.ts │ │ │ ├── header │ │ │ │ ├── header.component.html │ │ │ │ ├── header.component.scss │ │ │ │ ├── header.component.spec.ts │ │ │ │ ├── header.component.ts │ │ │ │ └── header.module.ts │ │ │ ├── help-message │ │ │ │ ├── help-message.component.html │ │ │ │ ├── help-message.component.scss │ │ │ │ ├── help-message.component.spec.ts │ │ │ │ ├── help-message.component.ts │ │ │ │ └── help-message.module.ts │ │ │ ├── native-feeds │ │ │ │ ├── native-feeds.component.html │ │ │ │ ├── native-feeds.component.scss │ │ │ │ ├── native-feeds.component.spec.ts │ │ │ │ ├── native-feeds.component.ts │ │ │ │ └── native-feeds.module.ts │ │ │ ├── native-options │ │ │ │ ├── native-options.component.html │ │ │ │ ├── native-options.component.scss │ │ │ │ ├── native-options.component.spec.ts │ │ │ │ ├── native-options.component.ts │ │ │ │ └── native-options.module.ts │ │ │ ├── options │ │ │ │ ├── options.component.html │ │ │ │ ├── options.component.scss │ │ │ │ ├── options.component.spec.ts │ │ │ │ ├── options.component.ts │ │ │ │ └── options.module.ts │ │ │ ├── playground-stateless │ │ │ │ ├── playground-stateless.component.html │ │ │ │ ├── playground-stateless.component.scss │ │ │ │ ├── playground-stateless.component.spec.ts │ │ │ │ ├── playground-stateless.component.ts │ │ │ │ └── playground-stateless.module.ts │ │ │ ├── playground │ │ │ │ ├── playground.component.html │ │ │ │ ├── playground.component.scss │ │ │ │ ├── playground.component.spec.ts │ │ │ │ ├── playground.component.ts │ │ │ │ └── playground.module.ts │ │ │ ├── prerender │ │ │ │ ├── prerender.component.html │ │ │ │ ├── prerender.component.scss │ │ │ │ ├── prerender.component.spec.ts │ │ │ │ ├── prerender.component.ts │ │ │ │ └── prerender.module.ts │ │ │ ├── publish-feed │ │ │ │ ├── publish-feed.component.html │ │ │ │ ├── publish-feed.component.scss │ │ │ │ ├── publish-feed.component.spec.ts │ │ │ │ ├── publish-feed.component.ts │ │ │ │ └── publish-feed.module.ts │ │ │ ├── push-options │ │ │ │ ├── push-options.component.html │ │ │ │ ├── push-options.component.scss │ │ │ │ ├── push-options.component.spec.ts │ │ │ │ ├── push-options.component.ts │ │ │ │ └── push-options.module.ts │ │ │ ├── push │ │ │ │ ├── push-as-email │ │ │ │ │ ├── push-as-email.component.html │ │ │ │ │ ├── push-as-email.component.scss │ │ │ │ │ ├── push-as-email.component.spec.ts │ │ │ │ │ ├── push-as-email.component.ts │ │ │ │ │ └── push-as-email.module.ts │ │ │ │ ├── push-as-webhook │ │ │ │ │ ├── push-as-webhook.component.html │ │ │ │ │ ├── push-as-webhook.component.scss │ │ │ │ │ ├── push-as-webhook.component.spec.ts │ │ │ │ │ ├── push-as-webhook.component.ts │ │ │ │ │ └── push-as-webhook.module.ts │ │ │ │ ├── push-to-mobile │ │ │ │ │ ├── push-to-mobile.component.html │ │ │ │ │ ├── push-to-mobile.component.scss │ │ │ │ │ ├── push-to-mobile.component.spec.ts │ │ │ │ │ ├── push-to-mobile.component.ts │ │ │ │ │ └── push-to-mobile.module.ts │ │ │ │ ├── push-to-web │ │ │ │ │ ├── push-to-web.component.html │ │ │ │ │ ├── push-to-web.component.scss │ │ │ │ │ ├── push-to-web.component.spec.ts │ │ │ │ │ ├── push-to-web.component.ts │ │ │ │ │ └── push-to-web.module.ts │ │ │ │ └── push-updates │ │ │ │ │ ├── push-updates.component.html │ │ │ │ │ ├── push-updates.component.scss │ │ │ │ │ ├── push-updates.component.spec.ts │ │ │ │ │ ├── push-updates.component.ts │ │ │ │ │ └── push-updates.module.ts │ │ │ ├── refine-feed │ │ │ │ ├── refine-feed.component.html │ │ │ │ ├── refine-feed.component.scss │ │ │ │ ├── refine-feed.component.spec.ts │ │ │ │ ├── refine-feed.component.ts │ │ │ │ └── refine-feed.module.ts │ │ │ ├── search │ │ │ │ ├── search.component.html │ │ │ │ ├── search.component.scss │ │ │ │ ├── search.component.spec.ts │ │ │ │ ├── search.component.ts │ │ │ │ └── search.module.ts │ │ │ ├── section │ │ │ │ ├── section.component.html │ │ │ │ ├── section.component.scss │ │ │ │ ├── section.component.spec.ts │ │ │ │ ├── section.component.ts │ │ │ │ └── section.module.ts │ │ │ ├── spinner │ │ │ │ ├── spinner.component.html │ │ │ │ ├── spinner.component.scss │ │ │ │ ├── spinner.component.spec.ts │ │ │ │ ├── spinner.component.ts │ │ │ │ └── spinner.module.ts │ │ │ └── watch-page-change │ │ │ │ ├── watch-page-change.component.html │ │ │ │ ├── watch-page-change.component.scss │ │ │ │ ├── watch-page-change.component.spec.ts │ │ │ │ ├── watch-page-change.component.ts │ │ │ │ └── watch-page-change.module.ts │ │ ├── services │ │ │ ├── app-settings.service.spec.ts │ │ │ ├── app-settings.service.ts │ │ │ ├── feed.service.spec.ts │ │ │ └── feed.service.ts │ │ └── wizard.component.ts │ ├── assets │ │ ├── .gitkeep │ │ ├── arrow.png │ │ ├── browser.png │ │ ├── rss_icon.png │ │ ├── rssproxy.png │ │ └── x.svg │ ├── environments │ │ ├── build.ts │ │ ├── environment.prod.ts │ │ ├── environment.ts │ │ └── rss-proxy-environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── robots.txt │ ├── sitemap.xml │ ├── styles.scss │ ├── test.ts │ └── variables.scss │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── tslint.json │ └── yarn.lock ├── settings.gradle.kts ├── tasks └── tokenSecret.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | .idea 2 | docs 3 | 4 | packages/playground/node_modules 5 | packages/playground/.angular 6 | packages/playground/.gradle 7 | packages/playground/e2e 8 | packages/playground/src 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | dist 4 | .build-cache 5 | .idea 6 | .gradle 7 | 8 | # Ignore Gradle build output directory 9 | build 10 | *.iml 11 | 12 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 16 4 | 5 | addons: 6 | apt: 7 | sources: 8 | - google-chrome 9 | packages: 10 | - google-chrome-stable 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM damoeb/feedless:core-0.1.3 2 | ENV APP_AUTHENTICATION=authRoot \ 3 | APP_JWT_SECRET=password \ 4 | APP_HOST_URL=http://localhost:8080 \ 5 | APP_ACTUATOR_PASSWORD=password \ 6 | APP_TIMEZONE=Europe/Berlin \ 7 | APP_LOG_LEVEL=error \ 8 | APP_ACTIVE_PROFILES="legacy,cache,static" \ 9 | APP_WHITELISTED_HOSTS="" \ 10 | AUTH_TOKEN_ANONYMOUS_VALIDFORDAYS=3 \ 11 | APP_ROOT_EMAIL=admin@localhost \ 12 | APP_ROOT_SECRET_KEY=password 13 | COPY packages/playground/dist/ ./public 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: install 2 | cd packages/playground && yarn testCi 3 | 4 | install: 5 | cd packages/playground && yarn --frozen-lockfile -s 6 | 7 | buildDockerImage: 8 | ./gradlew buildDockerImage 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RSS-proxy 2 | 3 | [![Build Status](https://app.travis-ci.com/damoeb/rss-proxy.svg?branch=master)](https://app.travis-ci.com/damoeb/rss-proxy) 4 | 5 | RSS-proxy allows you to do create an ATOM or JSON feed of any static website or feeds (web to feed), 6 | just by analyzing just the HTML structure. [Try the demo](https://rssproxy.migor.org). It is an alternative UI to [feedless](https://github.com/damoeb/feedless) with a reduced feature set. 7 | If you want advanced features like fulltext feeds, aggregation, persistence, authentication and others, checkout [feedless](https://github.com/damoeb/feedless/blob/master/docs/third-party-migration.md) 8 | 9 | ![Playground](https://github.com/damoeb/rss-proxy/raw/master/docs/rssproxy-candidates.png "Playground") 10 | 11 | ## Features 12 | - Web to Feed 13 | - Feed to Feed: pipe existing native feeds through `rss-proxy` to filter them 14 | - [Filters](https://github.com/damoeb/feedless/blob/master/docs/filters.md) 15 | - Self Hosting 16 | 17 | ## Advanced Features 18 | If you look for features below, you have to use [feedless](https://github.com/damoeb/feedless), the successor of `rss-proxy` 19 | - Feed Aggregation 20 | - Authentication and multi-tenancy 21 | - JavaScript Support (prerendering) 22 | - Fulltext Feeds and other content enrichments 23 | - Persistence 24 | - CLI 25 | - GraphQL API 26 | - Plugins 27 | 28 | # Changelog 29 | See [here](./changelog.md) 30 | 31 | ## Quickstart installation using docker 32 | If you have [docker](https://docs.docker.com/install/) or [podman](https://podman.io/getting-started/installation) installed, do this 33 | 34 | ``` 35 | docker pull damoeb/rss-proxy:2.1 36 | docker run -p 8080:8080 -e APP_API_GATEWAY_URL=https://foo.bar -it damoeb/rss-proxy:2.1 37 | ``` 38 | 39 | `APP_API_GATEWAY_URL` is your outfacing url, which will be used as host for feeds you create. 40 | 41 | Then open [localhost:8080](http://localhost:8080) in the browser. 42 | 43 | ## Understanding Docker requirements 44 | For RSS-Proxy to work with your Docker environment, you must ensure : 45 | - that you expose port 8080 on the container 46 | - that the rss-proxy container share a common network with the RSS reader app you are (probably self-hosting). The Docker `bridge` network is native to Docker in order to allow you to connect two containers on the same network. 47 | - then you need to personalize the `APP_API_GATEWAY_URL` : it must point to the IP of rss-proxy on the shared network selected and the container opened port (e.g : 192.172.0.3:8080). If you have a your rss-proxy runs under a named address you can simply point to that named address 48 | 49 | ## Legacy Version 1 50 | If you are interested in running the first prototype, this is how you do it. 51 | 52 | ``` 53 | docker pull damoeb/rss-proxy:1 54 | docker run -p 3000:3000 -it damoeb/rss-proxy:1 55 | ``` 56 | 57 | Then open [localhost:3000](http://localhost:3000) in the browser. 58 | 59 | ## License 60 | 61 | This project uses the following license: [GNU GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html). 62 | -------------------------------------------------------------------------------- /build-info.js: -------------------------------------------------------------------------------- 1 | const { writeFileSync } = require('fs'); 2 | 3 | // based on git.version.ts from https://stackoverflow.com/a/42199863 4 | 5 | const { exec } = require('child_process'); 6 | 7 | async function createVersionsFile(filename) { 8 | exec('git rev-parse --short HEAD', (error, stdout, stderr) => { 9 | if (error) { 10 | console.error(error); 11 | return; 12 | } 13 | 14 | const revision = stdout.toString().trim(); 15 | 16 | exec('git rev-parse --abbrev-ref HEAD', (error, stdout, stderr) => { 17 | if (error) { 18 | console.error(error); 19 | return; 20 | } 21 | 22 | const branch = stdout.toString().trim(); 23 | 24 | console.log(`version: '${process.env.npm_package_version}', revision: '${revision}', branch: '${branch}'`); 25 | 26 | const content = `{ 27 | "version": "${process.env.npm_package_version}", 28 | "revision": "${revision}", 29 | "date": "${new Date().getTime()}" 30 | }`; 31 | 32 | writeFileSync(filename, content, {encoding: 'utf8'}); 33 | 34 | }); 35 | 36 | }); 37 | } 38 | 39 | createVersionsFile(process.argv[2]); 40 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | dependencies { 6 | classpath ("com.github.node-gradle:gradle-node-plugin:${findProperty("gradleNodePluginVersion")}") 7 | } 8 | } 9 | 10 | plugins { 11 | id("org.ajoberstar.grgit") version "4.1.0" 12 | } 13 | 14 | tasks.register("buildDockerImage", Exec::class) { 15 | dependsOn(tasks.findByPath("packages:playground:build")) 16 | val majorMinorPatch = findProperty("proxyVersion") as String 17 | val parts = majorMinorPatch.split(".") 18 | val major = parts[0] 19 | val majorMinor = parts.slice(0..1).joinToString(".") 20 | 21 | val imageName = findProperty("dockerImageTag") as String 22 | val gitHash = grgit.head().abbreviatedId 23 | 24 | // see https://github.com/docker-library/official-images#multiple-architectures 25 | // install plarforms https://stackoverflow.com/a/60667468/807017 26 | // docker buildx ls 27 | // commandLine("docker", "buildx", "build", 28 | commandLine("docker", "build", 29 | // "--platform=linux/amd64", 30 | // "--platform=arm64v8", 31 | "-t", "${imageName}:${majorMinorPatch}", 32 | "-t", "${imageName}:${majorMinor}", 33 | "-t", "${imageName}:${major}", 34 | "-t", imageName, 35 | ".") 36 | } 37 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | # 2.1.0 - 25.05.2023 4 | - Migrated to [feedless-core-0.1.1](./Dockerfile) 5 | - All endpoints changed from `/api/` to `/api/legacy` 6 | - Maintenance messages can be opted-in by passing `debug=true` in the url 7 | - No tokens are used anymore, just throttling, caching and [host protection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) 8 | 9 | ## 2.0.0 - 03.06.2022 10 | - Full rewrite in kotlin 11 | 12 | ## 1.0.3 - 16.01.2021 13 | - Remove using ids in xpaths, cause they are not abstract enough 14 | - Bugfixes 15 | 16 | ## 1.0.2 - 09.01.2021 17 | - Merging rules and picking the right link 18 | 19 | ## 1.0.1 - 04.01.2021 20 | - Removes proxy endpoint 21 | - Adds support for dynamic websites (JavaScript) 22 | 23 | ## 1.0.0 - 04.01.2021 24 | - Improves UI to visualize feed output 25 | - Uses XPaths instead of CSS Selectors to identify feeds 26 | - Hardened API 27 | - Renders parsing errors in a valid feed 28 | - Bugfixes 29 | - Adds Retry-After Header 30 | 31 | ## 0.4.1 32 | - Allow short titles 33 | 34 | ## 0.4.0 35 | - Cache layer using memcache 36 | 37 | ## 0.3.0 38 | - analytics - [5617576](https://github.com/damoeb/rss-proxy/commit/5617576d80a69f0b5a0d5e69f4dd6d8bc7b06908) 39 | - Correct urls pointing to old repo - [99600d1](https://github.com/damoeb/rss-proxy/commit/99600d1d944df7160cea48adc6bcf4aa6943d138) 40 | - Bugfixes - [3f43d4a](https://github.com/damoeb/rss-proxy/commit/3f43d4a25749da476d4683bc3560e0a88fb06b24) 41 | 42 | ## 0.2.0 43 | - Using pm2 to keep node server running after crash - [5138d25](https://github.com/damoeb/rss-proxy/commit/5138d25667934f28991cd339b3816ec1078dec3d) 44 | - Simplify development by building the core-package automatically 45 | - travis-ci build 46 | 47 | ### 0.1.0 48 | - Working version 49 | 50 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | # envs https://medium.com/softonic-eng/docker-compose-from-development-to-production-88000124a57c 4 | 5 | services: 6 | 7 | rich-puppeteer: 8 | image: damoeb/rich-rss:puppeteer-0.1 9 | restart: always 10 | security_opt: 11 | - seccomp=chrome.json 12 | networks: 13 | - puppeteer 14 | 15 | rss-proxy: 16 | image: damoeb/rss-proxy:2.0.0-beta 17 | restart: always 18 | depends_on: 19 | - rich-puppeteer 20 | ports: 21 | - "8080:8080" 22 | environment: 23 | - LOG_LEVEL=info 24 | # -- CHANGE THIS -- 25 | - APP_PUBLIC_URL=http://localhost:8080 26 | - TOKEN_SECRET=1234_top_secret 27 | - PUPPETEER_HOST=http://rich-puppeteer:3000 28 | networks: 29 | - puppeteer 30 | 31 | networks: 32 | puppeteer: 33 | driver: bridge 34 | -------------------------------------------------------------------------------- /docker/loki/loki.yml: -------------------------------------------------------------------------------- 1 | auth_enabled: false 2 | 3 | server: 4 | http_listen_port: 3100 5 | grpc_listen_port: 9096 6 | 7 | common: 8 | path_prefix: /tmp/loki 9 | storage: 10 | filesystem: 11 | chunks_directory: /tmp/loki/chunks 12 | rules_directory: /tmp/loki/rules 13 | replication_factor: 1 14 | ring: 15 | instance_addr: 127.0.0.1 16 | kvstore: 17 | store: inmemory 18 | 19 | schema_config: 20 | configs: 21 | - from: 2020-10-24 22 | store: boltdb-shipper 23 | object_store: filesystem 24 | schema: v11 25 | index: 26 | prefix: index_ 27 | period: 24h 28 | 29 | ruler: 30 | alertmanager_url: http://localhost:9093 31 | 32 | # By default, Loki will send anonymous, but uniquely-identifiable usage and configuration 33 | # analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/ 34 | # 35 | # Statistics help us better understand how Loki is used, and they show us performance 36 | # levels for most users. This helps us prioritize features and documentation. 37 | # For more information on what's sent, look at 38 | # https://github.com/grafana/loki/blob/main/pkg/usagestats/stats.go 39 | # Refer to the buildReport method to see what goes into a report. 40 | # 41 | # If you would like to disable reporting, uncomment the following lines: 42 | #analytics: 43 | # reporting_enabled: false 44 | -------------------------------------------------------------------------------- /docker/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | # my global config 2 | global: 3 | scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. 4 | evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. 5 | # scrape_timeout is set to the global default (10s). 6 | 7 | # Load rules once and periodically evaluate them according to the global 'evaluation_interval'. 8 | rule_files: 9 | # - "first_rules.yml" 10 | # - "second_rules.yml" 11 | 12 | # A scrape configuration containing exactly one endpoint to scrape: 13 | # Here it's Prometheus itself. 14 | scrape_configs: 15 | # The job name is added as a label `job=` to any timeseries scraped from this config. 16 | # - job_name: 'prometheus' 17 | # # metrics_path defaults to '/metrics' 18 | # # scheme defaults to 'http'. 19 | # static_configs: 20 | # - targets: ['127.0.0.1:9090'] 21 | 22 | - job_name: 'spring-actuator' 23 | metrics_path: '/actuator/prometheus' 24 | scrape_interval: 5s 25 | static_configs: 26 | - targets: ['rss-proxy:8080'] 27 | # basic_auth: 28 | # username: prometheus 29 | # password: prometheus 30 | -------------------------------------------------------------------------------- /docs/js-support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/docs/js-support.png -------------------------------------------------------------------------------- /docs/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | server_name rssproxy.migor.org; 3 | 4 | location / { 5 | proxy_pass http://localhost:9000/; 6 | proxy_buffering off; 7 | proxy_cache_bypass $http_upgrade; 8 | 9 | # Proxy headers 10 | proxy_set_header Upgrade $http_upgrade; 11 | proxy_set_header Connection "upgrade"; 12 | proxy_set_header Host $host; 13 | proxy_set_header X-Real-IP $remote_addr; 14 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 15 | proxy_set_header X-Forwarded-Proto $scheme; 16 | proxy_set_header X-Forwarded-Host $host; 17 | proxy_set_header X-Forwarded-Port $server_port; 18 | 19 | # Proxy timeouts 20 | proxy_connect_timeout 60s; 21 | proxy_send_timeout 60s; 22 | proxy_read_timeout 60s; 23 | } 24 | 25 | # ssl ... 26 | 27 | } 28 | -------------------------------------------------------------------------------- /docs/rssproxy-candidates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/docs/rssproxy-candidates.png -------------------------------------------------------------------------------- /docs/text filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/docs/text filter.png -------------------------------------------------------------------------------- /docs/yahoo pipes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/docs/yahoo pipes.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | nodejsVersion=16 2 | gradleNodePluginVersion=3.1.0 3 | dockerImageTag=damoeb/rss-proxy 4 | proxyVersion=2.1.1 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /packages/playground/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | *.tgz 22 | .c9/ 23 | *.launch 24 | test 25 | .settings/ 26 | *.sublime-workspace 27 | 28 | # IDE - VSCode 29 | .vscode/* 30 | !.vscode/settings.json 31 | !.vscode/tasks.json 32 | !.vscode/launch.json 33 | !.vscode/extensions.json 34 | .history/* 35 | 36 | # misc 37 | /.sass-cache 38 | /connect.lock 39 | /coverage 40 | /libpeerconnection.log 41 | npm-debug.log 42 | yarn-error.log 43 | testem.log 44 | /typings 45 | 46 | # System Files 47 | .DS_Store 48 | Thumbs.db 49 | 50 | .angular 51 | -------------------------------------------------------------------------------- /packages/playground/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | .angular 3 | node_modules 4 | -------------------------------------------------------------------------------- /packages/playground/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": true, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /packages/playground/README.md: -------------------------------------------------------------------------------- 1 | ## Playground 2 | 3 | _Playground_ is the management module to configure the options of the feed generation _core_ module. It is written in angular. 4 | 5 | # Development 6 | 7 | ``` 8 | npm run install 9 | npm run start 10 | ``` 11 | -------------------------------------------------------------------------------- /packages/playground/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "playground": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "aot": false, 22 | "sourceMap": true, 23 | "progress": false, 24 | "buildOptimizer": false, 25 | "assets": [ 26 | "src/favicon.ico", 27 | "src/robots.txt", 28 | "src/ads.txt", 29 | "src/sitemap.xml", 30 | "src/assets" 31 | ], 32 | "styles": ["src/styles.scss"], 33 | "scripts": [] 34 | }, 35 | "configurations": { 36 | "production": { 37 | "fileReplacements": [ 38 | { 39 | "replace": "src/environments/environment.ts", 40 | "with": "src/environments/environment.prod.ts" 41 | } 42 | ], 43 | "optimization": true, 44 | "outputHashing": "all", 45 | "sourceMap": false, 46 | "namedChunks": false, 47 | "aot": true, 48 | "extractLicenses": true, 49 | "vendorChunk": false, 50 | "buildOptimizer": true, 51 | "budgets": [ 52 | { 53 | "type": "initial", 54 | "maximumWarning": "2mb", 55 | "maximumError": "5mb" 56 | }, 57 | { 58 | "type": "anyComponentStyle", 59 | "maximumWarning": "6kb", 60 | "maximumError": "10kb" 61 | } 62 | ] 63 | } 64 | } 65 | }, 66 | "serve": { 67 | "builder": "@angular-devkit/build-angular:dev-server", 68 | "options": { 69 | "browserTarget": "playground:build" 70 | }, 71 | "configurations": { 72 | "production": { 73 | "browserTarget": "playground:build:production" 74 | } 75 | } 76 | }, 77 | "extract-i18n": { 78 | "builder": "@angular-devkit/build-angular:extract-i18n", 79 | "options": { 80 | "browserTarget": "playground:build" 81 | } 82 | }, 83 | "test": { 84 | "builder": "@angular-devkit/build-angular:karma", 85 | "options": { 86 | "main": "src/test.ts", 87 | "polyfills": "src/polyfills.ts", 88 | "tsConfig": "tsconfig.spec.json", 89 | "karmaConfig": "karma.conf.js", 90 | "styles": [], 91 | "scripts": [], 92 | "assets": [ 93 | { 94 | "glob": "favicon.ico", 95 | "input": "src/", 96 | "output": "/" 97 | }, 98 | { 99 | "glob": "**/*", 100 | "input": "src/assets", 101 | "output": "/assets" 102 | } 103 | ] 104 | }, 105 | "configurations": { 106 | "ci": { 107 | "progress": false, 108 | "watch": false, 109 | "browsers": "ChromeHeadlessDocker", 110 | "fileReplacements": [ 111 | { 112 | "replace": "src/environments/environment.ts", 113 | "with": "src/environments/environment.prod.ts" 114 | } 115 | ] 116 | } 117 | } 118 | }, 119 | "e2e": { 120 | "builder": "@angular-devkit/build-angular:protractor", 121 | "options": { 122 | "protractorConfig": "e2e/protractor.conf.js", 123 | "devServerTarget": "playground:serve" 124 | }, 125 | "configurations": { 126 | "production": { 127 | "devServerTarget": "playground:serve:production" 128 | } 129 | } 130 | } 131 | } 132 | } 133 | }, 134 | "defaultProject": "playground" 135 | } 136 | -------------------------------------------------------------------------------- /packages/playground/browserslist: -------------------------------------------------------------------------------- 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 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 Chrome versions 10 | last 2 Firefox versions 11 | last 2 Safari major versions 12 | last 2 Edge major versions 13 | 14 | not ios_saf 15.2-15.3 15 | not safari 15.2-15.3 16 | 17 | not IE 9-11 # For IE 9-11 support, remove 'not'. 18 | -------------------------------------------------------------------------------- /packages/playground/build-info.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'fs'; 2 | import { dedent } from 'tslint/lib/utils'; 3 | 4 | // based on git.version.ts from https://stackoverflow.com/a/42199863 5 | 6 | const util = require('util'); 7 | const exec = util.promisify(require('child_process').exec); 8 | 9 | async function createVersionsFile(filename: string) { 10 | const revision = (await exec('git rev-parse --short HEAD')).stdout 11 | .toString() 12 | .trim(); 13 | const branch = (await exec('git rev-parse --abbrev-ref HEAD')).stdout 14 | .toString() 15 | .trim(); 16 | 17 | console.log( 18 | `version: '${process.env.npm_package_version}', revision: '${revision}', branch: '${branch}'`, 19 | ); 20 | 21 | const content = dedent` 22 | export const build = { 23 | version: '${process.env.npm_package_version}', 24 | revision: '${revision}', 25 | date: '${new Date().getTime()}' 26 | };`; 27 | 28 | writeFileSync(filename, content, { encoding: 'utf8' }); 29 | } 30 | 31 | createVersionsFile('src/environments/build.ts'); 32 | -------------------------------------------------------------------------------- /packages/playground/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.gradle.node.yarn.task.YarnTask 2 | 3 | plugins { 4 | id ("com.github.node-gradle.node") 5 | } 6 | 7 | 8 | // https://github.com/node-gradle/gradle-node-plugin/tree/master/examples/simple-node 9 | // https://github.com/node-gradle/gradle-node-plugin/blob/master/src/test/resources/fixtures/kotlin/build.gradle.kts 10 | node { 11 | val nodejsVersion = findProperty("nodejsVersion") as String 12 | version.set(nodejsVersion) 13 | npmVersion.set("") 14 | yarnVersion.set("") 15 | download.set(false) 16 | } 17 | 18 | val yarnInstallTask = tasks.register("yarnInstall") { 19 | args.set(listOf("install", "--frozen-lockfile", "--ignore-scripts")) 20 | inputs.files("yarn.lock") 21 | outputs.dir("node_modules") 22 | } 23 | 24 | val lintTask = tasks.register("lint") { 25 | args.set(listOf("lint")) 26 | dependsOn(yarnInstallTask) 27 | inputs.dir("src") 28 | inputs.files("yarn.lock") 29 | outputs.upToDateWhen { true } 30 | } 31 | 32 | val testTask = tasks.register("test") { 33 | args.set(listOf("testCi")) 34 | dependsOn(yarnInstallTask) 35 | inputs.dir("src") 36 | inputs.files("yarn.lock") 37 | outputs.upToDateWhen { true } 38 | } 39 | 40 | val fixVersionTask = tasks.register("fixPackageVersion", Exec::class) { 41 | val proxyVersion = findProperty("proxyVersion") as String 42 | commandLine("yarn version --new-version $proxyVersion".split(" ")) 43 | } 44 | 45 | val buildTask = tasks.register("build") { 46 | args.set(listOf("build")) 47 | dependsOn(yarnInstallTask, lintTask, testTask, fixVersionTask) 48 | inputs.dir(project.fileTree("src").exclude("**/*.spec.ts")) 49 | inputs.dir("node_modules") 50 | inputs.files("yarn.lock", "tsconfig.json", "tsconfig.build.json") 51 | outputs.dir("dist") 52 | } 53 | 54 | tasks.register("start") { 55 | args.set(listOf("start:dev")) 56 | dependsOn(yarnInstallTask) 57 | } 58 | -------------------------------------------------------------------------------- /packages/playground/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 } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: ['./src/**/*.e2e-spec.ts'], 13 | capabilities: { 14 | browserName: 'chrome', 15 | }, 16 | directConnect: true, 17 | baseUrl: 'http://localhost:4200/', 18 | framework: 'jasmine', 19 | jasmineNodeOpts: { 20 | showColors: true, 21 | defaultTimeoutInterval: 30000, 22 | print: function () {}, 23 | }, 24 | onPrepare() { 25 | require('ts-node').register({ 26 | project: require('path').join(__dirname, './tsconfig.json'), 27 | }); 28 | jasmine 29 | .getEnv() 30 | .addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /packages/playground/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 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', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('rss-proxy 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( 20 | jasmine.objectContaining({ 21 | level: logging.Level.SEVERE, 22 | } as logging.Entry), 23 | ); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/playground/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element( 10 | by.css('app-root .content span'), 11 | ).getText() as Promise; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/playground/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": ["jasmine", "jasminewd2", "node"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/playground/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma'), 14 | ], 15 | client: { 16 | clearContext: false, // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | customLaunchers: { 19 | ChromeHeadlessDocker: { 20 | base: 'ChromeHeadless', 21 | flags: ['--no-sandbox', '--disable-setuid-sandbox'], 22 | }, 23 | }, 24 | coverageIstanbulReporter: { 25 | dir: require('path').join(__dirname, './coverage/rss-it'), 26 | reports: ['html', 'lcovonly', 'text-summary'], 27 | fixWebpackSourcePaths: true, 28 | }, 29 | reporters: ['progress', 'kjhtml'], 30 | port: 9876, 31 | colors: true, 32 | logLevel: config.LOG_INFO, 33 | autoWatch: true, 34 | browsers: ['Chrome'], 35 | singleRun: false, 36 | restartOnFileChange: true, 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /packages/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rss-proxy/playground", 3 | "version": "2.1.1", 4 | "scripts": { 5 | "ng": "ng", 6 | "prestart": "npm run gen-build-info", 7 | "start": "ng serve --proxy-config proxy.conf.json --watch=true", 8 | "gen-build-info": "ts-node -O '{\"module\": \"commonjs\"}' build-info.ts", 9 | "prebuild": "npm run gen-build-info", 10 | "pretest": "npm run gen-build-info", 11 | "build": "ng build", 12 | "build:prod": "ng build --configuration production", 13 | "test": "ng test", 14 | "testCi": "#ng test --configuration=ci", 15 | "lint": "prettier -w .", 16 | "e2e": "ng e2e", 17 | "clean": "rm -rf dist" 18 | }, 19 | "private": true, 20 | "dependencies": { 21 | "@angular/common": "~13.1.0", 22 | "@angular/core": "~13.1.0", 23 | "@angular/forms": "~13.1.0", 24 | "@angular/platform-browser": "~13.1.0", 25 | "@angular/platform-browser-dynamic": "~13.1.0", 26 | "@angular/router": "~13.1.0", 27 | "bulma": "^0.9.4", 28 | "lodash": "^4.17.21", 29 | "rxjs": "~7.5.5", 30 | "toastr": "^2.1.4", 31 | "tslib": "^2.4.0", 32 | "urijs": "^1.19.11", 33 | "zone.js": "~0.11.5" 34 | }, 35 | "engines": { 36 | "node": ">=16" 37 | }, 38 | "devDependencies": { 39 | "@angular-devkit/build-angular": "~13.1.0", 40 | "@angular/cli": "~13.1.0", 41 | "@angular/compiler": "~13.1.0", 42 | "@angular/compiler-cli": "~13.1.0", 43 | "@angular/language-service": "~13.1.0", 44 | "@types/jasmine": "~4.0.3", 45 | "@types/jasminewd2": "~2.0.10", 46 | "@types/lodash": "^4.14.182", 47 | "@types/toastr": "^2.1.39", 48 | "@types/urijs": "^1.19.19", 49 | "jasmine-core": "~4.1.1", 50 | "jasmine-spec-reporter": "~7.0.0", 51 | "karma": "^6.3.20", 52 | "karma-chrome-launcher": "~3.1.1", 53 | "karma-coverage-istanbul-reporter": "~3.0.3", 54 | "karma-jasmine": "~5.0.1", 55 | "karma-jasmine-html-reporter": "^1.7.0", 56 | "ng-packagr": "^13.3.1", 57 | "prettier": "~2.6.2", 58 | "protractor": "^7.0.0", 59 | "ts-node": "~10.7.0", 60 | "tsickle": "^0.46.0", 61 | "tslint": "~6.1.3", 62 | "typescript": "~4.5.5" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/playground/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": { 3 | "target": "http://localhost:8080", 4 | "secure": false, 5 | "logLevel": "debug", 6 | "pathRewrite": { 7 | "^/api": "/api" 8 | } 9 | }, 10 | "/assets/*": { 11 | "target": "http://localhost:8080", 12 | "secure": false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/playground/src/ads.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/ads.txt -------------------------------------------------------------------------------- /packages/playground/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { PlaygroundStatelessComponent } from './components/playground-stateless/playground-stateless.component'; 4 | 5 | const routes: Routes = [ 6 | // { path: '', canActivate: [AuthService], component: PlaygroundComponent }, 7 | { 8 | path: '', 9 | component: PlaygroundStatelessComponent, 10 | }, 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forRoot(routes)], 15 | exports: [RouterModule], 16 | }) 17 | export class AppRoutingModule {} 18 | -------------------------------------------------------------------------------- /packages/playground/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/playground/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/app/app.component.scss -------------------------------------------------------------------------------- /packages/playground/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, TestBed } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | import { AppModule } from './app.module'; 4 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | import { AppSettingsService } from './services/app-settings.service'; 7 | 8 | describe('AppComponent', () => { 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [AppModule, HttpClientTestingModule, RouterTestingModule], 12 | providers: [ 13 | { 14 | provide: AppSettingsService, 15 | useValue: { get: () => ({ flags: {}, publicUrl: '' }) }, 16 | }, 17 | ], 18 | }).compileComponents(); 19 | })); 20 | 21 | it('should create the app', () => { 22 | const fixture = TestBed.createComponent(AppComponent); 23 | const app = fixture.debugElement.componentInstance; 24 | 25 | expect(app).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/playground/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'], 7 | }) 8 | export class AppComponent {} 9 | -------------------------------------------------------------------------------- /packages/playground/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { RouterModule } from '@angular/router'; 5 | import { HttpClientModule } from '@angular/common/http'; 6 | 7 | import { AppComponent } from './app.component'; 8 | import { PlaygroundModule } from './components/playground/playground.module'; 9 | import { AppRoutingModule } from './app-routing.module'; 10 | import { PlaygroundStatelessModule } from './components/playground-stateless/playground-stateless.module'; 11 | 12 | @NgModule({ 13 | declarations: [AppComponent], 14 | imports: [ 15 | BrowserModule, 16 | FormsModule, 17 | HttpClientModule, 18 | PlaygroundModule, 19 | PlaygroundStatelessModule, 20 | AppRoutingModule, 21 | RouterModule, 22 | ], 23 | providers: [], 24 | bootstrap: [AppComponent], 25 | }) 26 | export class AppModule {} 27 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/convert-format/convert-format.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 32 | 33 | 34 | 39 | 40 |
41 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/convert-format/convert-format.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/app/components/convert-format/convert-format.component.scss -------------------------------------------------------------------------------- /packages/playground/src/app/components/convert-format/convert-format.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { ConvertFormatComponent } from './convert-format.component'; 4 | import { ConvertFormatModule } from './convert-format.module'; 5 | 6 | describe('ConvertFormatComponent', () => { 7 | let component: ConvertFormatComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(async () => { 11 | await TestBed.configureTestingModule({ 12 | imports: [ConvertFormatModule], 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ConvertFormatComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/convert-format/convert-format.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { 3 | FeedFormat, 4 | GenericFeedWithParams, 5 | NativeFeedWithParams, 6 | } from '../../services/feed.service'; 7 | import { clone } from 'lodash'; 8 | 9 | @Component({ 10 | selector: 'app-convert-format', 11 | templateUrl: './convert-format.component.html', 12 | styleUrls: ['./convert-format.component.scss'], 13 | }) 14 | export class ConvertFormatComponent implements OnInit { 15 | @Input() 16 | private nativeFeedValue: NativeFeedWithParams; 17 | @Input() 18 | private genericFeedValue: GenericFeedWithParams; 19 | nativeFeed: NativeFeedWithParams; 20 | genericFeed: GenericFeedWithParams; 21 | 22 | format: FeedFormat; 23 | intoBucket: boolean; 24 | 25 | constructor() {} 26 | 27 | ngOnInit(): void { 28 | this.nativeFeed = clone(this.nativeFeedValue); 29 | this.genericFeed = clone(this.genericFeedValue); 30 | } 31 | 32 | applyFormat(format: FeedFormat) { 33 | this.format = format; 34 | 35 | if (this.nativeFeed) { 36 | this.nativeFeed.targetFormat = format; 37 | } else { 38 | this.genericFeed.targetFormat = format; 39 | } 40 | } 41 | 42 | addToBucket() { 43 | this.intoBucket = true; 44 | } 45 | 46 | getNativeFeed() { 47 | // to change the ref and force update 48 | return clone(this.nativeFeed); 49 | } 50 | 51 | getGenericFeed() { 52 | // to change the ref and force update 53 | return clone(this.genericFeed); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/convert-format/convert-format.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ConvertFormatComponent } from './convert-format.component'; 4 | import { SectionModule } from '../section/section.module'; 5 | import { FeedUrlModule } from '../feed-url/feed-url.module'; 6 | 7 | @NgModule({ 8 | declarations: [ConvertFormatComponent], 9 | exports: [ConvertFormatComponent], 10 | imports: [CommonModule, SectionModule, FeedUrlModule], 11 | }) 12 | export class ConvertFormatModule {} 13 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/dot/dot.component.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/dot/dot.component.scss: -------------------------------------------------------------------------------- 1 | .dot { 2 | display: inline-block; 3 | border-radius: 50%; 4 | background: #ff0000; 5 | width: 7px; 6 | height: 7px; 7 | margin-bottom: 7px; 8 | } 9 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/dot/dot.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DotComponent } from './dot.component'; 4 | import { DotModule } from './dot.module'; 5 | 6 | describe('DotComponent', () => { 7 | let component: DotComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [DotModule], 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DotComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/dot/dot.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-dot', 5 | templateUrl: './dot.component.html', 6 | styleUrls: ['./dot.component.scss'], 7 | }) 8 | export class DotComponent implements OnInit { 9 | constructor() {} 10 | 11 | ngOnInit() {} 12 | } 13 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/dot/dot.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { DotComponent } from './dot.component'; 4 | 5 | @NgModule({ 6 | declarations: [DotComponent], 7 | exports: [DotComponent], 8 | imports: [CommonModule], 9 | }) 10 | export class DotModule {} 11 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/export-options/export-options.component.html: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | 10 | 30 | 31 | 32 | 38 | 44 |
45 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/export-options/export-options.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/app/components/export-options/export-options.component.scss -------------------------------------------------------------------------------- /packages/playground/src/app/components/export-options/export-options.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { ExportOptionsComponent } from './export-options.component'; 3 | import { ExportOptionsModule } from './export-options.module'; 4 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 5 | import { AppSettingsService } from '../../services/app-settings.service'; 6 | import { ReplaySubject } from 'rxjs'; 7 | 8 | describe('ExportOptionsComponent', () => { 9 | let component: ExportOptionsComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(waitForAsync(async () => { 13 | await TestBed.configureTestingModule({ 14 | imports: [ExportOptionsModule, HttpClientTestingModule], 15 | providers: [ 16 | { 17 | provide: AppSettingsService, 18 | useValue: { 19 | get: () => ({ flags: {} }), 20 | watchShowHelp: () => new ReplaySubject().asObservable(), 21 | }, 22 | }, 23 | ], 24 | }).compileComponents(); 25 | })); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(ExportOptionsComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/export-options/export-options.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { 3 | GenericFeedWithParams, 4 | NativeFeedWithParams, 5 | } from '../../services/feed.service'; 6 | import { 7 | AppSettingsService, 8 | FeatureFlags, 9 | } from '../../services/app-settings.service'; 10 | 11 | @Component({ 12 | selector: 'app-export-options', 13 | templateUrl: './export-options.component.html', 14 | styleUrls: ['./export-options.component.scss'], 15 | }) 16 | export class ExportOptionsComponent implements OnInit { 17 | hasChosen: boolean; 18 | convert: boolean; 19 | push: boolean; 20 | content: boolean; 21 | 22 | @Input() 23 | nativeFeed: NativeFeedWithParams; 24 | @Input() 25 | genericFeedRule: GenericFeedWithParams; 26 | 27 | digest: boolean; 28 | flags: FeatureFlags; 29 | 30 | constructor(private appSettings: AppSettingsService) {} 31 | 32 | async ngOnInit() { 33 | await this.appSettings.waitForInit; 34 | this.flags = this.appSettings.get().flags; 35 | } 36 | 37 | private use(fn: () => void) { 38 | this.reset(); 39 | fn(); 40 | this.hasChosen = true; 41 | } 42 | 43 | private reset() { 44 | this.convert = null; 45 | this.push = null; 46 | this.content = null; 47 | } 48 | 49 | useConvert() { 50 | this.use(() => { 51 | this.convert = true; 52 | }); 53 | } 54 | 55 | usePush() { 56 | this.use(() => { 57 | this.push = true; 58 | }); 59 | } 60 | 61 | useContent() { 62 | this.use(() => { 63 | this.content = true; 64 | }); 65 | } 66 | 67 | useDigest() { 68 | this.use(() => { 69 | this.digest = true; 70 | }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/export-options/export-options.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ExportOptionsComponent } from './export-options.component'; 4 | import { SectionModule } from '../section/section.module'; 5 | import { PushUpdatesModule } from '../push/push-updates/push-updates.module'; 6 | import { ConvertFormatModule } from '../convert-format/convert-format.module'; 7 | 8 | @NgModule({ 9 | declarations: [ExportOptionsComponent], 10 | exports: [ExportOptionsComponent], 11 | imports: [ 12 | CommonModule, 13 | SectionModule, 14 | PushUpdatesModule, 15 | ConvertFormatModule, 16 | ], 17 | }) 18 | export class ExportOptionsModule {} 19 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/feed-url/feed-url.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | Your feed is ready. You will receive maintenance messages 5 | into your feed if there are problems 6 | 7 | 8 |
9 |
10 |

11 | {{ feed?.title }} 12 |

13 |

14 | {{ feed?.description }} 15 |

16 |
17 |
18 | 19 |
20 |
21 | 25 |
26 | 29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/feed-url/feed-url.component.scss: -------------------------------------------------------------------------------- 1 | .is-expanded { 2 | width: 100%; 3 | } 4 | textarea { 5 | min-height: 250px; 6 | } 7 | .copy { 8 | color: blue; 9 | } 10 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/feed-url/feed-url.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { FeedUrlComponent } from './feed-url.component'; 4 | import { FeedUrlModule } from './feed-url.module'; 5 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 6 | import { AppSettingsService } from '../../services/app-settings.service'; 7 | import { ReplaySubject } from 'rxjs'; 8 | 9 | describe('FeedUrlComponent', () => { 10 | let component: FeedUrlComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(waitForAsync(async () => { 14 | await TestBed.configureTestingModule({ 15 | imports: [FeedUrlModule, HttpClientTestingModule], 16 | providers: [ 17 | { 18 | provide: AppSettingsService, 19 | useValue: { 20 | get: () => ({ flags: {} }), 21 | watchShowHelp: () => new ReplaySubject().asObservable(), 22 | }, 23 | }, 24 | ], 25 | }).compileComponents(); 26 | })); 27 | 28 | beforeEach(() => { 29 | fixture = TestBed.createComponent(FeedUrlComponent); 30 | component = fixture.componentInstance; 31 | fixture.detectChanges(); 32 | }); 33 | 34 | it('should create', () => { 35 | expect(component).toBeTruthy(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/feed-url/feed-url.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionStrategy, 3 | ChangeDetectorRef, 4 | Component, 5 | Input, 6 | OnChanges, 7 | OnInit, 8 | SimpleChanges, 9 | } from '@angular/core'; 10 | import { 11 | FeedFormat, 12 | FeedService, 13 | GenericFeedWithParams, 14 | NativeFeedWithParams, 15 | PermanentFeed, 16 | } from '../../services/feed.service'; 17 | import { JsonFeed } from '../feed/feed.component'; 18 | import { clone } from 'lodash'; 19 | 20 | @Component({ 21 | selector: 'app-feed-url', 22 | templateUrl: './feed-url.component.html', 23 | styleUrls: ['./feed-url.component.scss'], 24 | changeDetection: ChangeDetectionStrategy.OnPush, 25 | }) 26 | export class FeedUrlComponent implements OnInit, OnChanges { 27 | @Input() 28 | private nativeFeedValue: NativeFeedWithParams; 29 | @Input() 30 | private genericFeedValue: GenericFeedWithParams; 31 | nativeFeed: NativeFeedWithParams; 32 | genericFeed: GenericFeedWithParams; 33 | feed: JsonFeed; 34 | actualFeedUrl: string; 35 | loading: boolean; 36 | 37 | constructor( 38 | private readonly feedService: FeedService, 39 | private readonly changeRef: ChangeDetectorRef, 40 | ) {} 41 | 42 | async ngOnChanges(changes: SimpleChanges): Promise { 43 | if (changes.nativeFeedValue && changes.nativeFeedValue.currentValue) { 44 | this.nativeFeed = clone(changes.nativeFeedValue.currentValue); 45 | } 46 | if (changes.genericFeedValue && changes.genericFeedValue.currentValue) { 47 | this.genericFeed = clone(changes.genericFeedValue.currentValue); 48 | } 49 | await this.requestFeedUrl(); 50 | this.changeRef.detectChanges(); 51 | } 52 | 53 | async ngOnInit(): Promise { 54 | this.nativeFeed = clone(this.nativeFeedValue); 55 | this.genericFeed = clone(this.genericFeedValue); 56 | } 57 | 58 | private async requestFeedUrl(): Promise { 59 | this.loading = true; 60 | this.actualFeedUrl = await this.requestPermanentFeedUrl(); 61 | // this.message = permanentFeedUrl.message; 62 | this.loading = false; 63 | } 64 | 65 | private async requestPermanentFeedUrl(): Promise { 66 | if (this.genericFeed) { 67 | return this.feedService.createFeedUrlForGeneric(this.genericFeed); 68 | } 69 | if (this.nativeFeed) { 70 | return this.feedService.createFeedUrlForNative(this.nativeFeed); 71 | } 72 | } 73 | 74 | format(): FeedFormat { 75 | if (this.genericFeed) { 76 | return this.genericFeed.targetFormat; 77 | } else { 78 | return this.nativeFeed?.targetFormat; 79 | } 80 | } 81 | 82 | copyUrl() {} 83 | } 84 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/feed-url/feed-url.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FeedUrlComponent } from './feed-url.component'; 4 | import { SectionModule } from '../section/section.module'; 5 | import { FormsModule } from '@angular/forms'; 6 | import { SpinnerModule } from '../spinner/spinner.module'; 7 | import { HelpMessageModule } from '../help-message/help-message.module'; 8 | 9 | @NgModule({ 10 | declarations: [FeedUrlComponent], 11 | imports: [ 12 | CommonModule, 13 | FormsModule, 14 | SectionModule, 15 | SpinnerModule, 16 | HelpMessageModule, 17 | ], 18 | exports: [FeedUrlComponent], 19 | }) 20 | export class FeedUrlModule {} 21 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/feed/feed.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ jsonFeed.title }}

4 |

{{ jsonFeed.description }}

5 |

{{ jsonFeed.home_page_url }}

6 |

{{ jsonFeed.date_published }}

7 |
8 | 9 |
10 |

{{ i + 1 }}. {{ item.title }}

11 | {{ item.url }} 18 |

{{ item.date_published }}

19 |

{{ item.content_text }}

20 |
21 |
22 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/feed/feed.component.scss: -------------------------------------------------------------------------------- 1 | .url { 2 | color: black; 3 | text-decoration: underline; 4 | } 5 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/feed/feed.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { FeedComponent } from './feed.component'; 4 | import { FeedModule } from './feed.module'; 5 | 6 | describe('FeedComponent', () => { 7 | let component: FeedComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(async () => { 11 | await TestBed.configureTestingModule({ 12 | imports: [FeedModule], 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FeedComponent); 18 | component = fixture.componentInstance; 19 | component.jsonFeed = { 20 | items: [], 21 | } as any; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/feed/feed.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | export interface JsonFeedItem { 4 | id: string; 5 | title: string; 6 | content_text: string; 7 | content_raw: string; 8 | content_raw_mime: string; 9 | url: string; 10 | date_published: string; 11 | tags: string[]; 12 | image: string; 13 | author: string; 14 | enclosures: any; 15 | commentsFeedUrl: string; 16 | } 17 | 18 | export interface JsonFeed { 19 | id: string; 20 | 21 | last_url: string; 22 | previous_url: string; 23 | next_url: string; 24 | feed_url: string; 25 | 26 | expired: false; 27 | title: string; 28 | description: string; 29 | home_page_url: string; 30 | date_published: string; 31 | items: JsonFeedItem[]; 32 | lastPage: number; 33 | selfPage: number; 34 | tags: string[]; 35 | } 36 | 37 | @Component({ 38 | selector: 'app-feed', 39 | templateUrl: './feed.component.html', 40 | styleUrls: ['./feed.component.scss'], 41 | }) 42 | export class FeedComponent implements OnInit { 43 | @Input() 44 | jsonFeed: JsonFeed; 45 | 46 | @Input() 47 | header = true; 48 | 49 | constructor() {} 50 | 51 | ngOnInit(): void {} 52 | } 53 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/feed/feed.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FeedComponent } from './feed.component'; 4 | 5 | @NgModule({ 6 | declarations: [FeedComponent], 7 | exports: [FeedComponent], 8 | imports: [CommonModule], 9 | }) 10 | export class FeedModule {} 11 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/footer/footer.component.html: -------------------------------------------------------------------------------- 1 | 48 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/footer/footer.component.scss: -------------------------------------------------------------------------------- 1 | .rp-footer { 2 | text-align: center; 3 | color: #cccccc; 4 | padding-top: 1.2em; 5 | padding-bottom: 1.2em; 6 | 7 | a, 8 | a:active { 9 | color: gray; 10 | } 11 | a:hover { 12 | color: black; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/footer/footer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { FooterComponent } from './footer.component'; 4 | import { FooterModule } from './footer.module'; 5 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 6 | import { RouterTestingModule } from '@angular/router/testing'; 7 | 8 | describe('FooterComponent', () => { 9 | let component: FooterComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(waitForAsync(async () => { 13 | await TestBed.configureTestingModule({ 14 | imports: [FooterModule, HttpClientTestingModule, RouterTestingModule], 15 | }).compileComponents(); 16 | })); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(FooterComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionStrategy, 3 | ChangeDetectorRef, 4 | Component, 5 | Input, 6 | OnInit, 7 | } from '@angular/core'; 8 | import { build } from '../../../environments/build'; 9 | import { 10 | AppSettingsService, 11 | FeatureFlags, 12 | } from '../../services/app-settings.service'; 13 | 14 | @Component({ 15 | selector: 'app-footer', 16 | templateUrl: './footer.component.html', 17 | styleUrls: ['./footer.component.scss'], 18 | changeDetection: ChangeDetectionStrategy.OnPush, 19 | }) 20 | export class FooterComponent implements OnInit { 21 | buildInfo: { date: string; version: string; revision: string }; 22 | flags: FeatureFlags; 23 | showHelp: boolean; 24 | @Input() 25 | showCredits: boolean; 26 | 27 | constructor( 28 | private readonly settings: AppSettingsService, 29 | private readonly changeDetectorRef: ChangeDetectorRef, 30 | ) {} 31 | 32 | async ngOnInit(): Promise { 33 | this.buildInfo = build; 34 | await this.settings.waitForInit; 35 | this.flags = this.settings.get().flags; 36 | this.changeDetectorRef.detectChanges(); 37 | 38 | this.settings.watchShowHelp().subscribe((showHelp) => { 39 | this.showHelp = showHelp; 40 | this.changeDetectorRef.detectChanges(); 41 | }); 42 | } 43 | 44 | formatDate(date: string) { 45 | return date; 46 | } 47 | 48 | help() { 49 | this.settings.setShowHelp(true); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/footer/footer.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FooterComponent } from './footer.component'; 4 | import { RouterModule } from '@angular/router'; 5 | 6 | @NgModule({ 7 | declarations: [FooterComponent], 8 | exports: [FooterComponent], 9 | imports: [CommonModule, RouterModule], 10 | }) 11 | export class FooterModule {} 12 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/generic-feeds/generic-feeds.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 28 | 29 | 139 | 140 |
141 |
142 | 149 |
150 |
151 |
152 | 153 | 158 |
159 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/generic-feeds/generic-feeds.component.scss: -------------------------------------------------------------------------------- 1 | $primary: blue; 2 | 3 | .source { 4 | width: 650px; 5 | flex: 1; 6 | display: flex; 7 | flex-direction: row; 8 | background: white; 9 | > * { 10 | flex: 1; 11 | } 12 | &.highlight { 13 | border: 3px solid $primary; 14 | } 15 | } 16 | 17 | .sample { 18 | white-space: nowrap; 19 | text-overflow: ellipsis; 20 | overflow: hidden; 21 | line-break: unset; 22 | font-size: small; 23 | margin-left: 5px; 24 | padding: 5px; 25 | border-left: 1px solid #ccc; 26 | } 27 | .iframe-wrapper { 28 | transform: scale(0.7); 29 | transform-origin: 0 0; 30 | } 31 | 32 | .is-preselected { 33 | border-left: 5px solid blue; 34 | } 35 | .is-expanded { 36 | width: 100%; 37 | } 38 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/generic-feeds/generic-feeds.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { GenericFeedsComponent } from './generic-feeds.component'; 4 | import { GenericFeedsModule } from './generic-feeds.module'; 5 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 6 | 7 | describe('GenericFeedsComponent', () => { 8 | let component: GenericFeedsComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(async () => { 12 | await TestBed.configureTestingModule({ 13 | imports: [GenericFeedsModule, HttpClientTestingModule], 14 | }).compileComponents(); 15 | })); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(GenericFeedsComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/generic-feeds/generic-feeds.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionStrategy, 3 | ChangeDetectorRef, 4 | Component, 5 | ElementRef, 6 | Input, 7 | OnInit, 8 | ViewChild, 9 | } from '@angular/core'; 10 | import { 11 | GenericFeedRule, 12 | GenericFeedWithParams, 13 | } from '../../services/feed.service'; 14 | import * as URI from 'urijs'; 15 | import { clone } from 'lodash'; 16 | 17 | interface ArticleCandidate { 18 | elem: HTMLElement; 19 | index: number; 20 | qualified: boolean; 21 | } 22 | 23 | function getRelativeCssPath( 24 | node: HTMLElement, 25 | context: HTMLElement, 26 | withClassNames = false, 27 | ): string { 28 | if (node.nodeType === 3 || node === context) { 29 | // todo mag this is not applicable 30 | return 'self'; 31 | } 32 | let path = node.tagName; // tagName for text nodes is undefined 33 | while (node.parentNode !== context) { 34 | node = node.parentNode as HTMLElement; 35 | if (typeof path === 'undefined') { 36 | path = getTagName(node, withClassNames); 37 | } else { 38 | path = `${getTagName(node, withClassNames)}>${path}`; 39 | } 40 | } 41 | return path; 42 | } 43 | 44 | export function patchHtml(html: string, url: string): Document { 45 | const doc = new DOMParser().parseFromString(html, 'text/html'); 46 | 47 | const base = doc.createElement('base'); 48 | base.setAttribute('href', url); 49 | doc.getElementsByTagName('head').item(0).appendChild(base); 50 | 51 | Array.from(doc.querySelectorAll('[href]')).forEach((el) => { 52 | try { 53 | const absoluteUrl = new URI(el.getAttribute('href')).absoluteTo(url); 54 | el.setAttribute('href', absoluteUrl.toString()); 55 | } catch (e) { 56 | // console.error(e); 57 | } 58 | }); 59 | 60 | return doc; 61 | } 62 | 63 | function getTagName(node: HTMLElement, withClassNames: boolean): string { 64 | if (!withClassNames) { 65 | return node.tagName; 66 | } 67 | const classList = Array.from(node.classList).filter( 68 | (cn) => cn.match('[0-9]+') === null, 69 | ); 70 | if (classList.length > 0) { 71 | return `${node.tagName}.${classList.join('.')}`; 72 | } 73 | return node.tagName; 74 | } 75 | 76 | @Component({ 77 | selector: 'app-generic-feeds', 78 | templateUrl: './generic-feeds.component.html', 79 | styleUrls: ['./generic-feeds.component.scss'], 80 | changeDetection: ChangeDetectionStrategy.OnPush, 81 | }) 82 | export class GenericFeedsComponent implements OnInit { 83 | @Input() 84 | prerendered: boolean; 85 | @Input() 86 | puppeteerScript: string; 87 | @Input() 88 | body: string; 89 | @Input() 90 | url: string; 91 | @Input() 92 | genericFeedRules: GenericFeedRule[]; 93 | 94 | currentRule: GenericFeedWithParams; 95 | 96 | @ViewChild('iframeElement', { static: true }) 97 | iframeRef: ElementRef; 98 | 99 | customDateXPath = ''; 100 | customLinkXPath = ''; 101 | customContextXPath = ''; 102 | 103 | private proxyUrl: string; 104 | refine: boolean; 105 | hasChosen: boolean; 106 | 107 | constructor(private readonly changeDetectorRef: ChangeDetectorRef) {} 108 | 109 | ngOnInit(): void { 110 | this.prepareIframe( 111 | patchHtml(this.body, this.url).documentElement.innerHTML, 112 | ); 113 | } 114 | 115 | applyCustomRule(currentRule: GenericFeedRule) { 116 | const customRule = clone(currentRule); 117 | customRule.id = 'custom'; 118 | customRule.linkXPath = this.customLinkXPath; 119 | customRule.contextXPath = this.customContextXPath; 120 | customRule.dateXPath = this.customDateXPath; 121 | this.applyRule(customRule); 122 | } 123 | 124 | applyRule(rule: GenericFeedRule) { 125 | this.currentRule = { 126 | ...rule, 127 | harvestUrl: this.url, 128 | prerendered: this.prerendered, 129 | puppeteerScript: this.puppeteerScript, 130 | } as GenericFeedWithParams; 131 | this.customContextXPath = this.currentRule.contextXPath; 132 | this.customDateXPath = this.currentRule.dateXPath; 133 | this.customLinkXPath = this.currentRule.linkXPath; 134 | this.highlightRule(rule); 135 | this.changeDetectorRef.detectChanges(); 136 | } 137 | 138 | private prepareIframe(html: string) { 139 | this.assignToIframe(html); 140 | } 141 | 142 | private assignToIframe(html: string) { 143 | this.proxyUrl = window.URL.createObjectURL( 144 | new Blob([html], { 145 | type: 'text/html', 146 | }), 147 | ); 148 | this.iframeRef.nativeElement.src = this.proxyUrl; 149 | this.changeDetectorRef.detectChanges(); 150 | } 151 | 152 | onIframeLoad(): void { 153 | // if (this.response.results.genericFeedRules) { 154 | // this.updateScores(); 155 | // } else { 156 | // } 157 | } 158 | 159 | updateScores(): void { 160 | const iframeDocument = this.iframeRef.nativeElement.contentDocument; 161 | this.genericFeedRules.forEach((rule) => { 162 | const articles = this.evaluateXPathInIframe( 163 | rule.contextXPath, 164 | iframeDocument, 165 | ) 166 | // remove hidden articles 167 | .filter((elem: any) => !!(elem.offsetWidth || elem.offsetHeight)); 168 | // remove empty articles 169 | // .filter((elem: any) => elem.textContent.trim() > 0) 170 | // .filter((elem: any) => Array.from(elem.querySelectorAll(rule.linkPath)).length > 0); 171 | if (articles.length === 0) { 172 | rule.score -= 20; 173 | // rule.hidden = true; 174 | } 175 | }); 176 | this.changeDetectorRef.detectChanges(); 177 | } 178 | 179 | private highlightRule(rule: GenericFeedRule): void { 180 | const iframeDocument = this.iframeRef.nativeElement.contentDocument; 181 | const id = 'rss-proxy-style'; 182 | 183 | try { 184 | iframeDocument.getElementById(id).remove(); 185 | } catch (e) {} 186 | const styleNode = iframeDocument.createElement('style'); 187 | styleNode.setAttribute('type', 'text/css'); 188 | styleNode.setAttribute('id', id); 189 | const allMatches: HTMLElement[] = this.evaluateXPathInIframe( 190 | rule.contextXPath, 191 | iframeDocument, 192 | ); 193 | 194 | const matchingIndexes = allMatches 195 | .map((elem) => { 196 | const index = Array.from(elem.parentElement.children).findIndex( 197 | (otherElem) => otherElem === elem, 198 | ); 199 | // const qualified = true; 200 | // if (qualified) { 201 | // console.log(`Keeping element ${index}`, elem); 202 | // } else { 203 | // console.log(`Removing unqualified element ${index}`, elem); 204 | // } 205 | return { elem, index } as ArticleCandidate; 206 | }) 207 | .map((candidate) => candidate.index); 208 | 209 | const cssSelectorContextPath = 210 | 'body>' + getRelativeCssPath(allMatches[0], iframeDocument.body, false); 211 | console.log(cssSelectorContextPath); 212 | const code = `${matchingIndexes 213 | .map((index) => `${cssSelectorContextPath}:nth-child(${index + 1})`) 214 | .join(', ')} { 215 | border: 3px solid blue!important; 216 | margin: 2px!important; 217 | padding: 2px!important; 218 | display: block; 219 | } 220 | `; 221 | 222 | styleNode.appendChild(iframeDocument.createTextNode(code)); 223 | const existingStyleNode = iframeDocument.head.querySelector(`#${id}`); 224 | if (existingStyleNode) { 225 | existingStyleNode.remove(); 226 | } 227 | iframeDocument.head.appendChild(styleNode); 228 | setTimeout(() => { 229 | const firstMatch = allMatches[0]; 230 | if (firstMatch) { 231 | firstMatch.scrollIntoView({ behavior: 'smooth' }); 232 | } 233 | }, 500); 234 | } 235 | 236 | private evaluateXPathInIframe( 237 | xPath: string, 238 | context: HTMLElement | Document, 239 | ): HTMLElement[] { 240 | const iframeDocument = this.iframeRef.nativeElement.contentDocument; 241 | const xpathResult = iframeDocument.evaluate(xPath, context, null, 5); 242 | const nodes: HTMLElement[] = []; 243 | let node = xpathResult.iterateNext(); 244 | while (node) { 245 | nodes.push(node as HTMLElement); 246 | node = xpathResult.iterateNext(); 247 | } 248 | return nodes; 249 | } 250 | 251 | private use(fn: () => void) { 252 | this.reset(); 253 | fn(); 254 | this.hasChosen = true; 255 | } 256 | 257 | private reset() { 258 | this.refine = null; 259 | } 260 | 261 | useRefine() { 262 | this.use(() => { 263 | this.refine = true; 264 | }); 265 | } 266 | 267 | edit() { 268 | this.reset(); 269 | this.hasChosen = false; 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/generic-feeds/generic-feeds.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { GenericFeedsComponent } from './generic-feeds.component'; 4 | import { SectionModule } from '../section/section.module'; 5 | import { FormsModule } from '@angular/forms'; 6 | import { RefineFeedModule } from '../refine-feed/refine-feed.module'; 7 | import { HelpMessageModule } from '../help-message/help-message.module'; 8 | 9 | @NgModule({ 10 | declarations: [GenericFeedsComponent], 11 | exports: [GenericFeedsComponent], 12 | imports: [ 13 | CommonModule, 14 | SectionModule, 15 | FormsModule, 16 | RefineFeedModule, 17 | HelpMessageModule, 18 | ], 19 | }) 20 | export class GenericFeedsModule {} 21 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/header/header.component.html: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/header/header.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/app/components/header/header.component.scss -------------------------------------------------------------------------------- /packages/playground/src/app/components/header/header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HeaderComponent } from './header.component'; 4 | 5 | describe('HeaderComponent', () => { 6 | let component: HeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [HeaderComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(HeaderComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-header', 5 | templateUrl: './header.component.html', 6 | styleUrls: ['./header.component.scss'], 7 | }) 8 | export class HeaderComponent implements OnInit { 9 | constructor() {} 10 | 11 | ngOnInit(): void {} 12 | } 13 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/header/header.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { HeaderComponent } from './header.component'; 4 | import { RouterModule } from '@angular/router'; 5 | 6 | @NgModule({ 7 | declarations: [HeaderComponent], 8 | exports: [HeaderComponent], 9 | imports: [CommonModule, RouterModule], 10 | }) 11 | export class HeaderModule {} 12 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/help-message/help-message.component.html: -------------------------------------------------------------------------------- 1 |
6 |
7 |

Guidance

8 | 9 |
10 |
11 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/help-message/help-message.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/app/components/help-message/help-message.component.scss -------------------------------------------------------------------------------- /packages/playground/src/app/components/help-message/help-message.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { HelpMessageComponent } from './help-message.component'; 4 | import { HelpMessageModule } from './help-message.module'; 5 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 6 | 7 | describe('HelpMessageComponent', () => { 8 | let component: HelpMessageComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(waitForAsync(async () => { 12 | await TestBed.configureTestingModule({ 13 | imports: [HelpMessageModule, HttpClientTestingModule], 14 | }).compileComponents(); 15 | })); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(HelpMessageComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/help-message/help-message.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionStrategy, 3 | ChangeDetectorRef, 4 | Component, 5 | OnInit, 6 | } from '@angular/core'; 7 | import { AppSettingsService } from '../../services/app-settings.service'; 8 | 9 | @Component({ 10 | selector: 'app-help-message', 11 | templateUrl: './help-message.component.html', 12 | styleUrls: ['./help-message.component.scss'], 13 | changeDetection: ChangeDetectionStrategy.OnPush, 14 | }) 15 | export class HelpMessageComponent implements OnInit { 16 | show: boolean; 17 | constructor( 18 | private readonly appSettings: AppSettingsService, 19 | private readonly changeRef: ChangeDetectorRef, 20 | ) {} 21 | 22 | ngOnInit(): void { 23 | this.appSettings.watchShowHelp().subscribe((showHelp) => { 24 | this.show = showHelp; 25 | this.changeRef.detectChanges(); 26 | }); 27 | } 28 | 29 | hide() { 30 | this.show = false; 31 | this.appSettings.setShowHelp(false); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/help-message/help-message.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { HelpMessageComponent } from './help-message.component'; 4 | 5 | @NgModule({ 6 | declarations: [HelpMessageComponent], 7 | exports: [HelpMessageComponent], 8 | imports: [CommonModule], 9 | }) 10 | export class HelpMessageModule {} 11 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/native-feeds/native-feeds.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 15 | 16 | 17 | 22 |
23 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/native-feeds/native-feeds.component.scss: -------------------------------------------------------------------------------- 1 | .menu-list { 2 | max-width: 300px; 3 | text-overflow: ellipsis; 4 | overflow: hidden; 5 | } 6 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/native-feeds/native-feeds.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { NativeFeedsComponent } from './native-feeds.component'; 4 | import { NativeFeedsModule } from './native-feeds.module'; 5 | 6 | describe('NativeFeedsComponent', () => { 7 | let component: NativeFeedsComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(async () => { 11 | await TestBed.configureTestingModule({ 12 | imports: [NativeFeedsModule], 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(NativeFeedsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/native-feeds/native-feeds.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { 3 | NativeFeedRef, 4 | NativeFeedWithParams, 5 | } from '../../services/feed.service'; 6 | 7 | @Component({ 8 | selector: 'app-native-feeds', 9 | templateUrl: './native-feeds.component.html', 10 | styleUrls: ['./native-feeds.component.scss'], 11 | }) 12 | export class NativeFeedsComponent implements OnInit { 13 | @Input() 14 | nativeFeeds: NativeFeedRef[]; 15 | currentNativeFeed: NativeFeedRef; 16 | hasChosen: boolean; 17 | 18 | constructor() {} 19 | 20 | ngOnInit(): void {} 21 | 22 | useFeed(feed: NativeFeedRef) { 23 | this.currentNativeFeed = feed; 24 | this.hasChosen = true; 25 | } 26 | 27 | withParams(): NativeFeedWithParams { 28 | return { 29 | feedUrl: this.currentNativeFeed.url, 30 | debug: false, 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/native-feeds/native-feeds.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NativeFeedsComponent } from './native-feeds.component'; 4 | import { SectionModule } from '../section/section.module'; 5 | import { NativeOptionsModule } from '../native-options/native-options.module'; 6 | 7 | @NgModule({ 8 | declarations: [NativeFeedsComponent], 9 | exports: [NativeFeedsComponent], 10 | imports: [CommonModule, SectionModule, NativeOptionsModule], 11 | }) 12 | export class NativeFeedsModule {} 13 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/native-options/native-options.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 32 | 33 | 34 | 39 | 40 | 45 | 49 |
50 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/native-options/native-options.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/app/components/native-options/native-options.component.scss -------------------------------------------------------------------------------- /packages/playground/src/app/components/native-options/native-options.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { NativeOptionsComponent } from './native-options.component'; 4 | import { NativeOptionsModule } from './native-options.module'; 5 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 6 | import { AppSettingsService } from '../../services/app-settings.service'; 7 | import { ReplaySubject } from 'rxjs'; 8 | 9 | describe('NativeOptionsComponent', () => { 10 | let component: NativeOptionsComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(waitForAsync(async () => { 14 | await TestBed.configureTestingModule({ 15 | imports: [NativeOptionsModule, HttpClientTestingModule], 16 | providers: [ 17 | { 18 | provide: AppSettingsService, 19 | useValue: { 20 | get: () => ({ flags: {} }), 21 | watchShowHelp: () => new ReplaySubject().asObservable(), 22 | }, 23 | }, 24 | ], 25 | }).compileComponents(); 26 | })); 27 | 28 | beforeEach(() => { 29 | fixture = TestBed.createComponent(NativeOptionsComponent); 30 | component = fixture.componentInstance; 31 | fixture.detectChanges(); 32 | }); 33 | 34 | it('should create', () => { 35 | expect(component).toBeTruthy(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/native-options/native-options.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { NativeFeedWithParams } from '../../services/feed.service'; 3 | import { AppSettingsService } from '../../services/app-settings.service'; 4 | import { WizardComponent } from '../../wizard.component'; 5 | 6 | @Component({ 7 | selector: 'app-native-options', 8 | templateUrl: './native-options.component.html', 9 | styleUrls: ['./native-options.component.scss'], 10 | }) 11 | export class NativeOptionsComponent extends WizardComponent implements OnInit { 12 | hasChosen: boolean; 13 | export: boolean; 14 | merge: boolean; 15 | refine: boolean; 16 | 17 | @Input() 18 | nativeFeed: NativeFeedWithParams; 19 | stateless: boolean; 20 | 21 | constructor(private settings: AppSettingsService) { 22 | super(); 23 | } 24 | 25 | async ngOnInit() { 26 | await this.settings.waitForInit; 27 | this.stateless = this.settings.get().flags.stateless; 28 | } 29 | 30 | private use(fn: () => void) { 31 | this.reset(); 32 | fn(); 33 | this.hasChosen = true; 34 | } 35 | 36 | private reset() { 37 | this.export = null; 38 | this.merge = null; 39 | this.refine = null; 40 | } 41 | 42 | useExport() { 43 | this.use(() => { 44 | this.export = true; 45 | }); 46 | } 47 | 48 | useMerge() { 49 | this.use(() => { 50 | this.merge = true; 51 | }); 52 | } 53 | 54 | useRefine() { 55 | this.use(() => { 56 | this.refine = true; 57 | }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/native-options/native-options.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NativeOptionsComponent } from './native-options.component'; 4 | import { SectionModule } from '../section/section.module'; 5 | import { ExportOptionsModule } from '../export-options/export-options.module'; 6 | import { RefineFeedModule } from '../refine-feed/refine-feed.module'; 7 | import { HelpMessageModule } from '../help-message/help-message.module'; 8 | 9 | @NgModule({ 10 | declarations: [NativeOptionsComponent], 11 | exports: [NativeOptionsComponent], 12 | imports: [ 13 | CommonModule, 14 | SectionModule, 15 | ExportOptionsModule, 16 | RefineFeedModule, 17 | HelpMessageModule, 18 | ], 19 | }) 20 | export class NativeOptionsModule {} 21 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/options/options.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 67 | 68 | 69 | 76 | 81 | 87 | 92 |
93 | 94 | 95 | 96 | 100 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/options/options.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/app/components/options/options.component.scss -------------------------------------------------------------------------------- /packages/playground/src/app/components/options/options.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { OptionsComponent } from './options.component'; 4 | import { OptionsModule } from './options.module'; 5 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 6 | import { AppSettingsService } from '../../services/app-settings.service'; 7 | import { ReplaySubject } from 'rxjs'; 8 | 9 | describe('OptionsComponent', () => { 10 | let component: OptionsComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(waitForAsync(async () => { 14 | await TestBed.configureTestingModule({ 15 | imports: [OptionsModule, HttpClientTestingModule], 16 | providers: [ 17 | { 18 | provide: AppSettingsService, 19 | useValue: { 20 | get: () => ({ flags: {} }), 21 | watchShowHelp: () => new ReplaySubject().asObservable(), 22 | }, 23 | }, 24 | ], 25 | }).compileComponents(); 26 | })); 27 | 28 | beforeEach(() => { 29 | fixture = TestBed.createComponent(OptionsComponent); 30 | component = fixture.componentInstance; 31 | component.response = { 32 | results: { nativeFeeds: [], genericFeedRules: [] }, 33 | } as any; 34 | fixture.detectChanges(); 35 | }); 36 | 37 | it('should create', () => { 38 | expect(component).toBeTruthy(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/options/options.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionStrategy, 3 | ChangeDetectorRef, 4 | Component, 5 | Input, 6 | OnInit, 7 | } from '@angular/core'; 8 | import { 9 | FeedDetectionResponse, 10 | GenericFeedRule, 11 | NativeFeedRef, 12 | NativeFeedWithParams, 13 | } from '../../services/feed.service'; 14 | import { 15 | AppSettingsService, 16 | FeatureFlags, 17 | } from '../../services/app-settings.service'; 18 | 19 | @Component({ 20 | selector: 'app-options', 21 | templateUrl: './options.component.html', 22 | styleUrls: ['./options.component.scss'], 23 | changeDetection: ChangeDetectionStrategy.OnPush, 24 | }) 25 | export class OptionsComponent implements OnInit { 26 | @Input() 27 | response: FeedDetectionResponse; 28 | @Input() 29 | static: boolean; 30 | nativeFeeds: NativeFeedRef[]; 31 | genericFeedRules: GenericFeedRule[]; 32 | hasChosen: boolean; 33 | watchPageChanges: boolean; 34 | isNativeFeed: boolean; 35 | prerender: boolean; 36 | flags: FeatureFlags; 37 | 38 | constructor( 39 | private readonly settings: AppSettingsService, 40 | private readonly changeDetectorRef: ChangeDetectorRef, 41 | ) {} 42 | 43 | async ngOnInit(): Promise { 44 | await this.settings.waitForInit; 45 | this.flags = this.settings.get().flags; 46 | this.isNativeFeed = 47 | this.response.results.mimeType.toLowerCase().indexOf('xml') > -1; 48 | this.changeDetectorRef.detectChanges(); 49 | } 50 | 51 | private use(fn: () => void) { 52 | this.reset(); 53 | fn(); 54 | this.hasChosen = true; 55 | } 56 | 57 | private reset() { 58 | this.nativeFeeds = null; 59 | this.genericFeedRules = null; 60 | this.prerender = null; 61 | this.watchPageChanges = null; 62 | } 63 | 64 | useNativeFeeds() { 65 | this.use(() => { 66 | this.nativeFeeds = this.response.results.nativeFeeds; 67 | }); 68 | } 69 | 70 | useGenericFeeds() { 71 | this.use(() => { 72 | this.genericFeedRules = this.response.results.genericFeedRules; 73 | }); 74 | } 75 | 76 | useWatchPageChanges() { 77 | this.use(() => { 78 | this.watchPageChanges = true; 79 | }); 80 | } 81 | 82 | useDynamicRendering() { 83 | this.use(() => { 84 | this.prerender = true; 85 | }); 86 | } 87 | 88 | createNativeFeed(): NativeFeedWithParams { 89 | return { 90 | feedUrl: this.response.options.harvestUrl, 91 | debug: false, 92 | }; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/options/options.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { OptionsComponent } from './options.component'; 4 | import { GenericFeedsModule } from '../generic-feeds/generic-feeds.module'; 5 | import { NativeFeedsModule } from '../native-feeds/native-feeds.module'; 6 | import { SectionModule } from '../section/section.module'; 7 | import { WatchPageChangeModule } from '../watch-page-change/watch-page-change.module'; 8 | import { PrerenderModule } from '../prerender/prerender.module'; 9 | import { NativeOptionsModule } from '../native-options/native-options.module'; 10 | import { HelpMessageModule } from '../help-message/help-message.module'; 11 | 12 | @NgModule({ 13 | declarations: [OptionsComponent], 14 | exports: [OptionsComponent], 15 | imports: [ 16 | CommonModule, 17 | GenericFeedsModule, 18 | NativeFeedsModule, 19 | SectionModule, 20 | WatchPageChangeModule, 21 | PrerenderModule, 22 | NativeOptionsModule, 23 | HelpMessageModule, 24 | ], 25 | }) 26 | export class OptionsModule {} 27 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/playground-stateless/playground-stateless.component.scss: -------------------------------------------------------------------------------- 1 | .loading { 2 | display: flex; 3 | position: absolute; 4 | height: 100vh; 5 | width: 100vw; 6 | align-items: center; 7 | justify-content: center; 8 | flex-direction: column; 9 | background: white; 10 | z-index: 1; 11 | } 12 | 13 | .hidden { 14 | display: none !important; 15 | } 16 | .is-expanded { 17 | width: 100%; 18 | } 19 | 20 | .images { 21 | display: flex; 22 | justify-content: center; 23 | height: 200px; 24 | > * { 25 | position: relative; 26 | } 27 | img { 28 | max-height: 100%; 29 | max-width: 100%; 30 | height: auto; 31 | position: absolute; 32 | top: 0; 33 | bottom: 0; 34 | left: 0; 35 | right: 0; 36 | margin: auto; 37 | } 38 | .rss { 39 | width: 100px; 40 | } 41 | .arrow { 42 | width: 50px; 43 | } 44 | .browser { 45 | width: 150px; 46 | } 47 | } 48 | 49 | ::placeholder { 50 | color: rgba(255, 255, 255, 0.3); 51 | opacity: 1; 52 | } 53 | 54 | .is-primary::placeholder { 55 | color: rgba(0, 0, 255, 0.3); 56 | opacity: 1; 57 | } 58 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/playground-stateless/playground-stateless.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { PlaygroundStatelessComponent } from './playground-stateless.component'; 3 | import { PlaygroundStatelessModule } from './playground-stateless.module'; 4 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | import { AppSettingsService } from '../../services/app-settings.service'; 7 | 8 | describe('WelcomeComponent', () => { 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [ 12 | PlaygroundStatelessModule, 13 | HttpClientTestingModule, 14 | RouterTestingModule, 15 | ], 16 | providers: [ 17 | { 18 | provide: AppSettingsService, 19 | useValue: { get: () => ({ flags: {}, publicUrl: '' }) }, 20 | }, 21 | ], 22 | }).compileComponents(); 23 | })); 24 | 25 | it('should create the component', () => { 26 | const fixture = TestBed.createComponent(PlaygroundStatelessComponent); 27 | const app = fixture.debugElement.componentInstance; 28 | 29 | expect(app).toBeTruthy(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/playground-stateless/playground-stateless.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { RouterModule } from '@angular/router'; 5 | 6 | import { PlaygroundStatelessComponent } from './playground-stateless.component'; 7 | import { FooterModule } from '../footer/footer.module'; 8 | import { SpinnerModule } from '../spinner/spinner.module'; 9 | import { FeedModule } from '../feed/feed.module'; 10 | import { HeaderModule } from '../header/header.module'; 11 | 12 | @NgModule({ 13 | declarations: [PlaygroundStatelessComponent], 14 | exports: [PlaygroundStatelessComponent], 15 | imports: [ 16 | CommonModule, 17 | FormsModule, 18 | RouterModule, 19 | FooterModule, 20 | SpinnerModule, 21 | FeedModule, 22 | HeaderModule, 23 | ], 24 | }) 25 | export class PlaygroundStatelessModule {} 26 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/playground/playground.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | 11 | 12 |
13 |
14 |

15 | Previous URLs 16 |

17 |

18 | {{ 19 | url 20 | }} 21 |

22 |
23 |
24 |
25 | 26 | 27 | 28 |
29 |
30 | {{ errorMessage }} 31 |
32 |
33 | 34 |
38 | 44 |
45 | 46 | 47 |
48 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/playground/playground.component.scss: -------------------------------------------------------------------------------- 1 | $primary: blue; 2 | 3 | .is-active { 4 | .sample { 5 | color: white; 6 | } 7 | } 8 | 9 | .blue-frame { 10 | border: 3px solid $primary; 11 | } 12 | 13 | .playground { 14 | height: 100vh; 15 | max-height: 100vh; 16 | max-width: 100vw; 17 | overflow: hidden; 18 | display: flex; 19 | padding-left: 5px; 20 | padding-top: 5px; 21 | flex-direction: column; 22 | } 23 | 24 | button.inactive { 25 | background: transparent; 26 | border: 1px solid black; 27 | } 28 | 29 | .unsupported-website { 30 | background-color: #ffa5a5; 31 | } 32 | 33 | .is-bold { 34 | font-weight: bold; 35 | } 36 | 37 | a.highlight { 38 | color: $primary !important; 39 | } 40 | 41 | .is-expanded { 42 | width: 100%; 43 | } 44 | 45 | .modal-background { 46 | opacity: 0.3; 47 | } 48 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/playground/playground.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { waitForAsync, TestBed } from '@angular/core/testing'; 2 | import { PlaygroundComponent } from './playground.component'; 3 | import { PlaygroundModule } from './playground.module'; 4 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | import { AppSettingsService } from '../../services/app-settings.service'; 7 | 8 | describe('PlaygroundComponent', () => { 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [PlaygroundModule, HttpClientTestingModule, RouterTestingModule], 12 | providers: [ 13 | { 14 | provide: AppSettingsService, 15 | useValue: { get: () => ({ flags: {}, publicUrl: '' }) }, 16 | }, 17 | ], 18 | }).compileComponents(); 19 | })); 20 | 21 | it('should create the component', () => { 22 | const fixture = TestBed.createComponent(PlaygroundComponent); 23 | const app = fixture.debugElement.componentInstance; 24 | 25 | expect(app).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/playground/playground.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionStrategy, 3 | ChangeDetectorRef, 4 | Component, 5 | OnInit, 6 | } from '@angular/core'; 7 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 8 | import { isEmpty } from 'lodash'; 9 | import { ActivatedRoute, Params, Router } from '@angular/router'; 10 | 11 | import { 12 | FeedDetectionResponse, 13 | FeedService, 14 | } from '../../services/feed.service'; 15 | import { build } from '../../../environments/build'; 16 | import { 17 | AppSettingsService, 18 | FeatureFlags, 19 | } from '../../services/app-settings.service'; 20 | import { firstValueFrom } from 'rxjs'; 21 | 22 | export type ArticleRecovery = 'none' | 'metadata' | 'full'; 23 | 24 | @Component({ 25 | selector: 'app-playground', 26 | templateUrl: './playground.component.html', 27 | styleUrls: ['./playground.component.scss'], 28 | changeDetection: ChangeDetectionStrategy.OnPush, 29 | }) 30 | export class PlaygroundComponent implements OnInit { 31 | response: FeedDetectionResponse; 32 | errorMessage: string; 33 | flags: FeatureFlags; 34 | 35 | constructor( 36 | private readonly httpClient: HttpClient, 37 | private readonly router: Router, 38 | private readonly settings: AppSettingsService, 39 | private readonly activatedRoute: ActivatedRoute, 40 | private readonly changeDetectorRef: ChangeDetectorRef, 41 | private readonly feedService: FeedService, 42 | ) { 43 | this.history = PlaygroundComponent.getHistory(); 44 | } 45 | 46 | url: string; 47 | actualUrl: string; 48 | hasResults = false; 49 | isLoading = false; 50 | history: string[]; 51 | 52 | showHistory: boolean; 53 | 54 | private static getHistory(): string[] { 55 | const localHistory = localStorage.getItem('history') || JSON.stringify([]); 56 | return localHistory ? JSON.parse(localHistory) : []; 57 | } 58 | 59 | async ngOnInit() { 60 | this.resetAll(); 61 | this.activatedRoute.queryParams.subscribe((params) => { 62 | if (params.url) { 63 | this.url = params.url; 64 | this.parseFromUrlInternal(); 65 | } 66 | }); 67 | 68 | await this.settings.waitForInit; 69 | this.flags = this.settings.get().flags; 70 | this.changeDetectorRef.detectChanges(); 71 | } 72 | 73 | async parseFromUrl(url: string) { 74 | this.url = url; 75 | if (this.isLoading) { 76 | return; 77 | } 78 | if (this.activatedRoute.snapshot.queryParams.url === this.url) { 79 | this.parseFromUrlInternal(); 80 | } else { 81 | const queryParams: Params = { url: this.url }; 82 | 83 | return this.router.navigate([], { 84 | relativeTo: this.activatedRoute, 85 | queryParams, 86 | queryParamsHandling: 'merge', // remove to replace all query params by provided 87 | }); 88 | } 89 | } 90 | 91 | getVersions() { 92 | return build; 93 | } 94 | 95 | resetAll() { 96 | this.response = null; 97 | this.hasResults = false; 98 | this.actualUrl = null; 99 | this.resetErrors(); 100 | this.changeDetectorRef.detectChanges(); 101 | } 102 | 103 | resetErrors() { 104 | this.errorMessage = null; 105 | } 106 | 107 | getBuildDate() { 108 | const date = new Date(parseInt(this.getVersions().date, 10)); 109 | return `${date.getUTCDate()}-${date.getUTCMonth()}-${date.getUTCFullYear()}`; 110 | } 111 | 112 | parseFromHistoryUrl(url: string) { 113 | return this.parseFromUrl(url); 114 | } 115 | 116 | private parseFromUrlInternal(): void { 117 | if (isEmpty(this.url)) { 118 | this.errorMessage = ''; 119 | this.changeDetectorRef.detectChanges(); 120 | return; 121 | } 122 | 123 | this.resetErrors(); 124 | this.addToHistory(this.url); 125 | 126 | if (!this.url.startsWith('http://') && !this.url.startsWith('https://')) { 127 | this.url = 'http://' + this.url; 128 | } 129 | 130 | try { 131 | // tslint:disable-next-line:no-unused-expression 132 | new URL(this.url); 133 | } catch (e) { 134 | this.errorMessage = 'Please enter a valid url'; 135 | this.changeDetectorRef.detectChanges(); 136 | return; 137 | } 138 | this.isLoading = true; 139 | this.changeDetectorRef.detectChanges(); 140 | 141 | this.fromStaticSource(); 142 | } 143 | 144 | private fromStaticSource() { 145 | console.log('from static source'); 146 | firstValueFrom(this.feedService.discover(this.url)).then( 147 | this.handleParserResponse(), 148 | (error: HttpErrorResponse) => { 149 | this.isLoading = false; 150 | this.hasResults = false; 151 | this.errorMessage = error.message; 152 | this.changeDetectorRef.detectChanges(); 153 | }, 154 | ); 155 | } 156 | 157 | private handleParserResponse() { 158 | return (response: FeedDetectionResponse) => { 159 | const results = response.results; 160 | 161 | results.genericFeedRules = results.genericFeedRules.map((gr, index) => { 162 | gr.id = index; 163 | return gr; 164 | }); 165 | 166 | this.response = response; 167 | 168 | this.hasResults = true; 169 | this.isLoading = false; 170 | this.actualUrl = response.options.harvestUrl; 171 | if (results.failed) { 172 | console.error('Proxy replied an error.', results.errorMessage); 173 | // tslint:disable-next-line:max-line-length 174 | this.errorMessage = results.errorMessage; 175 | } else { 176 | console.log('Proxy replies an generated feed'); 177 | // setTimeout(() => { 178 | // this.applyRule(results.genericFeedRules[0]); 179 | // }, 1000); 180 | // todo mag add fallback option 181 | } 182 | this.changeDetectorRef.detectChanges(); 183 | }; 184 | } 185 | 186 | private addToHistory(url: string) { 187 | let history = this.history.filter((otherUrl) => otherUrl !== url); 188 | history = history.reverse(); 189 | history.push(url); 190 | history = history.reverse(); 191 | history = history.filter((otherUrl, index) => index < 15); 192 | 193 | this.history = history; 194 | 195 | localStorage.setItem('history', JSON.stringify(history)); 196 | } 197 | 198 | clearResults() { 199 | this.url = null; 200 | this.resetAll(); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/playground/playground.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { RouterModule } from '@angular/router'; 5 | 6 | import { PlaygroundComponent } from './playground.component'; 7 | import { GenericFeedsModule } from '../generic-feeds/generic-feeds.module'; 8 | import { NativeFeedsModule } from '../native-feeds/native-feeds.module'; 9 | import { OptionsModule } from '../options/options.module'; 10 | import { FooterModule } from '../footer/footer.module'; 11 | import { SpinnerModule } from '../spinner/spinner.module'; 12 | import { SearchModule } from '../search/search.module'; 13 | import { HeaderModule } from '../header/header.module'; 14 | 15 | @NgModule({ 16 | declarations: [PlaygroundComponent], 17 | exports: [PlaygroundComponent], 18 | imports: [ 19 | CommonModule, 20 | FormsModule, 21 | RouterModule, 22 | GenericFeedsModule, 23 | NativeFeedsModule, 24 | OptionsModule, 25 | FooterModule, 26 | SpinnerModule, 27 | SearchModule, 28 | HeaderModule, 29 | ], 30 | }) 31 | export class PlaygroundModule {} 32 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/prerender/prerender.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 72 | 73 |
74 |
{{ errorMessage }}
75 | Screenshot 76 |
77 |
78 | 79 |
80 |
81 | 82 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |
99 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/prerender/prerender.component.scss: -------------------------------------------------------------------------------- 1 | .source { 2 | width: 450px; 3 | } 4 | 5 | img { 6 | width: 100%; 7 | } 8 | .puppeteer-script { 9 | min-height: 17rem; 10 | } 11 | .error-message { 12 | white-space: pre-wrap; 13 | color: darkred; 14 | background: #ffd7d7; 15 | margin-inline: 4px; 16 | padding: 10px; 17 | } 18 | .is-expanded { 19 | width: 100%; 20 | } 21 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/prerender/prerender.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { PrerenderComponent } from './prerender.component'; 4 | import { PrerenderModule } from './prerender.module'; 5 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 6 | import { AppSettingsService } from '../../services/app-settings.service'; 7 | import { ReplaySubject } from 'rxjs'; 8 | 9 | describe('PrerenderComponent', () => { 10 | let component: PrerenderComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(waitForAsync(async () => { 14 | await TestBed.configureTestingModule({ 15 | imports: [PrerenderModule, HttpClientTestingModule], 16 | providers: [ 17 | { 18 | provide: AppSettingsService, 19 | useValue: { 20 | get: () => ({ flags: {}, urls: {} }), 21 | watchShowHelp: () => new ReplaySubject().asObservable(), 22 | }, 23 | }, 24 | ], 25 | }).compileComponents(); 26 | })); 27 | 28 | beforeEach(() => { 29 | fixture = TestBed.createComponent(PrerenderComponent); 30 | component = fixture.componentInstance; 31 | fixture.detectChanges(); 32 | }); 33 | 34 | it('should create', () => { 35 | expect(component).toBeTruthy(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/prerender/prerender.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionStrategy, 3 | ChangeDetectorRef, 4 | Component, 5 | Input, 6 | OnDestroy, 7 | OnInit, 8 | } from '@angular/core'; 9 | import { 10 | AppSettingsService, 11 | FeatureFlags, 12 | } from '../../services/app-settings.service'; 13 | import { 14 | FeedDetectionResponse, 15 | FeedService, 16 | } from '../../services/feed.service'; 17 | import { firstValueFrom } from 'rxjs'; 18 | import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; 19 | 20 | @Component({ 21 | selector: 'app-prerender', 22 | templateUrl: './prerender.component.html', 23 | styleUrls: ['./prerender.component.scss'], 24 | changeDetection: ChangeDetectionStrategy.OnPush, 25 | }) 26 | export class PrerenderComponent implements OnInit, OnDestroy { 27 | @Input() 28 | siteUrl: string; 29 | 30 | puppeteerScript: string; 31 | 32 | hasChosen: boolean; 33 | watchPageChanges: boolean; 34 | genericFeedRules: boolean; 35 | error: boolean; 36 | errorMessage: string; 37 | private imageUrl: string; 38 | 39 | imageUrlSanitizes: SafeResourceUrl; 40 | response: FeedDetectionResponse; 41 | placeHolderScript = `Do anyting you can do in a dev console. Will be passed as page.evaluate()`; 42 | isLoading: boolean; 43 | flags: FeatureFlags; 44 | 45 | constructor( 46 | private readonly appSettings: AppSettingsService, 47 | private readonly changeDetectorRef: ChangeDetectorRef, 48 | private readonly sanitizer: DomSanitizer, 49 | private readonly feed: FeedService, 50 | ) {} 51 | 52 | ngOnDestroy(): void { 53 | window.URL.revokeObjectURL(this.imageUrl); 54 | } 55 | 56 | async ngOnInit() { 57 | await this.appSettings.waitForInit; 58 | this.flags = this.appSettings.get().flags; 59 | this.refresh(); 60 | } 61 | 62 | private use(fn: () => void) { 63 | this.reset(); 64 | fn(); 65 | this.hasChosen = true; 66 | } 67 | 68 | private reset() { 69 | this.genericFeedRules = null; 70 | this.watchPageChanges = null; 71 | } 72 | 73 | useWatchPageChanges() { 74 | this.use(() => { 75 | this.watchPageChanges = true; 76 | }); 77 | } 78 | 79 | useGenericFeeds() { 80 | this.use(() => { 81 | this.genericFeedRules = true; 82 | }); 83 | } 84 | 85 | refresh() { 86 | this.isLoading = true; 87 | this.response = null; 88 | return firstValueFrom( 89 | this.feed.discover(this.siteUrl, this.puppeteerScript, true), 90 | ).then( 91 | (successResponse) => { 92 | this.error = false; 93 | this.isLoading = false; 94 | this.handleResponse(successResponse); 95 | }, 96 | (errorResponse) => { 97 | this.error = true; 98 | this.isLoading = false; 99 | this.handleResponse(errorResponse); 100 | }, 101 | ); 102 | } 103 | 104 | private handleResponse(response: FeedDetectionResponse) { 105 | const results = response.results; 106 | 107 | results.genericFeedRules = results.genericFeedRules.map((gr, index) => { 108 | gr.id = index; 109 | return gr; 110 | }); 111 | 112 | this.response = response; 113 | this.errorMessage = results.errorMessage; 114 | 115 | const blob = new Blob([base64ToArrayBuffer(results.screenshot)], { 116 | type: 'image/png', 117 | }); 118 | this.imageUrl = window.URL.createObjectURL(blob); 119 | this.imageUrlSanitizes = this.sanitizer.bypassSecurityTrustResourceUrl( 120 | this.imageUrl, 121 | ); 122 | this.changeDetectorRef.detectChanges(); 123 | } 124 | 125 | edit() { 126 | this.hasChosen = false; 127 | this.reset(); 128 | } 129 | } 130 | 131 | function base64ToArrayBuffer(base64: string): ArrayBufferLike { 132 | const binaryString = atob(base64); 133 | const len = binaryString.length; 134 | const bytes = new Uint8Array(len); 135 | for (let i = 0; i < len; i++) { 136 | bytes[i] = binaryString.charCodeAt(i); 137 | } 138 | return bytes; 139 | } 140 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/prerender/prerender.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { PrerenderComponent } from './prerender.component'; 4 | import { SectionModule } from '../section/section.module'; 5 | import { GenericFeedsModule } from '../generic-feeds/generic-feeds.module'; 6 | import { WatchPageChangeModule } from '../watch-page-change/watch-page-change.module'; 7 | import { FormsModule } from '@angular/forms'; 8 | import { SpinnerModule } from '../spinner/spinner.module'; 9 | import { HelpMessageModule } from '../help-message/help-message.module'; 10 | 11 | @NgModule({ 12 | declarations: [PrerenderComponent], 13 | exports: [PrerenderComponent], 14 | imports: [ 15 | CommonModule, 16 | SectionModule, 17 | GenericFeedsModule, 18 | FormsModule, 19 | WatchPageChangeModule, 20 | SpinnerModule, 21 | HelpMessageModule, 22 | ], 23 | }) 24 | export class PrerenderModule {} 25 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/publish-feed/publish-feed.component.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/publish-feed/publish-feed.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/app/components/publish-feed/publish-feed.component.scss -------------------------------------------------------------------------------- /packages/playground/src/app/components/publish-feed/publish-feed.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { PublishFeedComponent } from './publish-feed.component'; 4 | import { PublishFeedModule } from './publish-feed.module'; 5 | 6 | describe('PublishFeedComponent', () => { 7 | let component: PublishFeedComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(async () => { 11 | await TestBed.configureTestingModule({ 12 | imports: [PublishFeedModule], 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PublishFeedComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/publish-feed/publish-feed.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-publish-feed', 5 | templateUrl: './publish-feed.component.html', 6 | styleUrls: ['./publish-feed.component.scss'], 7 | }) 8 | export class PublishFeedComponent implements OnInit { 9 | constructor() {} 10 | 11 | ngOnInit(): void {} 12 | } 13 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/publish-feed/publish-feed.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { PublishFeedComponent } from './publish-feed.component'; 4 | 5 | @NgModule({ 6 | declarations: [PublishFeedComponent], 7 | imports: [CommonModule], 8 | }) 9 | export class PublishFeedModule {} 10 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push-options/push-options.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 11 | 12 | 13 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push-options/push-options.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/app/components/push-options/push-options.component.scss -------------------------------------------------------------------------------- /packages/playground/src/app/components/push-options/push-options.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { PushOptionsComponent } from './push-options.component'; 4 | import { PushOptionsModule } from './push-options.module'; 5 | 6 | describe('PushOptionsComponent', () => { 7 | let component: PushOptionsComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(async () => { 11 | await TestBed.configureTestingModule({ 12 | imports: [PushOptionsModule], 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PushOptionsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push-options/push-options.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { 3 | GenericFeedWithParams, 4 | NativeFeedWithParams, 5 | } from '../../services/feed.service'; 6 | 7 | @Component({ 8 | selector: 'app-push-as-notification', 9 | templateUrl: './push-options.component.html', 10 | styleUrls: ['./push-options.component.scss'], 11 | }) 12 | export class PushOptionsComponent implements OnInit { 13 | hasChosen: boolean; 14 | mobile: boolean; 15 | web: boolean; 16 | 17 | @Input() 18 | nativeFeed: NativeFeedWithParams; 19 | @Input() 20 | genericFeedRule: GenericFeedWithParams; 21 | 22 | constructor() {} 23 | 24 | ngOnInit(): void {} 25 | 26 | private use(fn: () => void) { 27 | this.reset(); 28 | fn(); 29 | this.hasChosen = true; 30 | } 31 | 32 | private reset() { 33 | this.mobile = null; 34 | this.web = null; 35 | } 36 | 37 | useMobile() { 38 | this.use(() => { 39 | this.mobile = true; 40 | }); 41 | } 42 | 43 | useWeb() { 44 | this.use(() => { 45 | this.web = true; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push-options/push-options.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { PushOptionsComponent } from './push-options.component'; 4 | import { SectionModule } from '../section/section.module'; 5 | import { PushToWebModule } from '../push/push-to-web/push-to-web.module'; 6 | import { PushToMobileModule } from '../push/push-to-mobile/push-to-mobile.module'; 7 | 8 | @NgModule({ 9 | declarations: [PushOptionsComponent], 10 | exports: [PushOptionsComponent], 11 | imports: [CommonModule, SectionModule, PushToWebModule, PushToMobileModule], 12 | }) 13 | export class PushOptionsModule {} 14 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-as-email/push-as-email.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 |
7 |
8 | 14 |
15 |
16 |
17 | 18 |
19 | 20 |
21 | 22 |
23 |
24 | 25 |
26 | 27 |
28 | 29 |
30 |
31 | 32 | 33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-as-email/push-as-email.component.scss: -------------------------------------------------------------------------------- 1 | .is-expanded { 2 | width: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-as-email/push-as-email.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { PushAsEmailComponent } from './push-as-email.component'; 4 | 5 | describe('PushAsEmailComponent', () => { 6 | let component: PushAsEmailComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [PushAsEmailComponent], 12 | }).compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(PushAsEmailComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-as-email/push-as-email.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { 3 | GenericFeedWithParams, 4 | NativeFeedWithParams, 5 | } from '../../../services/feed.service'; 6 | 7 | @Component({ 8 | selector: 'app-push-as-email', 9 | templateUrl: './push-as-email.component.html', 10 | styleUrls: ['./push-as-email.component.scss'], 11 | }) 12 | export class PushAsEmailComponent implements OnInit { 13 | @Input() 14 | nativeFeed: NativeFeedWithParams; 15 | @Input() 16 | genericFeedRule: GenericFeedWithParams; 17 | 18 | digest = 'no'; 19 | email: string; 20 | 21 | constructor() {} 22 | 23 | ngOnInit(): void {} 24 | } 25 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-as-email/push-as-email.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { PushAsEmailComponent } from './push-as-email.component'; 5 | import { SectionModule } from '../../section/section.module'; 6 | 7 | @NgModule({ 8 | declarations: [PushAsEmailComponent], 9 | exports: [PushAsEmailComponent], 10 | imports: [CommonModule, SectionModule, FormsModule], 11 | }) 12 | export class PushAsEmailModule {} 13 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-as-webhook/push-as-webhook.component.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-as-webhook/push-as-webhook.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/app/components/push/push-as-webhook/push-as-webhook.component.scss -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-as-webhook/push-as-webhook.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { PushAsWebhookComponent } from './push-as-webhook.component'; 4 | 5 | describe('PushAsWebhookComponent', () => { 6 | let component: PushAsWebhookComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [PushAsWebhookComponent], 12 | }).compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(PushAsWebhookComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-as-webhook/push-as-webhook.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { 3 | GenericFeedWithParams, 4 | NativeFeedWithParams, 5 | } from '../../../services/feed.service'; 6 | 7 | @Component({ 8 | selector: 'app-push-as-webhook', 9 | templateUrl: './push-as-webhook.component.html', 10 | styleUrls: ['./push-as-webhook.component.scss'], 11 | }) 12 | export class PushAsWebhookComponent implements OnInit { 13 | @Input() 14 | nativeFeed: NativeFeedWithParams; 15 | @Input() 16 | genericFeedRule: GenericFeedWithParams; 17 | 18 | constructor() {} 19 | 20 | ngOnInit(): void {} 21 | } 22 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-as-webhook/push-as-webhook.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { PushAsWebhookComponent } from './push-as-webhook.component'; 4 | import { SectionModule } from '../../section/section.module'; 5 | 6 | @NgModule({ 7 | declarations: [PushAsWebhookComponent], 8 | exports: [PushAsWebhookComponent], 9 | imports: [CommonModule, SectionModule], 10 | }) 11 | export class PushAsWebhookModule {} 12 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-to-mobile/push-to-mobile.component.html: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-to-mobile/push-to-mobile.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/app/components/push/push-to-mobile/push-to-mobile.component.scss -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-to-mobile/push-to-mobile.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { PushToMobileComponent } from './push-to-mobile.component'; 4 | 5 | describe('PushToMobileComponent', () => { 6 | let component: PushToMobileComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [PushToMobileComponent], 12 | }).compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(PushToMobileComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-to-mobile/push-to-mobile.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-push-to-mobile', 5 | templateUrl: './push-to-mobile.component.html', 6 | styleUrls: ['./push-to-mobile.component.scss'], 7 | }) 8 | export class PushToMobileComponent implements OnInit { 9 | constructor() {} 10 | 11 | ngOnInit(): void {} 12 | } 13 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-to-mobile/push-to-mobile.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { PushToMobileComponent } from './push-to-mobile.component'; 4 | import { SectionModule } from '../../section/section.module'; 5 | 6 | @NgModule({ 7 | declarations: [PushToMobileComponent], 8 | exports: [PushToMobileComponent], 9 | imports: [CommonModule, SectionModule], 10 | }) 11 | export class PushToMobileModule {} 12 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-to-web/push-to-web.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-to-web/push-to-web.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/app/components/push/push-to-web/push-to-web.component.scss -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-to-web/push-to-web.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { PushToWebComponent } from './push-to-web.component'; 4 | 5 | describe('PushToWebComponent', () => { 6 | let component: PushToWebComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [PushToWebComponent], 12 | }).compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(PushToWebComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-to-web/push-to-web.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-push-to-web', 5 | templateUrl: './push-to-web.component.html', 6 | styleUrls: ['./push-to-web.component.scss'], 7 | }) 8 | export class PushToWebComponent implements OnInit { 9 | constructor() {} 10 | 11 | ngOnInit(): void {} 12 | } 13 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-to-web/push-to-web.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { PushToWebComponent } from './push-to-web.component'; 4 | import { SectionModule } from '../../section/section.module'; 5 | 6 | @NgModule({ 7 | declarations: [PushToWebComponent], 8 | exports: [PushToWebComponent], 9 | imports: [CommonModule, SectionModule], 10 | }) 11 | export class PushToWebModule {} 12 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-updates/push-updates.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 25 | 26 | 27 | 32 | 37 | 42 |
43 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-updates/push-updates.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/app/components/push/push-updates/push-updates.component.scss -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-updates/push-updates.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { PushUpdatesComponent } from './push-updates.component'; 4 | 5 | describe('PushUpdatesComponent', () => { 6 | let component: PushUpdatesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [PushUpdatesComponent], 12 | }).compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(PushUpdatesComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-updates/push-updates.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { 3 | GenericFeedWithParams, 4 | NativeFeedWithParams, 5 | } from '../../../services/feed.service'; 6 | 7 | @Component({ 8 | selector: 'app-push-updates', 9 | templateUrl: './push-updates.component.html', 10 | styleUrls: ['./push-updates.component.scss'], 11 | }) 12 | export class PushUpdatesComponent implements OnInit { 13 | hasChosen: boolean; 14 | 15 | @Input() 16 | nativeFeed: NativeFeedWithParams; 17 | @Input() 18 | genericFeedRule: GenericFeedWithParams; 19 | 20 | webhook: boolean; 21 | pushNotification: boolean; 22 | email: boolean; 23 | calendar: boolean; 24 | 25 | constructor() {} 26 | 27 | ngOnInit(): void {} 28 | 29 | private use(fn: () => void) { 30 | this.reset(); 31 | fn(); 32 | this.hasChosen = true; 33 | } 34 | 35 | private reset() { 36 | this.hasChosen = false; 37 | this.webhook = null; 38 | this.email = null; 39 | this.pushNotification = null; 40 | this.calendar = null; 41 | } 42 | 43 | useWebhook() { 44 | this.use(() => { 45 | this.webhook = true; 46 | }); 47 | } 48 | 49 | usePushNotification() { 50 | this.use(() => { 51 | this.pushNotification = true; 52 | }); 53 | } 54 | 55 | useEmail() { 56 | this.use(() => { 57 | this.email = true; 58 | }); 59 | } 60 | 61 | useCalendar() { 62 | this.use(() => { 63 | this.calendar = true; 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/push/push-updates/push-updates.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { PushUpdatesComponent } from './push-updates.component'; 4 | import { SectionModule } from '../../section/section.module'; 5 | import { PushAsWebhookModule } from '../push-as-webhook/push-as-webhook.module'; 6 | import { PushAsEmailModule } from '../push-as-email/push-as-email.module'; 7 | import { PushOptionsModule } from '../../push-options/push-options.module'; 8 | 9 | @NgModule({ 10 | declarations: [PushUpdatesComponent], 11 | exports: [PushUpdatesComponent], 12 | imports: [ 13 | CommonModule, 14 | SectionModule, 15 | PushAsWebhookModule, 16 | PushAsEmailModule, 17 | PushOptionsModule, 18 | ], 19 | }) 20 | export class PushUpdatesModule {} 21 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/refine-feed/refine-feed.component.scss: -------------------------------------------------------------------------------- 1 | .feed { 2 | flex: 1 0 auto; 3 | width: 500px; 4 | overflow: auto; 5 | background: #efefef; 6 | } 7 | 8 | .is-expanded { 9 | width: 100%; 10 | } 11 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/refine-feed/refine-feed.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { RefineFeedComponent } from './refine-feed.component'; 4 | import { RefineFeedModule } from './refine-feed.module'; 5 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 6 | import { AppSettingsService } from '../../services/app-settings.service'; 7 | import { ReplaySubject } from 'rxjs'; 8 | 9 | describe('RefineFeedComponent', () => { 10 | let component: RefineFeedComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(waitForAsync(async () => { 14 | await TestBed.configureTestingModule({ 15 | imports: [RefineFeedModule, HttpClientTestingModule], 16 | providers: [ 17 | { 18 | provide: AppSettingsService, 19 | useValue: { 20 | get: () => ({ flags: {} }), 21 | watchShowHelp: () => new ReplaySubject().asObservable(), 22 | }, 23 | }, 24 | ], 25 | }).compileComponents(); 26 | })); 27 | 28 | beforeEach(() => { 29 | fixture = TestBed.createComponent(RefineFeedComponent); 30 | component = fixture.componentInstance; 31 | fixture.detectChanges(); 32 | }); 33 | 34 | it('should create', () => { 35 | expect(component).toBeTruthy(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/refine-feed/refine-feed.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionStrategy, 3 | ChangeDetectorRef, 4 | Component, 5 | Input, 6 | OnInit, 7 | } from '@angular/core'; 8 | import { ArticleRecovery } from '../playground/playground.component'; 9 | import { 10 | FeedService, 11 | FeedWizardParams, 12 | GenericFeedWithParams, 13 | NativeFeedWithParams, 14 | } from '../../services/feed.service'; 15 | import { firstValueFrom } from 'rxjs'; 16 | import { JsonFeed } from '../feed/feed.component'; 17 | import { 18 | AppSettingsService, 19 | FeatureFlags, 20 | } from '../../services/app-settings.service'; 21 | import { clone } from 'lodash'; 22 | 23 | interface FilterExample { 24 | name: string; 25 | value: string; 26 | } 27 | 28 | @Component({ 29 | selector: 'app-refine-feed', 30 | templateUrl: './refine-feed.component.html', 31 | styleUrls: ['./refine-feed.component.scss'], 32 | changeDetection: ChangeDetectionStrategy.OnPush, 33 | }) 34 | export class RefineFeedComponent implements OnInit { 35 | @Input() 36 | private nativeFeedValue: NativeFeedWithParams; 37 | @Input() 38 | private genericFeedValue: GenericFeedWithParams; 39 | nativeFeed: NativeFeedWithParams; 40 | genericFeed: GenericFeedWithParams; 41 | @Input() 42 | showArticleRecovery: boolean; 43 | @Input() 44 | showDigest: boolean; 45 | @Input() 46 | showThrottle: boolean; 47 | @Input() 48 | showFilter: boolean; 49 | 50 | articleRecovery: ArticleRecovery = 'none'; 51 | filter = ''; 52 | loading = false; 53 | 54 | jsonFeed: JsonFeed; 55 | hasChosen: boolean; 56 | export: boolean; 57 | feedUrl: string; 58 | filterSamples: FilterExample[] = [ 59 | { 60 | name: 'Choose from samples', 61 | value: '', 62 | }, 63 | { 64 | name: 'Must include', 65 | value: 'justToWord', 66 | }, 67 | { 68 | name: 'Must not include', 69 | value: '-prefixBadWordWithMinus', 70 | }, 71 | // { 72 | // name: 'With link count', 73 | // value: 'links > 0', 74 | // }, 75 | ]; 76 | currentSample = ''; 77 | 78 | digestWindow = ''; 79 | digestStartingAt: Date; 80 | 81 | useThrottling = false; 82 | throttleMaxArticles = 5; 83 | throttleSortBy = 'score'; 84 | useDigest = false; 85 | flags: FeatureFlags; 86 | 87 | constructor( 88 | private readonly changeDetectorRef: ChangeDetectorRef, 89 | private readonly feedService: FeedService, 90 | private readonly appSettings: AppSettingsService, 91 | ) {} 92 | 93 | async ngOnInit() { 94 | this.nativeFeed = clone(this.nativeFeedValue); 95 | this.genericFeed = clone(this.genericFeedValue); 96 | 97 | await this.appSettings.waitForInit; 98 | this.flags = this.appSettings.get().flags; 99 | this.apply(); 100 | } 101 | 102 | private use(fn: () => void) { 103 | if (this.valid()) { 104 | this.reset(); 105 | fn(); 106 | this.hasChosen = true; 107 | } 108 | } 109 | 110 | private reset() { 111 | this.export = null; 112 | } 113 | 114 | useExport() { 115 | this.use(() => { 116 | this.export = true; 117 | }); 118 | } 119 | 120 | apply() { 121 | this.loading = true; 122 | this.jsonFeed = null; 123 | if (this.nativeFeed) { 124 | const params = this.updateParams(this.nativeFeed); 125 | this.feedUrl = this.feedService.createFeedUrlForNative(params); 126 | firstValueFrom(this.feedService.transformNativeFeed(params)).then( 127 | (response) => this.handleResponse(response), 128 | ); 129 | } 130 | if (this.genericFeed) { 131 | const params = this.updateParams(this.genericFeed); 132 | this.feedUrl = this.feedService.createFeedUrlForGeneric(params); 133 | // todo mag feedUrl must be constructed from params 134 | firstValueFrom(this.feedService.fetchGenericFeed(params)).then( 135 | (response) => this.handleResponse(response), 136 | ); 137 | } 138 | } 139 | 140 | private handleResponse(response: any) { 141 | this.jsonFeed = response; 142 | this.loading = false; 143 | this.changeDetectorRef.detectChanges(); 144 | } 145 | 146 | edit() { 147 | this.reset(); 148 | this.hasChosen = false; 149 | } 150 | 151 | private valid() { 152 | return true; 153 | } 154 | 155 | private updateParams(feed: T): T { 156 | feed.filter = this.filter; 157 | feed.articleRecovery = this.articleRecovery; 158 | return feed; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/refine-feed/refine-feed.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RefineFeedComponent } from './refine-feed.component'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { SectionModule } from '../section/section.module'; 6 | import { FeedModule } from '../feed/feed.module'; 7 | import { SpinnerModule } from '../spinner/spinner.module'; 8 | import { ExportOptionsModule } from '../export-options/export-options.module'; 9 | import { HelpMessageModule } from '../help-message/help-message.module'; 10 | 11 | @NgModule({ 12 | declarations: [RefineFeedComponent], 13 | exports: [RefineFeedComponent], 14 | imports: [ 15 | CommonModule, 16 | SectionModule, 17 | FormsModule, 18 | FeedModule, 19 | SpinnerModule, 20 | ExportOptionsModule, 21 | HelpMessageModule, 22 | ], 23 | }) 24 | export class RefineFeedModule {} 25 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/search/search.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 16 |
17 | 18 | 19 | 20 |
21 | 28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/search/search.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/app/components/search/search.component.scss -------------------------------------------------------------------------------- /packages/playground/src/app/components/search/search.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { SearchComponent } from './search.component'; 4 | import { SearchModule } from './search.module'; 5 | 6 | describe('SearchComponent', () => { 7 | let component: SearchComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(async () => { 11 | await TestBed.configureTestingModule({ 12 | imports: [SearchModule], 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SearchComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/search/search.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { debounce } from 'lodash'; 3 | 4 | @Component({ 5 | selector: 'app-search', 6 | templateUrl: './search.component.html', 7 | styleUrls: ['./search.component.scss'], 8 | }) 9 | export class SearchComponent { 10 | @Input() 11 | url: string; 12 | @Input() 13 | placeholder: string; 14 | // tslint:disable-next-line:no-output-native 15 | @Output() 16 | submitUrl = new EventEmitter(); 17 | @Output() 18 | clearResults = new EventEmitter(); 19 | // tslint:disable-next-line:no-output-native 20 | @Output() 21 | change = new EventEmitter(); 22 | 23 | isLoading = false; 24 | @Input() 25 | showSubmit = true; 26 | @Input() 27 | buttonText = 'Search'; 28 | 29 | constructor() { 30 | this.callTrigger = debounce(this.callTrigger, 50); 31 | } 32 | 33 | callTrigger() { 34 | console.log('callTrigger'); 35 | this.submitUrl.emit(this.url); 36 | } 37 | 38 | callChange(url: string) { 39 | this.change.emit(url); 40 | } 41 | 42 | clear() { 43 | this.url = ''; 44 | this.clearResults.emit(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/search/search.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { SearchComponent } from './search.component'; 4 | import { FormsModule } from '@angular/forms'; 5 | 6 | @NgModule({ 7 | declarations: [SearchComponent], 8 | exports: [SearchComponent], 9 | imports: [CommonModule, FormsModule], 10 | }) 11 | export class SearchModule {} 12 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/section/section.component.html: -------------------------------------------------------------------------------- 1 |
2 |
{{ title }}
3 |
4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/section/section.component.scss: -------------------------------------------------------------------------------- 1 | .section-title { 2 | border-bottom: 4px solid black; 3 | padding-top: 8px; 4 | padding-bottom: 6px; 5 | font-weight: bold; 6 | text-align: center; 7 | white-space: nowrap; 8 | min-width: 200px; 9 | 10 | &.active { 11 | border-bottom-color: blue; 12 | color: blue; 13 | } 14 | } 15 | 16 | .section-body { 17 | padding-top: 10px; 18 | display: flex; 19 | flex: 1; 20 | } 21 | 22 | .section-wrapper { 23 | display: flex; 24 | flex: 1; 25 | flex-direction: column; 26 | margin-right: 5px; 27 | } 28 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/section/section.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { SectionComponent } from './section.component'; 4 | import { SectionModule } from './section.module'; 5 | 6 | describe('SectionComponent', () => { 7 | let component: SectionComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(async () => { 11 | await TestBed.configureTestingModule({ 12 | imports: [SectionModule], 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SectionComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/section/section.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-section', 5 | templateUrl: './section.component.html', 6 | styleUrls: ['./section.component.scss'], 7 | }) 8 | export class SectionComponent implements OnInit { 9 | @Input() 10 | title: string; 11 | 12 | @Input() 13 | active: boolean; 14 | 15 | constructor() {} 16 | 17 | ngOnInit(): void {} 18 | } 19 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/section/section.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { SectionComponent } from './section.component'; 4 | 5 | @NgModule({ 6 | declarations: [SectionComponent], 7 | exports: [SectionComponent], 8 | imports: [CommonModule], 9 | }) 10 | export class SectionModule {} 11 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/spinner/spinner.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/spinner/spinner.component.scss: -------------------------------------------------------------------------------- 1 | .lds-facebook { 2 | display: inline-block; 3 | position: relative; 4 | width: 80px; 5 | height: 80px; 6 | transform: scale(0.5); 7 | transform-origin: 0 0; 8 | } 9 | .lds-facebook div { 10 | display: inline-block; 11 | position: absolute; 12 | left: 8px; 13 | width: 16px; 14 | background: #575757; 15 | animation: lds-facebook 1.2s cubic-bezier(0, 0.5, 0.5, 1) infinite; 16 | } 17 | .lds-facebook div:nth-child(1) { 18 | left: 8px; 19 | animation-delay: -0.24s; 20 | } 21 | .lds-facebook div:nth-child(2) { 22 | left: 32px; 23 | animation-delay: -0.12s; 24 | } 25 | .lds-facebook div:nth-child(3) { 26 | left: 56px; 27 | animation-delay: 0; 28 | } 29 | @keyframes lds-facebook { 30 | 0% { 31 | top: 8px; 32 | height: 64px; 33 | } 34 | 50%, 35 | 100% { 36 | top: 24px; 37 | height: 32px; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/spinner/spinner.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { SpinnerComponent } from './spinner.component'; 4 | import { SpinnerModule } from './spinner.module'; 5 | 6 | describe('SpinnerComponent', () => { 7 | let component: SpinnerComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(async () => { 11 | await TestBed.configureTestingModule({ 12 | imports: [SpinnerModule], 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SpinnerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/spinner/spinner.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-spinner', 5 | templateUrl: './spinner.component.html', 6 | styleUrls: ['./spinner.component.scss'], 7 | }) 8 | export class SpinnerComponent implements OnInit { 9 | constructor() {} 10 | 11 | ngOnInit(): void {} 12 | } 13 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/spinner/spinner.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { SpinnerComponent } from './spinner.component'; 4 | 5 | @NgModule({ 6 | declarations: [SpinnerComponent], 7 | exports: [SpinnerComponent], 8 | imports: [CommonModule], 9 | }) 10 | export class SpinnerModule {} 11 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/watch-page-change/watch-page-change.component.html: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/watch-page-change/watch-page-change.component.scss: -------------------------------------------------------------------------------- 1 | .is-expanded { 2 | width: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/watch-page-change/watch-page-change.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { WatchPageChangeComponent } from './watch-page-change.component'; 4 | import { WatchPageChangeModule } from './watch-page-change.module'; 5 | 6 | describe('WatchPageChangeComponent', () => { 7 | let component: WatchPageChangeComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(async () => { 11 | await TestBed.configureTestingModule({ 12 | imports: [WatchPageChangeModule], 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(WatchPageChangeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/watch-page-change/watch-page-change.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | export interface SiteUrl { 4 | url: string; 5 | prerender?: boolean; 6 | script?: string; 7 | } 8 | 9 | @Component({ 10 | selector: 'app-watch-page-change', 11 | templateUrl: './watch-page-change.component.html', 12 | styleUrls: ['./watch-page-change.component.scss'], 13 | }) 14 | export class WatchPageChangeComponent implements OnInit { 15 | @Input() 16 | body: string; 17 | @Input() 18 | siteUrl: SiteUrl; 19 | mode = 'selector'; 20 | 21 | constructor() {} 22 | 23 | ngOnInit(): void {} 24 | } 25 | -------------------------------------------------------------------------------- /packages/playground/src/app/components/watch-page-change/watch-page-change.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { WatchPageChangeComponent } from './watch-page-change.component'; 4 | import { SectionModule } from '../section/section.module'; 5 | import { FormsModule } from '@angular/forms'; 6 | 7 | @NgModule({ 8 | declarations: [WatchPageChangeComponent], 9 | exports: [WatchPageChangeComponent], 10 | imports: [CommonModule, SectionModule, FormsModule], 11 | }) 12 | export class WatchPageChangeModule {} 13 | -------------------------------------------------------------------------------- /packages/playground/src/app/services/app-settings.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 4 | import { AppSettingsService } from './app-settings.service'; 5 | 6 | describe('AppSettingsService', () => { 7 | beforeEach(() => 8 | TestBed.configureTestingModule({ 9 | imports: [HttpClientTestingModule], 10 | }), 11 | ); 12 | 13 | it('should be created', () => { 14 | const service: AppSettingsService = TestBed.inject(AppSettingsService); 15 | expect(service).toBeTruthy(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/playground/src/app/services/app-settings.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { firstValueFrom, Observable, ReplaySubject } from 'rxjs'; 4 | 5 | export interface Field { 6 | name: string; 7 | type: string; 8 | } 9 | export interface AppAnnouncement { 10 | id: string; 11 | fields: Field[]; 12 | title: string; 13 | body: string; 14 | submittable: boolean; 15 | showIf: ''; 16 | } 17 | export interface FeatureFlags { 18 | canPrerender: boolean; 19 | willExtractFulltext: boolean; 20 | canMail: boolean; 21 | canPush: boolean; 22 | stateless: boolean; 23 | } 24 | 25 | export interface ApiUrls { 26 | webToFeed: string; 27 | host: string; 28 | transformFeed: string; 29 | discoverFeeds: string; 30 | } 31 | 32 | export interface AppSettings { 33 | flags: FeatureFlags; 34 | urls: ApiUrls; 35 | announcements: AppAnnouncement[]; 36 | webToFeedVersion: boolean; 37 | } 38 | 39 | @Injectable({ 40 | providedIn: 'root', 41 | }) 42 | export class AppSettingsService { 43 | private appSettings: AppSettings; 44 | public readonly waitForInit: Promise; 45 | private showHelp: boolean; 46 | private helpSubject = new ReplaySubject(); 47 | 48 | constructor(private readonly httpClient: HttpClient) { 49 | this.waitForInit = this.init(); 50 | } 51 | private async init() { 52 | this.appSettings = await firstValueFrom( 53 | this.httpClient.get(`api/legacy/settings`), 54 | ); 55 | } 56 | 57 | get(): AppSettings { 58 | return this.appSettings; 59 | } 60 | 61 | watchShowHelp(): Observable { 62 | return this.helpSubject.asObservable(); 63 | } 64 | 65 | setShowHelp(change: boolean): void { 66 | this.showHelp = change; 67 | this.helpSubject.next(change); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/playground/src/app/services/feed.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { FeedService } from './feed.service'; 4 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 5 | import { AppSettingsService } from './app-settings.service'; 6 | 7 | describe('FeedService', () => { 8 | beforeEach(() => 9 | TestBed.configureTestingModule({ 10 | imports: [HttpClientTestingModule], 11 | providers: [ 12 | { 13 | provide: AppSettingsService, 14 | useValue: { get: () => ({ publicUrl: '' }) }, 15 | }, 16 | ], 17 | }), 18 | ); 19 | 20 | it('should be created', () => { 21 | const service = TestBed.inject(FeedService); 22 | expect(service).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/playground/src/app/services/feed.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { firstValueFrom, Observable } from 'rxjs'; 4 | import { ArticleRecovery } from '../components/playground/playground.component'; 5 | import { JsonFeed } from '../components/feed/feed.component'; 6 | import { ApiUrls, AppSettingsService } from './app-settings.service'; 7 | 8 | export interface Article { 9 | id: string; 10 | title: string; 11 | tags: string[]; 12 | content_text: string; 13 | content_raw: string; 14 | content_raw_mime: string; 15 | main_image_url: string; 16 | url: string; 17 | author: string; 18 | enclosures: null; 19 | date_published: string; 20 | commentsFeedUrl: string; 21 | } 22 | 23 | export interface GenericFeedRule { 24 | id?: number | string; 25 | linkXPath: string; 26 | extendContext: string; 27 | contextXPath: string; 28 | dateXPath: string; 29 | feedUrl: string; 30 | count: number; 31 | score: number; 32 | samples: Article[]; 33 | } 34 | 35 | export interface FeedReduce { 36 | limit: number; 37 | sortByField: string; 38 | sortAsc: boolean; 39 | // sliding window 40 | } 41 | 42 | export interface FeedWizardParams { 43 | filter?: string; 44 | articleRecovery?: ArticleRecovery; 45 | targetFormat?: FeedFormat; 46 | reduce?: FeedReduce; 47 | digest?: boolean; 48 | prerendered?: boolean; 49 | puppeteerScript?: string; 50 | } 51 | 52 | export interface NativeFeedWithParams extends FeedWizardParams { 53 | feedUrl: string; 54 | debug: boolean; 55 | } 56 | 57 | export interface GenericFeedWithParams 58 | extends GenericFeedRule, 59 | FeedWizardParams { 60 | harvestUrl: string; 61 | debug: boolean; 62 | } 63 | 64 | export interface FeedDetectionOptions { 65 | harvestUrl: string; 66 | original: string; 67 | withJavascript: boolean; 68 | } 69 | 70 | export interface NativeFeedRef { 71 | url: string; 72 | type: string; 73 | title: string; 74 | } 75 | 76 | export interface FeedDetectionResults { 77 | genericFeedRules: GenericFeedRule[]; 78 | relatedFeeds: []; 79 | mimeType: string; 80 | nativeFeeds: NativeFeedRef[]; 81 | body: string; 82 | failed: boolean; 83 | errorMessage?: string; 84 | screenshot?: string; 85 | } 86 | 87 | export interface FeedDetectionResponse { 88 | options: FeedDetectionOptions; 89 | results: FeedDetectionResults; 90 | } 91 | 92 | export type FeedFormat = 'atom' | 'rss' | 'json'; 93 | 94 | export type PermanentFeed = string; 95 | 96 | @Injectable({ 97 | providedIn: 'root', 98 | }) 99 | export class FeedService { 100 | private urls: ApiUrls; 101 | constructor( 102 | private readonly httpClient: HttpClient, 103 | settings: AppSettingsService, 104 | ) { 105 | settings.waitForInit.then(() => { 106 | this.urls = settings.get().urls; 107 | }); 108 | } 109 | 110 | discover( 111 | url: string, 112 | puppeteerScript = '', 113 | prerender = false, 114 | strictMode = false, 115 | ): Observable { 116 | const parserUrl = 117 | this.urls.discoverFeeds + 118 | this.params({ 119 | homepageUrl: url, 120 | script: puppeteerScript, 121 | prerender, 122 | strictMode, 123 | }); 124 | return this.httpClient.get(parserUrl, { 125 | withCredentials: true, 126 | }) as Observable; 127 | } 128 | 129 | transformNativeFeed(nativeFeed: NativeFeedWithParams): Observable { 130 | const parserUrl = this.createFeedUrlForNative(nativeFeed); 131 | return this.httpClient.get(parserUrl, { 132 | withCredentials: true, 133 | responseType: (!nativeFeed.targetFormat || 134 | nativeFeed.targetFormat === 'json' 135 | ? 'json' 136 | : 'text') as any, 137 | }) as Observable; 138 | } 139 | 140 | createFeedUrlForGeneric(genericRule: GenericFeedWithParams): string { 141 | const search = this.params({ 142 | v: 0.1, // version from env 143 | url: genericRule.harvestUrl, 144 | link: genericRule.linkXPath, 145 | context: genericRule.contextXPath, 146 | date: genericRule.dateXPath, 147 | x: genericRule.extendContext, 148 | re: genericRule.articleRecovery, 149 | q: genericRule.filter, 150 | out: genericRule.targetFormat, 151 | pp: genericRule.prerendered, 152 | ppS: genericRule.puppeteerScript, 153 | debug: genericRule.debug, 154 | }); 155 | return `${this.urls.webToFeed}${search}`; 156 | } 157 | 158 | createFeedUrlForNative(nativeFeed: NativeFeedWithParams): string { 159 | const search = this.params({ 160 | url: nativeFeed.feedUrl, 161 | re: nativeFeed.articleRecovery, 162 | q: nativeFeed.filter, 163 | debug: nativeFeed.debug, 164 | out: nativeFeed.targetFormat || 'json', 165 | }); 166 | 167 | return `${this.urls.transformFeed}${search}`; 168 | } 169 | 170 | fetchGenericFeed(genericRule: GenericFeedWithParams): Observable { 171 | // http://localhost:8080/api/web-to-feed?version=0.1&url=&linkXPath=&extendContext=&contextXPath=&filter= 172 | const parserUrl = this.createFeedUrlForGeneric(genericRule); 173 | return this.httpClient.get(parserUrl, { 174 | responseType: (!genericRule.targetFormat || 175 | genericRule.targetFormat === 'json' 176 | ? 'json' 177 | : 'text') as any, 178 | }) as Observable; 179 | } 180 | 181 | private params(param: { [key: string]: string | number | boolean }) { 182 | const search = Object.keys(param) 183 | .filter((k) => !!param[k]) 184 | .map((k) => `${k}=${encodeURIComponent(param[k] ?? '')}`) 185 | .join('&'); 186 | return '?' + search; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /packages/playground/src/app/wizard.component.ts: -------------------------------------------------------------------------------- 1 | import { FeedWizardParams } from './services/feed.service'; 2 | import { clone } from 'lodash'; 3 | 4 | export class WizardComponent { 5 | copy(feedWithParams: T): T { 6 | return clone(feedWithParams); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/playground/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/assets/.gitkeep -------------------------------------------------------------------------------- /packages/playground/src/assets/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/assets/arrow.png -------------------------------------------------------------------------------- /packages/playground/src/assets/browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/assets/browser.png -------------------------------------------------------------------------------- /packages/playground/src/assets/rss_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/assets/rss_icon.png -------------------------------------------------------------------------------- /packages/playground/src/assets/rssproxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/assets/rssproxy.png -------------------------------------------------------------------------------- /packages/playground/src/assets/x.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/playground/src/environments/build.ts: -------------------------------------------------------------------------------- 1 | 2 | export const build = { 3 | version: '2.1.0', 4 | revision: 'd6cbd09', 5 | date: '1686054133425' 6 | }; -------------------------------------------------------------------------------- /packages/playground/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | import { RssProxyEnvironment } from './rss-proxy-environment'; 2 | 3 | export const environment: RssProxyEnvironment = { 4 | production: true, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/playground/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 | import { RssProxyEnvironment } from './rss-proxy-environment'; 6 | 7 | export const environment: RssProxyEnvironment = { 8 | production: false, 9 | }; 10 | 11 | /* 12 | * For easier debugging in development mode, you can import the following file 13 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 14 | * 15 | * This import should be commented out in production mode because it will have a negative impact 16 | * on performance if an error is thrown. 17 | */ 18 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 19 | -------------------------------------------------------------------------------- /packages/playground/src/environments/rss-proxy-environment.ts: -------------------------------------------------------------------------------- 1 | export interface RssProxyEnvironment { 2 | production: boolean; 3 | } 4 | -------------------------------------------------------------------------------- /packages/playground/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damoeb/rss-proxy/d91dbdef4051951fc7a6d209a23aea55b5f85a1d/packages/playground/src/favicon.ico -------------------------------------------------------------------------------- /packages/playground/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RSS-proxy - Free smart RSS Feed Generator from any Website 6 | 19 | 23 | 24 | 25 | 26 | 30 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /packages/playground/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 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule) 13 | .catch((err) => console.error(err)); 14 | -------------------------------------------------------------------------------- /packages/playground/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 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | -------------------------------------------------------------------------------- /packages/playground/src/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | -------------------------------------------------------------------------------- /packages/playground/src/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | https://rssproxy.migor.org/ 10 | 2022-07-06T09:07:08+00:00 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/playground/src/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'variables.scss'; 2 | @import '~bulma/bulma'; 3 | @import '~toastr'; 4 | 5 | body { 6 | margin: 0; 7 | padding: 0; 8 | height: 100vh; 9 | } 10 | 11 | html { 12 | overflow-y: hidden; 13 | } 14 | 15 | //a { 16 | // color: blue; 17 | // //border-bottom: 2px solid black; 18 | // 19 | // &:hover { 20 | // text-decoration: none; 21 | // border-bottom-style: hidden; 22 | // } 23 | //} 24 | .menu-list { 25 | flex: 1; 26 | } 27 | app-section { 28 | display: flex; 29 | flex: 1; 30 | } 31 | app-generic-feeds { 32 | } 33 | app-native-feeds { 34 | } 35 | app-refine-feed { 36 | } 37 | app-options { 38 | li { 39 | a { 40 | white-space: nowrap; 41 | } 42 | } 43 | } 44 | 45 | .flex { 46 | display: flex; 47 | flex: 1; 48 | } 49 | 50 | .is-disabled { 51 | color: #cccccc !important; 52 | pointer-events: none; 53 | } 54 | 55 | .aside { 56 | width: 350px; 57 | display: flex; 58 | flex-direction: column; 59 | } 60 | 61 | .source { 62 | max-height: 80vh; 63 | } 64 | .is-primary { 65 | color: $primary; 66 | } 67 | 68 | app-spinner { 69 | display: flex; 70 | justify-content: center; 71 | } 72 | 73 | li { 74 | a.is-active { 75 | background-color: #363644 !important; 76 | } 77 | a.edit { 78 | color: blue; 79 | } 80 | } 81 | 82 | app-feed-url { 83 | width: 300px; 84 | } 85 | 86 | a { 87 | color: blue; 88 | } 89 | -------------------------------------------------------------------------------- /packages/playground/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 { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting, 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | // First, initialize the Angular testing environment. 11 | getTestBed().initTestEnvironment( 12 | BrowserDynamicTestingModule, 13 | platformBrowserDynamicTesting(), 14 | ); 15 | -------------------------------------------------------------------------------- /packages/playground/src/variables.scss: -------------------------------------------------------------------------------- 1 | $base-root-font-size: 14px; 2 | $base-font-size: 1rem; 3 | $primary: blue; 4 | $menu-item-active-background-color: $primary; 5 | -------------------------------------------------------------------------------- /packages/playground/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": ["src/main.ts", "src/polyfills.ts"], 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["src/test.ts", "src/**/*.spec.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "noImplicitAny": true, 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": ["es2018", "dom", "esnext.asynciterable"], 16 | "allowSyntheticDefaultImports": true 17 | }, 18 | "angularCompilerOptions": { 19 | "enableI18nLegacyMessageIdFormat": false, 20 | "strictInjectionParameters": true, 21 | "strictInputAccessModifiers": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/playground/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": ["jasmine", "node"] 6 | }, 7 | "files": ["src/test.ts", "src/polyfills.ts"], 8 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/playground/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "array-type": false, 5 | "arrow-parens": false, 6 | "deprecation": { 7 | "severity": "warning" 8 | }, 9 | "component-class-suffix": true, 10 | "contextual-lifecycle": true, 11 | "directive-class-suffix": true, 12 | "directive-selector": [true, "attribute", "app", "camelCase"], 13 | "component-selector": [true, "element", "app", "kebab-case"], 14 | "import-blacklist": [true, "rxjs/Rx"], 15 | "interface-name": false, 16 | "max-classes-per-file": false, 17 | "max-line-length": [true, 140], 18 | "member-access": false, 19 | "member-ordering": [ 20 | true, 21 | { 22 | "order": [ 23 | "static-field", 24 | "instance-field", 25 | "static-method", 26 | "instance-method" 27 | ] 28 | } 29 | ], 30 | "no-consecutive-blank-lines": false, 31 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 32 | "no-empty": false, 33 | "no-inferrable-types": [true, "ignore-params"], 34 | "no-non-null-assertion": true, 35 | "no-redundant-jsdoc": true, 36 | "no-switch-case-fall-through": true, 37 | "no-var-requires": false, 38 | "object-literal-key-quotes": [true, "as-needed"], 39 | "object-literal-sort-keys": false, 40 | "ordered-imports": false, 41 | "quotemark": [true, "single"], 42 | "trailing-comma": false, 43 | "no-conflicting-lifecycle": true, 44 | "no-host-metadata-property": true, 45 | "no-input-rename": true, 46 | "no-inputs-metadata-property": true, 47 | "no-output-native": true, 48 | "no-output-on-prefix": true, 49 | "no-output-rename": true, 50 | "no-outputs-metadata-property": true, 51 | "template-banana-in-box": true, 52 | "template-no-negated-async": true, 53 | "use-lifecycle-interface": true, 54 | "use-pipe-transform-interface": true 55 | }, 56 | "rulesDirectory": ["codelyzer"] 57 | } 58 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "rss-proxy" 2 | 3 | include("packages:playground") 4 | 5 | buildscript { 6 | repositories { 7 | gradlePluginPortal() 8 | } 9 | // dependencies { 10 | // val gradleNodePluginVersion = "3.1.0" 11 | // classpath ("com.github.node-gradle:gradle-node-plugin:$gradleNodePluginVersion") 12 | // } 13 | } 14 | -------------------------------------------------------------------------------- /tasks: -------------------------------------------------------------------------------- 1 | test urls: 2 | https://www.arte.tv/de/videos/RC-019568/the-killing/ 3 | 4 | https://developers.googleblog.com/ 5 | http://blog.spencermounta.in/ 6 | https://bookmarks.kovah.de/guest/links 7 | https://www.brandonsmith.ninja/ 8 | https://jon.bo/posts/ 9 | https://arzg.github.io/lang/ 10 | https://bulletin.nu/ 11 | https://demo.linkace.org/guest/links 12 | https://lukesmith.xyz/ 13 | https://blog.substack.com/ 14 | https://100millionbooks.org/ 15 | https://www.slowernews.com/ 16 | https://arxiv.org/search/?query=math&searchtype=all&source=header 17 | https://duckduckgo.com/html/?q=feynman 18 | http://paulgraham.com/articles.html 19 | 20 | with nothing: 21 | https://hystoria.100millionbooks.org/ 22 | 23 | 24 | Ideas: 25 | - publish feed to rich-rss instance 26 | - version check to rp.migor.org 27 | 28 | support old proxy http://localhost:4200/api/feed?url=http%3A%2F%2Fheise.de&pContext=%2F%2Fbody%2Fdiv%5B5%5D%2Fdiv%5B1%5D%2Fdiv%2Fsection%5B1%5D%2Fsection%5B1%5D%2Farticle&pLink=.%2Fa%5B1%5D&x=s 29 | 30 | filter: 31 | - linkCount 32 | - wordCount 33 | 34 | https://zeigren.com/posts/monitoring_prometheus_loki/ 35 | 36 | fetch error should still give you the dynamic option (bypass bot protection) 37 | -------------------------------------------------------------------------------- /tokenSecret.txt: -------------------------------------------------------------------------------- 1 | 1234_top_secret 2 | --------------------------------------------------------------------------------