Skip to content

Commit 83208d2

Browse files
authored
Merge pull request #1146 from nextcloud/feature/share-single-note
2 parents e851a33 + 72cf34b commit 83208d2

File tree

11 files changed

+242
-4
lines changed

11 files changed

+242
-4
lines changed

lib/AppInfo/Application.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
use OCP\AppFramework\Bootstrap\IBootstrap;
1010
use OCP\AppFramework\Bootstrap\IRegistrationContext;
1111
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
12+
use OCP\EventDispatcher\GenericEvent;
13+
use OCP\EventDispatcher\IEventDispatcher;
14+
use OCP\Share\Events\BeforeShareCreatedEvent;
15+
use OCP\Share\IShare;
1216

1317
class Application extends App implements IBootstrap {
1418
public const APP_ID = 'notes';
@@ -26,6 +30,21 @@ public function register(IRegistrationContext $context): void {
2630
BeforeTemplateRenderedEvent::class,
2731
BeforeTemplateRenderedListener::class
2832
);
33+
if (\class_exists(BeforeShareCreatedEvent::class)) {
34+
$context->registerEventListener(
35+
BeforeShareCreatedEvent::class,
36+
BeforeShareCreatedListener::class
37+
);
38+
} else {
39+
// FIXME: Remove once Nextcloud 28 is the minimum supported version
40+
\OCP\Server::get(IEventDispatcher::class)->addListener('OCP\Share::preShare', function (GenericEvent $event) {
41+
/** @var IShare $share */
42+
$share = $event->getSubject();
43+
44+
$modernListener = \OCP\Server::get(BeforeShareCreatedListener::class);
45+
$modernListener->overwriteShareTarget($share);
46+
}, 1000);
47+
}
2948
}
3049

