Merge branch 'master' into feature/778-custom-order

This commit is contained in:
Marcel Klehr 2020-07-23 16:39:13 +02:00
commit e440212183
49 changed files with 3794 additions and 2048 deletions

View File

@ -25,8 +25,9 @@ jobs:
# do not stop on another job's failure
fail-fast: false
matrix:
floccus-ref: ['master', 'develop']
node-versions: [13.x]
server-versions: ['18']
server-versions: ['19']
floccus-adapter:
- nextcloud-folders
test-name:
@ -36,17 +37,17 @@ jobs:
- benchmark parallel-root
browsers: ['chrome']
include:
- server-versions: 18
- server-versions: 19
floccus-adapter: nextcloud-folders
test-name: benchmark standard-root
browsers: chrome
- server-versions: 18
- server-versions: 19
floccus-adapter: nextcloud-folders
test-name: benchmark parallel-root
browsers: chrome
name: ${{matrix.floccus-adapter}}:${{ matrix.test-name}}
name: floccus@${{matrix.floccus-ref}} ${{matrix.floccus-adapter}}:${{ matrix.test-name}} ${{matrix.browsers}}
services:
hub:
@ -70,7 +71,7 @@ jobs:
MYSQL_HOST: mysql
NEXTCLOUD_TRUSTED_DOMAINS: nextcloud
volumes:
- apps:/home/runner/work/floccus/floccus/apps
- /home/runner/work/bookmarks/bookmarks/apps:/var/www/html/custom_apps
options: --name nextcloud
mysql:
image: mariadb:latest
@ -82,24 +83,37 @@ jobs:
uses: actions/checkout@v2
with:
repository: marcelklehr/floccus
ref: master
ref: ${{matrix.floccus-ref}}
path: floccus
- name: Checkout bookmarks app
uses: actions/checkout@v2
with:
path: apps/${{ env.APP_NAME }}
path: ${{ env.APP_NAME }}
if: matrix.floccus-adapter == 'nextcloud-folders'
- name: Enable bookmarks app
shell: bash
run: |
cd ${{ env.APP_NAME }}
composer install
if: matrix.floccus-adapter == 'nextcloud-folders'
- name: Enable bookmarks app
shell: bash
run: |
sudo cp -R ${{ env.APP_NAME }} apps/
NEXT_WAIT_TIME=0
until [ $NEXT_WAIT_TIME -eq 25 ] || docker exec --user www-data nextcloud php occ app:enable ${{ env.APP_NAME }}; do
sleep $(( NEXT_WAIT_TIME++ ))
done
[ $NEXT_WAIT_TIME -lt 25 ]
if: matrix.floccus-adapter != 'fake'
if: matrix.floccus-adapter == 'nextcloud-folders'
- name: List apps
shell: bash
run: |
docker exec --user www-data nextcloud php occ app:list
- name: Enable APCu
run: |
@ -111,18 +125,6 @@ jobs:
with:
node-version: ${{ matrix.node-versions }}
- name: Cache node modules
uses: actions/cache@v1
env:
cache-name: cache-node-modules
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Install dependencies & build
working-directory: floccus
run: |

View File

@ -79,6 +79,7 @@ jobs:
- name: Set up Nextcloud and install app
if: ${{ matrix.databases != 'pgsql'}}
run: |
sleep 25
mkdir data
./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$MYSQL_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password
./occ app:enable -vvv -f ${{ env.APP_NAME }}
@ -87,6 +88,7 @@ jobs:
- name: Set up Nextcloud and install app
if: ${{ matrix.databases == 'pgsql'}}
run: |
sleep 25
mkdir data
./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$PGSQL_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password
./occ app:enable -vvv -f ${{ env.APP_NAME }}

View File

@ -4,7 +4,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [unreleased]
## [3.3.0] - 2020-07-23
### New
- Implement Activity app integration
### Fixed
- Add repair step for duplicate shared folders
- Fix untagged search (on postgres)
- UI: Fix rename Tag
- Build: Don't ship source maps
- Repair steps: Add debug output
- Bookmarks: Additionally always sort by ID to make ordering stable
- Fix deletion of bookmarks that are not in tree
- Update dependencies
## [3.2.5] - 2020-07-17
### Fixed
- UI: Open URLs in new tabs
- UI: Fix infinite scroll
- Update documentation of API responses
- Fix changing bookmark's folders
- Fix tags API
## [3.2.4] - 2020-06-29
### Fixed
- API: Fix PUT bookmark requests
## [3.2.2] - 2020-06-28
### New
- In grid view the entire bookmark behaves like a link
@ -15,6 +44,8 @@ E.g. in case of a browser sync as source.
- Horizontal breadcrumb text alignment
- Checkbox size in grid view
- Menu toggle position
- Deletion of tags
- API: Don't send folder IDs that the client cannot access
## [3.2.1] - 2020-06-16
@ -276,7 +307,11 @@ Supported are NC 15 and 16, provided you are using PHP v7.1 and have gmp, intl a
- FIX folder collapse css
- FIX: Speed up findBookmarks SQL query
[3.2.1]: https://github.com/nextcloud/bookmarks/compare/v3.1.0...v3.2.1
[3.3.0]: https://github.com/nextcloud/bookmarks/compare/v3.2.5...v3.3.0
[3.2.5]: https://github.com/nextcloud/bookmarks/compare/v3.2.4...v3.2.5
[3.2.4]: https://github.com/nextcloud/bookmarks/compare/v3.2.2...v3.2.4
[3.2.2]: https://github.com/nextcloud/bookmarks/compare/v3.2.1...v3.2.2
[3.2.1]: https://github.com/nextcloud/bookmarks/compare/v3.2.0...v3.2.1
[3.2.0]: https://github.com/nextcloud/bookmarks/compare/v3.1.1...v3.2.0
[3.1.1]: https://github.com/nextcloud/bookmarks/compare/v3.1.0...v3.1.1
[3.1.0]: https://github.com/nextcloud/bookmarks/compare/v3.0.13...v3.1.0

View File

