Skip to content
Draft
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
4 changes: 4 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use OCA\Deck\Event\CardCreatedEvent;
use OCA\Deck\Event\CardDeletedEvent;
use OCA\Deck\Event\CardUpdatedEvent;
use OCA\Deck\Event\RegisterTextContextEventListener;
use OCA\Deck\Event\SessionClosedEvent;
use OCA\Deck\Event\SessionCreatedEvent;
use OCA\Deck\Federation\DeckFederationProvider;
Expand Down Expand Up @@ -52,6 +53,7 @@
use OCA\Deck\Teams\DeckTeamResourceProvider;
use OCA\Deck\UserMigration\DeckMigrator;
use OCA\Text\Event\LoadEditor;
use OCA\Text\Event\RegisterContextEvent;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
Expand Down Expand Up @@ -189,6 +191,8 @@
$context->registerTeamResourceProvider(DeckTeamResourceProvider::class);

$context->registerUserMigrator(DeckMigrator::class);

$context->registerEventListener(RegisterContextEvent::class, RegisterTextContextEventListener::class);

Check failure on line 195 in lib/AppInfo/Application.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

UndefinedClass

lib/AppInfo/Application.php:195:35: UndefinedClass: Class, interface or enum named OCA\Text\Event\RegisterContextEvent does not exist (see https://psalm.dev/019)
}

