feat(card-grid): Keep-style grid view with list/grid toggle (E04) - #1
Conversation
Rename notes_meta -> notesplus_meta in MetaMapper and base migrations, add idempotent Version6002 create-migration for instances enabled under the old name, and rename built asset tokens (notes-main -> notesplus-main).
Add a minimal FrontMatter parse/serialize layer (ADR-007); Note.getContent()
now returns the body without the fence and setContent() preserves owned attrs.
Wire color through getData, generateEtag, the updateProperty/{color} route, and
the API create/update controllers. Untested locally (no PHP); deploy-verify.
Add a Keep-style 12-color palette (notes-colors.js), a NoteColorPicker swatch grid, and wire setColor through NotesService + the note action menu. Colored notes get a left accent bar in the list. Build verified locally.
…aping Add a single validation choke point in Note::normalizeColorValue (empty->null, else lowercase #rrggbb, else InvalidArgumentException -> HTTP 400 via handleErrorResponse) and drop the duplicated '' ? null handling in the controllers. Memoize getRawContent so getData() reads the file once. Make FrontMatter::unquote undo YAML single-quote doubling so the helper is safe for E05 tags.
normalizeColor() now returns a value only for a valid #rrggbb, so a malformed stored value can never reach the --np-note-color custom property. The swatch picker gains a Back action.
Stand up tests/unit (phpunit.xml + composer autoload-dev + Make target + CI test-unit job). FrontMatterTest covers round-trip, bail cases, fence-in-body, apostrophe/hex quoting and CRLF. APIv1Test::testColor covers the end-to-end round-trip, 400-on-invalid, and clear.
📝 WalkthroughWalkthroughNotesPlus is rebranded across runtime paths, assets, and metadata tables. Notes gain front-matter-backed color, archive, and excerpt support. The frontend adds archive filtering and persisted list/grid views, while PHPUnit, Vitest, API, and Playwright coverage are expanded. ChangesNotesPlus rebranding
Note metadata and archive controls
Grid/list view
Test infrastructure
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant NotesView
participant AppStore
participant NotesGrid
User->>NotesView: Select grid view
NotesView->>AppStore: Persist view mode
AppStore-->>NotesView: Return grid mode
NotesView->>NotesGrid: Render grouped notes
NotesGrid-->>User: Display note cards
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
lib/Service/MetaService.php (1)
205-218: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
generateEtag()now has an unguarded exception path viagetColor()/getArchived().Both new getters read raw file content (via
Note::getRawContent()), which can throw. UnlikegenerateContentEtag()just above (wrapped in try/catch specifically because content reads can fail),generateEtag()callsgetColor()/getArchived()with no protection. Since this runs insidegetAll()'s per-note loop, one unreadable note now aborts meta-sync for all notes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/Service/MetaService.php` around lines 205 - 218, Update MetaService::generateEtag() to guard the getColor() and getArchived() calls against raw-content read failures, following the existing exception-handling approach in generateContentEtag(). Ensure an unreadable note does not abort getAll() processing for the remaining notes, while preserving normal ETag generation for readable notes.lib/Controller/NotesApiController.php (1)
161-207: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winPartial update risk:
colorvalidation can fail after other fields already persisted.
update()appliescontent/modified/title/category/favorite(185-198) beforecolor/archived(199-204). IfsetColor()throws\InvalidArgumentException(invalid hex), the request returns 400, but any earlier mutations in this same call (e.g.content,favorite) have already been written to the file/tags — the client sees "rejected" while the note was actually partially modified. Consider validatingcolorup front (before any mutation) or reordering so validating setters run first.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/Controller/NotesApiController.php` around lines 161 - 207, Validate the optional color before applying any note mutations in update(), using setColor() or the existing color-validation behavior to reject invalid hex values upfront. Ensure an InvalidArgumentException from color validation occurs before content, modified, title/category, favorite, or archived changes are persisted, while preserving normal updates for valid colors.
🧹 Nitpick comments (2)
src/components/NoteColorPicker.vue (1)
7-18: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueMinor a11y semantics nit: this is a mutually-exclusive selection, not independent toggles.
role="group"+aria-pressedon each swatch is valid for independent toggle buttons, but here only one color can be active at a time.role="radiogroup"witharia-checkedon each swatch would better convey the single-select semantics to screen readers.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/NoteColorPicker.vue` around lines 7 - 18, Update the NoteColorPicker container from group semantics to radiogroup, and replace each swatch button’s aria-pressed state with aria-checked while preserving the existing isActive(color.value) selection logic.lib/Service/Note.php (1)
105-183: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy liftRepeated, uncached front-matter parsing on every getter call.
getContent(),getColor(),getArchived()each independently call$this->frontMatter->parse($this->getRawContent()). A singlegetData()call parses the same raw content up to 4 times (color, archived, excerpt→getContent, content). More importantly, sincecolor/archived/excerptare not excludable byNotesController::index()(which hardcodesexclude=['etag','content']specifically to avoid the "expensive" content read noted ongenerateContentEtag), every note's raw file content is now read on every listing/poll request regardless of whether it changed — defeating that optimization.Consider caching the parsed
{attrs, body}result alongside$rawContent(invalidated on write), and/or caching the excerpt server-side (e.g. inMeta, recomputed only whencontentEtagchanges, mirroring the existinggenerateContentEtagcaching pattern) so the frequently-polled listing endpoint doesn't re-read every note's file on each poll.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/Service/Note.php` around lines 105 - 183, The Note getters repeatedly parse and reread raw content, defeating listing optimizations. Update Note’s content-loading flow around getContent(), getColor(), getArchived(), and getExcerpt() to cache the parsed {attrs, body} result alongside the raw content, reuse it across getters, and invalidate the cache whenever the note is written; preserve existing getter values and getData() behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/test.yml:
- Around line 30-40: Update the “Unit tests” step to invoke the PHPUnit binary
installed by setup-php through the job PATH instead of ./vendor/bin/phpunit,
unless PHPUnit is intentionally added to composer.json with the lockfile
updated.
In `@lib/Controller/NotesController.php`:
- Around line 293-306: Wrap the color and archived mutation/read flows in the
same $this->inLockScope(...) mechanism used by the title and category cases.
Ensure Note::setColor and Note::setArchived, along with their corresponding
getters, execute within the lock while preserving the existing null-checks and
result assignment behavior.
In `@lib/Db/MetaMapper.php`:
- Line 18: Update the migration represented by Version6002Date20260725120000 to
preserve existing metadata from notes_meta before MetaMapper switches to
notesplus_meta. Rename or copy the existing table and its rows into
notesplus_meta, handling the case where notes_meta is absent, so existing
metadata remains available after the migration.
In `@lib/Migration/Version3005Date20200528204430.php`:
- Around line 36-37: Keep Version3005Date20200528204430 unchanged; do not alter
its notesplus_meta table operation to perform a rename. Add a new forward
migration that explicitly detects the existing old table and migrates or renames
it to the intended table for installations where the historical migration
already ran.
In `@lib/Migration/Version3005Date20200528204431.php`:
- Line 36: Leave the historical table targets unchanged in
lib/Migration/Version3005Date20200528204431.php lines 36-36 and
lib/Migration/Version3005Date20200528204430.php lines 36-37. Add one new forward
migration that renames or copies notes_meta to notesplus_meta while preserving
rows and indexes, then update MetaMapper to use notesplus_meta only after that
migration; apply the MetaMapper change at lib/Db/MetaMapper.php line 18.
In `@lib/Migration/Version6002Date20260725120000.php`:
- Around line 25-26: Align the migration identifier in
Version6002Date20260725120000 with the version declared in appinfo/info.xml by
renaming it to the appropriate Version6000... prefix, including the class and
migration filename references. Only retain the 6002 prefix if you intentionally
update appinfo/info.xml to the corresponding 6.2.x version.
In `@lib/Service/Note.php`:
- Around line 255-284: Update setColor and setArchived to perform their
read-modify-write operations through the same locking and stale-content
protection used by setTitle/setCategory and full content updates. Ensure
getRawContent and writeRawContent execute within the appropriate inLockScope
context and use the existing ETag/check mechanism, preserving the attribute
mutation behavior while preventing concurrent edits from being overwritten.
- Around line 171-176: Update getData() around getColor() and getArchived() to
catch \Throwable from each raw-content read, matching the existing graceful
handling used for excerpt and content. When either read fails, preserve the
response shape, mark the note data with error=true, and continue processing so
one unreadable note cannot abort the listing.
In `@Makefile`:
- Around line 90-93: Add a .PHONY declaration for the test targets, including
test and test-unit, so matching files or directories cannot cause Make to skip
these recipes.
In `@src/components/NoteActionsMenu.vue`:
- Around line 213-239: Update onColorSelected, onToggleFavorite, and
onToggleArchived to catch failures from setColor, setFavorite, and setArchived
and report them through the existing showError mechanism, matching onRename and
onDeleteNote. Preserve the loading-state cleanup, including resetting
loading.favorite on both success and failure, while ensuring failed toggle
requests provide user feedback.
- Around line 59-76: Update onCategoryChange in NoteActionsMenu.vue to handle
string tag results before changing showCategorySelect: keep the selector open
while a new category is being created so the tag-apply flow can complete. Only
reset/hide the selector and move focus after a category is successfully applied
and differs from the current category, including the corresponding logic
referenced around lines 251–259.
In `@src/grouping.js`:
- Around line 39-49: Update the category grouping logic to mirror
NotesView.vue’s categoryToLabel behavior: derive each group label relative to
the selected category by removing its prefix, and suppress the label when the
group category equals the selected category. Use the existing selected-category
context and preserve grouping order and note membership in the surrounding
grouping function.
In `@vitest.config.js`:
- Around line 6-8: Reorder the imports in vitest.config.js so the
`@vitejs/plugin-vue2` import appears before the node:url import, satisfying the
configured perfectionist/sort-imports rule while leaving defineConfig unchanged.
---
Outside diff comments:
In `@lib/Controller/NotesApiController.php`:
- Around line 161-207: Validate the optional color before applying any note
mutations in update(), using setColor() or the existing color-validation
behavior to reject invalid hex values upfront. Ensure an
InvalidArgumentException from color validation occurs before content, modified,
title/category, favorite, or archived changes are persisted, while preserving
normal updates for valid colors.
In `@lib/Service/MetaService.php`:
- Around line 205-218: Update MetaService::generateEtag() to guard the
getColor() and getArchived() calls against raw-content read failures, following
the existing exception-handling approach in generateContentEtag(). Ensure an
unreadable note does not abort getAll() processing for the remaining notes,
while preserving normal ETag generation for readable notes.
---
Nitpick comments:
In `@lib/Service/Note.php`:
- Around line 105-183: The Note getters repeatedly parse and reread raw content,
defeating listing optimizations. Update Note’s content-loading flow around
getContent(), getColor(), getArchived(), and getExcerpt() to cache the parsed
{attrs, body} result alongside the raw content, reuse it across getters, and
invalidate the cache whenever the note is written; preserve existing getter
values and getData() behavior.
In `@src/components/NoteColorPicker.vue`:
- Around line 7-18: Update the NoteColorPicker container from group semantics to
radiogroup, and replace each swatch button’s aria-pressed state with
aria-checked while preserving the existing isActive(color.value) selection
logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ea7e9749-dcf9-4643-85b1-3280473559b0
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (40)
.github/workflows/test.ymlMakefileappinfo/routes.phpcomposer.jsonlib/AppInfo/DashboardWidget.phplib/Controller/Helper.phplib/Controller/NotesApiController.phplib/Controller/NotesController.phplib/Controller/PageController.phplib/Db/MetaMapper.phplib/Migration/Version3005Date20200528204430.phplib/Migration/Version3005Date20200528204431.phplib/Migration/Version6002Date20260725120000.phplib/Service/FrontMatter.phplib/Service/MetaService.phplib/Service/Note.phppackage.jsonplaywright/e2e/view-toggle.spec.tssrc/NotesService.jssrc/Util.jssrc/components/CategoriesList.vuesrc/components/NoteActionsMenu.vuesrc/components/NoteCard.vuesrc/components/NoteColorPicker.vuesrc/components/NoteItem.vuesrc/components/NotesGrid.vuesrc/components/NotesView.vuesrc/grouping.jssrc/grouping.test.jssrc/notes-colors.jssrc/stores/app.jssrc/stores/notes.jssrc/test-setup.jssrc/view-mode.jssrc/view-mode.test.jstemplates/main.phptests/api/APIv1Test.phptests/unit/Service/FrontMatterTest.phptests/unit/phpunit.xmlvitest.config.js
| case 'color': | ||
| if ($color !== null) { | ||
| $note->setColor($color); | ||
| } | ||
| $result = $note->getColor(); | ||
| break; | ||
|
|
||
| case 'archived': | ||
| if ($archived !== null) { | ||
| $note->setArchived($archived); | ||
| } | ||
| $result = $note->getArchived(); | ||
| break; | ||
|
|
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
color/archived cases skip the lock scope used for title/category.
title and category mutations run inside $this->inLockScope(...) because they touch the file. color/archived also mutate the file's raw content (via Note::setColor/setArchived's read-modify-write of the whole file), but are called directly here with no lock, unlike its siblings.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/Controller/NotesController.php` around lines 293 - 306, Wrap the color
and archived mutation/read flows in the same $this->inLockScope(...) mechanism
used by the title and category cases. Ensure Note::setColor and
Note::setArchived, along with their corresponding getters, execute within the
lock while preserving the existing null-checks and result assignment behavior.
| class MetaMapper extends QBMapper { | ||
| public function __construct(IDBConnection $db) { | ||
| parent::__construct($db, 'notes_meta'); | ||
| parent::__construct($db, 'notesplus_meta'); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Migrate existing metadata before switching the mapper table.
Existing installations have metadata in notes_meta; changing MetaMapper to notesplus_meta makes those rows invisible unless a new migration renames/copies the table and preserves its data. The supplied Version6002Date20260725120000.php context creates a new table, but does not show a transfer from notes_meta.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/Db/MetaMapper.php` at line 18, Update the migration represented by
Version6002Date20260725120000 to preserve existing metadata from notes_meta
before MetaMapper switches to notesplus_meta. Rename or copy the existing table
and its rows into notesplus_meta, handling the case where notes_meta is absent,
so existing metadata remains available after the migration.
| if ($schema->hasTable('notesplus_meta')) { | ||
| $schema->dropTable('notesplus_meta'); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Do not modify this historical migration to perform the table rename.
Existing installations that already ran Version3005Date20200528204430 will not rerun it, so changing the target from notes_meta does not migrate their data. Keep historical migrations immutable and add a new forward migration that handles the old table explicitly.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/Migration/Version3005Date20200528204430.php` around lines 36 - 37, Keep
Version3005Date20200528204430 unchanged; do not alter its notesplus_meta table
operation to perform a rename. Add a new forward migration that explicitly
detects the existing old table and migrates or renames it to the intended table
for installations where the historical migration already ran.
| $schema = $schemaClosure(); | ||
|
|
||
| $table = $schema->createTable('notes_meta'); | ||
| $table = $schema->createTable('notesplus_meta'); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift
Implement the metadata-table rename in one new forward migration. Editing historical migrations can cause fresh-install duplicate-table failures, while existing installations will retain rows in notes_meta and lose mapper visibility after the switch.
lib/Migration/Version3005Date20200528204431.php#L36-L36: leave the historical creation target unchanged; avoid creatingnotesplus_metatwice.lib/Migration/Version3005Date20200528204430.php#L36-L37: leave the historical drop target unchanged; do not rely on an already-applied migration for upgrades.lib/Db/MetaMapper.php#L18-L18: switch tonotesplus_metaonly after a forward migration renames/copies the existing table and preserves its rows and indexes.
📍 Affects 3 files
lib/Migration/Version3005Date20200528204431.php#L36-L36(this comment)lib/Migration/Version3005Date20200528204430.php#L36-L37lib/Db/MetaMapper.php#L18-L18
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/Migration/Version3005Date20200528204431.php` at line 36, Leave the
historical table targets unchanged in
lib/Migration/Version3005Date20200528204431.php lines 36-36 and
lib/Migration/Version3005Date20200528204430.php lines 36-37. Add one new forward
migration that renames or copies notes_meta to notesplus_meta while preserving
rows and indexes, then update MetaMapper to use notesplus_meta only after that
migration; apply the MetaMapper change at lib/Db/MetaMapper.php line 18.
| test: test-unit test-api | ||
|
|
||
| test-unit: | ||
| phpunit -c tests/unit/phpunit.xml --testdox |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Declare the test targets as phony.
Without a .PHONY declaration, a file or directory named test can cause Make to skip the test suite.
Proposed fix
+.PHONY: test test-unit test-api
+
test: test-unit test-api📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| test: test-unit test-api | |
| test-unit: | |
| phpunit -c tests/unit/phpunit.xml --testdox | |
| .PHONY: test test-unit test-api | |
| test: test-unit test-api | |
| test-unit: | |
| phpunit -c tests/unit/phpunit.xml --testdox |
🧰 Tools
🪛 checkmake (0.3.2)
[warning] 90-90: Target "test" should be declared PHONY.
(phonydeclared)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Makefile` around lines 90 - 93, Add a .PHONY declaration for the test
targets, including test and test-unit, so matching files or directories cannot
cause Make to skip these recipes.
Source: Linters/SAST tools
| <NcActionInput | ||
| v-else | ||
| :model-value="note.category" | ||
| type="multiselect" | ||
| label="label" | ||
| track-by="id" | ||
| :multiple="false" | ||
| :options="categories" | ||
| :disabled="loading.category" | ||
| :taggable="true" | ||
| @input="onCategoryChange" | ||
| @search-change="onCategoryChange" | ||
| > | ||
| <template #icon> | ||
| <FolderOutlineIcon :size="20" /> | ||
| </template> | ||
| {{ t('notesplus', 'Change category') }} | ||
| </NcActionInput> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)NoteActionsMenu\.vue$|(^|/)package\.json$|(^|/)pnpm-lock\.yaml$|(^|/)package-lock\.json$|(^|/)yarn\.lock$' || true
echo "== target outline =="
ast-grep outline src/components/NoteActionsMenu.vue --view compact || true
echo "== target relevant sections =="
sed -n '1,120p' src/components/NoteActionsMenu.vue
sed -n '120,280p' src/components/NoteActionsMenu.vue
echo "== package versions =="
if [ -f package.json ]; then jq -r '.dependencies["`@nextcloud/vue`"],.devDependencies["`@nextcloud/vue`"]' package.json 2>/dev/null || grep -n '`@nextcloud/vue`' package.json; fi
if [ -f pnpm-lock.yaml ]; then grep -n '`@nextcloud/vue`:' pnpm-lock.yaml | head -20 || true; fiRepository: zentala/notes-plus
Length of output: 7886
🌐 Web query:
@nextcloud/vue 8.39.0 NcActionInput multiselect taggable search-change event tag event
💡 Result:
In @nextcloud/vue version 8.39.0, the NcActionInput component uses the NcSelect component internally when configured with type="multiselect" [1][2][3]. Regarding the specific events you mentioned: 1. Search-change event: NcActionInput itself does not explicitly expose a search-change event. Because it wraps NcSelect for multiselect functionality, it primarily emits standard Vue events such as input, change, and update:value [2][3]. If you require search functionality (e.g., filtering options based on user input), you should typically interact with the underlying NcSelect or use a dedicated search component, as NcActionInput is designed as a higher-level wrapper for simple actions. 2. Tag event: There is no native tag event emitted by NcActionInput. When using type="multiselect", the component relies on the v-model binding (via the update:value event) to manage the array of selected items. Interactions related to adding or removing tags are handled through this v-model update cycle. If you are currently using NcActionInput for complex multiselect requirements, it is recommended to use the NcSelect component directly [4][5]. NcSelect provides richer support for advanced features like taggable inputs, custom search filtering, and specific events (such as search-change or tag-related events) that are not exposed by the restricted API of NcActionInput [4]. You can find the official documentation for NcSelect and other components in the Nextcloud Vue Style Guide [6]. Since you are on the v8.x stable branch, refer to the stable8 documentation link provided in the library's repository README [7].
Citations:
- 1: https://app.unpkg.com/@nextcloud/vue@9.8.2/files/dist/chunks/NcActionInput-BO0lB4VP.mjs
- 2: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcActionInput-BxlypAgV.mjs
- 3: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcActionInput-TP6P798j.cjs
- 4: Use
NcSelectinstead ofNcMultiselectin the components nextcloud-libraries/nextcloud-vue#3743 - 5: Use
NcSelectinstead ofNcMultiselectin the components nextcloud-libraries/nextcloud-vue#3743 - 6: https://nextcloud-vue-components.netlify.app/
- 7: https://github.com/nextcloud/nextcloud-vue/
🌐 Web query:
@nextcloud/vue 8.39.0 NcActionInput source NcSelect multiselect taggable
💡 Result:
In @nextcloud/vue v8.39.0, the NcActionInput component is a wrapper intended to provide a unified interface for various input types, including multiselect fields [1]. As part of the library's modernization efforts, deprecated components like NcMultiselect have been removed [2], and developers are encouraged to use NcSelect instead [2][3]. NcActionInput supports a multiselect type through its type prop, which internally utilizes NcSelect [1]. To implement a multiselect or taggable interface, you should rely on NcSelect, which is based on the underlying vue-select library [4][5]. Key implementation details: 1. Multiselect and Taggable Functionality: Since NcMultiselect is deprecated, NcSelect is the standard component for handling both single and multiple selections [2][3]. For multiselect behavior, you typically configure NcSelect with the multiple prop [6]. When taggable behavior is required (allowing users to create new options), this is usually handled by the underlying vue-select capabilities exposed via NcSelect [4][5]. 2. NcActionInput Integration: You can set the type prop of NcActionInput to "multiselect" [1]. While NcActionInput simplifies certain UI patterns, if your use case requires complex taggable or multiselect behaviors, it is often more flexible to use NcSelect directly, as seen in many Nextcloud internal applications [4][5]. 3. Documentation: For specific props and event usage, refer to the Nextcloud Vue Style Guide [3]. Because v8.x targets Nextcloud 28+ (Vue 2), ensure you are referencing the stable8 documentation site [7][8]. For the most robust implementation, verify the current properties passed to NcSelect in your specific environment, as it serves as the primary component for selection inputs [3][9]. If you require taggable inputs, ensure your NcSelect instance is configured to allow new option creation, which may require handling custom events or props provided by the underlying select implementation [4][5].
Citations:
- 1: https://app.unpkg.com/@nextcloud/vue@9.8.2/files/dist/chunks/NcActionInput-BO0lB4VP.mjs
- 2: Use
NcSelectinstead ofNcMultiselectin the components nextcloud-libraries/nextcloud-vue#3743 - 3: https://nextcloud-vue-components.netlify.app/
- 4: https://github.com/nextcloud/tasks/blob/main/src/components/AppSidebar/TagsItem.vue
- 5: https://github.com/nextcloud/tasks/blob/main/src/components/AppSidebar/MultiselectItem.vue
- 6: https://app.unpkg.com/@nextcloud/vue@8.23.1/files/dist/chunks/NcSelectTags-BKtOWgrz.mjs
- 7: https://github.com/nextcloud/nextcloud-vue/
- 8: https://www.npmjs.com/package/@nextcloud/vue
- 9: https://help.nextcloud.com/t/dropdown-list-field/237908
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== lockfile `@nextcloud/vue` entries =="
python3 - <<'PY'
from pathlib import Path
p=Path('package-lock.json')
if p.exists():
import json
data=json.loads(p.read_text())
for key, val in data.get('packages',{}).items():
if key.endswith('node_modules/@nextcloud/vue') or key == 'node_modules/@nextcloud/vue':
print(key, val.get('version'), val.get('resolved', ''))
PY
echo "== installed component sources if available =="
for d in node_modules/@nextcloud/vue/dist/chunks node_modules/@nextcloud/vue/node_modules/@nextcloud/vue/dist/chunks; do
if [ -d "$d" ]; then
echo "--- $d"
find "$d" -maxdepth 3 \( -name '*NcActionInput*' -o -name '*NcSelect*' -o -name '*Multiselect*' \) | sed -n '1,80p'
fi
done
echo "== package source inside tgz if `@nextcloud/vue` resolved to tarball =="
node - <<'JS'
const fs = require('fs')
const path = require('path')
const lock = JSON.parse(fs.readFileSync('package-lock.json','utf8'))
let pkg
for (const [key, val] of Object.entries(lock.packages || {})) {
if (key === 'node_modules/@nextcloud/vue') pkg = val; break
if (key.endsWith('node_modules/@nextcloud/vue')) pkg = val;
}
console.log(JSON.stringify(pkg, null, 2))
JSRepository: zentala/notes-plus
Length of output: 539
🌐 Web query:
@nextcloud/vue 8.39.0 source NcSelect tags taggable @tag @update:value
💡 Result:
In @nextcloud/vue version 8.39.0, NcSelectTags is a wrapper component designed to handle tagging logic, which internally utilizes the NcSelect component [1][2]. Regarding your query about taggable behavior and value updates: 1. Handling Value Updates: NcSelectTags emits an input event (or update:modelValue/update:model-value events depending on internal configuration) to communicate changes to the parent component [3][1][2]. The component handles the mapping between tag objects and IDs based on the multiple property [1][2]. If you need to intercept value updates, you should listen for these events, typically using v-model or @input in your parent template [3][4]. 2. Taggable Behavior: The NcSelectTags component is specifically purpose-built for tagging interfaces within Nextcloud [1][2]. It manages options filtering and tag selection internally [1][2]. If you are experiencing issues with tag search or selection, ensure that your props (such as fetchTags or options) are correctly passed to the component, as it automatically configures the underlying NcSelect instance [1][2]. 3. Component Evolution: Note that NcSelectTags is the recommended component for tag-related selections, whereas NcSelect is a general-purpose super-component [5][6]. If you are working on a migration or a new implementation, rely on the official Nextcloud Vue Style Guide for the most up-to-date usage patterns for these components [7]. If you are looking for specific event signatures, NcSelectTags in version 8.x typically maps selection changes to the model through its internal handleInput method, which then triggers the standard Vue event emission for v-model compatibility [1][2].
Citations:
- 1: https://app.unpkg.com/@nextcloud/vue@8.23.1/files/dist/chunks/NcSelectTags-BKtOWgrz.mjs
- 2: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcSelectTags-BceJx_FU.cjs
- 3: https://github.com/nextcloud/server/blob/f2ea807b/apps/workflowengine/src/components/Checks/FileSystemTag.vue
- 4: https://help.nextcloud.com/t/dropdown-list-field/237908
- 5: feat!: split
NcSelectsuper component intoNcSelectandNcSelectUsersnextcloud-libraries/nextcloud-vue#6732 - 6: Replace deprecated NcMultiselect with recommended NcSelect nextcloud/forms#1471
- 7: https://nextcloud-vue-components.netlify.app/
🌐 Web query:
@nextcloud/vue 8.39.0 source NcActionInput multiselect NcSelect search-change
💡 Result:
In @nextcloud/vue v8.39.0, the NcActionInput component serves as a wrapper for various input types, including NcSelect [1]. When using NcActionInput with the type set to multiselect or select, the underlying NcSelect component typically handles events by passing them through [2]. Regarding search functionality and events: 1. Event Name: The event emitted by NcSelect when the search query changes is named 'search' [3]. 2. Usage: This 'search' event is designed to allow developers to implement server-side searching or custom filtering by listening to the query string emitted during typing [3]. 3. Implementation: If you are using NcActionInput and need to capture search changes from an embedded NcSelect, you should be able to listen for the 'search' event directly on the NcActionInput component, as it is documented to pass through all events for the NcSelect component [2]. For authoritative guidance on available props and events for your specific version, refer to the stable8 documentation portal, which matches the v8.x release line [4][5]: https://stable8--nextcloud-vue-components.netlify.app/ [4].
Citations:
- 1: https://app.unpkg.com/@nextcloud/vue@9.8.2/files/dist/chunks/NcActionInput-BO0lB4VP.mjs
- 2: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcActionInput-BxlypAgV.mjs.map
- 3: fix(NcSelectUsers): add missing
searchevent nextcloud-libraries/nextcloud-vue#6811 - 4: https://www.npmjs.com/package/@nextcloud/vue
- 5: https://github.com/nextcloud-libraries/nextcloud-vue/blob/main/README.md
🌐 Web query:
@nextcloud/vue stable8 NcSelect source search-change tag taggable event search
💡 Result:
In the @nextcloud/vue library, specifically within the stable8 branch (which supports Vue 2), a significant update was made regarding search functionality for selection components [1][2]. A key development for the NcSelectUsers component was the addition of a missing search event, which was backported to stable8 (referenced in PR #6838 and PR #6811) [1][3]. This event is specifically intended to be used for triggering custom search logic (e.g., fetching more users) rather than relying on preloaded data [1]. Regarding your query on NcSelect, tag, and taggable functionality: 1. NcSelect: In recent versions, NcSelectUsers has been introduced as a recommended component for handling user selection, often serving as a replacement for older user-select property approaches within NcSelect [3][4]. 2. Search-change event: While the search event is explicitly documented as being added to NcSelectUsers to handle search-driven data fetching [1], standard NcSelect components typically inherit event patterns from the underlying Vue libraries (like vue-multiselect) they wrap [5]. 3. Tagging: If you are looking for "taggable" functionality, it is often implemented via component props or slots that manage arrays of options or custom input handling, as seen in various Nextcloud application implementations (e.g., tagging in tasks) [5]. For the most accurate implementation details tailored to your version, please consult the official stable8 documentation site, as the stable8 branch maintains its own documentation separate from the main (Vue 3) branch [2][6]. Documentation for stable8: https://stable8--nextcloud-vue-components.netlify.app [2]
Citations:
- 1: fix(NcSelectUsers): add missing
searchevent nextcloud-libraries/nextcloud-vue#6811 - 2: https://github.com/nextcloud/nextcloud-vue/
- 3: https://github.com/nextcloud-libraries/nextcloud-vue/blob/v8.26.1/CHANGELOG.md
- 4: https://github.com/nextcloud/nextcloud-vue/blob/master/CHANGELOG.md
- 5: https://github.com/nextcloud/tasks/blob/main/src/components/AppSidebar/TagsItem.vue
- 6: https://npmx.dev/package/@nextcloud/vue
🌐 Web query:
@nextcloud/vue 8.39.0 NcSelect source searchChangeEvent inputEvent taggable
💡 Result:
In @nextcloud/vue v8.39.0, NcSelect is a wrapper component that leverages vue-select (specifically compatible with the library's internal implementation) [1][2]. Key details regarding your requested features: searchChangeEvent: The NcSelect component generally exposes events from the underlying select library [3]. In the context of search functionality, events such as search (often mapped to the vue-select 'search' event) are used to trigger external search requests rather than preloading all options [4]. inputEvent: Similar to other form components in the library, input events are handled via standard Vue model binding (v-model) and event forwarding [3][5]. Recent updates to NcSelect have focused on forwarding standard Vue events (such as blur, focus, and keydown) to ensure consistent behavior with other input components like NcTextField [3]. taggable: The taggable functionality (allowing users to create new tags) is typically handled via the underlying vue-select properties that NcSelect wraps. If you encounter issues where search or tagging does not behave as expected, it is often due to the interaction between custom filters (e.g., options-filter) and the search input state [6]. For implementation, check the official stable8 documentation for the most accurate prop and event signatures for v8.39.0 [7][8]. Recent maintenance has improved the integration of NcTextField within the search slot to provide a consistent floating label design, which also ensures that event forwarding for inputs is standardized [3]. If you are experiencing specific bugs with tag search or input events, ensure that any custom filters are applied correctly, as they may override search behavior depending on the component version [6].
Citations:
- 1: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcSelect-CKgkjF4m.cjs
- 2: https://app.unpkg.com/@nextcloud/vue@9.8.2/files/dist/chunks/NcSelect-BOFzoCwK.mjs
- 3: fix(NcSelect): floating label design using NcTextField nextcloud-libraries/nextcloud-vue#8570
- 4: fix(NcSelectUsers): add missing
searchevent nextcloud-libraries/nextcloud-vue#6811 - 5: https://help.nextcloud.com/t/dropdown-list-field/237908
- 6: Tag search does not work anymore nextcloud-libraries/nextcloud-vue#3662
- 7: https://github.com/nextcloud/nextcloud-vue/
- 8: https://github.com/nextcloud-libraries/nextcloud-vue/blob/main/README.md
🏁 Script executed:
#!/bin/bash
set -euo pipefail
if [ -f package-lock.json ]; then
echo "== package-lock relevant NcActionInput snippets =="
rg -n '"NcActionInput"' -C 3 package-lock.json || true
fi
echo "== lockfile all `@nextcloud/vue` 8.39.0 entries =="
python3 - <<'PY'
import json
from pathlib import Path
data=json.loads(Path('package-lock.json').read_text())
for k,v in data.get('packages',{}).items():
if v.get('version') == '8.39.0' and '`@nextcloud/vue`' in k:
print(k, v.get('resolved'))
PYRepository: zentala/notes-plus
Length of output: 343
Handle created categories before hiding the selector.
onCategoryChange() resets showCategorySelect unconditionally before inspecting result, so creating a new category text can close the category selector before a tag-apply path can complete. Detect string tag results before resetting the panel, and only reset/move focus when the category is actually applied and changed. Also applies to 251-259.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/NoteActionsMenu.vue` around lines 59 - 76, Update
onCategoryChange in NoteActionsMenu.vue to handle string tag results before
changing showCategorySelect: keep the selector open while a new category is
being created so the tag-apply flow can complete. Only reset/hide the selector
and move focus after a category is successfully applied and differs from the
current category, including the corresponding logic referenced around lines
251–259.
| async onColorSelected(color) { | ||
| this.showColorSelect = false | ||
| if ((this.note.color ?? null) === (color ?? null)) { | ||
| return | ||
| } | ||
| this.loading.color = true | ||
| try { | ||
| await setColor(this.note.id, color) | ||
| } finally { | ||
| this.loading.color = false | ||
| } | ||
| }, | ||
|
|
||
| onToggleFavorite() { | ||
| this.loading.favorite = true | ||
| setFavorite(this.note.id, !this.note.favorite) | ||
| .catch(() => { | ||
| }) | ||
| .then(() => { | ||
| this.loading.favorite = false | ||
| }) | ||
| }, | ||
|
|
||
| onToggleArchived() { | ||
| setArchived(this.note.id, !this.note.archived) | ||
| .catch(() => {}) | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Favorite/archive/color toggles fail silently.
onColorSelected, onToggleFavorite, and onToggleArchived swallow request failures with empty .catch(() => {})/no catch at all, unlike onRename/onDeleteNote which call showError. If setColor/setFavorite/setArchived fail (network/ETag conflict), the user gets no indication the action didn't persist.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/NoteActionsMenu.vue` around lines 213 - 239, Update
onColorSelected, onToggleFavorite, and onToggleArchived to catch failures from
setColor, setFavorite, and setArchived and report them through the existing
showError mechanism, matching onRename and onDeleteNote. Preserve the
loading-state cleanup, including resetting loading.favorite on both success and
failure, while ensuring failed toggle requests provide user feedback.
| // category selected: one group per distinct category, preserving order | ||
| const groups = [] | ||
| for (const note of notes) { | ||
| const last = groups[groups.length - 1] | ||
| if (!last || last.key !== note.category) { | ||
| groups.push({ key: note.category, label: note.category, notes: [] }) | ||
| } | ||
| groups[groups.length - 1].notes.push(note) | ||
| } | ||
| return groups | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Category-group labels use the full path and never suppress the selected category.
In list view, categoryToLabel() strips the selected-category prefix and the caption is hidden entirely when the group is the selected category (category !== group.category). Here, label is always the raw note.category, so in grid mode every subcategory caption shows the absolute path instead of the relative name, and the selected category itself gets a redundant caption. This is a real, user-visible divergence between the two view modes for the same underlying data.
🐛 Proposed fix (mirrors NotesView.vue's `categoryToLabel`)
+import { categoryLabel } from './Util.js'
+
export function groupNotesForGrid(notes, selectedCategory) {
if (selectedCategory === null) {
...
}
// category selected: one group per distinct category, preserving order
const groups = []
for (const note of notes) {
const last = groups[groups.length - 1]
if (!last || last.key !== note.category) {
- groups.push({ key: note.category, label: note.category, notes: [] })
+ const label = note.category === selectedCategory
+ ? null
+ : categoryLabel(note.category.substring(selectedCategory.length + 1))
+ groups.push({ key: note.category, label, notes: [] })
}
groups[groups.length - 1].notes.push(note)
}
return groups
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // category selected: one group per distinct category, preserving order | |
| const groups = [] | |
| for (const note of notes) { | |
| const last = groups[groups.length - 1] | |
| if (!last || last.key !== note.category) { | |
| groups.push({ key: note.category, label: note.category, notes: [] }) | |
| } | |
| groups[groups.length - 1].notes.push(note) | |
| } | |
| return groups | |
| } | |
| import { categoryLabel } from './Util.js' | |
| // category selected: one group per distinct category, preserving order | |
| const groups = [] | |
| for (const note of notes) { | |
| const last = groups[groups.length - 1] | |
| if (!last || last.key !== note.category) { | |
| const label = note.category === selectedCategory | |
| ? null | |
| : categoryLabel(note.category.substring(selectedCategory.length + 1)) | |
| groups.push({ key: note.category, label, notes: [] }) | |
| } | |
| groups[groups.length - 1].notes.push(note) | |
| } | |
| return groups | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/grouping.js` around lines 39 - 49, Update the category grouping logic to
mirror NotesView.vue’s categoryToLabel behavior: derive each group label
relative to the selected category by removing its prefix, and suppress the label
when the group category equals the selected category. Use the existing
selected-category context and preserve grouping order and note membership in the
surrounding grouping function.
| import { fileURLToPath } from 'node:url' | ||
| import vue from '@vitejs/plugin-vue2' | ||
| import { defineConfig } from 'vitest/config' |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the import-order lint error.
The configured perfectionist/sort-imports rule requires @vitejs/plugin-vue2 to appear before node:url.
Proposed fix
-import { fileURLToPath } from 'node:url'
import vue from '`@vitejs/plugin-vue2`'
+import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vitest/config'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { fileURLToPath } from 'node:url' | |
| import vue from '@vitejs/plugin-vue2' | |
| import { defineConfig } from 'vitest/config' | |
| import vue from '`@vitejs/plugin-vue2`' | |
| import { fileURLToPath } from 'node:url' | |
| import { defineConfig } from 'vitest/config' |
🧰 Tools
🪛 ESLint
[error] 7-7: Expected "@vitejs/plugin-vue2" to come before "node:url".
(perfectionist/sort-imports)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@vitest.config.js` around lines 6 - 8, Reorder the imports in vitest.config.js
so the `@vitejs/plugin-vue2` import appears before the node:url import, satisfying
the configured perfectionist/sort-imports rule while leaving defineConfig
unchanged.
Source: Linters/SAST tools
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/Db/MetaMapper.php`:
- Line 18: Update Version6002Date20260725120000 to migrate rows from the legacy
notes_meta table before MetaMapper uses notesplus_meta, preserving metadata and
ETag values. Make the migration tolerate an absent legacy table and remain safe
to rerun without duplicating or overwriting existing data.
In `@lib/Service/SettingsService.php`:
- Around line 153-159: Update the setting-processing flow in setPublic() to
validate that $name exists in $this->attrs before reading its default or other
nested values. Drop or reject unknown setting names first, then retain the
existing default-removal behavior for recognized settings.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: eab338a6-f97e-49d8-b9b7-c13804788c58
📒 Files selected for processing (46)
.github/workflows/test.ymlcomposer.jsonlib/AppInfo/Application.phplib/AppInfo/BeforeShareCreatedListener.phplib/AppInfo/BeforeTemplateRenderedListener.phplib/AppInfo/Capabilities.phplib/AppInfo/DashboardWidget.phplib/AppInfo/SearchProvider.phplib/Controller/ChunkCursor.phplib/Controller/ETagDoesNotMatchException.phplib/Controller/Helper.phplib/Controller/NotesApiController.phplib/Controller/NotesController.phplib/Controller/PageController.phplib/Controller/SettingsController.phplib/Db/Meta.phplib/Db/MetaMapper.phplib/Listener/NoteFileEventsListener.phplib/Migration/Cleanup.phplib/Migration/EditorHint.phplib/Migration/Version3005Date20200528204430.phplib/Migration/Version3005Date20200528204431.phplib/Reference/NoteReferenceProvider.phplib/Service/ImageNotWritableException.phplib/Service/InsufficientStorageException.phplib/Service/MetaNote.phplib/Service/MetaService.phplib/Service/Note.phplib/Service/NoteDoesNotExistException.phplib/Service/NoteNotWritableException.phplib/Service/NoteUtil.phplib/Service/NotesFolderException.phplib/Service/NotesService.phplib/Service/SettingsService.phplib/Service/TagService.phplib/Service/Util.phpplaywright/start-nextcloud-server.mjssrc/NotesService.jssrc/components/EditorEasyMDE.vuesrc/components/EditorMarkdownIt.vuesrc/router.jstests/api/APIv02Test.phptests/api/APIv1Test.phptests/api/AbstractAPITest.phptests/api/CapabilitiesTest.phptests/api/CommonAPITest.php
🚧 Files skipped from review as they are similar to previous changes (4)
- composer.json
- src/NotesService.js
- lib/Controller/PageController.php
- lib/Service/Note.php
|
|
||
| class MetaMapper extends QBMapper { | ||
| public function __construct(IDBConnection $db) { | ||
| parent::__construct($db, 'notesplus_meta'); |
There was a problem hiding this comment.
Preserve legacy metadata before switching the mapper table.
MetaMapper now reads and writes notesplus_meta, while existing installations may still have rows in notes_meta. Ensure Version6002Date20260725120000 renames or copies the legacy rows before this mapper is used, handles an absent legacy table, and is safe to rerun; otherwise existing metadata and ETag state become invisible.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/Db/MetaMapper.php` at line 18, Update Version6002Date20260725120000 to
migrate rows from the legacy notes_meta table before MetaMapper uses
notesplus_meta, preserving metadata and ETag values. Make the migration tolerate
an absent legacy table and remain safe to rerun without duplicating or
overwriting existing data.
| $default = is_callable($this->attrs[$name]['default']) ? $this->attrs[$name]['default']($uid) : $this->attrs[$name]['default']; | ||
| if (!$writeDefaults && (!array_key_exists($name, $this->attrs) | ||
| || $value === null | ||
| || $value === $default | ||
| )) { | ||
| unset($settings[$name]); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Guard unknown setting names before dereferencing $this->attrs[$name].
NotesApiController::setSettings() passes request parameters directly to setPublic(). An unrecognized key reaches Line 153 before the unknown-key check on Lines 154-157, producing undefined-key/offset warnings and potentially failing the request. Drop or reject unknown keys first.
Proposed fix
foreach ($settings as $name => $value) {
+ if (!array_key_exists($name, $this->attrs)) {
+ unset($settings[$name]);
+ continue;
+ }
if ($value !== null && array_key_exists($name, $this->attrs)) {
$settings[$name] = $value = $this->attrs[$name]['validate']($value);
}
if ($name === 'notesPath' && $value !== null) {
continue;
}
$default = is_callable($this->attrs[$name]['default']) ? $this->attrs[$name]['default']($uid) : $this->attrs[$name]['default'];
- if (!$writeDefaults && (!array_key_exists($name, $this->attrs)
+ if (!$writeDefaults && (
|| $value === null
|| $value === $default
)) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/Service/SettingsService.php` around lines 153 - 159, Update the
setting-processing flow in setPublic() to validate that $name exists in
$this->attrs before reading its default or other nested values. Drop or reject
unknown setting names first, then retain the existing default-removal behavior
for recognized settings.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/NoteItem.vue (1)
82-99: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winUse the Vue 2
valuebinding for the category multiselect.This app uses
vue2.7, while@nextcloud/vue8.x multiselect input bindings usevalue/@update:value, not Vue 3model-value; as written, the existing category is not passed back into the component and can appear unselected.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/NoteItem.vue` around lines 82 - 99, Update the category NcActionInput binding in NoteItem.vue to use Vue 2’s value prop instead of model-value, and change the corresponding event handling to the `@update`:value contract expected by `@nextcloud/vue` 8.x. Preserve the existing note.category value and onCategoryChange behavior.
🧹 Nitpick comments (1)
src/components/noteActions.js (1)
167-170: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueArchive toggle has no loading state or error surface.
Unlike the favorite/color handlers, this swallows the rejection and never sets a loading flag, so a failed archive silently leaves the row unchanged. Consider reusing the
loadingpattern for consistent feedback.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/noteActions.js` around lines 167 - 170, Update onToggleArchived in the note action handlers to reuse the existing loading-state pattern used by the favorite/color handlers: set loading before calling setArchived, surface failures instead of swallowing the rejection, and clear loading when the operation settles so users receive consistent feedback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/noteActions.js`:
- Around line 182-190: Update onCategoryChange to wrap the awaited setCategory
call in a try/finally, matching onColorSelected, so loading.category is always
reset when setCategory succeeds or rejects; preserve the existing category
validation and error propagation behavior.
In `@src/components/NoteCard.vue`:
- Around line 94-96: Update NoteCard’s onCategoryChange handler to accept both
typed string values and { id, label } option payloads from the category
selector. Normalize either input into the existing category-selection flow so
newly created typed tags are persisted instead of ignored.
---
Outside diff comments:
In `@src/components/NoteItem.vue`:
- Around line 82-99: Update the category NcActionInput binding in NoteItem.vue
to use Vue 2’s value prop instead of model-value, and change the corresponding
event handling to the `@update`:value contract expected by `@nextcloud/vue` 8.x.
Preserve the existing note.category value and onCategoryChange behavior.
---
Nitpick comments:
In `@src/components/noteActions.js`:
- Around line 167-170: Update onToggleArchived in the note action handlers to
reuse the existing loading-state pattern used by the favorite/color handlers:
set loading before calling setArchived, surface failures instead of swallowing
the rejection, and clear loading when the operation settles so users receive
consistent feedback.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e21b6a2b-212b-41ba-970b-76ea5a4e05bd
📒 Files selected for processing (6)
playwright/e2e/note-actions.spec.tssrc/components/NoteCard.vuesrc/components/NoteItem.vuesrc/components/noteActions.jstests/api/APIv1Test.phptests/api/AbstractAPITest.php
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/api/APIv1Test.php
| async onCategoryChange(result) { | ||
| this.showCategorySelect = false | ||
| const category = result?.id ?? result?.label ?? null | ||
| if (category !== null && this.note.category !== category) { | ||
| this.loading.category = true | ||
| await setCategory(this.note.id, category) | ||
| this.loading.category = false | ||
| } | ||
| }, |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
loading.category sticks on failure.
setCategory rejects on sync errors (it rethrows after handleSyncError), so the reset never runs and the category input stays permanently disabled, plus an unhandled rejection. Mirror the try/finally used in onColorSelected.
🛡️ Proposed fix
if (category !== null && this.note.category !== category) {
this.loading.category = true
- await setCategory(this.note.id, category)
- this.loading.category = false
+ try {
+ await setCategory(this.note.id, category)
+ } finally {
+ this.loading.category = false
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async onCategoryChange(result) { | |
| this.showCategorySelect = false | |
| const category = result?.id ?? result?.label ?? null | |
| if (category !== null && this.note.category !== category) { | |
| this.loading.category = true | |
| await setCategory(this.note.id, category) | |
| this.loading.category = false | |
| } | |
| }, | |
| async onCategoryChange(result) { | |
| this.showCategorySelect = false | |
| const category = result?.id ?? result?.label ?? null | |
| if (category !== null && this.note.category !== category) { | |
| this.loading.category = true | |
| try { | |
| await setCategory(this.note.id, category) | |
| } finally { | |
| this.loading.category = false | |
| } | |
| } | |
| }, |
🧰 Tools
🪛 ast-grep (0.45.0)
[warning] 186-186: Avoid using the initial state variable in setState
Context: setCategory(this.note.id, category)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/noteActions.js` around lines 182 - 190, Update
onCategoryChange to wrap the awaited setCategory call in a try/finally, matching
onColorSelected, so loading.category is always reset when setCategory succeeds
or rejects; preserve the existing category validation and error propagation
behavior.
| :taggable="true" | ||
| @input="onCategoryChange" | ||
| @search-change="onCategoryChange" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
For @nextcloud/vue version 8.39.0, what event and payload does NcActionInput with type="multiselect" emit when taggable creates a new tag?
💡 Result:
In @nextcloud/vue version 8.39.0, the NcActionInput component with type="multiselect" does not natively expose a taggable property or emit a specific event for the creation of new tags. When NcActionInput is used with type="multiselect", it internally delegates to the NcSelect component [1][2]. The NcActionInput component itself is configured to emit standard events such as input, submit, change, and update:value (or update:modelValue) to handle changes to its state [1][3][2]. If functionality for creating tags is required, developers typically use the NcSelectTags component, which is designed for tag management and handles tag selection and input [4][3]. NcSelectTags emits update:modelValue (and its legacy equivalents) when tags are added or removed [4]. Please note that earlier versions of the library contained a deprecated NcMultiselect component, which has been removed in favor of NcSelect and its specialized variants [5][6].
Citations:
- 1: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcActionInput-TP6P798j.cjs
- 2: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcActionInput-BxlypAgV.mjs
- 3: https://github.com/nextcloud-libraries/nextcloud-vue/blob/main/CHANGELOG.md
- 4: https://app.unpkg.com/@nextcloud/vue@8.23.1/files/dist/chunks/NcSelectTags-BKtOWgrz.mjs
- 5: Use
NcSelectinstead ofNcMultiselectin the components nextcloud-libraries/nextcloud-vue#3743 - 6: Use
NcSelectinstead ofNcMultiselectin the components nextcloud-libraries/nextcloud-vue#3743
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Package versions:"
if [ -f package.json ]; then
node -e "const p=require('./package.json'); console.log(JSON.stringify({dependencies:p.dependencies?.['`@nextcloud/vue`'], devDependencies:p.devDependencies?.['`@nextcloud/vue`']}, null, 2))"
fi
echo
echo "Locate related files:"
fd -a 'NoteCard\.vue|noteActions\.js|package-lock\.json|pnpm-lock\.yaml|yarn\.lock' . | sed 's#^\./##'
echo
echo "Inspect NoteCard around multiselect/taggable:"
if [ -f src/components/NoteCard.vue ]; then
nl -ba src/components/NoteCard.vue | sed -n '70,120p'
fi
echo
echo "Inspect noteActions.js:"
if [ -f src/components/noteActions.js ]; then
nl -ba src/components/noteActions.js | sed -n '1,220p'
fi
echo
echo "Search onCategoryChange usages and taggable multiselect events:"
rg -n "onCategoryChange|taggable|`@search-change`|`@tag`|`@create`|type=.*multiselect|NcActionInput" src package.jsonRepository: zentala/notes-plus
Length of output: 450
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Inspect NoteCard.vue around multiselect/taggable:"
awk '{printf "%5d %s\n", NR, $0}' src/components/NoteCard.vue | sed -n '70,120p'
echo
echo "Inspect noteActions.js:"
awk '{printf "%5d %s\n", NR, $0}' src/components/noteActions.js | sed -n '1,220p'
echo
echo "Search relevant usages/events:"
rg -n "onCategoryChange|taggable|search-change|tag|create|type=.*multiselect|NcActionInput" src package.json
echo
echo "Fetch `@nextcloud/vue` 8.39.0 NcActionInput / NcSelect dist docs/events source references:"
python3 - <<'PY'
import json, urllib.request
urls = [
"https://unpkg.com/@nextcloud/vue@8.39.0/lib/components/NcActionInput.d.ts",
"https://unpkg.com/@nextcloud/vue@8.39.0/lib/components/NcInputField.d.ts",
"https://unpkg.com/@nextcloud/vue@8.39.0/lib/components/NcSelect.d.ts",
]
for url in urls:
print(f"\n--- {url}")
try:
with urllib.request.urlopen(url, timeout=20) as r:
data = r.read().decode("utf-8", errors="replace")
lines = data.splitlines()
for i,line in enumerate(lines,1):
if any(x in line for x in ["component:", "defineEmits", "NcMultiselect", "NcSelect", "create", "tag", "search-change", "update:modelValue"]):
print(f"{i:5d} {line}")
except Exception as e:
print(f"ERROR: {type(e).__name__}: {e}")
PYRepository: zentala/notes-plus
Length of output: 14957
🌐 Web query:
@nextcloud/vue 8.39.0 NcSelect taggable create event payload emit update:modelValue
💡 Result:
In @nextcloud/vue (specifically version 8.x and later), NcSelect and its variants like NcSelectTags use standard Vue 3-style v-model binding [1][2]. When using the taggable prop, the component emits an update:modelValue event to signal changes to the selected value [1][2]. Key details regarding the event payload and usage: 1. v-model Compatibility: The component uses modelValue as its primary prop for the selected value and emits update:modelValue for two-way data binding [1]. In older versions or when using the passthru prop, it may still support legacy input event patterns, but the modern implementation relies on update:modelValue [3][1]. 2. Event Payload: The payload emitted with the update:modelValue event is the updated value of the selection [1]. When taggable is true, and a user creates a new tag, the payload will include that new value (typically as a string, or an object if the internal structure requires it) alongside any previously selected items [1][2]. 3. Implementation Note: If you are encountering issues where new tags are not being captured or the selection appears null, ensure you are correctly binding v-model (or:modelValue and @update:modelValue) and that your logic handles the potential null or empty states, especially when clearing the selection [4]. For the most accurate implementation details for your specific version, please consult the documentation hosted at https://nextcloud-vue-components.netlify.app/ for v9 (Vue 3) or the stable8 documentation for v8 (Vue 2), as the underlying implementation of NcSelect heavily leverages vue-select and these mechanisms are standard for that library within the Nextcloud ecosystem [3][1][5].
Citations:
- 1: https://app.unpkg.com/@nextcloud/vue@9.8.2/files/dist/chunks/NcSelect-BOFzoCwK.mjs
- 2: https://app.unpkg.com/@nextcloud/vue@9.8.2/files/dist/chunks/NcSelectTags-D7qWNjXa.mjs
- 3: https://app.unpkg.com/@nextcloud/vue@8.23.1/files/dist/chunks/NcSelectTags-BKtOWgrz.mjs
- 4: fix(NcSelect*): add nullish types to modelValue nextcloud-libraries/nextcloud-vue#8214
- 5: https://github.com/nextcloud/nextcloud-vue/
🌐 Web query:
@nextcloud/vue 8.39.0 NcActionInput type multiselect taggable search-change input event
💡 Result:
In @nextcloud/vue version 8.39.0, the NcActionInput component does not natively support a multiselect type with taggable functionality [1][2]. Historically, NcActionInput included a multiselect option [2][3], but the underlying NcMultiselect component has been deprecated and removed from the library [4]. The library has migrated to using NcSelect for selection-based inputs [4], and NcActionInput has been updated to use NcSelect instead of the older NcMultiselect component [4]. Regarding the search-change event: 1. NcActionInput itself does not expose a native search-change event [2][3]. It primarily emits standard input events such as input, change, and submit [3][5]. 2. If you are implementing a searchable or taggable multiselect interface, the recommended approach is to use the NcSelect component directly [4]. NcSelect provides the necessary functionality for multiselect, taggable interfaces, and search-related events [4]. If you need custom search behavior, you should handle this via the events provided by the underlying component (e.g., NcSelect) rather than expecting NcActionInput to bridge a search-change event [4][5].
Citations:
- 1: https://app.unpkg.com/@nextcloud/vue@9.8.2/files/dist/chunks/NcActionInput-BO0lB4VP.mjs
- 2: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcActionInput-BxlypAgV.mjs
- 3: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcActionInput-TP6P798j.cjs
- 4: Use
NcSelectinstead ofNcMultiselectin the components nextcloud-libraries/nextcloud-vue#3743 - 5: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcActionInput-BxlypAgV.mjs.map
🌐 Web query:
nextcloud-vue 8.39.0 source NcActionInput multiselect NcSelect taggable
💡 Result:
In nextcloud-vue v8.39.0 [1], NcMultiselect is deprecated [2][3], and its functionality has been superseded by NcSelect [2]. NcActionInput includes support for a multiselect type [4][5]. When using NcActionInput with type="multiselect", the component internally utilizes NcSelect to provide the selection interface [5]. For tag-based selection, the recommended component is NcSelectTags [6][7]. This component wraps NcSelect and is specifically designed to handle tag lists, including features like automatic fetching of system tags or accepting custom options, as well as configuration for multiple selections [6][8][7]. Key Implementation Details: - Migration: All components that previously used NcMultiselect have been migrated to NcSelect [2][3]. - NcActionInput: Its type prop includes multiselect as a valid value [5]. It is not designed to accept a fully custom NcSelect instance directly as an input type, but rather switches internal behavior when type="multiselect" is set [5]. - NcSelectTags: This is the dedicated component for taggable interfaces [6][7]. It supports the multiple prop (defaulting to true) and allows for filtering or custom tagging behavior via its props, which are largely inherited from or modeled after NcSelect [6][7]. For current usage and API definitions, refer to the Nextcloud Vue Style Guide documentation site, which provides the authoritative reference for available props and component patterns [9][10].
Citations:
- 1: https://github.com/nextcloud-libraries/nextcloud-vue/releases/tag/v8.39.0
- 2: Use
NcSelectinstead ofNcMultiselectin the components nextcloud-libraries/nextcloud-vue#3743 - 3: Use
NcSelectinstead ofNcMultiselectin the components nextcloud-libraries/nextcloud-vue#3743 - 4: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcActionInput-TP6P798j.cjs
- 5: https://app.unpkg.com/@nextcloud/vue@9.8.2/files/dist/chunks/NcActionInput-BO0lB4VP.mjs
- 6: https://app.unpkg.com/@nextcloud/vue@9.8.2/files/dist/chunks/NcSelectTags-D7qWNjXa.mjs
- 7: https://app.unpkg.com/@nextcloud/vue@8.23.1/files/dist/chunks/NcSelectTags-BKtOWgrz.mjs
- 8: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcSelectTags-BceJx_FU.cjs
- 9: https://nextcloud-vue-components.netlify.app/
- 10: https://github.com/nextcloud/nextcloud-vue/
🌐 Web query:
nextcloud-vue 8.39.0 changelog NcMultiselect removed NcSelect NcActionInput
💡 Result:
The Nextcloud Vue library version 8.39.0, released on May 7, 2026, does not contain any breaking changes such as the removal of NcMultiselect, NcSelect, or NcActionInput [1][2]. The changelog for version 8.39.0 focuses on minor enhancements, specifically: - Changes to the active state design for NcAppNavigationItem and NcListItem [1][3]. - Adjustments to the NcAppContent edge style for Nextcloud 34+ [1][3]. - A redesign of the active tab in NcAppSidebarTab (as a rounded filled pill) [1][3]. - A new navigation design for NcAppSettingsDialog [1][3]. The components mentioned (NcMultiselect, NcSelect, NcActionInput) remain part of the library, though it is important to note that major breaking changes, including various component refactors and property renames, were introduced in version 9.0.0 (released in September 2025) [4][5]. For instance, NcSelect underwent significant updates in that transition, such as the removal of the userSelect property [4]. Version 8.x serves as a maintenance branch, and no such removal of these specific core components occurred in the 8.39.0 release [1][6].
Citations:
- 1: https://github.com/nextcloud-libraries/nextcloud-vue/releases/tag/v8.39.0
- 2: nextcloud-libraries/nextcloud-vue@v8.38.0...v8.39.0
- 3: https://github.com/nextcloud-libraries/nextcloud-vue/releases
- 4: https://unpkg.com/@nextcloud/vue@9.8.2/CHANGELOG.md
- 5: build(deps): Bump @nextcloud/vue from 9.0.0-rc.9 to 9.0.0 nextcloud/notifications#2531
- 6: Last call for
@nextcloud/vue v9breaking changes nextcloud-libraries/nextcloud-vue#6384
🌐 Web query:
nextcloud-vue 8.39.0 NcActionInput search-change event handler emits search string
💡 Result:
In nextcloud-vue version 8.39.0, the NcActionInput component does not natively expose a search-change event [1][2]. Historically, NcActionInput has emitted standard events such as input, submit, change, and update:value (which has since been migrated to update:modelValue) [1][3][2][4]. There is no documented search-change event handler for this component [1][2]. It is possible that such an event was confused with search functionality implemented in other components (such as NcSelectUsers, which does feature a search event) [5][6] or that a custom event handler was being manually implemented in a parent component. To handle input changes, you should rely on the standard Vue 3 v-model binding, which uses the update:modelValue event [3][4]. If you require specific search-as-you-type behavior, you may need to listen to the input event on the underlying input element or implement a debounced handler within the component wrapping the NcActionInput [1][2].
Citations:
- 1: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcActionInput-BxlypAgV.mjs
- 2: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcActionInput-TP6P798j.cjs
- 3: https://github.com/nextcloud-libraries/nextcloud-vue/blob/main/CHANGELOG.md
- 4: https://unpkg.com/@nextcloud/vue@9.8.2/CHANGELOG.md
- 5: https://github.com/nextcloud/nextcloud-vue/blob/master/CHANGELOG.md
- 6: nextcloud-libraries/nextcloud-vue@v9.0.0-alpha.8...v9.0.0-rc.0
🌐 Web query:
nextcloud-vue 8.39.0 NcActionInput search-change NcInputField emit
💡 Result:
In nextcloud-vue v8.39.0, both NcActionInput and NcInputField have undergone significant changes regarding event handling as part of the library's migration to Vue 3 standards and improved event naming conventions [1][2]. Key points regarding the components and their events: 1. Model Binding and Event Renaming: The value prop has been renamed to modelValue, and the update:value or input events have been replaced by the standard update:modelValue event [1]. This change applies to nearly all input-related components in the library, including NcActionInput and NcInputField [1]. 2. Event Naming: As part of a refactoring effort to standardize event names, custom events in nextcloud-vue have been updated to use camelCase [2]. When using these components in Vue 3, you should use the camelCase event names (e.g., @update:modelValue) to match the emitted events [2]. 3. Regarding "search-change": There is no standard event named search-change emitted by NcActionInput or NcInputField in the core library [3][1][4][5]. If you are experiencing a behavior related to a search input, it is likely that you are listening for a standard input, change, or update:modelValue event and reacting to the input value within your application logic [3][6]. If you are migrating or developing with version 8.39.0, ensure your code: - Uses v-model (which automatically binds to modelValue and listens for update:modelValue) instead of manual value binding and input events [1]. - Uses camelCase for custom event listeners [2]. - Consults the official documentation for the stable8 branch for component-specific API details, as the library maintains separate documentation for different major versions [7][8].
Citations:
- 1: https://github.com/nextcloud-libraries/nextcloud-vue/blob/main/CHANGELOG.md
- 2: refactor!: make all events camelcase and drop deprecated events nextcloud-libraries/nextcloud-vue#7059
- 3: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcActionInput-BxlypAgV.mjs
- 4: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcActionInput-TP6P798j.cjs
- 5: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/Components/NcInputField.mjs
- 6: https://app.unpkg.com/@nextcloud/vue@8.23.1/files/dist/chunks/NcInputField-CU5a68Pc.mjs
- 7: https://github.com/nextcloud-libraries/nextcloud-vue
- 8: https://github.com/nextcloud-libraries/nextcloud-vue/blob/main/README.md
🌐 Web query:
"search-change" "@nextcloud/vue" "NcSelect"
💡 Result:
The @nextcloud/vue library's NcSelect component uses the 'search' event to handle search queries [1][2]. When a user types in the input, the component emits the 'search' event with the current query string as the payload [1][2]. Developers typically use this event to trigger asynchronous data loading (e.g., API calls) to populate the select component's options [1][3][2]. It is standard practice to debounce this search handler to reduce the number of API requests while the user is typing [1][2][4]. Note that there is no 'search-change' event; the event used for tracking changes to the search input text within NcSelect is simply 'search' [1][2]. If you are migrating or working with newer versions of @nextcloud/vue: - The component has undergone significant changes in recent versions, including the migration to Vue 3 where the 'value' prop has been renamed to 'modelValue' and 'input' events have become 'update:modelValue' [5]. - For user selection specifically, the library provides a dedicated NcSelectUsers component, which is recommended over using the older 'user-select' property on NcSelect [6][5]. - Always consult the official documentation at https://nextcloud-vue-components.netlify.app/ for the latest API specifications relevant to your version [7][8].
Citations:
- 1: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcSettingsSelectGroup-Bx__1Iss.mjs
- 2: https://app.unpkg.com/@nextcloud/vue@8.23.1/files/dist/chunks/NcSettingsSelectGroup-CI2LoVeL.mjs
- 3: https://github.com/nextcloud/calendar/blob/main/src/components/Editor/Invitees/InviteesListSearch.vue
- 4: https://github.com/nextcloud/nextcloud-vue/blob/master/src/components/NcRichText/NcReferencePicker/NcSearch.vue
- 5: https://github.com/nextcloud-libraries/nextcloud-vue/blob/main/CHANGELOG.md
- 6: https://github.com/nextcloud-libraries/nextcloud-vue/blob/v8.26.1/CHANGELOG.md
- 7: https://github.com/nextcloud-libraries/nextcloud-vue
- 8: https://nextcloud-vue-components.netlify.app/
🌐 Web query:
"search-change" "@nextcloud/vue" "NcActionInput"
💡 Result:
NcActionInput is a UI component provided by the @nextcloud/vue library, designed to provide an input field within an action context [1][2]. It acts as a wrapper that allows developers to use various types of input fields (such as text, date, password, select, etc.) while maintaining a consistent Nextcloud-styled interface [1][2]. Key aspects of NcActionInput include: Functionality: It supports multiple input types via its type prop, including "text", "date", "datetime-local", "password", "number", "email", and others [1]. It integrates with the ActionGlobalMixin, which is standard for action-related components in the library [1]. Props: Key props include: - modelValue: The value of the input (notably, the component was updated to use modelValue instead of the deprecated value prop) [3][4]. - type: Specifies the type of input field to render [1]. - id and inputId: Used for accessibility and element identification [1]. - isNativePicker: A boolean flag to toggle the use of a native date-time picker [1]. Performance Considerations: As of mid-2025, the component has been identified as potentially heavy because it statically imports several large sub-components [2]. Efforts have been made to improve this, such as implementing lazy loading for its larger dependencies [2]. Usage: Like other components in @nextcloud/vue, it can be imported directly from the library [5]. Documentation: Developers can refer to the Nextcloud Vue Style Guide for live examples and specific API documentation regarding props, events, and slots [6][7]. Further technical details are tracked in the main repository's issues and changelogs [3][2].
Citations:
- 1: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcActionInput-TP6P798j.cjs
- 2: [NcActionInput] Component is too heavy including all input fields and pickers nextcloud-libraries/nextcloud-vue#7062
- 3: https://github.com/nextcloud-libraries/nextcloud-vue/blob/main/CHANGELOG.md
- 4: https://unpkg.com/@nextcloud/vue@9.8.2/CHANGELOG.md
- 5: https://github.com/nextcloud-libraries/nextcloud-vue
- 6: https://next--nextcloud-vue-components.netlify.app/
- 7: https://nextcloud-vue-components.netlify.app/
🌐 Web query:
@nextcloud/vue 8.39.0 dist NcSelect taggable $emit create taggableSelect
💡 Result:
In @nextcloud/vue v8.39.0, NcSelect is a wrapper component that leverages vue-select internally [1][2][3]. To enable the "taggable" functionality (where users can create new options on the fly), you pass the taggable prop directly to the NcSelect component [4]. When using taggable mode, NcSelect relies on vue-select's event handling. While standard value updates are emitted via the input event (or update:modelValue in newer patterns), custom creation of tags typically involves the taggable behavior managed by vue-select's internal logic. If you need to intercept or handle the creation of a new tag specifically, you should use the taggable prop alongside the create-option prop (if customization is needed) [4]. To emit custom events to a parent component when a user creates a tag, the common pattern is: 1. Define a wrapper method in your component that listens for the selection change or a specific creation event [4][5]. 2. Use the $emit function to pass the data to the parent [4][5]. Example implementation pattern: <NcSelect taggable:options="options" v-model="selected" @input="handleInput" /> <script> export default { methods: { handleInput(value) { // Custom logic for when a tag is selected or created this.$emit('create', value); } } } </script> Please note that for specific tag-related features (like system tags), Nextcloud provides specialized components such as NcSelectTags, which handle the API communication and tag creation logic automatically [1][3]. If you are working with standard tags, using NcSelectTags is recommended over raw NcSelect to ensure consistent behavior with the Nextcloud backend [1][3].
Citations:
- 1: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcSelectTags-BceJx_FU.cjs
- 2: https://app.unpkg.com/@nextcloud/vue@9.8.2/files/dist/chunks/NcSelect-BOFzoCwK.mjs
- 3: https://app.unpkg.com/@nextcloud/vue@8.23.1/files/dist/chunks/NcSelectTags-BKtOWgrz.mjs
- 4: https://stackoverflow.com/questions/72405593/vue3-and-vue-select-how-to-emit-child-data-to-parent-when-using-taggable-prop
- 5: https://github.com/nextcloud/tasks/blob/main/src/components/AppSidebar/TagsItem.vue
🌐 Web query:
@nextcloud/vue 8.39.0 dist NcSelect defineEmits create
💡 Result:
In @nextcloud/vue version 8.39.0, the NcSelect component is primarily designed for use in Vue 2 environments [1]. As a Vue 2 component, it does not use the Vue 3 script setup syntax (which relies on defineEmits) internally [1]. Regarding your query about using create and defineEmits with NcSelect: 1. defineEmits Context: The defineEmits function is a Vue 3 compiler macro [2]. Since @nextcloud/vue v8.x targets Vue 2 [1], you should not expect to use defineEmits inside your own components when importing or wrapping NcSelect. Instead, in Vue 2, you would define emitted events via the emits option in your component definition. 2. Component Usage: NcSelect is a standard Vue component [3]. You generally do not need to "create" it manually via functions; you import and use it in your template as a declarative component [4][1]: import { NcSelect } from '@nextcloud/vue' // In your Vue 2 component export default { components: { NcSelect }, //... } 3. Interfacing with NcSelect: If you are trying to handle events from NcSelect, you listen to them directly in your template using the standard v-on syntax (e.g., @input or @update), rather than relying on defineEmits [5][6]. If you are transitioning your application to Vue 3, please note that @nextcloud/vue v9.x is the version target for Vue 3 [1]. You should migrate to v9.x to gain support for Vue 3 features like script setup and defineEmits [1].
Citations:
- 1: https://github.com/nextcloud/nextcloud-vue/
- 2: https://exchangetuts.com/vue3-and-vue-select-how-to-emit-child-data-to-parent-when-using-taggable-prop-1761342902988114
- 3: https://app.unpkg.com/@nextcloud/vue@9.8.2/files/dist/chunks/NcSelect-BOFzoCwK.mjs
- 4: nextcloud/contacts@0969b46
- 5: https://help.nextcloud.com/t/ncselectuser-doesnt-get-updated-when-user-clicks-on-an-item/224951/8
- 6: https://help.nextcloud.com/t/ncselectuser-doesnt-get-updated-when-user-clicks-on-an-item/224951
🌐 Web query:
github nextcloud-libraries nextcloud-vue v8.39.0 src components NcSelect.js create-option taggable
💡 Result:
In @nextcloud/vue v8.39.0, the NcSelect component is a wrapper around the third-party library vue-select [1][2][3]. Because it acts as a wrapper, it inherits many of the underlying features of vue-select, including taggable support [2][3]. To use taggable functionality in NcSelect: 1. Taggable Prop: You can enable tagging by passing the taggable prop (e.g.,:taggable="true") to the NcSelect component [2]. 2. Create-Option Prop: When using taggable, you may also need to define the create-option prop if you need to customize how new tags are generated from user input [2]. The create-option function should return an object that matches the structure of your existing options [2]. Example usage pattern: <NcSelect taggable:options="options":create-option="option => ({ id: option, label: option })" v-model="selection" /> Note that @nextcloud/vue components often undergo refactoring. For instance, specific specialized select components like NcSelectTags or NcSelectUsers have been introduced to replace or supplement general NcSelect usage [4][3]. Always check the official Nextcloud Vue Style Guide for the most up-to-date implementation recommendations for your specific Nextcloud version, as documentation for stable8 (the branch containing v8.39.0) is available at https://stable8--nextcloud-vue-components.netlify.app [5][1].
Citations:
- 1: https://nextcloud-vue-components.netlify.app/
- 2: https://vue-select.org/guide/values.html
- 3: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcSelectTags-BceJx_FU.cjs
- 4: feat!: split
NcSelectsuper component intoNcSelectandNcSelectUsersnextcloud-libraries/nextcloud-vue#6732 - 5: https://github.com/nextcloud-libraries/nextcloud-vue
🌐 Web query:
npm nextcloud-vue@8.39.0 source NcSelect create-option vue-select
💡 Result:
In the nextcloud-vue library (version 8.39.0), the NcSelect component is a wrapper around the third-party vue-select component [1]. Regarding your specific interest in "create-option" functionality: 1. Relationship to vue-select: NcSelect internally utilizes vue-select [1]. While it abstracts many configurations, it passes properties and slots down to the underlying vue-select instance. 2. Handling Custom Options: If you are attempting to enable the creation of new options (a feature native to vue-select via the taggable and create-option props), you should pass these props directly to the NcSelect component. 3. Implementation Strategy: You can pass the taggable="true" prop to NcSelect to enable tag creation. If you need custom logic for creating options, you can use the create-option prop (or equivalent vue-select API) via the NcSelect component interface. 4. Architectural Note: Please be aware that in recent versions of nextcloud-vue, there has been a push to decompose "super-components" [2]. For specific use cases involving users, the library now provides a dedicated NcSelectUsers component, which is the recommended replacement for the older user-select functionality previously embedded within NcSelect [3][2]. For the most accurate implementation details regarding props allowed by your specific version, consult the documentation for vue-select, as NcSelect propagates these standard vue-select properties [1]. You can explore the component's structure in your node_modules/@nextcloud/vue/dist/components/NcSelect directory or via the official style guide [4].
Citations:
- 1: https://app.unpkg.com/@nextcloud/vue@8.19.0/files/dist/chunks/NcSelect-CKgkjF4m.cjs
- 2: feat!: split
NcSelectsuper component intoNcSelectandNcSelectUsersnextcloud-libraries/nextcloud-vue#6732 - 3: https://github.com/nextcloud/nextcloud-vue/blob/master/CHANGELOG.md
- 4: https://nextcloud-vue-components.netlify.app/
Persist typed category tags through value updates.
onCategoryChange() needs to handle both string inputs from these typed tags and { id, label } option payloads; otherwise newly created categories are ignored by the existing selection code.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/NoteCard.vue` around lines 94 - 96, Update NoteCard’s
onCategoryChange handler to accept both typed string values and { id, label }
option payloads from the category selector. Normalize either input into the
existing category-selection flow so newly created typed tags are persisted
instead of ignored.
Keep-style masonry card grid as an alternative to the list view, with a persisted list/grid toggle.
What
NotesViewswitches list ⇄ grid onstore.app.viewMode):NotesGrid.vueCSS multi-column masonry renderingNoteCard.vue(title, content excerpt, color tint, pinned/error/share state). Pinned notes sit above others.NoteActionsMenu.vue(favorite/archive/share/color/category/rename/delete) via avue-frag<Fragment>so the buttons hoist into the hostNcActions/NcListItem. One menu backs both the list row and the card — no drift.NoteItem.vuerefactored to consume it (behavior-neutral).localStorageviasrc/view-mode.js(guarded against disabled storage).excerptin the list payload (Note::getData) so cards render a preview without per-note fetches — the list still excludescontent. Derived from content, so not ingenerateEtag()(contentEtag covers it); no setter, no 4-place rule.Tests
view-modepersistence +grouping(Pinned/Others, category) — asserts counts so a dropped note fails red.testExcerpt: list carriesexcerptwithoutcontent; excerpt tracks content edits.view-toggle.spec.ts: toggle renders cards, persists across reload.Deferred (logged in the epic IMPRO)
vue-virtual-scrollerconflict (scroller needs fixed-size items); grid uses zero-JS CSS masonry now, windowed virtualization lands when Keep import (E08) justifies the volume.Deploy-verify
The
excerptfield needs a deploy-verify on the NC server (no local PHP), batched with the pending E01b/E02/E03.Summary by CodeRabbit
New Features
Bug Fixes