@ -7,7 +7,7 @@ source_dir=$(build_dir)/source
sign_dir=$(build_dir)/sign
package_name=$(app_name)
cert_dir=$(HOME)/.nextcloud/certificates
version+=3.2.1
version+=3.3.0
all: dev-setup build-js-production test test-php
@ -60,25 +60,24 @@ clean-dev:
appstore:
mkdir -p $(sign_dir)
rsync -a \
--exclude=/build \
--exclude=/docs \
--exclude=/translationfiles \
--exclude=/.tx \
--exclude=/tests \
--exclude=/screenshots \
--exclude=/.git \
--exclude=/.github \
--exclude=/l10n/l10n.pl \
--exclude=/CONTRIBUTING.md \
--exclude=/issue_template.md \
--exclude=/README.md \
--exclude=/.gitattributes \
--exclude=/.gitignore \
--exclude=/.scrutinizer.yml \
--exclude=/.travis.yml \
--exclude=/Makefile \
--exclude=/node_modules \
--include=/js \
--include=/vendor \
--include=/CHANGELOG.md \
--include=/README.md \
--include=/composer.json \
--include=/composer.lock \
--include=/vendor \
--include=/tests \
--include=/templates \
--include=/package.json \
--include=/package-lock.json \
--include=/src \
--include=/lib \
--include=/l10n \
--include=/img \
--include=/appinfo \
--exclude=**/*.map \
--exclude=/* \
$(project_dir)/ $(sign_dir)/$(app_name)
tar -czf $(build_dir)/$(app_name)-$(version).tar.gz \
-C $(sign_dir) $(app_name)

View File

@ -63,7 +63,7 @@ npm run build
- [QOwnNotes](https://www.qownnotes.org/) - Plain-text file markdown note taking desktop application (no sync, just importing bookmarks)
### iOS
- [Nextbookmark](https://gitlab.com/altepizza/nextbookmark) - A minimal client for iOS
- [Nextbookmark](https://gitlab.com/altepizza/nextbookmark) - A minimal client for iOS ([App Store entry](https://apps.apple.com/de/app/nextbookmark/id1500340092))
### Other
- [uMarks](https://open-store.io/app/umarks.ernesst) - App for Ubuntu touch

View File

@ -43,10 +43,22 @@ Requirements:
</settings>
<repair-steps>
<post-migration>
<step>OCA\Bookmarks\Migration\DeduplicateSharedFoldersRepairStep</step>
<step>OCA\Bookmarks\Migration\SuperfluousSharedFoldersRepairStep</step>
<step>OCA\Bookmarks\Migration\OrphanedSharesRepairStep</step>
<step>OCA\Bookmarks\Migration\OrphanedTreeItemsRepairStep</step>
<step>OCA\Bookmarks\Migration\SuperfluousSharedFoldersRepairStep</step>
<step>OCA\Bookmarks\Migration\GroupSharesUpdateRepairStep</step>
</post-migration>
</repair-steps>
<activity>
<settings>
<setting>OCA\Bookmarks\Activity\Setting</setting>
</settings>
<filters>
<filter>OCA\Bookmarks\Activity\Filter</filter>
</filters>
<providers>
<provider>OCA\Bookmarks\Activity\Provider</provider>
</providers>
</activity>
</info>

View File

@ -85,6 +85,9 @@ return [
['name' => 'tags#full_tags', 'url' => '/public/rest/v2/tag', 'verb' => 'GET'],
['name' => 'tags#rename_tag', 'url' => '/public/rest/v2/tag', 'verb' => 'POST'],
['name' => 'tags#delete_tag', 'url' => '/public/rest/v2/tag', 'verb' => 'DELETE'],
['name' => 'tags#rename_tag', 'url' => '/tag/{old_name}', 'verb' => 'POST'],
['name' => 'tags#rename_tag', 'url' => '/tag/{old_name}', 'verb' => 'PUT'],
['name' => 'tags#delete_tag', 'url' => '/tag/{old_name}', 'verb' => 'DELETE'],
['name' => 'folders#get_folders', 'url' => '/public/rest/v2/folder', 'verb' => 'GET'],
['name' => 'folders#get_folder', 'url' => '/public/rest/v2/folder/{folderId}', 'verb' => 'GET'],
['name' => 'folders#add_folder', 'url' => '/public/rest/v2/folder', 'verb' => 'POST'],

392
composer.lock generated
View File

@ -1,7 +1,7 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "dc5278c906cd69a3cc7219bc2aa71f4e",
@ -54,20 +54,20 @@
},
{
"name": "doctrine/inflector",
"version": "1.4.1",
"version": "1.4.3",
"source": {
"type": "git",
"url": "https://github.com/doctrine/inflector.git",
"reference": "4111f6853aea6f28b2b1dcfdde83d12dd3d5e6e3"
"reference": "4650c8b30c753a76bf44fb2ed00117d6f367490c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/inflector/zipball/4111f6853aea6f28b2b1dcfdde83d12dd3d5e6e3",
"reference": "4111f6853aea6f28b2b1dcfdde83d12dd3d5e6e3",
"url": "https://api.github.com/repos/doctrine/inflector/zipball/4650c8b30c753a76bf44fb2ed00117d6f367490c",
"reference": "4650c8b30c753a76bf44fb2ed00117d6f367490c",
"shasum": ""
},
"require": {
"php": "^7.2"
"php": "^7.2 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^7.0",
@ -128,7 +128,7 @@
"uppercase",
"words"
],
"time": "2020-05-09T15:09:09+00:00"
"time": "2020-05-29T07:19:59+00:00"
},
{
"name": "guzzlehttp/psr7",
@ -302,16 +302,16 @@
},
{
"name": "marcelklehr/link-preview",
"version": "v3.0.3",
"version": "v3.0.4",
"source": {
"type": "git",
"url": "https://github.com/marcelklehr/link-preview.git",
"reference": "2b79492e2cba41f0464265c09b93263c0276fc07"
"reference": "b86dad0620da330ff1c20e13a153f27570d1bc73"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/marcelklehr/link-preview/zipball/2b79492e2cba41f0464265c09b93263c0276fc07",
"reference": "2b79492e2cba41f0464265c09b93263c0276fc07",
"url": "https://api.github.com/repos/marcelklehr/link-preview/zipball/b86dad0620da330ff1c20e13a153f27570d1bc73",
"reference": "b86dad0620da330ff1c20e13a153f27570d1bc73",
"shasum": ""
},
"require": {
@ -352,7 +352,7 @@
"scraping",
"url"
],
"time": "2018-09-01T21:10:51+00:00"
"time": "2020-07-19T18:44:01+00:00"
},
{
"name": "paragonie/random_compat",
@ -619,29 +619,144 @@
"time": "2019-03-08T08:55:37+00:00"
},
{
"name": "rowbot/url",
"version": "3.0.1",
"name": "rowbot/idna",
"version": "0.1.2",
"source": {
"type": "git",
"url": "https://github.com/TRowbotham/URL-Parser.git",
"reference": "4c2e6d1c4efe47f42f62da034e6eaa8d719444cd"
"url": "https://github.com/TRowbotham/idna.git",
"reference": "400f6523b9b9fc0b7c301312fedb95ed42434478"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TRowbotham/URL-Parser/zipball/4c2e6d1c4efe47f42f62da034e6eaa8d719444cd",
"reference": "4c2e6d1c4efe47f42f62da034e6eaa8d719444cd",
"url": "https://api.github.com/repos/TRowbotham/idna/zipball/400f6523b9b9fc0b7c301312fedb95ed42434478",
"reference": "400f6523b9b9fc0b7c301312fedb95ed42434478",
"shasum": ""
},
"require": {
"php": ">=7.1",
"rowbot/punycode": "^1.0",
"symfony/polyfill-intl-normalizer": "^1.18"
},
"require-dev": {
"guzzlehttp/guzzle": "^6.5",
"phpstan/phpstan": "^0.12.20",
"phpstan/phpstan-deprecation-rules": "^0.12.2",
"phpstan/phpstan-strict-rules": "^0.12.2",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"squizlabs/php_codesniffer": "^3.5.1",
"symfony/cache": "^4.3 || ^5.0"
},
"type": "library",
"extra": {
"idna": {
"unicode_version": "13.0.0"
}
},
"autoload": {
"psr-4": {
"Rowbot\\Idna\\": "src/",
"Rowbot\\Idna\\Resource\\": "resources/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Trevor Rowbotham"
}
],
"description": "An implementation of UTS#46 Unicode IDNA Compatibility Processing.",
"keywords": [
"idn",
"idna",
"international domain names",
"iri",
"tr46",
"unicode",
"uts46"
],
"time": "2020-07-15T21:33:00+00:00"
},
{
"name": "rowbot/punycode",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/TRowbotham/punycode.git",
"reference": "2327f31c2f0703edf8bcb6f500d7b012c2722552"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TRowbotham/punycode/zipball/2327f31c2f0703edf8bcb6f500d7b012c2722552",
"reference": "2327f31c2f0703edf8bcb6f500d7b012c2722552",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"ext-mbstring": "*",
"phpstan/phpstan": "^0.12.20",
"phpstan/phpstan-deprecation-rules": "^0.12.2",
"phpstan/phpstan-strict-rules": "^0.12.2",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"squizlabs/php_codesniffer": "^3.5.1"
},
"type": "library",
"autoload": {
"psr-4": {
"Rowbot\\Punycode\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Trevor Rowbotham",
"homepage": "https://trowbotham.com",
"role": "Developer"
}
],
"description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA).",
"keywords": [
"punycode",
"rfc-3492",
"rfc3492"
],
"time": "2020-07-15T21:39:12+00:00"
},
{
"name": "rowbot/url",
"version": "3.1.0",
"source": {
"type": "git",
"url": "https://github.com/TRowbotham/URL-Parser.git",
"reference": "bb021eea7e8a8cad26155c75981ca432a92cc719"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TRowbotham/URL-Parser/zipball/bb021eea7e8a8cad26155c75981ca432a92cc719",
"reference": "bb021eea7e8a8cad26155c75981ca432a92cc719",
"shasum": ""
},
"require": {
"brick/math": "^0.8.13",
"ext-intl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"lib-icu": ">=4.6",
"php": ">=7.1"
"php": ">=7.1",
"rowbot/idna": "^0.1.2"
},
"require-dev": {
"guzzlehttp/guzzle": "^6.3",
"phpstan/phpstan": "^0.12.20",
"phpstan/phpstan-deprecation-rules": "^0.12.2",
"phpstan/phpstan-strict-rules": "^0.12.2",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"squizlabs/php_codesniffer": "^3.5.1",
"symfony/cache": "^4.1 || ^5.0"
},
"type": "library",
@ -657,7 +772,7 @@
"authors": [
{
"name": "Trevor Rowbotham",
"homepage": "http://trowbotham.com",
"homepage": "https://trowbotham.com",
"role": "Developer"
}
],
@ -671,11 +786,11 @@
"url-parser",
"url-parsing"
],
"time": "2020-02-29T21:31:27+00:00"
"time": "2020-07-16T03:41:26+00:00"
},
{
"name": "symfony/css-selector",
"version": "v3.4.40",
"version": "v3.4.42",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
@ -728,16 +843,16 @@
},
{
"name": "symfony/dom-crawler",
"version": "v3.4.40",
"version": "v3.4.42",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
"reference": "ceacdab4abf7695ef6bec77c8b7983e1544c6358"
"reference": "c3086a58a66b2a519c0b7ac80539a3727609ea9c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/ceacdab4abf7695ef6bec77c8b7983e1544c6358",
"reference": "ceacdab4abf7695ef6bec77c8b7983e1544c6358",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c3086a58a66b2a519c0b7ac80539a3727609ea9c",
"reference": "c3086a58a66b2a519c0b7ac80539a3727609ea9c",
"shasum": ""
},
"require": {
@ -781,20 +896,20 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
"time": "2020-03-16T08:31:04+00:00"
"time": "2020-05-22T19:35:43+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.17.0",
"version": "v1.18.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9"
"reference": "1c302646f6efc070cd46856e600e5e0684d6b454"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9",
"reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454",
"reference": "1c302646f6efc070cd46856e600e5e0684d6b454",
"shasum": ""
},
"require": {
@ -806,7 +921,11 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.17-dev"
"dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
@ -839,20 +958,87 @@
"polyfill",
"portable"
],
"time": "2020-05-12T16:14:59+00:00"
"time": "2020-07-14T12:35:20+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.16.0",
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.18.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "a54881ec0ab3b2005c406aed0023c062879031e7"
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a54881ec0ab3b2005c406aed0023c062879031e7",
"reference": "a54881ec0ab3b2005c406aed0023c062879031e7",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e",
"reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"time": "2020-07-14T12:35:20+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.18.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a",
"reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a",
"shasum": ""
},
"require": {
@ -864,7 +1050,11 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.16-dev"
"dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
@ -898,26 +1088,26 @@
"portable",
"shim"
],
"time": "2020-05-08T16:50:20+00:00"
"time": "2020-07-14T12:35:20+00:00"
}
],
"packages-dev": [
{
"name": "doctrine/instantiator",
"version": "1.3.0",
"version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "ae466f726242e637cebdd526a7d991b9433bacf1"
"reference": "f350df0268e904597e3bd9c4685c53e0e333feea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1",
"reference": "ae466f726242e637cebdd526a7d991b9433bacf1",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea",
"reference": "f350df0268e904597e3bd9c4685c53e0e333feea",
"shasum": ""
},
"require": {
"php": "^7.1"
"php": "^7.1 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^6.0",
@ -956,24 +1146,24 @@
"constructor",
"instantiate"
],
"time": "2019-10-21T16:45:58+00:00"
"time": "2020-05-29T17:27:14+00:00"
},
{
"name": "myclabs/deep-copy",
"version": "1.9.5",
"version": "1.10.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "b2c28789e80a97badd14145fda39b545d83ca3ef"
"reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef",
"reference": "b2c28789e80a97badd14145fda39b545d83ca3ef",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5",
"reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5",
"shasum": ""
},
"require": {
"php": "^7.1"
"php": "^7.1 || ^8.0"
},
"replace": {
"myclabs/deep-copy": "self.version"
@ -1004,7 +1194,7 @@
"object",
"object graph"
],
"time": "2020-01-17T21:11:47+00:00"
"time": "2020-06-29T13:22:24+00:00"
},
{
"name": "phar-io/manifest",
@ -1110,25 +1300,25 @@
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.1.0",
"version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
"reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b"
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b",
"reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
"dev-2.x": "2.x-dev"
}
},
"autoload": {
@ -1155,7 +1345,7 @@
"reflection",
"static analysis"
],
"time": "2020-04-27T09:25:28+00:00"
"time": "2020-06-27T09:03:43+00:00"
},
{
"name": "phpdocumentor/reflection-docblock",
@ -1212,30 +1402,29 @@
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.1.0",
"version": "1.3.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "7462d5f123dfc080dfdf26897032a6513644fc95"
"reference": "e878a14a65245fbe78f8080eba03b47c3b705651"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95",
"reference": "7462d5f123dfc080dfdf26897032a6513644fc95",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e878a14a65245fbe78f8080eba03b47c3b705651",
"reference": "e878a14a65245fbe78f8080eba03b47c3b705651",
"shasum": ""
},
"require": {
"php": "^7.2",
"php": "^7.2 || ^8.0",
"phpdocumentor/reflection-common": "^2.0"
},
"require-dev": {
"ext-tokenizer": "^7.2",
"mockery/mockery": "~1"
"ext-tokenizer": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
"dev-1.x": "1.x-dev"
}
},
"autoload": {
@ -1254,37 +1443,37 @@
}
],
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"time": "2020-02-18T18:59:58+00:00"
"time": "2020-06-27T10:12:23+00:00"
},
{
"name": "phpspec/prophecy",
"version": "v1.10.3",
"version": "1.11.1",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "451c3cd1418cf640de218914901e51b064abb093"
"reference": "b20034be5efcdab4fb60ca3a29cba2949aead160"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093",
"reference": "451c3cd1418cf640de218914901e51b064abb093",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/b20034be5efcdab4fb60ca3a29cba2949aead160",
"reference": "b20034be5efcdab4fb60ca3a29cba2949aead160",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
"sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0",
"sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0"
"doctrine/instantiator": "^1.2",
"php": "^7.2",
"phpdocumentor/reflection-docblock": "^5.0",
"sebastian/comparator": "^3.0 || ^4.0",
"sebastian/recursion-context": "^3.0 || ^4.0"
},
"require-dev": {
"phpspec/phpspec": "^2.5 || ^3.2",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
"phpspec/phpspec": "^6.0",
"phpunit/phpunit": "^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.10.x-dev"
"dev-master": "1.11.x-dev"
}
},
"autoload": {
@ -1317,7 +1506,7 @@
"spy",
"stub"
],
"time": "2020-03-05T15:02:03+00:00"
"time": "2020-07-08T12:44:21+00:00"
},
{
"name": "phpunit/php-code-coverage",
@ -1573,16 +1762,16 @@
},
{
"name": "phpunit/phpunit",
"version": "8.5.5",
"version": "8.5.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "63dda3b212a0025d380a745f91bdb4d8c985adb7"
"reference": "34c18baa6a44f1d1fbf0338907139e9dce95b997"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/63dda3b212a0025d380a745f91bdb4d8c985adb7",
"reference": "63dda3b212a0025d380a745f91bdb4d8c985adb7",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/34c18baa6a44f1d1fbf0338907139e9dce95b997",
"reference": "34c18baa6a44f1d1fbf0338907139e9dce95b997",
"shasum": ""
},
"require": {
@ -1652,7 +1841,7 @@
"testing",
"xunit"
],
"time": "2020-05-22T13:51:52+00:00"
"time": "2020-06-22T07:06:58+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
@ -2271,23 +2460,23 @@
},
{
"name": "theseer/tokenizer",
"version": "1.1.3",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
"reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9"
"reference": "75a63c33a8577608444246075ea0af0d052e452a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
"reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a",
"reference": "75a63c33a8577608444246075ea0af0d052e452a",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
"php": "^7.0"
"php": "^7.2 || ^8.0"
},
"type": "library",
"autoload": {
@ -2307,27 +2496,28 @@
}
],
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"time": "2019-06-13T22:48:21+00:00"
"time": "2020-07-12T23:59:07+00:00"
},
{
"name": "webmozart/assert",
"version": "1.8.0",
"version": "1.9.1",
"source": {
"type": "git",
"url": "https://github.com/webmozart/assert.git",
"reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6"
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozart/assert/zipball/ab2cb0b3b559010b75981b1bdce728da3ee90ad6",
"reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6",
"url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
"shasum": ""
},
"require": {
"php": "^5.3.3 || ^7.0",
"php": "^5.3.3 || ^7.0 || ^8.0",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
"phpstan/phpstan": "<0.12.20",
"vimeo/psalm": "<3.9.1"
},
"require-dev": {
@ -2355,7 +2545,7 @@
"check",
"validate"
],
"time": "2020-04-18T12:12:48+00:00"
"time": "2020-07-08T17:02:28+00:00"
}
],
"aliases": [],

View File

@ -56,7 +56,7 @@ Query bookmarks
{
"status": "success",
"data": [{ "id": "7", "title": "Google", "tags": ["firsttag"] }]
"data": [{ "id": 7, "title": "Google", "tags": ["firsttag"] }]
}
Create a bookmark
@ -102,12 +102,12 @@ Create a bookmark
{
"status": "success",
"item": {
"id": "7",
"id": 7,
"url": "http://google.com",
"title": "Google",
"description":"in case i forget",
"tags": ["search-engines", "uselessbookmark"],
"folders": ["-1"]
"folders": [-1]
}
}
@ -142,12 +142,12 @@ Get a bookmark
{
"status": "success",
"item": {
"id": "7",
"id": 7,
"url": "http://google.com",
"title": "Google",
"description":"in case i forget",
"tags": ["search-engines", "uselessbookmark"],
"folders": ["-1"]
"folders": [-1]
}
}
@ -190,12 +190,12 @@ Edit a bookmark
{
"status": "success",
"item": {
"id": "7",
"id": 7,
"url": "http://google.com",
"title": "Boogle",
"description":"in case i forget",
"tags": ["search-engines", "uselessbookmark"],
"folders": ["-1"]
"folders": [-1]
}
}

View File

@ -55,10 +55,10 @@ Get full hierarchy
{
"status": "success", "data": [
{"id": "1", "title": "work", "parent_folder": "-1"},
{"id": "2", "title": "personal", "parent_folder": "-1", "children": [
{"id": "3", "title": "garden", "parent_folder": "2"},
{"id": "4", "title": "music", "parent_folder": "2"}
{"id": 1, "title": "work", "parent_folder": -1},
{"id": 2, "title": "personal", "parent_folder": -1, "children": [
{"id": 3, "title": "garden", "parent_folder": 2},
{"id": 4, "title": "music", "parent_folder": 2}
]},
]
}
@ -93,9 +93,9 @@ Get single folder
{
"status": "success",
"item": {
"id": "2",
"id": 2,
"title": "My Personal Bookmarks",
"parent_folder": "-1"
"parent_folder": -1
}
}
@ -178,7 +178,7 @@ Edit a folder
"item": {
"id": 5,
"title": "optional physical activity",
"parent_folder": "-1"
"parent_folder": -1
}
}
@ -375,10 +375,10 @@ Get folder's content order
{
"status": "success",
"data": [
{"type": "folder", "id": "17"},
{"type": "bookmark", "id": "204"},
{"type": "bookmark", "id": "192"},
{"type": "bookmark", "id": "210"}
{"type": "folder", "id": 17},
{"type": "bookmark", "id": 204},
{"type": "bookmark", "id": 192},
{"type": "bookmark", "id": 210}
]
}
@ -409,10 +409,10 @@ Set folder's content order
{
"status": "success",
"data": [
{"type": "folder", "id": "17"},
{"type": "bookmark", "id": "204"},
{"type": "bookmark", "id": "192"},
{"type": "bookmark", "id": "210"}
{"type": "folder", "id": 17},
{"type": "bookmark", "id": 204},
{"type": "bookmark", "id": 192},
{"type": "bookmark", "id": 210}
]
}

View File

@ -3,10 +3,27 @@ OC.L10N.register(
{
"Details" : "Munudoù",
"Rename" : "Adenvel",
"Move" : "Diplasañ",
"Delete" : "Dilemel",
"Save" : "Entilañ",
"New folder" : "Heuliad nevez",
"Grid view" : "Diskwell ar roued",
"List view" : "Gwelidik listenn",
"Create" : "Krouiñ",
"Cancel" : "Arrest",
"Public" : "Publik",
"Recently added" : "Ouzpennet n'eus ket pel zo",
"Clear data" : "Lemel ar roadennoù",
"URL" : "URL",
"Tags" : "Klavioù",
"Sharing" : "Rannan",
"Share link" : "Rannan liamm",
"Copy link" : "Kopiañ al liamm",
"Allow editing" : "Cheñchamentoù aotreet",
"Link copied" : "Liamm eilet",
"Select" : "Dibab",
"Privacy" : "Prevezder",
"Link" : "Liamm",
"Folder" : "Teuliad"
},
"nplurals=5; plural=((n%10 == 1) && (n%100 != 11) && (n%100 !=71) && (n%100 !=91) ? 0 :(n%10 == 2) && (n%100 != 12) && (n%100 !=72) && (n%100 !=92) ? 1 :(n%10 ==3 || n%10==4 || n%10==9) && (n%100 < 10 || n% 100 > 19) && (n%100 < 70 || n%100 > 79) && (n%100 < 90 || n%100 > 99) ? 2 :(n != 0 && n % 1000000 == 0) ? 3 : 4);");

View File

@ -1,10 +1,27 @@
{ "translations": {
"Details" : "Munudoù",
"Rename" : "Adenvel",
"Move" : "Diplasañ",
"Delete" : "Dilemel",
"Save" : "Entilañ",
"New folder" : "Heuliad nevez",
"Grid view" : "Diskwell ar roued",
"List view" : "Gwelidik listenn",
"Create" : "Krouiñ",
"Cancel" : "Arrest",
"Public" : "Publik",
"Recently added" : "Ouzpennet n'eus ket pel zo",
"Clear data" : "Lemel ar roadennoù",
"URL" : "URL",
"Tags" : "Klavioù",
"Sharing" : "Rannan",
"Share link" : "Rannan liamm",
"Copy link" : "Kopiañ al liamm",
"Allow editing" : "Cheñchamentoù aotreet",
"Link copied" : "Liamm eilet",
"Select" : "Dibab",
"Privacy" : "Prevezder",
"Link" : "Liamm",
"Folder" : "Teuliad"
},"pluralForm" :"nplurals=5; plural=((n%10 == 1) && (n%100 != 11) && (n%100 !=71) && (n%100 !=91) ? 0 :(n%10 == 2) && (n%100 != 12) && (n%100 !=72) && (n%100 !=92) ? 1 :(n%10 ==3 || n%10==4 || n%10==9) && (n%100 < 10 || n% 100 > 19) && (n%100 < 70 || n%100 > 79) && (n%100 < 90 || n%100 > 99) ? 2 :(n != 0 && n % 1000000 == 0) ? 3 : 4);"
}

View File

@ -56,7 +56,7 @@ OC.L10N.register(
"RSS Feed" : "Fonte RSS",
"This is an RSS feed of the current result set with access restricted to you." : "Isto é unha fonre RSS do conxunto de resultados actual con acceso restrinxido para vostede.",
"Clear data" : "Limpar os datos",
"Permanently remove all bookmarks from your account. There is no going back!" : "Retirar permanentemente todos os marcadores da súa conta. Isto non ten volta atrás!",
"Permanently remove all bookmarks from your account. There is no going back!" : "Retirar de xeito permanente todos os marcadores da súa conta. Isto non ten volta atrás!",
"Delete all bookmarks" : "Eliminar todos os marcadores",
"Bookmarklet" : "Miniaplicación ligada",
"Drag this to your browser bookmarks and click it to quickly bookmark a webpage" : "Arrastre isto cara aos marcadores do seu navegador e prema para marcar rapidamente unha páxina web",

View File

@ -54,7 +54,7 @@
"RSS Feed" : "Fonte RSS",
"This is an RSS feed of the current result set with access restricted to you." : "Isto é unha fonre RSS do conxunto de resultados actual con acceso restrinxido para vostede.",
"Clear data" : "Limpar os datos",
"Permanently remove all bookmarks from your account. There is no going back!" : "Retirar permanentemente todos os marcadores da súa conta. Isto non ten volta atrás!",
"Permanently remove all bookmarks from your account. There is no going back!" : "Retirar de xeito permanente todos os marcadores da súa conta. Isto non ten volta atrás!",
"Delete all bookmarks" : "Eliminar todos os marcadores",
"Bookmarklet" : "Miniaplicación ligada",
"Drag this to your browser bookmarks and click it to quickly bookmark a webpage" : "Arrastre isto cara aos marcadores do seu navegador e prema para marcar rapidamente unha páxina web",

View File

@ -23,13 +23,17 @@ OC.L10N.register(
"List view" : "Prikaz popisa",
"Move selection" : "Premjesti odabir",
"Delete selection" : "Izbriši odabir",
"Open all selected" : "Otvori sve odabrano",
"Select all visible" : "Odaberi sve vidljivo",
"Cancel selection" : "Poništi odabir",
"Selected {folders} folders and {bookmarks} bookmarks" : "Odabrane mape {folders} i knjižne oznake {bookmarks}",
"_Selected %n bookmark_::_Selected %n bookmarks_" : ["Odabrana %n knjižna oznaka","Odabrane %n knjižne oznake","Odabrano %n knjižnih oznaka"],
"_Selected %n folder_::_Selected %n folders_" : ["Odabrana %n mapa","Odabrane %n mape","Odabrano %n mapa"],
"Enter a link" : "Unesite poveznicu",
"Create" : "Stvori",
"Cancel" : "Odustani",
"Enter folder title" : "Unesite naslov mape",
"Select folder" : "Odaberi mapu",
"Shared by {user}" : "Dijeli {user}",
"Shared privately" : "Privatno dijeljenje",
"Public" : "Javna",
@ -71,12 +75,14 @@ OC.L10N.register(
"Select a user or group" : "Odaberite korisnika ili grupu",
"Share link" : "Dijeli poveznicu",
"Copy link" : "Kopiraj poveznicu",
"Copy RSS feed" : "Kopiraj sažetak događaja RSS",
"Delete link" : "Izbriši poveznicu",
"Create public link" : "Stvori javnu poveznicu",
"Allow editing" : "Dopusti uređivanje",
"Allow sharing" : "Dopusti dijeljenje",
"Remove share" : "Ukloni dijeljenje",
"Link copied" : "Poveznica je kopirana",
"RSS feed copied" : "Sažetak događaja RSS kopiran",
"Select" : "Odaberi",
"Previews" : "Pretpregledi",
"In order to display real screenshots of your bookmarked websites, Bookmarks can use a third-party service to generate those." : "Kako biste prikazali stvarne snimke zaslona označenih mrežnih mjesta, možete se koristiti servisom treće strane za generiranje tih knjižnih oznaka.",
@ -98,6 +104,7 @@ OC.L10N.register(
"Link" : "Poveznica",
"Enter bookmark URL" : "Unesite URL knjižne oznake",
"Folder" : "Mapa",
"Root Folder" : "Korijenska mapa",
"Failed to find existing bookmark" : "Knjižna oznaka nije pronađena",
"Failed to create bookmark" : "Stvaranje knjižne oznake nije uspjelo",
"Failed to save bookmark" : "Spremanje knjižne oznake nije uspjelo",

View File

@ -21,13 +21,17 @@
"List view" : "Prikaz popisa",
"Move selection" : "Premjesti odabir",
"Delete selection" : "Izbriši odabir",
"Open all selected" : "Otvori sve odabrano",
"Select all visible" : "Odaberi sve vidljivo",
"Cancel selection" : "Poništi odabir",
"Selected {folders} folders and {bookmarks} bookmarks" : "Odabrane mape {folders} i knjižne oznake {bookmarks}",
"_Selected %n bookmark_::_Selected %n bookmarks_" : ["Odabrana %n knjižna oznaka","Odabrane %n knjižne oznake","Odabrano %n knjižnih oznaka"],
"_Selected %n folder_::_Selected %n folders_" : ["Odabrana %n mapa","Odabrane %n mape","Odabrano %n mapa"],
"Enter a link" : "Unesite poveznicu",
"Create" : "Stvori",
"Cancel" : "Odustani",
"Enter folder title" : "Unesite naslov mape",
"Select folder" : "Odaberi mapu",
"Shared by {user}" : "Dijeli {user}",
"Shared privately" : "Privatno dijeljenje",
"Public" : "Javna",
@ -69,12 +73,14 @@
"Select a user or group" : "Odaberite korisnika ili grupu",
"Share link" : "Dijeli poveznicu",
"Copy link" : "Kopiraj poveznicu",
"Copy RSS feed" : "Kopiraj sažetak događaja RSS",
"Delete link" : "Izbriši poveznicu",
"Create public link" : "Stvori javnu poveznicu",
"Allow editing" : "Dopusti uređivanje",
"Allow sharing" : "Dopusti dijeljenje",
"Remove share" : "Ukloni dijeljenje",
"Link copied" : "Poveznica je kopirana",
"RSS feed copied" : "Sažetak događaja RSS kopiran",
"Select" : "Odaberi",
"Previews" : "Pretpregledi",
"In order to display real screenshots of your bookmarked websites, Bookmarks can use a third-party service to generate those." : "Kako biste prikazali stvarne snimke zaslona označenih mrežnih mjesta, možete se koristiti servisom treće strane za generiranje tih knjižnih oznaka.",
@ -96,6 +102,7 @@
"Link" : "Poveznica",
"Enter bookmark URL" : "Unesite URL knjižne oznake",
"Folder" : "Mapa",
"Root Folder" : "Korijenska mapa",
"Failed to find existing bookmark" : "Knjižna oznaka nije pronađena",
"Failed to create bookmark" : "Stvaranje knjižne oznake nije uspjelo",
"Failed to save bookmark" : "Spremanje knjižne oznake nije uspjelo",

View File

@ -23,13 +23,17 @@ OC.L10N.register(
"List view" : "Приказ листе",
"Move selection" : "Премести одабир",
"Delete selection" : "Обриши одабир",
"Open all selected" : "Отвори све одабране",
"Select all visible" : "Одабери све видљиве",
"Cancel selection" : "Поништи одабир",
"Selected {folders} folders and {bookmarks} bookmarks" : "Одабрано {folders} фасцикла(е) и {bookmarks} обележивач(а)",
"_Selected %n bookmark_::_Selected %n bookmarks_" : ["Одабрано %n обележивач","Одабрано %n обележивача","Одабрано %n обележивача"],
"_Selected %n folder_::_Selected %n folders_" : ["Одабрана %n фасцикла","Одабране %n фасцикле","Одабрано %n фасцикли"],
"Enter a link" : "Унесите везу",
"Create" : "Направи",
"Cancel" : "Поништи",
"Enter folder title" : "Унесите назив фасцикле",
"Select folder" : "Одабери фасциклу",
"Shared by {user}" : "Поделио {user}",
"Shared privately" : "Подељено приватно",
"Public" : "Јавно",
@ -71,12 +75,14 @@ OC.L10N.register(
"Select a user or group" : "Одаберите корисника или групу",
"Share link" : "Веза дељења",
"Copy link" : "Копирај везу",
"Copy RSS feed" : "Копирај RSS довод",
"Delete link" : "Обриши везу",
"Create public link" : "Направи јавну везу",
"Allow editing" : "Дозволи уређивање",
"Allow sharing" : "Дозволи дељење",
"Remove share" : "Уклони дељење",
"Link copied" : "Веза ископирана",
"RSS feed copied" : "RSS довод копиран",
"Select" : "Изабери",
"Previews" : "Прегледи",
"In order to display real screenshots of your bookmarked websites, Bookmarks can use a third-party service to generate those." : "Да би приказале прави изглед сајта, Обележивач може користити сервис треће стране да би их генерисале.",
@ -98,6 +104,7 @@ OC.L10N.register(
"Link" : "Веза",
"Enter bookmark URL" : "Унеси адресу за бележење",
"Folder" : "Фасцикла",
"Root Folder" : "Корена фасцикла",
"Failed to find existing bookmark" : "Неспешно тражење постојећег обележивача",
"Failed to create bookmark" : "Грешка при креирању обележивача",
"Failed to save bookmark" : "Грешка при чувању обележивача",

View File

@ -21,13 +21,17 @@
"List view" : "Приказ листе",
"Move selection" : "Премести одабир",
"Delete selection" : "Обриши одабир",
"Open all selected" : "Отвори све одабране",
"Select all visible" : "Одабери све видљиве",
"Cancel selection" : "Поништи одабир",
"Selected {folders} folders and {bookmarks} bookmarks" : "Одабрано {folders} фасцикла(е) и {bookmarks} обележивач(а)",
"_Selected %n bookmark_::_Selected %n bookmarks_" : ["Одабрано %n обележивач","Одабрано %n обележивача","Одабрано %n обележивача"],
"_Selected %n folder_::_Selected %n folders_" : ["Одабрана %n фасцикла","Одабране %n фасцикле","Одабрано %n фасцикли"],
"Enter a link" : "Унесите везу",
"Create" : "Направи",
"Cancel" : "Поништи",
"Enter folder title" : "Унесите назив фасцикле",
"Select folder" : "Одабери фасциклу",
"Shared by {user}" : "Поделио {user}",
"Shared privately" : "Подељено приватно",
"Public" : "Јавно",
@ -69,12 +73,14 @@
"Select a user or group" : "Одаберите корисника или групу",
"Share link" : "Веза дељења",
"Copy link" : "Копирај везу",
"Copy RSS feed" : "Копирај RSS довод",
"Delete link" : "Обриши везу",
"Create public link" : "Направи јавну везу",
"Allow editing" : "Дозволи уређивање",
"Allow sharing" : "Дозволи дељење",
"Remove share" : "Уклони дељење",
"Link copied" : "Веза ископирана",
"RSS feed copied" : "RSS довод копиран",
"Select" : "Изабери",
"Previews" : "Прегледи",
"In order to display real screenshots of your bookmarked websites, Bookmarks can use a third-party service to generate those." : "Да би приказале прави изглед сајта, Обележивач може користити сервис треће стране да би их генерисале.",
@ -96,6 +102,7 @@
"Link" : "Веза",
"Enter bookmark URL" : "Унеси адресу за бележење",
"Folder" : "Фасцикла",
"Root Folder" : "Корена фасцикла",
"Failed to find existing bookmark" : "Неспешно тражење постојећег обележивача",
"Failed to create bookmark" : "Грешка при креирању обележивача",
"Failed to save bookmark" : "Грешка при чувању обележивача",

View File

@ -1,14 +1,20 @@
OC.L10N.register(
"bookmarks",
{
"Select bookmark" : "Chọn bookmark",
"Details" : "Thông tin",
"Rename" : "Đổi tên",
"Move" : "Dịch chuyển",
"Delete" : "Xóa",
"Enter bookmark title" : "Nhập tiêu đề bookmark",
"Save" : "Lưu",
"Select one or more tags" : "Chọn một hoặc nhiều thẻ",
"New folder" : "Tạo thư mục",
"Grid view" : "Xem dạng Lưới",
"List view" : "Xem dạng Danh sách",
"Create" : "Tạo mới",
"Cancel" : "Hủy bỏ",
"Enter folder title" : "Nhập tiêu đề thư mục",
"Public" : "Công khai",
"Import" : "Nhập vào",
"Export" : "Xuất ra",
@ -25,8 +31,12 @@ OC.L10N.register(
"Allow editing" : "Cho phép chỉnh sửa",
"Select" : "Select",
"Privacy" : "Riêng tư",
"Failed to load settings" : "Tải cấu hình thất bại",
"Failed to save settings" : "Lưu cấu hình thất bại",
"Add a bookmark" : "Thêm một bookmark",
"Title" : "Tên",
"Link" : "Liên kết",
"Folder" : "Thư mục"
"Folder" : "Thư mục",
"Root Folder" : "Thư mục gốc"
},
"nplurals=1; plural=0;");

View File

@ -1,12 +1,18 @@
{ "translations": {
"Select bookmark" : "Chọn bookmark",
"Details" : "Thông tin",
"Rename" : "Đổi tên",
"Move" : "Dịch chuyển",
"Delete" : "Xóa",
"Enter bookmark title" : "Nhập tiêu đề bookmark",
"Save" : "Lưu",
"Select one or more tags" : "Chọn một hoặc nhiều thẻ",
"New folder" : "Tạo thư mục",
"Grid view" : "Xem dạng Lưới",
"List view" : "Xem dạng Danh sách",
"Create" : "Tạo mới",
"Cancel" : "Hủy bỏ",
"Enter folder title" : "Nhập tiêu đề thư mục",
"Public" : "Công khai",
"Import" : "Nhập vào",
"Export" : "Xuất ra",
@ -23,8 +29,12 @@
"Allow editing" : "Cho phép chỉnh sửa",
"Select" : "Select",
"Privacy" : "Riêng tư",
"Failed to load settings" : "Tải cấu hình thất bại",
"Failed to save settings" : "Lưu cấu hình thất bại",
"Add a bookmark" : "Thêm một bookmark",
"Title" : "Tên",
"Link" : "Liên kết",
"Folder" : "Thư mục"
"Folder" : "Thư mục",
"Root Folder" : "Thư mục gốc"
},"pluralForm" :"nplurals=1; plural=0;"
}

View File

@ -0,0 +1,228 @@
<?php
namespace OCA\Bookmarks\Activity;
use OCA\Bookmarks\Db\Bookmark;
use OCA\Bookmarks\Db\BookmarkMapper;
use OCA\Bookmarks\Db\Folder;
use OCA\Bookmarks\Db\FolderMapper;
use OCA\Bookmarks\Db\SharedFolder;
use OCA\Bookmarks\Db\SharedFolderMapper;
use OCA\Bookmarks\Db\TreeMapper;
use OCA\Bookmarks\Events\BeforeDeleteEvent;
use OCA\Bookmarks\Events\ChangeEvent;
use OCA\Bookmarks\Events\CreateEvent;
use OCA\Bookmarks\Events\MoveEvent;
use OCA\Bookmarks\Service\Authorizer;
use OCP\Activity\IManager;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\IL10N;
class ActivityPublisher implements IEventListener {
/**
* @var IManager
*/
private $activityManager;
private $appName;
/**
* @var IL10N
*/
private $l;
/**
* @var SharedFolderMapper
*/
private $sharedFolderMapper;
/**
* @var FolderMapper
*/
private $folderMapper;
/**
* @var BookmarkMapper
*/
private $bookmarkMapper;
/**
* @var TreeMapper
*/
private $treeMapper;
/**
* @var Authorizer
*/
private $authorizer;
public function __construct($appName, IManager $activityManager, IL10N $l, SharedFolderMapper $sharedFolderMapper, FolderMapper $folderMapper, BookmarkMapper $bookmarkMapper, TreeMapper $treeMapper, Authorizer $authorizer) {
$this->appName = $appName;
$this->activityManager = $activityManager;
$this->l = $l;
$this->sharedFolderMapper = $sharedFolderMapper;
$this->folderMapper = $folderMapper;
$this->bookmarkMapper = $bookmarkMapper;
$this->treeMapper = $treeMapper;
$this->authorizer = $authorizer;
}
/**
* Handle events
*
* @param Event $event
*/
public function handle(Event $event): void {
if (!($event instanceof ChangeEvent)) {
return;
}
switch ($event->getType()) {
case TreeMapper::TYPE_FOLDER:
$this->publishFolder($event);
break;
case TreeMapper::TYPE_BOOKMARK:
$this->publishBookmark($event);
break;
case TreeMapper::TYPE_SHARE:
$this->publishShare($event);
break;
}
}
public function publishShare(ChangeEvent $event) {
$activity = $this->activityManager->generateEvent();
$activity->setApp($this->appName);
$activity->setType('bookmarks');
$activity->setAuthor($this->authorizer->getUserId());
$activity->setTimestamp(time());
/**
* @var $sharedFolder SharedFolder
*/
try {
$sharedFolder = $this->sharedFolderMapper->find($event->getId());
} catch (DoesNotExistException $e) {
return;
} catch (MultipleObjectsReturnedException $e) {
return;
}
$activity->setObject(TreeMapper::TYPE_FOLDER, $sharedFolder->getFolderId());
if ($event instanceof CreateEvent) {
$activity->setSubject('share_created', ['folder' => $sharedFolder->getTitle(), 'sharee' => $sharedFolder->getUserId()]);
} elseif ($event instanceof BeforeDeleteEvent) {
$activity->setSubject('share_deleted', ['folder' => $sharedFolder->getTitle(), 'sharee' => $sharedFolder->getUserId()]);
} else {
return;
}
foreach([$activity->getAuthor(), $sharedFolder->getUserId()] as $user) {
$activity->setAffectedUser($user);
$this->activityManager->publish($activity);
}
}
public function publishFolder(ChangeEvent $event) {
$activity = $this->activityManager->generateEvent();
$activity->setApp($this->appName);
$activity->setType('bookmarks');
$activity->setAuthor($this->authorizer->getUserId());
$activity->setTimestamp(time());
/**
* @var $folder Folder
*/
try {
$folder = $this->folderMapper->find($event->getId());
} catch (DoesNotExistException $e) {
return;
} catch (MultipleObjectsReturnedException $e) {
return;
}
$activity->setObject(TreeMapper::TYPE_FOLDER, $folder->getId());
if ($event instanceof CreateEvent) {
$activity->setSubject('folder_created', ['folder' => $folder->getTitle()]);
} elseif ($event instanceof BeforeDeleteEvent) {
$activity->setSubject('folder_deleted', ['folder' => $folder->getTitle()]);
} elseif ($event instanceof MoveEvent) {
$activity->setSubject('folder_moved', ['folder' => $folder->getTitle()]);
} else {
return;
}
/**
* @var $shares SharedFolder[]
*/
$shares = $this->sharedFolderMapper->findByOwner($this->authorizer->getUserId());
$shares = array_merge($shares, $this->sharedFolderMapper->findByUser($this->authorizer->getUserId()));
$affectedShares = array_filter($shares, function($sharedFolder) use ($folder){
return $this->treeMapper->hasDescendant($sharedFolder->getFolderId(), TreeMapper::TYPE_FOLDER, $folder->getId());
});
$affectedUsers = array_map(static function($sharedFolder) {
return $sharedFolder->getUserId();
}, $affectedShares);
$affectedUsers[] = $folder->getUserId();
$affectedUsers[] = $this->authorizer->getUserId();
$affectedUsers = array_unique($affectedUsers);
foreach($affectedUsers as $user) {
$activity->setAffectedUser($user);
$this->activityManager->publish($activity);
}
}
public function publishBookmark(ChangeEvent $event) {
$activity = $this->activityManager->generateEvent();
$activity->setApp($this->appName);
$activity->setType('bookmarks');
$activity->setAuthor($this->authorizer->getUserId());
$activity->setTimestamp(time());
/**
* @var $bookmark Bookmark
*/
try {
$bookmark = $this->bookmarkMapper->find($event->getId());
} catch (DoesNotExistException $e) {
return;
} catch (MultipleObjectsReturnedException $e) {
return;
}
$activity->setObject(TreeMapper::TYPE_BOOKMARK, $bookmark->getId());
if ($event instanceof CreateEvent) {
$activity->setSubject('bookmark_created', ['bookmark' => $bookmark->getTitle()]);
} elseif ($event instanceof BeforeDeleteEvent) {
$activity->setSubject('bookmark_deleted', ['bookmark' => $bookmark->getTitle()]);
} else {
return;
}
/**
* @var $shares SharedFolder[]
*/
$shares = $this->sharedFolderMapper->findByOwner($this->authorizer->getUserId());
$shares = array_merge($shares, $this->sharedFolderMapper->findByUser($this->authorizer->getUserId()));
$affectedShares = array_filter($shares, function($sharedFolder) use ($bookmark) {
return $this->treeMapper->hasDescendant($sharedFolder->getFolderId(), TreeMapper::TYPE_BOOKMARK, $bookmark->getId());
});
$affectedUsers = array_map(static function($sharedFolder) {
return $sharedFolder->getUserId();
}, $affectedShares);
$affectedUsers[] = $bookmark->getUserId();
$affectedUsers[] = $this->authorizer->getUserId();
$affectedUsers = array_unique($affectedUsers);
foreach($affectedUsers as $user) {
$activity->setAffectedUser($user);
$this->activityManager->publish($activity);
}
}
}

