├── l10n
├── .gitkeep
├── ps.json
├── ur_PK.json
├── ps.js
├── ur_PK.js
├── an.json
├── an.js
├── hy.json
├── hy.js
├── ms_MY.json
├── kn.json
├── ms_MY.js
├── kn.js
├── tk.json
├── tk.js
├── gd.json
├── gd.js
├── ta.json
├── bs.json
├── ta.js
├── bs.js
├── kab.json
├── km.json
├── kab.js
├── km.js
├── sr@latin.json
├── si.json
├── sr@latin.js
├── si.js
├── uz.json
├── uz.js
├── az.json
├── nn_NO.json
├── az.js
├── nn_NO.js
├── cy_GB.json
├── cy_GB.js
├── bn_BD.json
├── bn_BD.js
├── br.json
├── br.js
├── af.json
├── af.js
├── eo.json
├── eo.js
├── mn.json
├── lb.json
├── mn.js
├── lb.js
├── vi.json
├── vi.js
├── mk.json
├── mk.js
├── ia.json
└── ia.js
├── .envrc
├── .github
├── styles
│ ├── vocab.txt
│ └── config
│ │ └── vocabularies
│ │ └── Nextcloud-News
│ │ └── accept.txt
├── ISSUE_TEMPLATE
│ ├── config.yml
│ └── bug_report.md
├── pull_request_template.md
├── workflows
│ ├── changelog-enforcer.yml
│ ├── lint-text.yml
│ ├── documentation.yml
│ ├── lint.yml
│ ├── lint-info-xml.yml
│ ├── api-php-tests.yml
│ └── api-php-static-code-check.yml
├── no-response.yml
├── stale.yml
└── dependabot.yml
├── .stylelintignore
├── docker
├── news.config.php
├── Dockerfile
├── docker-compose.yml
├── Makefile
└── README.md
├── img
├── app-128.png
├── app-512.png
├── favicon.ico
├── favicon.png
├── favicon-touch.png
├── facebook.svg
├── app.svg
├── rss.svg
├── twitter.svg
├── favicon-touch.svg
├── favicon.svg
└── app-dark.svg
├── screenshots
├── 1.png
├── 2.png
├── 3.png
├── 1-small.png
├── 2-small.png
└── 3-small.png
├── docs
├── assets
│ ├── favicon.png
│ └── logo.svg
├── features
│ ├── themes.md
│ ├── customCSS.md
│ └── integration.md
├── README.md
├── index.md
└── maintenance.md
├── tests
├── test_helper
│ └── feeds
│ │ ├── logo.png
│ │ ├── favicon.ico
│ │ └── Nextcloud.opml
├── command
│ ├── helpers
│ │ └── settings.bash
│ ├── explore.bats
│ ├── update.bats
│ ├── opml.bats
│ └── folders.bats
├── api
│ └── helpers
│ │ └── settings.bash
├── bootstrap.php
├── updater
│ └── helpers
│ │ └── settings.bash
├── javascript
│ └── unit
│ │ ├── components
│ │ ├── routes
│ │ │ ├── Explore.spec.ts
│ │ │ ├── Recent.spec.ts
│ │ │ └── Item.spec.ts
│ │ └── App.spec.ts
│ │ ├── setup.ts
│ │ └── services
│ │ ├── share.service.spec.ts
│ │ └── folder.service.spec.ts
└── Unit
│ ├── Controller
│ └── JSONHttpErrorTest.php
│ └── Utility
│ └── TimeTest.php
├── vendor-bin
└── php-scoper
│ └── composer.json
├── stylelint.config.cjs
├── .vale.ini
├── src
├── types
│ ├── ExploreSite.ts
│ ├── Folder.ts
│ ├── nextcloud__router.d.ts
│ ├── FeedItem.ts
│ ├── ApiRoutes.ts
│ ├── Feed.ts
│ └── MutationTypes.ts
├── main-admin.js
├── utils
│ ├── unreadCache.ts
│ ├── dateUtils.ts
│ └── itemFilter.ts
├── enums
│ └── index.ts
├── main-cron-warning.js
├── README.md
├── components
│ └── routes
│ │ ├── Recent.vue
│ │ ├── Item.vue
│ │ ├── All.vue
│ │ └── Starred.vue
├── dataservices
│ └── share.service.ts
├── store
│ └── index.ts
└── main.js
├── bin
├── git
│ └── hooks
│ │ ├── pre-commit
│ │ └── README.md
└── tools
│ ├── check_feeds.sh
│ ├── file_from_env.php
│ └── generate_authors.php
├── templates
├── part.content.warnings.php
├── admin.php
└── index.php
├── _typos.toml
├── shell.nix
├── .tx
└── config
├── lib
├── Controller
│ ├── Exceptions
│ │ └── NotLoggedInException.php
│ ├── FaviconController.php
│ ├── FaviconApiController.php
│ ├── JSONHttpErrorTrait.php
│ ├── ApiPayloadTrait.php
│ ├── Controller.php
│ └── ImportController.php
├── Constants.php
├── Explore
│ ├── Exceptions
│ │ └── RecommendedSiteNotFoundException.php
│ └── RecommendedSites.php
├── Db
│ ├── IAPI.php
│ ├── ListType.php
│ └── EntityJSONSerializer.php
├── Utility
│ ├── Time.php
│ └── Cache.php
├── Settings
│ ├── AdminSection.php
│ └── AdminSettings.php
├── Listeners
│ ├── AddMissingIndicesListener.php
│ └── UserSettingsListener.php
├── Service
│ ├── Exceptions
│ │ ├── ServiceNotFoundException.php
│ │ ├── ServiceConflictException.php
│ │ ├── ServiceValidationException.php
│ │ └── ServiceException.php
│ └── UpdaterService.php
├── Scraper
│ └── IScraper.php
├── Migration
│ ├── RemoveUnusedJob.php
│ ├── Version150302Date20210312231251.php
│ ├── Version250200Date20241219085150.php
│ └── Version150400Date20210318215425.php
├── Hooks
│ └── UserDeleteHook.php
├── Command
│ ├── Config
│ │ ├── OpmlExport.php
│ │ ├── FeedDelete.php
│ │ ├── FolderDelete.php
│ │ ├── OpmlImport.php
│ │ ├── FolderAdd.php
│ │ ├── FeedList.php
│ │ └── FolderList.php
│ └── Updater
│ │ └── BeforeUpdate.php
├── Plugin
│ └── Client
│ │ └── Plugin.php
├── Fetcher
│ ├── FaviconDataAccess.php
│ └── IFeedFetcher.php
└── Cron
│ └── UpdaterJob.php
├── eslint.config.mjs
├── .gitmodules
├── .editorconfig
├── phpstan.neon.dist
├── .devcontainer
├── README.md
├── setup.sh
└── devcontainer.json
├── tsconfig.json
├── vite.config.ts
├── .mailmap
├── term.kdl
├── .gitignore
├── phpunit.xml
├── mkdocs.yml
├── README.md
└── scoper.inc.php
/l10n/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.envrc:
--------------------------------------------------------------------------------
1 | use nix
2 |
--------------------------------------------------------------------------------
/.github/styles/vocab.txt:
--------------------------------------------------------------------------------
1 | # test
2 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | coverage/
--------------------------------------------------------------------------------
/docker/news.config.php:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/_typos.toml:
--------------------------------------------------------------------------------
1 | [files]
2 | extend-exclude = [
3 | "l10n/*.js*",
4 | "js-old/**",
5 | "/tests/test_helper/feeds/*",
6 | "tests/Unit/Db/ItemTest.php",
7 | "AUTHORS.md"
8 | ]
9 |
--------------------------------------------------------------------------------
/docs/features/themes.md:
--------------------------------------------------------------------------------
1 | # Themes
2 |
3 | Nextcloud News can look different with the following themes:
4 |
5 | * [Nextcloud News Themes](https://github.com/cwmke/nextcloud-news-themes)
6 |
--------------------------------------------------------------------------------
/tests/command/helpers/settings.bash:
--------------------------------------------------------------------------------
1 | user=admin
2 | NC_FEED="http://localhost:8090/Nextcloud.rss"
3 | HEISE_FEED="http://localhost:8090/heise.xml"
4 | NO_GUID_FEED="http://localhost:8090/no_guid_feed.xml"
5 |
--------------------------------------------------------------------------------
/src/types/Folder.ts:
--------------------------------------------------------------------------------
1 | import type { Feed } from './Feed.ts'
2 |
3 | export type Folder = {
4 | feeds: Feed[]
5 | feedCount: number
6 | updateErrorCount: number
7 | name: string
8 | id: number
9 | opened: boolean
10 | }
11 |
--------------------------------------------------------------------------------
/src/types/nextcloud__router.d.ts:
--------------------------------------------------------------------------------
1 | // src/types/nextcloud__router.d.ts
2 | declare module '@nextcloud/router' {
3 | export function generateUrl(route: string): string
4 | export function generateOcsUrl(route: string): string
5 | }
6 |
--------------------------------------------------------------------------------
/src/types/FeedItem.ts:
--------------------------------------------------------------------------------
1 | export type FeedItem = {
2 | id: string
3 | title: string
4 | unread: boolean
5 | starred: boolean
6 | feedId: number
7 | guidHash: string
8 | pubDate: number
9 | url: string
10 | keepUnread: boolean
11 | }
12 |
--------------------------------------------------------------------------------
/templates/admin.php:
--------------------------------------------------------------------------------
1 |
4 | // SPDX-Licence-Identifier: AGPL-3.0-or-later
5 | \OCP\Util::addScript('news', 'news-admin-settings');
6 | ?>
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/api/helpers/settings.bash:
--------------------------------------------------------------------------------
1 | export user=admin
2 | export NC_FEED="http://localhost:8090/Nextcloud.rss"
3 | export HEISE_FEED="http://localhost:8090/heise.xml"
4 | export BASE_URLv1="http://localhost:8080/index.php/apps/news/api/v1-2"
5 | export NC_HOST="http://localhost:8080"
--------------------------------------------------------------------------------
/bin/git/hooks/README.md:
--------------------------------------------------------------------------------
1 | # Hooks
2 |
3 | The following hooks are provided:
4 |
5 | * Update checksums before you commit
6 |
7 | # Installation
8 | Link the files in here into your **.git/hooks** directory:
9 |
10 | ln -s -r bin/git/hooks/pre-commit .git/hooks/
11 |
12 |
--------------------------------------------------------------------------------
/img/facebook.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/command/explore.bats:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bats
2 |
3 | load "helpers/settings"
4 |
5 | TESTSUITE="Explore"
6 |
7 | @test "[$TESTSUITE] Create new" {
8 | curl --fail "$NC_FEED"
9 |
10 | run ./occ news:generate-explore --votes 100 "$NC_FEED"
11 | [ "$status" -eq 0 ]
12 | }
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import {} }:
2 | pkgs.mkShell {
3 | nativeBuildInputs = with pkgs; [
4 | gnumake
5 | nodejs
6 | php83
7 | php83Packages.composer
8 | zellij # smart terminal workspace
9 | lazygit # git terminal
10 | ];
11 | }
12 |
--------------------------------------------------------------------------------
/src/types/ApiRoutes.ts:
--------------------------------------------------------------------------------
1 | import { generateUrl } from '@nextcloud/router'
2 |
3 | export const API_ROUTES = {
4 | FOLDER: generateUrl('/apps/news/folders'),
5 | FEED: generateUrl('/apps/news/feeds'),
6 | ITEMS: generateUrl('/apps/news/items'),
7 | FAVICON: generateUrl('/apps/news/favicon'),
8 | }
9 |
--------------------------------------------------------------------------------
/tests/test_helper/feeds/Nextcloud.opml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Subscriptions
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.tx/config:
--------------------------------------------------------------------------------
1 | [main]
2 | host = https://www.transifex.com
3 | lang_map = sk_SK: sk, th_TH: th, ja_JP: ja, bg_BG: bg, cs_CZ: cs, fi_FI: fi, hu_HU: hu, nb_NO: nb
4 |
5 | [o:nextcloud:p:nextcloud:r:news]
6 | file_filter = translationfiles//news.po
7 | source_file = translationfiles/templates/news.pot
8 | source_lang = en
9 | type = PO
10 |
11 |
--------------------------------------------------------------------------------
/bin/tools/check_feeds.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | feeds=$(cat ./lib/Explore/feeds/feeds.en.json | jq -r .[][0].feed)
4 |
5 | for feed in ${feeds} ; do
6 | if [[ $feed == "http://"* ]]; then
7 | echo "Insecure feed $feed"
8 | exit 1;
9 | fi
10 | if ! curl --fail --silent "$feed" > /dev/null; then
11 | echo "Failed to fetch $feed"
12 | exit 1;
13 | fi
14 | done
--------------------------------------------------------------------------------
/lib/Controller/Exceptions/NotLoggedInException.php:
--------------------------------------------------------------------------------
1 |
2 | // SPDX-Licence-Identifier: AGPL-3.0-or-later
3 |
4 | import { translate as t } from '@nextcloud/l10n'
5 | import { createApp } from 'vue'
6 | import AdminSettings from './components/AdminSettings.vue'
7 |
8 | const app = createApp(AdminSettings)
9 |
10 | app.config.globalProperties.t = t
11 |
12 | app.mount('#vue-admin-news')
13 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |
9 | * @copyright Bernhard Posselt 2012, 2014
10 | */
11 |
12 | require_once __DIR__ . '/../../../tests/bootstrap.php';
13 | require_once __DIR__ . '/../vendor/autoload.php';
14 |
--------------------------------------------------------------------------------
/tests/updater/helpers/settings.bash:
--------------------------------------------------------------------------------
1 | export user=admin
2 | export NC_FEED="http://localhost:8090/Nextcloud.rss"
3 | export HEISE_FEED="http://localhost:8090/heise.xml"
4 | export BASE_URLv1="http://localhost:8080/index.php/apps/news/api/v1-2"
5 | export NC_HOST="http://localhost:8080"
6 | export TEST_FEED="http://localhost:8090/test.xml"
7 | export FEED1="http://localhost:8090/feed1.xml"
8 | export FEED2="http://localhost:8090/feed2.xml"
--------------------------------------------------------------------------------
/l10n/an.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Download" : "Escargar",
3 | "Close" : "Zarrar",
4 | "Folder name" : "Nombre de carpeta",
5 | "Username" : "Nombre d'usuario",
6 | "Password" : "Clau",
7 | "New folder" : "Nueva carpeta",
8 | "Share" : "Compartir",
9 | "Settings" : "Configuración",
10 | "Rename" : "Renombrar",
11 | "Delete" : "Borrar"
12 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
13 | }
--------------------------------------------------------------------------------
/lib/Constants.php:
--------------------------------------------------------------------------------
1 |
3 |
4 | ## Summary
5 |
6 |
7 |
8 | ## Checklist
9 |
10 | - Code is [properly formatted](https://nextcloud.github.io/news/developer/#coding-style-guidelines)
11 | - [Sign-off message](https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md) is added to all commits
12 | - Changelog entry added for all important changes.
13 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | # https://hub.docker.com/_/nextcloud/
2 | FROM nextcloud:30-apache
3 | #FROM ghcr.io/pbek/nextcloud-docker-pre-apache:latest
4 | #FROM ghcr.io/digital-blueprint/nextcloud-docker-pre-apache:latest
5 |
6 | COPY entrypoint.sh /
7 |
8 | RUN apt-get update && apt-get install -y sqlite3
9 | RUN deluser www-data
10 | RUN useradd -u 1000 -ms /bin/bash www-data
11 | RUN usermod -a -G www-data www-data
12 | RUN mkdir /var/www/deploy
13 |
--------------------------------------------------------------------------------
/l10n/an.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Download" : "Escargar",
5 | "Close" : "Zarrar",
6 | "Folder name" : "Nombre de carpeta",
7 | "Username" : "Nombre d'usuario",
8 | "Password" : "Clau",
9 | "New folder" : "Nueva carpeta",
10 | "Share" : "Compartir",
11 | "Settings" : "Configuración",
12 | "Rename" : "Renombrar",
13 | "Delete" : "Borrar"
14 | },
15 | "nplurals=2; plural=(n != 1);");
16 |
--------------------------------------------------------------------------------
/src/utils/unreadCache.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * updates unread cache
3 | *
4 | * @param newItems latest unread items from store
5 | * @param unreadCache cache array to update
6 | */
7 | export function updateUnreadCache(newItems, unreadCache) {
8 | const cachedItemIds = new Set(unreadCache.map((item) => item.id))
9 |
10 | for (const item of newItems) {
11 | if (!cachedItemIds.has(item.id)) {
12 | unreadCache.push(item)
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "tests/test_helper/bats-support"]
2 | path = tests/test_helper/bats-support
3 | url = https://github.com/bats-core/bats-support.git
4 | [submodule "tests/test_helper/bats-assert"]
5 | path = tests/test_helper/bats-assert
6 | url = https://github.com/bats-core/bats-assert.git
7 | [submodule "tests/test_helper/php-feed-generator"]
8 | path = tests/test_helper/php-feed-generator
9 | url = https://github.com/Grotax/php-feed-generator.git
10 |
--------------------------------------------------------------------------------
/templates/index.php:
--------------------------------------------------------------------------------
1 | $fileName) {
8 | style($appName, $fileName);
9 | }
10 | foreach (Plugin::getScripts() as $appName => $fileName) {
11 | script($appName, $fileName);
12 | }
13 |
14 | print_unescaped($this->inc('part.content.warnings'))
15 |
16 | ?>
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | trim_trailing_whitespace = true
5 | end_of_line = lf
6 | insert_final_newline = true
7 |
8 | [*.{php,html,css}]
9 | indent_style = space
10 | indent_size = 4
11 | charset = utf-8
12 |
13 | [*.{js,ts,vue}]
14 | indent_style = tab
15 | indent_size = 4
16 | charset = utf-8
17 |
18 | [*.bats]
19 | indent_style = space
20 | indent_size = 2
21 | charset = utf-8
22 |
23 | [Makefile]
24 | indent_style = tab
25 | indent_size = 4
26 |
27 |
--------------------------------------------------------------------------------
/.github/workflows/changelog-enforcer.yml:
--------------------------------------------------------------------------------
1 | name: "Changelog Enforcer"
2 | on:
3 | pull_request:
4 | types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]
5 |
6 | jobs:
7 | # Enforces the update of a changelog file on every pull request
8 | changelog:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: dangoslen/changelog-enforcer@v3
12 | with:
13 | changeLogPath: 'CHANGELOG.md'
14 | skipLabels: 'Skip-Changelog'
15 |
--------------------------------------------------------------------------------
/tests/command/update.bats:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bats
2 |
3 | # This only works with NC 26
4 |
5 | load "helpers/settings"
6 | load "../test_helper/bats-support/load"
7 | load "../test_helper/bats-assert/load"
8 |
9 | TESTSUITE="Update"
10 |
11 | @test "[$TESTSUITE] Job status" {
12 | run ./occ news:updater:job
13 |
14 | [ "$status" -eq 0 -o "$status" -eq 2 ]
15 | }
16 |
17 | @test "[$TESTSUITE] Job reset" {
18 | run ./occ news:updater:job --reset
19 |
20 | assert_success
21 | }
--------------------------------------------------------------------------------
/.github/workflows/lint-text.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - master
8 |
9 | jobs:
10 | typos:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v6.0.1
14 |
15 | - name: typos-action
16 | uses: crate-ci/typos@master
17 |
18 | vale:
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: actions/checkout@v6.0.1
22 |
23 | - uses: errata-ai/vale-action@reviewdog
24 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | Welcome to the Nextcloud News APP documentation, it contains information for users, administrators and developers. News is an APP for Nextcloud that can be installed from the official [APP Store](https://apps.nextcloud.com/apps/news).
4 |
5 | News offers the user an RSS/Atom feed reader and can be used to subscribe to multiple feeds, which get automatically updated in the background. Additionally, news offers a REST-API that allows clients to synchronize with News.
--------------------------------------------------------------------------------
/src/enums/index.ts:
--------------------------------------------------------------------------------
1 | export enum DISPLAY_MODE {
2 | DEFAULT = '0',
3 | COMPACT = '1',
4 | SCREENREADER = '2',
5 | }
6 |
7 | export enum FEED_ORDER {
8 | DEFAULT = 0,
9 | OLDEST = 1,
10 | NEWEST = 2,
11 | }
12 |
13 | export enum FEED_UPDATE_MODE {
14 | UNREAD = 0,
15 | IGNORE = 1,
16 | }
17 |
18 | export enum SPLIT_MODE {
19 | VERTICAL = '0',
20 | HORIZONTAL = '1',
21 | OFF = '2',
22 | }
23 |
24 | export enum ITEM_HEIGHT {
25 | DEFAULT = '111',
26 | COMPACT = '44',
27 | }
28 |
--------------------------------------------------------------------------------
/phpstan.neon.dist:
--------------------------------------------------------------------------------
1 | parameters:
2 | inferPrivatePropertyTypeFromConstructor: true
3 | treatPhpDocTypesAsCertain: false
4 | reportUnmatchedIgnoredErrors: false
5 | bootstrapFiles:
6 | - %currentWorkingDirectory%/../../lib/base.php
7 | excludePaths:
8 | - %currentWorkingDirectory%/lib/Migration/*.php
9 | - %currentWorkingDirectory%/lib/Vendor/*
10 | - %currentWorkingDirectory%/lib/AppInfo/Application.php
11 | - %currentWorkingDirectory%/lib/Fetcher/Client/Legacy*.php
12 |
--------------------------------------------------------------------------------
/src/types/Feed.ts:
--------------------------------------------------------------------------------
1 | import type { FEED_ORDER, FEED_UPDATE_MODE } from '../enums/index.ts'
2 |
3 | export type Feed = {
4 | folderId?: number
5 | unreadCount: number
6 | url: string
7 | title?: string
8 | autoDiscover?: boolean
9 | faviconLink?: string
10 | id?: number
11 | pinned: boolean
12 | preventUpdate: boolean
13 | ordering: FEED_ORDER
14 | fullTextEnabled: boolean
15 | updateMode: FEED_UPDATE_MODE
16 | updateErrorCount: number
17 | lastUpdateError: string
18 | location: string
19 | }
20 |
--------------------------------------------------------------------------------
/l10n/hy.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Download" : "Բեռնել",
3 | "Close" : "Փակել",
4 | "Folder name" : "Պանակի անուն",
5 | "Username" : "Օգտանուն",
6 | "Password" : "Գաղտնաբառ",
7 | "New folder" : "Նոր պանակ",
8 | "Credentials" : "Credentials",
9 | "Move" : "Տեղափոխել",
10 | "Share" : "Կիսվել",
11 | "Settings" : "Կարգավորումներ",
12 | "Rename" : "Վերանվանել",
13 | "Delete" : "հեռացնել",
14 | "Folder" : "Պանակ",
15 | "Refresh" : "Թարմացնել"
16 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
17 | }
--------------------------------------------------------------------------------
/l10n/hy.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Download" : "Բեռնել",
5 | "Close" : "Փակել",
6 | "Folder name" : "Պանակի անուն",
7 | "Username" : "Օգտանուն",
8 | "Password" : "Գաղտնաբառ",
9 | "New folder" : "Նոր պանակ",
10 | "Credentials" : "Credentials",
11 | "Move" : "Տեղափոխել",
12 | "Share" : "Կիսվել",
13 | "Settings" : "Կարգավորումներ",
14 | "Rename" : "Վերանվանել",
15 | "Delete" : "հեռացնել",
16 | "Folder" : "Պանակ",
17 | "Refresh" : "Թարմացնել"
18 | },
19 | "nplurals=2; plural=(n != 1);");
20 |
--------------------------------------------------------------------------------
/.devcontainer/README.md:
--------------------------------------------------------------------------------
1 | # DevContainer
2 |
3 | ## Image Used in This Project
4 |
5 | This project uses a Nextcloud Docker image for the development container. The image is defined in the `.devcontainer.json` configuration file. For more details about the image and its configuration, you can refer to the [GitHub repository](https://github.com/juliusknorr/nextcloud-dev).
6 |
7 | ## Visual Studio Code
8 |
9 | For information on how to configure and use Dev Containers, please visit the [official documentation](https://code.visualstudio.com/docs/remote/containers).
10 |
--------------------------------------------------------------------------------
/l10n/ms_MY.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Download" : "Muat turun",
3 | "Close" : "Tutup",
4 | "Web address" : "Alamat sesawang",
5 | "Username" : "Nama pengguna",
6 | "Password" : "Kata laluan",
7 | "Credentials" : "Credentials",
8 | "Move" : "Move",
9 | "Share" : "Kongsi",
10 | "Settings" : "Tetapan",
11 | "Documentation" : "Dokumentasi",
12 | "Rename" : "Namakan",
13 | "Delete" : "Padam",
14 | "by" : "oleh",
15 | "Folder" : "Folder",
16 | "Refresh" : "Refresh"
17 | },"pluralForm" :"nplurals=1; plural=0;"
18 | }
--------------------------------------------------------------------------------
/docker/docker-compose.yml:
--------------------------------------------------------------------------------
1 | name: nextcloud-news
2 |
3 | services:
4 | app:
5 | container_name: nextcloud-news-app
6 | build: .
7 | ports:
8 | - 8081:80
9 | environment:
10 | - NEXTCLOUD_ADMIN_USER=admin
11 | - NEXTCLOUD_ADMIN_PASSWORD=admin
12 | - SQLITE_DATABASE=mydb
13 | - NEXTCLOUD_TRUSTED_DOMAINS=localhost 127.0.0.1
14 | volumes:
15 | - nextcloud:/var/www/html
16 | - ..:/var/www/html/custom_apps/news
17 | - ./news.config.php:/var/www/html/config/news.config.php
18 |
19 | volumes:
20 | nextcloud:
21 |
--------------------------------------------------------------------------------
/l10n/kn.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Download" : "ಪ್ರತಿಯನ್ನು ಸ್ಥಳೀಯವಾಗಿ ಉಳಿಸಿಕೊಳ್ಳಿ",
3 | "Close" : "ಮುಚ್ಚು",
4 | "Username" : "ಬಳಕೆಯ ಹೆಸರು",
5 | "Password" : "ಗುಪ್ತಪದ",
6 | "New folder" : "ಹೊಸ ಕೋಶ",
7 | "Credentials" : "ರುಜುವಾತುಗಳು",
8 | "Move" : "Move",
9 | "Share" : "ಹಂಚಿಕೊಳ್ಳಿ",
10 | "Settings" : "ಆಯ್ಕೆ",
11 | "Documentation" : "ದಸ್ತಾವೇಜು",
12 | "Rename" : "ಮರುಹೆಸರಿಸು",
13 | "Delete" : "ಅಳಿಸಿ",
14 | "Folder" : "ಕೋಶ",
15 | "right" : "ಬಲ",
16 | "left" : "ಎಡ"
17 | },"pluralForm" :"nplurals=2; plural=(n > 1);"
18 | }
--------------------------------------------------------------------------------
/l10n/ms_MY.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Download" : "Muat turun",
5 | "Close" : "Tutup",
6 | "Web address" : "Alamat sesawang",
7 | "Username" : "Nama pengguna",
8 | "Password" : "Kata laluan",
9 | "Credentials" : "Credentials",
10 | "Move" : "Move",
11 | "Share" : "Kongsi",
12 | "Settings" : "Tetapan",
13 | "Documentation" : "Dokumentasi",
14 | "Rename" : "Namakan",
15 | "Delete" : "Padam",
16 | "by" : "oleh",
17 | "Folder" : "Folder",
18 | "Refresh" : "Refresh"
19 | },
20 | "nplurals=1; plural=0;");
21 |
--------------------------------------------------------------------------------
/img/app.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/l10n/kn.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Download" : "ಪ್ರತಿಯನ್ನು ಸ್ಥಳೀಯವಾಗಿ ಉಳಿಸಿಕೊಳ್ಳಿ",
5 | "Close" : "ಮುಚ್ಚು",
6 | "Username" : "ಬಳಕೆಯ ಹೆಸರು",
7 | "Password" : "ಗುಪ್ತಪದ",
8 | "New folder" : "ಹೊಸ ಕೋಶ",
9 | "Credentials" : "ರುಜುವಾತುಗಳು",
10 | "Move" : "Move",
11 | "Share" : "ಹಂಚಿಕೊಳ್ಳಿ",
12 | "Settings" : "ಆಯ್ಕೆ",
13 | "Documentation" : "ದಸ್ತಾವೇಜು",
14 | "Rename" : "ಮರುಹೆಸರಿಸು",
15 | "Delete" : "ಅಳಿಸಿ",
16 | "Folder" : "ಕೋಶ",
17 | "right" : "ಬಲ",
18 | "left" : "ಎಡ"
19 | },
20 | "nplurals=2; plural=(n > 1);");
21 |
--------------------------------------------------------------------------------
/lib/Explore/Exceptions/RecommendedSiteNotFoundException.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @copyright 2012 Alessandro Cosentino
11 | * @copyright 2012-2014 Bernhard Posselt
12 | */
13 |
14 | namespace OCA\News\Explore\Exceptions;
15 |
16 | use Exception;
17 |
18 | class RecommendedSiteNotFoundException extends Exception
19 | {
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main-cron-warning.js:
--------------------------------------------------------------------------------
1 | // Send error to Vuex State for displaying in Vue Application
2 | window.store.commit('SET_ERROR', {
3 | toString: () => t('news', 'Ajax or webcron mode detected! Your feeds will not be updated!'),
4 | links: [
5 | {
6 | url: 'https://docs.nextcloud.org/server/latest/admin_manual/configuration_server/background_jobs_configuration.html#cron',
7 | text: t('news', 'How to set up the operating system cron'),
8 | }, {
9 | url: 'https://github.com/nextcloud/news-updater',
10 | text: t('news', 'Install and set up a faster parallel updater that uses the News app\'s update API'),
11 | },
12 | ],
13 | })
14 |
--------------------------------------------------------------------------------
/l10n/tk.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Download" : "12",
3 | "Close" : "Ýap",
4 | "Folder name" : "Papkanyň ady",
5 | "Username" : "Ulanyjy ady",
6 | "Password" : "Açarsöz",
7 | "New folder" : "Täze papka döretmek",
8 | "Move" : "Göçüriň",
9 | "Share" : "Paýlaş",
10 | "Default" : "Bellenen",
11 | "Settings" : "Sazlamalar",
12 | "Rename" : "Adyny üýtgetmek",
13 | "Delete" : "Pozmak",
14 | "Newest first" : "Täze ilkinji",
15 | "Oldest first" : "Ilki bilen iň köne",
16 | "Not available" : "Elýeterli däl",
17 | "Refresh" : "Täzelemek"
18 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
19 | }
--------------------------------------------------------------------------------
/lib/Db/IAPI.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @author Paul Tirk
11 | * @copyright 2012 Alessandro Cosentino
12 | * @copyright 2012-2014 Bernhard Posselt
13 | * @copyright 2020 Paul Tirk
14 | */
15 |
16 | namespace OCA\News\Db;
17 |
18 | interface IAPI
19 | {
20 | public function toAPI();
21 | public function toAPI2(bool $reduced = false): array;
22 | }
23 |
--------------------------------------------------------------------------------
/l10n/tk.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Download" : "12",
5 | "Close" : "Ýap",
6 | "Folder name" : "Papkanyň ady",
7 | "Username" : "Ulanyjy ady",
8 | "Password" : "Açarsöz",
9 | "New folder" : "Täze papka döretmek",
10 | "Move" : "Göçüriň",
11 | "Share" : "Paýlaş",
12 | "Default" : "Bellenen",
13 | "Settings" : "Sazlamalar",
14 | "Rename" : "Adyny üýtgetmek",
15 | "Delete" : "Pozmak",
16 | "Newest first" : "Täze ilkinji",
17 | "Oldest first" : "Ilki bilen iň köne",
18 | "Not available" : "Elýeterli däl",
19 | "Refresh" : "Täzelemek"
20 | },
21 | "nplurals=2; plural=(n != 1);");
22 |
--------------------------------------------------------------------------------
/l10n/gd.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Download" : "Luchdaich a-nuas",
3 | "Close" : "Dùin",
4 | "Folder name" : "Ainm a’ phasgain",
5 | "Username" : "Ainm-cleachdaiche",
6 | "Password" : "Facal-faire",
7 | "New folder" : "Pasgan ùr",
8 | "Move" : "Gluais",
9 | "Share" : "Co-roinn",
10 | "Default" : "Bunaiteach",
11 | "Settings" : "Roghainnean",
12 | "Rename" : "Thoir ainm ùr air",
13 | "Delete" : "Sguab às",
14 | "Newest first" : "As ùire an toiseach",
15 | "Oldest first" : "As sine an toiseach",
16 | "Refresh" : "Ath-nuadhaich"
17 | },"pluralForm" :"nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3;"
18 | }
--------------------------------------------------------------------------------
/src/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript Development
2 | JavaScript is built and minified using webpack.
3 |
4 | Therefore you need to install **Node.js 6+ and npm**.
5 |
6 | Then install the local dependencies by running:
7 |
8 | npm install
9 |
10 | ## Tasks
11 | The following tasks are available:
12 |
13 | * **Build the JavaScript**:
14 |
15 | npm run build
16 |
17 | * **Build the JavaScript in Dev Mode**:
18 |
19 | npm run build
20 |
21 | * **Watch for changes and build JavaScript**:
22 |
23 | npm run watch
24 |
25 | * **Run JavaScript unit tests**:
26 |
27 | npm run karma
28 |
29 | * **Watch for changes and run JavaScript unit tests**:
30 |
31 | npm run watch-karma
32 |
--------------------------------------------------------------------------------
/bin/tools/file_from_env.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
10 | * @copyright Benjamin Brahmer 2020
11 | */
12 |
13 | if ($argc < 2) {
14 | echo "This script expects two parameters:\n";
15 | echo "./file_from_env.php ENV_VAR PATH_TO_FILE\n";
16 | exit(1);
17 | }
18 |
19 | # Read environment variable
20 | $content = getenv($argv[1]);
21 |
22 | if (!$content){
23 | echo "Variable was empty\n";
24 | exit(1);
25 | }
26 |
27 | file_put_contents($argv[2], $content);
28 |
29 | echo "Done...\n";
--------------------------------------------------------------------------------
/l10n/gd.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Download" : "Luchdaich a-nuas",
5 | "Close" : "Dùin",
6 | "Folder name" : "Ainm a’ phasgain",
7 | "Username" : "Ainm-cleachdaiche",
8 | "Password" : "Facal-faire",
9 | "New folder" : "Pasgan ùr",
10 | "Move" : "Gluais",
11 | "Share" : "Co-roinn",
12 | "Default" : "Bunaiteach",
13 | "Settings" : "Roghainnean",
14 | "Rename" : "Thoir ainm ùr air",
15 | "Delete" : "Sguab às",
16 | "Newest first" : "As ùire an toiseach",
17 | "Oldest first" : "As sine an toiseach",
18 | "Refresh" : "Ath-nuadhaich"
19 | },
20 | "nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3;");
21 |
--------------------------------------------------------------------------------
/l10n/ta.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Download" : "பதிவிறக்குக",
3 | "Close" : "மூடுக",
4 | "Web address" : "வலைய முகவரி",
5 | "Folder name" : "கோப்புறை பெயர்",
6 | "Username" : "பயனாளர் பெயர்",
7 | "Password" : "கடவுச்சொல்",
8 | "Credentials" : "சான்று ஆவணங்கள்",
9 | "Move" : "Move",
10 | "Share" : "பகிர்வு",
11 | "Settings" : "அமைப்புகள்",
12 | "Documentation" : "ஆவணமாக்கல்",
13 | "Keyboard shortcuts" : "விசைப்பலகை குறுக்குவழிகள்",
14 | "Rename" : "பெயர்மாற்றம்",
15 | "Delete" : "நீக்குக",
16 | "by" : "மூலம்",
17 | "Title" : "தலைப்பு",
18 | "Folder" : "கோப்புறை",
19 | "Refresh" : "மீள் ஏற்றுக"
20 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
21 | }
--------------------------------------------------------------------------------
/tests/javascript/unit/components/routes/Explore.spec.ts:
--------------------------------------------------------------------------------
1 | import axios from '@nextcloud/axios'
2 | import { shallowMount } from '@vue/test-utils'
3 | import { describe, expect, it } from 'vitest'
4 | import Explore from '../../../../../src/components/routes/Explore.vue'
5 |
6 | describe('Explore.vue', () => {
7 | 'use strict'
8 |
9 | it('should initialize without showing AddFeed Component', () => {
10 | axios.get.mockResolvedValue({ data: { } })
11 |
12 | const wrapper = shallowMount(Explore, {
13 | mocks: {
14 | $store: {
15 | state: {
16 | feeds: [],
17 | folders: [],
18 | },
19 | },
20 | },
21 | })
22 |
23 | expect(wrapper.vm.$data.showAddFeed).toBeFalsy()
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/l10n/bs.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Download" : "Preuzmite",
3 | "Close" : "Zatvori",
4 | "Username" : "Korisničko ime",
5 | "Password" : "Lozinka",
6 | "New folder" : "Nova fascikla",
7 | "Credentials" : "Vjerodajnice",
8 | "Move" : "Move",
9 | "Share" : "Podjeli",
10 | "Starred" : "Označeno",
11 | "Settings" : "Postavke",
12 | "Documentation" : "Dokumentacija",
13 | "Keyboard shortcuts" : "Tipkovni prečaci",
14 | "Rename" : "Preimenuj",
15 | "Delete" : "Obriši",
16 | "by" : "od strane",
17 | "Title" : "Naslov",
18 | "Folder" : "Fasikla"
19 | },"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"
20 | }
--------------------------------------------------------------------------------
/l10n/ta.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Download" : "பதிவிறக்குக",
5 | "Close" : "மூடுக",
6 | "Web address" : "வலைய முகவரி",
7 | "Folder name" : "கோப்புறை பெயர்",
8 | "Username" : "பயனாளர் பெயர்",
9 | "Password" : "கடவுச்சொல்",
10 | "Credentials" : "சான்று ஆவணங்கள்",
11 | "Move" : "Move",
12 | "Share" : "பகிர்வு",
13 | "Settings" : "அமைப்புகள்",
14 | "Documentation" : "ஆவணமாக்கல்",
15 | "Keyboard shortcuts" : "விசைப்பலகை குறுக்குவழிகள்",
16 | "Rename" : "பெயர்மாற்றம்",
17 | "Delete" : "நீக்குக",
18 | "by" : "மூலம்",
19 | "Title" : "தலைப்பு",
20 | "Folder" : "கோப்புறை",
21 | "Refresh" : "மீள் ஏற்றுக"
22 | },
23 | "nplurals=2; plural=(n != 1);");
24 |
--------------------------------------------------------------------------------
/lib/Utility/Time.php:
--------------------------------------------------------------------------------
1 |
9 | * @copyright 2016 Bernhard Posselt
10 | */
11 |
12 | namespace OCA\News\Utility;
13 |
14 | class Time
15 | {
16 | public function getTime(): int
17 | {
18 | return time();
19 | }
20 |
21 | /**
22 | * @return string the current unix time in milliseconds
23 | */
24 | public function getMicroTime(): string
25 | {
26 | list($millisecs, $secs) = explode(" ", microtime());
27 | return $secs . substr($millisecs, 2, 6);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/l10n/bs.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Download" : "Preuzmite",
5 | "Close" : "Zatvori",
6 | "Username" : "Korisničko ime",
7 | "Password" : "Lozinka",
8 | "New folder" : "Nova fascikla",
9 | "Credentials" : "Vjerodajnice",
10 | "Move" : "Move",
11 | "Share" : "Podjeli",
12 | "Starred" : "Označeno",
13 | "Settings" : "Postavke",
14 | "Documentation" : "Dokumentacija",
15 | "Keyboard shortcuts" : "Tipkovni prečaci",
16 | "Rename" : "Preimenuj",
17 | "Delete" : "Obriši",
18 | "by" : "od strane",
19 | "Title" : "Naslov",
20 | "Folder" : "Fasikla"
21 | },
22 | "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);");
23 |
--------------------------------------------------------------------------------
/l10n/kab.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Download" : "Sider",
3 | "Close" : "Mdel",
4 | "Folder name" : "Isem n ukaram",
5 | "Username" : "Isem n useqdac",
6 | "Password" : "Awal uffir",
7 | "New folder" : "Akaram amaynut",
8 | "Move" : "Senkez",
9 | "Share" : "Bḍu",
10 | "Default" : "Prédéfini(e)",
11 | "Off" : "Ur irmid ara",
12 | "Settings" : "Iɣewwaṛen",
13 | "Keyboard shortcuts" : "Inegzumen n unasiw",
14 | "Rename" : "Beddel isem",
15 | "Delete" : "Kkes",
16 | "Newest first" : "Imaynuten d imezwura",
17 | "Oldest first" : "Iqbuṛen d imezwura",
18 | "Title" : "Azwel",
19 | "Folder" : "Akaram",
20 | "Refresh" : "Sismeḍ"
21 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
22 | }
--------------------------------------------------------------------------------
/.devcontainer/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
4 | # SPDX-License-Identifier: CC0-1.0
5 |
6 | # better prompt
7 | echo 'export PS1="\[\e[32m\]root@devcontainer\[\e[0m\]:\[\e[34m\]\W\[\e[0m\]# "' >> ~/.bashrc
8 |
9 | # Map NEXTCLOUD_VERSION to SERVER_BRANCH for the bootstrap script
10 | if [ -n "$NEXTCLOUD_VERSION" ]; then
11 | export SERVER_BRANCH="stable${NEXTCLOUD_VERSION}"
12 | echo "Setting SERVER_BRANCH to: $SERVER_BRANCH"
13 | fi
14 |
15 | # Show what we're using
16 | echo "Environment variables:"
17 | env | grep -E "(NEXTCLOUD|SERVER_BRANCH)" | sort
18 |
19 | (
20 | cd /tmp && /usr/local/bin/bootstrap.sh apache2ctl start
21 | )
22 |
23 | make composer
24 | make npm
--------------------------------------------------------------------------------
/img/rss.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/l10n/km.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Unread articles" : "អត្ថបទដែលមិនទាន់អាន",
3 | "Download" : "ទាញយក",
4 | "Close" : "បិទ",
5 | "Folder name" : "ឈ្មោះថត",
6 | "Username" : "ឈ្មោះអ្នកប្រើ",
7 | "Password" : "ពាក្យសម្ងាត់",
8 | "New folder" : "ថតថ្មី",
9 | "Credentials" : "Credentials",
10 | "Move" : "Move",
11 | "Share" : "ចែករំលែក",
12 | "All articles" : "អត្ថបទទាំងអស់",
13 | "Starred" : "បានដាក់ផ្កាយ",
14 | "Settings" : "ការកំណត់",
15 | "Documentation" : "កម្រងឯកសារ",
16 | "Rename" : "ប្ដូរឈ្មោះ",
17 | "Delete" : "លុប",
18 | "by" : "ដោយ",
19 | "Folder" : "ថត",
20 | "right" : "ខាងស្ដាំ",
21 | "left" : "ខាងឆ្វេង"
22 | },"pluralForm" :"nplurals=1; plural=0;"
23 | }
--------------------------------------------------------------------------------
/.github/no-response.yml:
--------------------------------------------------------------------------------
1 | # Configuration for probot-no-response - https://github.com/probot/no-response
2 |
3 | # Number of days of inactivity before an Issue is closed for lack of response
4 | daysUntilClose: 14
5 | # Label requiring a response
6 | responseRequiredLabel: needs info
7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable
8 | closeComment: >
9 | This issue has been automatically closed because there has been no response
10 | to our request for more information from the original author. With only the
11 | information that is currently in the issue, we don't have enough information
12 | to take action. Please reach out if you have or find the answers we need so
13 | that we can investigate further.
14 |
--------------------------------------------------------------------------------
/lib/Db/ListType.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @copyright 2012 Alessandro Cosentino
11 | * @copyright 2012-2014 Bernhard Posselt
12 | */
13 |
14 | namespace OCA\News\Db;
15 |
16 | /**
17 | * Enum FeedType
18 | *
19 | * @package OCA\News\Db
20 | */
21 | class ListType
22 | {
23 | const FEED = 0;
24 | const FOLDER = 1;
25 | const STARRED = 2;
26 | const ALL_ITEMS = 3;
27 | const SHARED = 4;
28 | const EXPLORE = 5;
29 | const UNREAD = 6;
30 | }
31 |
--------------------------------------------------------------------------------
/l10n/kab.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Download" : "Sider",
5 | "Close" : "Mdel",
6 | "Folder name" : "Isem n ukaram",
7 | "Username" : "Isem n useqdac",
8 | "Password" : "Awal uffir",
9 | "New folder" : "Akaram amaynut",
10 | "Move" : "Senkez",
11 | "Share" : "Bḍu",
12 | "Default" : "Prédéfini(e)",
13 | "Off" : "Ur irmid ara",
14 | "Settings" : "Iɣewwaṛen",
15 | "Keyboard shortcuts" : "Inegzumen n unasiw",
16 | "Rename" : "Beddel isem",
17 | "Delete" : "Kkes",
18 | "Newest first" : "Imaynuten d imezwura",
19 | "Oldest first" : "Iqbuṛen d imezwura",
20 | "Title" : "Azwel",
21 | "Folder" : "Akaram",
22 | "Refresh" : "Sismeḍ"
23 | },
24 | "nplurals=2; plural=(n != 1);");
25 |
--------------------------------------------------------------------------------
/l10n/km.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Unread articles" : "អត្ថបទដែលមិនទាន់អាន",
5 | "Download" : "ទាញយក",
6 | "Close" : "បិទ",
7 | "Folder name" : "ឈ្មោះថត",
8 | "Username" : "ឈ្មោះអ្នកប្រើ",
9 | "Password" : "ពាក្យសម្ងាត់",
10 | "New folder" : "ថតថ្មី",
11 | "Credentials" : "Credentials",
12 | "Move" : "Move",
13 | "Share" : "ចែករំលែក",
14 | "All articles" : "អត្ថបទទាំងអស់",
15 | "Starred" : "បានដាក់ផ្កាយ",
16 | "Settings" : "ការកំណត់",
17 | "Documentation" : "កម្រងឯកសារ",
18 | "Rename" : "ប្ដូរឈ្មោះ",
19 | "Delete" : "លុប",
20 | "by" : "ដោយ",
21 | "Folder" : "ថត",
22 | "right" : "ខាងស្ដាំ",
23 | "left" : "ខាងឆ្វេង"
24 | },
25 | "nplurals=1; plural=0;");
26 |
--------------------------------------------------------------------------------
/l10n/sr@latin.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Download" : "Preuzmi",
3 | "Close" : "Zatvori",
4 | "Folder name" : "Naziv fascikle",
5 | "Username" : "Username",
6 | "Password" : "Password",
7 | "New folder" : "Nova fascikla",
8 | "Credentials" : "Credentials",
9 | "Move" : "Premesti",
10 | "Share" : "Podeli",
11 | "Starred" : "Ozvezdano",
12 | "Settings" : "Postavke",
13 | "Documentation" : "Dokumentacija",
14 | "Rename" : "Rename",
15 | "Delete" : "Delete",
16 | "Rename Feed" : "Preimenuj dovod",
17 | "Newest first" : "Prvo novije",
18 | "Oldest first" : "prvo starije",
19 | "Refresh" : "Osveži"
20 | },"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"
21 | }
--------------------------------------------------------------------------------
/tests/javascript/unit/setup.ts:
--------------------------------------------------------------------------------
1 | import { config } from '@vue/test-utils'
2 | import { vi } from 'vitest'
3 |
4 | global.appName = 'news'
5 | global.appVersion = '1.0.0'
6 |
7 | global.OC = {
8 | getLanguage() {
9 | return 'en-US'
10 | },
11 | }
12 |
13 | // Mock nextcloud translate functions
14 | config.global.mocks.$t = function(_app: any, string: any) {
15 | return string
16 | }
17 | config.global.mocks.t = config.global.mocks.$t
18 | global.t = config.global.mocks.$t
19 |
20 | config.global.mocks.$n = function(app: any, singular: any) {
21 | return singular
22 | }
23 | config.global.mocks.n = config.global.mocks.$n
24 |
25 | // Mock nextcloud helpers
26 | vi.mock('@nextcloud/axios')
27 | vi.mock('@nextcloud/capabilities', () => ({
28 | getCapabilities: vi.fn(() => ({})),
29 | }))
30 |
--------------------------------------------------------------------------------
/docs/features/customCSS.md:
--------------------------------------------------------------------------------
1 | # Custom CSS
2 |
3 | Sometimes you want to add additional CSS for a feed to improve the rendering. This can very easily be done by adding a CSS class to **css/custom.css** following the following naming convention:
4 |
5 | * Take the URL from the \ attribute (e.g.: \ \)
6 | * Extract the Domain from the URL (e.g.: )
7 | * Strip the leading **www.** (e.g.: google.de)
8 | * Replace all . with - (e.g.: google-de)
9 | * Prepend **custom-** (e.g.: custom-google-de)
10 |
11 | Each class rule should be prefixed with **#app-content** and should only affect the article body. An example rule would be:
12 |
13 | ```css
14 | #app-content .custom-google-de .body {
15 | /* Custom CSS rules here */
16 | }
17 | ```
18 |
--------------------------------------------------------------------------------
/l10n/si.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "News" : "පුවත්",
3 | "Download" : "බාගන්න",
4 | "Close" : "වසන්න",
5 | "Web address" : "වියමන ලිපිනය",
6 | "Folder name" : "බහාලුමේ නම",
7 | "Username" : "පරිශීලක නාමය",
8 | "Password" : "මුර පදය",
9 | "New folder" : "නව බහාලුම",
10 | "Folder exists already!" : "බහාලුම දැනටමත් පවතී!",
11 | "Update interval" : "යාවත්කාලීන කාල පරතරය",
12 | "Share" : "බෙදාගන්න",
13 | "Settings" : "සැකසුම්",
14 | "Rename" : "නැවත නම් කරන්න",
15 | "Open website" : "වියමන අඩවිය විවෘත කරන්න",
16 | "Folder" : "බහාලුම",
17 | "right" : "දකුණ",
18 | "left" : "වම",
19 | "Load previous folder" : "පෙර බහාලුම පූරණය කරන්න",
20 | "Load next folder" : "ඊළඟ බහාලුම පූරණය කරන්න"
21 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
22 | }
--------------------------------------------------------------------------------
/l10n/sr@latin.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Download" : "Preuzmi",
5 | "Close" : "Zatvori",
6 | "Folder name" : "Naziv fascikle",
7 | "Username" : "Username",
8 | "Password" : "Password",
9 | "New folder" : "Nova fascikla",
10 | "Credentials" : "Credentials",
11 | "Move" : "Premesti",
12 | "Share" : "Podeli",
13 | "Starred" : "Ozvezdano",
14 | "Settings" : "Postavke",
15 | "Documentation" : "Dokumentacija",
16 | "Rename" : "Rename",
17 | "Delete" : "Delete",
18 | "Rename Feed" : "Preimenuj dovod",
19 | "Newest first" : "Prvo novije",
20 | "Oldest first" : "prvo starije",
21 | "Refresh" : "Osveži"
22 | },
23 | "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);");
24 |
--------------------------------------------------------------------------------
/.github/workflows/documentation.yml:
--------------------------------------------------------------------------------
1 | name: Publish docs via GitHub Pages
2 | on:
3 | push:
4 | branches:
5 | - master
6 | paths:
7 | - 'docs/**'
8 |
9 | jobs:
10 | build:
11 | name: Deploy docs
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout master
15 | uses: actions/checkout@v6.0.1
16 |
17 | - name: Deploy docs
18 | uses: mhausenblas/mkdocs-deploy-gh-pages@master
19 | # Or use mhausenblas/mkdocs-deploy-gh-pages@nomaterial to build without the mkdocs-material theme
20 | env:
21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22 | #CUSTOM_DOMAIN: optionaldomain.com
23 | #CONFIG_FILE: folder/mkdocs.yml
24 | #EXTRA_PACKAGES: build-base
25 | # GITHUB_DOMAIN: github.myenterprise.com
--------------------------------------------------------------------------------
/l10n/si.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "News" : "පුවත්",
5 | "Download" : "බාගන්න",
6 | "Close" : "වසන්න",
7 | "Web address" : "වියමන ලිපිනය",
8 | "Folder name" : "බහාලුමේ නම",
9 | "Username" : "පරිශීලක නාමය",
10 | "Password" : "මුර පදය",
11 | "New folder" : "නව බහාලුම",
12 | "Folder exists already!" : "බහාලුම දැනටමත් පවතී!",
13 | "Update interval" : "යාවත්කාලීන කාල පරතරය",
14 | "Share" : "බෙදාගන්න",
15 | "Settings" : "සැකසුම්",
16 | "Rename" : "නැවත නම් කරන්න",
17 | "Open website" : "වියමන අඩවිය විවෘත කරන්න",
18 | "Folder" : "බහාලුම",
19 | "right" : "දකුණ",
20 | "left" : "වම",
21 | "Load previous folder" : "පෙර බහාලුම පූරණය කරන්න",
22 | "Load next folder" : "ඊළඟ බහාලුම පූරණය කරන්න"
23 | },
24 | "nplurals=2; plural=(n != 1);");
25 |
--------------------------------------------------------------------------------
/l10n/uz.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Download" : "Yuklab olish",
3 | "Close" : "Yopish",
4 | "Folder name" : "Folder name",
5 | "Username" : "Username",
6 | "Password" : "Password",
7 | "New folder" : "New folder",
8 | "Credentials" : "Hisob ma'lumotlari",
9 | "Subscribe" : "Obuna boʻlish",
10 | "Move" : "Move",
11 | "Share" : "Ulashish",
12 | "Default" : "Standart",
13 | "Off" : "Oʻchirilgan",
14 | "Settings" : "Sozlamalar",
15 | "Documentation" : "Hujjatlar",
16 | "Keyboard shortcuts" : "Klaviatura yorliqlari",
17 | "Rename" : "Nomini o'zgartirish",
18 | "Delete" : "O'chirish",
19 | "Title" : "Sarlavha",
20 | "Not available" : "Mavjud emas",
21 | "right" : "right",
22 | "left" : "left",
23 | "Refresh" : "Refresh"
24 | },"pluralForm" :"nplurals=1; plural=0;"
25 | }
--------------------------------------------------------------------------------
/src/components/routes/Recent.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
30 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Configuration for probot-stale - https://github.com/probot/stale
2 |
3 | # Number of days of inactivity before an Issue or Pull Request becomes stale
4 | daysUntilStale: 21
5 | # Number of days of inactivity before a stale Issue or Pull Request is closed
6 | daysUntilClose: 7
7 | # Issues or Pull Requests with these labels will never be considered stale
8 | exemptLabels:
9 | - 0. Needs triage
10 | - 1. to develop
11 | - 2. developing
12 | - 3. to review
13 | - 4. to release
14 | - help wanted
15 | - in progress
16 | # Label to use when marking as stale
17 | staleLabel: stale
18 | # Comment to post when marking as stale. Set to `false` to disable
19 | markComment: >
20 | This issue has been automatically marked as stale because it has not had
21 | recent activity. It will be closed if no further activity occurs.
22 |
--------------------------------------------------------------------------------
/l10n/uz.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Download" : "Yuklab olish",
5 | "Close" : "Yopish",
6 | "Folder name" : "Folder name",
7 | "Username" : "Username",
8 | "Password" : "Password",
9 | "New folder" : "New folder",
10 | "Credentials" : "Hisob ma'lumotlari",
11 | "Subscribe" : "Obuna boʻlish",
12 | "Move" : "Move",
13 | "Share" : "Ulashish",
14 | "Default" : "Standart",
15 | "Off" : "Oʻchirilgan",
16 | "Settings" : "Sozlamalar",
17 | "Documentation" : "Hujjatlar",
18 | "Keyboard shortcuts" : "Klaviatura yorliqlari",
19 | "Rename" : "Nomini o'zgartirish",
20 | "Delete" : "O'chirish",
21 | "Title" : "Sarlavha",
22 | "Not available" : "Mavjud emas",
23 | "right" : "right",
24 | "left" : "left",
25 | "Refresh" : "Refresh"
26 | },
27 | "nplurals=1; plural=0;");
28 |
--------------------------------------------------------------------------------
/l10n/az.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Unread articles" : "Oxunulmamış məqalə",
3 | "Download" : "Yüklə",
4 | "Close" : "Bağla",
5 | "Folder name" : "Qovluq adı",
6 | "Username" : "İstifadəçi adı",
7 | "Password" : "Şifrə",
8 | "New folder" : "Yeni qovluq",
9 | "Credentials" : "Səlahiyyətlər",
10 | "Subscribe" : "Abunə",
11 | "Last job ran {relativeTime}." : "Son işin icra vaxtı {relativeTime}.",
12 | "Move" : "Move",
13 | "Share" : "Paylaş",
14 | "All articles" : "Bütün məqalələr",
15 | "Starred" : "Ulduzlu",
16 | "Settings" : "Quraşdırmalar",
17 | "Documentation" : "Sənədlər",
18 | "Rename" : "Adı dəyiş",
19 | "Delete" : "Sil",
20 | "by" : "onunla",
21 | "Folder" : "Qovluq",
22 | "right" : "Sağ",
23 | "left" : "Sol",
24 | "Refresh" : "Yenilə"
25 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
26 | }
--------------------------------------------------------------------------------
/l10n/nn_NO.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "News" : "Nyhende",
3 | "Download" : "Last ned",
4 | "Close" : "Lukk",
5 | "Web address" : "Nettadresse",
6 | "Folder name" : "Mappenamn",
7 | "Username" : "Brukarnamn",
8 | "Password" : "Passord",
9 | "New folder" : "Ny mappe",
10 | "Credentials" : "Credentials",
11 | "Show all articles" : "Vis alle artiklar",
12 | "Move" : "Move",
13 | "Share" : "Del",
14 | "Settings" : "Innstillingar",
15 | "Documentation" : "Dokumentasjon",
16 | "Keyboard shortcuts" : "Tastatursnarvegar",
17 | "Rename" : "Endra namn",
18 | "Delete" : "Ta bort",
19 | "Newest first" : "Nyaste fyrst",
20 | "Oldest first" : "Eldste fyrst",
21 | "by" : "av",
22 | "Download video" : "Last ned video",
23 | "Folder" : "Mappe",
24 | "Refresh" : "Oppfrisk"
25 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
26 | }
--------------------------------------------------------------------------------
/src/dataservices/share.service.ts:
--------------------------------------------------------------------------------
1 | import type { AxiosResponse } from '@nextcloud/axios'
2 |
3 | import axios from '@nextcloud/axios'
4 | import { generateOcsUrl } from '@nextcloud/router'
5 |
6 | export class ShareService {
7 | /**
8 | * Retrieves all of users matching the search term
9 | *
10 | * @param query {String} search string
11 | * @return Folders contained in data.folders property
12 | */
13 | static fetchUsers(query: string): Promise {
14 | return axios.get(generateOcsUrl(`apps/files_sharing/api/v1/sharees?search=${query}&itemType=news_item&perPage=5/`))
15 | }
16 |
17 | static async share(id: number, users: string[]): Promise {
18 | const promises = []
19 | for (const shareName of users) {
20 | promises.push(axios.post(`items/${id}/share/${shareName}`))
21 | }
22 |
23 | await Promise.all(promises)
24 |
25 | return true
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/l10n/az.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Unread articles" : "Oxunulmamış məqalə",
5 | "Download" : "Yüklə",
6 | "Close" : "Bağla",
7 | "Folder name" : "Qovluq adı",
8 | "Username" : "İstifadəçi adı",
9 | "Password" : "Şifrə",
10 | "New folder" : "Yeni qovluq",
11 | "Credentials" : "Səlahiyyətlər",
12 | "Subscribe" : "Abunə",
13 | "Last job ran {relativeTime}." : "Son işin icra vaxtı {relativeTime}.",
14 | "Move" : "Move",
15 | "Share" : "Paylaş",
16 | "All articles" : "Bütün məqalələr",
17 | "Starred" : "Ulduzlu",
18 | "Settings" : "Quraşdırmalar",
19 | "Documentation" : "Sənədlər",
20 | "Rename" : "Adı dəyiş",
21 | "Delete" : "Sil",
22 | "by" : "onunla",
23 | "Folder" : "Qovluq",
24 | "right" : "Sağ",
25 | "left" : "Sol",
26 | "Refresh" : "Yenilə"
27 | },
28 | "nplurals=2; plural=(n != 1);");
29 |
--------------------------------------------------------------------------------
/img/twitter.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/l10n/nn_NO.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "News" : "Nyhende",
5 | "Download" : "Last ned",
6 | "Close" : "Lukk",
7 | "Web address" : "Nettadresse",
8 | "Folder name" : "Mappenamn",
9 | "Username" : "Brukarnamn",
10 | "Password" : "Passord",
11 | "New folder" : "Ny mappe",
12 | "Credentials" : "Credentials",
13 | "Show all articles" : "Vis alle artiklar",
14 | "Move" : "Move",
15 | "Share" : "Del",
16 | "Settings" : "Innstillingar",
17 | "Documentation" : "Dokumentasjon",
18 | "Keyboard shortcuts" : "Tastatursnarvegar",
19 | "Rename" : "Endra namn",
20 | "Delete" : "Ta bort",
21 | "Newest first" : "Nyaste fyrst",
22 | "Oldest first" : "Eldste fyrst",
23 | "by" : "av",
24 | "Download video" : "Last ned video",
25 | "Folder" : "Mappe",
26 | "Refresh" : "Oppfrisk"
27 | },
28 | "nplurals=2; plural=(n != 1);");
29 |
--------------------------------------------------------------------------------
/lib/Settings/AdminSection.php:
--------------------------------------------------------------------------------
1 | url = $url;
17 | $this->l = $l;
18 | }
19 |
20 | /**
21 | * @return string
22 | */
23 | public function getID()
24 | {
25 | return 'news';
26 | }
27 |
28 | public function getName()
29 | {
30 | return $this->l->t('News');
31 | }
32 |
33 | public function getPriority()
34 | {
35 | return 10;
36 | }
37 |
38 | /**
39 | * @return string
40 | */
41 | public function getIcon()
42 | {
43 | return $this->url->imagePath('news', 'app-dark.svg');
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "image": "ghcr.io/juliusknorr/nextcloud-dev-php82:latest",
3 | "remoteUser": "root",
4 | "updateRemoteUserUID": true,
5 | "forwardPorts": [80],
6 | "containerEnv": {
7 | "NEXTCLOUD_VERSION": "31",
8 | "NEXTCLOUD_AUTOINSTALL_APPS": "news",
9 | "XDEBUG_MODE": "debug"
10 | },
11 | "customizations": {
12 | "vscode": {
13 | "extensions": [
14 | "felixfbecker.php-intellisense",
15 | "octref.vetur"
16 | ],
17 | "settings": {
18 | "php.suggest.basic": false,
19 | "git.alwaysSignOff": true
20 | }
21 | }
22 | },
23 | "workspaceMount": "source=${localWorkspaceFolder},target=/var/www/html/apps-extra/news,type=bind",
24 | "workspaceFolder": "/var/www/html/apps-extra/news",
25 | "overrideCommand": true,
26 | "postAttachCommand": "bash ./.devcontainer/setup.sh",
27 | "portsAttributes": {
28 | "80": {
29 | "label": "Webserver"
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/lib/Listeners/AddMissingIndicesListener.php:
--------------------------------------------------------------------------------
1 |
10 | * @copyright 2023 Benjamin Brahmer
11 | */
12 |
13 | namespace OCA\News\Listeners;
14 |
15 | use OCP\EventDispatcher\Event;
16 | use OCP\EventDispatcher\IEventListener;
17 | use OCP\DB\Events\AddMissingIndicesEvent;
18 |
19 | /**
20 | * @template-implements IEventListener
21 | */
22 | class AddMissingIndicesListener implements IEventListener
23 | {
24 | public function handle(Event $event): void
25 | {
26 | if (!$event instanceof AddMissingIndicesEvent) {
27 | return;
28 | }
29 |
30 | $event->addMissingIndex('news_feeds', 'news_feeds_deleted_at_index', ['deleted_at']);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/Db/EntityJSONSerializer.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @copyright 2012 Alessandro Cosentino
11 | * @copyright 2012-2014 Bernhard Posselt
12 | */
13 |
14 | namespace OCA\News\Db;
15 |
16 | trait EntityJSONSerializer
17 | {
18 |
19 | /**
20 | * Serialize object properties.
21 | *
22 | * @param array $properties Serializable properties
23 | *
24 | * @return array
25 | */
26 | public function serializeFields(array $properties): array
27 | {
28 | $result = [];
29 | foreach ($properties as $property) {
30 | $result[$property] = $this->$property; //@phpstan-ignore-line
31 | }
32 | return $result;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/Listeners/UserSettingsListener.php:
--------------------------------------------------------------------------------
1 | */
16 | class UserSettingsListener implements IEventListener
17 | {
18 |
19 | public function handle(Event $event): void
20 | {
21 | if (!($event instanceof BeforePreferenceSetEvent || $event instanceof BeforePreferenceDeletedEvent)) {
22 | return;
23 | }
24 |
25 | if ($event->getAppId() !== 'news') {
26 | return;
27 | }
28 |
29 | $event->setValid(true);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/l10n/cy_GB.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Download" : "Llwytho i lawr",
3 | "Close" : "Cau",
4 | "Web address" : "Cyfeiriad gwe",
5 | "Username" : "Enw defnyddiwr",
6 | "Password" : "Cyfrinair",
7 | "New folder" : "Ffolder newydd",
8 | "Credentials" : "Credentials",
9 | "Subscribe" : "Tanysgrifio",
10 | "Move" : "Symud",
11 | "Share" : "Rhannu",
12 | "Mark read" : "Marcio wedi'i ddarllen",
13 | "Settings" : "Gosodiadau",
14 | "Documentation" : "Dogfennaeth",
15 | "Keyboard shortcuts" : "Llwybrau byr bysellfwrdd",
16 | "Rename" : "Ailenwi",
17 | "Delete" : "Dileu",
18 | "Newest first" : "Diweddaraf gyntaf",
19 | "Oldest first" : "Hynaf gyntaf",
20 | "Mark unread" : "Nodi heb ei ddarllen",
21 | "by" : "gan",
22 | "Folder" : "Plygell",
23 | "Not available" : "Ddim ar gael",
24 | "Refresh" : "Adnewyddu"
25 | },"pluralForm" :"nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;"
26 | }
--------------------------------------------------------------------------------
/lib/Service/Exceptions/ServiceNotFoundException.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @copyright 2012 Alessandro Cosentino
11 | * @copyright 2012-2014 Bernhard Posselt
12 | */
13 |
14 | namespace OCA\News\Service\Exceptions;
15 |
16 | use Exception;
17 | use OCP\AppFramework\Db\IMapperException;
18 |
19 | /**
20 | * Class ServiceNotFoundException
21 | *
22 | * @package OCA\News\Service\Exceptions
23 | */
24 | class ServiceNotFoundException extends ServiceException
25 | {
26 | /**
27 | * @inheritDoc
28 | */
29 | public static function from(IMapperException $exception): ServiceException
30 | {
31 | return new self($exception->getMessage(), $exception->getCode(), $exception);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/Service/Exceptions/ServiceConflictException.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @copyright 2012 Alessandro Cosentino
11 | * @copyright 2012-2014 Bernhard Posselt
12 | */
13 |
14 | namespace OCA\News\Service\Exceptions;
15 |
16 | use Exception;
17 | use OCP\AppFramework\Db\IMapperException;
18 |
19 | /**
20 | * Class ServiceConflictException
21 | *
22 | * @package OCA\News\Service\Exceptions
23 | */
24 | class ServiceConflictException extends ServiceException
25 | {
26 |
27 | /**
28 | * @inheritDoc
29 | */
30 | public static function from(IMapperException $exception): ServiceException
31 | {
32 | return new self($exception->getMessage(), $exception->getCode(), $exception);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/l10n/cy_GB.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Download" : "Llwytho i lawr",
5 | "Close" : "Cau",
6 | "Web address" : "Cyfeiriad gwe",
7 | "Username" : "Enw defnyddiwr",
8 | "Password" : "Cyfrinair",
9 | "New folder" : "Ffolder newydd",
10 | "Credentials" : "Credentials",
11 | "Subscribe" : "Tanysgrifio",
12 | "Move" : "Symud",
13 | "Share" : "Rhannu",
14 | "Mark read" : "Marcio wedi'i ddarllen",
15 | "Settings" : "Gosodiadau",
16 | "Documentation" : "Dogfennaeth",
17 | "Keyboard shortcuts" : "Llwybrau byr bysellfwrdd",
18 | "Rename" : "Ailenwi",
19 | "Delete" : "Dileu",
20 | "Newest first" : "Diweddaraf gyntaf",
21 | "Oldest first" : "Hynaf gyntaf",
22 | "Mark unread" : "Nodi heb ei ddarllen",
23 | "by" : "gan",
24 | "Folder" : "Plygell",
25 | "Not available" : "Ddim ar gael",
26 | "Refresh" : "Adnewyddu"
27 | },
28 | "nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;");
29 |
--------------------------------------------------------------------------------
/lib/Service/Exceptions/ServiceValidationException.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @copyright 2012 Alessandro Cosentino
11 | * @copyright 2012-2014 Bernhard Posselt
12 | */
13 |
14 | namespace OCA\News\Service\Exceptions;
15 |
16 | use Exception;
17 | use OCP\AppFramework\Db\IMapperException;
18 |
19 | /**
20 | * Class ServiceValidationException
21 | *
22 | * @package OCA\News\Service\Exceptions
23 | */
24 | class ServiceValidationException extends ServiceException
25 | {
26 |
27 | /**
28 | * @inheritDoc
29 | */
30 | public static function from(IMapperException $exception): ServiceException
31 | {
32 | return new self($exception->getMessage(), $exception->getCode(), $exception);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/utils/dateUtils.ts:
--------------------------------------------------------------------------------
1 | import { formatRelativeTime } from '@nextcloud/l10n'
2 | import moment from '@nextcloud/moment'
3 |
4 | /**
5 | * Returns locale formatted date string
6 | *
7 | * @param epoch date value in epoch format
8 | * @return locale formatted date string
9 | */
10 | export function formatDate(epoch: number) {
11 | return moment.unix(epoch).format('L, LTS') // e.g. "04/20/2025 18:12:21"
12 | }
13 |
14 | /**
15 | * Returns locale relative date string
16 | *
17 | * @param epoch date value in epoch format
18 | * @return locale relative date string
19 | */
20 | export function formatDateRelative(epoch: number) {
21 | return epoch ? formatRelativeTime(epoch * 1000) : '' // e.g. "one hour ago"
22 | }
23 |
24 | /**
25 | * Returns ISO date string
26 | *
27 | * @param epoch date value in epoch format
28 | * @return ISO date string
29 | */
30 | export function formatDateISO(epoch: number) {
31 | return moment.unix(epoch).toISOString() // ISO 8601 e.g. "2025-04-20T18:12:21Z"
32 | }
33 |
--------------------------------------------------------------------------------
/l10n/bn_BD.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Unread articles" : "অপঠিত লেখা",
3 | "Download" : "ডাউনলোড",
4 | "Close" : "বন্ধ",
5 | "Web address" : "ওয়েব ঠিকানা",
6 | "Folder name" : "ফোলডারের নাম",
7 | "Username" : "ব্যবহারকারী",
8 | "Password" : "কূটশব্দ",
9 | "Feed exists already!" : "ফিড পূর্বে থেকেই বিদ্যমান",
10 | "New folder" : "নব ফােলডার",
11 | "Folder exists already!" : "ফোল্ডার ইতোমধ্যে বিদ্যমান!",
12 | "Credentials" : "Credentials",
13 | "Subscribe" : "গ্রাহক হোন",
14 | "Move" : "Move",
15 | "Share" : "ভাগাভাগি কর",
16 | "All articles" : "সব লেখা",
17 | "Starred" : "তারা চিহ্নিত",
18 | "Settings" : "নিয়ামকসমূহ",
19 | "Documentation" : "নথিবদ্ধকরণ",
20 | "Keyboard shortcuts" : "কী-বোর্ড শর্টকাট",
21 | "Rename" : "পূনঃনামকরণ",
22 | "Delete" : "মুছে",
23 | "by" : "কর্তৃক",
24 | "Folder" : "ফোল্ডার",
25 | "right" : "ডান",
26 | "left" : "বাম",
27 | "Refresh" : "নবোদ্যম"
28 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
29 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /**
2 | * Initially copied from typescript+vue2 project generated by @vue/cli-plugin v5.0.0
3 | */
4 | {
5 | "extends": "@vue/tsconfig",
6 | "compilerOptions": {
7 | "target": "esnext",
8 | "module": "esnext",
9 | "strict": true,
10 | "moduleResolution": "node",
11 | "experimentalDecorators": true,
12 | "skipLibCheck": true,
13 | "esModuleInterop": true,
14 | "allowSyntheticDefaultImports": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "useDefineForClassFields": true,
17 | "sourceMap": true,
18 | "baseUrl": ".",
19 | "types": [
20 | "vitest",
21 | "node"
22 | ],
23 | "paths": {
24 | "@/*": [
25 | "src/*"
26 | ]
27 | },
28 | "lib": [
29 | "esnext",
30 | "dom",
31 | "dom.iterable",
32 | "scripthost"
33 | ]
34 | },
35 | "include": [
36 | "src/**/*.ts",
37 | "src/**/*.vue",
38 | "tests/**/*.ts"
39 | ],
40 | "exclude": [
41 | "node_modules"
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | /*!
2 | * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3 | * SPDX-License-Identifier: CC0-1.0
4 | */
5 |
6 | import { createAppConfig } from '@nextcloud/vite-config'
7 | import { resolve } from 'node:path'
8 |
9 | export default createAppConfig({
10 | main: 'src/main.js',
11 | 'admin-settings': 'src/main-admin.js',
12 | 'cron-warning': 'src/main-cron-warning.js',
13 | }, {
14 | inlineCSS: { relativeCSSInjection: true },
15 | config: {
16 | build: {
17 | cssCodeSplit: true,
18 | },
19 | test: {
20 | coverage: {
21 | include: ['src/**/*.ts', 'src/**/*.vue'],
22 | provider: 'istanbul',
23 | reporter: ['lcov', 'text'],
24 | },
25 | environment: 'jsdom',
26 | setupFiles: resolve(__dirname, './tests/javascript/unit/setup.js'),
27 | server: {
28 | deps: {
29 | inline: [
30 | // Fix unresolvable .css extension for ssr
31 | /@nextcloud\/vue/,
32 | /@nextcloud\/dialogs/,
33 | ],
34 | },
35 | },
36 | },
37 | }
38 | })
39 |
40 |
--------------------------------------------------------------------------------
/docker/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: bash check-code show-log test build
2 |
3 | # Try to use "docker compose" and fall back to "docker-compose" if not available
4 | DOCKER_COMPOSE := $(shell docker compose > /dev/null && echo docker compose || echo docker-compose)
5 |
6 | build:
7 | $(DOCKER_COMPOSE) build
8 |
9 | ls-db:
10 | $(DOCKER_COMPOSE) run --rm app su -c "ls -hal data/mydb.db*" www-data
11 |
12 | fetch-db:
13 | $(DOCKER_COMPOSE) run --rm app su -c "cp data/mydb.db apps/news" www-data
14 |
15 | push-db:
16 | $(DOCKER_COMPOSE) run --rm app su -c "cp apps/news/mydb.db* data" www-data
17 |
18 | bash:
19 | $(DOCKER_COMPOSE) run --rm app su -c "bash" www-data
20 |
21 | cron:
22 | $(DOCKER_COMPOSE) run --rm app su -c "php cron.php" www-data
23 |
24 | check-code:
25 | $(DOCKER_COMPOSE) run --rm app su -c "./occ app:check-code news" www-data
26 |
27 | show-log:
28 | $(DOCKER_COMPOSE) run --rm app tail -f /var/www/html/data/nextcloud.log
29 |
30 | #test:
31 | # $(DOCKER_COMPOSE) run --rm app su -c "cd custom_apps/news && make test" www-data
32 |
--------------------------------------------------------------------------------
/l10n/bn_BD.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Unread articles" : "অপঠিত লেখা",
5 | "Download" : "ডাউনলোড",
6 | "Close" : "বন্ধ",
7 | "Web address" : "ওয়েব ঠিকানা",
8 | "Folder name" : "ফোলডারের নাম",
9 | "Username" : "ব্যবহারকারী",
10 | "Password" : "কূটশব্দ",
11 | "Feed exists already!" : "ফিড পূর্বে থেকেই বিদ্যমান",
12 | "New folder" : "নব ফােলডার",
13 | "Folder exists already!" : "ফোল্ডার ইতোমধ্যে বিদ্যমান!",
14 | "Credentials" : "Credentials",
15 | "Subscribe" : "গ্রাহক হোন",
16 | "Move" : "Move",
17 | "Share" : "ভাগাভাগি কর",
18 | "All articles" : "সব লেখা",
19 | "Starred" : "তারা চিহ্নিত",
20 | "Settings" : "নিয়ামকসমূহ",
21 | "Documentation" : "নথিবদ্ধকরণ",
22 | "Keyboard shortcuts" : "কী-বোর্ড শর্টকাট",
23 | "Rename" : "পূনঃনামকরণ",
24 | "Delete" : "মুছে",
25 | "by" : "কর্তৃক",
26 | "Folder" : "ফোল্ডার",
27 | "right" : "ডান",
28 | "left" : "বাম",
29 | "Refresh" : "নবোদ্যম"
30 | },
31 | "nplurals=2; plural=(n != 1);");
32 |
--------------------------------------------------------------------------------
/lib/Scraper/IScraper.php:
--------------------------------------------------------------------------------
1 |
9 | * @copyright 2019 Gioele Falcetti
10 | */
11 |
12 | namespace OCA\News\Scraper;
13 |
14 | interface IScraper
15 | {
16 | /**
17 | * Scrape feed url
18 | *
19 | * @param string $url
20 | *
21 | * @return bool False if failed
22 | *
23 | */
24 | public function scrape(string $url): bool;
25 |
26 | /**
27 | * Get the scraped content
28 | *
29 | * @return string|null
30 | *
31 | */
32 | public function getContent(): ?string;
33 |
34 | /**
35 | * Get the RTL (right-to-left) information
36 | *
37 | * @param bool $default Return this value if the scraper is unable to determine it
38 | *
39 | * @return bool
40 | *
41 | */
42 | public function getRTL(bool $default = false): bool;
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Unit/Controller/JSONHttpErrorTest.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @copyright 2012 Alessandro Cosentino
11 | * @copyright 2012-2014 Bernhard Posselt
12 | */
13 |
14 | namespace OCA\News\Tests\Unit\Controller;
15 |
16 | use OCA\News\Controller\JSONHttpErrorTrait;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class JSONHttpErrorTest extends TestCase
20 | {
21 |
22 |
23 | public function testError()
24 | {
25 | $ex = new \Exception('hi');
26 | $test = new DummyTraitingClass();
27 | $result = $test->error($ex, 3);
28 |
29 | $this->assertEquals(['message' => 'hi'], $result->getData());
30 | $this->assertEquals(3, $result->getStatus());
31 | }
32 | }
33 |
34 |
35 | class DummyTraitingClass
36 | {
37 | use JSONHttpErrorTrait;
38 | }
39 |
--------------------------------------------------------------------------------
/.mailmap:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | David-Development
7 |
8 | Alessandro Cosentino Alessandro
9 | Jenkins for ownCloud Jenkins for ownCloud
10 | Thomas Müller Thomas Mueller
11 | bastei bastei
12 | Konrad Graefe kgraefe
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/tests/command/opml.bats:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bats
2 |
3 | setup(){
4 | load "../test_helper/bats-support/load"
5 | load "../test_helper/bats-assert/load"
6 | load "helpers/settings"
7 | }
8 |
9 | TESTSUITE="OPML"
10 |
11 | teardown() {
12 | ID_LIST=($(./occ news:feed:list 'admin' | grep -Po '"id": \K([0-9]+)' | tr '\n' ' '))
13 | for ID in $ID_LIST; do
14 | ./occ news:feed:delete "$user" "$ID"
15 | done
16 | }
17 |
18 | @test "[$TESTSUITE] Import" {
19 | run ./occ news:opml:import "$user" apps/news/tests/test_helper/feeds/Nextcloud.opml
20 | assert_success
21 |
22 | run ./occ news:feed:list "$user"
23 | assert_success
24 |
25 | if ! echo "$output" | grep "title.*Nextcloud"; then
26 | assert_output --partial "Feed not imported"
27 | fi
28 | }
29 |
30 | @test "[$TESTSUITE] Export" {
31 | run ./occ news:feed:add "$user" "$NC_FEED" --title "Something-${BATS_SUITE_TEST_NUMBER}"
32 | assert_success
33 |
34 | run ./occ news:opml:export "$user"
35 | assert_success
36 |
37 | if ! echo "$output" | grep "Something-${BATS_SUITE_TEST_NUMBER}"; then
38 | assert_output --partial "Feed not exported"
39 | fi
40 | }
41 |
--------------------------------------------------------------------------------
/term.kdl:
--------------------------------------------------------------------------------
1 | // https://zellij.dev/documentation/creating-a-layout
2 | layout {
3 | tab name="main" focus=true {
4 | pane size=1 borderless=true {
5 | plugin location="zellij:tab-bar"
6 | }
7 | pane split_direction="vertical" size="60%" {
8 | pane {
9 | command "lazygit"
10 | focus true
11 | }
12 | pane cwd="docker" command="docker" {
13 | args "compose" "up"
14 | start_suspended false
15 | }
16 | }
17 | pane split_direction="vertical" size="40%" {
18 | pane size="25%" {
19 | command "npm"
20 | args "install"
21 | }
22 | pane size="25%" {
23 | command "composer"
24 | args "install"
25 | }
26 | pane size="50%" {
27 | command "npm"
28 | args "run" "watch"
29 | }
30 | }
31 | pane size=1 borderless=true {
32 | plugin location="zellij:status-bar"
33 | }
34 | }
35 | tab name="term" {
36 | pane size=1 borderless=true {
37 | plugin location="zellij:tab-bar"
38 | }
39 | pane cwd="docker"
40 | pane size=1 borderless=true {
41 | plugin location="zellij:status-bar"
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - master
8 |
9 | jobs:
10 | eslint:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [16.x]
16 |
17 | name: eslint node${{ matrix.node-versions }}
18 | steps:
19 | - uses: actions/checkout@v6.0.1
20 |
21 | - name: Set up node ${{ matrix.node-versions }}
22 | uses: actions/setup-node@v6.1.0
23 | with:
24 | node-version: ${{ matrix.node-versions }}
25 |
26 | - name: Install dependencies
27 | run: npm ci
28 |
29 | - name: Lint
30 | run: npm run lint
31 |
32 | stylelint:
33 | runs-on: ubuntu-latest
34 |
35 | strategy:
36 | matrix:
37 | node-version: [16.x]
38 |
39 | name: stylelint node${{ matrix.node-versions }}
40 | steps:
41 | - uses: actions/checkout@v6.0.1
42 |
43 | - name: Set up node ${{ matrix.node-versions }}
44 | uses: actions/setup-node@v6.1.0
45 | with:
46 | node-version: ${{ matrix.node-versions }}
47 |
48 | - name: Install dependencies
49 | run: npm ci
50 |
51 | - name: Lint
52 | run: npm run stylelint
53 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import type { AppInfoState } from './app.ts'
2 | import type { FeedState } from './feed.ts'
3 | import type { FolderState } from './folder.ts'
4 | import type { ItemState } from './item.ts'
5 |
6 | import { APPLICATION_MUTATION_TYPES, FEED_ITEM_MUTATION_TYPES, FEED_MUTATION_TYPES, FOLDER_MUTATION_TYPES } from '../types/MutationTypes.ts'
7 | import app, { APPLICATION_ACTION_TYPES } from './app.ts'
8 | import feeds, { FEED_ACTION_TYPES } from './feed.ts'
9 | import folders, { FOLDER_ACTION_TYPES } from './folder.ts'
10 | import items, { FEED_ITEM_ACTION_TYPES } from './item.ts'
11 |
12 | export const MUTATIONS = {
13 | ...APPLICATION_MUTATION_TYPES,
14 | ...FEED_MUTATION_TYPES,
15 | ...FOLDER_MUTATION_TYPES,
16 | ...FEED_ITEM_MUTATION_TYPES,
17 | }
18 |
19 | export const ACTIONS = {
20 | ...APPLICATION_ACTION_TYPES,
21 | ...FEED_ACTION_TYPES,
22 | ...FOLDER_ACTION_TYPES,
23 | ...FEED_ITEM_ACTION_TYPES,
24 | }
25 |
26 | export type AppState = FolderState & FeedState & ItemState & AppInfoState
27 |
28 | type Func = (name: string, value: unknown) => void
29 | export type ActionParams = { commit: Func, dispatch: Func, state: T }
30 |
31 | export default {
32 | modules: {
33 | feeds,
34 | folders,
35 | items,
36 | app,
37 | },
38 | }
39 |
--------------------------------------------------------------------------------
/.github/workflows/lint-info-xml.yml:
--------------------------------------------------------------------------------
1 | # This workflow is provided via the organization template repository
2 | #
3 | # https://github.com/nextcloud/.github
4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5 | #
6 | # SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
7 | # SPDX-License-Identifier: MIT
8 |
9 | name: Lint info.xml
10 |
11 | on: pull_request
12 |
13 | permissions:
14 | contents: read
15 |
16 | concurrency:
17 | group: lint-info-xml-${{ github.head_ref || github.run_id }}
18 | cancel-in-progress: true
19 |
20 | jobs:
21 | xml-linters:
22 | runs-on: ubuntu-latest-low
23 |
24 | name: info.xml lint
25 | steps:
26 | - name: Checkout
27 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
28 | with:
29 | persist-credentials: false
30 |
31 | - name: Download schema
32 | run: wget https://raw.githubusercontent.com/nextcloud/appstore/master/nextcloudappstore/api/v1/release/info.xsd
33 |
34 | - name: Lint info.xml
35 | uses: ChristophWurst/xmllint-action@36f2a302f84f8c83fceea0b9c59e1eb4a616d3c1 # v1.2
36 | with:
37 | xml-file: ./appinfo/info.xml
38 | xml-schema-file: ./info.xsd
39 |
--------------------------------------------------------------------------------
/l10n/br.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Download" : "Pellgargañ",
3 | "Close" : "Serriñ",
4 | "Web address" : "Chom-lec'h web",
5 | "No folder" : "Teuliad ebet",
6 | "Folder name" : "Anv teuliad",
7 | "Username" : "anv implijer",
8 | "Password" : "Ger-tremen",
9 | "New folder" : "Teuliad nevez",
10 | "Credentials" : "Kretaatoù",
11 | "Move" : "Diplasañ",
12 | "Share" : "Rannan",
13 | "All articles" : "Pep pennad",
14 | "Settings" : "Arventennoù",
15 | "Documentation" : "Diellvadur",
16 | "Rename" : "Adenvel",
17 | "Delete" : "Dilemel",
18 | "Newest first" : "An hini nevesañ da gentañ",
19 | "Oldest first" : "An hini kozhoñ da gentañ",
20 | "by" : "gant",
21 | "Play audio" : "Loc'hañ an audio",
22 | "Download video" : "Pellkargañ ar video",
23 | "Open website" : "Digor al lec'hienn-web",
24 | "Title" : "Titl",
25 | "Folder" : "Teuliad",
26 | "Refresh" : "Freskaat"
27 | },"pluralForm" :"nplurals=5; plural=((n%10 == 1) && (n%100 != 11) && (n%100 !=71) && (n%100 !=91) ? 0 :(n%10 == 2) && (n%100 != 12) && (n%100 !=72) && (n%100 !=92) ? 1 :(n%10 ==3 || n%10==4 || n%10==9) && (n%100 < 10 || n% 100 > 19) && (n%100 < 70 || n%100 > 79) && (n%100 < 90 || n%100 > 99) ? 2 :(n != 0 && n % 1000000 == 0) ? 3 : 4);"
28 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #specific to news app
2 | composer.phar
3 | node_modules/
4 | vendor/
5 | *.log
6 | /build/
7 | js/
8 | .rvm
9 | *.clover
10 | .phpunit.result.cache
11 | site/
12 | coverage
13 |
14 | #scoped dependencies
15 | lib/Vendor/*
16 |
17 | #bats
18 | tests/api/helpers/settings-override.bash
19 | tests/test_helper/feeds/test.xml
20 | tests/test_helper/feeds/feed1.xml
21 | tests/test_helper/feeds/feed2.xml
22 |
23 | #bats
24 | tests/api/helpers/settings-override.bash
25 | tests/test_helper/feeds/test.xml
26 | tests/test_helper/feeds/feed1.xml
27 | tests/test_helper/feeds/feed2.xml
28 |
29 | # python
30 | PKG-INFO
31 | *pyc
32 | *~
33 | __pycache__
34 | bin/updater/dist/
35 | bin/updater/build
36 |
37 | # just sane ignores
38 | .*.sw[po]
39 | *.bak
40 | *.BAK
41 | *~
42 | *.orig
43 | *.class
44 | .cvsignore
45 | Thumbs.db
46 | *.py[co]
47 | _darcs/*
48 | CVS/*
49 | .svn/*
50 | RCS/*
51 |
52 | # kdevelop
53 | .kdev
54 | *.kdev4
55 |
56 | # Lokalize
57 | *lokalize*
58 |
59 | # eclipse
60 | .project
61 | .settings
62 |
63 | # netbeans
64 | nbproject
65 |
66 | # vscode
67 | .vscode
68 |
69 | # phpStorm
70 | .idea
71 |
72 | # geany
73 | *.geany
74 |
75 | # Cloud9IDE
76 | .settings.xml
77 |
78 | # vim ex mode
79 | .vimrc
80 |
81 | # Mac OS
82 | .DS_Store
83 | *.iml
84 |
85 | # VS Code
86 | .vscode
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import axios from '@nextcloud/axios'
2 | import { createApp } from 'vue'
3 | import { createStore } from 'vuex'
4 | import App from './App.vue'
5 | import router from './routes/index.ts'
6 | import mainStore, { MUTATIONS } from './store/index.ts'
7 |
8 | const store = createStore(mainStore)
9 |
10 | /**
11 | * Handles errors returned during application runtime
12 | *
13 | * @param {Error} error Error thrown
14 | * @return {Promise} Error promise
15 | */
16 | function handleErrors(error) {
17 | store.commit(MUTATIONS.SET_ERROR, error)
18 | return Promise.reject(error)
19 | }
20 |
21 | /**
22 | * onSuccessCallback is intentionally undefined (triggers on 2xx responses)
23 | * Any status codes that falls outside the range of 2xx cause this function to trigger
24 | */
25 | axios.interceptors.response.use(undefined, handleErrors)
26 |
27 | const app = createApp(App)
28 |
29 | app.use(store)
30 | app.use(router)
31 |
32 | app.config.globalProperties.t = t
33 | app.config.globalProperties.n = n
34 | app.config.globalProperties.OC = OC
35 | app.config.globalProperties.OCA = OCA
36 |
37 | app.config.errorHandler = handleErrors
38 |
39 | app.mount('#content')
40 |
41 | // Make store accessible for setting cron warning (also for plugins in the future)
42 | window.store = store
43 |
--------------------------------------------------------------------------------
/l10n/br.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Download" : "Pellgargañ",
5 | "Close" : "Serriñ",
6 | "Web address" : "Chom-lec'h web",
7 | "No folder" : "Teuliad ebet",
8 | "Folder name" : "Anv teuliad",
9 | "Username" : "anv implijer",
10 | "Password" : "Ger-tremen",
11 | "New folder" : "Teuliad nevez",
12 | "Credentials" : "Kretaatoù",
13 | "Move" : "Diplasañ",
14 | "Share" : "Rannan",
15 | "All articles" : "Pep pennad",
16 | "Settings" : "Arventennoù",
17 | "Documentation" : "Diellvadur",
18 | "Rename" : "Adenvel",
19 | "Delete" : "Dilemel",
20 | "Newest first" : "An hini nevesañ da gentañ",
21 | "Oldest first" : "An hini kozhoñ da gentañ",
22 | "by" : "gant",
23 | "Play audio" : "Loc'hañ an audio",
24 | "Download video" : "Pellkargañ ar video",
25 | "Open website" : "Digor al lec'hienn-web",
26 | "Title" : "Titl",
27 | "Folder" : "Teuliad",
28 | "Refresh" : "Freskaat"
29 | },
30 | "nplurals=5; plural=((n%10 == 1) && (n%100 != 11) && (n%100 !=71) && (n%100 !=91) ? 0 :(n%10 == 2) && (n%100 != 12) && (n%100 !=72) && (n%100 !=92) ? 1 :(n%10 ==3 || n%10==4 || n%10==9) && (n%100 < 10 || n% 100 > 19) && (n%100 < 70 || n%100 > 79) && (n%100 < 90 || n%100 > 99) ? 2 :(n != 0 && n % 1000000 == 0) ? 3 : 4);");
31 |
--------------------------------------------------------------------------------
/lib/Migration/RemoveUnusedJob.php:
--------------------------------------------------------------------------------
1 | logger = $logger;
26 | $this->joblist = $jobList;
27 | }
28 |
29 | /**
30 | * Returns the step's name
31 | */
32 | public function getName()
33 | {
34 | return 'Remove the unused News update job';
35 | }
36 |
37 | /**
38 | * @param IOutput $output
39 | */
40 | public function run(IOutput $output)
41 | {
42 | if ($this->joblist->has("OCA\News\Cron\Updater", null)) {
43 | $output->info("Job exists, attempting to remove");
44 | $this->joblist->remove("OCA\News\Cron\Updater");
45 | $output->info("Job removed");
46 | } else {
47 | $output->info("Job does not exist, all good");
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/l10n/af.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "News" : "Nuus",
3 | "Shared with me" : "Gedeel met my",
4 | "Download" : "Laai Af",
5 | "Close" : "Sluit",
6 | "Web address" : "Webadres",
7 | "No folder" : "Geen vouer",
8 | "Folder name" : "Vouernaam",
9 | "Username" : "Gebruikersnaam",
10 | "Password" : "Wagwoord",
11 | "New folder" : "Nuwe vouer",
12 | "Folder exists already!" : "Vouers bestaan reeds!",
13 | "Subscribe" : "Teken in",
14 | "Move" : "Skuif",
15 | "Share" : "Deel",
16 | "All articles" : "Alle artikels",
17 | "Explore" : "Ontdek",
18 | "Settings" : "Instellings",
19 | "Documentation" : "Dokumentasie",
20 | "Keyboard shortcuts" : "Sneltoetse",
21 | "Rename" : "Hernoem",
22 | "Delete" : "Skrap",
23 | "Newest first" : "Nuutste eerste",
24 | "Oldest first" : "Oudste eerste",
25 | "by" : "deur",
26 | "Play audio" : "Speel oudio af",
27 | "Download audio" : "Laai oudio af",
28 | "Download video" : "Laai video af",
29 | "Folder" : "Vouer",
30 | "Keyboard shortcut" : "Snelsleutel",
31 | "Jump to next article" : "Spring na volgende artikel",
32 | "Jump to previous article" : "Spring na vorige artikel",
33 | "right" : "regs",
34 | "left" : "links",
35 | "Refresh" : "Verfris"
36 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
37 | }
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | ./tests/Unit
7 |
8 |
9 |
10 |
11 |
12 | ./lib/
13 |
14 |
15 | ./lib/AppInfo/Application.php
16 | ./lib/Controller/JSONHttpErrorTrait.php
17 | ./lib/*/Exceptions
18 | ./lib/Migration
19 | ./lib/Vendor
20 | ./lib/Db/FeedType.php
21 | ./lib/Db/IAPI.php
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/types/MutationTypes.ts:
--------------------------------------------------------------------------------
1 | export const FEED_MUTATION_TYPES = {
2 | ADD_FEED: 'ADD_FEED',
3 | SET_FEEDS: 'SET_FEEDS',
4 | UPDATE_FEED: 'UPDATE_FEED',
5 | MOVE_FEED: 'MOVE_FEED',
6 |
7 | SET_NEWEST_ITEM_ID: 'SET_NEWEST_ITEM_ID',
8 | SET_FEED_ALL_READ: 'SET_FEED_ALL_READ',
9 | MODIFY_FEED_UNREAD_COUNT: 'MODIFY_FEED_UNREAD_COUNT',
10 |
11 | FEED_DELETE: 'FEED_DELETE',
12 | }
13 |
14 | export const FOLDER_MUTATION_TYPES = {
15 | SET_FOLDERS: 'SET_FOLDERS',
16 | DELETE_FOLDER: 'DELETE_FOLDER',
17 | UPDATE_FOLDER: 'UPDATE_FOLDER',
18 | REMOVE_FOLDER_FEED: 'REMOVE_FOLDER_FEED',
19 |
20 | MODIFY_FOLDER_UNREAD_COUNT: 'MODIFY_FOLDER_UNREAD_COUNT',
21 | }
22 |
23 | export const FEED_ITEM_MUTATION_TYPES = {
24 | SET_ITEMS: 'SET_ITEMS',
25 | UPDATE_ITEM: 'UPDATE_ITEM',
26 |
27 | SET_SELECTED_ITEM: 'SET_SELECTED_ITEM',
28 | SET_PLAYING_ITEM: 'SET_PLAYING_ITEM',
29 |
30 | SET_STARRED_COUNT: 'SET_STARRED_COUNT',
31 | SET_UNREAD_COUNT: 'SET_UNREAD_COUNT',
32 | MODIFY_UNREAD_COUNT: 'MODIFY_UNREAD_COUNT',
33 |
34 | SET_FETCHING: 'SET_FETCHING',
35 | SET_ALL_LOADED: 'SET_ALL_LOADED',
36 | SET_LAST_ITEM_LOADED: 'SET_LAST_ITEM_LOADED',
37 | SET_NEWEST_ITEM_ID: 'SET_NEWEST_ITEM_ID',
38 | RESET_ITEM_STATES: 'RESET_ITEM_STATES',
39 | }
40 |
41 | export const APPLICATION_MUTATION_TYPES = {
42 | SET_ERROR: 'SET_ERROR',
43 | SET_LOADING: 'SET_LOADING',
44 | }
45 |
--------------------------------------------------------------------------------
/l10n/af.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "News" : "Nuus",
5 | "Shared with me" : "Gedeel met my",
6 | "Download" : "Laai Af",
7 | "Close" : "Sluit",
8 | "Web address" : "Webadres",
9 | "No folder" : "Geen vouer",
10 | "Folder name" : "Vouernaam",
11 | "Username" : "Gebruikersnaam",
12 | "Password" : "Wagwoord",
13 | "New folder" : "Nuwe vouer",
14 | "Folder exists already!" : "Vouers bestaan reeds!",
15 | "Subscribe" : "Teken in",
16 | "Move" : "Skuif",
17 | "Share" : "Deel",
18 | "All articles" : "Alle artikels",
19 | "Explore" : "Ontdek",
20 | "Settings" : "Instellings",
21 | "Documentation" : "Dokumentasie",
22 | "Keyboard shortcuts" : "Sneltoetse",
23 | "Rename" : "Hernoem",
24 | "Delete" : "Skrap",
25 | "Newest first" : "Nuutste eerste",
26 | "Oldest first" : "Oudste eerste",
27 | "by" : "deur",
28 | "Play audio" : "Speel oudio af",
29 | "Download audio" : "Laai oudio af",
30 | "Download video" : "Laai video af",
31 | "Folder" : "Vouer",
32 | "Keyboard shortcut" : "Snelsleutel",
33 | "Jump to next article" : "Spring na volgende artikel",
34 | "Jump to previous article" : "Spring na vorige artikel",
35 | "right" : "regs",
36 | "left" : "links",
37 | "Refresh" : "Verfris"
38 | },
39 | "nplurals=2; plural=(n != 1);");
40 |
--------------------------------------------------------------------------------
/lib/Service/Exceptions/ServiceException.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @copyright 2012 Alessandro Cosentino
11 | * @copyright 2012-2014 Bernhard Posselt
12 | */
13 |
14 | namespace OCA\News\Service\Exceptions;
15 |
16 | use Exception;
17 | use OCP\AppFramework\Db\IMapperException;
18 |
19 | /**
20 | * Class ServiceException
21 | *
22 | * @package OCA\News\Service\Exceptions
23 | */
24 | abstract class ServiceException extends Exception
25 | {
26 |
27 | /**
28 | * Constructor
29 | *
30 | * @param string $msg the error message
31 | * @param int $code
32 | * @param Exception|null $previous
33 | */
34 | final public function __construct(string $msg, int $code = 0, ?Exception $previous = null)
35 | {
36 | parent::__construct($msg, $code, $previous);
37 | }
38 |
39 | /**
40 | * Create exception from Mapper exception.
41 | *
42 | * @param IMapperException $exception Existing exception
43 | *
44 | * @return static
45 | */
46 | abstract public static function from(IMapperException $exception): ServiceException;
47 | }
48 |
--------------------------------------------------------------------------------
/tests/javascript/unit/services/share.service.spec.ts:
--------------------------------------------------------------------------------
1 | import axios from '@nextcloud/axios'
2 | import { beforeEach, describe, expect, it } from 'vitest'
3 | import { ShareService } from './../../../../src/dataservices/share.service'
4 |
5 | describe('share.service.ts', () => {
6 | 'use strict'
7 |
8 | beforeEach(() => {
9 | axios.get.mockReset()
10 | axios.post.mockReset()
11 | })
12 |
13 | describe('fetchUsers', () => {
14 | it('should call GET to retrieve users', async () => {
15 | axios.get.mockResolvedValue({ data: { feeds: [] } })
16 |
17 | await ShareService.fetchUsers('abc')
18 |
19 | expect(axios.get).toBeCalled()
20 | const args = axios.get.mock.calls[0]
21 |
22 | expect(args[0]).toContain('search=abc')
23 | })
24 | })
25 |
26 | describe('share', () => {
27 | it('should call POST for each user passed', async () => {
28 | await ShareService.share(123, ['share-user'])
29 |
30 | expect(axios.post).toBeCalledTimes(1)
31 | let args = axios.post.mock.calls[0]
32 |
33 | expect(args[0]).toContain('123/share/share-user')
34 |
35 | await ShareService.share(345, ['share-user', 'share2'])
36 |
37 | expect(axios.post).toBeCalledTimes(3)
38 |
39 | args = axios.post.mock.calls[1]
40 | expect(args[0]).toContain('345/share/share-user')
41 | args = axios.post.mock.calls[2]
42 | expect(args[0]).toContain('345/share/share2')
43 | })
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/lib/Hooks/UserDeleteHook.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @copyright 2012 Alessandro Cosentino
11 | * @copyright 2012-2014 Bernhard Posselt
12 | */
13 |
14 | namespace OCA\News\Hooks;
15 |
16 | use OCA\News\AppInfo\Application;
17 | use OCA\News\Service\FeedServiceV2;
18 | use OCA\News\Service\FolderServiceV2;
19 | use OCA\News\Service\ItemServiceV2;
20 | use OCP\EventDispatcher\Event;
21 | use OCP\EventDispatcher\IEventListener;
22 | use OCP\User\Events\BeforeUserDeletedEvent;
23 |
24 | class UserDeleteHook implements IEventListener
25 | {
26 |
27 | /**
28 | * Handle user deletion
29 | *
30 | * @param BeforeUserDeletedEvent $event
31 | */
32 | public function handle(Event $event): void
33 | {
34 | $userId = $event->getUser()->getUID();
35 |
36 | $app = new Application();
37 | $container = $app->getContainer();
38 |
39 | // order is important!
40 | $container->get(ItemServiceV2::class)->deleteUser($userId);
41 | $container->get(FeedServiceV2::class)->deleteUser($userId);
42 | $container->get(FolderServiceV2::class)->deleteUser($userId);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/javascript/unit/components/routes/Recent.spec.ts:
--------------------------------------------------------------------------------
1 | import type { Store } from 'vuex'
2 |
3 | import { shallowMount } from '@vue/test-utils'
4 | import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
5 | import Vuex from 'vuex'
6 | import ContentTemplate from '../../../../../src/components/ContentTemplate.vue'
7 | import Recent from '../../../../../src/components/routes/Recent.vue'
8 |
9 | describe('Recent.vue', () => {
10 | 'use strict'
11 | let wrapper: any
12 |
13 | const mockItems = [
14 | {
15 | id: 1,
16 | }, {
17 | id: 2,
18 | }, {
19 | id: 3,
20 | }, {
21 | id: 4,
22 | },
23 | ]
24 |
25 | const mockItemIds = [2, 3]
26 |
27 | let store: Store
28 | beforeAll(() => {
29 | store = new Vuex.Store({
30 | state: {
31 | items: {
32 | allItems: mockItems,
33 | recentItemIds: mockItemIds,
34 | },
35 | },
36 | getters: {
37 | allItems: () => mockItems,
38 | recentItemIds: () => mockItemIds,
39 | },
40 | })
41 |
42 | store.dispatch = vi.fn()
43 | store.commit = vi.fn()
44 |
45 | wrapper = shallowMount(Recent, {
46 | global: {
47 | plugins: [store],
48 | },
49 | })
50 | })
51 |
52 | beforeEach(() => {
53 | vi.clearAllMocks()
54 | })
55 |
56 | it('should get recent items from state', () => {
57 | expect((wrapper.findComponent(ContentTemplate)).props().items.length).toEqual(2)
58 | })
59 | })
60 |
--------------------------------------------------------------------------------
/l10n/eo.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Unread articles" : "Nelegitaj artikoloj",
3 | "News" : "Novaĵo",
4 | "Download" : "Elŝuti",
5 | "Close" : "Fermi",
6 | "Web address" : "TTT-adreso",
7 | "No folder" : "Neniu dosierujo",
8 | "Folder name" : "Dosierujnomo",
9 | "Username" : "Uzantonomo",
10 | "Password" : "Pasvorto",
11 | "New folder" : "Nova dosierujo",
12 | "Folder exists already!" : "Dosierujo jam ekzistas!",
13 | "Credentials" : "Aŭtentigiloj",
14 | "Subscribe" : "Aboni",
15 | "Show all articles" : "Montri ĉiujn artikolojn",
16 | "Move" : "Movi",
17 | "Share" : "Kunhavigi",
18 | "Share with" : "Kunhavigi kun",
19 | "Mark read" : "Marki kiel legita",
20 | "All articles" : "Ĉiuj artikoloj",
21 | "Starred" : "Markita",
22 | "Settings" : "Agordo",
23 | "Documentation" : "Dokumentaro",
24 | "Keyboard shortcuts" : "Fulmoklavoj",
25 | "Rename" : "Alinomigi",
26 | "Delete" : "Forigi",
27 | "Newest first" : "Pli novaj unue",
28 | "Oldest first" : "Malpli novaj unue",
29 | "Mark unread" : "Marki kiel nelegita",
30 | "Close details" : "Fermi detalojn",
31 | "by" : "de",
32 | "Open website" : "Malfermi TTT-ejon",
33 | "Folder" : "Dosierujo",
34 | "Not available" : "Ne disponeble",
35 | "right" : "dekstro",
36 | "left" : "maldekstro",
37 | "Refresh" : "Refreŝigi"
38 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
39 | }
--------------------------------------------------------------------------------
/l10n/eo.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Unread articles" : "Nelegitaj artikoloj",
5 | "News" : "Novaĵo",
6 | "Download" : "Elŝuti",
7 | "Close" : "Fermi",
8 | "Web address" : "TTT-adreso",
9 | "No folder" : "Neniu dosierujo",
10 | "Folder name" : "Dosierujnomo",
11 | "Username" : "Uzantonomo",
12 | "Password" : "Pasvorto",
13 | "New folder" : "Nova dosierujo",
14 | "Folder exists already!" : "Dosierujo jam ekzistas!",
15 | "Credentials" : "Aŭtentigiloj",
16 | "Subscribe" : "Aboni",
17 | "Show all articles" : "Montri ĉiujn artikolojn",
18 | "Move" : "Movi",
19 | "Share" : "Kunhavigi",
20 | "Share with" : "Kunhavigi kun",
21 | "Mark read" : "Marki kiel legita",
22 | "All articles" : "Ĉiuj artikoloj",
23 | "Starred" : "Markita",
24 | "Settings" : "Agordo",
25 | "Documentation" : "Dokumentaro",
26 | "Keyboard shortcuts" : "Fulmoklavoj",
27 | "Rename" : "Alinomigi",
28 | "Delete" : "Forigi",
29 | "Newest first" : "Pli novaj unue",
30 | "Oldest first" : "Malpli novaj unue",
31 | "Mark unread" : "Marki kiel nelegita",
32 | "Close details" : "Fermi detalojn",
33 | "by" : "de",
34 | "Open website" : "Malfermi TTT-ejon",
35 | "Folder" : "Dosierujo",
36 | "Not available" : "Ne disponeble",
37 | "right" : "dekstro",
38 | "left" : "maldekstro",
39 | "Refresh" : "Refreŝigi"
40 | },
41 | "nplurals=2; plural=(n != 1);");
42 |
--------------------------------------------------------------------------------
/lib/Command/Config/OpmlExport.php:
--------------------------------------------------------------------------------
1 | opmlService = $opmlService;
23 | }
24 |
25 | /**
26 | * Configure command
27 | *
28 | * @return void
29 | */
30 | protected function configure()
31 | {
32 | $this->setName('news:opml:export')
33 | ->setDescription('Print OPML file')
34 | ->addArgument('user-id', InputArgument::REQUIRED, 'User data to export');
35 | }
36 |
37 | /**
38 | * Execute command
39 | *
40 | * @param InputInterface $input
41 | * @param OutputInterface $output
42 | *
43 | * @return int
44 | */
45 | protected function execute(InputInterface $input, OutputInterface $output): int
46 | {
47 | $user = $input->getArgument('user-id');
48 |
49 | $output->write($this->opmlService->export($user));
50 | return 0;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/components/routes/Item.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
64 |
--------------------------------------------------------------------------------
/bin/tools/generate_authors.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
10 | * @copyright Bernhard Posselt 2016
11 | */
12 |
13 | $cmd = 'git --no-pager shortlog -nse HEAD';
14 | exec($cmd, $contributors);
15 |
16 | // extract data from git output into an array
17 | $regex = '/^\s*(?P\d+)\s*(?P.*\w)\s*<(?P[^\s]+)>$/';
18 | $contributors = array_map(function ($contributor) use ($regex) {
19 | $result = [];
20 | preg_match($regex, $contributor, $result);
21 | return $result;
22 | }, $contributors);
23 |
24 | // filter out bots
25 | $contributors = array_filter($contributors, function ($contributor) {
26 | if (empty($contributor['name']) || empty($contributor['email'])) {
27 | return false;
28 | }
29 | if (strpos($contributor['email'], 'bot') || strpos($contributor['name'], 'bot')) {
30 | return false;
31 | }
32 | return true;
33 | });
34 |
35 | // turn tuples into markdown
36 | $markdownLines = array_map(function ($contrib) {
37 | return '* [' . $contrib['name'] . '](mailto:' . $contrib['email'] . ')';
38 | }, $contributors);
39 |
40 | // add headline
41 | array_unshift($markdownLines, '# Authors');
42 |
43 | $markdown = implode("\n", $markdownLines);
44 | file_put_contents('AUTHORS.md', $markdown);
45 |
--------------------------------------------------------------------------------
/lib/Command/Updater/BeforeUpdate.php:
--------------------------------------------------------------------------------
1 |
9 | * @copyright Bernhard Posselt 2016
10 | */
11 |
12 | namespace OCA\News\Command\Updater;
13 |
14 | use OCA\News\Service\UpdaterService;
15 | use Symfony\Component\Console\Command\Command;
16 | use Symfony\Component\Console\Input\InputInterface;
17 | use Symfony\Component\Console\Output\OutputInterface;
18 |
19 | class BeforeUpdate extends Command
20 | {
21 | /**
22 | * @var UpdaterService Updater
23 | */
24 | private $updaterService;
25 |
26 | public function __construct(UpdaterService $updater)
27 | {
28 | parent::__construct();
29 | $this->updaterService = $updater;
30 | }
31 |
32 | /**
33 | * @return void
34 | */
35 | protected function configure()
36 | {
37 | $this->setName('news:updater:before-update')
38 | ->setDescription(
39 | 'This is used to clean up the database. It ' .
40 | 'deletes folders and feeds that are marked for ' .
41 | 'deletion'
42 | );
43 | }
44 |
45 | protected function execute(InputInterface $input, OutputInterface $output): int
46 | {
47 | $this->updaterService->beforeUpdate();
48 |
49 | return 0;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/docs/maintenance.md:
--------------------------------------------------------------------------------
1 | # Maintenance
2 |
3 | ## Release
4 | Releases are created automatically by GitHub Actions. A release is triggered via a GitHub Release.
5 | The GitHub Action will then start a build based on the git tag. A release can only be approved by [@Grotax](https://github.com/Grotax) or [@SMillerDev](https://github.com/SMillerDev). An admin of the Nextcloud organization can always overwrite these settings. The private key is stored as environmental secret in GitHub. The owner of the private key is [@Grotax](https://github.com/Grotax).
6 |
7 | ## Support
8 | ### PHP
9 | While the app should try to support all PHP versions that Nextcloud currently supports,
10 | the real focus when deciding to cut a PHP version should be on maintenance burden.
11 | Users are nice, but devs should be a priority in decisions that are likely to impact them significantly.
12 |
13 | ### Issues
14 | - Bug reports without test cases (feed URL and action is enough) can be closed with or without comment.
15 |
16 | - Feature requests without thoughtful commentary or pull request can be closed with or without comment,
17 | unless a developer is interested to support such a feature.
18 |
19 | - Issues without activity in the last 30 days can be closed with or without comment.
20 | If this is a bug you care about that isn't getting attention, fix it.
21 | If you're good enough to understand the bug, you're good enough to fix it.
22 |
23 |
24 | _Largely inspired by https://gist.github.com/ryanflorence/124070e7c4b3839d4573_
25 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | # Maintain dependencies for npm
9 | - package-ecosystem: "npm"
10 | directory: "/"
11 | schedule:
12 | interval: "daily"
13 | labels:
14 | - "dependencies"
15 | - "Skip-Changelog"
16 | versioning-strategy: increase
17 | groups:
18 | vue:
19 | patterns:
20 | - "vue"
21 | - "vuex"
22 | - "vue-router"
23 | - "@vue/*"
24 | - "vue-material-design-icons"
25 | vite:
26 | patterns:
27 | - "vite"
28 | - "vitest"
29 | - "@vitest/*"
30 | types:
31 | patterns:
32 | - "@types/*"
33 |
34 | # Maintain dependencies for Composer
35 | - package-ecosystem: "composer"
36 | directory: "/"
37 | schedule:
38 | interval: "daily"
39 | labels:
40 | - "dependencies"
41 | - "Skip-Changelog"
42 | versioning-strategy: increase
43 |
44 | # Maintain dependencies for GitHub Actions
45 | - package-ecosystem: "github-actions"
46 | directory: "/"
47 | schedule:
48 | interval: "daily"
49 | labels:
50 | - "dependencies"
51 | - "Skip-Changelog"
52 |
--------------------------------------------------------------------------------
/lib/Utility/Cache.php:
--------------------------------------------------------------------------------
1 |
9 | * @copyright 2023 Benjamin Brahmer
10 | */
11 | namespace OCA\News\Utility;
12 |
13 | use OCP\ITempManager;
14 | use OCP\IConfig;
15 |
16 | class Cache
17 | {
18 |
19 |
20 | /**
21 | * @var ITempManager
22 | */
23 | private $ITempManager;
24 |
25 | /**
26 | * @var IConfig
27 | */
28 | private $IConfig;
29 |
30 |
31 | public function __construct(
32 | ITempManager $ITempManager,
33 | IConfig $IConfig
34 | ) {
35 | $this->ITempManager = $ITempManager;
36 | $this->IConfig = $IConfig;
37 | }
38 |
39 | /**
40 | * Get a news app cache directory
41 | *
42 | * @param String $name for the sub-directory, is created if not existing
43 | *
44 | * @return String $directory The path for the cache
45 | */
46 | public function getCache(String $name): String
47 | {
48 | $baseDir = $this->ITempManager->getTempBaseDir();
49 | $instanceID = $this->IConfig->getSystemValue('instanceid');
50 |
51 | $directory = join(DIRECTORY_SEPARATOR, [$baseDir, "news-" . $instanceID, 'cache', $name]);
52 |
53 | if (!is_dir($directory)) {
54 | mkdir($directory, 0770, true);
55 | }
56 |
57 | return $directory;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/l10n/mn.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "News" : "Мэдээ",
3 | "Download" : "Татах",
4 | "Close" : "Хаах",
5 | "Web address" : "Вэб хаяг",
6 | "No folder" : "Хавтас алга",
7 | "Folder name" : "Хавтасны нэр",
8 | "Username" : "Хэрэглэгчийн нэр",
9 | "Password" : "Нууц үг",
10 | "New folder" : "Шинэ хавтас",
11 | "Credentials" : "Итгэмжлэл",
12 | "Subscribe" : "Захиалга",
13 | "Move" : "Зөөх",
14 | "Share" : "Түгээх",
15 | "Explore" : "Хайх",
16 | "Settings" : "Тохиргоо",
17 | "Rename" : "Нэрлэнэ үү",
18 | "Delete" : "Устгах",
19 | "Newest first" : "Шинийг урд нь",
20 | "Oldest first" : "Хуучныг урд нь",
21 | "Toggle star article" : "Өгүүллийг соль",
22 | "Play audio" : "Аудио тоглуулах",
23 | "Download audio" : "Аудиог татаж авах",
24 | "Download video" : "Видеог татаж авах",
25 | "Folder" : "Хавтас",
26 | "Keyboard shortcut" : "Keyboard товчлуур",
27 | "Jump to next article" : "Дараагийн өгүүллийг үзээрэй",
28 | "Jump to previous article" : "Өмнөх нийтлэл рүү очих",
29 | "right" : "баруун",
30 | "left" : "зүүн",
31 | "Refresh" : "Сэргээх",
32 | "Load next feed" : "Дараагийн тэжээлийг ачаална уу",
33 | "Load previous feed" : "Өмнөх тэжээлийг ачаална уу",
34 | "Load previous folder" : "Өмнөх хавтсыг ачаална уу",
35 | "Load next folder" : "Дараагийн хавтасыг ачаална уу",
36 | "How to set up the operating system cron" : "Үйлдлийн системийн cron-г хэрхэн тохируулах талаар"
37 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
38 | }
--------------------------------------------------------------------------------
/l10n/lb.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Unread articles" : "Ongeliesen Artikelen",
3 | "Download" : "Download",
4 | "Close" : "Zoumaachen",
5 | "Web address" : "Link",
6 | "No folder" : "Keen Dossier",
7 | "Folder name" : "Dossiers Numm:",
8 | "Username" : "Benotzernumm",
9 | "Password" : "Passwuert",
10 | "New folder" : "Neien Dossier",
11 | "Folder exists already!" : "Dësen Dossier gëtt et schonns!",
12 | "Credentials" : "Credentials",
13 | "Subscribe" : "Umellen",
14 | "Show all articles" : "All d'Artikelen uweisen",
15 | "Move" : "Verschieben",
16 | "Share" : "Deelen",
17 | "All articles" : "All Artikelen",
18 | "Explore" : "Erfuerschen",
19 | "Settings" : "Astellungen",
20 | "Documentation" : "Dokumentatioun",
21 | "Rename" : "Ëm-benennen",
22 | "Delete" : "Läschen",
23 | "by" : "vun",
24 | "Play audio" : "Toun ofspillen",
25 | "Download audio" : "Toun eroflueden",
26 | "Download video" : "Video eroflueden",
27 | "Open website" : "Oppe Websäit",
28 | "Folder" : "Dossier",
29 | "Jump to next article" : "Op den nächsten Artikel sprangen",
30 | "Jump to previous article" : "Op den Artikel vu virdru sprangen",
31 | "right" : "riets",
32 | "left" : "lénks",
33 | "Open article in new tab" : "Den Artikel an engem neien Tab opmaachen",
34 | "Refresh" : "Opfrëschen",
35 | "Load previous folder" : "Den Dossier vu virdrun lueden",
36 | "Load next folder" : "Den nächsten Dossier lueden"
37 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
38 | }
--------------------------------------------------------------------------------
/l10n/mn.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "News" : "Мэдээ",
5 | "Download" : "Татах",
6 | "Close" : "Хаах",
7 | "Web address" : "Вэб хаяг",
8 | "No folder" : "Хавтас алга",
9 | "Folder name" : "Хавтасны нэр",
10 | "Username" : "Хэрэглэгчийн нэр",
11 | "Password" : "Нууц үг",
12 | "New folder" : "Шинэ хавтас",
13 | "Credentials" : "Итгэмжлэл",
14 | "Subscribe" : "Захиалга",
15 | "Move" : "Зөөх",
16 | "Share" : "Түгээх",
17 | "Explore" : "Хайх",
18 | "Settings" : "Тохиргоо",
19 | "Rename" : "Нэрлэнэ үү",
20 | "Delete" : "Устгах",
21 | "Newest first" : "Шинийг урд нь",
22 | "Oldest first" : "Хуучныг урд нь",
23 | "Toggle star article" : "Өгүүллийг соль",
24 | "Play audio" : "Аудио тоглуулах",
25 | "Download audio" : "Аудиог татаж авах",
26 | "Download video" : "Видеог татаж авах",
27 | "Folder" : "Хавтас",
28 | "Keyboard shortcut" : "Keyboard товчлуур",
29 | "Jump to next article" : "Дараагийн өгүүллийг үзээрэй",
30 | "Jump to previous article" : "Өмнөх нийтлэл рүү очих",
31 | "right" : "баруун",
32 | "left" : "зүүн",
33 | "Refresh" : "Сэргээх",
34 | "Load next feed" : "Дараагийн тэжээлийг ачаална уу",
35 | "Load previous feed" : "Өмнөх тэжээлийг ачаална уу",
36 | "Load previous folder" : "Өмнөх хавтсыг ачаална уу",
37 | "Load next folder" : "Дараагийн хавтасыг ачаална уу",
38 | "How to set up the operating system cron" : "Үйлдлийн системийн cron-г хэрхэн тохируулах талаар"
39 | },
40 | "nplurals=2; plural=(n != 1);");
41 |
--------------------------------------------------------------------------------
/tests/Unit/Utility/TimeTest.php:
--------------------------------------------------------------------------------
1 |
4 | *
5 | * @license AGPL-3.0
6 | *
7 | * This code is free software: you can redistribute it and/or modify
8 | * it under the terms of the GNU Affero General Public License, version 3,
9 | * as published by the Free Software Foundation.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License, version 3,
17 | * along with this program. If not, see
18 | *
19 | */
20 |
21 | namespace OCA\News\Tests\Unit\Utility;
22 |
23 | use OCA\News\Utility\Time;
24 | use PHPUnit\Framework\TestCase;
25 |
26 | class TimeTest extends TestCase
27 | {
28 | /**
29 | * Test if the correct type is returned
30 | */
31 | public function testTime(): void
32 | {
33 | $cur = time();
34 |
35 | $time = new Time();
36 | $result = $time->getTime();
37 | $this->assertIsInt($result);
38 | $this->assertTrue($result >= $cur);
39 | }
40 |
41 |
42 | public function testMicroTime(): void
43 | {
44 | $cur = microtime(true) * 1000000;
45 |
46 | $time = new Time();
47 | $result = (float) $time->getMicroTime();
48 | $this->assertTrue($result >= $cur);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/l10n/lb.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Unread articles" : "Ongeliesen Artikelen",
5 | "Download" : "Download",
6 | "Close" : "Zoumaachen",
7 | "Web address" : "Link",
8 | "No folder" : "Keen Dossier",
9 | "Folder name" : "Dossiers Numm:",
10 | "Username" : "Benotzernumm",
11 | "Password" : "Passwuert",
12 | "New folder" : "Neien Dossier",
13 | "Folder exists already!" : "Dësen Dossier gëtt et schonns!",
14 | "Credentials" : "Credentials",
15 | "Subscribe" : "Umellen",
16 | "Show all articles" : "All d'Artikelen uweisen",
17 | "Move" : "Verschieben",
18 | "Share" : "Deelen",
19 | "All articles" : "All Artikelen",
20 | "Explore" : "Erfuerschen",
21 | "Settings" : "Astellungen",
22 | "Documentation" : "Dokumentatioun",
23 | "Rename" : "Ëm-benennen",
24 | "Delete" : "Läschen",
25 | "by" : "vun",
26 | "Play audio" : "Toun ofspillen",
27 | "Download audio" : "Toun eroflueden",
28 | "Download video" : "Video eroflueden",
29 | "Open website" : "Oppe Websäit",
30 | "Folder" : "Dossier",
31 | "Jump to next article" : "Op den nächsten Artikel sprangen",
32 | "Jump to previous article" : "Op den Artikel vu virdru sprangen",
33 | "right" : "riets",
34 | "left" : "lénks",
35 | "Open article in new tab" : "Den Artikel an engem neien Tab opmaachen",
36 | "Refresh" : "Opfrëschen",
37 | "Load previous folder" : "Den Dossier vu virdrun lueden",
38 | "Load next folder" : "Den nächsten Dossier lueden"
39 | },
40 | "nplurals=2; plural=(n != 1);");
41 |
--------------------------------------------------------------------------------
/l10n/vi.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Unread articles" : "Bài chưa đọc",
3 | "Download" : "Tải về",
4 | "Close" : "Đóng",
5 | "Web address" : "Địa chỉ Web",
6 | "No folder" : "Không có thư mục nào",
7 | "Folder name" : "Tên thư mục",
8 | "Username" : "Tên đăng nhập",
9 | "Password" : "Mật khẩu",
10 | "Feed exists already!" : "Nguồn đã tồn tại",
11 | "New folder" : "Tạo thư mục",
12 | "Folder exists already!" : "Thư mục đã tồn tại!",
13 | "Credentials" : "Giấy chứng nhận",
14 | "Subscribe" : "Theo dõi",
15 | "Last job ran {relativeTime}." : "Công việc cuối cùng đã chạy {relativeTime}.",
16 | "Move" : "Dịch chuyển",
17 | "Share" : "Chia sẻ",
18 | "Off" : "Tắt",
19 | "Mark read" : "Đánh dấu là đã đọc",
20 | "All articles" : "Mọi bài viết",
21 | "Starred" : "Starred",
22 | "Settings" : "Cài đặt",
23 | "Documentation" : "Tài liệu hướng dẫn",
24 | "Keyboard shortcuts" : "Phím tắt",
25 | "Rename" : "Sửa tên",
26 | "Delete" : "Xóa",
27 | "Rename Feed" : "Đổi tên nguồn dữ liệu",
28 | "Newest first" : "Mới trước",
29 | "Oldest first" : "Cũ trước",
30 | "Mark unread" : "Đánh dấu là chưa đọc",
31 | "by" : "bởi",
32 | "Open website" : "Mở trang web",
33 | "Title" : "Tên",
34 | "Folder" : "Thư mục",
35 | "Not available" : "Không khả dụng",
36 | "Keyboard shortcut" : "Phím tắt",
37 | "Action" : "Hành động",
38 | "right" : "phải",
39 | "left" : "trái",
40 | "Refresh" : "Làm mới",
41 | "Shift" : "Shift"
42 | },"pluralForm" :"nplurals=1; plural=0;"
43 | }
--------------------------------------------------------------------------------
/lib/Explore/RecommendedSites.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @copyright 2012 Alessandro Cosentino
11 | * @copyright 2012-2014 Bernhard Posselt
12 | */
13 |
14 | namespace OCA\News\Explore;
15 |
16 | use OCA\News\Explore\Exceptions\RecommendedSiteNotFoundException;
17 |
18 | class RecommendedSites
19 | {
20 |
21 | private $directory;
22 |
23 | /**
24 | * @param string $exploreDir the absolute path to where the recommendation
25 | * config files lie without a trailing slash
26 | */
27 | public function __construct(string $exploreDir)
28 | {
29 | $this->directory = $exploreDir;
30 | }
31 |
32 |
33 | /**
34 | * @param string $languageCode
35 | *
36 | * @return array
37 | *
38 | * @throws RecommendedSiteNotFoundException
39 | */
40 | public function forLanguage(string $languageCode): array
41 | {
42 | $file = $this->directory . '/feeds.' . $languageCode . '.json';
43 |
44 | if (file_exists($file)) {
45 | return json_decode(file_get_contents($file), true);
46 | } else {
47 | $msg = 'No recommended sites found for language code ' .
48 | $languageCode;
49 | throw new RecommendedSiteNotFoundException($msg);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us fix an issue you are having
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | # IMPORTANT
11 |
12 | Read and tick the following checkbox after you have created the issue or place an x inside the brackets ;)
13 |
14 | * [ ] I have read the [CONTRIBUTING.md](https://github.com/nextcloud/news/blob/master/CONTRIBUTING.md) and followed the provided tips
15 | * [ ] I accept that the issue will be closed without comment if I do not check [here](https://github.com/nextcloud/news/blob/master/CONTRIBUTING.md#hints-for-reporting-bugs)
16 | * [ ] I accept that the issue will be closed without comment if I do not fill out all items in the issue template.
17 |
18 |
19 | ## Explain the Problem
20 | What problem did you encounter?
21 |
22 | ## Steps to Reproduce
23 | Explain what you did to encounter the issue
24 | 1. Click
25 | 2. Drag
26 | 3. ??
27 | 4. Profit!
28 |
29 | ## System Information
30 | * News app version:
31 | * Nextcloud version:
32 | * Cron type: (system cron/python updater/...)
33 | * PHP version:
34 | * Database and version:
35 | * Browser and version:
36 | * OS and version:
37 |
38 |
39 | Contents of nextcloud/data/nextcloud.log
40 |
41 | ```json
42 | Paste output here
43 | ```
44 |
45 |
46 |
47 | Contents of Browser Error Console
48 | Read http://ggnome.com/wiki/Using_The_Browser_Error_Console if you are unsure what to put here
49 |
50 | ```
51 | Paste output here
52 | ```
53 |
54 |
--------------------------------------------------------------------------------
/l10n/vi.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Unread articles" : "Bài chưa đọc",
5 | "Download" : "Tải về",
6 | "Close" : "Đóng",
7 | "Web address" : "Địa chỉ Web",
8 | "No folder" : "Không có thư mục nào",
9 | "Folder name" : "Tên thư mục",
10 | "Username" : "Tên đăng nhập",
11 | "Password" : "Mật khẩu",
12 | "Feed exists already!" : "Nguồn đã tồn tại",
13 | "New folder" : "Tạo thư mục",
14 | "Folder exists already!" : "Thư mục đã tồn tại!",
15 | "Credentials" : "Giấy chứng nhận",
16 | "Subscribe" : "Theo dõi",
17 | "Last job ran {relativeTime}." : "Công việc cuối cùng đã chạy {relativeTime}.",
18 | "Move" : "Dịch chuyển",
19 | "Share" : "Chia sẻ",
20 | "Off" : "Tắt",
21 | "Mark read" : "Đánh dấu là đã đọc",
22 | "All articles" : "Mọi bài viết",
23 | "Starred" : "Starred",
24 | "Settings" : "Cài đặt",
25 | "Documentation" : "Tài liệu hướng dẫn",
26 | "Keyboard shortcuts" : "Phím tắt",
27 | "Rename" : "Sửa tên",
28 | "Delete" : "Xóa",
29 | "Rename Feed" : "Đổi tên nguồn dữ liệu",
30 | "Newest first" : "Mới trước",
31 | "Oldest first" : "Cũ trước",
32 | "Mark unread" : "Đánh dấu là chưa đọc",
33 | "by" : "bởi",
34 | "Open website" : "Mở trang web",
35 | "Title" : "Tên",
36 | "Folder" : "Thư mục",
37 | "Not available" : "Không khả dụng",
38 | "Keyboard shortcut" : "Phím tắt",
39 | "Action" : "Hành động",
40 | "right" : "phải",
41 | "left" : "trái",
42 | "Refresh" : "Làm mới",
43 | "Shift" : "Shift"
44 | },
45 | "nplurals=1; plural=0;");
46 |
--------------------------------------------------------------------------------
/lib/Plugin/Client/Plugin.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @copyright 2012 Alessandro Cosentino
11 | * @copyright 2012-2014 Bernhard Posselt
12 | */
13 |
14 | namespace OCA\News\Plugin\Client;
15 |
16 | /**
17 | * TODO: remove this? it seems old.. there is no global list of plugins and seems plugins register themselves?
18 | *
19 | * We actually really want to avoid this global list of plugins. A way would be
20 | * for News plugin apps to register themselves in a special database table
21 | * and the News app would just pull out the scripts that should be attached
22 | * but atm there is no really good way since there is no uninstall hook which
23 | * would remove the plugin from the apps so fk it :)
24 | */
25 | class Plugin
26 | {
27 |
28 | private static $scripts = [];
29 | private static $styles = [];
30 |
31 | public static function registerStyle($appName, $styleName): void
32 | {
33 | self::$styles[$appName] = $styleName;
34 | }
35 |
36 | public static function registerScript($appName, $scriptName): void
37 | {
38 | self::$scripts[$appName] = $scriptName;
39 | }
40 |
41 | public static function getStyles()
42 | {
43 | return self::$styles;
44 | }
45 |
46 | public static function getScripts()
47 | {
48 | return self::$scripts;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lib/Command/Config/FeedDelete.php:
--------------------------------------------------------------------------------
1 | feedService = $feedService;
23 | }
24 |
25 | /**
26 | * Configure command
27 | *
28 | * @return void
29 | */
30 | protected function configure()
31 | {
32 | $this->setName('news:feed:delete')
33 | ->setDescription('Remove a feed')
34 | ->addArgument('user-id', InputArgument::REQUIRED, 'User to remove the feed from')
35 | ->addArgument('feed-id', InputArgument::REQUIRED, 'Feed ID', null);
36 | }
37 |
38 | /**
39 | * Execute command
40 | *
41 | * @param InputInterface $input
42 | * @param OutputInterface $output
43 | *
44 | * @return int
45 | */
46 | protected function execute(InputInterface $input, OutputInterface $output): int
47 | {
48 | $user = $input->getArgument('user-id');
49 | $id = (int) $input->getArgument('feed-id');
50 |
51 | $this->feedService->delete($user, $id);
52 |
53 | return 0;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/Migration/Version150302Date20210312231251.php:
--------------------------------------------------------------------------------
1 | hasTable('news_items')) {
36 | $table = $schema->getTable('news_items');
37 | $table->addColumn('categories_json', 'json', [
38 | 'notnull' => false
39 | ]);
40 | }
41 |
42 | return $schema;
43 | }
44 |
45 | /**
46 | * @param IOutput $output
47 | * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
48 | * @param array $options
49 | */
50 | public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/lib/Controller/FaviconController.php:
--------------------------------------------------------------------------------
1 | appData->getFileContent(Constants::LOGO_INFO_DIR, 'img_'.$feedUrlHash);
41 | if ($logo_hash) {
42 | $feed_logo = $this->appData->getFileContent(Constants::LOGO_IMAGE_DIR, $logo_hash);
43 | }
44 | if (!$feed_logo) {
45 | $feed_logo = file_get_contents(__DIR__ . '/../../img/rss.svg');
46 | }
47 | $finfo = new \finfo(FILEINFO_MIME_TYPE);
48 | $mime = $finfo->buffer($feed_logo);
49 | return new DataDownloadResponse($feed_logo, $feedUrlHash, $mime);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/docs/features/integration.md:
--------------------------------------------------------------------------------
1 | # Integrations
2 |
3 | ## Is There An Subscription URL To Easily Subscribe To Feeds?
4 |
5 | By appending `/index.php/apps/news?subscribe_to=SOME_RSS_URL` to your NextCloud base path URL, you can launch the News app with a pre-filled URL, e.g.:
6 |
7 | Ex.
8 |
9 | https://yourdomain.com/nextcloud/index.php/apps/news?subscribe_to=https://github.com/nextcloud/news/releases
10 |
11 | ### Known Working Integrations
12 |
13 | #### Chrome / Edge
14 |
15 | 1. Install [RSS Subscription Extension (by Google)](https://chrome.google.com/webstore/detail/rss-subscription-extensio/nlbjncdgjeocebhnmkbbbdekmmmcbfjd) extension
16 | 1. Open the extension's options menu
17 | 1. Click `Add..`
18 | 1. In the *Description* field, enter a description for the RSS reader entry. 'NextCloud News' is a reasonable name.
19 | 1. Enter `https:///index.php/apps/news?subscribe_to=%s` replacing <NEXTCLOUD_BASE_PATH> with the base URL path to your NextCloud instance.
20 | * Domain based example:
21 | * Domain+subpath based example:
22 |
23 | #### Firefox
24 |
25 | 1. Install Firefox Add-on Extension [Awesome RSS](https://addons.mozilla.org/en-US/firefox/addon/awesome-rss/)
26 | 1. Open the `Preferences` for the extension
27 | 1. In the 'Subscribe using' section, select the `NextCloud` radio button
28 | 1. In the field link field, enter the base NextCloud URL.
29 | * Domain based example:
30 | * Domain+subpath based example:
31 |
--------------------------------------------------------------------------------
/l10n/mk.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Unread articles" : "Непрочитани статии",
3 | "News" : "Новости",
4 | "Download" : "Преземи",
5 | "Close" : "Затвори",
6 | "Folder name" : "Име на папка",
7 | "Username" : "Корисничко име",
8 | "Password" : "Лозинка",
9 | "Feed exists already!" : "Каналот веќе постои!",
10 | "New folder" : "Нова папка",
11 | "Credentials" : "Акредитиви",
12 | "Subscribe" : "Претплата",
13 | "Last job ran {relativeTime}." : "Последната процедура се изврши пред {relativeTime}.",
14 | "Move" : "Премести",
15 | "Share" : "Сподели",
16 | "Default" : "Предефиниран",
17 | "Vertical" : "Вертикално",
18 | "Horizontal" : "Хоризонтално",
19 | "Mark read" : "Означи како прочитано",
20 | "All articles" : "Сите статии",
21 | "Starred" : "Со ѕвезда",
22 | "Explore" : "Истражувај",
23 | "Settings" : "Параметри",
24 | "Documentation" : "Документација",
25 | "Keyboard shortcuts" : "Кратенки преку тастатура",
26 | "Rename" : "Преименувај",
27 | "Delete" : "Избриши",
28 | "Newest first" : "Новите прво",
29 | "Oldest first" : "Старите прво",
30 | "Mark unread" : "Означи како непрочитано",
31 | "by" : "од",
32 | "Open website" : "Отвори вебсајт",
33 | "Title" : "Наслов",
34 | "Folder" : "Папка",
35 | "Not available" : "Недостапно",
36 | "Keyboard shortcut" : "Кратенка преку тастатура",
37 | "Action" : "Акција",
38 | "right" : "десно",
39 | "left" : "лево",
40 | "Refresh" : "Освежи",
41 | "Load next feed" : "Вчитај следен канал",
42 | "Shift" : "Shift"
43 | },"pluralForm" :"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;"
44 | }
--------------------------------------------------------------------------------
/lib/Migration/Version250200Date20241219085150.php:
--------------------------------------------------------------------------------
1 | getTable('news_feeds');
40 | if (!$table->hasColumn('next_update_time')) {
41 | $table->addColumn('next_update_time', 'bigint', [
42 | 'notnull' => false
43 | ]);
44 | }
45 |
46 | return $schema;
47 | }
48 |
49 | /**
50 | * @param IOutput $output
51 | * @param Closure(): ISchemaWrapper $schemaClosure
52 | * @param array $options
53 | */
54 | public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/Fetcher/FaviconDataAccess.php:
--------------------------------------------------------------------------------
1 |
9 | * @copyright 2024 Ben Vidulich
10 | */
11 |
12 | namespace OCA\News\Fetcher;
13 |
14 | use OCA\News\Vendor\Favicon\DataAccess;
15 |
16 | use OCA\News\Config\FetcherConfig;
17 |
18 | /**
19 | * Modified version of DataAccess with a configurable user agent header.
20 | */
21 | class FaviconDataAccess extends DataAccess
22 | {
23 | /**
24 | * @var FetcherConfig
25 | */
26 | private $fetcherConfig;
27 |
28 | public function __construct(
29 | FetcherConfig $fetcherConfig,
30 | ) {
31 | $this->fetcherConfig = $fetcherConfig;
32 | }
33 |
34 | public function retrieveUrl($url)
35 | {
36 | $this->setContext();
37 | return @file_get_contents($url);
38 | }
39 |
40 | public function retrieveHeader($url)
41 | {
42 | $this->setContext();
43 | $headers = @get_headers($url, 1);
44 | return is_array($headers) ? array_change_key_case($headers) : [];
45 | }
46 |
47 | private function setContext()
48 | {
49 | stream_context_set_default(
50 | [
51 | 'http' => [
52 | 'method' => 'GET',
53 | 'follow_location' => 0,
54 | 'max_redirects' => 1,
55 | 'timeout' => 10,
56 | 'header' => 'User-Agent: ' . $this->fetcherConfig->getUserAgent(),
57 | ]
58 | ]
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/javascript/unit/components/App.spec.ts:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils'
2 | import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
3 | import App from '../../../../src/App.vue'
4 | import { MUTATIONS } from '../../../../src/store/index.ts'
5 |
6 | describe('FeedItemDisplay.vue', () => {
7 | 'use strict'
8 | let wrapper: any
9 |
10 | const dispatchStub = vi.fn()
11 | const commitStub = vi.fn()
12 | beforeAll(() => {
13 | wrapper = shallowMount(App, {
14 | global: {
15 | mocks: {
16 | $store: {
17 | state: {
18 | items: {
19 | playingItem: undefined,
20 | },
21 | app: {
22 | error: undefined,
23 | },
24 | },
25 | dispatch: dispatchStub,
26 | commit: commitStub,
27 | },
28 | },
29 | stubs: {
30 | RouterView: true,
31 | },
32 | },
33 | })
34 | })
35 |
36 | beforeEach(() => {
37 | dispatchStub.mockReset()
38 | commitStub.mockReset()
39 | })
40 |
41 | it('should send SET_PLAYING_ITEM with undefined to stop playback', () => {
42 | wrapper.vm.stopPlaying()
43 |
44 | expect(commitStub).toBeCalledWith(MUTATIONS.SET_PLAYING_ITEM, undefined)
45 | })
46 |
47 | it('should stop all video elements in page when playing video', () => {
48 | const pauseStub = vi.fn()
49 | document.getElementsByTagName = vi.fn().mockReturnValue([{ pause: pauseStub }])
50 |
51 | wrapper.vm.stopVideo()
52 |
53 | expect(pauseStub).toBeCalled()
54 | })
55 |
56 | it('should remove app state error with commit and undefined', () => {
57 | wrapper.vm.removeError()
58 |
59 | expect(commitStub).toBeCalledWith(MUTATIONS.SET_ERROR, undefined)
60 | })
61 | })
62 |
--------------------------------------------------------------------------------
/l10n/mk.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Unread articles" : "Непрочитани статии",
5 | "News" : "Новости",
6 | "Download" : "Преземи",
7 | "Close" : "Затвори",
8 | "Folder name" : "Име на папка",
9 | "Username" : "Корисничко име",
10 | "Password" : "Лозинка",
11 | "Feed exists already!" : "Каналот веќе постои!",
12 | "New folder" : "Нова папка",
13 | "Credentials" : "Акредитиви",
14 | "Subscribe" : "Претплата",
15 | "Last job ran {relativeTime}." : "Последната процедура се изврши пред {relativeTime}.",
16 | "Move" : "Премести",
17 | "Share" : "Сподели",
18 | "Default" : "Предефиниран",
19 | "Vertical" : "Вертикално",
20 | "Horizontal" : "Хоризонтално",
21 | "Mark read" : "Означи како прочитано",
22 | "All articles" : "Сите статии",
23 | "Starred" : "Со ѕвезда",
24 | "Explore" : "Истражувај",
25 | "Settings" : "Параметри",
26 | "Documentation" : "Документација",
27 | "Keyboard shortcuts" : "Кратенки преку тастатура",
28 | "Rename" : "Преименувај",
29 | "Delete" : "Избриши",
30 | "Newest first" : "Новите прво",
31 | "Oldest first" : "Старите прво",
32 | "Mark unread" : "Означи како непрочитано",
33 | "by" : "од",
34 | "Open website" : "Отвори вебсајт",
35 | "Title" : "Наслов",
36 | "Folder" : "Папка",
37 | "Not available" : "Недостапно",
38 | "Keyboard shortcut" : "Кратенка преку тастатура",
39 | "Action" : "Акција",
40 | "right" : "десно",
41 | "left" : "лево",
42 | "Refresh" : "Освежи",
43 | "Load next feed" : "Вчитај следен канал",
44 | "Shift" : "Shift"
45 | },
46 | "nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;");
47 |
--------------------------------------------------------------------------------
/lib/Controller/FaviconApiController.php:
--------------------------------------------------------------------------------
1 | appData->getFileContent(Constants::LOGO_INFO_DIR, 'img_'.$feedUrlHash);
43 | if ($logo_hash) {
44 | $feed_logo = $this->appData->getFileContent(Constants::LOGO_IMAGE_DIR, $logo_hash);
45 | }
46 | if (!$feed_logo) {
47 | $feed_logo = file_get_contents(__DIR__ . '/../../img/rss.svg');
48 | }
49 | $finfo = new \finfo(FILEINFO_MIME_TYPE);
50 | $mime = $finfo->buffer($feed_logo);
51 | return new DataDownloadResponse($feed_logo, $feedUrlHash, $mime);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/routes/All.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
62 |
--------------------------------------------------------------------------------
/lib/Controller/JSONHttpErrorTrait.php:
--------------------------------------------------------------------------------
1 |
9 | * @copyright Bernhard Posselt 2014
10 | */
11 |
12 | namespace OCA\News\Controller;
13 |
14 | use \OCP\AppFramework\Http\JSONResponse;
15 |
16 | trait JSONHttpErrorTrait
17 | {
18 | /**
19 | * @param \Exception $exception The exception to report
20 | * @param int $code The http error code
21 | * @return JSONResponse
22 | */
23 | public function error(\Exception $exception, int $code)
24 | {
25 | return new JSONResponse(['message' => $exception->getMessage()], $code);
26 | }
27 |
28 | /**
29 | * @param \Exception $exception
30 | * @param int $code
31 | * @return \OCP\AppFramework\Http\JSONResponse
32 | */
33 | public function errorResponseWithExceptionV2(\Exception $exception, int $code): JSONResponse
34 | {
35 | return $this->errorResponseV2(
36 | $exception->getMessage(),
37 | $exception->getCode(),
38 | $code
39 | );
40 | }
41 |
42 | /**
43 | * @param string $message
44 | * @param int $code
45 | * @param int $httpStatusCode
46 | * @return \OCP\AppFramework\Http\JSONResponse
47 | */
48 | public function errorResponseV2(string $message, int $code, int $httpStatusCode): JSONResponse
49 | {
50 | return new JSONResponse([
51 | 'error' => [
52 | 'code' => $code,
53 | 'message' => $message,
54 | ]
55 | ], $httpStatusCode);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/Migration/Version150400Date20210318215425.php:
--------------------------------------------------------------------------------
1 | hasTable('news_items')) {
36 | $table = $schema->getTable('news_items');
37 | $table->addColumn('shared_by', 'string', [
38 | 'notnull' => false,
39 | 'length' => 64
40 | ]);
41 | }
42 |
43 | return $schema;
44 | }
45 |
46 | /**
47 | * @param IOutput $output
48 | * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
49 | * @param array $options
50 | */
51 | public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/docker/README.md:
--------------------------------------------------------------------------------
1 | # Nextcloud Development Environment
2 |
3 | ## Installation / Running
4 |
5 | ```bash
6 | make build
7 | docker compose up
8 | ```
9 |
10 | Afterward you should be able to open (admin/admin) to
11 | log in to your Nextcloud instance.
12 |
13 | Press Ctrl+C to stop the container.
14 |
15 | ## Check nextcloud.log
16 |
17 | For debugging, you can show the `nextcloud.log`:
18 |
19 | ```bash
20 | make show-log
21 | ```
22 |
23 | There also is a [logging web interface](http://localhost:8081/index.php/settings/admin/logging).
24 |
25 | ## Create shell in docker container
26 |
27 | To check if the container is still running:
28 | ```bash
29 | docker ps
30 | ```
31 | It should show something like this:
32 | ```bash
33 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
34 | a1b2c3d4e5f6 nextcloud-news-app "docker-entrypoint.…" 2 hours ago Up 2 hours 0.0.0.0:8081->80/tcp nextcloud-news-app
35 | ```
36 |
37 | To open a shell as www-data which you need to run occ commands run:
38 | ``` bash
39 | docker exec -u www-data -it nextcloud-news-app /bin/bash
40 | ```
41 |
42 | To open a shell as root run
43 | ``` bash
44 | docker exec -it nextcloud-news-app /bin/bash
45 | ```
46 |
47 | To exit press Ctrl+D
48 |
49 | ### Inside the shell
50 |
51 | Use sqlite3 to open the db
52 |
53 | ```bash
54 | sqlite3 data/mydb.db
55 | ```
56 |
57 | More on the sqlite3 cli: https://www.sqlite.org/cli.html
58 |
59 | ## Tip
60 |
61 | In case something is broken try to reset the container:
62 |
63 | ```bash
64 | docker compose build; docker compose down; docker volume rm nextcloud-news_nextcloud
65 | ```
66 |
--------------------------------------------------------------------------------
/lib/Settings/AdminSettings.php:
--------------------------------------------------------------------------------
1 | initialState->provideInitialState($setting, $this->config->getValueString(
26 | Application::NAME,
27 | $setting,
28 | (string) Application::DEFAULT_SETTINGS[$setting]
29 | ));
30 | }
31 |
32 | if ($this->service->isCronProperlyConfigured()) {
33 | $lastUpdate = $this->service->getUpdateTime();
34 | } else {
35 | $lastUpdate = 0;
36 | }
37 |
38 | $this->initialState->provideInitialState("lastCron", $lastUpdate);
39 |
40 | $lastLogoPurge = $this->config->getValueInt(
41 | Application::NAME,
42 | 'lastLogoPurge',
43 | 0
44 | );
45 | $this->initialState->provideInitialState("lastLogoPurge", $lastLogoPurge);
46 |
47 | return new TemplateResponse(Application::NAME, 'admin', []);
48 | }
49 |
50 | public function getSection()
51 | {
52 | return 'news';
53 | }
54 |
55 | public function getPriority()
56 | {
57 | return 40;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/lib/Cron/UpdaterJob.php:
--------------------------------------------------------------------------------
1 |
9 | * @copyright 2012-2014 Bernhard Posselt
10 | */
11 |
12 | namespace OCA\News\Cron;
13 |
14 | use OCP\BackgroundJob\TimedJob;
15 | use OCP\AppFramework\Utility\ITimeFactory;
16 |
17 | use OCA\News\AppInfo\Application;
18 | use OCA\News\Service\StatusService;
19 | use OCA\News\Service\UpdaterService;
20 | use OCP\IAppConfig;
21 |
22 | class UpdaterJob extends TimedJob
23 | {
24 |
25 | public function __construct(
26 | ITimeFactory $time,
27 | private IAppConfig $config,
28 | private StatusService $statusService,
29 | private UpdaterService $updaterService
30 | ) {
31 | parent::__construct($time);
32 |
33 | $interval = $this->config->getValueString(
34 | Application::NAME,
35 | 'updateInterval',
36 | Application::DEFAULT_SETTINGS['updateInterval']
37 | );
38 |
39 | $this->setInterval($interval);
40 | }
41 |
42 | /**
43 | * @return void
44 | */
45 | protected function run($argument): void
46 | {
47 | $uses_cron = $this->config->getValueBool(
48 | Application::NAME,
49 | 'useCronUpdates',
50 | Application::DEFAULT_SETTINGS['useCronUpdates']
51 | );
52 |
53 | if (!$uses_cron || !$this->statusService->isCronProperlyConfigured()) {
54 | return;
55 | }
56 |
57 | $this->updaterService->beforeUpdate();
58 | $this->updaterService->update();
59 | $this->updaterService->afterUpdate();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/javascript/unit/services/folder.service.spec.ts:
--------------------------------------------------------------------------------
1 | import axios from '@nextcloud/axios'
2 | import { beforeEach, describe, expect, it } from 'vitest'
3 | import { FolderService } from './../../../../src/dataservices/folder.service'
4 |
5 | describe('folder.service.ts', () => {
6 | 'use strict'
7 |
8 | beforeEach(() => {
9 | axios.get.mockReset()
10 | axios.post.mockReset()
11 | axios.delete.mockReset()
12 | })
13 |
14 | describe('fetchAllFolders', () => {
15 | it('should call GET to retrieve all folders', async () => {
16 | axios.get.mockResolvedValue({ data: { feeds: [] } })
17 |
18 | await FolderService.fetchAllFolders()
19 |
20 | expect(axios.get).toBeCalled()
21 | })
22 | })
23 |
24 | describe('createFolder', () => {
25 | it('should call POST with folderName param', async () => {
26 | await FolderService.createFolder({ name: 'abc' })
27 |
28 | expect(axios.post).toBeCalled()
29 | const args = axios.post.mock.calls[0]
30 |
31 | expect(args[1].folderName).toEqual('abc')
32 | })
33 | })
34 |
35 | describe('renameFolder', () => {
36 | it('should call POST with item id in URL and folderName param', async () => {
37 | await FolderService.renameFolder({ id: 123, name: 'abc' })
38 |
39 | expect(axios.post).toBeCalled()
40 | const args = axios.post.mock.calls[0]
41 |
42 | expect(args[0]).toContain('123/rename')
43 | expect(args[1].folderName).toEqual('abc')
44 | })
45 | })
46 |
47 | describe('deleteFolder', () => {
48 | it('should call POST with item id in URL and read param', async () => {
49 | await FolderService.deleteFolder({ id: 123 })
50 |
51 | expect(axios.delete).toBeCalled()
52 | const args = axios.delete.mock.calls[0]
53 |
54 | expect(args[0]).toContain('123')
55 | })
56 | })
57 | })
58 |
--------------------------------------------------------------------------------
/lib/Controller/ApiPayloadTrait.php:
--------------------------------------------------------------------------------
1 | toAPI()];
25 | }
26 |
27 | if (!is_array($data)) {
28 | return $return;
29 | }
30 |
31 | foreach ($data as $entity) {
32 | if ($entity instanceof IAPI) {
33 | $return[] = $entity->toAPI();
34 | }
35 | }
36 | return $return;
37 | }
38 |
39 | /**
40 | * Serialize an entity
41 | *
42 | * @param IAPI $data
43 | *
44 | * @return array
45 | */
46 | public function serializeEntityV2($data, bool $reduced = false): array
47 | {
48 | return $data->toAPI2($reduced);
49 | }
50 |
51 | /**
52 | * Serialize array of entities
53 | *
54 | * @param array $data
55 | *
56 | * @return array
57 | */
58 | public function serializeEntitiesV2($data, bool $reduced = false): array
59 | {
60 | $return = [];
61 | foreach ($data as $entity) {
62 | $return[] = $entity->toAPI2($reduced);
63 | }
64 | return $return;
65 | }
66 |
67 | public function responseV2($data, $code = Http::STATUS_OK)
68 | {
69 | return new JSONResponse($data, $code);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/Controller/Controller.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @author David Guillot
11 | * @copyright 2020 Sean Molenaar
12 | */
13 |
14 | namespace OCA\News\Controller;
15 |
16 | use OCA\News\AppInfo\Application;
17 | use OCA\News\Controller\Exceptions\NotLoggedInException;
18 | use OCP\AppFramework\Controller as BaseController;
19 | use \OCP\IUser;
20 | use \OCP\IRequest;
21 | use \OCP\IUserSession;
22 |
23 | /**
24 | * Class ApiController
25 | *
26 | * @package OCA\News\Controller
27 | */
28 | class Controller extends BaseController
29 | {
30 |
31 | /**
32 | * ApiController constructor.
33 | *
34 | * Stores the user session to be able to leverage the user in further methods
35 | *
36 | * @param IRequest $request The request
37 | * @param IUserSession|null $userSession The user session
38 | */
39 | public function __construct(IRequest $request, private ?IUserSession $userSession)
40 | {
41 | parent::__construct(Application::NAME, $request);
42 | }
43 |
44 | /**
45 | * @return IUser|null
46 | */
47 | protected function getUser(): ?IUser
48 | {
49 | if ($this->userSession === null) {
50 | throw new NotLoggedInException();
51 | }
52 |
53 | return $this->userSession->getUser();
54 | }
55 |
56 | /**
57 | * @return string
58 | */
59 | protected function getUserId()
60 | {
61 | return $this->getUser()->getUID();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/utils/itemFilter.ts:
--------------------------------------------------------------------------------
1 | import { FEED_ORDER } from '../enums/index.ts'
2 |
3 | /**
4 | * get sorting for the actual route including individual feed order
5 | *
6 | * @param store - The vuex store instance containing application state
7 | * @param fetchKey - A key used for the selected route
8 | * @return Sort order oldestFirst
9 | */
10 | export function getOldestFirst(store, fetchKey: string): boolean {
11 | const feedOrdering = store.state.feeds?.ordering?.[fetchKey]
12 | if (!fetchKey.startsWith('feed-') || feedOrdering === FEED_ORDER.DEFAULT) {
13 | return store.getters.oldestFirst
14 | }
15 | return feedOrdering === FEED_ORDER.OLDEST
16 | }
17 |
18 | /**
19 | * filter out items that are already loaded but not in view range
20 | *
21 | * @param store - The vuex store instance containing application state
22 | * @param items - An array of feed items to be filtered
23 | * @param fetchKey - A key used for the selected route
24 | * @return The filtered array of feed items.
25 | */
26 | export function outOfScopeFilter(store, items: FeedItem[], fetchKey: string): FeedItem[] {
27 | const lastItemLoaded = store.state.items.lastItemLoaded?.[fetchKey]
28 | const oldestFirst = getOldestFirst(store, fetchKey)
29 |
30 | if (!lastItemLoaded) {
31 | return items
32 | }
33 | return items.filter((item) => {
34 | return (oldestFirst ? lastItemLoaded >= item.id : lastItemLoaded <= item.id)
35 | })
36 | }
37 |
38 | /**
39 | * sort array of feed items
40 | *
41 | * @param items - An array of feed items to be sorted
42 | * @param oldestFirst - Direction of sorting
43 | * @return The sorted array of feed items
44 | */
45 | export function sortedFeedItems(items: feedItem[], oldestFirst: boolean): FeedItem[] {
46 | return [...items].sort((a, b) => {
47 | return oldestFirst ? a.id - b.id : b.id - a.id
48 | })
49 | }
50 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Nextcloud News App
2 | site_url: https://nextcloud.github.io/news/
3 | site_description: Documentation for the Nextcloud News App
4 | site_author: Nextcloud News Team
5 | repo_url: https://github.com/nextcloud/news
6 | edit_uri: blob/master/documentation/
7 |
8 | nav:
9 | - index.md
10 | - install.md
11 | - clients.md
12 | - user.md
13 | - admin.md
14 | - developer.md
15 | - troubleshooting.md
16 | - Features:
17 | - Integration: features/integration.md
18 | - Custom CSS: features/customCSS.md
19 | - Plugins: features/plugins.md
20 | - Themes: features/themes.md
21 | - REST API:
22 | - API v1.2: api/api-v1-2.md
23 | - API v1.3: api/api-v1-3.md
24 | - API v2: api/api-v2.md
25 | - maintenance.md
26 |
27 | theme:
28 | name: material
29 | logo: assets/logo.svg
30 | favicon: assets/favicon.png
31 | font: false
32 | palette:
33 | - media: "(prefers-color-scheme: light)"
34 | scheme: default
35 | accent: indigo
36 | primary: indigo
37 | toggle:
38 | icon: material/toggle-switch-off-outline
39 | name: Switch to dark mode
40 | - media: "(prefers-color-scheme: dark)"
41 | scheme: slate
42 | primary: indigo
43 | accent: indigo
44 | toggle:
45 | icon: material/toggle-switch
46 | name: Switch to light mode
47 |
48 | features:
49 | - navigation.indexes
50 | - navigation.tracking
51 | - navigation.instant
52 | - navigation.expand
53 | - navigation.sections
54 |
55 | markdown_extensions:
56 | - admonition
57 | - pymdownx.highlight:
58 | anchor_linenums: true
59 | line_spans: "code-line"
60 | pygments_lang_class: true
61 | - pymdownx.inlinehilite
62 | - pymdownx.snippets
63 | - pymdownx.superfences
64 |
65 | # extra:
66 | # version:
67 | # provider: mike
68 |
--------------------------------------------------------------------------------
/lib/Command/Config/FolderDelete.php:
--------------------------------------------------------------------------------
1 | folderService = $folderService;
24 | }
25 |
26 | /**
27 | * Configure command
28 | *
29 | * @return void
30 | */
31 | protected function configure()
32 | {
33 | $this->setName('news:folder:delete')
34 | ->setDescription('Remove a folder')
35 | ->addArgument('user-id', InputArgument::REQUIRED, 'User to remove the folder from')
36 | ->addArgument('folder-id', InputArgument::REQUIRED, 'Folder ID', null);
37 | }
38 |
39 | /**
40 | * Execute command
41 | *
42 | * @param InputInterface $input
43 | * @param OutputInterface $output
44 | *
45 | * @return int
46 | */
47 | protected function execute(InputInterface $input, OutputInterface $output): int
48 | {
49 | $user = $input->getArgument('user-id');
50 | $id = $input->getArgument('folder-id');
51 |
52 | if ($id === null) {
53 | throw new ServiceValidationException('Can not remove root folder!');
54 | }
55 |
56 | $this->folderService->delete($user, intval($id));
57 |
58 | return 0;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/command/folders.bats:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bats
2 |
3 | load "helpers/settings"
4 |
5 | TESTSUITE="Folders"
6 |
7 | teardown() {
8 | ID_LIST=($(./occ news:feed:list 'admin' | grep -Po '"id": \K([0-9]+)' | tr '\n' ' '))
9 | for ID in $ID_LIST; do
10 | ./occ news:feed:delete "$user" "$ID"
11 | done
12 | }
13 |
14 | @test "[$TESTSUITE] Create new" {
15 | run ./occ news:folder:add "$user" "Something-${BATS_SUITE_TEST_NUMBER}"
16 | [ "$status" -eq 0 ]
17 |
18 |
19 | if echo "$output" | grep 'new folder'; then
20 | ret_status=$?
21 | echo "Folder ID not returned"
22 | return $ret_status
23 | fi
24 | }
25 |
26 | @test "[$TESTSUITE] List all" {
27 | ./occ news:folder:add "$user" "Something-${BATS_SUITE_TEST_NUMBER}"
28 |
29 | run ./occ news:folder:list "$user"
30 | [ "$status" -eq 0 ]
31 |
32 | if echo "$output" | grep "Something-${BATS_SUITE_TEST_NUMBER}"; then
33 | ret_status=$?
34 | echo "Folder not found in list"
35 | return $ret_status
36 | fi
37 | }
38 |
39 | @test "[$TESTSUITE] Read all" {
40 | ./occ news:folder:add "$user" "Something-${BATS_SUITE_TEST_NUMBER}"
41 |
42 | ID=$(./occ news:folder:list 'admin' | grep "Something-${BATS_SUITE_TEST_NUMBER}" -1 | head -1 | grep -oE '[0-9]*')
43 |
44 | run ./occ news:folder:read "$user" "$ID" -v
45 | [ "$status" -eq 0 ]
46 |
47 | if ! echo "$output" | grep "items as read"; then
48 | ret_status=$?
49 | echo "Folder not read"
50 | return $ret_status
51 | fi
52 | }
53 |
54 | @test "[$TESTSUITE] Delete all" {
55 | ID=$(./occ news:folder:add "$user" "Something-${BATS_SUITE_TEST_NUMBER}" | grep -oE '[0-9]*')
56 |
57 | run ./occ news:folder:list "$user"
58 | [ "$status" -eq 0 ]
59 |
60 | echo "$output" | grep "Something-${BATS_SUITE_TEST_NUMBER}"
61 |
62 | run ./occ news:folder:delete "$user" "$ID"
63 | [ "$status" -eq 0 ]
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/routes/Starred.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
65 |
--------------------------------------------------------------------------------
/lib/Command/Config/OpmlImport.php:
--------------------------------------------------------------------------------
1 | opmlService = $opmlService;
23 | }
24 |
25 | /**
26 | * Configure command
27 | *
28 | * @return void
29 | */
30 | protected function configure()
31 | {
32 | $this->setName('news:opml:import')
33 | ->setDescription('Import OPML file')
34 | ->addArgument('user-id', InputArgument::REQUIRED, 'User to import data for')
35 | ->addArgument('file', InputArgument::REQUIRED, 'Data to import');
36 | }
37 |
38 | /**
39 | * Execute command
40 | *
41 | * @param InputInterface $input
42 | * @param OutputInterface $output
43 | *
44 | * @return int
45 | */
46 | protected function execute(InputInterface $input, OutputInterface $output): int
47 | {
48 | $user = $input->getArgument('user-id');
49 | $data = file_get_contents($input->getArgument('file'));
50 | if ($data === false) {
51 | $output->writeln('Failed to read data file!');
52 | return 2;
53 | }
54 |
55 | $success = $this->opmlService->import($user, $data);
56 | if ($success === false) {
57 | $output->write("Failed to import data");
58 | return 1;
59 | }
60 |
61 | return 0;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nextcloud News app
2 |   
3 |
4 | The News app is an RSS/Atom feed aggregator. It offers a [RESTful API](https://nextcloud.github.io/news/developer/#apis) for app developers. The source code is [available on GitHub](https://github.com/nextcloud/news)
5 |
6 | You can install it via the integrated app store browser in Nextcloud, [News in the appstore](https://apps.nextcloud.com/apps/news).
7 |
8 | ## Documentation
9 | The documentation can be found [here](https://nextcloud.github.io/news/), the source of the documentation is on [GitHub](https://github.com/nextcloud/news/blob/master/docs)
10 |
11 | ### Common Guides
12 |
13 | There are some small guides for dealing with common setup and issues.
14 |
15 | * [Troubleshooting Guide](docs/troubleshooting.md)
16 | * [Integration Guide](docs/features/integration.md)
17 |
18 |
19 | ## Bugs
20 | Please read the [appropriate section in the contributing notices](https://github.com/nextcloud/news/blob/master/CONTRIBUTING.md#issues)
21 |
22 | ## Updating Notices
23 | To receive notifications when a new News app version was released, simply add the following Atom feed in your currently installed News app:
24 |
25 | https://github.com/nextcloud/news/releases.atom
26 |
27 | ## Screenshots
28 | 
29 |
30 | ## Maintainers
31 |
32 | * [Benjamin Brahmer](https://github.com/Grotax)
33 | * [Sean Molenaar](https://github.com/SMillerDev)
34 |
35 | ### Special thanks to the Feed-IO library
36 | Feed-io takes care of fetching feeds and processing them.
37 | [alexdebril/feed-io](https://github.com/alexdebril/feed-io)
38 |
--------------------------------------------------------------------------------
/.github/workflows/api-php-tests.yml:
--------------------------------------------------------------------------------
1 | name: PHP Tests
2 | on:
3 | pull_request
4 |
5 | jobs:
6 | php:
7 | runs-on: ubuntu-latest
8 | continue-on-error: ${{ matrix.experimental }}
9 | name: "PHP: Nextcloud ${{ matrix.nextcloud }} - PHP ${{ matrix.php-versions }} - DB ${{ matrix.database }}"
10 | strategy:
11 | matrix:
12 | php-versions: ['8.3']
13 | nextcloud: ['stable31']
14 | database: ['sqlite']
15 | experimental: [false]
16 | include:
17 | - php-versions: 8.3
18 | nextcloud: stable31
19 | database: sqlite
20 | experimental: false
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v6.0.1
24 | with:
25 | fetch-depth: 2 # https://github.com/codecov/codecov-action/issues/190#issuecomment-790729633
26 |
27 | - name: Setup PHP
28 | uses: shivammathur/setup-php@v2
29 | with:
30 | php-version: ${{ matrix.php-versions }}
31 | extensions: pdo_sqlite,pdo_mysql,pdo_pgsql,gd,zip
32 | coverage: pcov
33 | env:
34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35 |
36 | ### Back to normal setup
37 | - name: Set up server non MySQL
38 | uses: SMillerDev/nextcloud-actions/setup-nextcloud@main
39 | with:
40 | cron: true
41 | version: ${{ matrix.nextcloud }}
42 | database-type: ${{ matrix.database }}
43 |
44 | - name: Prime app build
45 | run: make
46 |
47 | - name: Configure server with app
48 | uses: SMillerDev/nextcloud-actions/setup-nextcloud-app@main
49 | with:
50 | app: 'news'
51 | check-code: false
52 |
53 | - name: Prep PHP tests
54 | working-directory: ../server/apps/news
55 | run: make php-test-dependencies
56 |
57 | - name: Unittests
58 | working-directory: ../server/apps/news
59 | run: make unit-test
60 |
--------------------------------------------------------------------------------
/tests/javascript/unit/components/routes/Item.spec.ts:
--------------------------------------------------------------------------------
1 | import type { Store } from 'vuex'
2 |
3 | import { shallowMount } from '@vue/test-utils'
4 | import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
5 | import Vuex from 'vuex'
6 | import ContentTemplate from '../../../../../src/components/ContentTemplate.vue'
7 | import Item from '../../../../../src/components/routes/Item.vue'
8 |
9 | describe('Item.vue', () => {
10 | 'use strict'
11 | let wrapper: any
12 |
13 | const mockItems = [
14 | {
15 | id: 123,
16 | },
17 | ]
18 |
19 | let store: Store
20 | beforeAll(() => {
21 | store = new Vuex.Store({
22 | state: {
23 | items: {
24 | fetchingItems: {
25 | all: false,
26 | },
27 | lastItemLoaded: {
28 | all: 1,
29 | },
30 | allItems: mockItems,
31 | },
32 | feeds: {
33 | },
34 | app: {
35 | oldestFirst: false,
36 | },
37 | },
38 | actions: {
39 | },
40 | getters: {
41 | allItems: () => mockItems,
42 | oldestFirst: (state) => state.app.oldestFirst,
43 | },
44 | })
45 |
46 | store.dispatch = vi.fn()
47 | store.commit = vi.fn()
48 |
49 | wrapper = shallowMount(Item, {
50 | props: {
51 | itemId: '123',
52 | },
53 | global: {
54 | plugins: [store],
55 | },
56 | })
57 | })
58 |
59 | beforeEach(() => {
60 | vi.clearAllMocks()
61 | })
62 |
63 | it('should show item with id 123', () => {
64 | expect((wrapper.findComponent(ContentTemplate)).props().items[0].id).toEqual(123)
65 | })
66 |
67 | it('should dispatch FETCH_ITEMS action if not fetchingItems.all', () => {
68 | wrapper.vm.$store.state.items.fetchingItems.all = false
69 | wrapper.vm.fetchMore()
70 | expect(store.dispatch).toBeCalled()
71 | })
72 |
73 | it('should not dispatch FETCH_ITEMS action if fetchingItems.all', () => {
74 | wrapper.vm.$store.state.items.fetchingItems.all = true
75 | wrapper.vm.fetchMore()
76 | expect(store.dispatch).not.toBeCalled()
77 | })
78 | })
79 |
--------------------------------------------------------------------------------
/lib/Fetcher/IFeedFetcher.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @copyright 2012 Alessandro Cosentino
11 | * @copyright 2012-2014 Bernhard Posselt
12 | */
13 |
14 | namespace OCA\News\Fetcher;
15 |
16 | use OCA\News\Vendor\FeedIo\Reader\ReadErrorException;
17 | use OCA\News\Db\Feed;
18 | use OCA\News\Db\Item;
19 |
20 | interface IFeedFetcher
21 | {
22 |
23 | /**
24 | * Fetch feed content.
25 | *
26 | * @param string $url remote url of the feed
27 | * @param bool $fullTextEnabled If true use a scraper to download the full article
28 | * @param string|null $user if given, basic auth is set for this feed
29 | * @param string|null $password if given, basic auth is set for this feed. Ignored if user is empty
30 | * @param string|null $httpLastModified if given, will be used when sending a request to servers
31 | *
32 | * @return array an array containing the new feed and its items, first
33 | * element being the Feed and second element being an array of Items
34 | *
35 | * @throws ReadErrorException if the Feed-IO fetcher encounters a problem
36 | */
37 | public function fetch(
38 | string $url,
39 | bool $fullTextEnabled,
40 | ?string $user,
41 | ?string $password,
42 | ?string $httpLastModified
43 | ): array;
44 |
45 | /**
46 | * Can a fetcher handle a feed.
47 | *
48 | * @param string $url the url that should be fetched
49 | *
50 | * @return boolean if the fetcher can handle the url. This fetcher will be
51 | * used exclusively to fetch the feed and the items of the page
52 | */
53 | public function canHandle(string $url): bool;
54 | }
55 |
--------------------------------------------------------------------------------
/lib/Command/Config/FolderAdd.php:
--------------------------------------------------------------------------------
1 | folderService = $folderService;
24 | }
25 |
26 | /**
27 | * Configure command
28 | *
29 | * @return void
30 | */
31 | protected function configure()
32 | {
33 | $this->setName('news:folder:add')
34 | ->setDescription('Add a folder')
35 | ->addArgument('user-id', InputArgument::REQUIRED, 'User to add the folder for')
36 | ->addArgument('name', InputArgument::REQUIRED, 'Folder name', null)
37 | ->addOption('parent', null, InputOption::VALUE_OPTIONAL, 'Parent folder');
38 | }
39 |
40 | /**
41 | * Execute command
42 | *
43 | * @param InputInterface $input
44 | * @param OutputInterface $output
45 | *
46 | * @return int
47 | */
48 | protected function execute(InputInterface $input, OutputInterface $output): int
49 | {
50 | $user = $input->getArgument('user-id');
51 | $name = $input->getArgument('name');
52 | $parent = $input->getOption('parent');
53 |
54 | if ($parent !== null) {
55 | $parent = intval($parent);
56 | }
57 |
58 | $folder = $this->folderService->create($user, $name, $parent);
59 |
60 | $output->writeln('new folder: ' . $folder->getId());
61 |
62 | return 0;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/lib/Command/Config/FeedList.php:
--------------------------------------------------------------------------------
1 | feedService = $feedService;
27 | }
28 |
29 | /**
30 | * Configure command
31 | *
32 | * @return void
33 | */
34 | protected function configure()
35 | {
36 | $this->setName('news:feed:list')
37 | ->setDescription('List all feeds')
38 | ->addArgument('user-id', InputArgument::REQUIRED, 'User to list the feeds for')
39 | ->addOption('recursive', null, InputOption::VALUE_NONE, 'Fetch the feed recursively');
40 | }
41 |
42 | /**
43 | * Execute command
44 | *
45 | * @param InputInterface $input
46 | * @param OutputInterface $output
47 | *
48 | * @return int|void
49 | */
50 | protected function execute(InputInterface $input, OutputInterface $output)
51 | {
52 | $user = $input->getArgument('user-id');
53 | $recursive = $input->getOption('recursive');
54 |
55 | if ($recursive !== false) {
56 | $feeds = $this->feedService->findAllForUserRecursive($user);
57 | } else {
58 | $feeds = $this->feedService->findAllForUser($user);
59 | }
60 |
61 | $output->writeln(json_encode($this->serialize($feeds), JSON_PRETTY_PRINT));
62 |
63 | return 0;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/lib/Controller/ImportController.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @copyright 2012 Alessandro Cosentino
11 | * @copyright 2012-2014 Bernhard Posselt
12 | */
13 |
14 | namespace OCA\News\Controller;
15 |
16 | use OCA\News\Service\OpmlService;
17 | use OCA\News\Service\Exceptions\ServiceValidationException;
18 | use \OCP\IRequest;
19 | use OCP\IUserSession;
20 | use OCP\AppFramework\Http\Attribute\NoAdminRequired;
21 | use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
22 |
23 | /**
24 | * Class ExportController
25 | *
26 | * @package OCA\News\Controller
27 | */
28 | class ImportController extends Controller
29 | {
30 |
31 | public function __construct(
32 | IRequest $request,
33 | ?IUserSession $userSession,
34 | private OpmlService $opmlService
35 | ) {
36 | parent::__construct($request, $userSession);
37 | }
38 |
39 |
40 | #[NoCSRFRequired]
41 | #[NoAdminRequired]
42 | public function opml(): array
43 | {
44 | $data = '';
45 | if (isset($this->request->files['file'])) {
46 | $file = $this->request->files['file'];
47 | $data = file_get_contents($file['tmp_name']);
48 | } else {
49 | $data = $this->request->getContent();
50 | }
51 |
52 |
53 | $status = '';
54 | $message = '';
55 | try {
56 | $this->opmlService->import($this->getUserId(), $data);
57 | $status = "ok";
58 | } catch (ServiceValidationException $e) {
59 | $status = "error";
60 | $message = $e->getMessage();
61 | }
62 |
63 | $response = [
64 | 'status' => $status,
65 | 'message' => $message,
66 | ];
67 |
68 | return $response;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/.github/workflows/api-php-static-code-check.yml:
--------------------------------------------------------------------------------
1 | name: Static analysis
2 | on:
3 | pull_request
4 | jobs:
5 | static-phpstan-analysis:
6 | runs-on: ubuntu-latest
7 | continue-on-error: true
8 | strategy:
9 | matrix:
10 | php-versions: ['8.2', '8.3' ]
11 | nextcloud: [ 'stable31' ]
12 | database: [ 'sqlite' ]
13 | include:
14 | - php-versions: 8.3
15 | nextcloud: pre-release
16 | database: sqlite
17 | experimental: true
18 | - php-versions: 8.4
19 | nextcloud: pre-release
20 | database: sqlite
21 | experimental: true
22 | name: "phpstan: Nextcloud ${{ matrix.nextcloud }} with ${{ matrix.php-versions }}"
23 | steps:
24 | - name: Checkout
25 | uses: actions/checkout@v6.0.1
26 | - name: Set up php
27 | uses: shivammathur/setup-php@master
28 | with:
29 | php-version: ${{ matrix.php-versions }}
30 | extensions: pdo_sqlite,pdo_mysql,pdo_pgsql,gd,zip
31 | coverage: none
32 | env:
33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34 |
35 | - name: Set up server non MySQL
36 | uses: SMillerDev/nextcloud-actions/setup-nextcloud@main
37 | with:
38 | cron: true
39 | version: ${{ matrix.nextcloud }}
40 | database-type: ${{ matrix.database }}
41 |
42 | - name: Build app
43 | run: make composer
44 |
45 | - name: Configure server with app
46 | uses: SMillerDev/nextcloud-actions/setup-nextcloud-app@main
47 | with:
48 | app: 'news'
49 | check-code: false
50 | force: true
51 |
52 | - name: Prep code scanning
53 | working-directory: ../server/apps/news
54 | run: make php-test-dependencies
55 |
56 | - name: PHPCS
57 | working-directory: ../server/apps/news
58 | run: make phpcs
59 |
60 | - name: PHPStan
61 | working-directory: ../server/apps/news
62 | run: make phpstan
63 |
--------------------------------------------------------------------------------
/l10n/ia.json:
--------------------------------------------------------------------------------
1 | { "translations": {
2 | "Unread articles" : "Articulos non legite",
3 | "Download" : "Discargar",
4 | "Close" : "Clauder",
5 | "Web address" : "Adresse Web",
6 | "No folder" : "Nulle dossier",
7 | "Folder name" : "Nomine de dossier",
8 | "Username" : "Nomine de usator",
9 | "Password" : "Contrasigno",
10 | "Feed exists already!" : "Syndication ja existe!",
11 | "New folder" : "Nove dossier",
12 | "Folder exists already!" : "Dossier ja existe!",
13 | "Credentials" : "Datos de authentication",
14 | "Subscribe" : "Subscribe",
15 | "Show all articles" : "Monstrar tote articulos",
16 | "Move" : "Mover",
17 | "Share" : "Compartir",
18 | "All articles" : "Tote articulos",
19 | "Starred" : "Favoritate",
20 | "Explore" : "Explorar",
21 | "Settings" : "Configurationes",
22 | "Documentation" : "Documentation",
23 | "Keyboard shortcuts" : "Combinationes de claves",
24 | "Disable mark read through scrolling" : "Disactivar le marcation como legite per rolamento",
25 | "Reverse ordering (oldest on top)" : "Ordination reverse (le plus ancian in le topo)",
26 | "Rename" : "Renominar",
27 | "Delete" : "Deler",
28 | "Toggle star article" : "Alternar stato de favorito del articulo",
29 | "by" : "per",
30 | "Play audio" : "Reproducer audio",
31 | "Download audio" : "Discargar audio",
32 | "Download video" : "Discargar video",
33 | "Open website" : "Aperir sito web",
34 | "Keep article unread" : "Mantener articulo como non legite",
35 | "Folder" : "Dossier",
36 | "Keyboard shortcut" : "Combination de claves",
37 | "Jump to next article" : "Saltar al articulo sequente",
38 | "Jump to previous article" : "Saltar al articulo previe",
39 | "right" : "dextere",
40 | "left" : "sinistre",
41 | "Star article and jump to next one" : "Favoritar articulo e saltar al sequente",
42 | "Open article in new tab" : "Aperir articulo in nove scheda",
43 | "Refresh" : "Refrescar"
44 | },"pluralForm" :"nplurals=2; plural=(n != 1);"
45 | }
--------------------------------------------------------------------------------
/lib/Command/Config/FolderList.php:
--------------------------------------------------------------------------------
1 | folderService = $folderService;
27 | }
28 |
29 | /**
30 | * Configure command
31 | *
32 | * @return void
33 | */
34 | protected function configure()
35 | {
36 | $this->setName('news:folder:list')
37 | ->setDescription('List all folders')
38 | ->addArgument('user-id', InputArgument::REQUIRED, 'User to list the folders for')
39 | ->addOption('recursive', null, InputOption::VALUE_NONE, 'Fetch the folder recursively');
40 | }
41 |
42 | /**
43 | * Execute command
44 | *
45 | * @param InputInterface $input
46 | * @param OutputInterface $output
47 | *
48 | * @return int|void
49 | */
50 | protected function execute(InputInterface $input, OutputInterface $output)
51 | {
52 | $user = $input->getArgument('user-id');
53 | $recursive = $input->getOption('recursive');
54 |
55 | if ($recursive !== false) {
56 | $folders = $this->folderService->findAllForUserRecursive($user);
57 | } else {
58 | $folders = $this->folderService->findAllForUser($user);
59 | }
60 |
61 | $output->writeln(json_encode($this->serialize($folders), JSON_PRETTY_PRINT));
62 |
63 | return 0;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/l10n/ia.js:
--------------------------------------------------------------------------------
1 | OC.L10N.register(
2 | "news",
3 | {
4 | "Unread articles" : "Articulos non legite",
5 | "Download" : "Discargar",
6 | "Close" : "Clauder",
7 | "Web address" : "Adresse Web",
8 | "No folder" : "Nulle dossier",
9 | "Folder name" : "Nomine de dossier",
10 | "Username" : "Nomine de usator",
11 | "Password" : "Contrasigno",
12 | "Feed exists already!" : "Syndication ja existe!",
13 | "New folder" : "Nove dossier",
14 | "Folder exists already!" : "Dossier ja existe!",
15 | "Credentials" : "Datos de authentication",
16 | "Subscribe" : "Subscribe",
17 | "Show all articles" : "Monstrar tote articulos",
18 | "Move" : "Mover",
19 | "Share" : "Compartir",
20 | "All articles" : "Tote articulos",
21 | "Starred" : "Favoritate",
22 | "Explore" : "Explorar",
23 | "Settings" : "Configurationes",
24 | "Documentation" : "Documentation",
25 | "Keyboard shortcuts" : "Combinationes de claves",
26 | "Disable mark read through scrolling" : "Disactivar le marcation como legite per rolamento",
27 | "Reverse ordering (oldest on top)" : "Ordination reverse (le plus ancian in le topo)",
28 | "Rename" : "Renominar",
29 | "Delete" : "Deler",
30 | "Toggle star article" : "Alternar stato de favorito del articulo",
31 | "by" : "per",
32 | "Play audio" : "Reproducer audio",
33 | "Download audio" : "Discargar audio",
34 | "Download video" : "Discargar video",
35 | "Open website" : "Aperir sito web",
36 | "Keep article unread" : "Mantener articulo como non legite",
37 | "Folder" : "Dossier",
38 | "Keyboard shortcut" : "Combination de claves",
39 | "Jump to next article" : "Saltar al articulo sequente",
40 | "Jump to previous article" : "Saltar al articulo previe",
41 | "right" : "dextere",
42 | "left" : "sinistre",
43 | "Star article and jump to next one" : "Favoritar articulo e saltar al sequente",
44 | "Open article in new tab" : "Aperir articulo in nove scheda",
45 | "Refresh" : "Refrescar"
46 | },
47 | "nplurals=2; plural=(n != 1);");
48 |
--------------------------------------------------------------------------------
/img/favicon-touch.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/Service/UpdaterService.php:
--------------------------------------------------------------------------------
1 |
9 | * @author Bernhard Posselt
10 | * @copyright 2012 Alessandro Cosentino
11 | * @copyright 2012-2014 Bernhard Posselt
12 | */
13 |
14 |
15 | namespace OCA\News\Service;
16 |
17 | use OCP\BackgroundJob\IJobList;
18 | use OCA\News\Cron\UpdaterJob;
19 |
20 | class UpdaterService
21 | {
22 |
23 | /**
24 | * @var FolderServiceV2
25 | */
26 | private $folderService;
27 |
28 | /**
29 | * @var FeedServiceV2
30 | */
31 | private $feedService;
32 |
33 | /**
34 | * @var ItemServiceV2
35 | */
36 | private $itemService;
37 |
38 | /** @var IJobList */
39 | private $jobList;
40 |
41 | public function __construct(
42 | FolderServiceV2 $folderService,
43 | FeedServiceV2 $feedService,
44 | ItemServiceV2 $itemService,
45 | IJobList $jobList
46 | ) {
47 | $this->folderService = $folderService;
48 | $this->feedService = $feedService;
49 | $this->itemService = $itemService;
50 | $this->jobList = $jobList;
51 | }
52 |
53 |
54 | public function beforeUpdate(): void
55 | {
56 | $this->folderService->purgeDeleted(null, null);
57 | $this->feedService->purgeDeleted(null, null);
58 | $this->feedService->purgeFeedLogos();
59 | }
60 |
61 |
62 | public function update(): void
63 | {
64 | $this->feedService->fetchAll();
65 | }
66 |
67 |
68 | public function afterUpdate(): void
69 | {
70 | $this->itemService->purgeOverThreshold();
71 | }
72 |
73 | public function reset(): int
74 | {
75 | $myJobList = $this->jobList->getJobsIterator(UpdaterJob::class, 1, 0);
76 | $job = $myJobList->current();
77 |
78 | $this->jobList->resetBackgroundJob($job);
79 |
80 | return 0;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/img/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/scoper.inc.php:
--------------------------------------------------------------------------------
1 | path($name)
51 | ->files()
52 | ->exclude(["test", "tests", "composer", "bin"])
53 | ->notName("autoload.php")
54 | ->in("vendor/");
55 | }
56 |
57 | return [
58 | "prefix" => "OCA\\News\\Vendor",
59 | 'exclude-namespaces' => [ 'Psr\\Log\\' ],
60 | "finders" => $finderConfig,
61 | ];
62 |
--------------------------------------------------------------------------------
/img/app-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
56 |
--------------------------------------------------------------------------------