Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,21 @@ module.exports = {
extends: [
'@nextcloud',
],
settings: {
// Resolve '@/...' via simple alias to avoid webpack resolver in ESLint
'import/resolver': {
alias: {
map: [
['@', './src'],
],
extensions: ['.js', '.ts', '.vue', '.json'],
},
},
},
rules: {
'jsdoc/require-jsdoc': 'off',
'vue/first-attribute-linebreak': 'off',
// The Node resolver doesn't know webpack aliases; rely on import/no-unresolved instead
'n/no-missing-import': 'off',
},
}
24 changes: 24 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,29 @@
['name' => 'settings#getSettings', 'url' => '/api/settings', 'verb' => 'GET'],
['name' => 'settings#updateSettings', 'url' => '/api/settings', 'verb' => 'PUT'],
['name' => 'settings#rebase', 'url' => '/api/settings/rebase', 'verb' => 'POST'],

// UI page routes for SPA deep links
['name' => 'ui#sources', 'url' => '/sources', 'verb' => 'GET'],
['name' => 'ui#sourcesLogs', 'url' => '/sources/logs', 'verb' => 'GET'],
['name' => 'ui#endpoints', 'url' => '/endpoints', 'verb' => 'GET'],
['name' => 'ui#endpointsLogs', 'url' => '/endpoints/logs', 'verb' => 'GET'],
['name' => 'ui#endpointsId', 'url' => '/endpoints/{id}', 'verb' => 'GET'],
['name' => 'ui#consumers', 'url' => '/consumers', 'verb' => 'GET'],
['name' => 'ui#consumersId', 'url' => '/consumers/{id}', 'verb' => 'GET'],
['name' => 'ui#webhooks', 'url' => '/webhooks', 'verb' => 'GET'],
['name' => 'ui#jobs', 'url' => '/jobs', 'verb' => 'GET'],
['name' => 'ui#jobsLogs', 'url' => '/jobs/logs', 'verb' => 'GET'],
['name' => 'ui#mappings', 'url' => '/mappings', 'verb' => 'GET'],
['name' => 'ui#mappingsId', 'url' => '/mappings/{id}', 'verb' => 'GET'],
['name' => 'ui#rules', 'url' => '/rules', 'verb' => 'GET'],
['name' => 'ui#rulesId', 'url' => '/rules/{id}', 'verb' => 'GET'],
['name' => 'ui#synchronizations', 'url' => '/synchronizations', 'verb' => 'GET'],
['name' => 'ui#synchronizationsContracts', 'url' => '/synchronizations/contracts', 'verb' => 'GET'],
['name' => 'ui#synchronizationsLogs', 'url' => '/synchronizations/logs', 'verb' => 'GET'],
['name' => 'ui#cloudEvents', 'url' => '/cloud-events', 'verb' => 'GET'],
['name' => 'ui#cloudEventsEvents', 'url' => '/cloud-events/events', 'verb' => 'GET'],
['name' => 'ui#cloudEventsEventsId', 'url' => '/cloud-events/events/{id}', 'verb' => 'GET'],
['name' => 'ui#cloudEventsLogs', 'url' => '/cloud-events/logs', 'verb' => 'GET'],
['name' => 'ui#import', 'url' => '/import', 'verb' => 'GET'],
],
];
334 changes: 334 additions & 0 deletions docs/DEEPLINKING_AND_SPOT.md

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions docs/refactor-view-to-route-param-id.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
## Refactor a View to Use Router Param `:id` as Source of Truth

This guide explains how to refactor list/detail views so the selected item is driven by the route param `:id`, not by global store selection.

### 0) To know:
- code should be written in the options api, no code can be written in the setup script.

### 1) Routing
- Ensure a route exists like `/entities/:id` that renders the parent index view (list + detail).

### 2) List Component (navigation and highlighting)
- On list row click, navigate to the route: `$router.push('/entities/' + item.id)`.
- For active state and visual selection, compare against `$route.params.id`.
- Keep store interactions for modals or actions as needed, but do not rely on store for selection.

### 3) Parent Index View (fetch by id and pass down)
- Create local state: `selectedItem`, `loading`, `loadError`.
- Watch `$route.params.id` (immediate):
- If missing: clear `selectedItem` and optionally clear store item.
- If present: call the store fetch action (e.g., `store.fetchEntity(id)`), set `selectedItem` from the returned entity, handle errors and 404.
- 404 errors are not a thrown error by default, so the catch won't handle it and a loadError wont be shown. A check needs to be made if its 4XX and then throw a error.
- Render precedence in parent:
- If no `id`: render empty state.
- Else if `loadError`: render a generic error screen (no actions necessary).
- Else: render the detail component.
- Pass only the data needed to the detail component (e.g., `:item`, optionally `:loading`).
- Listen to child updates (e.g., `@item-updated`) and resync from store if mutations occur.

#### 3.5) Rendering error example
```html
<NcEmptyContent v-else-if="loadError"
class="detailContainer"
name="Error"
description="Failed to load endpoint.">
<template #icon>
<Api />
</template>
<template #action>
<div style="display: flex; gap: 0.5rem;">
<NcButton type="secondary" @click="endpointStore.setEndpointItem(null); loadError = false; $router.push('/endpoints')">
Terug
</NcButton>
<NcButton type="primary" @click="endpointStore.setEndpointItem(null); loadError = false; $router.push('/endpoints'); navigationStore.setModal('editEndpoint')">
Endpoint toevoegen
</NcButton>
</div>
</template>
</NcEmptyContent>
```

### 4) Detail Component (consume props)
- Accept `item` (object) and optionally `loading` (boolean) as props.
- Render fields from `item` instead of reading a global store selection.
- For mutations (save/delete), continue using the store actions; after a successful change, emit an update event (e.g., `this.$emit('item-updated')`).
- Guard nullable fields (`item?.arrayField?.join(', ') || '-'`).

### 5) Error and Loading States
- Parent exclusively handles error display (generic error view) and loading state gating.
- Child focuses on rendering data and triggering actions.

### 6) Minimal Touches
- Avoid unrelated refactors. Keep styles and existing actions intact where possible.

### Example APIs
- Store should expose:
- `fetchEntity(id)` that returns `{ entity }` and sets the current item for modals/actions.
- `saveEntity(entity)`, `deleteEntity(id)`, etc.

Following these steps keeps the URL as the single source of truth, improves deep-linking, and avoids stale selection state.


Loading
Loading