3150
public function boot(IBootContext $context): void {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OCA\Notes\AppInfo;
6+
7+
use OCA\Notes\Service\NoteUtil;
8+
use OCA\Notes\Service\SettingsService;
9+
use OCP\EventDispatcher\Event;
10+
use OCP\EventDispatcher\IEventListener;
11+
use OCP\Files\File;
12+
use OCP\Share\Events\BeforeShareCreatedEvent;
13+
use OCP\Share\IShare;
14+
15+
class BeforeShareCreatedListener implements IEventListener {
16+
private SettingsService $settings;
17+
private NoteUtil $noteUtil;
18+
19+
public function __construct(SettingsService $settings, NoteUtil $noteUtil) {
20+
$this->settings = $settings;
21+
$this->noteUtil = $noteUtil;
22+
}
23+
24+
public function handle(Event $event): void {
25+
if (!($event instanceof BeforeShareCreatedEvent)) {
26+
return;
27+
}
28+
29+
$this->overwriteShareTarget($event->getShare());
30+
}
31+
32+
public function overwriteShareTarget(IShare $share): void {
33+
$itemType = $share->getNode() instanceof File ? 'file' : 'folder';
34+
$fileSourcePath = $share->getNode()->getPath();
35+
$itemTarget = $share->getTarget();
36+
$uidOwner = $share->getSharedBy();
37+
$ownerPath = $this->noteUtil->getRoot()->getUserFolder($uidOwner)->getPath();
38+
$ownerNotesPath = $ownerPath . '/' . $this->settings->get($uidOwner, 'notesPath');
39+
40+
$receiver = $share->getSharedWith();
41+
$receiverPath = $this->noteUtil->getRoot()->getUserFolder($receiver)->getPath();
42+
$receiverNotesInternalPath = $this->settings->get($receiver, 'notesPath');
43+
$receiverNotesPath = $receiverPath . '/' . $receiverNotesInternalPath;
44+
$this->noteUtil->getOrCreateFolder($receiverNotesPath);
45+
46+
if ($itemType !== 'file' || strpos($fileSourcePath, $ownerNotesPath) !== 0) {
47+
return;
48+
}
49+
50+
$share->setTarget('/' . $receiverNotesInternalPath . $itemTarget);
51+
}
52+
}

lib/Controller/PageController.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44

55
namespace OCA\Notes\Controller;
66

7+
use OCA\Files\Event\LoadSidebar;
78
use OCA\Notes\AppInfo\Application;
89
use OCA\Notes\Service\NotesService;
910

1011
use OCA\Notes\Service\SettingsService;
1112
use OCA\Text\Event\LoadEditor;
13+
use OCA\Viewer\Event\LoadViewer;
1214
use OCP\App\IAppManager;
1315
use OCP\AppFramework\Controller;
1416
use OCP\AppFramework\Http\ContentSecurityPolicy;
@@ -66,6 +68,14 @@ public function index() : TemplateResponse {
6668
$this->eventDispatcher->dispatchTyped(new LoadEditor());
6769
}
6870

71+
if (class_exists(LoadSidebar::class)) {
72+
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
73+
}
74+
75+
if (\OCP\Server::get(IAppManager::class)->isEnabledForUser('viewer') && class_exists(LoadViewer::class)) {
76+
$this->eventDispatcher->dispatchTyped(new LoadViewer());
77+
}
78+
6979
$this->initialState->provideInitialState(
7080
'config',
7181
(array)\OCP\Server::get(SettingsService::class)->getPublic($this->userSession->getUser()->getUID())

lib/Service/Note.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ public function getData(array $exclude = []) : array {
105105
if (!in_array('readonly', $exclude)) {
106106
$data['readonly'] = $this->getReadOnly();
107107
}
108+
$data['internalPath'] = $this->noteUtil->getPathForUser($this->file);
109+
$data['shareTypes'] = $this->noteUtil->getShareTypes($this->file);
110+
$data['isShared'] = (bool) count($data['shareTypes']);
108111
$data['error'] = false;
109112
$data['errorType'] = '';
110113
if (!in_array('content', $exclude)) {

lib/Service/NoteUtil.php

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,49 @@
44

55
namespace OCA\Notes\Service;
66

7+
use OCP\Files\File;
78
use OCP\Files\Folder;
89
use OCP\Files\IRootFolder;
910
use OCP\Files\Node;
1011
use OCP\IDBConnection;
12+
use OCP\IUserSession;
13+
use OCP\Share\IManager;
14+
use OCP\Share\IShare;
1115

1216
class NoteUtil {
1317
private const MAX_TITLE_LENGTH = 100;
1418
public Util $util;
1519
private IDBConnection $db;
1620
private IRootFolder $root;
1721
private TagService $tagService;
22+
private IManager $shareManager;
23+
private IUserSession $userSession;
1824

1925
public function __construct(
2026
Util $util,
2127
IRootFolder $root,
2228
IDBConnection $db,
23-
TagService $tagService
29+
TagService $tagService,
30+
IManager $shareManager,
31+
IUserSession $userSession
2432
) {
2533
$this->util = $util;
2634
$this->root = $root;
2735
$this->db = $db;
2836
$this->tagService = $tagService;
37+
$this->shareManager = $shareManager;
38+
$this->userSession = $userSession;
2939
}
3040

3141
public function getRoot() : IRootFolder {
3242
return $this->root;
3343
}
3444

45+
public function getPathForUser(File $file) {
46+
$userFolder = $this->root->getUserFolder($this->userSession->getUser()->getUID());
47+
return $userFolder->getRelativePath($file->getPath());
48+
}
49+
3550
public function getTagService() : TagService {
3651
return $this->tagService;
3752
}
@@ -203,4 +218,29 @@ public function ensureNoteIsWritable(Node $node) : void {
203218
throw new NoteNotWritableException();
204219
}
205220
}
221+
222+
public function getShareTypes(File $file): array {
223+
$userId = $file->getOwner()->getUID();
224+
$requestedShareTypes = [
225+
IShare::TYPE_USER,
226+
IShare::TYPE_GROUP,
227+
IShare::TYPE_LINK,
228+
IShare::TYPE_REMOTE,
229+
IShare::TYPE_EMAIL,
230+
IShare::TYPE_ROOM,
231+
IShare::TYPE_DECK,
232+
IShare::TYPE_SCIENCEMESH,
233+
];
234+
$shareTypes = [];
235+
236+
foreach ($requestedShareTypes as $shareType) {
237+
$shares = $this->shareManager->getSharesBy($userId, $shareType, $file, false, 1, 0);
238+
239+
if (count($shares)) {
240+
$shareTypes[] = $shareType;
241+
}
242+
}
243+
244+
return $shareTypes;
245+
}
206246
}

src/NotesService.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ export const refreshNote = (noteId, lastETag) => {
150150
return response.headers.etag
151151
}
152152
const currentContent = store.getters.getNote(noteId).content
153+
store.commit('setNoteAttribute', { noteId, attribute: 'internalPath', value: response.data.internalPath })
153154
// only update if local content has not changed
154155
if (oldContent === currentContent) {
155156
_updateLocalNote(response.data)
@@ -290,6 +291,7 @@ export const autotitleNote = noteId => {
290291
.put(url('/notes/' + noteId + '/autotitle'))
291292
.then((response) => {
292293
store.commit('setNoteAttribute', { noteId, attribute: 'title', value: response.data })
294+
refreshNote(noteId)
293295
})
294296
.catch(err => {
295297
console.error(err)

src/components/NoteItem.vue

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,21 @@
2626
fill-color="var(--color-text-lighter)"
2727
/>
2828
</template>
29+
<template v-if="isShared" #indicator>
30+
<ShareVariantIcon :size="16" fill-color="#0082c9" />
31+
</template>
2932
<template #actions>
3033
<NcActionButton :icon="actionFavoriteIcon" @click="onToggleFavorite">
3134
{{ actionFavoriteText }}
3235
</NcActionButton>
3336

37+
<NcActionButton @click="onToggleSharing">
38+
<template #icon>
39+
<ShareVariantIcon :size="20" />
40+
</template>
41+
{{ t('notes', 'Share') }}
42+
</NcActionButton>
43+
3444
<NcActionButton v-if="!showCategorySelect" @click="showCategorySelect = true">
3545
<template #icon>
3646
<FolderIcon :size="20" />
@@ -90,6 +100,8 @@ import StarIcon from 'vue-material-design-icons/Star.vue'
90100
import { categoryLabel, routeIsNewNote } from '../Util.js'
91101
import { showError } from '@nextcloud/dialogs'
92102
import { setFavorite, setTitle, fetchNote, deleteNote, setCategory } from '../NotesService.js'
103+
import ShareVariantIcon from 'vue-material-design-icons/ShareVariant.vue'
104+
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
93105
94106
export default {
95107
name: 'NoteItem',
@@ -104,6 +116,7 @@ export default {
104116
NcActionSeparator,
105117
NcActionInput,
106118
PencilIcon,
119+
ShareVariantIcon,
107120
},
108121
109122
props: {
@@ -122,13 +135,17 @@ export default {
122135
newTitle: '',
123136
renaming: false,
124137
showCategorySelect: false,
138+
isShareCreated: false,
125139
}
126140
},
127141
128142
computed: {
129143
isSelected() {
130144
return this.$store.getters.getSelectedNote() === this.note.id
131145
},
146+
isShared() {
147+
return this.note.isShared || this.isShareCreated
148+
},
132149
133150
title() {
134151
return this.note.title + (this.note.unsaved ? ' *' : '')
@@ -167,6 +184,15 @@ export default {
167184
]
168185
},
169186
},
187+
188+
mounted() {
189+
subscribe('files_sharing:share:created', this.onShareCreated)
190+
},
191+
192+
destroyed() {
193+
unsubscribe('files_sharing:share:created', this.onShareCreated)
194+
},
195+
170196
methods: {
171197
onMenuChange(state) {
172198
this.actionsOpen = state
@@ -251,6 +277,23 @@ export default {
251277
this.actionsOpen = false
252278
}
253279
},
280+
onToggleSharing() {
281+
if (window?.OCA?.Files?.Sidebar?.setActiveTab) {
282+
emit('toggle-navigation', { open: false })
283+
setTimeout(() => {
284+
window.dispatchEvent(new Event('resize'))
285+
}, 200)
286+
window.OCA.Files.Sidebar.setActiveTab('sharing')
287+
window.OCA.Files.Sidebar.open(this.note.internalPath)
288+
}
289+
},
290+
async onShareCreated(event) {
291+
const { share } = event
292+
293+
if (share.fileSource === this.note.id) {
294+
this.isShareCreated = true
295+
}
296+
},
254297
},
255298
}
256299
</script>

src/components/NotePlain.vue

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ import {
9696
isMobile,
9797
} from '@nextcloud/vue'
9898
import { showError } from '@nextcloud/dialogs'
99-
import { emit } from '@nextcloud/event-bus'
99+
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
100100
101101
import EditIcon from 'vue-material-design-icons/LeadPencil.vue'
102102
import EyeIcon from 'vue-material-design-icons/Eye.vue'
@@ -193,6 +193,8 @@ export default {
193193
document.addEventListener('fullscreenchange', this.onDetectFullscreen)
194194
document.addEventListener('keydown', this.onKeyPress)
195195
document.addEventListener('visibilitychange', this.onVisibilityChange)
196+
subscribe('files_versions:restore:requested', this.onFileRestoreRequested)
197+
subscribe('files_versions:restore:restored', this.onFileRestored)
196198
},
197199
198200
destroyed() {
@@ -203,6 +205,8 @@ export default {
203205
document.removeEventListener('keydown', this.onKeyPress)
204206
document.removeEventListener('visibilitychange', this.onVisibilityChange)
205207
this.onUpdateTitle(null)
208+
unsubscribe('files_versions:restore:requested', this.onFileRestoreRequested)
209+
unsubscribe('files_versions:restore:restored', this.onFileRestored)
206210
},
207211
208212
methods: {
@@ -392,6 +396,25 @@ export default {
392396
conflictSolutionRemote(this.note)
393397
this.showConflict = false
394398
},
399+
400+
async onFileRestoreRequested(event) {
401+
const { fileInfo } = event
402+
403+
if (fileInfo.id !== this.note.id) {
404+
return
405+
}
406+
407+
this.loading = true
408+
},
409+
410+
async onFileRestored(version) {
411+
if (version.fileId !== this.note.id) {
412+
return
413+
}
414+
415+
this.refreshNote()
416+
this.loading = false
417+
},
395418
},
396419
}
397420
</script>

0 commit comments

Comments
 (0)