diff --git a/AGENTS.md b/AGENTS.md index fab108aa8..eed13b7db 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,16 +64,16 @@ There are no PHP unit tests. Three suites exist: ### Backend (`lib/`) - Two parallel controller stacks share logic via `lib/Controller/Helper.php`: - `NotesController` — internal endpoints for the Vue frontend (`/notes/...`) - - `NotesApiController` — the **public versioned REST API** (`/api/v0.2|v1/...`, attachments on v1.4) used by the Android/iOS and third-party clients. It is a stability contract documented in `docs/api/` — changes must stay backward compatible and be reflected there (and in `lib/AppInfo/Capabilities.php` for new API versions). + - `NotesApiController` — the **public versioned REST API** (`/api/v0.2|v1/...`; attachment routes and `GET settings` also accept `v1.4`) used by the Android/iOS and third-party clients. It is a stability contract documented in `docs/api/` — changes must stay backward compatible and be reflected there (new API versions go into `Application::$API_VERSIONS` in `lib/AppInfo/Application.php`, exposed via `Capabilities.php` and the `X-Notes-API-Versions` header). - Routes are declared in `appinfo/routes.php`. - `lib/Service/NotesService.php` is the core: resolves the notes folder, wraps files in `Note` objects (`Note`/`MetaNote` are file wrappers, not entities). `MetaService` maintains the DB metadata cache; `NoteUtil`/`TagService` handle file/tag plumbing. Errors are communicated via typed exceptions in `lib/Service/` which `Helper` maps to HTTP status codes. - `ChunkCursor` + ETags implement chunked/pruned note listing for large collections (see `docs/api/README.md`). - App wiring (event listeners, dashboard widget, search provider, reference provider) lives in `lib/AppInfo/Application.php`. ### Frontend (`src/`) -- Entry points: `main.js` (main app), `dashboard.js` (dashboard widget), `config.js` (admin settings) — one webpack bundle each. +- Entry points: `main.js` (main app) and `dashboard.js` (dashboard widget) — one webpack bundle each. `config.js` only holds polling/autosave intervals. - State lives in three Pinia stores (`src/stores/app.js`, `notes.js`, `sync.js`), aggregated by `src/store.js`. `src/NotesService.js` contains the server-communication layer including the sync queue and conflict handling (`ConflictSolution.vue`); components dispatch through it rather than calling axios directly. -- Note editing has three modes: `EditorEasyMDE.vue` (rich md editing), `EditorMarkdownIt.vue` (preview), `EditorPlain.vue`. +- Note editing has three modes (`noteMode` setting): `rich` (`NoteRich.vue`, embeds the Text app's editor), `edit` (`NotePlain.vue` with `EditorEasyMDE.vue`) and `preview` (`NotePlain.vue` with `EditorMarkdownIt.vue`). ## Conventions diff --git a/README.md b/README.md index 5fb9eefd8..275792f99 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Nextcloud will notify you about possible updates. Please have a look at [CHANGEL Before reporting bugs: * get the newest version of the Notes app -* please consider also installing the [latest development version](https://github.com/nextcloud/notes/archive/master.zip) +* please consider also installing the [latest development version](https://github.com/nextcloud/notes/archive/main.zip) * [check if they have already been reported](https://github.com/nextcloud/notes/issues) @@ -33,8 +33,8 @@ Before reporting bugs: ## :warning: Developer Info -[![Lint](https://github.com/nextcloud/notes/workflows/Lint/badge.svg?branch=master&event=push)](https://github.com/nextcloud/notes/actions?query=workflow%3ALint+event%3Apush+branch%3Amaster) -[![Test](https://github.com/nextcloud/notes/workflows/Test/badge.svg?branch=master&event=push)](https://github.com/nextcloud/notes/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster) +[![Test](https://github.com/nextcloud/notes/actions/workflows/test.yml/badge.svg?branch=main&event=push)](https://github.com/nextcloud/notes/actions/workflows/test.yml?query=branch%3Amain+event%3Apush) +[![Node tests](https://github.com/nextcloud/notes/actions/workflows/node-test.yml/badge.svg?branch=main&event=push)](https://github.com/nextcloud/notes/actions/workflows/node-test.yml?query=branch%3Amain+event%3Apush) ### Building the app @@ -62,6 +62,6 @@ occ config:app:set notes defaultFolder --value="Shared notes" | Setting | Property name | Default | Other available option(s) | |---------|---------------|---------|---------------------------| -| Display mode for notes | noteMode | edit | preview | -| File extension for new notes | fileSuffix | .txt | .md | +| Display mode for notes | noteMode | rich (if the Text app is enabled), otherwise edit | edit, preview | +| File extension for new notes | fileSuffix | .md | .txt | | Folder to store your notes | defaultFolder | Notes | _Custom_ | diff --git a/docs/api/README.md b/docs/api/README.md index 59362ac96..77378ac62 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -30,30 +30,30 @@ We distinguish major and minor versions: - a major version comes with changes that are incompatible to the previous version and therefore would break old clients. Major versions come with a new base URL path. - a minor version has changes that are realized compatible to the previous version. Old clients can still use the current API endpoint, but they need adoption in order to use new features. -### Compability between minor versions +### Compatibility between minor versions Minor versions of the same major version use the same API endpoint (path). Therefore, they must be compatible. In order to realize forward compatibility between minor versions, clients must follow some general rules regarding the API: -- when processing the JSON response, unknown fields must be ignored (e.g. if API version 1.0 does not define the note's attribute "tags", a client must ignore such an unkown field in order to be compatible with a possible future version (e.g. 1.4) which defines such a field) +- when processing the JSON response, unknown fields must be ignored (e.g. if API version 1.0 does not define the note's attribute "tags", a client must ignore such an unknown field in order to be compatible with a possible future version (e.g. 1.4) which defines such a field) - when processing the HTTP response code, a client must be able to handle newly introduced error codes (e.g. if API 1.0 does not explicitly define response code 405, the client must handle it at least like 400; same with a 5xx code). In order to realize backwards compatibility between minor versions, a client must follow the following rules: - when sending a request which uses a feature that wasn't available from beginning of the used major version, the client has to cope with the situation that the server ignores parts of the request -- when processing the JSON response, the server may ommit fields that where not available from beginning of the used major version +- when processing the JSON response, the server may omit fields that were not available from beginning of the used major version If a client requires a certain feature, it should check the list of supported API version from server (see *Capabilities*). -### Capabilites +### Capabilities From Notes app version 3.3, supported API versions can be queried using the [Nextcloud Capabilities API](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html#capabilities-api). A request like - curl -u user:password -X GET -H "OCS-APIRequest: true" -H "Accept: application/json" https://yournextcloud.com/ocs/v2.php/cloud/capabilities + curl -u user:password -X GET -H "OCS-APIRequest: true" -H "Accept: application/json" https://nextcloud.example.com/ocs/v2.php/cloud/capabilities will return the following result (in this example, irrelevant attributes are omitted and formatting was introduced): @@ -63,8 +63,9 @@ will return the following result (in this example, irrelevant attributes are omi "data": { "capabilities": { "notes": { - "api_version": [ "0.2", "1.0" ], - "version": "3.6.0" + "api_version": [ "0.2", "1.3", "1.4" ], + "version": "6.1.0", + "notes_path": "Notes" } } } @@ -74,12 +75,13 @@ will return the following result (in this example, irrelevant attributes are omi | Attribute | Type | Description | since app version | |:--------------|:----------------|:------------|:------------------| -| `api_version` | list of strings | list of supported API version; for each supported major API version, the highest supported minor API version is listed, e.g. `[ "0.2", "1.1" ]` | Notes 3.3 | -| `version` | string | app version, e.g. `"3.6.0"` | Notes 3.6 | +| `api_version` | list of strings | list of supported API versions; for each supported major API version, at least the highest supported minor API version is listed, e.g. `[ "0.2", "1.3", "1.4" ]`. Clients should use the highest listed minor version per major version. | Notes 3.3 | +| `version` | string | app version, e.g. `"6.1.0"` | Notes 3.6 | +| `notes_path` | string or null | path of the user's notes folder, relative to the user folder, e.g. `"Notes"`; `null` if the request is not authenticated | Notes 4.12 | -From Notes app version 3.3, the list of supported API versions is also provided in every response from the Notes API. +From Notes app version 3.3, the list of supported API versions is also provided in the responses from the Notes API (except for the *Get attachment* endpoint). For this, the HTTP header `X-Notes-API-Versions` is used. -It contains a coma-separated list of versions, e.g., `X-Notes-API-Versions: 0.2, 1.0`. +It contains a comma-separated list of versions, e.g., `X-Notes-API-Versions: 0.2, 1.3, 1.4`. ### Processing API version information In order to be compatible to older Notes version, you may want to implement multiple API versions in your client application. @@ -97,9 +99,9 @@ Therefore running Nextcloud **with SSL is highly recommended** otherwise **every You can test your request using `curl`: - curl -u user:password -H "Accept: application/json" https://yournextcloud.com/index.php/apps/notes/api/v1/notes + curl -u user:password -H "Accept: application/json" https://nextcloud.example.com/index.php/apps/notes/api/v1/notes -If you have enabled two-factor authentication you will have to create an app specific password for accessing the API. Please see [Nextcloud documentation](https://docs.nextcloud.com/server/latest/user_manual/session_management.html) for further details. +If you have enabled two-factor authentication you will have to create an app specific password for accessing the API. Please see [Nextcloud documentation](https://docs.nextcloud.com/server/latest/user_manual/en/session_management.html) for further details. ## Input parameters diff --git a/docs/api/v1.md b/docs/api/v1.md index 64ab2da02..9dfe427af 100644 --- a/docs/api/v1.md +++ b/docs/api/v1.md @@ -15,7 +15,9 @@ In this document, the Notes API major version 1 and all its minor versions are d | **1.1** | Notes 3.4 (May 2020) | Filter "Get all notes" by category | | **1.2** | Notes 4.1 (June 2021) | Preventing lost updates, read-only notes, settings | | **1.3** | Notes 4.5 (August 2022) | Allow custom file suffixes | -| **1.4** | Notes 6.1 (August 2026) | Add external image API | +| **1.4** | Notes 4.12.3 (August 2025) | Add attachment API | + +Since Notes 6.1, attachments are stored in a note-specific folder and can be deleted via the API. This did not introduce a new API version; check the app `version` capability (see [README](README.md#capabilities)) if you depend on it. Attachment endpoints can be reached with both base paths `/api/v1/` and `/api/v1.4/`. ## Note attributes @@ -32,6 +34,10 @@ The app and the API is mainly about notes. So, let's have a look about the attri | `category` | string (read/write) | Every note is assigned to a category. By default, the category is an empty string (not null), which means the note is uncategorized. Categories are mapped to folders in the file backend. Illegal characters are automatically removed and the respective folder is automatically created. Sub-categories (mapped to sub-folders) can be created by using `/` as delimiter. | 1.0 | | `favorite` | boolean (read/write) | If a note is marked as favorite, it is displayed at the top of the notes' list. Default is `false`. | 1.0 | | `modified` | integer (read/write) | Unix timestamp for the last modified date/time of the note. If not provided on note creation or content update, the current time is used. | 1.0 | +| `error` | boolean (read‑only) | `true` if the note's content could not be read. In this case, `content` contains an error message instead of the note's content and `readonly` is `true`. The error can be temporary (e.g. if the file is locked), so a later request may succeed. The content is only checked if it is part of the response, so this is always `false` if `content` is excluded (see `exclude` parameter). | 1.0 | +| `errorType` | string (read‑only) | If `error` is `true`, the class name of the exception that occurred while reading the note's content, otherwise an empty string. Values depend on the server and its storage backends and are not stable; use them for display or logging only. | 1.2 | + +Responses also contain the fields `internalPath`, `shareTypes` and `isShared`. They are used internally by the Notes web app, are not part of the API contract and may change at any time. Clients should ignore them. ## Settings @@ -41,18 +47,30 @@ Since API version 1.2, it is possible to change app settings using the API. The | Attribute | Type | Description | since API version | |:----------|:-----|:------------|:------------------| | `notesPath` | string | Path to the folder, where note's files are stored in Nextcloud. The path must be relative to the user folder. Default is the localized string `Notes`. | 1.2 | -| `fileSuffix` | string | Newly created note's files will have this file suffix. For API version 1.2, only the values `.txt` or `.md` are allowed. Since API version 1.3, also custom suffixes can be chosen. Default is `.txt`. | 1.2 | +| `fileSuffix` | string | Newly created note's files will have this file suffix. For API version 1.2, only the values `.txt` or `.md` are allowed. Since API version 1.3, also custom suffixes can be chosen. Default is `.md` (administrators may configure another default). | 1.2 | +| `noteMode` | string | Display mode of the Notes web app: `rich`, `edit` or `preview`. `rich` is only available if the Text app is enabled. | Notes 4.2 | +| `showHidden` | boolean | Show hidden files in the Notes web app. | Notes 6.1 | +| `loadRecentOnStartUp` | boolean | Open the last viewed note on start up of the Notes web app. | Notes 6.1 | + +Settings that were added without a new API version are marked with the app version that introduced them. + + +## Error responses + +Besides the status codes listed for each endpoint, every endpoint except [Get attachment](#get-attachment-get-attachmentid) (which returns `404` for any error) may return: +- `423 Locked`: the note's file is currently locked, retry later. +- `500 Internal Server Error`: unexpected server error. ## Endpoints and Operations The base URL for all calls is: - https://user:password@yournextcloud.com/index.php/apps/notes/api/v1/ + https://user:password@nextcloud.example.com/index.php/apps/notes/api/v1/ All defined routes in the specification are appended to this url. To access all notes for instance use this url (here shown as `curl` command): - curl -u user:password -H "Accept: application/json" https://yournextcloud.com/index.php/apps/notes/api/v1/notes + curl -u user:password -H "Accept: application/json" https://nextcloud.example.com/index.php/apps/notes/api/v1/notes @@ -63,7 +81,7 @@ All defined routes in the specification are appended to this url. To access all | Parameter | Type | Description | since API version | |:----------|:-----|:------------|:------------------| | `category` | string, optional | Filter the result by category name, e.g. `?category=recipes`. Notes with another category are not included in the result. *Compatibility note:* before API v1.1, this parameter is ignored; i.e., the result contains all notes regardless of this parameter. | 1.1 | -| `exclude` | string, optional | Fields which should be excluded from response, seperated with a comma e.g.: `?exclude=content,title`. You can use this in order to reduce transferred data size if you are interested in specific attributes, only. | 1.0 | +| `exclude` | string, optional | Fields which should be excluded from response, separated with a comma e.g.: `?exclude=content,title`. You can use this in order to reduce transferred data size if you are interested in specific attributes, only. | 1.0 | | `pruneBefore` | integer, optional | All notes without change before of this Unix timestamp are purged from the response, i.e. only the attribute `id` is included. You should use the Unix timestamp value from the last request's HTTP response header `Last-Modified` in order to reduce transferred data size. | 1.0 | | `chunkSize` | integer, optional | The response will contain no more than the given number of full notes. If there are more notes, then the result is chunked and the HTTP response header `X-Notes-Chunk-Cursor` is sent with a string value. In order to request the next chunk, a new request have to be made with parameter `chunkCursor` filled with that string value. *Compatibility note:* before API v1.2, this parameter is ignored; i.e., the result contains all notes regardless of this parameter. | 1.2 | | `chunkCursor` | string, optional | To be used together with the parameter `chunkSize`. You must use the string value from the last request's HTTP response header `X-Notes-Chunk-Cursor` in order to get the next chunk of notes. Don't use this parameter for requesting the first chunk. *Compatibility note:* before API v1.2, this parameter is ignored; i.e., the result contains all notes regardless of this parameter. | 1.2 | @@ -100,9 +118,10 @@ No valid authentication credentials supplied.
Details #### Request parameters -| Parameter | Type | Description | -|:------|:-----|:-----| -| `id` | integer, required (path) | ID of the note to query. | +| Parameter | Type | Description | since API version | +|:------|:-----|:-----|:-----| +| `id` | integer, required (path) | ID of the note to query. | 1.0 | +| `exclude` | string, optional | Fields which should be excluded from response, separated with a comma e.g.: `?exclude=content,title`. | 1.0 | | `If-None-Match` | HTTP header, optional | Use this in order to reduce transferred data size (see [HTTP ETag](https://en.wikipedia.org/wiki/HTTP_ETag)). You should use the value from the note's attribute `etag` or from the last request's HTTP response header `ETag`. | 1.2 | #### Response @@ -150,7 +169,7 @@ Note not found. - **Body**: note (see section [Note attributes](#note-attributes)), example see section [Get single note](#get-single-note-get-notesid). ##### 400 Bad Request -Invalid ID supplied. +Invalid title or category supplied. ##### 401 Unauthorized No valid authentication credentials supplied. @@ -235,7 +254,10 @@ None. ```js { "notesPath": "Notes", - "fileSuffix": ".txt" + "fileSuffix": ".md", + "noteMode": "rich", + "showHidden": false, + "loadRecentOnStartUp": true } ``` @@ -287,12 +309,12 @@ No valid authentication credentials supplied. | Parameter | Type | Description | |:----------|:-----------------------------|:-------------------------------------------| | `id` | integer, required (path) | ID of the note to load the attachment from | -| `path` | string, required (request) | Path of the attachment to load | +| `path` | string, required (query) | Path of the attachment, relative to the note's category folder (e.g. the `filename` returned by [Put attachment](#put-attachment-post-attachmentid)) | Example: ```bash -curl -u "user:password" "https://yournextcloud.com/index.php/apps/notes/api/v1.4/attachment/?path=" -o .jpg +curl -u "user:password" "https://nextcloud.example.com/index.php/apps/notes/api/v1.4/attachment/?path=" -o .jpg ``` @@ -305,6 +327,9 @@ Endpoint not supported by installed notes app version (requires API version 1.4) ##### 401 Unauthorized No valid authentication credentials supplied. + +##### 404 Not Found +Note or attachment not found. This status is returned for any error while loading the attachment.
@@ -317,6 +342,7 @@ No valid authentication credentials supplied. | Parameter | Type | Description | |:----------|:------------------------|:------------------------------------------------| | `id` | integer, required (path)| ID of the note to upload the attachment to | +| `file` | file, required (multipart/form-data) | The file to upload | Example: @@ -324,7 +350,7 @@ Example: curl -u "user:password" \ -X POST \ -F "file=@/path/to/image.png" \ - "https://yournextcloud.com/index.php/apps/notes/api/v1.4/attachment/" + "https://nextcloud.example.com/index.php/apps/notes/api/v1.4/attachment/" # The post request will return the path where the image is stored: {"filename":".attachments./image.png"} @@ -342,10 +368,59 @@ curl -u "user:password" \ *Compatibility note:* in Notes 6.0 and earlier, the uploaded file was stored directly in the note's category folder under a randomly generated name, and this field contained only that flat file name (e.g. `"d8aef2005b4f815fec8ade5388240f2c.png"`). Since Notes 6.1, uploads are stored in a note-specific `.attachments.` folder, and this field contains the retained (deduplicated) original file name relative to the notes folder's category directory. Existing attachments stored under the old flat name are still served, so this change is backward compatible for reading. ##### 400 Bad Request +Invalid file name. + +##### 401 Unauthorized +No valid authentication credentials supplied. + +##### 404 Not Found +Note not found. + +##### 405 Method Not Allowed Endpoint not supported by installed notes app version (requires API version 1.4). +##### 500 Internal Server Error +The file could not be stored (e.g. missing upload or insufficient permissions). + + + +### Delete attachment (`DELETE /attachment/{id}`) +
Details + +*(since Notes 6.1, see [Minor versions](#minor-versions))* + +#### Request parameters +| Parameter | Type | Description | +|:----------|:---------------------------|:---------------------------------------------| +| `id` | integer, required (path) | ID of the note the attachment belongs to | +| `path` | string, required (query) | Path of the attachment, e.g. the `filename` returned by [Put attachment](#put-attachment-post-attachmentid). Only the file name is used: only files inside the note's `.attachments.` folder can be deleted. | + +If the attachment folder is empty afterwards, it is removed as well. + +Example: + +```bash +curl -u "user:password" -X DELETE "https://nextcloud.example.com/index.php/apps/notes/api/v1/attachment/?path=.attachments./image.png" +``` + +#### Response +##### 200 OK +Attachment is deleted. + +##### 400 Bad Request +Invalid file name. + ##### 401 Unauthorized No valid authentication credentials supplied. + +##### 403 Forbidden +The note is read-only. + +##### 404 Not Found +Note or attachment not found. + +##### 405 Method Not Allowed +Endpoint not supported by installed notes app version (requires Notes 6.1).
## Preventing lost updates and conflict solution @@ -366,5 +441,5 @@ If an update conflict occurs, the client can use this reference state in order t There are several options on how to merge an attribute: - a) *Let the user decide*: ask the user whether i) overwrite local changes, ii) overwrite remote changes, or iii) save local (or remote) changes as new note. - b) *Let the user merge*: provide an interface which allows for merging the files (you know it from your version control). -- c) *Try to merge automatically*: merge all changes automatically, e.g. for the `content` attribute using the [google-diff-match-patch](https://code.google.com/p/google-diff-match-patch/) ([Demo](https://neil.fraser.name/software/diff_match_patch/svn/trunk/demos/demo_patch.html), [Code](https://github.com/bystep15/google-diff-match-patch)) library. +- c) *Try to merge automatically*: merge all changes automatically, e.g. for the `content` attribute using the [diff-match-patch](https://github.com/google/diff-match-patch) library.