mirror of
https://github.com/chylex/Nextcloud-News.git
synced 2024-11-25 07:42:47 +01:00
Compare commits
3 Commits
3fdd21b9fd
...
912125bc75
Author | SHA1 | Date | |
---|---|---|---|
912125bc75 | |||
2cfd08be88 | |||
c865d03eb9 |
@ -5,16 +5,11 @@ trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{php,html,css}]
|
||||
[*.{js,php,html}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
|
||||
[*.{js,ts,vue}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
|
||||
[*.bats]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
51
.eslintrc.js
51
.eslintrc.js
@ -1,52 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: {
|
||||
ts: '@typescript-eslint/parser',
|
||||
},
|
||||
ecmaVersion: 2020,
|
||||
},
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:vue/base',
|
||||
'plugin:vue/essential',
|
||||
'@vue/standard',
|
||||
'@vue/typescript/recommended',
|
||||
'@nextcloud',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
],
|
||||
ignorePatterns: ['*.d.ts', 'l10n/*.js'],
|
||||
rules: {
|
||||
'no-console': 'warn',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
|
||||
// TODO: Trouble importing .ts files into .vue files for some reason?
|
||||
'import/extensions': 'off',
|
||||
'n/no-missing-import': 'off',
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: {
|
||||
extensions: ['.ts'],
|
||||
},
|
||||
},
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*spec.ts', 'tests/javascript/unit/setup.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/store/*.ts'],
|
||||
rules: {
|
||||
'function-paren-newline': ['error', 'multiline'],
|
||||
},
|
||||
},
|
||||
],
|
||||
]
|
||||
}
|
||||
|
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@ -7,7 +7,7 @@ version: 2
|
||||
updates:
|
||||
# Maintain dependencies for npm
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
directory: "/js"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
|
23
.github/workflows/api-integration-tests.yml
vendored
23
.github/workflows/api-integration-tests.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['8.0', '8.1']
|
||||
nextcloud: ['stable27']
|
||||
nextcloud: ['stable24', 'stable25', 'stable26']
|
||||
database: ['sqlite', 'pgsql', 'mysql']
|
||||
experimental: [false]
|
||||
include:
|
||||
@ -41,9 +41,17 @@ jobs:
|
||||
nextcloud: pre-release
|
||||
database: sqlite
|
||||
experimental: true
|
||||
- php-versions: 7.4
|
||||
nextcloud: stable24
|
||||
database: sqlite
|
||||
experimental: false
|
||||
- php-versions: 8.2
|
||||
nextcloud: stable26
|
||||
database: sqlite
|
||||
experimental: false
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@ -53,16 +61,14 @@ jobs:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: pdo_sqlite,pdo_mysql,pdo_pgsql,gd,zip
|
||||
coverage: none
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup BATS & httpie
|
||||
run: sudo apt-get install -y httpie && sudo npm install -g bats@1.11.0
|
||||
run: sudo apt-get install -y httpie && npm install -g bats@1.7.0
|
||||
|
||||
### MySQL specific setup
|
||||
- name: Setup mysql
|
||||
if: matrix.database == 'mysql'
|
||||
uses: getong/mariadb-action@v1.11
|
||||
uses: getong/mariadb-action@v1.1
|
||||
with:
|
||||
mariadb version: '10.5'
|
||||
host port: ${{ env.MYSQL_PORT }}
|
||||
@ -99,7 +105,7 @@ jobs:
|
||||
database-password: ${{ env.POSTGRES_PASSWORD }}
|
||||
|
||||
- name: Prime app build
|
||||
run: make composer
|
||||
run: make
|
||||
|
||||
- name: Configure server with app
|
||||
uses: SMillerDev/nextcloud-actions/setup-nextcloud-app@main
|
||||
@ -124,6 +130,9 @@ jobs:
|
||||
kill %1
|
||||
kill %2
|
||||
|
||||
- name: Setup problem matchers for PHP
|
||||
run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
|
||||
|
||||
- name: Functional tests maintenance
|
||||
working-directory: ../server
|
||||
run: |
|
||||
|
12
.github/workflows/api-php-static-code-check.yml
vendored
12
.github/workflows/api-php-static-code-check.yml
vendored
@ -8,25 +8,27 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['8.0', '8.1', '8.2' ]
|
||||
nextcloud: [ 'stable27' ]
|
||||
nextcloud: [ 'stable26' ]
|
||||
database: [ 'sqlite' ]
|
||||
include:
|
||||
- php-versions: 8.2
|
||||
nextcloud: pre-release
|
||||
database: sqlite
|
||||
experimental: true
|
||||
- php-versions: 7.4
|
||||
nextcloud: stable24
|
||||
database: sqlite
|
||||
experimental: false
|
||||
name: "phpstan: Nextcloud ${{ matrix.nextcloud }} with ${{ matrix.php-versions }}"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@master
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: pdo_sqlite,pdo_mysql,pdo_pgsql,gd,zip
|
||||
coverage: none
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up server non MySQL
|
||||
uses: SMillerDev/nextcloud-actions/setup-nextcloud@main
|
||||
@ -36,7 +38,7 @@ jobs:
|
||||
database-type: ${{ matrix.database }}
|
||||
|
||||
- name: Build app
|
||||
run: make composer
|
||||
run: make
|
||||
|
||||
- name: Configure server with app
|
||||
uses: SMillerDev/nextcloud-actions/setup-nextcloud-app@main
|
||||
|
13
.github/workflows/api-php-tests.yml
vendored
13
.github/workflows/api-php-tests.yml
vendored
@ -10,19 +10,19 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['8.1']
|
||||
nextcloud: ['stable27']
|
||||
nextcloud: ['stable26']
|
||||
database: ['sqlite']
|
||||
experimental: [false]
|
||||
codecoverage: [false]
|
||||
include:
|
||||
- php-versions: 8.2
|
||||
nextcloud: stable27
|
||||
- php-versions: 8.0
|
||||
nextcloud: stable25
|
||||
database: sqlite
|
||||
experimental: false
|
||||
codecoverage: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2 # https://github.com/codecov/codecov-action/issues/190#issuecomment-790729633
|
||||
|
||||
@ -32,8 +32,6 @@ jobs:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: pdo_sqlite,pdo_mysql,pdo_pgsql,gd,zip
|
||||
coverage: pcov
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
### Back to normal setup
|
||||
- name: Set up server non MySQL
|
||||
@ -52,6 +50,9 @@ jobs:
|
||||
app: 'news'
|
||||
check-code: false
|
||||
|
||||
- name: Setup problem matchers for PHPUnit
|
||||
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
||||
|
||||
- name: Prep PHP tests
|
||||
working-directory: ../server/apps/news
|
||||
run: make php-test-dependencies
|
||||
|
12
.github/workflows/build-release.yml
vendored
12
.github/workflows/build-release.yml
vendored
@ -15,21 +15,21 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['8.1']
|
||||
nextcloud: ['stable27']
|
||||
nextcloud: ['stable26']
|
||||
database: ['sqlite']
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@7fdd3ece872ec7ec4c098ae5ab7637d5e0a96067 #2.26.0
|
||||
uses: shivammathur/setup-php@afefcaf556d98dc7896cca380e181decb609ca44
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: pdo_sqlite,pdo_mysql,pdo_pgsql,gd,zip
|
||||
coverage: none
|
||||
|
||||
- name: Set up server non MySQL
|
||||
uses: SMillerDev/nextcloud-actions/setup-nextcloud@1e38cb369e2193642279c64ee140fc93ddd4fa77
|
||||
uses: SMillerDev/nextcloud-actions/setup-nextcloud@main
|
||||
with:
|
||||
cron: false
|
||||
version: ${{ matrix.nextcloud }}
|
||||
@ -39,7 +39,7 @@ jobs:
|
||||
run: make
|
||||
|
||||
- name: Configure server with app
|
||||
uses: SMillerDev/nextcloud-actions/setup-nextcloud-app@1e38cb369e2193642279c64ee140fc93ddd4fa77
|
||||
uses: SMillerDev/nextcloud-actions/setup-nextcloud-app@main
|
||||
with:
|
||||
app: ${{ env.APP_NAME }}
|
||||
check-code: false
|
||||
@ -52,7 +52,7 @@ jobs:
|
||||
app_public_crt: ${{ secrets.APP_PUBLIC_CRT }}
|
||||
|
||||
- name: Upload app tarball to release
|
||||
uses: svenstaro/upload-release-action@04733e069f2d7f7f0b4aebc4fbdbce8613b03ccd
|
||||
uses: svenstaro/upload-release-action@7319e4733ec7a184d739a6f412c40ffc339b69c7
|
||||
id: attach_to_release
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
2
.github/workflows/documentation.yml
vendored
2
.github/workflows/documentation.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Deploy docs
|
||||
uses: mhausenblas/mkdocs-deploy-gh-pages@master
|
||||
|
6
.github/workflows/frontend-nodejs-tests.yml
vendored
6
.github/workflows/frontend-nodejs-tests.yml
vendored
@ -4,18 +4,18 @@ on:
|
||||
|
||||
jobs:
|
||||
php:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
name: "Frontend: Nextcloud ${{ matrix.nextcloud }} - PHP ${{ matrix.php-versions }} - DB ${{ matrix.database }}"
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['8.1']
|
||||
nextcloud: ['stable27']
|
||||
nextcloud: ['stable24']
|
||||
database: ['sqlite']
|
||||
experimental: [false]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
|
6
.github/workflows/lint-eslint.yml
vendored
6
.github/workflows/lint-eslint.yml
vendored
@ -22,17 +22,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Read package.json node and npm engines version
|
||||
uses: skjnldsv/read-package-engines-version-actions@v3
|
||||
uses: skjnldsv/read-package-engines-version-actions@v2.1
|
||||
id: versions
|
||||
with:
|
||||
fallbackNode: '^16'
|
||||
fallbackNpm: '^6'
|
||||
|
||||
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ steps.versions.outputs.nodeVersion }}
|
||||
|
||||
|
52
.github/workflows/lint.yml
vendored
52
.github/workflows/lint.yml
vendored
@ -1,52 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
|
||||
name: eslint node${{ matrix.node-versions }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up node ${{ matrix.node-versions }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-versions }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
stylelint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
|
||||
name: stylelint node${{ matrix.node-versions }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up node ${{ matrix.node-versions }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-versions }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run stylelint
|
14
.github/workflows/post-merge-tasks.yml
vendored
14
.github/workflows/post-merge-tasks.yml
vendored
@ -7,20 +7,18 @@ jobs:
|
||||
php:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: false
|
||||
name: "Coverage: Nextcloud ${{ matrix.nextcloud }} PHP ${{ matrix.php-version }}"
|
||||
name: "Coverage: Nextcloud PHP ${{ matrix.php-versions }}"
|
||||
strategy:
|
||||
matrix:
|
||||
nextcloud: ['stable27']
|
||||
codecoverage: [true]
|
||||
php-version: ["8.1"]
|
||||
nextcloud: ['stable26']
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
php-version: '8.1'
|
||||
|
||||
### Back to normal setup
|
||||
- name: Set up server non MySQL
|
||||
@ -41,11 +39,7 @@ jobs:
|
||||
|
||||
- name: Prep PHP tests
|
||||
run: cd ../server/apps/news && make php-test-dependencies
|
||||
|
||||
- name: Unittests
|
||||
run: cd ../server/apps/news && make unit-test
|
||||
env:
|
||||
CODECOVERAGE: ${{ matrix.codecoverage }}
|
||||
|
||||
- name: Upload codecoverage
|
||||
run: cd ../server/apps/news && bash <(curl -s https://codecov.io/bash) -f build/php-unit.clover
|
||||
|
6
.github/workflows/updater-test.yml
vendored
6
.github/workflows/updater-test.yml
vendored
@ -17,12 +17,12 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['8.1']
|
||||
nextcloud: ['stable27']
|
||||
nextcloud: ['stable26']
|
||||
database: ['sqlite']
|
||||
experimental: [false]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@ -34,7 +34,7 @@ jobs:
|
||||
coverage: none
|
||||
|
||||
- name: Setup BATS & httpie
|
||||
run: sudo apt-get install -y httpie && sudo npm install -g bats@1.11.0
|
||||
run: sudo apt-get install -y httpie && npm install -g bats@1.7.0
|
||||
|
||||
- name: Set up server
|
||||
uses: SMillerDev/nextcloud-actions/setup-nextcloud@main
|
||||
|
14
.gitignore
vendored
14
.gitignore
vendored
@ -2,20 +2,15 @@
|
||||
composer.phar
|
||||
node_modules/
|
||||
vendor/
|
||||
js/build
|
||||
*.log
|
||||
/build/
|
||||
js/
|
||||
/js/coverage/
|
||||
js/*.xml
|
||||
.rvm
|
||||
*.clover
|
||||
.phpunit.result.cache
|
||||
site/
|
||||
coverage
|
||||
|
||||
#bats
|
||||
tests/api/helpers/settings-override.bash
|
||||
tests/test_helper/feeds/test.xml
|
||||
tests/test_helper/feeds/feed1.xml
|
||||
tests/test_helper/feeds/feed2.xml
|
||||
|
||||
#bats
|
||||
tests/api/helpers/settings-override.bash
|
||||
@ -60,9 +55,6 @@ RCS/*
|
||||
# netbeans
|
||||
nbproject
|
||||
|
||||
# vscode
|
||||
.vscode
|
||||
|
||||
# phpStorm
|
||||
.idea
|
||||
|
||||
|
@ -1,2 +0,0 @@
|
||||
vendor/
|
||||
coverage/
|
20
AUTHORS.md
20
AUTHORS.md
@ -3,14 +3,13 @@
|
||||
* [Benjamin Brahmer](mailto:info@b-brahmer.de)
|
||||
* [Alessandro Cosentino](mailto:cosenal@gmail.com)
|
||||
* [Marco Nassabain](mailto:marco.nassabain@hotmail.com)
|
||||
* [Devlin Junker](mailto:devlin.junker@gmail.com)
|
||||
* [Robin Appelman](mailto:icewind@owncloud.com)
|
||||
* [Sean Molenaar](mailto:sean@seanmolenaar.eu)
|
||||
* [Paul Tirk](mailto:paultirk@paultirk.com)
|
||||
* [Gregor Tätzner](mailto:gregor@freenet.de)
|
||||
* [Sean Molenaar](mailto:SMillerDev@users.noreply.github.com)
|
||||
* [anoy](mailto:anoymouserver+github@mailbox.org)
|
||||
* [Morris Jobke](mailto:hey@morrisjobke.de)
|
||||
* [Paul Tirk](mailto:paultirk@paultirk.com)
|
||||
* [Jimmy Huynh](mailto:jimmy.huynh@etu.unistra.fr)
|
||||
* [Aurélien](mailto:dav.aurelien@gmail.com)
|
||||
* [Jan-Christoph Borchardt](mailto:hey@jancborchardt.net)
|
||||
@ -22,7 +21,6 @@
|
||||
* [Bernhard Posselt](mailto:bep@foryouandyourcustomers.com)
|
||||
* [Thomas Müller](mailto:thomas.mueller@tmit.eu)
|
||||
* [Hoàng Đức Hiếu](mailto:hdhoang@zahe.me)
|
||||
* [IgorA100](mailto:igora100@gmail.com)
|
||||
* [Marco Nassabain](mailto:marco.nassabain@etu.unistra.fr)
|
||||
* [rakekniven](mailto:mark.ziegler@rakekniven.de)
|
||||
* [Daniel Opitz](mailto:git@copynpaste.de)
|
||||
@ -39,9 +37,6 @@
|
||||
* [Thomas Tanghus](mailto:thomas@tanghus.net)
|
||||
* [Volkan Gezer](mailto:volkangezer@gmail.com)
|
||||
* [Xéfir Destiny](mailto:xefir@crystalyx.net)
|
||||
* [devlinjunker](mailto:devlin.junker@gmail.com)
|
||||
* [hydrian](mailto:ben.tyger@tygerclan.net)
|
||||
* [mortee](mailto:mortee@kavemalna.hu)
|
||||
* [Daniel Opitz](mailto:danopz@users.noreply.github.com)
|
||||
* [Daniel Rheinbay](mailto:danielrheinbay@gmail.com)
|
||||
* [Lars Bensmann](mailto:lars@almosthappy.de)
|
||||
@ -59,7 +54,6 @@
|
||||
* [Qingping Hou](mailto:dave2008713@gmail.com)
|
||||
* [Roman](mailto:reverse@jamm.me)
|
||||
* [b_b](mailto:bruno@eliaz.fr)
|
||||
* [chylex](mailto:contact@chylex.com)
|
||||
* [heyarne](mailto:arne@schlueter.is)
|
||||
* [marco.nassabain@etu.unistra.fr](mailto:marco.nassabain@hotmail.com)
|
||||
* [Anderson Silva](mailto:UnderEu@users.noreply.github.com)
|
||||
@ -69,26 +63,23 @@
|
||||
* [Igor Bubelov](mailto:igor@bubelov.com)
|
||||
* [Jan C. Borchardt](mailto:hey@jancborchardt.net)
|
||||
* [Jimmy Huynh](mailto:linkatox@gmail.com)
|
||||
* [Joas Schilling](mailto:213943+nickvergessen@users.noreply.github.com)
|
||||
* [John Kristensen](mailto:john@jerrykan.com)
|
||||
* [Keunes](mailto:11229646+keunes@users.noreply.github.com)
|
||||
* [Konrad Graefe](mailto:konradgraefe@aol.com)
|
||||
* [Loki3000](mailto:github@labcms.ru)
|
||||
* [Maik Kulbe](mailto:info@linux-web-development.de)
|
||||
* [Manuel Sailer](mailto:github.com@manuel-sailer.de)
|
||||
* [Martin Vuille](mailto:jpmv27@yahoo.com)
|
||||
* [Michael Gapczynski](mailto:mtgap@owncloud.com)
|
||||
* [Nikita Chernyi](mailto:rakshazi@users.noreply.github.com)
|
||||
* [Peter Hedlund](mailto:peter@peterandlinda.com)
|
||||
* [Petra Mirelli](mailto:80395360+iNtEgraIR2021@users.noreply.github.com)
|
||||
* [Simon Spannagel](mailto:simonspa@kth.se)
|
||||
* [bbBowser](mailto:carspi@mail.de)
|
||||
* [benediktb](mailto:benedikt@blablub.de)
|
||||
* [chylex](mailto:contact@chylex.com)
|
||||
* [coderkun](mailto:olli@coderkun.de)
|
||||
* [davidak](mailto:git@davidak.de)
|
||||
* [hooger](mailto:horvathg.1988@gmail.com)
|
||||
* [lsmooth](mailto:ls@lsmooth.de)
|
||||
* [mortee](mailto:mortee.github@kavemalna.hu)
|
||||
* [s17t.net](mailto:mail+github@s17t.net)
|
||||
* [Accalia](mailto:Accalia@Elementia.me)
|
||||
* [Accalia Elementia](mailto:accalia@elementia.me)
|
||||
@ -113,7 +104,6 @@
|
||||
* [Chris Aumann](mailto:me@chr4.org)
|
||||
* [Chris Danser](mailto:skiingwiz@gmail.com)
|
||||
* [Chris Noxz](mailto:chris@noxz.tech)
|
||||
* [Christof Dorner](mailto:christof@chdorner.com)
|
||||
* [Christoph Wurst](mailto:ChristophWurst@users.noreply.github.com)
|
||||
* [Clemens](mailto:clemens@clemensknost.de)
|
||||
* [Colin W](mailto:cwmke@users.noreply.github.com)
|
||||
@ -137,18 +127,14 @@
|
||||
* [Freddo](mailto:f.falk@protonmail.com)
|
||||
* [Freddo3000](mailto:f.falk@protonmail.com)
|
||||
* [Greg](mailto:greg@toolstack.com)
|
||||
* [HK2FB](mailto:tom.schmidt@helsana.ch)
|
||||
* [Hagen](mailto:derhagen@users.noreply.github.com)
|
||||
* [Hanzei](mailto:Hanzei@users.noreply.github.com)
|
||||
* [Hendrik Leppelsack](mailto:hendrik@leppelsack.de)
|
||||
* [Jasper Knockaert](mailto:jasper@knockaert.nl)
|
||||
* [Jimmy Boucher](mailto:75694650+jboucher614@users.noreply.github.com)
|
||||
* [Josh](mailto:josh.t.richards@gmail.com)
|
||||
* [Kevin Decherf](mailto:kevin@kdecherf.com)
|
||||
* [Kuba Orlik](mailto:kontakt@kuba-orlik.name)
|
||||
* [Maceček Richard](mailto:46937538+macecekrichard@users.noreply.github.com)
|
||||
* [Marc Cousin](mailto:marc.cousin@people-doc.com)
|
||||
* [Marcus Nitzschke](mailto:mail@kendix.org)
|
||||
* [Martin Ferretti](mailto:ferrettimartin@protonmail.com)
|
||||
* [Matthias](mailto:matthias.baier@mabaart.de)
|
||||
* [Matthias Blümel](mailto:user@inanna.local)
|
||||
@ -156,7 +142,6 @@
|
||||
* [Michael Grosser](mailto:github@stp-ip.net)
|
||||
* [Michael Hamann](mailto:michael@content-space.de)
|
||||
* [Michael Holley](mailto:michaelwholley@gmail.com)
|
||||
* [Mynacol](mailto:Mynacol@users.noreply.github.com)
|
||||
* [NanoSector](mailto:rick@nanosector.nl)
|
||||
* [Nick Frey](mailto:nickfrey123@gmail.com)
|
||||
* [Oliver Herst](mailto:oliver.herst@googlemail.com)
|
||||
@ -203,7 +188,6 @@
|
||||
* [markusj](mailto:markusj@users.noreply.github.com)
|
||||
* [mnassabain](mailto:34754819+mnassabain@users.noreply.github.com)
|
||||
* [mormegil](mailto:mormegil@centrum.cz)
|
||||
* [nextcloud-command](mailto:nextcloud-command@users.noreply.github.com)
|
||||
* [nextcloud486153](mailto:78801830+nextcloud486153@users.noreply.github.com)
|
||||
* [nexus-uw](mailto:you@example.com)
|
||||
* [repat](mailto:repat@repat.de)
|
||||
|
110
CHANGELOG.md
110
CHANGELOG.md
@ -2,118 +2,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
The format is mostly based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), older entries don't fully match.
|
||||
|
||||
You can also check [on GitHub](https://github.com/nextcloud/news/releases), the release notes there are generated automatically and include every pull request.
|
||||
|
||||
# Unreleased
|
||||
## [25.x.x]
|
||||
## [21.x.x]
|
||||
### Changed
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
# Releases
|
||||
## [25.0.0-alpha8] - 2024-07-07
|
||||
### Changed
|
||||
- Add support for moving feeds to another folder from the sidebar feed menu (#2707)
|
||||
- Persist the filter state and show unread items by default (#2704)
|
||||
|
||||
### Fixed
|
||||
- Fix undefined item when using `j` and `k` keyboards shortcuts in an empty feed (#2689)
|
||||
|
||||
## [25.0.0-alpha7] - 2024-06-10
|
||||
### Changed
|
||||
- added alternative development environment (#2670)
|
||||
- Implement `j` and `k` keyboards shortcuts for navigating through feed items (#2671)
|
||||
- Implement `s`, `i` and `l` keyboards shortcuts for staring current feed item (#2677)
|
||||
- Implement `o` keyboards shortcut for opening the URL of current feed item (#2677)
|
||||
- Implement `u` keyboards shortcut for marking current feed item read/unread (#2677)
|
||||
- Implement highlighting of active feed item (#2677)
|
||||
|
||||
# Releases
|
||||
## [25.0.0-alpha6] - 2024-05-07
|
||||
### Changed
|
||||
- Improve layout of feed item row (#2569)
|
||||
|
||||
### Fixed
|
||||
- Reset content scroll position when feed item is changed (#2569)
|
||||
- Fix link to feed in article header (#2569)
|
||||
|
||||
## [25.0.0-alpha5] - 2024-04-01
|
||||
### Changed
|
||||
- make occ news:updater:job exit with code 2 if last update was too long ago (#2590)
|
||||
- Fix deprecated variable reference in ExportController.php (#2602)
|
||||
- Add support for Nextcloud 29 (#2611)
|
||||
|
||||
## [25.0.0-alpha4] - 2023-01-25
|
||||
### Changed
|
||||
- Add DB index for news_feeds.deleted_at (#2526)
|
||||
|
||||
### Fixed
|
||||
- PostgreSQL implement fix for marking over 65535 unread items as "read" (#2557)
|
||||
|
||||
## [25.0.0-alpha3] - 2023-12-24
|
||||
### Changed
|
||||
- Changed default page when starting app (#2515)
|
||||
- Downgrade feed-io to 5.3.1 (#2497)
|
||||
|
||||
### Fixed
|
||||
- Fix search support for Nextcloud 28 (#2432)
|
||||
|
||||
## [25.0.0-alpha2] - 2023-11-08
|
||||
### Changed
|
||||
- Add support for Nextcloud 28
|
||||
- Use Nextcloud vue components for item list and article view (#2401)
|
||||
- Fix aspect ratio of article images (#2401)
|
||||
|
||||
### Fixed
|
||||
- Adjust search urls to match changed Vue routes (#2408)
|
||||
|
||||
## [25.0.0-alpha1] - 2023-10-24
|
||||
### Changed
|
||||
- Major Rewrite of the Frontend with Vue JS (#748)
|
||||
For comments and suggestions for the new UI, please use this: https://github.com/nextcloud/news/discussions/2388
|
||||
- Set User Agent for curl in Scraper (#2380)
|
||||
- Drop support for Nextcloud 26, Supported 27
|
||||
|
||||
## [24.0.0] - 2023-09-26
|
||||
No major changes compared to 24.0.0-beta1.
|
||||
|
||||
## [24.0.0-beta1] - 2023-08-26
|
||||
### Changed
|
||||
- Drop support for Nextcloud 25, Supported: 26, 27 (#2316)
|
||||
- Add a new command for occ `./occ news:updater:job` allows to check and reset the update job (#2166)
|
||||
- Check for available http(s) compression options and use them (gzip, deflate, brotli) (#2328)
|
||||
- Change and unify [cache](https://nextcloud.github.io/news/install/#cache) to use the instance ID of Nextcloud (#2331)
|
||||
|
||||
## [23.0.0] - 2023-08-16
|
||||
No notable changes compared to 23.0.0-beta1
|
||||
|
||||
## [23.0.0-beta1] - 2023-08-09
|
||||
### Changed
|
||||
- Drop support for PHP 7.4 new min. version is php 8.0 (#2237)
|
||||
- Upgrade feed-io to v5.1.3 (#2238)
|
||||
### Fixed
|
||||
- Some feeds missing items (#2236)
|
||||
|
||||
## [22.0.0] - 2023-07-23
|
||||
### Changed
|
||||
- Support deflate and gzip compression for HTTP response bodies (#2269)
|
||||
- Broke apart old FAQ into different guides. Deprecated old FAQ (#2285)
|
||||
|
||||
## [22.0.0-beta2] - 2023-06-18
|
||||
### Changed
|
||||
- allowEvalScript set to true (#2262)
|
||||
|
||||
## [22.0.0-beta1] - 2023-05-18
|
||||
### Changed
|
||||
- Drop support for Nextcloud 24 (#2223)
|
||||
- Add support for Nextcloud 27 (#2223)
|
||||
|
||||
## [21.2.0] - 2023-05-06
|
||||
### Changed
|
||||
- Improve visibility of links in dark theme (#2215)
|
||||
|
||||
## [21.2.0-beta4] - 2023-04-16
|
||||
### Fixed
|
||||
- Fix audio player floating when scrolling in NC25+ (#2142)
|
||||
@ -134,7 +30,7 @@ No notable changes compared to 23.0.0-beta1
|
||||
- Use httpLastModified field for If-Modified-Since header when fetching feed updates (#2119)
|
||||
|
||||
## [21.1.0] - 2023-03-20
|
||||
No notable changes compared to 21.1.0-beta1
|
||||
No notable changes compared 21.1.0-beta1
|
||||
|
||||
## [21.1.0-beta1] - 2023-03-13
|
||||
### Changed
|
||||
@ -142,7 +38,7 @@ No notable changes compared to 21.1.0-beta1
|
||||
- (Nextcloud 26+) Add info card to the admin settings, showing last job execution (#2141)
|
||||
|
||||
## [21.0.0] - 2023-02-28
|
||||
No notable changes compared to 21.0.0-beta1
|
||||
No notable changes compared 21.0.0-beta1
|
||||
|
||||
## [21.0.0-beta1] - 2023-02-14
|
||||
### Changed
|
||||
|
@ -25,7 +25,7 @@ Read this when you want to:
|
||||
* Disable all browser add-ons to make sure that it's not a plugin's fault (adblockers, especially cosmetic filters)
|
||||
* Clear your PHP opcode cache if you use any by restarting your webserver.
|
||||
* [Check if they have already been reported](https://github.com/nextcloud/news/issues?state=open)
|
||||
* [Check if your problem is covered in the Troubleshooting section](https://nextcloud.github.io/news/troubleshooting/)
|
||||
* [Check if your problem is covered in the FAQ section]( https://nextcloud.github.io/news/faq)
|
||||
|
||||
### Debugging issues
|
||||
* Enable debug mode in your **config/config.php**:
|
||||
|
84
Makefile
84
Makefile
@ -52,7 +52,7 @@ cert_dir=$(HOME)/.nextcloud/certificates
|
||||
npm:=$(shell which npm 2> /dev/null)
|
||||
composer:=$(shell which composer 2> /dev/null)
|
||||
ifeq (,$(composer))
|
||||
composer:=php "$(build_tools_directory)/composer.phar"
|
||||
composer:=php $(build_tools_directory)/composer.phar
|
||||
endif
|
||||
|
||||
#Support xDebug 3.0+
|
||||
@ -74,26 +74,28 @@ build:
|
||||
composer:
|
||||
ifeq (, $(shell which composer 2> /dev/null))
|
||||
@echo "No composer command available, downloading a copy from the web"
|
||||
mkdir -p "$(build_tools_directory)"
|
||||
mkdir -p $(build_tools_directory)
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
mv composer.phar "$(build_tools_directory)"
|
||||
mv composer.phar $(build_tools_directory)
|
||||
endif
|
||||
$(composer) install --prefer-dist --no-dev
|
||||
|
||||
# Installs npm dependencies
|
||||
.PHONY: npm
|
||||
npm:
|
||||
ifneq (, $(npm))
|
||||
$(npm) ci
|
||||
$(npm) run build
|
||||
ifneq (, $(npm))
|
||||
cd js && $(npm) run build
|
||||
else
|
||||
@echo "npm command not available, please install nodejs first"
|
||||
@exit 1
|
||||
endif
|
||||
|
||||
# Removes the appstore build and compiled js files
|
||||
# Removes the appstore build
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf ./build ./js/*
|
||||
rm -rf ./build
|
||||
|
||||
# Reports PHP codestyle violations
|
||||
.PHONY: phpcs
|
||||
@ -124,9 +126,9 @@ dist:
|
||||
# Builds the source package
|
||||
.PHONY: source
|
||||
source:
|
||||
rm -rf "$(source_build_directory)" "$(source_artifact_directory)"
|
||||
mkdir -p "$(source_build_directory)" "$(source_artifact_directory)"
|
||||
rsync -rv . "$(source_build_directory)" \
|
||||
rm -rf $(source_build_directory) $(source_artifact_directory)
|
||||
mkdir -p $(source_build_directory) $(source_artifact_directory)
|
||||
rsync -rv . $(source_build_directory) \
|
||||
--exclude=/.git/ \
|
||||
--exclude=/.idea/ \
|
||||
--exclude=/build/ \
|
||||
@ -137,13 +139,13 @@ ifdef CAN_SIGN
|
||||
else
|
||||
@echo $(sign_skip_msg)
|
||||
endif
|
||||
tar -cvzf "$(source_package_name).tar.gz" -C "$(source_build_directory)/../" $(app_name)
|
||||
tar -cvzf $(source_package_name).tar.gz -C $(source_build_directory)/../ $(app_name)
|
||||
|
||||
# Builds the source package for the app store, ignores php and js tests
|
||||
.PHONY: appstore
|
||||
appstore:
|
||||
rm -rf "$(appstore_build_directory)" "$(appstore_sign_dir)" "$(appstore_artifact_directory)"
|
||||
install -d "$(appstore_sign_dir)/$(app_name)"
|
||||
rm -rf $(appstore_build_directory) $(appstore_sign_dir) $(appstore_artifact_directory)
|
||||
install -d $(appstore_sign_dir)/$(app_name)
|
||||
cp -r \
|
||||
"appinfo" \
|
||||
"css" \
|
||||
@ -152,49 +154,50 @@ appstore:
|
||||
"lib" \
|
||||
"templates" \
|
||||
"vendor" \
|
||||
"$(appstore_sign_dir)/$(app_name)"
|
||||
$(appstore_sign_dir)/$(app_name)
|
||||
|
||||
# remove composer binaries, those aren't needed
|
||||
rm -rf "$(appstore_sign_dir)/$(app_name)/vendor/bin"
|
||||
rm -rf $(appstore_sign_dir)/$(app_name)/vendor/bin
|
||||
# the App Store doesn't like .git
|
||||
rm -rf "$(appstore_sign_dir)/$(app_name)/vendor/arthurhoaro/favicon/.git"
|
||||
rm -rf $(appstore_sign_dir)/$(app_name)/vendor/arthurhoaro/favicon/.git
|
||||
# remove large test files
|
||||
rm -rf "$(appstore_sign_dir)/$(app_name)/vendor/fivefilters/readability.php/test"
|
||||
rm -rf $(appstore_sign_dir)/$(app_name)/vendor/fivefilters/readability.php/test
|
||||
|
||||
install "COPYING" "$(appstore_sign_dir)/$(app_name)"
|
||||
install "AUTHORS.md" "$(appstore_sign_dir)/$(app_name)"
|
||||
install "CHANGELOG.md" "$(appstore_sign_dir)/$(app_name)"
|
||||
install "COPYING" $(appstore_sign_dir)/$(app_name)
|
||||
install "AUTHORS.md" $(appstore_sign_dir)/$(app_name)
|
||||
install "CHANGELOG.md" $(appstore_sign_dir)/$(app_name)
|
||||
|
||||
#remove stray .htaccess files since they are filtered by nextcloud
|
||||
find "$(appstore_sign_dir)" -name .htaccess -exec rm {} \;
|
||||
find $(appstore_sign_dir) -name .htaccess -exec rm {} \;
|
||||
|
||||
# on macOS there is no option "--parents" for the "cp" command
|
||||
mkdir -p "$(appstore_sign_dir)/$(app_name)/js"
|
||||
cp js/* "$(appstore_sign_dir)/$(app_name)/js/"
|
||||
mkdir -p $(appstore_sign_dir)/$(app_name)/js/build $(appstore_sign_dir)/$(app_name)/js/admin
|
||||
cp js/build/app.min.js $(appstore_sign_dir)/$(app_name)/js/build
|
||||
cp js/build/news-admin-settings.js* $(appstore_sign_dir)/$(app_name)/js/build
|
||||
|
||||
# export the key and cert to a file
|
||||
@if [ ! -f "$(cert_dir)/$(app_name).key" ] || [ ! -f "$(cert_dir)/$(app_name).crt" ]; then \
|
||||
@if [ ! -f $(cert_dir)/$(app_name).key ] || [ ! -f $(cert_dir)/$(app_name).crt ]; then \
|
||||
echo "Key and cert do not exist"; \
|
||||
mkdir -p "$(cert_dir)"; \
|
||||
mkdir -p $(cert_dir); \
|
||||
php ./bin/tools/file_from_env.php "app_private_key" "$(cert_dir)/$(app_name).key"; \
|
||||
php ./bin/tools/file_from_env.php "app_public_crt" "$(cert_dir)/$(app_name).crt"; \
|
||||
fi
|
||||
|
||||
@if [ -f "$(cert_dir)/$(app_name).key" ]; then \
|
||||
@if [ -f $(cert_dir)/$(app_name).key ]; then \
|
||||
echo "Signing app files…"; \
|
||||
php ../../occ integrity:sign-app \
|
||||
--privateKey="$(cert_dir)/$(app_name).key"\
|
||||
--certificate="$(cert_dir)/$(app_name).crt"\
|
||||
--path="$(appstore_sign_dir)/$(app_name)"; \
|
||||
--privateKey=$(cert_dir)/$(app_name).key\
|
||||
--certificate=$(cert_dir)/$(app_name).crt\
|
||||
--path=$(appstore_sign_dir)/$(app_name); \
|
||||
echo "Signing app files ... done"; \
|
||||
fi
|
||||
mkdir -p "$(appstore_artifact_directory)"
|
||||
tar -czf "$(appstore_package_name).tar.gz" -C "$(appstore_sign_dir)" $(app_name)
|
||||
mkdir -p $(appstore_artifact_directory)
|
||||
tar -czf $(appstore_package_name).tar.gz -C $(appstore_sign_dir) $(app_name)
|
||||
|
||||
|
||||
.PHONY: js-test
|
||||
js-test:
|
||||
$(npm) run test
|
||||
cd js && $(npm) run test
|
||||
|
||||
.PHONY: php-test-dependencies
|
||||
php-test-dependencies:
|
||||
@ -202,7 +205,12 @@ php-test-dependencies:
|
||||
|
||||
.PHONY: unit-test
|
||||
unit-test:
|
||||
./vendor/phpunit/phpunit/phpunit -c phpunit.xml --coverage-clover build/php-unit.clover
|
||||
@if [ "$(CODECOVERAGE)" = "true" ]; then \
|
||||
./vendor/phpunit/phpunit/phpunit -c phpunit.xml --coverage-clover build/php-unit.clover; \
|
||||
else \
|
||||
./vendor/phpunit/phpunit/phpunit -c phpunit.xml --no-coverage; \
|
||||
fi
|
||||
|
||||
|
||||
# Command for running JS and PHP tests. Works for package.json files in the js/
|
||||
# and root directory. If phpunit is not installed systemwide, a copy is fetched
|
||||
@ -221,16 +229,8 @@ feed-test:
|
||||
|
||||
.PHONY: feed-server
|
||||
feed-server:
|
||||
php -S 127.0.0.1:8090 -t "$(CURDIR)/tests/test_helper/feeds"
|
||||
php -S 127.0.0.1:8090 -t $(CURDIR)/tests/test_helper/feeds
|
||||
|
||||
.PHONY: nextcloud-server
|
||||
nextcloud-server:
|
||||
php -S 127.0.0.1:8080 -t "$(CURDIR)/../../."
|
||||
|
||||
.PHONY: term
|
||||
term:
|
||||
zellij --layout term.kdl attach nextcloud-news -cf
|
||||
|
||||
.PHONY: term-kill
|
||||
term-kill:
|
||||
zellij delete-session nextcloud-news -f
|
||||
php -S 127.0.0.1:8080 -t $(CURDIR)/../../.
|
11
README.md
11
README.md
@ -1,4 +1,7 @@
|
||||
# Nextcloud News app
|
||||
|
||||
**We need help with the frontend, check the issue tracker if you are interested!**
|
||||
|
||||
![Release status](https://github.com/nextcloud/news/workflows/Build%20and%20publish%20app%20release/badge.svg) ![Integration Tests](https://github.com/nextcloud/news/workflows/Integration%20Tests/badge.svg) ![Frontend tests](https://github.com/nextcloud/news/workflows/Frontend%20tests/badge.svg) [![Code coverage](https://img.shields.io/codecov/c/github/nextcloud/news.svg?style=flat)](https://codecov.io/gh/nextcloud/news/)
|
||||
|
||||
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)
|
||||
@ -6,14 +9,6 @@ The News app is an RSS/Atom feed aggregator. It offers a [RESTful API](https://n
|
||||
## Documentation
|
||||
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)
|
||||
|
||||
### Common Guides
|
||||
|
||||
There are some small guides for dealing with common setup and issues.
|
||||
|
||||
* [Troubleshooting Guide](docs/troubleshooting.md)
|
||||
* [Integration Guide](docs/features/integration.md)
|
||||
|
||||
|
||||
## Bugs
|
||||
Please read the [appropriate section in the contributing notices](https://github.com/nextcloud/news/blob/master/CONTRIBUTING.md#issues)
|
||||
|
||||
|
@ -21,7 +21,7 @@ Create a [feature request](https://github.com/nextcloud/news/discussions/new)
|
||||
|
||||
Report a [feed issue](https://github.com/nextcloud/news/discussions/new)
|
||||
]]></description>
|
||||
<version>25.0.0-alpha8</version>
|
||||
<version>21.2.0-beta4</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Benjamin Brahmer</author>
|
||||
<author>Sean Molenaar</author>
|
||||
@ -43,7 +43,7 @@ Report a [feed issue](https://github.com/nextcloud/news/discussions/new)
|
||||
<screenshot small-thumbnail="https://raw.githubusercontent.com/nextcloud/news/master/screenshots/2-small.png">https://raw.githubusercontent.com/nextcloud/news/master/screenshots/2.png</screenshot>
|
||||
<screenshot small-thumbnail="https://raw.githubusercontent.com/nextcloud/news/master/screenshots/3-small.png">https://raw.githubusercontent.com/nextcloud/news/master/screenshots/3.png</screenshot>
|
||||
<dependencies>
|
||||
<php min-version="8.0" min-int-size="64"/>
|
||||
<php min-version="7.4" min-int-size="64"/>
|
||||
<database min-version="10">pgsql</database>
|
||||
<database>sqlite</database>
|
||||
<database min-version="8.0">mysql</database>
|
||||
@ -55,7 +55,7 @@ Report a [feed issue](https://github.com/nextcloud/news/discussions/new)
|
||||
<lib>json</lib>
|
||||
|
||||
<owncloud max-version="0" min-version="0"/>
|
||||
<nextcloud min-version="27" max-version="29"/>
|
||||
<nextcloud min-version="24" max-version="26"/>
|
||||
</dependencies>
|
||||
|
||||
<background-jobs>
|
||||
@ -75,7 +75,6 @@ Report a [feed issue](https://github.com/nextcloud/news/discussions/new)
|
||||
<command>OCA\News\Command\Updater\UpdateUser</command>
|
||||
<command>OCA\News\Command\Updater\BeforeUpdate</command>
|
||||
<command>OCA\News\Command\Updater\AfterUpdate</command>
|
||||
<command>OCA\News\Command\Updater\Job</command>
|
||||
<command>OCA\News\Command\Config\FolderList</command>
|
||||
<command>OCA\News\Command\Config\FolderAdd</command>
|
||||
<command>OCA\News\Command\Config\FolderDelete</command>
|
||||
|
@ -1,6 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
],
|
||||
presets: ['@babel/preset-env', '@babel/preset-typescript'],
|
||||
}
|
||||
// SPDX-FileCopyrightText: Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
const babelConfig = require('@nextcloud/babel-config')
|
||||
|
||||
module.exports = babelConfig
|
||||
|
@ -40,12 +40,12 @@
|
||||
"source": "https://github.com/nextcloud/news/"
|
||||
},
|
||||
"require": {
|
||||
"php": "~8.0",
|
||||
"ezyang/htmlpurifier": "^4.17.0",
|
||||
"php": "^7.4 || ~8.0",
|
||||
"ezyang/htmlpurifier": "^4.16.0",
|
||||
"pear/net_url2": "^2.2.2",
|
||||
"riimu/kit-pathjoin": "^1.2.0",
|
||||
"debril/feed-io": "v5.3.1",
|
||||
"arthurhoaro/favicon": "^2.0.0",
|
||||
"debril/feed-io": "^v4.9.12",
|
||||
"arthurhoaro/favicon": "^1.3.3",
|
||||
"fivefilters/readability.php": "^3.1",
|
||||
"ext-json": "*",
|
||||
"ext-simplexml": "*",
|
||||
@ -55,15 +55,15 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "9.6.*",
|
||||
"squizlabs/php_codesniffer": "^3.9.2",
|
||||
"phpstan/phpstan": "^1.10.60",
|
||||
"phpstan/phpstan-doctrine": "^1.4.3",
|
||||
"phpstan/phpstan-strict-rules": "^1.5.2",
|
||||
"phpstan/phpstan-phpunit": "^1.3.16",
|
||||
"phpstan/extension-installer": "^1.3.1",
|
||||
"squizlabs/php_codesniffer": "^3.7.2",
|
||||
"phpstan/phpstan": "^1.10.14",
|
||||
"phpstan/phpstan-doctrine": "^1.3.37",
|
||||
"phpstan/phpstan-strict-rules": "^1.5.1",
|
||||
"phpstan/phpstan-phpunit": "^1.3.11",
|
||||
"phpstan/extension-installer": "^1.3.0",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.1",
|
||||
"guzzlehttp/guzzle": "^7.3.0",
|
||||
"doctrine/dbal": "^3.8.0",
|
||||
"doctrine/dbal": "^3.6.2",
|
||||
"symfony/console": "^4.4.19",
|
||||
"psr/log": "^1.1.0"
|
||||
},
|
||||
|
479
composer.lock
generated
479
composer.lock
generated
File diff suppressed because it is too large
Load Diff
773
css/content.css
Normal file
773
css/content.css
Normal file
@ -0,0 +1,773 @@
|
||||
/**
|
||||
* Nextcloud - News
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Bernhard Posselt <dev@bernhard-posselt.com>
|
||||
* @copyright Bernhard Posselt 2014
|
||||
*/
|
||||
|
||||
#app-content {
|
||||
width: 100%;
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
#app-navigation:not(.hidden) + #app-content {
|
||||
width: calc(100% - 300px);
|
||||
}
|
||||
|
||||
#app-content #searchresults {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#app-content #text-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#app-content #text-content h1 {
|
||||
font-size: 18pt;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#app-content #text-content p {
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
#app-content .no-feeds-available {
|
||||
text-align: center;
|
||||
color: var(--color-text-lighter);
|
||||
font-size: 16pt;
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: table;
|
||||
}
|
||||
|
||||
#app-content .no-feeds-available p {
|
||||
vertical-align: middle;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
#articles {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Podcast settings
|
||||
*/
|
||||
#app-content .podcast {
|
||||
height: 50px;
|
||||
z-index: 10000;
|
||||
background-color: #111;
|
||||
display: flex;
|
||||
position: sticky;
|
||||
top: 50px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#app-content.nc-major-version-gte-25 .podcast {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
#app-content .podcast audio {
|
||||
display: block;
|
||||
width: calc(100% - 60px);
|
||||
border-radius: 0;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
#app-content .podcast button,
|
||||
#app-content .podcast a {
|
||||
display: block;
|
||||
float: left;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
background-color: transparent;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
#app-content .podcast-close {
|
||||
background-size: 25px;
|
||||
background-image: url('../img/close.svg');
|
||||
}
|
||||
|
||||
#app-content .podcast-download {
|
||||
transform: rotate(270deg);
|
||||
background-size: 20px;
|
||||
background-image: url('../img/arrow-white.svg');
|
||||
}
|
||||
|
||||
#app-content .podcast audio::-webkit-media-controls-enclosure {
|
||||
max-width: inherit;
|
||||
}
|
||||
|
||||
#app-content .podcast.fixed {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#app-content.nc-major-version-gte-25 .podcast {
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
@media only screen and (width < 1024px) {
|
||||
#app-content.nc-major-version-gte-25 .podcast {
|
||||
left: 35px;
|
||||
width: calc(100% - 35px);
|
||||
}
|
||||
}
|
||||
|
||||
#notification a {
|
||||
text-decoration: underline;
|
||||
color: blue;
|
||||
}
|
||||
|
||||
#notification li {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Content styles
|
||||
*/
|
||||
#app-content.loading-content {
|
||||
background: url('../img/loading.gif') no-repeat;
|
||||
background-color: var(--color-main-background);
|
||||
background-position: calc(50% - 16px) calc(50% - 16px);
|
||||
/* Overrides the snap.js animation making the loading icon to fly in app-content. */
|
||||
transition: none !important;
|
||||
-webkit-transition: none !important;
|
||||
-moz-webkit-transition: none !important;
|
||||
}
|
||||
|
||||
#app-content.loading-content #app-content-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#app-content:not(.explore) #articles:after {
|
||||
content: '';
|
||||
display: block;
|
||||
/* Subtract the height of the Nextcloud header. */
|
||||
height: calc(100vh - 50px);
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#app-content .autopaging: #articles:after {
|
||||
content: '';
|
||||
background-image: url('../img/loading.gif');
|
||||
background-position: calc(50% - 16px) calc(50% - 16px);
|
||||
/* Overrides the snap.js animation making the loading icon to fly in app-content. */
|
||||
transition: none !important;
|
||||
-webkit-transition: none !important;
|
||||
-moz-webkit-transition: none !important;
|
||||
}
|
||||
|
||||
#app-content .finished-auto-paging #articles:after {
|
||||
background: var(--color-main-background);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
.pull-to-refresh {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
background-image: -moz-linear-gradient(top, #eee 0%, #fefefe 100%);
|
||||
background-image: -webkit-linear-gradient(top, #eee 0%, #fefefe 100%);
|
||||
background-image: -ms-linear-gradient(top, #eee 0%, #fefefe 100%);
|
||||
background-image: linear-gradient(top, #eee 0%, #fefefe 100%);
|
||||
}
|
||||
|
||||
.pull-to-refresh.show-pull-to-refresh {
|
||||
transition: all 0.5s ease;
|
||||
padding-top: 10px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rules for a single feed item
|
||||
*/
|
||||
#app-content .only-in-expanded {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#app-content .compact .only-in-expanded {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#app-content .only-in-compact {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#app-content .compact .only-in-compact {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#app-content .item {
|
||||
cursor: default;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
#app-content .item.active {
|
||||
background-image: linear-gradient(to right, orange 0, orange 2px, transparent 2px);
|
||||
}
|
||||
|
||||
#app-content .compact li.item div.utils:hover {
|
||||
background-color: var(--color-background-hover);
|
||||
}
|
||||
|
||||
#app-content :not(.compact) .item {
|
||||
}
|
||||
|
||||
#app-content .item:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utils panel
|
||||
*/
|
||||
#app-content .utils {
|
||||
width: 100%;
|
||||
line-height: 52px;
|
||||
box-sizing: border-box;
|
||||
padding: 15px 50px 0 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#app-content .compact .utils {
|
||||
cursor: pointer;
|
||||
padding: 0 15px 0 0;
|
||||
-webkit-position: sticky;
|
||||
position: sticky;
|
||||
top: 50px;
|
||||
background-color: var(--color-main-background);
|
||||
min-height: 43px;
|
||||
}
|
||||
|
||||
#app-content.nc-major-version-gte-25 .compact .utils {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
/* Only apply opacity to the util bar, not the dropdown */
|
||||
#app-content .compact .utils ul {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
#app-content .utils ul {
|
||||
height: 43px;
|
||||
list-style-type: none;
|
||||
display: flex;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#app-content .compact .util-spacer,
|
||||
#app-content .compact .util,
|
||||
#app-content .compact .only-in-compact {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
#app-content .compact .title {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
#app-content .open .utils ul {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#app-content .utils ul > li {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#app-content .util-spacer {
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
#app-content .compact .util-spacer {
|
||||
width: 5px;
|
||||
}
|
||||
@media screen and (max-width: 1024px) {
|
||||
#app-content .compact .util-spacer {
|
||||
width: 0;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
#app-content .item:first-of-type .util-spacer {
|
||||
width: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
#app-content .utils li {
|
||||
line-height: 43px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#app-content .utils > ul > li:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
#app-content .utils > ul > li:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
#app-content .utils .util {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#app-content .utils .article-action-plugins {
|
||||
|
||||
}
|
||||
|
||||
#app-content .utils button,
|
||||
#app-content .utils .external {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 43px;
|
||||
width: 40px;
|
||||
border: 0;
|
||||
float: left;
|
||||
background-color: transparent;
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 25px;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
#app-content .utils .external,
|
||||
#app-content .utils .menu button,
|
||||
#app-content .utils .icon-toggle {
|
||||
display: inline-block;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
#app-content .utils .external:hover,
|
||||
#app-content .utils .menu button:hover,
|
||||
#app-content .utils .icon-toggle:hover,
|
||||
#app-content .utils .icon-toggle.keep-unread {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#app-content .utils .star {
|
||||
background-image: url('../img/inactive_star.svg');
|
||||
}
|
||||
|
||||
#app-content .utils .starred {
|
||||
background-image: url('../img/active_star.svg');
|
||||
}
|
||||
|
||||
#app-content .utils .star:hover {
|
||||
background-image: url('../img/hover_star.svg');
|
||||
}
|
||||
|
||||
#app-content .utils .share {
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
#app-content .utils .share:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#app-content .utils .more button {
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
#app-content .utils .more button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#app-content .utils .more {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#app-content .article-actions {
|
||||
display: none;
|
||||
filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75));
|
||||
-webkit-filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75));
|
||||
-moz-filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75));
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
border-radius: 3px;
|
||||
z-index: 1000;
|
||||
border: 1px solid transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#app-content .article-actions ul {
|
||||
display: flex;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#app-content .article-actions li {
|
||||
float: left;
|
||||
width: 40px !important;
|
||||
}
|
||||
|
||||
#app-content #articles:not(.compact) .article-actions {
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
#app-content #articles:not(.compact) .article-actions:after,
|
||||
#app-content #articles:not(.compact) .article-actions:before {
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
#app-content #articles.compact .article-actions {
|
||||
right: -10px;
|
||||
}
|
||||
|
||||
#app-content #articles.compact .article-actions:after,
|
||||
#app-content #articles.compact .article-actions:before {
|
||||
right: 9px;
|
||||
}
|
||||
|
||||
#app-content .article-actions:after,
|
||||
#app-content .article-actions:before {
|
||||
bottom: 100%;
|
||||
border: solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#app-content .article-actions:after {
|
||||
border-color: rgba(255, 255, 255, 0);
|
||||
border-bottom-color: var(--color-border);
|
||||
border-width: 5px;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
#app-content .article-actions:before {
|
||||
border-color: rgba(255, 255, 255, 0);
|
||||
border-bottom-color: var(--color-border);
|
||||
border-width: 9px;
|
||||
margin-left: -9px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Article title
|
||||
*/
|
||||
#app-content .utils .title {
|
||||
width: 100%;
|
||||
padding-left: 35px;
|
||||
background-position: 6px 10px;
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#app-content .compact .utils .title h1 {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
#app-content .utils .title h1 a {
|
||||
/*text-overflow: ellipsis;*/
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
color: var(--color-main-text);
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
padding-top: 8px;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
#app-content .open .title .intro {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#app-content .title .intro {
|
||||
color: var(--color-text-lighter);
|
||||
font-size: 10pt;
|
||||
font-weight: normal;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#app-content .open .utils .title h1 a {
|
||||
overflow: auto;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
#app-content .read .utils .title h1 a {
|
||||
font-weight: normal;
|
||||
color: var(--color-text-lighter);
|
||||
}
|
||||
|
||||
#app-content .utils .date {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Expanded content
|
||||
*/
|
||||
#app-content .heading {
|
||||
padding: 25px 0 10px 0;
|
||||
}
|
||||
|
||||
#app-content .heading h1 {
|
||||
font-size: 19px;
|
||||
max-width: 770px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#app-content .heading a {
|
||||
font-weight: bold;
|
||||
color: var(--color-main-text);
|
||||
text-decoration: none;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
#app-content .heading a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#app-content .heading .date {
|
||||
float: right;
|
||||
display: inline-block;
|
||||
font-size: 15px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
#app-content .read .heading a {
|
||||
font-weight: normal !important;
|
||||
color: var(--color-text-lighter);
|
||||
}
|
||||
|
||||
#app-content .feed-view .source {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#app-content .source img {
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
margin: 0 0 -2px 2px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actual content
|
||||
*/
|
||||
#app-content .article {
|
||||
min-height: 69px;
|
||||
padding: 0 50px 50px 50px;
|
||||
}
|
||||
|
||||
#app-content .compact .article {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#app-content .open .article {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp
|
||||
*/
|
||||
#app-content .subtitle {
|
||||
color: var(--color-text-lighter);
|
||||
font-size: 15px;
|
||||
max-width: 770px;
|
||||
padding: 25px 0;
|
||||
}
|
||||
|
||||
#app-content .subtitle a {
|
||||
color: var(--color-text-lighter);
|
||||
}
|
||||
|
||||
#app-content .subtitle a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#app-content .date {
|
||||
color: var(--color-text-lighter);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Author
|
||||
*/
|
||||
#app-content .author {
|
||||
color: var(--color-text-lighter);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
#app-content .author a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#app-content .author a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enclosure
|
||||
*/
|
||||
#app-content .enclosure {
|
||||
padding: 20px 0;
|
||||
max-width: 770px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#app-content .enclosure audio,
|
||||
#app-content .enclosure video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#app-content .enclosure video {
|
||||
background-color: #000;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#app-content .enclosure-error {
|
||||
line-height: 1.5;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Article body
|
||||
*/
|
||||
#app-content .body {
|
||||
max-width: 770px;
|
||||
font-size: 14px;
|
||||
clear: both;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
#app-content .body p {
|
||||
line-height: 1.5;
|
||||
margin: 7px 0 14px 0;
|
||||
}
|
||||
|
||||
#app-content .body img,
|
||||
#app-content .body table {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
overflow: auto;
|
||||
word-wrap: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
#app-content .body table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
#app-content .body table th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#app-content .body table th,
|
||||
#app-content .body table td {
|
||||
border: 1px solid var(--color-text-lighter);
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
#app-content .body iframe {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#app-content .body > div > img:first-child,
|
||||
#app-content .body > div > :first-child > img:first-child {
|
||||
padding: 0 14px 0 0;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#app-content .body h1,
|
||||
#app-content .body h2 {
|
||||
font-size: 17px;
|
||||
font-weight: bold;
|
||||
color: var(--color-main-text);
|
||||
margin: 21px 0 5px 0;
|
||||
}
|
||||
|
||||
#app-content .body h3 {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: var(--color-main-text);
|
||||
margin: 21px 0 0 0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#app-content .body h4,
|
||||
#app-content .body h5,
|
||||
#app-content .body h6 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#app-content .body > div {
|
||||
margin: 7px 0;
|
||||
}
|
||||
|
||||
#app-content .body a {
|
||||
color: #0c76ff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
body.theme--dark #app-content .body a, /* NC 24 */
|
||||
[data-theme-dark] #app-content .body a { /* NC 25 */
|
||||
color: #9cc7ff;
|
||||
}
|
||||
|
||||
#app-content .body ul {
|
||||
margin: 7px 0;
|
||||
padding-left: 14px;
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
#app-content .body ol {
|
||||
margin: 7px 0;
|
||||
padding-left: 21px;
|
||||
}
|
||||
|
||||
#app-content .body ul li {
|
||||
cursor: default;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
#app-content .body pre {
|
||||
padding: 7px 7px 7px 14px;
|
||||
background-color: var(--color-background-darker);
|
||||
border: 1px solid var(--color-border-dark);
|
||||
margin: 7px 0 14px 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#app-content .body pre::-webkit-scrollbar {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
#app-content .body pre::-webkit-scrollbar-thumb {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
#app-content .body pre::-webkit-scrollbar-track-piece {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
#app-content .body code,
|
||||
#app-content .body pre {
|
||||
font-family: monospace;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#app-content .body blockquote {
|
||||
border-left: 1px solid rgb(210, 210, 210);
|
||||
margin: 25px 0;
|
||||
padding: 0 0 0 15px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#app-content .body em {
|
||||
font-style: italic;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#app-content *[dir=rtl] {
|
||||
text-align: right;
|
||||
}
|
@ -18,8 +18,3 @@
|
||||
#app-content .custom-sueddeutsche-de .body img {
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
body.theme--dark #app-content .body a, /* NC 24 */
|
||||
[data-theme-dark] #app-content .body a { /* NC 25 */
|
||||
color: #9cc7ff;
|
||||
}
|
||||
|
@ -8,7 +8,6 @@
|
||||
* @copyright 2020, Jan C. Borchardt, https://jancborchardt.net
|
||||
* @copyright Bernhard Posselt 2014
|
||||
*/
|
||||
|
||||
/**
|
||||
* Explore styles
|
||||
*/
|
||||
@ -16,4 +15,43 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#explore {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 45px 0 45px 45px;
|
||||
}
|
||||
#explore .grid-item {
|
||||
width: 300px;
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: var(--border-radius-large);
|
||||
margin: 0 24px 24px 0;
|
||||
padding: 24px;
|
||||
}
|
||||
#explore .grid-item .explore-title {
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 center;
|
||||
background-size: 24px;
|
||||
padding-left: 32px;
|
||||
}
|
||||
#explore .grid-item .explore-title a {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
#explore .grid-item .explore-title a:hover, #explore .grid-item .explore-title a:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
#explore .grid-item .explore-logo {
|
||||
text-align: center;
|
||||
margin-top: 25px;
|
||||
}
|
||||
#explore .grid-item .explore-logo img {
|
||||
width: 100%;
|
||||
}
|
||||
#explore .grid-item .explore-subscribe {
|
||||
margin-top: 16px;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=explore.css.map */
|
||||
|
@ -1,57 +0,0 @@
|
||||
/**
|
||||
* Nextcloud - News
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Bernhard Posselt <dev@bernhard-posselt.com>
|
||||
* @copyright 2020, Jan C. Borchardt, https://jancborchardt.net
|
||||
* @copyright Bernhard Posselt 2014
|
||||
*/
|
||||
|
||||
/**
|
||||
* Explore styles
|
||||
*/
|
||||
.explore #app-content-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#explore {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 45px 0 45px 45px;
|
||||
|
||||
.grid-item {
|
||||
width: 300px;
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: var(--border-radius-large);
|
||||
margin: 0 24px 24px 0;
|
||||
padding: 24px;
|
||||
|
||||
.explore-title {
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 center;
|
||||
background-size: 24px;
|
||||
padding-left: 32px;
|
||||
|
||||
a {
|
||||
word-wrap: break-word;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.explore-logo {
|
||||
text-align: center;
|
||||
margin-top: 25px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
@import 'explore';
|
||||
@import 'admin';
|
||||
@import 'app';
|
||||
@import 'content';
|
||||
@import 'custom';
|
||||
@import 'mobile';
|
||||
@import 'navigation';
|
||||
@import 'settings';
|
||||
@import 'shortcuts';
|
@ -34,6 +34,60 @@
|
||||
background-position: 14px center;
|
||||
}
|
||||
|
||||
|
||||
/* actual form content */
|
||||
#app-navigation .add-new-popup {
|
||||
display: none;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
border-top: 1px solid var(--color-border-dark);
|
||||
border-bottom: 1px solid var(--color-border-dark);
|
||||
}
|
||||
|
||||
|
||||
#app-navigation input.ng-invalid {
|
||||
border: 1px solid var(--color-error);
|
||||
}
|
||||
|
||||
#app-navigation .add-new-popup input,
|
||||
#app-navigation .add-new-popup select {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
padding: 5px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#app-navigation .add-new-popup select,
|
||||
#app-navigation #new-feed [name="folderName"] {
|
||||
width: calc(100% - 36px);
|
||||
border-right: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#app-navigation .add-new-popup .add-new-folder-primary {
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#app-navigation .add-new-popup .add-new-basicauth-toggle {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
#app-navigation .add-new-popup .add-feed-basicauth {
|
||||
width: 100%;
|
||||
padding: 0 0 10px;
|
||||
}
|
||||
|
||||
#app-navigation .add-new-popup .error {
|
||||
padding: 0 0 10px 0;
|
||||
}
|
||||
|
||||
/* navigation */
|
||||
#app-navigation ul.with-icon > li > a,
|
||||
#app-navigation ul.with-icon > li > ul > li > a {
|
||||
@ -64,6 +118,7 @@
|
||||
background-image: url('../img/twitter.svg') !important;
|
||||
}
|
||||
|
||||
|
||||
.col-4 {
|
||||
-ms-flex: 0 0 33.333333%;
|
||||
flex: 0 0 33.333333%;
|
||||
@ -79,7 +134,7 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.icon-dropdown {
|
||||
.icon-dropdown{
|
||||
background-position: center !important;
|
||||
background-size: 24px;
|
||||
padding: 2em !important;
|
||||
@ -90,13 +145,14 @@
|
||||
.icon-full-text-enabled {
|
||||
background-image: url('../img/fulltext.svg');
|
||||
background-size: 50%;
|
||||
opacity: 0.5;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
|
||||
.icon-full-text-disabled {
|
||||
background-image: url('../img/nonfulltext.svg');
|
||||
background-size: 50%;
|
||||
opacity: 0.5;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
#app-navigation .unread > a {
|
||||
@ -134,10 +190,10 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// #app-navigation .error-message:hover .title
|
||||
// #app-navigation .error-message:hover .message {
|
||||
#app-navigation .error-message:hover .title
|
||||
#app-navigation .error-message:hover .message {
|
||||
|
||||
// }
|
||||
}
|
||||
|
||||
#app-navigation .error-message button {
|
||||
top: 0;
|
||||
@ -153,7 +209,7 @@
|
||||
background-image: url('../img/close.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 5px top 5px;
|
||||
opacity: 0.9;
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
#app-navigation .error-message button:hover {
|
||||
@ -169,7 +225,7 @@
|
||||
|
||||
#app-navigation .animate-show.ng-hide-add-active,
|
||||
#app-navigation .animate-show.ng-hide-remove-active {
|
||||
transition: 0.2s linear opacity;
|
||||
transition: .2s linear opacity;
|
||||
}
|
||||
|
||||
#app-navigation .animate-show {
|
||||
@ -188,8 +244,8 @@
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
// #app-navigation .feed-normal-ordering {
|
||||
// }
|
||||
#app-navigation .feed-normal-ordering {
|
||||
}
|
||||
|
||||
#app-navigation .icon-pinned {
|
||||
background-image: url('../img/pinned.svg');
|
||||
@ -207,10 +263,6 @@
|
||||
background-image: url('../img/updatemodeunread.svg');
|
||||
}
|
||||
|
||||
#app-navigation .icon-arrow {
|
||||
background-image: url('../img/arrow.svg');
|
||||
}
|
||||
|
||||
#app-navigation .updateerror a {
|
||||
background-color: var(--color-warning) !important;
|
||||
}
|
@ -36,7 +36,7 @@
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
#app-settings-content label input[type='checkbox'] {
|
||||
#app-settings-content label input[type="checkbox"] {
|
||||
margin-left: -18px;
|
||||
max-width: 16px;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// #app-shortcuts {
|
||||
// }
|
||||
#app-shortcuts {
|
||||
}
|
||||
|
||||
#app-shortcuts table {
|
||||
border-collapse: collapse;
|
||||
|
@ -1,11 +0,0 @@
|
||||
# https://hub.docker.com/_/nextcloud/
|
||||
FROM nextcloud:29-apache
|
||||
#FROM ghcr.io/pbek/nextcloud-docker-pre-apache:latest
|
||||
#FROM ghcr.io/digital-blueprint/nextcloud-docker-pre-apache:latest
|
||||
|
||||
COPY entrypoint.sh /
|
||||
|
||||
RUN deluser www-data
|
||||
RUN useradd -u 1000 -ms /bin/bash www-data
|
||||
RUN usermod -a -G www-data www-data
|
||||
RUN mkdir /var/www/deploy
|
@ -1,28 +0,0 @@
|
||||
.PHONY: bash check-code show-log test build
|
||||
|
||||
# Try to use "docker compose" and fall back to "docker-compose" if not available
|
||||
DOCKER_COMPOSE := $(shell docker compose > /dev/null && echo docker compose || echo docker-compose)
|
||||
|
||||
build:
|
||||
$(DOCKER_COMPOSE) build
|
||||
|
||||
ls-db:
|
||||
$(DOCKER_COMPOSE) run --rm app su -c "ls -hal data/mydb.db*" www-data
|
||||
|
||||
fetch-db:
|
||||
$(DOCKER_COMPOSE) run --rm app su -c "cp data/mydb.db apps/news" www-data
|
||||
|
||||
push-db:
|
||||
$(DOCKER_COMPOSE) run --rm app su -c "cp apps/news/mydb.db* data" www-data
|
||||
|
||||
bash:
|
||||
$(DOCKER_COMPOSE) run --rm app su -c "bash" www-data
|
||||
|
||||
check-code:
|
||||
$(DOCKER_COMPOSE) run --rm app su -c "./occ app:check-code news" www-data
|
||||
|
||||
show-log:
|
||||
$(DOCKER_COMPOSE) run --rm app tail -f /var/www/html/data/nextcloud.log
|
||||
|
||||
#test:
|
||||
# $(DOCKER_COMPOSE) run --rm app su -c "cd custom_apps/news && make test" www-data
|
@ -1,29 +0,0 @@
|
||||
# Nextcloud Development Environment
|
||||
|
||||
## Installation / Running
|
||||
|
||||
```bash
|
||||
make build
|
||||
docker compose up
|
||||
```
|
||||
|
||||
Afterward you should be able to open <http://localhost:8081/index.php/apps/news> (admin/admin) to
|
||||
log in to your Nextcloud instance.
|
||||
|
||||
## Check nextcloud.log
|
||||
|
||||
For debugging, you can show the `nextcloud.log`:
|
||||
|
||||
```bash
|
||||
make show-log
|
||||
```
|
||||
|
||||
There also is a [logging web interface](http://localhost:8081/index.php/settings/admin/logging).
|
||||
|
||||
## Tip
|
||||
|
||||
In case something is broken try to reset the container:
|
||||
|
||||
```bash
|
||||
docker compose build; docker compose down; docker volume rm nextcloud-news_nextcloud
|
||||
```
|
@ -1,19 +0,0 @@
|
||||
name: nextcloud-news
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- 8081:80
|
||||
environment:
|
||||
- NEXTCLOUD_ADMIN_USER=admin
|
||||
- NEXTCLOUD_ADMIN_PASSWORD=admin
|
||||
- SQLITE_DATABASE=mydb
|
||||
- NEXTCLOUD_TRUSTED_DOMAINS=localhost 127.0.0.1
|
||||
volumes:
|
||||
- nextcloud:/var/www/html
|
||||
- ..:/var/www/html/custom_apps/news
|
||||
- ./news.config.php:/var/www/html/config/news.config.php
|
||||
|
||||
volumes:
|
||||
nextcloud:
|
@ -1,198 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
chown www-data:root /var/www/deploy
|
||||
chown www-data:root /var/www/html/custom_apps
|
||||
chown www-data:root /var/www/html/config
|
||||
|
||||
# version_greater A B returns whether A > B
|
||||
version_greater() {
|
||||
[ "$(printf '%s\n' "$@" | sort -t '.' -n -k1,1 -k2,2 -k3,3 -k4,4 | head -n 1)" != "$1" ]
|
||||
}
|
||||
|
||||
# return true if specified directory is empty
|
||||
directory_empty() {
|
||||
[ -z "$(ls -A "$1/")" ]
|
||||
}
|
||||
|
||||
run_as() {
|
||||
if [ "$(id -u)" = 0 ]; then
|
||||
su -p www-data -s /bin/sh -c "$1"
|
||||
else
|
||||
sh -c "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# usage: file_env VAR [DEFAULT]
|
||||
# ie: file_env 'XYZ_DB_PASSWORD' 'example'
|
||||
# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
|
||||
# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
|
||||
file_env() {
|
||||
local var="$1"
|
||||
local fileVar="${var}_FILE"
|
||||
local def="${2:-}"
|
||||
local varValue=$(env | grep -E "^${var}=" | sed -E -e "s/^${var}=//")
|
||||
local fileVarValue=$(env | grep -E "^${fileVar}=" | sed -E -e "s/^${fileVar}=//")
|
||||
if [ -n "${varValue}" ] && [ -n "${fileVarValue}" ]; then
|
||||
echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "${varValue}" ]; then
|
||||
export "$var"="${varValue}"
|
||||
elif [ -n "${fileVarValue}" ]; then
|
||||
export "$var"="$(cat "${fileVarValue}")"
|
||||
elif [ -n "${def}" ]; then
|
||||
export "$var"="$def"
|
||||
fi
|
||||
unset "$fileVar"
|
||||
}
|
||||
|
||||
if expr "$1" : "apache" 1>/dev/null; then
|
||||
if [ -n "${APACHE_DISABLE_REWRITE_IP+x}" ]; then
|
||||
a2disconf remoteip
|
||||
fi
|
||||
fi
|
||||
|
||||
if expr "$1" : "apache" 1>/dev/null || [ "$1" = "php-fpm" ] || [ "${NEXTCLOUD_UPDATE:-0}" -eq 1 ]; then
|
||||
if [ -n "${REDIS_HOST+x}" ]; then
|
||||
|
||||
echo "Configuring Redis as session handler"
|
||||
{
|
||||
echo 'session.save_handler = redis'
|
||||
# check if redis host is an unix socket path
|
||||
if [ "$(echo "$REDIS_HOST" | cut -c1-1)" = "/" ]; then
|
||||
if [ -n "${REDIS_HOST_PASSWORD+x}" ]; then
|
||||
echo "session.save_path = \"unix://${REDIS_HOST}?auth=${REDIS_HOST_PASSWORD}\""
|
||||
else
|
||||
echo "session.save_path = \"unix://${REDIS_HOST}\""
|
||||
fi
|
||||
# check if redis password has been set
|
||||
elif [ -n "${REDIS_HOST_PASSWORD+x}" ]; then
|
||||
echo "session.save_path = \"tcp://${REDIS_HOST}:${REDIS_HOST_PORT:=6379}?auth=${REDIS_HOST_PASSWORD}\""
|
||||
else
|
||||
echo "session.save_path = \"tcp://${REDIS_HOST}:${REDIS_HOST_PORT:=6379}\""
|
||||
fi
|
||||
} > /usr/local/etc/php/conf.d/redis-session.ini
|
||||
fi
|
||||
|
||||
installed_version="0.0.0.0"
|
||||
if [ -f /var/www/html/version.php ]; then
|
||||
# shellcheck disable=SC2016
|
||||
installed_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')"
|
||||
fi
|
||||
# shellcheck disable=SC2016
|
||||
image_version="$(php -r 'require "/usr/src/nextcloud/version.php"; echo implode(".", $OC_Version);')"
|
||||
|
||||
if version_greater "$installed_version" "$image_version"; then
|
||||
echo "Can't start Nextcloud because the version of the data ($installed_version) is higher than the docker image version ($image_version) and downgrading is not supported. Are you sure you have pulled the newest image version?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if version_greater "$image_version" "$installed_version"; then
|
||||
echo "Initializing nextcloud $image_version ..."
|
||||
if [ "$installed_version" != "0.0.0.0" ]; then
|
||||
echo "Upgrading nextcloud from $installed_version ..."
|
||||
run_as 'php /var/www/html/occ app:list' | sed -n "/Enabled:/,/Disabled:/p" > /tmp/list_before
|
||||
fi
|
||||
if [ "$(id -u)" = 0 ]; then
|
||||
rsync_options="-rlDog --chown www-data:root"
|
||||
else
|
||||
rsync_options="-rlD"
|
||||
fi
|
||||
rsync $rsync_options --delete --exclude-from=/upgrade.exclude /usr/src/nextcloud/ /var/www/html/
|
||||
|
||||
for dir in config data custom_apps themes; do
|
||||
if [ ! -d "/var/www/html/$dir" ] || directory_empty "/var/www/html/$dir"; then
|
||||
rsync $rsync_options --include "/$dir/" --exclude '/*' /usr/src/nextcloud/ /var/www/html/
|
||||
fi
|
||||
done
|
||||
rsync $rsync_options --include '/version.php' --exclude '/*' /usr/src/nextcloud/ /var/www/html/
|
||||
echo "Initializing finished"
|
||||
|
||||
#install
|
||||
if [ "$installed_version" = "0.0.0.0" ]; then
|
||||
echo "New nextcloud instance"
|
||||
|
||||
file_env NEXTCLOUD_ADMIN_PASSWORD
|
||||
file_env NEXTCLOUD_ADMIN_USER
|
||||
|
||||
if [ -n "${NEXTCLOUD_ADMIN_USER+x}" ] && [ -n "${NEXTCLOUD_ADMIN_PASSWORD+x}" ]; then
|
||||
# shellcheck disable=SC2016
|
||||
install_options='-n --admin-user "$NEXTCLOUD_ADMIN_USER" --admin-pass "$NEXTCLOUD_ADMIN_PASSWORD"'
|
||||
if [ -n "${NEXTCLOUD_DATA_DIR+x}" ]; then
|
||||
# shellcheck disable=SC2016
|
||||
install_options=$install_options' --data-dir "$NEXTCLOUD_DATA_DIR"'
|
||||
fi
|
||||
|
||||
file_env MYSQL_DATABASE
|
||||
file_env MYSQL_PASSWORD
|
||||
file_env MYSQL_USER
|
||||
file_env POSTGRES_DB
|
||||
file_env POSTGRES_PASSWORD
|
||||
file_env POSTGRES_USER
|
||||
|
||||
install=false
|
||||
if [ -n "${SQLITE_DATABASE+x}" ]; then
|
||||
echo "Installing with SQLite database"
|
||||
# shellcheck disable=SC2016
|
||||
install_options=$install_options' --database-name "$SQLITE_DATABASE"'
|
||||
install=true
|
||||
elif [ -n "${MYSQL_DATABASE+x}" ] && [ -n "${MYSQL_USER+x}" ] && [ -n "${MYSQL_PASSWORD+x}" ] && [ -n "${MYSQL_HOST+x}" ]; then
|
||||
echo "Installing with MySQL database"
|
||||
# shellcheck disable=SC2016
|
||||
install_options=$install_options' --database mysql --database-name "$MYSQL_DATABASE" --database-user "$MYSQL_USER" --database-pass "$MYSQL_PASSWORD" --database-host "$MYSQL_HOST"'
|
||||
install=true
|
||||
elif [ -n "${POSTGRES_DB+x}" ] && [ -n "${POSTGRES_USER+x}" ] && [ -n "${POSTGRES_PASSWORD+x}" ] && [ -n "${POSTGRES_HOST+x}" ]; then
|
||||
echo "Installing with PostgreSQL database"
|
||||
# shellcheck disable=SC2016
|
||||
install_options=$install_options' --database pgsql --database-name "$POSTGRES_DB" --database-user "$POSTGRES_USER" --database-pass "$POSTGRES_PASSWORD" --database-host "$POSTGRES_HOST"'
|
||||
install=true
|
||||
fi
|
||||
|
||||
if [ "$install" = true ]; then
|
||||
echo "starting nextcloud installation"
|
||||
max_retries=10
|
||||
try=0
|
||||
until run_as "php /var/www/html/occ maintenance:install $install_options" || [ "$try" -gt "$max_retries" ]
|
||||
do
|
||||
echo "retrying install..."
|
||||
try=$((try+1))
|
||||
sleep 10s
|
||||
done
|
||||
if [ "$try" -gt "$max_retries" ]; then
|
||||
echo "installing of nextcloud failed!"
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "${NEXTCLOUD_TRUSTED_DOMAINS+x}" ]; then
|
||||
echo "setting trusted domains…"
|
||||
NC_TRUSTED_DOMAIN_IDX=1
|
||||
for DOMAIN in $NEXTCLOUD_TRUSTED_DOMAINS ; do
|
||||
DOMAIN=$(echo "$DOMAIN" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
||||
run_as "php /var/www/html/occ config:system:set trusted_domains $NC_TRUSTED_DOMAIN_IDX --value=$DOMAIN"
|
||||
NC_TRUSTED_DOMAIN_IDX=$(($NC_TRUSTED_DOMAIN_IDX+1))
|
||||
done
|
||||
fi
|
||||
else
|
||||
echo "running web-based installer on first connect!"
|
||||
fi
|
||||
fi
|
||||
#upgrade
|
||||
else
|
||||
run_as 'php /var/www/html/occ upgrade'
|
||||
|
||||
run_as 'php /var/www/html/occ app:list' | sed -n "/Enabled:/,/Disabled:/p" > /tmp/list_after
|
||||
echo "The following apps have been disabled:"
|
||||
diff /tmp/list_before /tmp/list_after | grep '<' | cut -d- -f2 | cut -d: -f1
|
||||
rm -f /tmp/list_before /tmp/list_after
|
||||
|
||||
fi
|
||||
fi
|
||||
|
||||
# if we get an error "ln: /var/www/html/apps/news: cannot overwrite directory" we need to remove that directory in the container
|
||||
run_as 'rm -Rf /var/www/html/apps/news'
|
||||
|
||||
run_as 'ln -sfT /var/www/html/custom_apps/news /var/www/html/apps/news'
|
||||
run_as "php /var/www/html/occ app:enable news"
|
||||
fi
|
||||
|
||||
exec "$@"
|
@ -1 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
@ -1,32 +1,10 @@
|
||||
# Admin
|
||||
|
||||
Welcome to the Admin documentation.
|
||||
|
||||
## Administration via OCC
|
||||
|
||||
News integrates with Nextclouds command line tool occ.
|
||||
|
||||
To get an overview over the available commands simply execute `./occ list news`
|
||||
|
||||
In most environments you will have to call occ like this:
|
||||
|
||||
```bash
|
||||
sudo -u www-data php ./occ list news
|
||||
```
|
||||
|
||||
More information about occ here: [Nextcloud Admin Manual](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/occ_command.html)
|
||||
|
||||
## Settings
|
||||
|
||||
The following sections explain some of the more complicated settings on the admin page.
|
||||
|
||||
### System Cron
|
||||
|
||||
Welcome to the Admin documentation this page explains some of the configuration options for news.
|
||||
## System Cron
|
||||
Nextcloud uses cron to run regular jobs, News relies on the Job system to execute the feed updates.
|
||||
Alternatively you may use an [external updater](https://nextcloud.github.io/news/clients/#update-clients), in this case you need to disable the system cron in the settings.
|
||||
|
||||
### Auto purge count
|
||||
|
||||
## Auto purge count
|
||||
- The default value is 200.
|
||||
- To disable this feature, use -1.
|
||||
- Unread and starred items are not deleted.
|
||||
@ -39,35 +17,30 @@ In this case the limit will be 210 instead of 200, for that feed.
|
||||
|
||||
This is needed to prevent items from reappearing in the feed.
|
||||
|
||||
### Purge unread items
|
||||
|
||||
## Purge unread items
|
||||
This changes the behavior of the auto purging to also purge unread items. This is useful if you have users with a lot of unread items.
|
||||
|
||||
**Starred items are always kept.**
|
||||
|
||||
### Explore Service
|
||||
|
||||
## Explore Service
|
||||
If you are using the News app in your company/community, it might be interesting to offer your users a bunch of easily to discover default feeds. You could also create a website where people can add and up-vote news feeds like bigger cloud feed readers like Feedly do it or even convert their APIs into a service for the News app (if someone wants to provide one for the News app, feel free to contact us by creating an issue in the bug tracker).
|
||||
|
||||
The URL should be a path to a directory which contains a JSON file in the format of **feeds.LANG_CODE.json** where LANG_CODE is a two character language code (e.g. **en** or **de**).
|
||||
|
||||
For example, entering the URL **<https://domain.com/directory>** as explore URL will produce the following request for German users:
|
||||
For example, entering the URL **https://domain.com/directory** as explore URL will produce the following request for German users:
|
||||
|
||||
GET https://domain.com/directory/feeds.de.json
|
||||
|
||||
**Do not forget to implement [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) in your API, otherwise the request will fail!**
|
||||
|
||||
### Update Interval
|
||||
|
||||
## Update Interval
|
||||
The update interval is used to determine when the next update of all feeds should be done.
|
||||
By default, the value is set to 3600 seconds (1 hour) You can configure this interval as an administrator.
|
||||
The new value is only applied after the next run of the updater.
|
||||
|
||||
#### What is a good update interval?
|
||||
|
||||
### What is a good update interval?
|
||||
That depends on your individual needs.
|
||||
Please keep in mind that the lower you set your update interval, the more traffic is generated.
|
||||
|
||||
#### Can I set individual update intervals per feed/user?
|
||||
|
||||
### Can I set individual update intervals per feed/user?
|
||||
No, that is not possible.
|
@ -23,7 +23,6 @@ You have to design your app with these things in mind!:
|
||||
* **Use a library to compare versions, ideally one that uses semantic versioning**
|
||||
|
||||
## Authentication & Basics
|
||||
|
||||
Because REST is stateless you have to send user and password each time you access the API. Therefore running Nextcloud **with SSL is highly recommended** otherwise **everyone in your network can log your credentials**.
|
||||
|
||||
The base URL for all calls is:
|
||||
@ -43,13 +42,11 @@ where $CREDENTIALS is:
|
||||
base64(USER:PASSWORD)
|
||||
|
||||
## How To Sync
|
||||
|
||||
This is a small overview over how you should sync your articles with the Nextcloud News app. For more fine-grained details about the API see further down.
|
||||
|
||||
All routes are given relative to the base API url (e.g.: <https://yournextcloud.com/index.php/apps/news/api/v1-2>)
|
||||
All routes are given relative to the base API url (e.g.: https://yournextcloud.com/index.php/apps/news/api/v1-2)
|
||||
|
||||
### Initial Sync
|
||||
|
||||
The intial sync happens, when a user adds a Nextcloud account in your app. In that case you should fetch all feeds, folders and unread or starred articles from the News app. Do not fetch all articles, not only because it syncs faster, but also because the user is primarily interested in unread articles. To fetch all unread and starred articles, you must call 4 routes:
|
||||
|
||||
* **unread articles**: GET /items?type=3&getRead=false&batchSize=-1
|
||||
@ -60,7 +57,6 @@ The intial sync happens, when a user adds a Nextcloud account in your app. In th
|
||||
The JSON response structures can be viewed further down.
|
||||
|
||||
### Syncing
|
||||
|
||||
When syncing, you want to push read/unread and starred/unstarred items to the server and receive new and updated items, feeds and folders. To do that, call the following routes:
|
||||
|
||||
* **Notify the News app of unread articles**: PUT /items/unread/multiple {"items": [1, 3, 5] }
|
||||
@ -71,6 +67,7 @@ When syncing, you want to push read/unread and starred/unstarred items to the se
|
||||
* **Get new feeds**: GET /feeds
|
||||
* **Get new items and modified items**: GET /items/updated?lastModified=12123123123&type=3
|
||||
|
||||
|
||||
## Accessing API from a web application
|
||||
|
||||
**News 1.401** implements CORS which allows web applications to access the API. **To access the API in a webapp you need to send the correct authorization header instead of simply putting auth data into the URL!**. An example request in jQuery would look like this:
|
||||
@ -94,9 +91,7 @@ $.ajax({
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
An example with AngularJS would look like this:
|
||||
|
||||
```js
|
||||
angular.module('YourApp', [])
|
||||
.config(['$httpProvider', '$provide', function ($httpProvider, $provide) {
|
||||
@ -134,17 +129,14 @@ angular.module('YourApp', [])
|
||||
```
|
||||
|
||||
## Input
|
||||
|
||||
In general the input parameters can be in the URL or request body, the App Framework doesnt differentiate between them.
|
||||
|
||||
So JSON in the request body like:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"id": 3
|
||||
}
|
||||
```
|
||||
|
||||
will be treated the same as
|
||||
|
||||
/?id=3
|
||||
@ -157,13 +149,11 @@ It is recommended though that you use the following convention:
|
||||
* **DELETE**: parameters as JSON in the request body
|
||||
|
||||
## Output
|
||||
|
||||
The output is JSON.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Folders
|
||||
|
||||
#### Get all folders
|
||||
|
||||
* **Status**: Implemented
|
||||
@ -171,8 +161,7 @@ The output is JSON.
|
||||
* **Route**: /folders
|
||||
* **Parameters**: none
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
@ -184,26 +173,22 @@ The output is JSON.
|
||||
```
|
||||
|
||||
#### Create a folder
|
||||
|
||||
Creates a new folder and returns a new folder object
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: POST
|
||||
* **Route**: /folders
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"name": "folder name"
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 409**: If the folder exists already
|
||||
* **HTTP 422**: If the folder name is invalid (for instance empty)
|
||||
* **HTTP 409**: If the folder exists already
|
||||
* **HTTP 422**: If the folder name is invalid (for instance empty)
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
@ -215,7 +200,6 @@ Creates a new folder and returns a new folder object
|
||||
```
|
||||
|
||||
#### Delete a folder
|
||||
|
||||
Deletes a folder with the id folderId and all the feeds it contains
|
||||
|
||||
* **Status**: Implemented
|
||||
@ -223,28 +207,25 @@ Deletes a folder with the id folderId and all the feeds it contains
|
||||
* **Route**: /folders/{folderId}
|
||||
* **Parameters**: none
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the folder does not exist
|
||||
* **HTTP 404**: If the folder does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Rename a folder
|
||||
|
||||
Only the name can be updated
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: PUT
|
||||
* **Route**: /folders/{folderId}
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"name": "folder name"
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 409**: If the folder name does already exist
|
||||
* **HTTP 404**: If the folder does not exist
|
||||
* **HTTP 422**: If the folder name is invalid (for instance empty)
|
||||
* **HTTP 409**: If the folder name does already exist
|
||||
* **HTTP 404**: If the folder does not exist
|
||||
* **HTTP 422**: If the folder name is invalid (for instance empty)
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark items of a folder as read
|
||||
@ -253,17 +234,15 @@ Only the name can be updated
|
||||
* **Method**: PUT
|
||||
* **Route**: /folders/{folderId}/read
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
// mark all items read lower than equal that id
|
||||
// this is mean to prevent marking items as read which the client/user does not yet know of
|
||||
"newestItemId": 10
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
### Feeds
|
||||
@ -282,8 +261,7 @@ The following attributes are **not sanitized** meaning: including them in your w
|
||||
* **Route**: /feeds
|
||||
* **Parameters**: none
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"feeds": [
|
||||
{
|
||||
@ -312,27 +290,23 @@ The following attributes are **not sanitized** meaning: including them in your w
|
||||
```
|
||||
|
||||
#### Create a feed
|
||||
|
||||
Creates a new feed and returns the feed
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: POST
|
||||
* **Route**: /feeds
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"url": "http:\/\/www.cyanogenmod.org\/wp-content\/themes\/cyanogenmod\/images\/favicon.ico",
|
||||
"folderId": 81 // id of the parent folder, null for root
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 409**: If the feed exists already
|
||||
* **HTTP 422**: If the feed cant be read (most likely contains errors)
|
||||
* **HTTP 409**: If the feed exists already
|
||||
* **HTTP 422**: If the feed cant be read (most likely contains errors)
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"feeds": [
|
||||
{
|
||||
@ -353,7 +327,6 @@ Creates a new feed and returns the feed
|
||||
```
|
||||
|
||||
#### Delete a feed
|
||||
|
||||
Deletes a feed with the id feedId and all of its items
|
||||
|
||||
* **Status**: Implemented
|
||||
@ -361,7 +334,7 @@ Deletes a feed with the id feedId and all of its items
|
||||
* **Route**: /feeds/{feedId}
|
||||
* **Parameters**: none
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Move a feed to a different folder
|
||||
@ -370,15 +343,13 @@ Deletes a feed with the id feedId and all of its items
|
||||
* **Method**: PUT
|
||||
* **Route**: /feeds/{feedId}/move
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"folderId": null // id of the parent folder, null for root
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Rename a feed
|
||||
@ -387,15 +358,13 @@ Deletes a feed with the id feedId and all of its items
|
||||
* **Method**: PUT
|
||||
* **Route**: /feeds/{feedId}/rename
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"feedTitle": "New Title"
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark items of a feed as read
|
||||
@ -404,19 +373,18 @@ Deletes a feed with the id feedId and all of its items
|
||||
* **Method**: PUT
|
||||
* **Route**: /feeds/{feedId}/read
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
// mark all items read lower than equal that id
|
||||
// this is mean to prevent marking items as read which the client/user does not yet know of
|
||||
"newestItemId": 10
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
|
||||
### Items
|
||||
|
||||
#### Sanitation
|
||||
@ -432,7 +400,6 @@ The following attributes are **not sanitized** meaning: including them in your w
|
||||
* **mediaDescription**
|
||||
|
||||
#### Types
|
||||
|
||||
| Name | Default | Types |
|
||||
|------------------|---------|--------------|
|
||||
| author | null | string\|null |
|
||||
@ -457,13 +424,11 @@ The following attributes are **not sanitized** meaning: including them in your w
|
||||
| url | | string\|null |
|
||||
|
||||
#### Get items
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: GET
|
||||
* **Route**: /items
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"batchSize": 10, // the number of items that should be returned, defaults to -1, new in 5.2.3: -1 returns all items
|
||||
"offset": 30, // only return older (lower than equal that id) items than the one with id 30
|
||||
@ -473,10 +438,8 @@ The following attributes are **not sanitized** meaning: including them in your w
|
||||
"oldestFirst": false // implemented in 3.002, if true it reverse the sort order
|
||||
}
|
||||
```
|
||||
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
@ -504,14 +467,12 @@ The following attributes are **not sanitized** meaning: including them in your w
|
||||
```
|
||||
|
||||
##### Example
|
||||
|
||||
Autopaging would work like this:
|
||||
|
||||
* Get the **first 20** items from a feed with **id 12**
|
||||
|
||||
**GET /items**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"batchSize": 20,
|
||||
"offset": 0,
|
||||
@ -525,7 +486,7 @@ The item with the lowest item id is 43.
|
||||
|
||||
* Get the next **20** items: **GET /items**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"batchSize": 20,
|
||||
"offset": 43,
|
||||
@ -535,16 +496,15 @@ The item with the lowest item id is 43.
|
||||
}
|
||||
```
|
||||
|
||||
#### Get updated items
|
||||
|
||||
#### Get updated items
|
||||
This is used to stay up to date.
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: GET
|
||||
* **Route**: /items/updated
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"lastModified": 123231, // returns only items with a lastModified timestamp >= than this one
|
||||
// this may also return already existing items whose read or starred status
|
||||
@ -553,10 +513,8 @@ This is used to stay up to date.
|
||||
"id": 12 // the id of the folder or feed, Use 0 for Starred and All
|
||||
}
|
||||
```
|
||||
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
@ -581,73 +539,62 @@ This is used to stay up to date.
|
||||
```
|
||||
|
||||
#### Mark an item as read
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: PUT
|
||||
* **Route**: /items/{itemId}/read
|
||||
* **Parameters**: none
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark multiple items as read
|
||||
|
||||
* **Status**: Implemented in 1.2
|
||||
* **Method**: PUT
|
||||
* **Route**: /items/read/multiple
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"items": [2, 3] // ids of the items
|
||||
}
|
||||
```
|
||||
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark an item as unread
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: PUT
|
||||
* **Route**: /items/{itemId}/unread
|
||||
* **Parameters**: none
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark multiple items as unread
|
||||
|
||||
* **Status**: Implemented in 1.2
|
||||
* **Method**: PUT
|
||||
* **Route**: /items/unread/multiple
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"items": [2, 3] // ids of the items
|
||||
}
|
||||
```
|
||||
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark an item as starred
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: PUT
|
||||
* **Route**: /items/{feedId}/{guidHash}/star
|
||||
* **Parameters**: none
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark multiple items as starred
|
||||
|
||||
* **Status**: Implemented in 1.2
|
||||
* **Method**: PUT
|
||||
* **Route**: /items/star/multiple
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
@ -657,27 +604,23 @@ This is used to stay up to date.
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark an item as unstarred
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: PUT
|
||||
* **Route**: /items/{feedId}/{guidHash}/unstar
|
||||
* **Parameters**: none
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark multiple items as unstarred
|
||||
|
||||
* **Status**: Implemented in 1.2
|
||||
* **Method**: PUT
|
||||
* **Route**: /items/unstar/multiple
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
@ -687,7 +630,6 @@ This is used to stay up to date.
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark all items as read
|
||||
@ -696,19 +638,18 @@ This is used to stay up to date.
|
||||
* **Method**: PUT
|
||||
* **Route**: /items/read
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
// mark all items read lower than equal that id
|
||||
// this is mean to prevent marking items as read which the client/user does not yet know of
|
||||
"newestItemId": 10
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
|
||||
### Updater
|
||||
|
||||
To enable people to write their own update scripts instead of relying on the sequential built in web and system cron, API routes and console commands have been created.
|
||||
@ -723,7 +664,6 @@ Updating should be done in the following fashion:
|
||||
This [implementation in Python](https://github.com/nextcloud/news-updater) should give you a good idea how to design and run it.
|
||||
|
||||
#### Trigger cleanup before update
|
||||
|
||||
This is used to clean up the database. It deletes folders and feeds that are marked for deletion
|
||||
|
||||
* **Status**: Implemented in 1.601
|
||||
@ -734,9 +674,7 @@ This is used to clean up the database. It deletes folders and feeds that are mar
|
||||
|
||||
**New in 8.1.0**: The console command for achieving the same result is:
|
||||
|
||||
```bash
|
||||
php -f nextcloud/occ news:updater:before-update
|
||||
```
|
||||
php -f nextcloud/occ news:updater:before-update
|
||||
|
||||
#### Get feed ids and usernames for all feeds
|
||||
|
||||
@ -746,8 +684,7 @@ php -f nextcloud/occ news:updater:before-update
|
||||
* **Route**: /feeds/all
|
||||
* **Parameters**: none
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"feeds": [
|
||||
{
|
||||
@ -760,9 +697,8 @@ php -f nextcloud/occ news:updater:before-update
|
||||
|
||||
**New in 8.1.0, Removed in 16.0.0**: The console command for achieving the same result is:
|
||||
|
||||
```bash
|
||||
php -f nextcloud/occ news:updater:all-feeds
|
||||
```
|
||||
php -f nextcloud/occ news:updater:all-feeds
|
||||
|
||||
|
||||
#### Trigger a feed update
|
||||
|
||||
@ -771,26 +707,21 @@ php -f nextcloud/occ news:updater:all-feeds
|
||||
* **Method**: GET
|
||||
* **Route**: /feeds/update
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"userId": "john",
|
||||
"feedId": 3
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **Returns**: Nothing
|
||||
|
||||
**New in 8.1.0**: The console command for achieving the same result is:
|
||||
|
||||
```bash
|
||||
php -f nextcloud/occ news:updater:update-feed 3 john
|
||||
```
|
||||
php -f nextcloud/occ news:updater:update-feed 3 john
|
||||
|
||||
#### Trigger cleanup after update
|
||||
|
||||
This is used to clean up the database. It removes old read articles which are not starred
|
||||
|
||||
* **Status**: Implemented in 1.601
|
||||
@ -801,9 +732,7 @@ This is used to clean up the database. It removes old read articles which are no
|
||||
|
||||
**New in 8.1.0**: The console command for achieving the same result is:
|
||||
|
||||
```bash
|
||||
php -f nextcloud/occ news:updater:after-update
|
||||
```
|
||||
php -f nextcloud/occ news:updater:after-update
|
||||
|
||||
### Version
|
||||
|
||||
@ -814,8 +743,7 @@ php -f nextcloud/occ news:updater:after-update
|
||||
* **Route**: /version
|
||||
* **Parameters**: none
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"version": "5.2.3"
|
||||
}
|
||||
@ -832,8 +760,7 @@ This API can be used to display warnings and errors in your client if the web ap
|
||||
* **Route**: /status
|
||||
* **Parameters**: none
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"version": "5.2.4",
|
||||
"warnings": {
|
||||
@ -859,9 +786,8 @@ If **incorrectDbCharset** is true you should display a warning that database cha
|
||||
This API can be used to retrieve metadata about the current user.
|
||||
|
||||
DEPRECATED: This API is deprecated, use the Nextcloud APIs instead.
|
||||
|
||||
* <https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html#user-metadata> for user data
|
||||
* `https://nc.url/avatar/{userid}/{size}?v={1|2}` for the avatar
|
||||
- https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html#user-metadata for user data
|
||||
- `https://nc.url/avatar/{userid}/{size}?v={1|2}` for the avatar
|
||||
|
||||
#### Get the status
|
||||
|
||||
@ -870,8 +796,7 @@ DEPRECATED: This API is deprecated, use the Nextcloud APIs instead.
|
||||
* **Route**: /user
|
||||
* **Parameters**: none
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"userId": "john",
|
||||
"displayName": "John Doe",
|
||||
|
@ -23,7 +23,6 @@ You have to design your app with these things in mind!:
|
||||
* **Use a library to compare versions, ideally one that uses semantic versioning**
|
||||
|
||||
## Authentication & Basics
|
||||
|
||||
Because REST is stateless you have to send user and password each time you access the API. Therefore running Nextcloud **with SSL is highly recommended** otherwise **everyone in your network can log your credentials**.
|
||||
|
||||
The base URL for all calls is:
|
||||
@ -43,13 +42,11 @@ where $CREDENTIALS is:
|
||||
base64(USER:PASSWORD)
|
||||
|
||||
## How To Sync
|
||||
|
||||
This is a small overview over how you should sync your articles with the Nextcloud News app. For more fine-grained details about the API see further down.
|
||||
|
||||
All routes are given relative to the base API url (e.g.: <https://yournextcloud.com/index.php/apps/news/api/v1-3>)
|
||||
All routes are given relative to the base API url (e.g.: https://yournextcloud.com/index.php/apps/news/api/v1-3)
|
||||
|
||||
### Initial Sync
|
||||
|
||||
The intial sync happens, when a user adds a Nextcloud account in your app. In that case you should fetch all feeds, folders and unread or starred articles from the News app. Do not fetch all articles, not only because it syncs faster, but also because the user is primarily interested in unread articles. To fetch all unread and starred articles, you must call 4 routes:
|
||||
|
||||
* **unread articles**: GET /items?type=3&getRead=false&batchSize=-1
|
||||
@ -60,7 +57,6 @@ The intial sync happens, when a user adds a Nextcloud account in your app. In th
|
||||
The JSON response structures can be viewed further down.
|
||||
|
||||
### Syncing
|
||||
|
||||
When syncing, you want to push read/unread and starred/unstarred items to the server and receive new and updated items, feeds and folders. To do that, call the following routes:
|
||||
|
||||
* **Notify the News app of unread articles**: PUT /items/unread/multiple {"items": [1, 3, 5]}
|
||||
@ -71,6 +67,7 @@ When syncing, you want to push read/unread and starred/unstarred items to the se
|
||||
* **Get new feeds**: GET /feeds
|
||||
* **Get new items and modified items**: GET /items/updated?lastModified=12123123123&type=3
|
||||
|
||||
|
||||
## Accessing API from a web application
|
||||
|
||||
**News 1.401** implements CORS which allows web applications to access the API. **To access the API in a webapp you need to send the correct authorization header instead of simply putting auth data into the URL!**. An example request in jQuery would look like this:
|
||||
@ -94,9 +91,7 @@ $.ajax({
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
An example with AngularJS would look like this:
|
||||
|
||||
```js
|
||||
angular.module('YourApp', [])
|
||||
.config(['$httpProvider', '$provide', function ($httpProvider, $provide) {
|
||||
@ -134,17 +129,14 @@ angular.module('YourApp', [])
|
||||
```
|
||||
|
||||
## Input
|
||||
|
||||
In general the input parameters can be in the URL or request body, the App Framework doesnt differentiate between them.
|
||||
|
||||
So JSON in the request body like:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"id": 3
|
||||
}
|
||||
```
|
||||
|
||||
will be treated the same as
|
||||
|
||||
/?id=3
|
||||
@ -157,13 +149,11 @@ It is recommended though that you use the following convention:
|
||||
* **DELETE**: parameters as JSON in the request body
|
||||
|
||||
## Output
|
||||
|
||||
The output is JSON.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Folders
|
||||
|
||||
#### Get all folders
|
||||
|
||||
* **Status**: Implemented
|
||||
@ -171,8 +161,7 @@ The output is JSON.
|
||||
* **Route**: /folders
|
||||
* **Parameters**: none
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
@ -184,26 +173,22 @@ The output is JSON.
|
||||
```
|
||||
|
||||
#### Create a folder
|
||||
|
||||
Creates a new folder and returns a new folder object
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: POST
|
||||
* **Route**: /folders
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"name": "folder name"
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 409**: If the folder exists already
|
||||
* **HTTP 422**: If the folder name is invalid (for instance empty)
|
||||
* **HTTP 409**: If the folder exists already
|
||||
* **HTTP 422**: If the folder name is invalid (for instance empty)
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
@ -215,7 +200,6 @@ Creates a new folder and returns a new folder object
|
||||
```
|
||||
|
||||
#### Delete a folder
|
||||
|
||||
Deletes a folder with the id folderId and all the feeds it contains
|
||||
|
||||
* **Status**: Implemented
|
||||
@ -223,28 +207,25 @@ Deletes a folder with the id folderId and all the feeds it contains
|
||||
* **Route**: /folders/{folderId}
|
||||
* **Parameters**: none
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the folder does not exist
|
||||
* **HTTP 404**: If the folder does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Rename a folder
|
||||
|
||||
Only the name can be updated
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: PUT
|
||||
* **Route**: /folders/{folderId}
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"name": "folder name"
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 409**: If the folder name does already exist
|
||||
* **HTTP 404**: If the folder does not exist
|
||||
* **HTTP 422**: If the folder name is invalid (for instance empty)
|
||||
* **HTTP 409**: If the folder name does already exist
|
||||
* **HTTP 404**: If the folder does not exist
|
||||
* **HTTP 422**: If the folder name is invalid (for instance empty)
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark items of a folder as read
|
||||
@ -253,17 +234,15 @@ Only the name can be updated
|
||||
* **Method**: POST
|
||||
* **Route**: /folders/{folderId}/read
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
// mark all items read lower than equal that id
|
||||
// this is mean to prevent marking items as read which the client/user does not yet know of
|
||||
"newestItemId": 10
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
### Feeds
|
||||
@ -282,8 +261,7 @@ The following attributes are **not sanitized** meaning: including them in your w
|
||||
* **Route**: /feeds
|
||||
* **Parameters**: none
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"feeds": [
|
||||
{
|
||||
@ -312,27 +290,23 @@ The following attributes are **not sanitized** meaning: including them in your w
|
||||
```
|
||||
|
||||
#### Create a feed
|
||||
|
||||
Creates a new feed and returns the feed
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: POST
|
||||
* **Route**: /feeds
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"url": "http:\/\/www.cyanogenmod.org\/wp-content\/themes\/cyanogenmod\/images\/favicon.ico",
|
||||
"folderId": 81 // id of the parent folder, null for root
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 409**: If the feed exists already
|
||||
* **HTTP 422**: If the feed cant be read (most likely contains errors)
|
||||
* **HTTP 409**: If the feed exists already
|
||||
* **HTTP 422**: If the feed cant be read (most likely contains errors)
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"feeds": [
|
||||
{
|
||||
@ -353,7 +327,6 @@ Creates a new feed and returns the feed
|
||||
```
|
||||
|
||||
#### Delete a feed
|
||||
|
||||
Deletes a feed with the id feedId and all of its items
|
||||
|
||||
* **Status**: Implemented
|
||||
@ -361,7 +334,7 @@ Deletes a feed with the id feedId and all of its items
|
||||
* **Route**: /feeds/{feedId}
|
||||
* **Parameters**: none
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Move a feed to a different folder
|
||||
@ -370,15 +343,13 @@ Deletes a feed with the id feedId and all of its items
|
||||
* **Method**: POST
|
||||
* **Route**: /feeds/{feedId}/move
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"folderId": null // id of the parent folder, null for root
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Rename a feed
|
||||
@ -387,15 +358,13 @@ Deletes a feed with the id feedId and all of its items
|
||||
* **Method**: POST
|
||||
* **Route**: /feeds/{feedId}/rename
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"feedTitle": "New Title"
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark items of a feed as read
|
||||
@ -404,19 +373,18 @@ Deletes a feed with the id feedId and all of its items
|
||||
* **Method**: POST
|
||||
* **Route**: /feeds/{feedId}/read
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
// mark all items read lower than equal that id
|
||||
// this is mean to prevent marking items as read which the client/user does not yet know of
|
||||
"newestItemId": 10
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
|
||||
### Items
|
||||
|
||||
#### Sanitation
|
||||
@ -432,7 +400,6 @@ The following attributes are **not sanitized** meaning: including them in your w
|
||||
* **mediaDescription**
|
||||
|
||||
#### Types
|
||||
|
||||
| Name | Default | Types |
|
||||
|------------------|---------|--------------|
|
||||
| author | null | string\|null |
|
||||
@ -457,13 +424,11 @@ The following attributes are **not sanitized** meaning: including them in your w
|
||||
| url | | string\|null |
|
||||
|
||||
#### Get items
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: GET
|
||||
* **Route**: /items
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"batchSize": 10, // the number of items that should be returned, defaults to -1, new in 5.2.3: -1 returns all items
|
||||
"offset": 30, // only return older (lower than equal that id) items than the one with id 30
|
||||
@ -473,10 +438,8 @@ The following attributes are **not sanitized** meaning: including them in your w
|
||||
"oldestFirst": false // implemented in 3.002, if true it reverse the sort order
|
||||
}
|
||||
```
|
||||
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
@ -504,14 +467,12 @@ The following attributes are **not sanitized** meaning: including them in your w
|
||||
```
|
||||
|
||||
##### Example
|
||||
|
||||
Autopaging would work like this:
|
||||
|
||||
* Get the **first 20** items from a feed with **id 12**
|
||||
|
||||
**GET /items**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"batchSize": 20,
|
||||
"offset": 0,
|
||||
@ -525,7 +486,7 @@ The item with the lowest item id is 43.
|
||||
|
||||
* Get the next **20** items: **GET /items**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"batchSize": 20,
|
||||
"offset": 43,
|
||||
@ -535,16 +496,15 @@ The item with the lowest item id is 43.
|
||||
}
|
||||
```
|
||||
|
||||
#### Get updated items
|
||||
|
||||
#### Get updated items
|
||||
This is used to stay up to date.
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: GET
|
||||
* **Route**: /items/updated
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"lastModified": 123231, // returns only items with a lastModified timestamp >= than this one
|
||||
// this may also return already existing items whose read or starred status
|
||||
@ -553,10 +513,8 @@ This is used to stay up to date.
|
||||
"id": 12 // the id of the folder or feed, Use 0 for Starred and All
|
||||
}
|
||||
```
|
||||
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
@ -581,103 +539,87 @@ This is used to stay up to date.
|
||||
```
|
||||
|
||||
#### Mark an item as read
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: POST
|
||||
* **Route**: /items/{itemId}/read
|
||||
* **Parameters**: none
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark multiple items as read
|
||||
|
||||
* **Status**: Implemented in API 1.2
|
||||
* **Method**: POST
|
||||
* **Route**: /items/read/multiple
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"itemIds": [2, 3] // ids of the items
|
||||
}
|
||||
```
|
||||
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark an item as unread
|
||||
|
||||
* **Status**: Implemented
|
||||
* **Method**: POST
|
||||
* **Route**: /items/{itemId}/unread
|
||||
* **Parameters**: none
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark multiple items as unread
|
||||
|
||||
* **Status**: Implemented in API 1.2
|
||||
* **Method**: POST
|
||||
* **Route**: /items/unread/multiple
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"itemIds": [2, 3] // ids of the items
|
||||
}
|
||||
```
|
||||
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark an item as starred
|
||||
|
||||
* **Status**: Implemented in API 1.3
|
||||
* **Method**: POST
|
||||
* **Route**: /items/{itemId}/star
|
||||
* **Parameters**: none
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark multiple items as starred
|
||||
|
||||
* **Status**: Implemented in API 1.3
|
||||
* **Method**: POST
|
||||
* **Route**: /items/star/multiple
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"itemIds": [1, ...]
|
||||
}
|
||||
```
|
||||
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark an item as unstarred
|
||||
|
||||
* **Status**: Implemented in API 1.3
|
||||
* **Method**: POST
|
||||
* **Route**: /items/{itemId}/unstar
|
||||
* **Parameters**: none
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **HTTP 404**: If the item does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark multiple items as unstarred
|
||||
|
||||
* **Status**: Implemented in API 1.3
|
||||
* **Method**: POST
|
||||
* **Route**: /items/unstar/multiple
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"itemIds": [1, ...]
|
||||
}
|
||||
```
|
||||
|
||||
* **Returns**: nothing
|
||||
|
||||
#### Mark all items as read
|
||||
@ -686,19 +628,18 @@ This is used to stay up to date.
|
||||
* **Method**: POST
|
||||
* **Route**: /items/read
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
// mark all items read lower than equal that id
|
||||
// this is mean to prevent marking items as read which the client/user does not yet know of
|
||||
"newestItemId": 10
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **Returns**: nothing
|
||||
|
||||
|
||||
### Updater
|
||||
|
||||
To enable people to write their own update scripts instead of relying on the sequential built in web and system cron, API routes and console commands have been created.
|
||||
@ -713,7 +654,6 @@ Updating should be done in the following fashion:
|
||||
This [implementation in Python](https://github.com/nextcloud/news-updater) should give you a good idea how to design and run it.
|
||||
|
||||
#### Trigger cleanup before update
|
||||
|
||||
This is used to clean up the database. It deletes folders and feeds that are marked for deletion
|
||||
|
||||
* **Status**: Implemented in News 1.601
|
||||
@ -724,9 +664,7 @@ This is used to clean up the database. It deletes folders and feeds that are mar
|
||||
|
||||
**New in 8.1.0**: The console command for achieving the same result is:
|
||||
|
||||
```bash
|
||||
php -f nextcloud/occ news:updater:before-update
|
||||
```
|
||||
php -f nextcloud/occ news:updater:before-update
|
||||
|
||||
#### Get feed ids and usernames for all feeds
|
||||
|
||||
@ -736,8 +674,7 @@ php -f nextcloud/occ news:updater:before-update
|
||||
* **Route**: /feeds/all
|
||||
* **Parameters**: none
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"feeds": [
|
||||
{
|
||||
@ -750,9 +687,8 @@ php -f nextcloud/occ news:updater:before-update
|
||||
|
||||
**New in 8.1.0, Removed in 16.0.0**: The console command for achieving the same result is:
|
||||
|
||||
```bash
|
||||
php -f nextcloud/occ news:updater:all-feeds
|
||||
```
|
||||
php -f nextcloud/occ news:updater:all-feeds
|
||||
|
||||
|
||||
#### Trigger a feed update
|
||||
|
||||
@ -761,26 +697,21 @@ php -f nextcloud/occ news:updater:all-feeds
|
||||
* **Method**: GET
|
||||
* **Route**: /feeds/update
|
||||
* **Parameters**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"userId": "john",
|
||||
"feedId": 3
|
||||
}
|
||||
```
|
||||
|
||||
* **Return codes**:
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **HTTP 404**: If the feed does not exist
|
||||
* **Returns**: Nothing
|
||||
|
||||
**New in 8.1.0**: The console command for achieving the same result is:
|
||||
|
||||
```bash
|
||||
php -f nextcloud/occ news:updater:update-feed 3 john
|
||||
```
|
||||
php -f nextcloud/occ news:updater:update-feed 3 john
|
||||
|
||||
#### Trigger cleanup after update
|
||||
|
||||
This is used to clean up the database. It removes old read articles which are not starred
|
||||
|
||||
* **Status**: Implemented in News 1.601
|
||||
@ -791,9 +722,7 @@ This is used to clean up the database. It removes old read articles which are no
|
||||
|
||||
**New in 8.1.0**: The console command for achieving the same result is:
|
||||
|
||||
```bash
|
||||
php -f nextcloud/occ news:updater:after-update
|
||||
```
|
||||
php -f nextcloud/occ news:updater:after-update
|
||||
|
||||
### Version
|
||||
|
||||
@ -804,8 +733,7 @@ php -f nextcloud/occ news:updater:after-update
|
||||
* **Route**: /version
|
||||
* **Parameters**: none
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"version": "5.2.3"
|
||||
}
|
||||
@ -822,8 +750,7 @@ This API can be used to display warnings and errors in your client if the web ap
|
||||
* **Route**: /status
|
||||
* **Parameters**: none
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"version": "5.2.4",
|
||||
"warnings": {
|
||||
@ -849,9 +776,8 @@ If **incorrectDbCharset** is true you should display a warning that database cha
|
||||
This API can be used to retrieve metadata about the current user.
|
||||
|
||||
DEPRECATED: This API is deprecated, use the Nextcloud APIs instead.
|
||||
|
||||
* <https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html#user-metadata> for user data
|
||||
* `https://nc.url/avatar/{userid}/{size}?v={1|2}` for the avatar
|
||||
- https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html#user-metadata for user data
|
||||
- `https://nc.url/avatar/{userid}/{size}?v={1|2}` for the avatar
|
||||
|
||||
#### Get the status
|
||||
|
||||
@ -860,8 +786,7 @@ DEPRECATED: This API is deprecated, use the Nextcloud APIs instead.
|
||||
* **Route**: /user
|
||||
* **Parameters**: none
|
||||
* **Returns**:
|
||||
|
||||
```json
|
||||
```jsonc
|
||||
{
|
||||
"userId": "john",
|
||||
"displayName": "John Doe",
|
||||
|
@ -7,7 +7,6 @@ The **News app** offers a RESTful API which can be used to sync folders, feeds a
|
||||
In addition, an updater API is exposed which enables API users to run feed updates in parallel using a REST API or Nextcloud console API.
|
||||
|
||||
## Conventions
|
||||
|
||||
This document uses the following conventions:
|
||||
|
||||
* Object aliases as comments
|
||||
@ -23,7 +22,6 @@ There are two types of aliases:
|
||||
* Object arrays
|
||||
|
||||
**Objects**:
|
||||
|
||||
```js
|
||||
{
|
||||
"folder": { /* folder object */ },
|
||||
@ -33,7 +31,6 @@ There are two types of aliases:
|
||||
means that the folder attributes will be listed inside the **folder** object
|
||||
|
||||
**Object arrays**:
|
||||
|
||||
```js
|
||||
{
|
||||
"folders": [ /* array of folder objects */ ],
|
||||
@ -81,7 +78,6 @@ You have to design your app with these things in mind!:
|
||||
* **Use a library to compare versions**, ideally one that uses semantic versioning
|
||||
|
||||
## Request Format
|
||||
|
||||
The base URL for all calls is:
|
||||
|
||||
https://yournextcloud.com/index.php/apps/news/api/v2
|
||||
@ -114,11 +110,9 @@ The request body is either passed in the URL in case of a **GET** request (e.g.:
|
||||
**Note**: The current Etag implementation contains a unix timestamp in milliseconds. This is an implementation detail and you should not rely on it.
|
||||
|
||||
### API Level Detection
|
||||
|
||||
Check the [API level route](#api-level)
|
||||
|
||||
### Authentication
|
||||
|
||||
Because REST is stateless you have to re-send user and password each time you access the API. Therefore running Nextcloud **with SSL is highly recommended** otherwise **everyone in your network can log your credentials**.
|
||||
|
||||
Credentials are passed as an HTTP header using [HTTP basic auth](https://en.wikipedia.org/wiki/Basic_access_authentication#Client_side):
|
||||
@ -134,7 +128,6 @@ This authentication/authorization method will be the recommended default until c
|
||||
**Note**: Even if login cookies are sent back to your client, they will not be considered for authentication.
|
||||
|
||||
## Response Format
|
||||
|
||||
The status codes are not always provided by the News app itself, but might also be returned because of Nextcloud internal errors.
|
||||
|
||||
The following status codes can always be returned by Nextcloud:
|
||||
@ -174,7 +167,6 @@ The response body is a JSON structure that looks like this, which contains the a
|
||||
In case of an **4xx** or **5xx** error the request was not successful and has to be retried. For instance marking items as read locally and syncing should send the same request again the next time the user syncs in case an error occurred.
|
||||
|
||||
## Security Guidelines
|
||||
|
||||
Read the following notes carefully to prevent being subject to security exploits:
|
||||
|
||||
* You should always enforce SSL certificate verification and never offer a way to turn it off. Certificate verification is important to prevent MITM attacks which is especially important in the mobile world where users are almost always connected to untrusted networks. In case a user runs a self-signed certificate on his server ask him to either install his certificate on his device or direct him to one of the many ways to sign his certificate for free (most notably letsencrypt.com)
|
||||
@ -184,8 +176,7 @@ Read the following notes carefully to prevent being subject to security exploits
|
||||
* If you are building a client in JavaScript or are using a link with **target="blank"**, remember to set the **window.opener** property to **null** and/or add a **rel="noreferrer"** to your link to prevent your app from being [target by an XSS attack](https://medium.com/@jitbit/target-blank-the-most-underestimated-vulnerability-ever-96e328301f4c#.wf2ddytbh)
|
||||
|
||||
## Syncing
|
||||
|
||||
All routes are given relative to the base API url, e.g.: **/sync** becomes **<https://yourNextcloud.com/index.php/apps/news/api/v2/sync>**
|
||||
All routes are given relative to the base API url, e.g.: **/sync** becomes **https://yourNextcloud.com/index.php/apps/news/api/v2/sync**
|
||||
|
||||
There are two usecases for syncing:
|
||||
|
||||
@ -193,7 +184,6 @@ There are two usecases for syncing:
|
||||
* **Syncing local and remote changes**: the user has synced at least once and wants to submit and receive changes
|
||||
|
||||
### Initial Sync
|
||||
|
||||
The intial sync happens when a user adds an Nextcloud account in your app. In that case you want to download all folders, feeds and unread/starred items. To do this, make the following request:
|
||||
|
||||
* **Method**: GET
|
||||
@ -212,7 +202,6 @@ and the following HTTP headers:
|
||||
* **Etag**: A string containing a cache header, maximum size 64 ASCII characters, e.g. 6d82cbb050ddc7fa9cbb659014546e59
|
||||
|
||||
and the following request body:
|
||||
|
||||
```js
|
||||
{
|
||||
"folders": [ /* array of folder objects */ ],
|
||||
@ -227,8 +216,8 @@ and the following request body:
|
||||
* [Feeds](#feeds)
|
||||
* [Items](#items)
|
||||
|
||||
### Sync Local And Remote Changes
|
||||
|
||||
### Sync Local And Remote Changes
|
||||
After the initial sync the app has all folders, feeds and items. Now you want to push changes and retrieve updates from the server. To do this, make the following request:
|
||||
|
||||
* **Method**: POST
|
||||
@ -285,7 +274,6 @@ This also applies to folders and feeds, however the reduced folder and feed obje
|
||||
If you push a list of items to be marked read/starred, there can also be less items in the response than the ones which were initially sent. This means that the item was deleted by the cleanup job and should be removed from the client device.
|
||||
|
||||
For instance let's take a look at the following example. You are **POST**ing the following JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [{
|
||||
@ -326,9 +314,7 @@ The item with the **id** **7** is missing from the response. This means that it
|
||||
For folders and feeds all ids will be returned so you can compare the existing ids with your locally available feeds and folders and remove the difference.
|
||||
|
||||
## Folders
|
||||
|
||||
Folders are represented using the following data structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 3,
|
||||
@ -342,7 +328,6 @@ The attributes mean the following:
|
||||
* **name**: Abitrary long text, folder's name
|
||||
|
||||
### Deleting A Folder
|
||||
|
||||
To delete a folder, use the following request:
|
||||
|
||||
* **Method**: DELETE
|
||||
@ -371,7 +356,6 @@ In case of an HTTP 200, the deleted folder is returned in full in the response,
|
||||
**Note**: If you delete a folder locally, you should also delete all feeds whose **folderId** attribute matches the folder's **id** attribute and also delete all items whose **feedId** attribute matches the feeds' **id** attribute. This is done automatically on the server and will also be missing on the next request.
|
||||
|
||||
### Creating A Folder
|
||||
|
||||
To create a folder, use the following request:
|
||||
|
||||
* **Method**: POST
|
||||
@ -379,7 +363,6 @@ To create a folder, use the following request:
|
||||
* **Authentication**: [required](#authentication)
|
||||
|
||||
with the following request body:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Folder name"
|
||||
@ -403,7 +386,6 @@ In case of an HTTP 200, the created or already existing folder is returned in fu
|
||||
```
|
||||
|
||||
### Changing A Folder
|
||||
|
||||
The following attributes can be changed on the folder:
|
||||
|
||||
* **name**
|
||||
@ -417,7 +399,6 @@ To change any number of attributes on a folder, use the following request and pr
|
||||
* **Authentication**: [required](#authentication)
|
||||
|
||||
with the following request body:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "New folder name"
|
||||
@ -443,8 +424,8 @@ In case of an HTTP 200, the changed or already existing folder is returned in fu
|
||||
}
|
||||
```
|
||||
|
||||
## Feeds
|
||||
|
||||
## Feeds
|
||||
Feeds are represented using the following data structure:
|
||||
|
||||
```json
|
||||
@ -483,8 +464,8 @@ The attributes mean the following:
|
||||
* **1**: Error occured during feed update
|
||||
* **message**: Translated error message depending on the user's configured server locale
|
||||
|
||||
### Deleting A Feed
|
||||
|
||||
### Deleting A Feed
|
||||
To delete a feed, use the following request:
|
||||
|
||||
* **Method**: DELETE
|
||||
@ -493,6 +474,7 @@ To delete a feed, use the following request:
|
||||
* **{id}**: feed's id
|
||||
* **Authentication**: [required](#authentication)
|
||||
|
||||
|
||||
The following response is being returned:
|
||||
|
||||
Status codes:
|
||||
@ -501,6 +483,7 @@ Status codes:
|
||||
* **404**: Feed with given id was not found, no error object
|
||||
* Other Nextcloud errors, see [Response Format](#response-format)
|
||||
|
||||
|
||||
In case of an HTTP 200, the deleted feed is returned in full in the response, e.g.:
|
||||
|
||||
```js
|
||||
@ -514,15 +497,14 @@ In case of an HTTP 200, the deleted feed is returned in full in the response, e.
|
||||
**Note**: If you delete a feed locally, you should also delete all items whose **feedId** attribute matches the feeds' **id** attribute. This is done automatically on the server and will also be missing on the next request.
|
||||
|
||||
### Creating A feed
|
||||
|
||||
To create a feed, use the following request:
|
||||
|
||||
* **Method**: POST
|
||||
* **Route**: /feeds
|
||||
* **Authentication**: [required](#authentication)
|
||||
|
||||
with the following request body:
|
||||
|
||||
with the following request body:
|
||||
```json
|
||||
{
|
||||
"url": "https://feed.url.com",
|
||||
@ -536,7 +518,7 @@ with the following request body:
|
||||
}
|
||||
```
|
||||
|
||||
* **url**: Abitrary long text, the url needs to have the full schema e.g. <https://the-url.com>. In case the user omits the schema, prepending **https** is recommended
|
||||
* **url**: Abitrary long text, the url needs to have the full schema e.g. https://the-url.com. In case the user omits the schema, prepending **https** is recommended
|
||||
* **folderId**: 64bit Integer, the feed's folder or **0** in case no folder is specified
|
||||
* **name (optional)**: Abitrary long text, the feeds name or if not given taken from the RSS/Atom feed
|
||||
* **basicAuthUser (optional)**: Abitrary long text, if given basic auth headers are sent for the feed
|
||||
@ -545,6 +527,7 @@ with the following request body:
|
||||
* **isPinned (optional)**: See [Feeds](#feeds)
|
||||
* **fullTextEnabled (optional)**: See [Feeds](#feeds)
|
||||
|
||||
|
||||
The following response is being returned:
|
||||
|
||||
Status codes:
|
||||
@ -574,7 +557,6 @@ In case of an HTTP 200, the created feed is returned in full in the response, e.
|
||||
**Note**: Because the next sync would also pull in the added feed and items again, the added items will be omitted for saving bandwidth. This also means that after successfully creating a feed you will need to query the [sync route](#sync-local-and-remote-changes) again.
|
||||
|
||||
### Changing A Feed
|
||||
|
||||
To change a feed, use the following request:
|
||||
|
||||
* **Method**: PATCH
|
||||
@ -583,8 +565,8 @@ To change a feed, use the following request:
|
||||
* **{id}**: feed's id
|
||||
* **Authentication**: [required](#authentication)
|
||||
|
||||
with the following request body:
|
||||
|
||||
with the following request body:
|
||||
```json
|
||||
{
|
||||
"url": "https://feed.url.com",
|
||||
@ -604,9 +586,9 @@ All parameters are optional
|
||||
* **name (optional)**: Abitrary long text, the feeds name or if not given taken from the RSS/Atom feed
|
||||
* **basicAuthUser (optional)**: Abitrary long text, if given basic auth headers are sent for the feed
|
||||
* **basicAuthPassword (optional)**: Abitrary long text, if given basic auth headers are sent for the feed
|
||||
* **ordering (optional)**: See [feeds](#feeds)
|
||||
* **isPinned (optional)**: See [feeds](#feeds)
|
||||
* **fullTextEnabled (optional)**: See [feeds](#feeds)
|
||||
* **ordering (optional)**: See [feeds](#Feeds)
|
||||
* **isPinned (optional)**: See [feeds](#Feeds)
|
||||
* **fullTextEnabled (optional)**: See [feeds](#Feeds)
|
||||
* **folderId (optional)**: 64bit Integer, the feed's folder or **0** in case no folder is specified
|
||||
|
||||
The following response is being returned:
|
||||
@ -666,9 +648,7 @@ The attributes mean the following:
|
||||
* **contentHash**: 64 ASCII characters, used to determine if the item on the client is up to or out of date. The difference between the contentHash and the fingerprint attribute is that contentHash is always calculated from a stable set of attributes (title, author, url, enclosure, body) whereas the fingerprint is calculated from a set of attributes depending on the feed. The reason for this is that some feeds use different URLs for the same article so you would not want to include the URL as uniqueness criteria in that case. If the fingerprint was used for syncing however, an URL update would never reach the client.
|
||||
|
||||
### Full
|
||||
|
||||
A full item contains the full content:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 5,
|
||||
@ -691,9 +671,7 @@ A full item contains the full content:
|
||||
```
|
||||
|
||||
### Reduced
|
||||
|
||||
A reduced item only contains the item status:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 5,
|
||||
@ -703,7 +681,6 @@ A reduced item only contains the item status:
|
||||
```
|
||||
|
||||
## Updater
|
||||
|
||||
Instead of using the built in, slow cron updater you can use the parallel update API to update feeds. The API can be accessed through REST or Nextcloud console API.
|
||||
|
||||
The API should be used in the following way:
|
||||
@ -719,14 +696,11 @@ If the REST API is used, Authorization is required via Basic Auth and the user n
|
||||
If the Nextcloud console API is used, no authorization is required.
|
||||
|
||||
### Clean Up Before Update
|
||||
|
||||
This is used to clean up the database. It deletes folders and feeds that are marked for deletion.
|
||||
|
||||
**Console API**:
|
||||
|
||||
```bash
|
||||
php -f /path/to/nextcloud/occ news:updater:before-update
|
||||
```
|
||||
php -f /path/to/nextcloud/occ news:updater:before-update
|
||||
|
||||
**REST API**:
|
||||
|
||||
@ -735,14 +709,11 @@ php -f /path/to/nextcloud/occ news:updater:before-update
|
||||
* **Authentication**: [admin](#authentication)
|
||||
|
||||
### Get All Feeds And User Ids
|
||||
|
||||
This call returns pairs of feed ids and user ids.
|
||||
|
||||
**Console API**:
|
||||
|
||||
```bash
|
||||
php -f /path/to/nextcloud/occ news:updater:all-feeds
|
||||
```
|
||||
php -f /path/to/nextcloud/occ news:updater:all-feeds
|
||||
|
||||
**REST API**:
|
||||
|
||||
@ -750,6 +721,7 @@ php -f /path/to/nextcloud/occ news:updater:all-feeds
|
||||
* **Route**: /updater/all-feeds
|
||||
* **Authentication**: [admin](#authentication)
|
||||
|
||||
|
||||
Both APIs will return the following response body or terminal output:
|
||||
|
||||
```js
|
||||
@ -762,7 +734,6 @@ Both APIs will return the following response body or terminal output:
|
||||
```
|
||||
|
||||
### Update A User's Feed
|
||||
|
||||
After all feed ids and user ids are known, feeds can be updated in parallel.
|
||||
|
||||
**Console API**:
|
||||
@ -771,9 +742,8 @@ After all feed ids and user ids are known, feeds can be updated in parallel.
|
||||
* **{feedId}**: the feed's id
|
||||
* **{userId}**: the user's id
|
||||
|
||||
```bash
|
||||
php -f /path/to/nextcloud/occ news:updater:update-feed {feedId} {userId}
|
||||
```
|
||||
|
||||
php -f /path/to/nextcloud/occ news:updater:update-feed {feedId} {userId}
|
||||
|
||||
**REST API**:
|
||||
|
||||
@ -784,15 +754,13 @@ php -f /path/to/nextcloud/occ news:updater:update-feed {feedId} {userId}
|
||||
* **{userId}**: the user's id
|
||||
* **Authentication**: [admin](#authentication)
|
||||
|
||||
### Clean Up After Update
|
||||
|
||||
### Clean Up After Update
|
||||
This is used to clean up the database. It removes old read articles which are not starred.
|
||||
|
||||
**Console API**:
|
||||
|
||||
```bash
|
||||
php -f /path/to/nextcloud/occ news:updater:after-update
|
||||
```
|
||||
php -f /path/to/nextcloud/occ news:updater:after-update
|
||||
|
||||
**REST API**:
|
||||
|
||||
@ -801,13 +769,13 @@ php -f /path/to/nextcloud/occ news:updater:after-update
|
||||
* **Authentication**: [admin](#authentication)
|
||||
|
||||
## Meta Data
|
||||
|
||||
The retrieve meta data about the app, use the following request:
|
||||
|
||||
* **Method**: GET
|
||||
* **Route**: /
|
||||
* **Authentication**: [required](#authentication)
|
||||
|
||||
|
||||
The following response is being returned:
|
||||
|
||||
Status codes:
|
||||
@ -845,12 +813,12 @@ The attributes mean the following:
|
||||
* **data**: Abitrary long text, the user's image encoded as base64
|
||||
* **mime**: Abitrary long text, avatar mimetype
|
||||
|
||||
## API Level
|
||||
|
||||
## API Level
|
||||
To find out which API levels are supported, make a request to the following route:
|
||||
|
||||
* **Method**: GET
|
||||
* **Route**: <https://yournextcloud.com/index.php/apps/news/api>
|
||||
* **Route**: https://yournextcloud.com/index.php/apps/news/api
|
||||
* **Authentication**: none
|
||||
|
||||
The following response is being returned:
|
||||
@ -861,7 +829,6 @@ Status codes:
|
||||
* **404**: The user is either running a version prior to **8.8.0** or the News app is disabled or not installed.
|
||||
|
||||
In case of an HTTP 200, the supported API levels are returned as JSON, e.g.:
|
||||
|
||||
```json
|
||||
{
|
||||
"apiLevels": ["v1-2", "v2"]
|
||||
@ -873,7 +840,7 @@ In case of an HTTP 200, the supported API levels are returned as JSON, e.g.:
|
||||
To find out if a user is running an older News version than **8.8.0**, make a request to the following route:
|
||||
|
||||
* **Method**: GET
|
||||
* **Route**: <https://yournextcloud.com/index.php/apps/news/api/v1-2/version>
|
||||
* **Route**: https://yournextcloud.com/index.php/apps/news/api/v1-2/version
|
||||
* **Authentication**: [required](#authentication)
|
||||
|
||||
Status codes:
|
||||
|
@ -1,5 +1,4 @@
|
||||
# Developer
|
||||
|
||||
Welcome to the Nextcloud News App developer documentation.
|
||||
|
||||
News is open for contributions, if you plan to implement a new feature make sure to open a [discussion](https://github.com/nextcloud/news/discussions/new?category=Features). Describe the feature that you are planing and your first idea how to implement it.
|
||||
@ -8,7 +7,6 @@ This ensures that you don't start working on something which collides with the t
|
||||
For small fixes and improvements feel free to directly create a PR, the maintainers are happy to review your code.
|
||||
|
||||
## APIs
|
||||
|
||||
News offers an API that can be used by clients to synchronize with the server.
|
||||
There are two API declarations, so far only V1 has been fully implemented.
|
||||
Work on V2 has started with low priority.
|
||||
@ -18,55 +16,27 @@ Work on V2 has started with low priority.
|
||||
- [API-V2](api/api-v2.md)
|
||||
|
||||
## Coding Style Guidelines
|
||||
|
||||
The PHP code should all adhere to [PSR-2](https://www.php-fig.org/psr/psr-2/).
|
||||
*Note that this is a different codestyle than Nextcloud itself uses.*
|
||||
To test the codestyle you can run `make phpcs`.
|
||||
|
||||
The application Front End uses Vue 2.7 and the Nextcloud Libraries [Vue Components](https://github.com/nextcloud-libraries/nextcloud-vue) for building the Application running inside your Nextcloud instance. For linting these files, we are using eslint, see the [config file](https://github.com/nextcloud/news/blob/master/.eslintrc.js). We also have Unit Tests for the components that run with Jest, please ensure these pass when adding new features/fixing bugs.
|
||||
For linting JavaScript, a [jshint file](https://github.com/nextcloud/news/blob/master/js/.jshintrc) is used that is run before compiling the JavaScript.
|
||||
|
||||
## General Developer setup
|
||||
|
||||
Check the Nextcloud [documentation](https://docs.nextcloud.com/server/latest/developer_manual/getting_started/devenv.html) to learn how to setup a developer environment, alternatively to a proper web server you can also use the [builtin php server](https://www.php.net/manual/en/features.commandline.webserver.php) on demand, it is enough for development purposes.
|
||||
|
||||
When your setup is running, clone the news repository in the `apps/` directory inside the server.
|
||||
|
||||
Change into the news directory and run `make` to build the app, you will need php, composer, node, npm and maybe more.
|
||||
Change into the news directory and run make to build the app, you will need php, composer, node, npm and maybe more.
|
||||
|
||||
Now you can basically use the news app and test any changes you make on your local development environment. Check out the `appinfo/routes.php` file and `lib/controller/` directory for details on API controllers. Or check out `package.json` for npm scripts and the `src/` directory for the front end Vue Application.
|
||||
|
||||
## Alternative Developer setup
|
||||
With [PR 2670](https://github.com/nextcloud/news/pull/2670) new options to create a development environment were added.
|
||||
|
||||
See the README in the docker directory.
|
||||
|
||||
There is also a nix-shell config and zellij layout prepared.
|
||||
|
||||
If you have issues with the setup create a [new discussion](https://github.com/nextcloud/news/discussions).
|
||||
|
||||
### Frontend Tips/Organization
|
||||
|
||||
- We use the Nextcloud Vue component library for most of the form controls and navigation
|
||||
- Vuex is used for state management, this is similar to Redux and has Actions/Mutations and Getters
|
||||
- We are using the Nextcloud Webpack Vue configuration and have enabled Typescript support and importing in the Vue components
|
||||
- We use ESLint and StyleLint for ensuring correct formatting of the Scripts and HTML
|
||||
|
||||
## Testing
|
||||
|
||||
Please make sure to run all tests before submitting any pull requests.
|
||||
|
||||
### Frontend Unit Tests
|
||||
|
||||
Frontend unit tests are written with Jest and can be run with `npm run test`.
|
||||
|
||||
### API and CLI Integration Tests
|
||||
Now you can basically use the news app and test your changes.
|
||||
|
||||
## Running Integration tests locally
|
||||
We use [bats](https://bats-core.readthedocs.io/en/stable/) to run integration tests against the API and the cli.
|
||||
|
||||
Check how to install bats on your system in the [official documentation](https://bats-core.readthedocs.io/en/stable/installation.html).
|
||||
|
||||
You also need to pull the submodules of the news repo.
|
||||
|
||||
```bash
|
||||
git submodules update --init
|
||||
```
|
||||
|
123
docs/faq.md
Normal file
123
docs/faq.md
Normal file
@ -0,0 +1,123 @@
|
||||
# FAQ
|
||||
|
||||
## My browser shows a mixed content warning (Connection is Not Secure)
|
||||
If you are serving your Nextcloud over HTTPS your browser will very likely warn you with a yellow warnings sign about your connection not being secure.
|
||||
|
||||
Chrome will show no green HTTPS lock sign, Firefox will show you the following image
|
||||
![Mixed Passive Content](https://ffp4g1ylyit3jdyti1hqcvtb-wpengine.netdna-ssl.com/security/files/2015/10/mixed-passive-click1-600x221.png)
|
||||
|
||||
Note that this warning **is not red and won't block the page like the following images** which signal **a serious issue**:
|
||||
|
||||
![Untrusted Cert](https://www.inmotionhosting.com/support/images/stories/website/errors/ssl/chrome-self-signed-ssl-warning.png)
|
||||
![Mixed Active Content](https://www.howtogeek.com/wp-content/uploads/2014/02/650x367xchrome-mixed-content-https-problem.png.pagespeed.gp+jp+jw+pj+js+rj+rp+rw+ri+cp+md.ic.r_lQiZiq38.png)
|
||||
|
||||
### What is the cause of the (yellow) error message
|
||||
|
||||
This warning is caused by [mixed passive content](https://developer.mozilla.org/en/docs/Security/MixedContent) and means that your page loads passive resources from non HTTPS resources, such as:
|
||||
|
||||
* Images
|
||||
* Video/Audio
|
||||
|
||||
This allows a possible attacker to perform a MITM (man-in-the-middle) attack by serving you different images or audio/video.
|
||||
|
||||
### Why doesn't the News app fix it
|
||||
|
||||
The News app fully prevents mixed **active** content by only allowing HTTPS iframes from known locations; other possible mixed active content elements such as <script\> are stripped from the feed. Because images and audio/video are an integral part of a feed, we can not simply strip them.
|
||||
|
||||
Since an attacker can not execute code in contrast to mixed active content, but only replace images/audio/video in your feed reader, this is **not considered to be a security issue**. If, for whatever reason (e.g. feed which would allow fishing), this is a security problem for you, contact the specific feed provider and ask him to serve his feed content over HTTPS.
|
||||
|
||||
### Why don't you simply use an HTTPS image/audio/video proxy
|
||||
|
||||
For the same reason that we can't fix non HTTPS websites: It does not fix the underlying issue, but only silences it. If you are using an image HTTPS proxy, an attacker can simply attack your image proxy since the proxy fetches insecure content. **Even worse**: if your image proxy serves these images from the same domain as your Nextcloud installation, you [are vulnerable to XSS via SVG images](https://www.owasp.org/images/0/03/Mario_Heiderich_OWASP_Sweden_The_image_that_called_me.pdf). In addition, people feel safe when essentially they are not.
|
||||
|
||||
Since most people don't understand mixed content and don't have two domains and a standalone server for the image proxy, it is very likely they will choose to host it under the same domain.
|
||||
|
||||
Because we care about our users' security and don't want to hide security warnings, we won't fix (aka silence) this issue.
|
||||
|
||||
The only fix for this issue is that feed providers serve their content over HTTPS.
|
||||
|
||||
## I am getting: Exception: Some\\Class does not exist errors in my nextcloud.log
|
||||
This is very often caused by missing or old files, e.g. by failing to upload all the News app files or errors during installation. Before you report a bug, please recheck if all files from the archive are in place and accessible.
|
||||
|
||||
## Feeds not updated
|
||||
Feeds can be updated using Nextcloud's system cron or an [external updater](https://nextcloud.github.io/news/clients/#update-clients) via the API
|
||||
**The feed update is not run in Webcron and AJAX cron mode!**
|
||||
|
||||
### System Cron
|
||||
* Check if you are using the system cron (Cron) setting on the admin page. AJAX and Web cron will not update feeds
|
||||
* Check if the cronjob exists with **crontab -u www-data -e** (replace www-data with your httpd user)
|
||||
* Check the file permissions of the **cron.php** file and if **www-data** (or whatever your httpd user is called like) can read and execute that script
|
||||
* Check if you can execute the cron with **sudo -u www-data php -f nextcloud/cron.php** (replace www-data with your httpd user)
|
||||
* Check your **data/nextcloud.log** for errors
|
||||
* Check if the cronjob is ever executed by placing an **error_log('updating');** in the [background job file](https://github.com/nextcloud/news/blob/master/lib/Service/UpdaterService.php#L55). If the cronjob runs, there should be an updating log statement in your httpd log.
|
||||
* If there is no **updating** statement in your logs check if your cronjob is executed by executing a different script
|
||||
* Check if the **oc_jobs** table has a **reserved_at** entry with a value other than 0. If it does for whatever reason, set it to 0. You can check this by executing:
|
||||
|
||||
```sql
|
||||
SELECT * from oc_jobs WHERE class LIKE '%News%' ORDER BY id;
|
||||
```
|
||||
|
||||
You will get two rows where column `class`will be `OCA\News\Cron\Updater` and `OCA\News\Cron\UpdaterJob`.
|
||||
|
||||
!!! info
|
||||
|
||||
In newer versions of News (21.x.x) the old job OCA\News\Cron\Updater was removed from the DB.
|
||||
|
||||
Reset the `reserved_at` by executing
|
||||
|
||||
```sql
|
||||
UPDATE oc_jobs SET reserved_at = 0 WHERE id = <id from above SELECT statement>;
|
||||
```
|
||||
|
||||
* If your cron works fine, but Nextcloud's cronjobs are never executed, file a bug in [server](https://github.com/nextcloud/server/)
|
||||
|
||||
### External Updater
|
||||
* Check if your configuration is set to **not** use the system cron.
|
||||
* Consult the documentation of the updater
|
||||
* Check your **data/nextcloud.log** for errors
|
||||
|
||||
## Adding feeds that use self-signed certificates
|
||||
If you want to add a feed that uses a self-signed certificate that is not signed by a trusted CA the request will fail with "SSL certificate is invalid". A common solution is to turn off the certificate verification **which is wrong** and **makes your installation vulnerable to MITM attacks**. Therefore **turning off certificate verification is not supported**.
|
||||
|
||||
|
||||
If you have control over the feed in question, consider signing your certificate for free on one of the following providers:
|
||||
|
||||
* [Let's Encrypt](https://letsencrypt.org/)
|
||||
* [ZeroSSL](https://zerossl.com/)
|
||||
|
||||
If you do not have control over the chosen feed, you should [download the certificate from the feed's website](https://superuser.com/questions/97201/how-to-save-a-remote-server-ssl-certificate-locally-as-a-file) and [add it to your server's trusted certificates](https://www.onlinesmartketer.com/2009/06/23/curl-adding-installing-trusting-new-self-signed-certificate/). The exact procedure however may vary depending on your distribution.
|
||||
|
||||
## Is There An Subscription URL To Easily Subscribe To Feeds
|
||||
|
||||
By appending **?subscribe_to=SOME_URL** to your News app URL, you can launch the News app with a pre-filled URL, e.g.:
|
||||
|
||||
https://yourdomain.com/nextcloud/index.php/apps/news?subscribe_to=https://github.com/nextcloud/news/releases
|
||||
|
||||
## Database table grows too big
|
||||
|
||||
If your users have subscribed to some high-volume feeds where a lot of items remain unread,
|
||||
this can lead to an oversized news table over time. As a consequence, the database upgrade of the news app can take several hours, during which Nextcloud cannot be used.
|
||||
|
||||
By default, Nextcloud News purges old news items above a certain threshold each time it fetches new news items. The maximum number of items per feed
|
||||
that should be kept during the purging can be defined through the “Maximum read count per feed” setting in the admin UI or the `autoPurgeCount`
|
||||
value in the config.
|
||||
Additionally you may enable the option to also purge unread items `purgeUnread`. This is useful if your users have large amounts of unread items.
|
||||
Starred items are always exempt from purging.
|
||||
|
||||
The command `occ news:updater:after-update [--purge-unread] [<purge-count>]` can be used to manually purge old news items across the instance. With
|
||||
the `--purge-unread` option, unread items are also purged (starred items are still exempt). If `purge-count` is not specified, the configured
|
||||
`autoPurgeCount` is used.
|
||||
|
||||
The purge count only applies to the items that are purged. For example, when purging a feed that has 100 unread items, 100 starred read
|
||||
items and 100 unstarred read items, using a `purge-count` of 50 would keep all unread and starred items and the latest 50 read items. Using
|
||||
a `purge-count` of 50 along with `--purge-unread` would keep the all starred items plus the latest 50 from the set of unread and read items.
|
||||
|
||||
## Missing 4-byte support SQLSTATE[22007]: Invalid datetime format: 1366 Incorrect string value: ...
|
||||
|
||||
This is likely caused by your feed using emojis in the feed title or text.
|
||||
|
||||
The DB is then not able to store the feed and runs into strange decoding errors.
|
||||
|
||||
You need to convert your DB to support 4 bytes, check the [Nextcloud documentation](https://docs.nextcloud.com/server/stable/admin_manual/configuration_database/mysql_4byte_support.html).
|
||||
|
||||
References [#1165](https://github.com/nextcloud/news/issues/1165) [#526](https://github.com/nextcloud/news/issues/526)
|
@ -1,9 +1,8 @@
|
||||
# Custom CSS
|
||||
|
||||
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:
|
||||
|
||||
* Take the URL from the \<link> attribute (e.g.: \<link><https://www.google.de/path?my=query> \</link>)
|
||||
* Extract the Domain from the URL (e.g.: <www.google.de>)
|
||||
* Take the URL from the \<link> attribute (e.g.: \<link>https://www.google.de/path?my=query \</link>)
|
||||
* Extract the Domain from the URL (e.g.: www.google.de)
|
||||
* Strip the leading **www.** (e.g.: google.de)
|
||||
* Replace all . with - (e.g.: google-de)
|
||||
* Prepend **custom-** (e.g.: custom-google-de)
|
||||
|
@ -1,30 +0,0 @@
|
||||
# Integrations
|
||||
|
||||
## Is There An Subscription URL To Easily Subscribe To Feeds?
|
||||
|
||||
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.:
|
||||
|
||||
Ex.
|
||||
|
||||
https://yourdomain.com/nextcloud/index.php/apps/news?subscribe_to=https://github.com/nextcloud/news/releases
|
||||
|
||||
### Known Working Integrations
|
||||
|
||||
#### Chrome / Edge
|
||||
|
||||
1. Install [RSS Subscription Extension (by Google)](https://chrome.google.com/webstore/detail/rss-subscription-extensio/nlbjncdgjeocebhnmkbbbdekmmmcbfjd) extension
|
||||
1. Open the extension's options menu
|
||||
1. Click `Add..`
|
||||
1. In the *Description* field, enter a description for the RSS reader entry. 'NextCloud News' is a reasonable name.
|
||||
1. Enter `https://<NEXTCLOUD_BASE_PATH>/index.php/apps/news?subscribe_to=%s` replacing <NEXTCLOUD_BASE_PATH> with the base URL path to your NextCloud instance.
|
||||
* Domain based example: <https://cloud.mydomain.com/index.php/apps/news?subscribe_to=%s>
|
||||
* Domain+subpath based example: <https://cloud.mydomain.com/nextcloud/index.php/apps/news?subscribe_to=%s>
|
||||
|
||||
#### Firefox
|
||||
|
||||
1. Install Firefox Add-on Extension [Awesome RSS](https://addons.mozilla.org/en-US/firefox/addon/awesome-rss/)
|
||||
1. Open the `Preferences` for the extension
|
||||
1. In the 'Subscribe using' section, select the `NextCloud` radio button
|
||||
1. In the field link field, enter the base NextCloud URL.
|
||||
* Domain based example: <https://cloud.mydomain.com/>
|
||||
* Domain+subpath based example: <https://cloud.mydomain.com/nextcloud/>
|
@ -9,91 +9,52 @@ There are essentially three different use cases for plugins:
|
||||
* Dropping in additional CSS or JavaScript
|
||||
|
||||
## The Basics
|
||||
|
||||
Whatever plugin you want to create, you first need to create a basic structure. A plugin is basically just an app, so you can take advantage of the full [Nextcloud app API](https://docs.nextcloud.org/server/latest/developer_manual/app/index.html). [Take a look at the developer docs](https://docs.nextcloud.com/server/latest/developer_manual/app_development/index.html) or [dig into the tutorial](https://docs.nextcloud.com/server/latest/developer_manual/app_development/tutorial.html).
|
||||
Whatever plugin you want to create, you first need to create a basic structure. A plugin is basically just an app, so you can take advantage of the full [Nextcloud app API](https://docs.nextcloud.org/server/latest/developer_manual/app/index.html). If you want you can [take a look at the developer docs](https://docs.nextcloud.org/server/latest/developer_manual/app/index.html) or [dig into the tutorial](https://docs.nextcloud.org/server/latest/developer_manual/app/tutorial.html).
|
||||
|
||||
However, if you just want to start slow, the full process is described below.
|
||||
|
||||
First create a skeleton app using the [web interface](https://apps.nextcloud.com/developer/apps/generate)
|
||||
|
||||
The application name affects the name and namespace of your plugin and only one app can exist using the same name. Choose wisely. This will become the directory name in the Nextcloud `apps/` directory
|
||||
First create the following directories and files:
|
||||
|
||||
* **newsplugin/**
|
||||
* **appinfo/**
|
||||
* **app.php**
|
||||
* **info.xml**
|
||||
|
||||
**Note**: You must license your app under the [AGPL 3 or later](https://www.gnu.org/licenses/agpl-3.0.en.html) to comply with the News app's license. Don't forget to add the license as plain text file if you want to distribute your app!
|
||||
The first folder name affects the name and namespace of your plugin and only one app can exist using the same name. Choose wisely.
|
||||
|
||||
Then we want to make sure that our code is only run if the News app is enabled. To do that put the following PHP code into the **newsplugin/lib/AppInfo/Application.php** file:
|
||||
|
||||
```php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\NewsBookmarkPlugin\AppInfo;
|
||||
|
||||
use OCP\AppFramework\App;
|
||||
use OCP\Util;
|
||||
use OCP\App as Test;
|
||||
|
||||
class Application extends App {
|
||||
public const APP_ID = 'newsbookmarkplugin';
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(self::APP_ID);
|
||||
|
||||
|
||||
// your code here
|
||||
}
|
||||
}
|
||||
First let's add some meta ata about our app. Open the **newsplugin/appinfo/info.xml** and add the following contents:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<info>
|
||||
<id>newsplugin</id>
|
||||
<name>Example News Plugin</name>
|
||||
<description>This plugin allows you to share articles via Twitter</description>
|
||||
<licence>AGPL</licence>
|
||||
<author>Your Name Here</author>
|
||||
<version>0.0.1</version>
|
||||
<dependencies>
|
||||
<nextcloud min-version="10"/>
|
||||
<owncloud min-version="9.1"/>
|
||||
<php min-version="5.6"/>
|
||||
</dependencies>
|
||||
</info>
|
||||
```
|
||||
|
||||
## Server-Side Plugin
|
||||
**Note**: You must license your app under the [AGPL 3 or later](https://www.gnu.org/licenses/agpl-3.0.en.html) to comply with the News app's license. Don't forget to add the license as plain text file if you want to distribute your app!
|
||||
|
||||
A Server-Side plugin is a plugin that uses the same infrastructure as the News app for its own purposes. An example would be a plugin that makes the starred entries of a user available via an interface or a bookmark app that also shows starred articles as bookmarks.
|
||||
|
||||
It's very easy to interface with the News app. Because all Classes are registered in the **news/app/application.php** it takes almost no effort to use the same infrastructure.
|
||||
|
||||
**Note**: Keep in mind that these classes are essentially private which means they might break if the News app changes. There is no real public API so use at your own risk ;)
|
||||
|
||||
Since you don't want to extend the app but use its resources, its advised that you don't inherit from the **Application** class but rather include it in your own container in **newsplugin/appinfo/application.php**:
|
||||
Then we want to make sure that our code is only run if the News app is enabled. To do that put the following PHP code into the **newsplugin/appinfo/app.php** file:
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace OCA\NewsPlugin\AppInfo;
|
||||
use OCP\App;
|
||||
|
||||
use OCP\AppFramework\App;
|
||||
use OCA\News\AppInfo\Application as News;
|
||||
|
||||
class Application extends App {
|
||||
|
||||
public function __construct (array $urlParams=[]) {
|
||||
parent::__construct('newsplugin', $urlParams);
|
||||
|
||||
$container = $this->getContainer();
|
||||
|
||||
$container->registerService('NewsContainer', function($c) {
|
||||
$app = new News();
|
||||
return $app->getContainer();
|
||||
});
|
||||
|
||||
$container->registerService(OCA\News\Service\FeedService::class, function($c) {
|
||||
// use the feed service from the news app, you can use all
|
||||
// defined classes but its recommended that you stick to the
|
||||
// mapper and service classes since they are less likely to change
|
||||
return $c->query('NewsContainer')->query(OCA\News\Service\FeedService::class);
|
||||
});
|
||||
}
|
||||
|
||||
if (App::isEnabled('news')) {
|
||||
// your code here
|
||||
}
|
||||
```
|
||||
|
||||
Using automatic container assembly you can then use it from your code by simply adding the type to your constructors.
|
||||
|
||||
# TODO: Update the following
|
||||
|
||||
If your plugin integrates with another Nextcloud app, make sure to also require it be installed. If you depend on the Bookmarks app for instance use:
|
||||
|
||||
```php
|
||||
@ -131,7 +92,6 @@ This will tell the News app to load the following files after its own JavaScript
|
||||
* **newspluing/css/style.css**
|
||||
|
||||
### Adding Basic JavaScript Functionality
|
||||
|
||||
You can basically add any JavaScript you want. If you want to add a new article action, this is a bit more complicated because it's hard to hook into Angular from the outside. Therefore, the News app provides an API which makes creating additional article actions a breeze.
|
||||
|
||||
A basic article action looks like this:
|
||||
@ -198,3 +158,53 @@ Then open the **newspluing/css/style.css** file and add the following CSS:
|
||||
```
|
||||
|
||||
Reload the News app and click the three dots menu, sit back and enjoy :)
|
||||
|
||||
## Server-Side Plugin
|
||||
A Server-Side plugin is a plugin that uses the same infrastructure as the News app for its own purposes. An example would be a plugin that makes the starred entries of a user available via an interface or a bookmark app that also shows starred articles as bookmarks.
|
||||
|
||||
It's very easy to interface with the News app. Because all Classes are registered in the **news/app/application.php** it takes almost no effort to use the same infrastructure.
|
||||
|
||||
**Note**: Keep in mind that these classes are essentially private which means they might break if the News app changes. There is no real public API so use at your own risk ;)
|
||||
|
||||
Since you don't want to extend the app but use its resources, its advised that you don't inherit from the **Application** class but rather include it in your own container in **newsplugin/appinfo/application.php**:
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace OCA\NewsPlugin\AppInfo;
|
||||
|
||||
use OCP\AppFramework\App;
|
||||
use OCA\News\AppInfo\Application as News;
|
||||
|
||||
class Application extends App {
|
||||
|
||||
public function __construct (array $urlParams=[]) {
|
||||
parent::__construct('newsplugin', $urlParams);
|
||||
|
||||
$container = $this->getContainer();
|
||||
|
||||
$container->registerService('NewsContainer', function($c) {
|
||||
$app = new News();
|
||||
return $app->getContainer();
|
||||
});
|
||||
|
||||
$container->registerService(OCA\News\Service\FeedService::class, function($c) {
|
||||
// use the feed service from the news app, you can use all
|
||||
// defined classes but its recommended that you stick to the
|
||||
// mapper and service classes since they are less likely to change
|
||||
return $c->query('NewsContainer')->query(OCA\News\Service\FeedService::class);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Using automatic container assembly you can then use it from your code by simply adding the type to your constructors.
|
||||
|
||||
|
||||
### Examples
|
||||
Client-side plugins:
|
||||
|
||||
* [Mail Share](https://github.com/cosenal/mailsharenewsplugin): Client-side plugin to share articles by email
|
||||
Server-side plugins:
|
||||
|
||||
* [Feed Central](https://github.com/Raydiation/feedcentral): Publish your feeds as RSS
|
||||
|
@ -1,5 +1,4 @@
|
||||
# Themes
|
||||
|
||||
Nextcloud News can look different with the following themes:
|
||||
|
||||
* [Nextcloud News Themes](https://github.com/cwmke/nextcloud-news-themes)
|
||||
* [Nextcloud News Themes](https://github.com/cwmke/nextcloud-news-themes)
|
@ -2,8 +2,8 @@
|
||||
|
||||
## Dependencies
|
||||
* 64bit OS (starting with News 16.0.0)
|
||||
* PHP >= 8.0
|
||||
* Nextcloud (current stable version)
|
||||
* PHP >= 7.3
|
||||
* Nextcloud 22
|
||||
* libxml >= 2.7.8
|
||||
|
||||
You also need some PHP extensions:
|
||||
@ -21,25 +21,16 @@ You also need some PHP extensions:
|
||||
* MySQL >= 8.0
|
||||
* SQLite (discouraged)
|
||||
|
||||
Also see the [Nextcloud documentation](https://docs.nextcloud.com/server/stable/admin_manual/configuration_database/linux_database_configuration.html?highlight=database). Oracle is currently not supported by News.
|
||||
Also see the [Nextcloud documentation](https://docs.nextcloud.com/server/stable/admin_manual/configuration_database/linux_database_configuration.html?highlight=database). Oracle is currently not supported by news.
|
||||
|
||||
## Performance Notices
|
||||
* Use MySQL/MariaDB or PostgreSQL for better database performance
|
||||
* Use the [updater script to thread and speed up the update](https://github.com/nextcloud/news-updater)
|
||||
|
||||
## Cache
|
||||
News and it's libraries require a writeable temporary directory used as cache. The base directory depends on your system.
|
||||
You can [configure a custom directory](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/config_sample_php_parameters.html?highlight=temp#tempdirectory) if you want.
|
||||
|
||||
In most cases the base directory will be `/tmp`. News will create a folder `news-$instanceID` the [instance ID is defined by Nextcloud](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/config_sample_php_parameters.html?highlight=temp#instanceid).
|
||||
|
||||
Inside that folder a subfolder `cache` is created, inside this cache folder news and libraries will try to create cache directories for caching images, html and more.
|
||||
|
||||
You need to ensure that your web-server user can write to that directory.
|
||||
|
||||
## Before you install/update the News app
|
||||
Before you install the app do the following:
|
||||
|
||||
* Check that your **nextcloud/data/** directory is owned by your web server user and that it is write/readable
|
||||
* Check that your installation fulfills the [requirements listed above](#dependencies)
|
||||
* [Set up Nextcloud Background Jobs](https://docs.nextcloud.org/server/latest/admin_manual/configuration_server/background_jobs_configuration.html#cron) to enable feed updates.
|
||||
|
||||
@ -117,15 +108,15 @@ If you changed the temporary directory for Nextcloud you need to check on your o
|
||||
|
||||
Careful, this next part is only intended for admins, that know what they are doing.
|
||||
|
||||
To remove the tables from the DB we drop the tables of News.
|
||||
Your installation might have a different prefix than `oc_` but it is the default in most installations.
|
||||
To remove the tables from the DB we drop the tables of news.
|
||||
Your installation might have a different prefix then `oc_` but it is the default in most installations.
|
||||
Connect to your DB and execute the commands. Don't forget to switch to the right database.
|
||||
For example in mysql: `use nextcloud;`
|
||||
|
||||
```sql
|
||||
DROP TABLE oc_news_items;
|
||||
DROP TABLE oc_news_feeds;
|
||||
DROP TABLE oc_news_folders;
|
||||
DROP TABLE oc_news_feeds;
|
||||
DROP TABLE oc_news_items;
|
||||
```
|
||||
|
||||
Then we remove the traces in the migrations table.
|
||||
@ -149,4 +140,4 @@ DELETE FROM oc_jobs WHERE class='OCA\\News\\Cron\\Updater';
|
||||
DELETE FROM oc_jobs WHERE argument='["OCA\\\\News\\\\Cron\\\\Updater","run"]';
|
||||
```
|
||||
|
||||
Now nothing is left from News in your Nextcloud installation.
|
||||
Now nothing is left from news in your nextcloud installation.
|
@ -1,168 +0,0 @@
|
||||
# Troubleshooting
|
||||
|
||||
This is a brief list of common issues that come up with NextCloud News.
|
||||
|
||||
## My browser shows a mixed content warning (Connection is Not Secure)
|
||||
|
||||
If you are serving your Nextcloud over HTTPS your browser will very likely warn you with a yellow warnings sign about your connection not being secure.
|
||||
|
||||
* Chrome will show no green HTTPS lock sign.
|
||||
* Firefox will show you the following image Mixed Passive Content ![Mixed Content Type](https://ffp4g1ylyit3jdyti1hqcvtb-wpengine.netdna-ssl.com/security/files/2015/10/mixed-passive-click1-600x221.png)
|
||||
|
||||
Note that this warning **is not red and won't block the page like the following images** which signal a serious issue:
|
||||
|
||||
* Chrome ![Chrome error](https://www.inmotionhosting.com/support/images/stories/website/errors/ssl/chrome-self-signed-ssl-warning.png)
|
||||
* Firefox ![Firefox error](https://www.howtogeek.com/wp-content/uploads/2014/02/650x367xchrome-mixed-content-https-problem.png.pagespeed.gp+jp+jw+pj+js+rj+rp+rw+ri+cp+md.ic.r_lQiZiq38.png)
|
||||
|
||||
### What is the cause of the (yellow) error message?
|
||||
|
||||
This warning is caused by mixed passive content and means that your page loads passive resources from non HTTPS resources, such as:
|
||||
|
||||
* Images
|
||||
* Video/Audio
|
||||
* Some Ads
|
||||
|
||||
This allows a possible attacker to perform a MITM (man-in-the-middle) attack by serving you different images or audio/video.
|
||||
|
||||
### Why doesn't the News app fix it?
|
||||
|
||||
The News app fully prevents mixed **active** content by only allowing HTTPS iframes from known locations; other possible mixed active content elements such as <script\> are stripped from the feed. Because images and audio/video are an integral part of a feed, we can not simply strip them.
|
||||
|
||||
Since an attacker can not execute code in contrast to mixed active content, but only replace images/audio/video in your feed reader, this is **not considered to be a security issue**. If, for whatever reason (e.g. feed which would allow fishing), this is a security problem for you, contact the specific feed provider and ask him to serve his feed content over HTTPS.
|
||||
|
||||
### Why don't you simply use an HTTPS image/audio/video proxy?
|
||||
|
||||
For the same reason that we can't fix non HTTPS websites: It does not fix the underlying issue, but only silences it. If you are using an image HTTPS proxy, an attacker can simply attack your image proxy since the proxy fetches insecure content. **Even worse**: if your image proxy serves these images from the same domain as your Nextcloud installation, you are [vulnerable to XSS via SVG images](https://www.owasp.org/images/0/03/Mario_Heiderich_OWASP_Sweden_The_image_that_called_me.pdf). In addition, people feel safe when essentially they are not.
|
||||
|
||||
Since most people don't understand mixed content and don't have two domains and a standalone server for the image proxy, it is very likely they will choose to host it under the same domain.
|
||||
|
||||
Because we care about our users' security and don't want to hide security warnings, we won't fix (aka silence) this issue.
|
||||
|
||||
The only fix for this issue is that feed providers serve their content over HTTPS.
|
||||
|
||||
## I am getting: Exception: Some\Class does not exist errors in my nextcloud.log
|
||||
|
||||
This is very often caused by missing or old files, e.g. by failing to upload all the News app files or errors during installation. Before you report a bug, please recheck if all files from the archive are in place and accessible.
|
||||
|
||||
## Feeds not updated
|
||||
|
||||
Feeds can be updated using Nextcloud's system cron or an external updater via the API. **The feed update is not run in Webcron and AJAX cron mode!**
|
||||
|
||||
### Validating Using System Cron
|
||||
|
||||
!!! info
|
||||
|
||||
This requires Nextcloud 26 or newer and News 24.0.0 or newer.
|
||||
|
||||
Follow this checklist:
|
||||
|
||||
* Check admin settings of Nextcloud, was the last cron execution ok.
|
||||
* Check the logs for errors.
|
||||
* Does your [cache configuration](install.md#cache) work?
|
||||
* Check the News admin settings, system cron is used to update news.
|
||||
* You should see a info card at the top, which will tell you when the last job execution was.
|
||||
* If the card is red it is very likely that the update job is stuck.
|
||||
* If it is green then maybe only some feeds are failing to update, check the Nextcloud logs.
|
||||
|
||||
If you believe the job is stuck you can reset it. For further steps you need to use occ.
|
||||
|
||||
You can check again the status of the job.
|
||||
(replace `www-data` with your httpd user)
|
||||
|
||||
```bash
|
||||
sudo -u www-data php ./occ news:updater:job
|
||||
Checking update Status
|
||||
Last Execution was 2023-03-20 12:20:03 UTC
|
||||
```
|
||||
|
||||
The same check that is done in the News admin settings can be done using occ too.
|
||||
Adding the `--check-elapsed` option displays the time elapsed since the last execution,
|
||||
and if it's considered too long ago, a message will be displayed, and the command returns
|
||||
with exit code 2. This can be used in scripts to send an alert for example.
|
||||
|
||||
```console
|
||||
$ sudo -u www-data php ./occ news:updater:job --check-elapsed
|
||||
Checking update Status
|
||||
Last Execution was 2023-03-20 12:20:03 UTC
|
||||
8 hours, 21 minutes, 20 seconds ago
|
||||
Something is wrong with the news cronjob, execution delay exceeded the configured interval.
|
||||
```
|
||||
|
||||
If you think the job is stuck you can reset it, this may lead to issues if the job is currently running!
|
||||
|
||||
```bash
|
||||
sudo -u www-data php ./occ news:updater:job --reset
|
||||
Checking update Status
|
||||
Last Execution was 2023-03-20 12:20:03 UTC
|
||||
Attempting to reset the job.
|
||||
Done, job should execute on next schedule.
|
||||
```
|
||||
|
||||
The output of the command should have changed.
|
||||
|
||||
```bash
|
||||
sudo -u www-data php ./occ news:updater:job
|
||||
Checking update Status
|
||||
Last Execution was 1970-01-01 00:00:00 UTC
|
||||
```
|
||||
|
||||
After some time has passed the timestamp should be close to the current time.
|
||||
|
||||
If this did not help, check the logs and open a issue or discussion on GitHub.
|
||||
|
||||
#### Outdated Steps
|
||||
|
||||
Follow these steps if you are running an older version of News and Nextcloud.
|
||||
|
||||
* Check if you are using the system cron (Cron) setting on the admin page. AJAX and Web cron will not update feeds
|
||||
* Check if the cronjob exists with `crontab -u www-data -e` (replace www-data with your httpd user)
|
||||
* Check the file permissions of the cron.php file and if www-data (or whatever your httpd user is called like) can read and execute that script
|
||||
* Check if you can execute the cron with `sudo -u www-data php -f nextcloud/cron.php` (replace www-data with your httpd user)
|
||||
* Check your `data/nextcloud.log` for errors
|
||||
* Check if the cronjob is ever executed by placing an `error_log('updating');` in the [background job file](https://github.com/nextcloud/news/blob/master/lib/Service/UpdaterService.php#L55). If the cronjob runs, there should be an updating log statement in your httpd log.
|
||||
* If there is no updating statement in your logs check if your cronjob is executed by executing a different script
|
||||
* Check if the oc_jobs table has a reserved_at entry with a value other than 0. If it does for whatever reason, set it to 0. You can check this by executing:
|
||||
|
||||
```sql
|
||||
SELECT * from oc_jobs WHERE class LIKE '%News%' ORDER BY id;
|
||||
```
|
||||
|
||||
You will get two rows where column class will be `OCA\News\Cron\Updater` and `OCA\News\Cron\UpdaterJob`.
|
||||
|
||||
!!! info
|
||||
|
||||
In newer versions of News (21.x.x) the old job OCA\News\Cron\Updater was removed from the DB.
|
||||
|
||||
Reset the reserved_at by executing
|
||||
|
||||
```sql
|
||||
UPDATE oc_jobs SET reserved_at = 0 WHERE id = <id from above SELECT statement>;
|
||||
```
|
||||
|
||||
If your cron works fine, but Nextcloud's cronjobs are never executed, file a bug in [server](https://github.com/nextcloud/server/)
|
||||
|
||||
### Using External Updater
|
||||
|
||||
* Check if your configuration is set to not use the system cron.
|
||||
* Consult the documentation of the updater
|
||||
* Check your data/nextcloud.log for errors
|
||||
|
||||
## Database table grows too big
|
||||
|
||||
If your users have subscribed to some high-volume feeds where a lot of items remain unread, this can lead to an oversized news table over time. As a consequence, the database upgrade of the news app can take several hours, during which Nextcloud cannot be used.
|
||||
|
||||
By default, Nextcloud News purges old news items above a certain threshold each time it fetches new news items. The maximum number of items per feed that should be kept during the purging can be defined through the “Maximum read count per feed” setting in the admin UI or the `autoPurgeCount` value in the config. Additionally you may enable the option to also purge unread items `purgeUnread`. This is useful if your users have large amounts of unread items. Starred items are always exempt from purging.
|
||||
|
||||
The command `occ news:updater:after-update [--purge-unread] [<purge-count>]` can be used to manually purge old news items across the instance. With the `--purge-unread option`, unread items are also purged (starred items are still exempt). If `purge-count` is not specified, the configured `autoPurgeCount` is used.
|
||||
|
||||
The purge count only applies to the items that are purged. For example, when purging a feed that has 100 unread items, 100 starred read items and 100 unstarred read items, using a purge-count of 50 would keep all unread and starred items and the latest 50 read items. Using a `purge-count` of 50 along with `--purge-unread` would keep the all starred items plus the latest 50 from the set of unread and read items.
|
||||
|
||||
## Missing 4-byte support SQLSTATE[22007]: Invalid datetime format: 1366 Incorrect string value:
|
||||
|
||||
This is likely caused by your feed using emojis in the feed title or text.
|
||||
|
||||
The DB is then not able to store the feed and runs into strange decoding errors.
|
||||
|
||||
You need to convert your DB to support 4 bytes, check the [Nextcloud documentation](https://docs.nextcloud.com/server/stable/admin_manual/configuration_database/mysql_4byte_support.html).
|
||||
|
||||
References [#1165](https://github.com/nextcloud/news/issues/1165) [#526](https://github.com/nextcloud/news/issues/526)
|
@ -1,7 +1,9 @@
|
||||
# JavaScript Development
|
||||
JavaScript is built and minified using webpack.
|
||||
JavaScript is built and minified using gulp.
|
||||
|
||||
Therefore you need to install **Node.js 6+ and npm**.
|
||||
Therefore you need to install **Node.js 6+ and npm**. Then use npm to install **gulp-cli**:
|
||||
|
||||
sudo npm -g install gulp-cli
|
||||
|
||||
Then install the local dependencies by running:
|
||||
|
||||
@ -12,20 +14,16 @@ The following tasks are available:
|
||||
|
||||
* **Build the JavaScript**:
|
||||
|
||||
npm run build
|
||||
|
||||
* **Build the JavaScript in Dev Mode**:
|
||||
|
||||
npm run build
|
||||
gulp
|
||||
|
||||
* **Watch for changes and build JavaScript**:
|
||||
|
||||
npm run watch
|
||||
gulp watch
|
||||
|
||||
* **Run JavaScript unit tests**:
|
||||
|
||||
npm run karma
|
||||
gulp karma
|
||||
|
||||
* **Watch for changes and run JavaScript unit tests**:
|
||||
|
||||
npm run watch-karma
|
||||
gulp watch-karma
|
187
js/controller/ShareController.js
Normal file
187
js/controller/ShareController.js
Normal file
@ -0,0 +1,187 @@
|
||||
/**
|
||||
* Nextcloud - News
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Marco Nassabain <marco.nassabain@hotmail.com>
|
||||
* @author Nicolas Wendling <nicolas.wendling1011@gmail.com>
|
||||
* @author Jimmy Huynh <natorisaki@gmail.com>
|
||||
* @author Aurélien David <dav.aurelien@gmail.com>
|
||||
*/
|
||||
app.controller('ShareController', function (ShareResource, Loading) {
|
||||
'use strict';
|
||||
|
||||
/** Array containing users to share an item with */
|
||||
this.userList = [];
|
||||
|
||||
/** Value used to check if the received response is the most recent one */
|
||||
this.searchQuery = '';
|
||||
|
||||
/** True if the most recent request failed */
|
||||
this.searchUsersFailed = false;
|
||||
|
||||
/**
|
||||
* @param search Username search query
|
||||
*
|
||||
* Retrieve users matching search query using OC
|
||||
*/
|
||||
this.searchUsers = function(search) {
|
||||
this.searchUsersFailed = false;
|
||||
if (!search || search === '') {
|
||||
this.userList = [];
|
||||
return;
|
||||
}
|
||||
|
||||
Loading.setLoading('user', true);
|
||||
this.searchQuery = search;
|
||||
|
||||
ShareResource.getUsers(search)
|
||||
.then((response) => {
|
||||
if (this.searchQuery === search) {
|
||||
this.userList = response.ocs.data.exact.users;
|
||||
this.userList = this.userList.concat(response.ocs.data.users);
|
||||
Loading.setLoading('user', false);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (this.searchQuery === search) {
|
||||
this.userList = [];
|
||||
this.searchUsersFailed = true;
|
||||
Loading.setLoading('user', false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** Dictionary mapping articles to users they're shared with */
|
||||
this.usersSharedArticles = [];
|
||||
|
||||
/**
|
||||
* Test whether an item is shared with a user
|
||||
*
|
||||
* @param itemId ID of the item being shared
|
||||
* @param userId User ID of the recipient
|
||||
* @returns boolean
|
||||
*/
|
||||
this.itemIsSharedWithUser = function(itemId, userId) {
|
||||
let item = this.usersSharedArticles.find(i => i.id === itemId);
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
let user = item.users.find(u => u.id === userId);
|
||||
if (!user || !user.status) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inserts an item share action into the dictionary
|
||||
*
|
||||
* @param itemId ID of the item being shared
|
||||
* @param userId User ID of the recipient
|
||||
* @param status boolean indicating if the share was successful
|
||||
*/
|
||||
this.addItemShareWithUser = function(itemId, userId, status) {
|
||||
let item = this.usersSharedArticles.find(i => i.id === itemId);
|
||||
if (!item) {
|
||||
item = {
|
||||
id: itemId,
|
||||
users: []
|
||||
};
|
||||
this.usersSharedArticles.push(item);
|
||||
}
|
||||
let user = item.users.find(u => u.id === userId);
|
||||
if (!user) {
|
||||
user = {
|
||||
id: userId,
|
||||
status: status
|
||||
};
|
||||
item.users.push(user);
|
||||
}
|
||||
user.status = status;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param itemId ID of the item to be shared
|
||||
* @param userId ID of the recipient
|
||||
*
|
||||
* Call the /share route with the appropriate params to share an item.
|
||||
* Fills this.usersSharedArticles to avoid re-sharing the same article
|
||||
* with the same user multiple times.
|
||||
*/
|
||||
this.shareItem = function(itemId, userId) {
|
||||
if (this.itemIsSharedWithUser(itemId, userId)) {
|
||||
return;
|
||||
}
|
||||
Loading.setLoading(userId, true);
|
||||
|
||||
ShareResource.shareItem(itemId, userId)
|
||||
.then(() => {
|
||||
this.addItemShareWithUser(itemId, userId, true);
|
||||
Loading.setLoading(userId, false);
|
||||
})
|
||||
.catch(() => {
|
||||
this.addItemShareWithUser(itemId, userId, false);
|
||||
Loading.setLoading(userId, false);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates whether the share action is in progress
|
||||
*
|
||||
* @param userId User ID of the recipient
|
||||
* @returns boolean
|
||||
*/
|
||||
this.isLoading = function(userId) {
|
||||
return Loading.isLoading(userId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates whether the share actions matches the given status
|
||||
*
|
||||
* @param itemId ID of the item being shared
|
||||
* @param userId User ID of the recipient
|
||||
* @param status true (successful) / false (failed)
|
||||
* @returns boolean
|
||||
*/
|
||||
this.isStatus = function(itemId, userId, status) {
|
||||
let item = this.usersSharedArticles.find(i => i.id === itemId);
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
let user = item.users.find(u => u.id === userId);
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
return user.status === status;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the social sharing app for the given media is active
|
||||
*
|
||||
* @param media
|
||||
* @returns boolean
|
||||
*/
|
||||
this.isSocialAppEnabled = function(media) {
|
||||
let app = 'socialsharing_' + media;
|
||||
return app in OC.appswebroots;
|
||||
};
|
||||
|
||||
this.isAnySocialAppEnabled = function() {
|
||||
let media = ['facebook', 'twitter', 'email'];
|
||||
return media.some(m => this.isSocialAppEnabled(m));
|
||||
};
|
||||
|
||||
this.getFacebookUrl = function(url){
|
||||
return 'https://www.facebook.com/sharer/sharer.php?u='+url;
|
||||
};
|
||||
|
||||
this.getTwitterUrl = function(url){
|
||||
return 'https://twitter.com/intent/tweet?url='+url;
|
||||
};
|
||||
|
||||
this.getEmailUrl = function(url, object, body){
|
||||
return encodeURI('mailto:?subject=' + object + '&body=' + body + ' ' + url);
|
||||
};
|
||||
});
|
31
js/directive/ClickOutside.js
Normal file
31
js/directive/ClickOutside.js
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Nextcloud - News
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Marco Nassabain <marco.nassabain@hotmail.com>
|
||||
* @author Nicolas Wendling <nicolas.wendling1011@gmail.com>
|
||||
* @author Jimmy Huynh <natorisaki@gmail.com>
|
||||
* @author Aurélien David <dav.aurelien@gmail.com>
|
||||
*/
|
||||
app.directive('clickOutside', function ($document) {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
clickOutside: '&'
|
||||
},
|
||||
link: function (scope, el) {
|
||||
|
||||
$document.on('click', function (e) {
|
||||
if (el !== e.target && !el[0].contains(e.target)) {
|
||||
scope.$apply(function () {
|
||||
scope.$eval(scope.clickOutside);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
58
js/filter/RelativeTimestamp.js
Normal file
58
js/filter/RelativeTimestamp.js
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Nextcloud - News
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Alec Kojaev <alec@kojaev.name>
|
||||
* @copyright Alec Kojaev 2021
|
||||
*/
|
||||
app.filter('relativeTimestamp', ['SettingsResource', function (SettingsResource) {
|
||||
'use strict';
|
||||
|
||||
const languageCode = SettingsResource.get('language');
|
||||
const relFormat = Intl.RelativeTimeFormat ?
|
||||
new Intl.RelativeTimeFormat(languageCode, { numeric: 'auto' }) : null;
|
||||
const maxRelDistance = 90*86400*1000;
|
||||
const relLimits = [
|
||||
[ 7*86400*1000, 'week' ],
|
||||
[ 86400*1000, 'day' ],
|
||||
[ 3600*1000, 'hour' ],
|
||||
[ 60*1000, 'minute' ],
|
||||
[ 1*1000, 'second' ]
|
||||
];
|
||||
const absLimits = [
|
||||
[ 7*86400*1000, { hour: '2-digit', minute: '2-digit', dayPeriod: 'narrow',
|
||||
year: 'numeric', month: 'short', day: 'numeric' } ],
|
||||
[ 43200*1000, { hour: '2-digit', minute: '2-digit', dayPeriod: 'narrow',
|
||||
weekday: 'long' } ],
|
||||
[ 0, { hour: '2-digit', minute: '2-digit', dayPeriod: 'narrow' } ]
|
||||
];
|
||||
|
||||
return function (timestamp) {
|
||||
if (!Number.isFinite(timestamp)) {
|
||||
return timestamp;
|
||||
}
|
||||
const ts = new Date(timestamp);
|
||||
const dist = ts.getTime() - Date.now();
|
||||
const absDist = Math.abs(dist);
|
||||
if (relFormat && absDist < maxRelDistance) {
|
||||
for (const [ scale, unit ] of relLimits) {
|
||||
const value = Math.trunc(dist / scale);
|
||||
if (value !== 0) {
|
||||
return relFormat.format(value, unit);
|
||||
}
|
||||
}
|
||||
// We arrive here only if distance from now is less than 1 second
|
||||
return relFormat.format(0, 'second');
|
||||
} else {
|
||||
for (const [ limit, options ] of absLimits) {
|
||||
if (absDist >= limit) {
|
||||
return ts.toLocaleString(languageCode, options);
|
||||
}
|
||||
}
|
||||
// We shouldn't be here
|
||||
return ts.toLocaleString(languageCode, absLimits[absLimits.length - 1][1]);
|
||||
}
|
||||
};
|
||||
}]);
|
79
js/gulpfile.js
Normal file
79
js/gulpfile.js
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Nextcloud - News
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Bernhard Posselt <dev@bernhard-posselt.com>
|
||||
* @copyright Bernhard Posselt 2012, 2014
|
||||
*/
|
||||
|
||||
/*jslint node: true */
|
||||
'use strict';
|
||||
|
||||
const gulp = require('gulp'),
|
||||
ngAnnotate = require('gulp-ng-annotate'),
|
||||
terser = require('gulp-terser'),
|
||||
jshint = require('gulp-jshint'),
|
||||
KarmaServer = require('karma').Server,
|
||||
concat = require('gulp-concat'),
|
||||
sourcemaps = require('gulp-sourcemaps');
|
||||
|
||||
// Configuration
|
||||
const buildTarget = 'app.min.js';
|
||||
const karmaConfig = __dirname + '/karma.conf.js';
|
||||
const destinationFolder = __dirname + '/build/';
|
||||
const sources = [
|
||||
'node_modules/angular/angular.min.js',
|
||||
'node_modules/angular-animate/angular-animate.min.js',
|
||||
'node_modules/angular-route/angular-route.min.js',
|
||||
'node_modules/angular-sanitize/angular-sanitize.min.js',
|
||||
'node_modules/masonry-layout/dist/masonry.pkgd.min.js',
|
||||
'app/App.js', 'app/Config.js', 'app/Run.js',
|
||||
'controller/**/*.js',
|
||||
'filter/**/*.js',
|
||||
'service/**/*.js',
|
||||
'gui/**/*.js',
|
||||
'plugin/**/*.js',
|
||||
'utility/**/*.js',
|
||||
'directive/**/*.js'
|
||||
];
|
||||
const testSources = ['tests/**/*.js'];
|
||||
const watchSources = sources.concat(testSources).concat(['*.js', '!news-admin-settings.js']);
|
||||
const lintSources = watchSources;
|
||||
|
||||
// tasks
|
||||
gulp.task('lint', () => {
|
||||
return gulp.src(lintSources)
|
||||
.pipe(jshint())
|
||||
.pipe(jshint.reporter('default'))
|
||||
.pipe(jshint.reporter('fail'));
|
||||
});
|
||||
|
||||
gulp.task('default', gulp.series('lint', () => {
|
||||
return gulp.src(sources)
|
||||
.pipe(ngAnnotate())
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat(buildTarget))
|
||||
.pipe(terser())
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest(destinationFolder));
|
||||
}));
|
||||
|
||||
gulp.task('watch', () => {
|
||||
gulp.watch(watchSources, ['default']);
|
||||
});
|
||||
|
||||
gulp.task('karma', (done) => {
|
||||
new KarmaServer({
|
||||
configFile: karmaConfig,
|
||||
singleRun: true
|
||||
}, done).start();
|
||||
});
|
||||
|
||||
gulp.task('watch-karma', (done) => {
|
||||
new KarmaServer({
|
||||
configFile: karmaConfig,
|
||||
autoWatch: true
|
||||
}, done).start();
|
||||
});
|
12942
js/package-lock.json
generated
Normal file
12942
js/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
63
js/package.json
Normal file
63
js/package.json
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "nextcloud-news",
|
||||
"description": "An RSS/Atom feed reader",
|
||||
"main": "build/app.min.js",
|
||||
"scripts": {
|
||||
"test": "node node_modules/gulp-cli/bin/gulp.js karma",
|
||||
"prebuild": "npm ci",
|
||||
"build": "node node_modules/gulp-cli/bin/gulp.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nextcloud/news"
|
||||
},
|
||||
"keywords": [
|
||||
"rss",
|
||||
"atom",
|
||||
"feed",
|
||||
"reader",
|
||||
"nextcloud",
|
||||
"app"
|
||||
],
|
||||
"author": "Benjamin Brahmer",
|
||||
"license": "AGPL-3.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextcloud/news/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"resolutions": {
|
||||
"natives": "1.1.3"
|
||||
},
|
||||
"private": true,
|
||||
"homepage": "https://github.com/nextcloud/news",
|
||||
"devDependencies": {
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-cli": "^2.3.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-jshint": "^2.1.0",
|
||||
"gulp-ng-annotate": "^2.1.0",
|
||||
"gulp-sourcemaps": "^3.0.0",
|
||||
"gulp-terser": "^2.1.0",
|
||||
"jasmine-core": "^4.6.0",
|
||||
"jquery": "^3.6.4",
|
||||
"jshint": "^2.13.6",
|
||||
"karma": "^6.4.1",
|
||||
"karma-chrome-launcher": "^3.2.0",
|
||||
"karma-coverage": "^2.2.0",
|
||||
"karma-firefox-launcher": "^2.1.2",
|
||||
"karma-jasmine": "^5.1.0",
|
||||
"minimatch": "^9.0.0",
|
||||
"natives": "^1.1.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"angular": "^1.8.3",
|
||||
"angular-animate": "^1.8.3",
|
||||
"angular-mocks": "^1.8.3",
|
||||
"angular-route": "^1.8.3",
|
||||
"angular-sanitize": "^1.8.3",
|
||||
"debug": "^4.3.4",
|
||||
"masonry-layout": "^4.2.2"
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user