Skip to content

Commit ff897cf

Browse files
committed
enh: Add migration step to cover migration for existing users
Signed-off-by: Julius Härtl <jus@bitgrid.net>
1 parent 55605e5 commit ff897cf

File tree

13 files changed

+194
-4
lines changed

13 files changed

+194
-4
lines changed

appinfo/info.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ The Notes app is a distraction free notes taking app for [Nextcloud](https://www
2828
<repair-steps>
2929
<post-migration>
3030
<step>OCA\Notes\Migration\Cleanup</step>
31+
<step>OCA\Notes\Migration\EditorHint</step>
3132
</post-migration>
3233
</repair-steps>
3334

appinfo/routes.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
////////// S E T T I N G S //////////
101101
['name' => 'settings#set', 'url' => '/settings', 'verb' => 'PUT'],
102102
['name' => 'settings#get', 'url' => '/settings', 'verb' => 'GET'],
103+
['name' => 'settings#migrate', 'url' => '/settings/migrate', 'verb' => 'POST'],
103104

104105

105106
////////// A P I //////////

lib/Controller/PageController.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,55 @@
44

55
namespace OCA\Notes\Controller;
66

7+
use OCA\Notes\AppInfo\Application;
78
use OCA\Notes\Service\NotesService;
89

10+
use OCA\Notes\Service\SettingsService;
911
use OCA\Text\Event\LoadEditor;
12+
use OCP\App\IAppManager;
1013
use OCP\AppFramework\Controller;
1114
use OCP\AppFramework\Http\TemplateResponse;
1215
use OCP\AppFramework\Http\ContentSecurityPolicy;
1316
use OCP\AppFramework\Http\RedirectResponse;
17+
use OCP\AppFramework\Services\IInitialState;
1418
use OCP\EventDispatcher\IEventDispatcher;
19+
use OCP\IConfig;
1520
use OCP\IRequest;
1621
use OCP\IURLGenerator;
1722
use OCP\IUserSession;
1823

1924
class PageController extends Controller {
2025
private NotesService $notesService;
26+
private IConfig $config;
2127
private IUserSession $userSession;
2228
private IURLGenerator $urlGenerator;
2329
private IEventDispatcher $eventDispatcher;
30+
private IInitialState $initialState;
2431

2532
public function __construct(
2633
string $AppName,
2734
IRequest $request,
2835
NotesService $notesService,
36+
IConfig $config,
2937
IUserSession $userSession,
3038
IURLGenerator $urlGenerator,
31-
IEventDispatcher $eventDispatcher
39+
IEventDispatcher $eventDispatcher,
40+
IInitialState $initialState
3241
) {
3342
parent::__construct($AppName, $request);
3443
$this->notesService = $notesService;
44+
$this->config = $config;
3545
$this->userSession = $userSession;
3646
$this->urlGenerator = $urlGenerator;
3747
$this->eventDispatcher = $eventDispatcher;
48+
$this->initialState = $initialState;
3849
}
3950

4051

4152
/**
4253
* @NoAdminRequired
4354
* @NoCSRFRequired
55+
* @suppress PhanUndeclaredClassReference, PhanTypeMismatchArgument, PhanUndeclaredClassMethod
4456
*/
4557
public function index() : TemplateResponse {
4658
$devMode = !is_file(dirname(__FILE__).'/../../js/notes-main.js');
@@ -50,10 +62,20 @@ public function index() : TemplateResponse {
5062
[ ]
5163
);
5264

53-
if (class_exists(LoadEditor::class)) {
65+
if (\OCP\Server::get(IAppManager::class)->isEnabledForUser('text') && class_exists(LoadEditor::class)) {
5466
$this->eventDispatcher->dispatchTyped(new LoadEditor());
5567
}
5668

69+
$this->initialState->provideInitialState(
70+
'config',
71+
\OCP\Server::get(SettingsService::class)->getPublic($this->userSession->getUser()->getUID())
72+
);
73+
74+
$this->initialState->provideInitialState(
75+
'editorHint',
76+
$this->config->getUserValue($this->userSession->getUser()->getUID(), Application::APP_ID, 'editorHint', '')
77+
);
78+
5779
$csp = new ContentSecurityPolicy();
5880
$csp->addAllowedImageDomain('*');
5981
$response->setContentSecurityPolicy($csp);

lib/Controller/SettingsController.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,9 @@ public function set(): JSONResponse {
4848
public function get(): JSONResponse {
4949
return new JSONResponse($this->service->getAll($this->getUID()));
5050
}
51+
52+
public function migrate(): JSONResponse {
53+
$this->service->delete($this->getUID(), 'editorHint');
54+
return new JSONResponse();
55+
}
5156
}

lib/Migration/EditorHint.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2023 Julius Härtl <jus@bitgrid.net>
7+
*
8+
* @author Julius Härtl <jus@bitgrid.net>
9+
*
10+
* @license GNU AGPL version 3 or any later version
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*/
25+
26+
27+
namespace OCA\Notes\Migration;
28+
29+
use OCA\Notes\AppInfo\Application;
30+
use OCP\IConfig;
31+
use OCP\IUser;
32+
use OCP\IUserManager;
33+
use OCP\Migration\IOutput;
34+
use OCP\Migration\IRepairStep;
35+
36+
class EditorHint implements IRepairStep {
37+
private IConfig $config;
38+
private IUserManager $userManager;
39+
public function __construct(IConfig $config, IUserManager $userManager) {
40+
$this->config = $config;
41+
$this->userManager = $userManager;
42+
}
43+
44+
public function getName() {
45+
return 'Show a hint about the new editor to existing users';
46+
}
47+
48+
public function run(IOutput $output) {
49+
$appVersion = $this->config->getAppValue('text', 'installed_version');
50+
51+
if (!$appVersion || version_compare($appVersion, '4.7.0') !== -1) {
52+
return;
53+
}
54+
55+
$this->userManager->callForSeenUsers(function (IUser $user) {
56+
if ($this->config->getUserValue($user->getUID(), Application::APP_ID, 'notesLastViewedNote', '') === '') {
57+
return;
58+
}
59+
60+
$this->config->setUserValue($user->getUID(), Application::APP_ID, 'editorHint', 'yes');
61+
});
62+
}
63+
}

lib/Service/SettingsService.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ public function get(string $uid, string $name) : string {
168168
}
169169
}
170170

171+
public function delete(string $uid, string $name): void {
172+
$this->config->deleteUserValue($uid, Application::APP_ID, $name);
173+
}
174+
171175
public function getPublic(string $uid) : \stdClass {
172176
// initialize and load settings
173177
$settings = $this->getAll($uid, true);

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@nextcloud/axios": "^2.2.0",
1717
"@nextcloud/dialogs": "^3.2.0",
1818
"@nextcloud/event-bus": "^3.0.2",
19+
"@nextcloud/initial-state": "^2.0.0",
1920
"@nextcloud/moment": "^1.2.1",
2021
"@nextcloud/router": "^2.0.1",
2122
"@nextcloud/vue": "^7.3.0",

src/App.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<template>
2-
<NcContent app-name="notes" :content-class="{loading: loading.notes}">
2+
<EditorHint v-if="editorHint" @close="editorHint=false" />
3+
<NcContent v-else app-name="notes" :content-class="{loading: loading.notes}">
34
<NcAppNavigation :class="{loading: loading.notes, 'icon-error': error}">
45
<NcAppNavigationNew
56
v-show="!loading.notes && !error"
@@ -52,6 +53,7 @@ import {
5253
NcAppNavigationItem,
5354
NcContent,
5455
} from '@nextcloud/vue'
56+
import { loadState } from '@nextcloud/initial-state'
5557
import { showSuccess, TOAST_UNDO_TIMEOUT, TOAST_PERMANENT_TIMEOUT } from '@nextcloud/dialogs'
5658
import '@nextcloud/dialogs/styles/toast.scss'
5759
@@ -61,6 +63,7 @@ import PlusIcon from 'vue-material-design-icons/Plus.vue'
6163
import AppSettings from './components/AppSettings.vue'
6264
import NavigationList from './components/NavigationList.vue'
6365
import AppHelp from './components/AppHelp.vue'
66+
import EditorHint from './components/Modal/EditorHint.vue'
6467
6568
import { config } from './config.js'
6669
import { fetchNotes, noteExists, createNote, undoDeleteNote } from './NotesService.js'
@@ -72,6 +75,7 @@ export default {
7275
components: {
7376
AppHelp,
7477
AppSettings,
78+
EditorHint,
7579
InfoIcon,
7680
NavigationList,
7781
NcAppContent,
@@ -97,6 +101,7 @@ export default {
97101
deletedNotes: [],
98102
refreshTimer: null,
99103
helpVisible: false,
104+
editorHint: loadState('notes', 'editorHint', '') === 'yes' && window.oc_appswebroots.text,
100105
}
101106
},
102107

src/NotesService.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ export const setSettings = settings => {
4646
})
4747
}
4848

49+
export const deleteEditorMode = () => {
50+
return axios
51+
.post(url('/settings/migrate'))
52+
.catch(err => {
53+
console.error(err)
54+
throw err
55+
})
56+
}
57+
4958
export const getDashboardData = () => {
5059
return axios
5160
.get(url('/notes/dashboard'))

src/components/Modal/EditorHint.vue

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<template>
2+
<NcModal>
3+
<div class="editor-hint-modal">
4+
<h2>{{ t('notes', 'Rich text editor') }}</h2>
5+
6+
<p>{{ t('notes', 'You can now switch to use the easy to use and distraction free rich text editor. It allows you to edit notes without seeing any markdown marks.') }}</p>
7+
8+
<p>{{ t('notes', 'This option can also be changed later on in the Notes app settings.') }}</p>
9+
10+
<div class="submit-buttons">
11+
<NcButton type="secondary" :disabled="loading" @click="useOld">
12+
{{ t('notes', 'Keep plain markdown editor') }}
13+
</NcButton>
14+
<NcButton type="primary" :disabled="loading" @click="useNew">
15+
{{ t('notes', 'Use rich editor') }}
16+
</NcButton>
17+
</div>
18+
</div>
19+
</NcModal>
20+
</template>
21+
<script>
22+
import { NcModal, NcButton } from '@nextcloud/vue'
23+
import { loadState } from '@nextcloud/initial-state'
24+
25+
import { deleteEditorMode, setSettings } from './../../NotesService.js'
26+
27+
export default {
28+
components: {
29+
NcModal,
30+
NcButton,
31+
},
32+
data() {
33+
return {
34+
loading: false,
35+
}
36+
},
37+
methods: {
38+
async useOld() {
39+
const oldState = loadState('notes', 'config', {})
40+
setSettings({
41+
...oldState,
42+
noteMode: oldState?.nodeMode ?? 'edit',
43+
})
44+
await deleteEditorMode()
45+
this.$emit('close')
46+
},
47+
async useNew() {
48+
setSettings({
49+
...loadState('notes', 'config', {}),
50+
noteMode: 'rich',
51+
})
52+
await deleteEditorMode()
53+
this.$emit('close')
54+
},
55+
},
56+
}
57+
</script>
58+
<style lang="scss" scoped>
59+
.editor-hint-modal {
60+
margin: 24px;
61+
}
62+
63+
.submit-buttons {
64+
display: flex;
65+
justify-content: flex-end;
66+
margin-top: 24px;
67+
68+
button {
69+
margin-left: 12px;
70+
}
71+
}
72+
</style>

src/components/Note.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default {
2424
2525
computed: {
2626
rich() {
27-
return store.state.app.settings.noteMode === 'rich'
27+
return window.oc_appswebroots.text && store.state.app.settings.noteMode === 'rich'
2828
},
2929
},
3030
}

src/store/app.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { set } from 'vue'
2+
13
const state = {
24
settings: {},
35
isSaving: false,
@@ -14,6 +16,10 @@ const mutations = {
1416
state.settings = settings
1517
},
1618

19+
setNoteMode(state, mode) {
20+
set(state.settings, 'noteMode', mode)
21+
},
22+
1723
setSaving(state, isSaving) {
1824
state.isSaving = isSaving
1925
},

0 commit comments

Comments
 (0)