66
lib/Activity/Filter.php Normal file
View File

@ -0,0 +1,66 @@
<?php
namespace OCA\Bookmarks\Activity;
use OCP\Activity\IFilter;
class Filter implements IFilter {
/**
* @var \OCP\IL10N
*/
private $l;
/**
* @var \OCP\IURLGenerator
*/
private $urlGenerator;
public function __construct(\OCP\IL10N $l, \OCP\IURLGenerator $urlGenerator) {
$this->l = $l;
$this->urlGenerator = $urlGenerator;
}
/**
* @inheritDoc
*/
public function getIdentifier() {
return 'bookmarks';
}
/**
* @inheritDoc
*/
public function getName() {
return $this->l->t('Bookmarks');
}
/**
* @inheritDoc
*/
public function getPriority() {
return 10;
}
/**
* @inheritDoc
*/
public function getIcon() {
return $this->urlGenerator->imagePath('bookmarks', 'bookmarks-black.svg');
}
/**
* @inheritDoc
*/
public function filterTypes(array $types) {
return $types;
}
/**
* @inheritDoc
*/
public function allowedApps() {
return ['bookmarks'];
}
}

224
lib/Activity/Provider.php Normal file
View File

@ -0,0 +1,224 @@
<?php
namespace OCA\Bookmarks\Activity;
use OCA\Bookmarks\Db\Folder;
use OCA\Bookmarks\Db\TreeMapper;
use OCP\Activity\IEvent;
use OCP\Activity\IManager;
use OCP\Activity\IProvider;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\L10N\IFactory;
class Provider implements IProvider {
/**
* @var IFactory
*/
private $languageFactory;
/**
* @var IURLGenerator
*/
private $url;
/**
* @var IUserManager
*/
private $userManager;
/**
* @var IManager
*/
private $activityManager;
/**
* @var \OCP\IL10N
*/
private $l;
/**
* @var TreeMapper
*/
private $treeMapper;
public function __construct(IFactory $languageFactory, IURLGenerator $url, IUserManager $userManager, IManager $activityManager, TreeMapper $treeMapper) {
$this->languageFactory = $languageFactory;
$this->url = $url;
$this->userManager = $userManager;
$this->activityManager = $activityManager;
$this->treeMapper = $treeMapper;
}
/**
* @inheritDoc
*/
public function parse($language, IEvent $event, IEvent $previousEvent = null) {
if ($event->getApp() !== 'bookmarks') {
throw new \InvalidArgumentException();
}
$this->l = $this->languageFactory->get('bookmarks', $language);
$event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('bookmarks', 'bookmarks-black.svg')));
$subjectParameters = $event->getSubjectParameters();
$isSharee = $event->getAffectedUser() === $this->activityManager->getCurrentUserId();
$sharee = $this->userManager->get($subjectParameters['sharee']);
if ($sharee !== null) {
$shareeName = $sharee->getDisplayName();
}else{
$shareeName = null;
}
$isAuthor = $event->getAuthor() === $this->activityManager->getCurrentUserId();
$author = $this->userManager->get($event->getAuthor());
if ($author !== null) {
$authorName = $author->getDisplayName();
}else{
$authorName = null;
}
switch($event->getSubject()) {
case 'bookmark_created':
if ($isAuthor) {
$event->setParsedSubject($this->l->t('You bookmarked "%s"', [
$subjectParameters['bookmark']
]));
}elseif ($authorName){
$event->setParsedSubject($this->l->t('%1$s bookmarked "%2$s"', [
$authorName,
$subjectParameters['bookmark'],
]));
}else {
$event->setParsedSubject($this->l->t('Someone bookmarked "%s"', [
$subjectParameters['bookmark']
]));
}
break;
case 'bookmark_deleted':
if ($isAuthor) {
$event->setParsedSubject($this->l->t('You deleted "%s"', [
$subjectParameters['bookmark']
]));
}elseif ($authorName){
$event->setParsedSubject($this->l->t('%1$s deleted "%1$s"', [
$authorName,
$subjectParameters['bookmark'],
]));
}else {
$event->setParsedSubject($this->l->t('Someone deleted "%s"', [
$subjectParameters['bookmark']
]));
}
break;
case 'folder_created':
if ($isAuthor) {
$event->setParsedSubject($this->l->t('You created folder "%s"', [
$subjectParameters['folder']
]));
}elseif ($authorName){
$event->setParsedSubject($this->l->t('%1$s created folder "%2$s"', [
$authorName,
$subjectParameters['folder']
]));
}else {
$event->setParsedSubject($this->l->t('Someone created folder "%s"', [
$subjectParameters['folder']
]));
}
break;
case 'folder_moved':
if ($isAuthor) {
$event->setParsedSubject($this->l->t('You moved folder "%s"', [
$subjectParameters['folder']
]));
}elseif ($authorName){
$event->setParsedSubject($this->l->t('%1$s moved folder "%2$s"', [
$authorName,
$subjectParameters['folder']
]));
}else {
$event->setParsedSubject($this->l->t('Someone moved folder "%s"', [
$subjectParameters['folder']
]));
}
break;
case 'folder_deleted':
if ($isAuthor) {
$event->setParsedSubject($this->l->t('You deleted folder "%s"', [
$subjectParameters['folder']
]));
}elseif ($authorName){
$event->setParsedSubject($this->l->t('%1$s deleted folder "%2$s"', [
$authorName,
$subjectParameters['folder'],
]));
}else {
$event->setParsedSubject($this->l->t('Someone deleted folder "%s"', [
$subjectParameters['folder']
]));
}
break;
case 'share_created':
if ($isAuthor && $shareeName !== null) {
$event->setParsedSubject($this->l->t('You shared folder "%1$s" with %2$s', [
$subjectParameters['folder'],
$shareeName
]));
}elseif ($isAuthor){
$event->setParsedSubject($this->l->t('You shared folder "%s" with someone', [
$subjectParameters['folder']
]));
}elseif ($authorName && $isSharee) {
$event->setParsedSubject($this->l->t('%1$s shared folder "%2$s" with you', [
$authorName,
$subjectParameters['folder'],
]));
}elseif ($isSharee) {
$event->setParsedSubject($this->l->t('Someone shared folder "%s" with you', [
$subjectParameters['folder']
]));
}
break;
case 'share_deleted':
if ($isAuthor && $shareeName) {
$event->setParsedSubject($this->l->t('You unshared folder "%1$s" with %2$s', [
$subjectParameters['folder'],
$shareeName
]));
}elseif ($isAuthor){
$event->setParsedSubject($this->l->t('You unshared folder "%s" with someone', [
$subjectParameters['folder']
]));
}elseif ($authorName && $isSharee) {
$event->setParsedSubject($this->l->t('%1$s unshared folder "%2$s" with you', [
$subjectParameters['folder'],
$authorName
]));
}elseif ($isSharee) {
$event->setParsedSubject($this->l->t('Someone unshared folder "%s" with you', [
$subjectParameters['folder']
]));
}
break;
default:
throw new \InvalidArgumentException();
}
if ($event->getObjectType() === TreeMapper::TYPE_FOLDER && !str_contains($event->getSubject(), 'deleted')) {
$event->setLink($this->url->linkToRouteAbsolute('bookmarks.web_view.indexfolder', ['folder' => $event->getObjectId()]));
}
if ($event->getObjectType() === TreeMapper::TYPE_BOOKMARK && !str_contains($event->getSubject(), 'deleted')) {
/**
* @var $folders Folder[]
*/
$folders = $this->treeMapper->findParentsOf(TreeMapper::TYPE_BOOKMARK, $event->getObjectId());
$folders = array_filter($folders, function($folder) {
return $folder->getUserId() === $this->activityManager->getCurrentUserId();
});
if (isset($folders[0])) {
$event->setLink($this->url->linkToRouteAbsolute('bookmarks.web_view.indexfolder', ['folder' => $folders[0]->getId()]));
}
}
return $event;
}
}