public function registerCommentsEntity(IEventDispatcher $eventDispatcher): void {
Expand Down
31 changes: 31 additions & 0 deletions lib/Event/RegisterTextContextEventListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

declare(strict_types=1);

namespace OCA\Deck\Event;

use OCA\Deck\Provider\TextContextProviderFactory;
use OCA\Text\Event\RegisterContextEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;

class RegisterTextContextEventListener implements IEventListener{

Check failure on line 17 in lib/Event/RegisterTextContextEventListener.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

MissingTemplateParam

lib/Event/RegisterTextContextEventListener.php:17:51: MissingTemplateParam: OCA\Deck\Event\RegisterTextContextEventListener has missing template params when extending OCP\EventDispatcher\IEventListener, expecting 1 (see https://psalm.dev/182)
public function __construct(
private readonly TextContextProviderFactory $textContextProviderFactory,

Check failure on line 19 in lib/Event/RegisterTextContextEventListener.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

MissingDependency

lib/Event/RegisterTextContextEventListener.php:19:3: MissingDependency: OCA\Deck\Provider\TextContextProviderFactory depends on class or interface oca\text\context\icontextfactory that does not exist (see https://psalm.dev/157)
){}
public function handle(Event $event): void{
if (!$event instanceof RegisterContextEvent) {

Check failure on line 22 in lib/Event/RegisterTextContextEventListener.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

UndefinedClass

lib/Event/RegisterTextContextEventListener.php:22:26: UndefinedClass: Class, interface or enum named OCA\Text\Event\RegisterContextEvent does not exist (see https://psalm.dev/019)
return;
}

$event->getContextManager()->registerContext(
'deck_card',
TextContextProviderFactory::class
);
}
}
118 changes: 118 additions & 0 deletions lib/Provider/TextContextProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Deck\Provider;

use Co\Http\Server;
use OC\Security\SecureRandom;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Service\CardService;
use OCA\Deck\Service\PermissionService;
use OCA\Text\Context\DocumentData;
use OCA\Text\Context\IContext;
use OCA\Text\Context\SessionInfo;
use OCA\Text\Db\Document;
use OCA\Text\Exception\DocumentSaveConflictException;
use OCP\Files\File;

class TextContextProvider implements IContext {

Check failure on line 24 in lib/Provider/TextContextProvider.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

UndefinedClass

lib/Provider/TextContextProvider.php:24:38: UndefinedClass: Class, interface or enum named OCA\Text\Context\IContext does not exist (see https://psalm.dev/019)
public function __construct(
private readonly SecureRandom $secureRandom,
private readonly CardService $cardService,
private readonly CardMapper $cardMapper,
private readonly PermissionService $permissionService,
private readonly string $userId,
private readonly int $cardId,
) {
}
public function getId(): int {
return $this->cardId;
}

public function getType(): string {
return 'deck_card';
}

public function toString(): string {
return $this->getType() . ' (' . $this->getId() . ')';
}

public function buildDocument(): Document{
$document = new Document();
$document->setContextType($this->getType());
$document->setContextId($this->getId());
$document->setLastSavedVersion(0);
$document->setLastSavedVersionTime(1);
$document->setLastSavedVersionEtag($this->secureRandom->generate(6));

$document->setChecksum($this->computeChecksum());
$document->setBaseVersionEtag(uniqid());
return $document;
}

public function prepareSession(DocumentData $documentData): SessionInfo {
if ($documentData->documentState === null) {
$content = null;
} else {
$content = $this->cardService->find($this->cardId)->getDescription();
}
$readonly = !$this->permissionService->checkPermission($this->cardMapper, $this->cardId, Acl::PERMISSION_EDIT, $this->userId);
return new SessionInfo(
content: $content,
readOnly: $readonly,
lock: null,
hasOwner: true,
);
}

public function isReadOnly(): bool {
return !$this->permissionService->checkPermission($this->cardMapper, $this->cardId, Acl::PERMISSION_EDIT, $this->userId);
}

public function updateDocument(Document $document): ?Document {
if ($this->computeChecksum() !== $document->getChecksum()) {
$card = $this->cardService->find($this->cardId);
throw new DocumentSaveConflictException($card->getDescription());
}
return null;
}

public function getFile(): ?File{
return null;
}

public function loadContent(): ?string {
$card = $this->cardService->find($this->cardId);
if ($card === null) {
return null;
}
return $card->getDescription();
}

public function saveWithLock(string $content, callable $doWhileLocked): void {
$card = $this->cardService->find($this->cardId);
if ($card === null) {
return;
}
$card->setDescription($content);
$this->cardService->update($card->getId(), $card->getTitle(), $card->getStackId(), $card->getType(), $card->getOwner(), $card->getDescription(), $card->getOrder());
}

public function cleanup(): void {
}

private function computeChecksum(): string {
$card = $this->cardService->find($this->cardId);
if ($card === null) {
return '';
}
return hash('crc32', $card->getDescription());
}

}
39 changes: 39 additions & 0 deletions lib/Provider/TextContextProviderFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Deck\Provider;

use OC\Security\SecureRandom;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Service\CardService;
use OCA\Deck\Service\PermissionService;
use OCA\Text\Context\IContext;
use OCA\Text\Context\IContextFactory;
use OCP\IUser;
use OCP\Share\IShare;

class TextContextProviderFactory implements IContextFactory{

Check failure on line 19 in lib/Provider/TextContextProviderFactory.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

UndefinedClass

lib/Provider/TextContextProviderFactory.php:19:45: UndefinedClass: Class, interface or enum named OCA\Text\Context\IContextFactory does not exist (see https://psalm.dev/019)
public function __construct(
private readonly SecureRandom $secureRandom,
private readonly CardService $cardService,
private readonly CardMapper $cardMapper,
private readonly PermissionService $permissionService,
private readonly string $userId,
) {
}

public function build(IUser|IShare $auth, string $type, int $id): IContext {
return new TextContextProvider(
secureRandom: $this->secureRandom,
cardService: $this->cardService,
cardMapper: $this->cardMapper,
permissionService: $this->permissionService,
userId: $this->userId,
cardId: $id,
);
}
}
6 changes: 3 additions & 3 deletions src/components/card/Description.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
id="description-preview"
dir="auto"
@click="clickedPreview"
v-html="renderedDescription" />

Check warning on line 43 in src/components/card/Description.vue

View workflow job for this annotation

GitHub Actions / NPM lint

'v-html' directive can lead to XSS attack
<p v-else-if="!descriptionEditing" class="placeholder" @click="showEditor()">
{{ t('deck', 'Write a description …') }}
</p>
Expand Down Expand Up @@ -117,7 +117,7 @@
},
data() {
return {
textAppAvailable: !!window.OCA?.Text?.createEditor,
textAppAvailable: !!window.OCA?.Text?.createCollaborativeEditor,
editor: null,
keyExitState: 0,
descriptionOld: '',
Expand Down Expand Up @@ -205,9 +205,9 @@
this.descriptionLastEdit = 0
this.descriptionOld = this.card.description
this.description = this.card.description
this.editor = await window.OCA.Text.createEditor({
this.editor = await window.OCA.Text.createCollaborativeEditor({
context: { type: 'deck_card', id: this.card.id },
el: this.$refs.editor,
content: this.card.description,
readOnly: !this.canEdit,
onLoaded: () => {
this.descriptionLastEdit = 0
Expand Down
Loading