78
lib/Activity/Setting.php Normal file
View File

@ -0,0 +1,78 @@
<?php
namespace OCA\Bookmarks\Activity;
use OCP\Activity\ISetting;
use OCP\IL10N;
class Setting implements ISetting {
/** @var IL10N */
protected $l;
/**
* @param IL10N $l
*/
public function __construct(IL10N $l) {
$this->l = $l;
}
/**
* @return string Lowercase a-z and underscore only identifier
* @since 11.0.0
*/
public function getIdentifier() {
return 'bookmarks';
}
/**
* @return string A translated string
* @since 11.0.0
*/
public function getName() {
return $this->l->t('Bookmarks');
}
/**
* @return int whether the filter should be rather on the top or bottom of
* the admin section. The filters are arranged in ascending order of the
* priority values. It is required to return a value between 0 and 100.
* @since 11.0.0
*/
public function getPriority() {
return 10;
}
/**
* @return bool True when the option can be changed for the stream
* @since 11.0.0
*/
public function canChangeStream() {
return true;
}
/**
* @return bool True when the option can be changed for the stream
* @since 11.0.0
*/
public function isDefaultEnabledStream() {
return true;
}
/**
* @return bool True when the option can be changed for the mail
* @since 11.0.0
*/
public function canChangeMail() {
return true;
}
/**
* @return bool True when the option can be changed for the stream
* @since 11.0.0
*/
public function isDefaultEnabledMail() {
return false;
}
}

View File

@ -14,6 +14,7 @@
namespace OCA\Bookmarks\AppInfo;
use OCA\Bookmarks\Activity\ActivityPublisher;
use OCA\Bookmarks\Events\BeforeDeleteEvent;
use OCA\Bookmarks\Events\CreateEvent;
use OCA\Bookmarks\Events\MoveEvent;
@ -47,10 +48,17 @@ class Application extends App {
});
$dispatcher = $this->getContainer()->query(IEventDispatcher::class);
$dispatcher->addServiceListener(CreateEvent::class, HashManager::class);
$dispatcher->addServiceListener(UpdateEvent::class, HashManager::class);
$dispatcher->addServiceListener(BeforeDeleteEvent::class, HashManager::class);
$dispatcher->addServiceListener(MoveEvent::class, HashManager::class);
$dispatcher->addServiceListener(CreateEvent::class, ActivityPublisher::class);
$dispatcher->addServiceListener(UpdateEvent::class, ActivityPublisher::class);
$dispatcher->addServiceListener(BeforeDeleteEvent::class, ActivityPublisher::class);
$dispatcher->addServiceListener(MoveEvent::class, ActivityPublisher::class);
$dispatcher->addServiceListener(BeforeUserDeletedEvent::class, UserGroupListener::class);
$dispatcher->addServiceListener(UserAddedEvent::class, UserGroupListener::class);
$dispatcher->addServiceListener(UserRemovedEvent::class, UserGroupListener::class);

View File

@ -160,12 +160,14 @@ class BookmarkMapper extends QBMapper {
$sqlSortColumn = $params->getSortBy('lastmodified', $this->getSortByColumns());
if ($sqlSortColumn === 'title') {
$qb->orderBy($qb->createFunction('UPPER(`b`.`title`)'), 'ASC');
$qb->addOrderBy($qb->createFunction('UPPER(`b`.`title`)'), 'ASC');
} else if ($sqlSortColumn === 'index') {
$qb->orderBy('t.'.$sqlSortColumn, 'ASC');
} else {
$qb->orderBy('b.'.$sqlSortColumn, 'DESC');
$qb->addOrderBy('t.'.$sqlSortColumn, 'ASC');
} else {
$qb->addOrderBy('b.'.$sqlSortColumn, 'DESC');
}
// Always sort by id additionally, so the ordering is stable
$qb->addOrderBy('b.id', 'ASC');
if ($params->getLimit() !== -1) {
$qb->setMaxResults($params->getLimit());
@ -321,18 +323,9 @@ class BookmarkMapper extends QBMapper {
* @return array|Entity[]
*/
public function findUntagged($userId, QueryParameters $params): array {
// select b.id from oc_bookmarks b LEFT JOIN oc_bookmarks_tags t ON b.id = t.bookmark_id WHERE t.bookmark_id IS NULL
$qb = $this->_findByTags($userId);
$dbType = $this->config->getSystemValue('dbtype', 'sqlite');
if ($dbType === 'pgsql') {
$tagsCol = $qb->createFunction('array_to_string(array_agg(' . $qb->getColumnName('t.tag') . "), ',')");
} else {
$tagsCol = $qb->createFunction('GROUP_CONCAT(' . $qb->getColumnName('t.tag') . ')');
}
$qb->groupBy(...Bookmark::$columns);
$qb->having($qb->expr()->eq($tagsCol, $qb->createPositionalParameter('', IQueryBuilder::PARAM_STR)));
$qb->orHaving($qb->expr()->isNull($tagsCol));
$qb->andWhere($qb->expr()->isNull('t.bookmark_id'));
$this->_queryBuilderSortAndPaginate($qb, $params);
return $this->findEntities($qb);
@ -454,16 +447,7 @@ class BookmarkMapper extends QBMapper {
// normalize url
$entity->setUrl($this->urlNormalizer->normalize($entity->getUrl()));
$entity->setLastmodified(time());
$newEntity = parent::update($entity);
// trigger event
$this->eventDispatcher->dispatch(
UpdateEvent::class,
new UpdateEvent(TreeMapper::TYPE_BOOKMARK, $entity->getId())
);
return $newEntity;
return parent::update($entity);
}
/**
@ -504,10 +488,6 @@ class BookmarkMapper extends QBMapper {
}
parent::insert($entity);
$this->eventDispatcher->dispatch(CreateEvent::class,
new CreateEvent(TreeMapper::TYPE_BOOKMARK, $entity->getId())
);
return $entity;
}

View File

@ -113,7 +113,6 @@ class FolderMapper extends QBMapper {
*/
public function update(Entity $entity): Entity {
parent::update($entity);
$this->eventDispatcher->dispatch(UpdateEvent::class, new UpdateEvent(TreeMapper::TYPE_FOLDER, $entity->getId()));
return $entity;
}
@ -123,7 +122,6 @@ class FolderMapper extends QBMapper {
*/
public function insert(Entity $entity): Entity {
parent::insert($entity);
$this->eventDispatcher->dispatch(CreateEvent::class, new CreateEvent(TreeMapper::TYPE_FOLDER, $entity->getId()));
return $entity;
}
}

View File

@ -2,6 +2,7 @@
namespace OCA\Bookmarks\Db;
use OCA\Bookmarks\Events\CreateEvent;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
@ -19,15 +20,35 @@ class SharedFolderMapper extends QBMapper {
* @var IDBConnection
*/
protected $db;
/**
* @var \OCP\EventDispatcher\IEventDispatcher
*/
private $eventDispatcher;
/**
* TagMapper constructor.
*
* @param IDBConnection $db
* @param \OCP\EventDispatcher\IEventDispatcher $eventDispatcher
*/
public function __construct(IDBConnection $db) {
public function __construct(IDBConnection $db, \OCP\EventDispatcher\IEventDispatcher $eventDispatcher) {
parent::__construct($db, 'bookmarks_shared_folders', SharedFolder::class);
$this->db = $db;
$this->eventDispatcher = $eventDispatcher;
}
/**
* @param int $id
* @return Entity
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
*/
public function find(int $id): Entity {
$qb = $this->db->getQueryBuilder();
$qb->select(SharedFolder::$columns)
->from('bookmarks_shared_folders', 'sf')
->where($qb->expr()->eq('sf.id', $qb->createPositionalParameter($id)));
return $this->findEntity($qb);
}
/**
@ -190,11 +211,25 @@ class SharedFolderMapper extends QBMapper {
return parent::delete($sharedFolder);
}
public function mount(int $id, int $share_id) {
public function mount(int $id, int $share_id): void {
$qb = $this->db->getQueryBuilder();
$qb->insert('bookmarks_shared_to_shares')->values([
'shared_folder_id' => $qb->createPositionalParameter($id),
'share_id' => $qb->createPositionalParameter($share_id)
])->execute();
$this->eventDispatcher->dispatch(CreateEvent::class, new CreateEvent(
TreeMapper::TYPE_SHARE,
$id
));
}
public function findByUser(string $userId): array {
$qb = $this->db->getQueryBuilder();
$qb->select(array_map(static function ($c) {
return 'sf.' . $c;
}, SharedFolder::$columns))
->from('bookmarks_shared_folders', 'sf')
->where($qb->expr()->eq('sf.user_id', $qb->createPositionalParameter($userId)));
return $this->findEntities($qb);
}
}

View File

@ -217,4 +217,23 @@ class TagMapper {
$qb->execute();
}
}
public function deleteTag($userId, string $old) {
$qb = $this->db->getQueryBuilder();
$qb
->select('t.bookmark_id')
->from('bookmarks_tags', 't')
->innerJoin('t', 'bookmarks', 'bm', $qb->expr()->eq('t.bookmark_id', 'bm.id'))
->where($qb->expr()->eq('t.tag', $qb->createNamedParameter($old)))
->andWhere($qb->expr()->eq('bm.user_id', $qb->createNamedParameter($userId)));
$affectedBookmarks = $qb->execute()->fetchAll(\PDO::FETCH_COLUMN);
if (count($affectedBookmarks) !== 0) {
$qb = $this->db->getQueryBuilder();
$qb
->delete('bookmarks_tags')
->where($qb->expr()->in('bookmark_id', array_map([$qb, 'createNamedParameter'], $affectedBookmarks)))
->andWhere($qb->expr()->eq('tag', $qb->createNamedParameter($old)));
$qb->execute();
}
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace OCA\Bookmarks\Migration;
use OCA\Bookmarks\Db\SharedFolder;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
class DeduplicateSharedFoldersRepairStep implements IRepairStep {
/**
* @var IDBConnection
*/
private $db;
public function __construct(IDBConnection $db) {
$this->db = $db;
}
/**
* Returns the step's name
*/
public function getName() {
return 'Deduplicate shared bookmark folders';
}
/**
* @param IOutput $output
*/
public function run(IOutput $output) {
$qb = $this->db->getQueryBuilder();
$qb->select('p1.id')
->from('bookmarks_shared_folders', 'p1')
->leftJoin('p1', 'bookmarks_shared_folders', 'p2', $qb->expr()->andX(
$qb->expr()->eq('p1.folder_id', 'p2.folder_id'),
$qb->expr()->eq('p1.user_id', 'p2.user_id')
))
->where($qb->expr()->lt('p2.id', 'p1.id'));
$duplicateSharedFolders = $qb->execute();
$i = 0;
while ($sharedFolder = $duplicateSharedFolders->fetchColumn()) {
$qb = $this->db->getQueryBuilder();
$qb->delete('bookmarks_shared_folders')
->where($qb->expr()->eq('id', $qb->createPositionalParameter($sharedFolder)))
->andWhere($qb->expr()->eq('type', $qb->createPositionalParameter('share')))
->execute();
$qb = $this->db->getQueryBuilder();
$qb->delete('bookmarks_shared_folders')
->where($qb->expr()->eq('id', $qb->createPositionalParameter($sharedFolder)))
->execute();
$i++;
}
$output->info("Removed $i duplicate shares");
}
}

View File

@ -35,6 +35,7 @@ class OrphanedSharesRepairStep implements IRepairStep {
->leftJoin('s', 'bookmarks_folders', 'f', $qb->expr()->eq('f.id', 's.folder_id'))
->where($qb->expr()->isNull('f.id'));
$shares = $qb->execute();
$i = 0;
while ($share = $shares->fetchColumn()) {
$qb = $this->db->getQueryBuilder();
$folders = $qb->select('f.id')
@ -56,7 +57,10 @@ class OrphanedSharesRepairStep implements IRepairStep {
$qb->delete('bookmarks_shares')
->where($qb->expr()->eq('id', $qb->createPositionalParameter($share)))
->execute();
$i++;
}
$output->info("Removed $i orphaned shares");
$qb = $this->db->getQueryBuilder();
$publics = $qb->select('p.id')
->from('bookmarks_folders_public', 'p')
@ -64,11 +68,14 @@ class OrphanedSharesRepairStep implements IRepairStep {
->where($qb->expr()->isNull('f.id'))
->execute()
->fetchAll(\PDO::FETCH_COLUMN);
$i = 0;
foreach ($publics as $publicId) {
$qb = $this->db->getQueryBuilder();
$qb->delete('bookmarks_folders_public')
->where($qb->expr()->eq('id', $qb->createPositionalParameter($publicId)))
->execute();
$i++;
}
$output->info("Removed $i orphaned public links");
}
}

View File

@ -35,13 +35,17 @@ class OrphanedTreeItemsRepairStep implements IRepairStep {
->leftJoin('t', 'bookmarks', 'b', $qb->expr()->eq('b.id', 't.id'))
->where($qb->expr()->isNull('b.id'));
$orphanedBookmarks = $qb->execute();
$i = 0;
while ($bookmark = $orphanedBookmarks->fetchColumn()) {
$qb = $this->db->getQueryBuilder();
$qb->delete('bookmarks_tree')
->where($qb->expr()->eq('id', $qb->createPositionalParameter($bookmark)))
->andWhere($qb->expr()->eq('type', $qb->createPositionalParameter('bookmark')))
->execute();
$i++;
}
$output->info("Removed $i orphaned bookmarks");
$qb = $this->db->getQueryBuilder();
$qb->select('t.id')
->from('bookmarks_tree', 't')
@ -50,12 +54,15 @@ class OrphanedTreeItemsRepairStep implements IRepairStep {
->where($qb->expr()->isNull('f.id'))
->andWhere($qb->expr()->isNull('r.folder_id'));
$orphanedFolders = $qb->execute();
$i = 0;
while ($folder = $orphanedFolders->fetchColumn()) {
$qb = $this->db->getQueryBuilder();
$qb->delete('bookmarks_tree')
->where($qb->expr()->eq('id', $qb->createPositionalParameter($folder)))
->andWhere($qb->expr()->eq('type', $qb->createPositionalParameter('folder')))
->execute();
$i++;
}
$output->info("Removed $i orphaned bookmark folders");
}
}

View File

@ -38,6 +38,7 @@ class SuperfluousSharedFoldersRepairStep implements IRepairStep {
->where($qb->expr()->eq('t.type', $qb->createPositionalParameter('share')))
->andWhere($qb->expr()->eq('s.owner', 'sf.user_id'));
$superfluousSharedFolders = $qb->execute();
$i = 0;
while ($sharedFolder = $superfluousSharedFolders->fetchColumn()) {
$qb = $this->db->getQueryBuilder();
$qb->delete('bookmarks_tree')
@ -48,6 +49,8 @@ class SuperfluousSharedFoldersRepairStep implements IRepairStep {
$qb->delete('bookmarks_shared_folders')
->where($qb->expr()->eq('id', $qb->createPositionalParameter($sharedFolder)))
->execute();
$i++;
}
$output->info("Removed $i superfluous shares");
}
}

View File

@ -10,6 +10,8 @@ use OCA\Bookmarks\Db\Folder;
use OCA\Bookmarks\Db\FolderMapper;
use OCA\Bookmarks\Db\TagMapper;
use OCA\Bookmarks\Db\TreeMapper;
use OCA\Bookmarks\Events\CreateEvent;
use OCA\Bookmarks\Events\UpdateEvent;
use OCA\Bookmarks\Exception\AlreadyExistsError;
use OCA\Bookmarks\Exception\UnsupportedOperation;
use OCA\Bookmarks\Exception\UrlParseError;
@ -54,6 +56,10 @@ class BookmarkService {
* @var FolderService
*/
private $folders;
/**
* @var \OCP\EventDispatcher\IEventDispatcher
*/
private $eventDispatcher;
/**
* BookmarksService constructor.
@ -67,8 +73,9 @@ class BookmarkService {
* @param BookmarkPreviewer $bookmarkPreviewer
* @param FaviconPreviewer $faviconPreviewer
* @param FolderService $folders
* @param \OCP\EventDispatcher\IEventDispatcher $eventDispatcher
*/
public function __construct(BookmarkMapper $bookmarkMapper, FolderMapper $folderMapper, TagMapper $tagMapper, TreeMapper $treeMapper, Authorizer $authorizer, LinkExplorer $linkExplorer, BookmarkPreviewer $bookmarkPreviewer, FaviconPreviewer $faviconPreviewer, \OCA\Bookmarks\Service\FolderService $folders) {
public function __construct(BookmarkMapper $bookmarkMapper, FolderMapper $folderMapper, TagMapper $tagMapper, TreeMapper $treeMapper, Authorizer $authorizer, LinkExplorer $linkExplorer, BookmarkPreviewer $bookmarkPreviewer, FaviconPreviewer $faviconPreviewer, \OCA\Bookmarks\Service\FolderService $folders, \OCP\EventDispatcher\IEventDispatcher $eventDispatcher) {
$this->bookmarkMapper = $bookmarkMapper;
$this->treeMapper = $treeMapper;
$this->authorizer = $authorizer;
@ -78,6 +85,7 @@ class BookmarkService {
$this->bookmarkPreviewer = $bookmarkPreviewer;
$this->faviconPreviewer = $faviconPreviewer;
$this->folders = $folders;
$this->eventDispatcher = $eventDispatcher;
}
/**
@ -168,6 +176,9 @@ class BookmarkService {
$this->tagMapper->setOn($tags, $bookmark->getId());
$this->treeMapper->addToFolders(TreeMapper::TYPE_BOOKMARK, $bookmark->getId(), $folders);
$this->eventDispatcher->dispatch(CreateEvent::class,
new CreateEvent(TreeMapper::TYPE_BOOKMARK, $bookmark->getId())
);
return $bookmark;
}
@ -232,11 +243,19 @@ class BookmarkService {
* @var $currentOwnFolders Folder[]
*/
$currentOwnFolders = $this->treeMapper->findParentsOf(TreeMapper::TYPE_BOOKMARK, $bookmark->getId());
$currentInaccessibleOwnFolders = array_filter($currentOwnFolders, function($folder) use ($userId) {
return $this->folders->findShareByDescendantAndUser($folder, $userId) === null;
});
if ($bookmark->getUserId() !== $userId) {
$currentInaccessibleOwnFolders = array_map(static function ($f) {
return $f->getId();
}, array_filter($currentOwnFolders, function ($folder) use ($userId) {
return $this->folders->findShareByDescendantAndUser($folder, $userId) === null;
})
);
}else{
$currentInaccessibleOwnFolders = [];
}
$this->treeMapper->setToFolders(TreeMapper::TYPE_BOOKMARK, $bookmark->getId(), array_merge($currentInaccessibleOwnFolders, $ownFolders));
$ownFolders = array_unique(array_merge($currentInaccessibleOwnFolders, $ownFolders));
$this->treeMapper->setToFolders(TreeMapper::TYPE_BOOKMARK, $bookmark->getId(), $ownFolders);
if (count($ownFolders) === 0) {
$this->bookmarkMapper->delete($bookmark);
return null;
@ -246,6 +265,13 @@ class BookmarkService {
if ($tags !== null) {
$this->tagMapper->setOn($tags, $bookmark->getId());
}
// trigger event
$this->eventDispatcher->dispatch(
UpdateEvent::class,
new UpdateEvent(TreeMapper::TYPE_BOOKMARK, $bookmark->getId())
);
$this->bookmarkMapper->update($bookmark);
return $bookmark;
@ -283,7 +309,7 @@ class BookmarkService {
$bookmark = $this->bookmarkMapper->find($bookmarkId);
if ($folder->getUserId() === $bookmark->getUserId()) {
$this->treeMapper->addToFolders(TreeMapper::TYPE_BOOKMARK, $bookmarkId, [$folderId]);
}else{
} else {
$this->_addBookmark($bookmark->getTitle(), $bookmark->getUrl(), $bookmark->getDescription(), $folder->getUserId(), [], [$folder->getId()]);
}
}
@ -300,6 +326,9 @@ class BookmarkService {
foreach ($parents as $parent) {
$this->treeMapper->deleteEntry(TreeMapper::TYPE_BOOKMARK, $bookmark->getId(), $parent->getId());
}
if (count($parents) === 0) {
$this->bookmarkMapper->delete($bookmark);
}
}
/**

View File

@ -13,6 +13,9 @@ use OCA\Bookmarks\Db\SharedFolder;
use OCA\Bookmarks\Db\SharedFolderMapper;
use OCA\Bookmarks\Db\ShareMapper;
use OCA\Bookmarks\Db\TreeMapper;
use OCA\Bookmarks\Events\CreateEvent;
use OCA\Bookmarks\Events\MoveEvent;
use OCA\Bookmarks\Events\UpdateEvent;
use OCA\Bookmarks\Exception\AlreadyExistsError;
use OCA\Bookmarks\Exception\HtmlParseError;
use OCA\Bookmarks\Exception\UnauthorizedAccessError;
@ -20,6 +23,7 @@ use OCA\Bookmarks\Exception\UnsupportedOperation;
use OCA\Bookmarks\Exception\UserLimitExceededError;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\Share\IShare;
@ -57,6 +61,10 @@ class FolderService {
* @var IL10N
*/
private $l10n;
/**
* @var IEventDispatcher
*/
private $eventDispatcher;
/**
* FolderService constructor.
@ -69,8 +77,9 @@ class FolderService {
* @param IGroupManager $groupManager
* @param HtmlImporter $htmlImporter
* @param IL10N $l10n
* @param IEventDispatcher $eventDispatcher
*/
public function __construct(FolderMapper $folderMapper, TreeMapper $treeMapper, ShareMapper $shareMapper, SharedFolderMapper $sharedFolderMapper, PublicFolderMapper $publicFolderMapper, IGroupManager $groupManager, \OCA\Bookmarks\Service\HtmlImporter $htmlImporter, IL10N $l10n) {
public function __construct(FolderMapper $folderMapper, TreeMapper $treeMapper, ShareMapper $shareMapper, SharedFolderMapper $sharedFolderMapper, PublicFolderMapper $publicFolderMapper, IGroupManager $groupManager, \OCA\Bookmarks\Service\HtmlImporter $htmlImporter, IL10N $l10n, IEventDispatcher $eventDispatcher) {
$this->folderMapper = $folderMapper;
$this->treeMapper = $treeMapper;
$this->shareMapper = $shareMapper;
@ -79,6 +88,7 @@ class FolderService {
$this->groupManager = $groupManager;
$this->htmlImporter = $htmlImporter;
$this->l10n = $l10n;
$this->eventDispatcher = $eventDispatcher;
}
/**
@ -101,6 +111,8 @@ class FolderService {
$this->folderMapper->insert($folder);
$this->treeMapper->move(TreeMapper::TYPE_FOLDER, $folder->getId(), $parentFolderId);
$this->eventDispatcher->dispatch(CreateEvent::class, new CreateEvent(TreeMapper::TYPE_FOLDER, $folder->getId()));
return $folder;
}
@ -170,10 +182,8 @@ class FolderService {
foreach ($shares as $share) {
$this->deleteShare($share->getId());
}
$publicFolders = $this->publicFolderMapper->findByFolder($folderId);
foreach ($publicFolders as $publicFolder) {
$this->publicFolderMapper->delete($publicFolder);
}
$publicFolder = $this->publicFolderMapper->findByFolder($folderId);
$this->publicFolderMapper->delete($publicFolder);
return;
}
@ -232,6 +242,7 @@ class FolderService {
if (isset($title)) {
$folder->setTitle($title);
$this->folderMapper->update($folder);
$this->eventDispatcher->dispatch(UpdateEvent::class, new UpdateEvent(TreeMapper::TYPE_FOLDER, $folder->getId()));
}
if (isset($parent_folder)) {
$this->treeMapper->move(TreeMapper::TYPE_FOLDER, $folder->getId(), $parent_folder);

4199
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "bookmarks",
"version": "3.2.1",
"version": "3.3.0",
"main": "js/index.js",
"scripts": {
"dev": "webpack --config webpack.dev.js",
@ -20,25 +20,25 @@
},
"homepage": "https://github.com/nextcloud/bookmarks#readme",
"dependencies": {
"@babel/polyfill": "^7.10.1",
"@babel/polyfill": "^7.10.4",
"@nextcloud/auth": "^1.3.0",
"@nextcloud/axios": "^1.3.2",
"@nextcloud/axios": "^1.3.3",
"@nextcloud/dialogs": "^1.4.0",
"@nextcloud/router": "^1.1.0",
"@nextcloud/vue": "^2.0.0",
"@nextcloud/vue": "^2.2.1",
"copy-text-to-clipboard": "^2.2.0",
"humanize-duration": "^3.23.0",
"humanize-duration": "^3.23.1",
"vue": "^2.6.11",
"vue-click-outside": "^1.1.0",
"vue-router": "^3.3.4",
"vue-simple-progress": "^1.1.1",
"vuex": "^3.4.0",
"vuex": "^3.5.1",
"vuex-router-sync": "^5.0.0"
},
"devDependencies": {
"@babel/core": "^7.10.2",
"@babel/core": "^7.10.5",
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
"@babel/preset-env": "^7.10.2",
"@babel/preset-env": "^7.10.4",
"@nextcloud/eslint-config": "^2.0.0",
"@nextcloud/eslint-plugin": "^1.4.0",
"@vue/test-utils": "^1.0.3",
@ -49,7 +49,7 @@
"eslint-config-standard": "^12.0.0",
"eslint-import-resolver-webpack": "^0.12.1",
"eslint-loader": "^3.0.4",
"eslint-plugin-import": "^2.21.2",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-standard": "^4.0.1",
@ -61,10 +61,10 @@
"stylelint-config-recommended-scss": "^3.3.0",
"stylelint-scss": "^3.17.2",
"stylelint-webpack-plugin": "^0.10.5",
"vue-loader": "^15.9.2",
"vue-loader": "^15.9.3",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.10",
"webpack-cli": "^3.3.12",
"webpack-merge": "^4.2.2",
"webpack-node-externals": "^1.7.2"
},

View File

@ -13,7 +13,8 @@
}">
<a
:href="url"
class="bookmark__click-link" />
class="bookmark__click-link"
target="_blank" />
<template v-if="!renaming">
<div v-if="isEditable" class="bookmark__checkbox">
<input v-model="selected" class="checkbox" type="checkbox"><label

View File

@ -3,7 +3,8 @@
:class="{
bookmarkslist: true,
'bookmarkslist--gridview': viewMode === 'grid'
}">
}"
@scroll="onScroll">
<CreateBookmark v-if="newBookmark" />
<CreateFolder v-if="newFolder" />
<template v-if="$route.name === routes.FOLDER || $route.name === routes.HOME">
@ -107,6 +108,16 @@ export default {
return this.$store.state.viewMode
},
},
methods: {
onScroll() {
if (
this.$el.scrollHeight
< this.$el.scrollTop + this.$el.clientHeight + 500
) {
this.$store.dispatch(actions.FETCH_PAGE)
}
},
},
}
</script>
<style>

View File

@ -49,9 +49,11 @@
</AppNavigationItem>
</template>
</ul>
<AppNavigationSettings v-if="!isPublic">
<Settings />
</AppNavigationSettings>
<template #footer>
<AppNavigationSettings v-if="!isPublic">
<Settings />
</AppNavigationSettings>
</template>
</AppNavigation>
</template>

View File

@ -60,7 +60,6 @@ export default {
},
async created() {
document.addEventListener('scroll', this.onScroll)
this.search = new OCA.Search(this.onSearch, this.onResetSearch)
// set loading indicator
this.$store.commit(mutations.FETCH_START, { type: 'bookmarks' })
@ -124,15 +123,6 @@ export default {
onResetSearch() {
this.$router.push({ name: privateRoutes.HOME })
},
onScroll() {
if (
document.body.scrollHeight
< window.scrollY + window.innerHeight + 500
) {
this.$store.dispatch(actions.FETCH_PAGE)
}
},
},
}
</script>

View File

@ -47,7 +47,6 @@ export default {
},
async created() {
document.addEventListener('scroll', this.onScroll)
// this.search = new OCA.Search(this.onSearch, this.onResetSearch)
this.$store.commit(mutations.SET_AUTH_TOKEN, this.$route.params.token)
// set loading indicator
@ -97,15 +96,6 @@ export default {
onResetSearch() {
this.$router.push({ name: this.routes.HOME })
},
onScroll() {
if (
document.body.scrollHeight
< window.scrollY + window.innerHeight + 500
) {
this.$store.dispatch(actions.FETCH_PAGE)
}
},
},
}
</script>

View File

@ -2,8 +2,8 @@
const rev = '#1'
const DYNAMIC_CACHE = 'dynamic-cache-v3.2.1' + rev
const STATIC_CACHE = 'static-cache-v3.2.1' + rev
const DYNAMIC_CACHE = 'dynamic-cache-v3.2.4' + rev
const STATIC_CACHE = 'static-cache-v3.2.4' + rev
const FILES_TO_CACHE = [
'./',
]

View File

@ -294,10 +294,10 @@ export default {
})
},
[actions.RENAME_TAG]({ commit, dispatch, state }, { oldName, newName }) {
async [actions.RENAME_TAG]({ commit, dispatch, state }, { oldName, newName }) {
commit(mutations.FETCH_START, { type: 'tag' })
try {
const response = axios
const response = await axios
.put(url(state, `/tag/${oldName}`), {
name: newName,
})
@ -315,7 +315,7 @@ export default {
commit(mutations.FETCH_END, 'tag')
commit(
mutations.SET_ERROR,
AppGlobal.methods.t('bookmarks', 'Failed to create bookmark')
AppGlobal.methods.t('bookmarks', 'Failed to rename tag')
)
throw err
}
@ -583,6 +583,7 @@ export default {
},
[actions.FETCH_PAGE]({ dispatch, commit, state }) {
if (state.fetchState.reachedEnd) return
if (state.loading.bookmarks) return
let canceled = false
commit(mutations.FETCH_START, {
type: 'bookmarks',

View File

@ -157,6 +157,13 @@ export default {
Vue.set(state.fetchState, 'page', 0)
Vue.set(state.fetchState, 'reachedEnd', false)
Vue.set(state.fetchState, 'query', query)
// cancel currently running request
if (typeof state.loading['bookmarks'] === 'function') {
state.loading['bookmarks']()
}
// stop loading
Vue.set(state.loading, 'bookmarks', false)
},
[mutations.FETCH_START](state, event) {
if (typeof state.loading[event.type] === 'function') {

View File

@ -6,6 +6,7 @@ use OCA\Bookmarks\BackgroundJobs\PreviewsJob;
use OC\BackgroundJob\JobList;
use OCA\Bookmarks\Db\Bookmark;
use OCA\Bookmarks\Db\BookmarkMapper;
use OCA\Bookmarks\Service\Authorizer;
use PHPUnit\Framework\TestCase;
/**

View File

@ -196,6 +196,7 @@ class BookmarkControllerTest extends TestCase {
* @throws MultipleObjectsReturnedException
*/
public function setupBookmarks(): void {
$this->authorizer->setUserId($this->userId);
$bookmark1 = Bookmark::fromArray([
'userId' => $this->userId,
'url' => 'https://www.golem.de',
@ -227,6 +228,7 @@ class BookmarkControllerTest extends TestCase {
*/
public function setupBookmarksWithPublicFolder(): void {
$this->setupBookmarks();
$this->authorizer->setUserId($this->userId);
$this->folder1 = new Folder();
$this->folder1->setTitle('foo');
@ -261,6 +263,7 @@ class BookmarkControllerTest extends TestCase {
*/
public function setupBookmarksWithSharedFolder(): void {
$this->setupBookmarksWithPublicFolder();
$this->authorizer->setUserId($this->userId);
$this->folders->createShare($this->folder1->getId(), $this->otherUserId,\OCP\Share\IShare::TYPE_USER, true, false);
}
@ -392,6 +395,32 @@ class BookmarkControllerTest extends TestCase {
$this->assertEquals('', $bookmark->getTitle()); // normalized URL
}
/**
* @throws AlreadyExistsError
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws UrlParseError
* @throws UserLimitExceededError
*/
public function testEditBookmarkFolders(): void {
$this->cleanUp();
$this->setupBookmarksWithPublicFolder();
$this->authorizer->setUserId($this->userId);
$res = $this->controller->newBookmark('https://www.heise.de', 'Heise', 'PublicNoTag', ['four'], [$this->folder1->getId()]);
$this->assertEquals('success', $res->getData()['status'], var_export($res->getData(), true));
$id = $res->getData()['item']['id'];
$this->controller->editBookmark($id, 'https://www.heise.de', '', null, null, [$this->folder2->getId()]);
$bookmark = $this->bookmarkMapper->find($id);
$parents = $this->treeMapper->findParentsOf(TreeMapper::TYPE_BOOKMARK, $id);
$this->assertEquals('https://www.heise.de/', $bookmark->getUrl()); // normalized URL
$this->assertEquals('', $bookmark->getTitle()); // normalized URL
$this->assertEquals([$this->folder2->getId()], array_map(function($f){
return $f->getId();
}, $parents)); // has the folders we set
}
/**
* @throws AlreadyExistsError
* @throws MultipleObjectsReturnedException

View File

@ -219,6 +219,7 @@ class FolderControllerTest extends TestCase {
* @throws MultipleObjectsReturnedException
*/
public function setupBookmarks() {
$this->authorizer->setUserId($this->userId);
$this->folder1 = new Folder();
$this->folder1->setTitle('foo');
$this->folder1->setUserId($this->userId);
@ -260,6 +261,7 @@ class FolderControllerTest extends TestCase {
* @throws MultipleObjectsReturnedException
*/
public function setupPublicFolder(): void {
$this->authorizer->setUserId($this->userId);
$this->publicFolder = new PublicFolder();
$this->publicFolder->setFolderId($this->folder1->getId());
$this->publicFolderMapper->insert($this->publicFolder);
@ -275,6 +277,7 @@ class FolderControllerTest extends TestCase {
* @throws \OCP\AppFramework\Db\DoesNotExistException
*/
public function setupSharedFolder() {
$this->authorizer->setUserId($this->userId);
$this->folders->createShare($this->folder1->getId(), $this->otherUser, \OCP\Share\IShare::TYPE_USER, true, false);
}
@ -578,6 +581,7 @@ class FolderControllerTest extends TestCase {
$this->cleanUp();
$this->setupBookmarks();
$this->setupPublicFolder();
$this->authorizer->setUserId(null);
$output = $this->public->getFolder($this->folder1->getId());
$data = $output->getData();
$this->assertEquals('success', $data['status'], var_export($data, true));
@ -594,6 +598,7 @@ class FolderControllerTest extends TestCase {
public function testReadPublicFail(): void {
$this->cleanUp();
$this->setupBookmarks();
$this->authorizer->setUserId(null);
$output = $this->public->getFolder($this->folder1->getId());
$data = $output->getData();
$this->assertEquals('error', $data['status'], var_export($data, true));
@ -609,6 +614,7 @@ class FolderControllerTest extends TestCase {
$this->cleanUp();
$this->setupBookmarks();
$this->setupPublicFolder();
$this->authorizer->setUserId(null);
$output = $this->public->addFolder('bla', $this->folder1->getId());
$data = $output->getData();
$this->assertEquals('error', $data['status'], var_export($data, true));
@ -625,6 +631,7 @@ class FolderControllerTest extends TestCase {
$this->cleanUp();
$this->setupBookmarks();
$this->setupPublicFolder();
$this->authorizer->setUserId(null);
$output = $this->public->editFolder($this->folder2->getId(), 'blabla');
$data = $output->getData();
$this->assertEquals('error', $data['status'], var_export($data, true));
@ -645,6 +652,7 @@ class FolderControllerTest extends TestCase {
$this->cleanUp();
$this->setupBookmarks();
$this->setupPublicFolder();
$this->authorizer->setUserId(null);
$output = $this->public->deleteFolder($this->folder1->getId());
$data = $output->getData();
$this->assertEquals('error', $data['status'], var_export($data, true));
@ -663,6 +671,7 @@ class FolderControllerTest extends TestCase {
$this->cleanUp();
$this->setupBookmarks();
$this->setupPublicFolder();
$this->authorizer->setUserId(null);
$output = $this->public->getFolderChildrenOrder($this->folder1->getId(), -1);
$data = $output->getData();
$this->assertEquals('success', $data['status'], var_export($data, true));
@ -682,6 +691,7 @@ class FolderControllerTest extends TestCase {
public function testSetFullHierarchyPublicFail(): void {
$this->cleanUp();
$this->setupBookmarks();
$this->authorizer->setUserId(null);
$output = $this->public->setFolderChildrenOrder($this->folder1->getId(), [
['type' => 'bookmark', 'id' => $this->bookmark1Id],
['type' => 'folder', 'id' => $this->folder2->getId()],
@ -713,6 +723,7 @@ class FolderControllerTest extends TestCase {
$this->cleanUp();
$this->setupBookmarks();
$this->setupPublicFolder();
$this->authorizer->setUserId(null);
$output = $this->public->getFolders($this->folder1->getId(), -1);
$data = $output->getData();
$this->assertCount(1, $data['data']);

View File

@ -108,6 +108,21 @@ class TagMapperTest extends TestCase {
$this->assertNotContains(['name' => 'four', 'count' => 0], $allTagsWithCount);
}
/**
* @depends testAddToAndFind
*/
public function testDelete() {
$this->tagMapper->deleteTag($this->userId, 'one');
$allTags = $this->tagMapper->findAll($this->userId);
$this->assertNotContains('one', $allTags);
$this->assertContains('two', $allTags);
$this->assertContains('three', $allTags);
$allTagsWithCount = $this->tagMapper->findAllWithCount($this->userId);
$this->assertContains(['name' => 'two', 'count' => 2], $allTagsWithCount);
$this->assertContains(['name' => 'three', 'count' => 1], $allTagsWithCount);
}
/**
* @depends testAddToAndFind
* @dataProvider singleBookmarksProvider