Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/text.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Veyssier <eneiluj@posteo.net>2022-06-08 18:44:49 +0300
committerJulien Veyssier <eneiluj@posteo.net>2022-09-06 15:44:53 +0300
commit3fec1f052dbb8cbe33172e779ac853eeb934ee62 (patch)
tree9cbdb5e28225dce8e45b14b06829668615fb6f55
parent3af1eb84126c4bb9396b701c963ee229cdc26c66 (diff)
allow media file upload, handle display in ImageView
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
-rw-r--r--appinfo/routes.php5
-rw-r--r--lib/Controller/ImageController.php130
-rw-r--r--lib/Service/ImageService.php257
-rw-r--r--src/components/Editor/MediaHandler.vue41
-rw-r--r--src/nodes/ImageView.vue72
-rw-r--r--src/services/ImageResolver.js88
-rw-r--r--src/services/SyncService.js12
7 files changed, 487 insertions, 118 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 9d1726566..99bd34973 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -29,7 +29,10 @@ return [
'routes' => [
['name' => 'Image#insertImageFile', 'url' => '/image/filepath', 'verb' => 'POST'],
['name' => 'Image#uploadImage', 'url' => '/image/upload', 'verb' => 'POST'],
- ['name' => 'Image#getImage', 'url' => '/image', 'verb' => 'GET'],
+ ['name' => 'Image#getImageFile', 'url' => '/image', 'verb' => 'GET'],
+ ['name' => 'Image#getMediaFile', 'url' => '/media', 'verb' => 'GET'],
+ ['name' => 'Image#getMediaFilePreview', 'url' => '/mediaPreview', 'verb' => 'GET'],
+ ['name' => 'Image#getMediaFileMetadata', 'url' => '/mediaMetadata', 'verb' => 'GET'],
['name' => 'Session#create', 'url' => '/session/create', 'verb' => 'PUT'],
['name' => 'Session#fetch', 'url' => '/session/fetch', 'verb' => 'POST'],
diff --git a/lib/Controller/ImageController.php b/lib/Controller/ImageController.php
index 219fa7b68..36616e554 100644
--- a/lib/Controller/ImageController.php
+++ b/lib/Controller/ImageController.php
@@ -33,6 +33,7 @@ use OCA\Text\Service\ImageService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataDownloadResponse;
use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Http\RedirectResponse;
use OCP\Files\IMimeTypeDetector;
use OCP\IL10N;
use OCP\IRequest;
@@ -121,9 +122,11 @@ class ImageController extends Controller {
try {
$file = $this->getUploadedFile('image');
if (isset($file['tmp_name'], $file['name'], $file['type'])) {
+ /*
if (!in_array($file['type'], self::IMAGE_MIME_TYPES, true)) {
return new DataResponse(['error' => 'Image type not supported'], Http::STATUS_BAD_REQUEST);
}
+ */
$newFileResource = fopen($file['tmp_name'], 'rb');
if ($newFileResource === false) {
throw new Exception('Could not read file');
@@ -179,7 +182,7 @@ class ImageController extends Controller {
* @NoCSRFRequired
* @PublicPage
*
- * Serve the images in the editor
+ * Serve the image files in the editor
* @param int $documentId
* @param int $sessionId
* @param string $sessionToken
@@ -187,17 +190,17 @@ class ImageController extends Controller {
* @param string|null $shareToken
* @return DataDownloadResponse|DataResponse
*/
- public function getImage(int $documentId, int $sessionId, string $sessionToken, string $imageFileName, ?string $shareToken = null) {
+ public function getImageFile(int $documentId, int $sessionId, string $sessionToken, string $imageFileName, ?string $shareToken = null) {
if (!$this->sessionService->isValidSession($documentId, $sessionId, $sessionToken)) {
return new DataResponse('', Http::STATUS_FORBIDDEN);
}
try {
if ($shareToken) {
- $imageFile = $this->imageService->getImagePublic($documentId, $imageFileName, $shareToken);
+ $imageFile = $this->imageService->getImageFilePublic($documentId, $imageFileName, $shareToken);
} else {
$userId = $this->getUserIdFromSession($documentId, $sessionId, $sessionToken);
- $imageFile = $this->imageService->getImage($documentId, $imageFileName, $userId);
+ $imageFile = $this->imageService->getImageFile($documentId, $imageFileName, $userId);
}
return $imageFile !== null
? new DataDownloadResponse(
@@ -207,7 +210,124 @@ class ImageController extends Controller {
)
: new DataResponse('', Http::STATUS_NOT_FOUND);
} catch (Exception $e) {
- $this->logger->error('getImage error', ['exception' => $e]);
+ $this->logger->error('getImageFile error', ['exception' => $e]);
+ return new DataResponse('', Http::STATUS_NOT_FOUND);
+ }
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * Serve the media files in the editor
+ * @param int $documentId
+ * @param int $sessionId
+ * @param string $sessionToken
+ * @param string $mediaFileName
+ * @param string|null $shareToken
+ * @return DataDownloadResponse|DataResponse
+ */
+ public function getMediaFile(int $documentId, int $sessionId, string $sessionToken, string $mediaFileName, ?string $shareToken = null) {
+ if (!$this->sessionService->isValidSession($documentId, $sessionId, $sessionToken)) {
+ return new DataResponse('', Http::STATUS_FORBIDDEN);
+ }
+
+ try {
+ if ($shareToken) {
+ $mediaFile = $this->imageService->getMediaFilePublic($documentId, $mediaFileName, $shareToken);
+ } else {
+ $userId = $this->getUserIdFromSession($documentId, $sessionId, $sessionToken);
+ $mediaFile = $this->imageService->getMediaFile($documentId, $mediaFileName, $userId);
+ }
+ return $mediaFile !== null
+ ? new DataDownloadResponse(
+ $mediaFile->getContent(),
+ (string) Http::STATUS_OK,
+ $this->getSecureMimeType($mediaFile->getMimeType())
+ )
+ : new DataResponse('', Http::STATUS_NOT_FOUND);
+ } catch (Exception $e) {
+ $this->logger->error('getMediaFile error', ['exception' => $e]);
+ return new DataResponse('', Http::STATUS_NOT_FOUND);
+ }
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * Serve the media files preview in the editor
+ * @param int $documentId
+ * @param int $sessionId
+ * @param string $sessionToken
+ * @param string $mediaFileName
+ * @param string|null $shareToken
+ * @return DataDownloadResponse|DataResponse|RedirectResponse
+ */
+ public function getMediaFilePreview(int $documentId, int $sessionId, string $sessionToken, string $mediaFileName, ?string $shareToken = null) {
+ if (!$this->sessionService->isValidSession($documentId, $sessionId, $sessionToken)) {
+ return new DataResponse('', Http::STATUS_FORBIDDEN);
+ }
+
+ try {
+ if ($shareToken) {
+ $preview = $this->imageService->getMediaFilePreviewPublic($documentId, $mediaFileName, $shareToken);
+ } else {
+ $userId = $this->getUserIdFromSession($documentId, $sessionId, $sessionToken);
+ $preview = $this->imageService->getMediaFilePreview($documentId, $mediaFileName, $userId);
+ }
+ if ($preview === null) {
+ return new DataResponse('', Http::STATUS_NOT_FOUND);
+ }
+ if ($preview['type'] === 'file') {
+ return new DataDownloadResponse(
+ $preview['file']->getContent(),
+ (string) Http::STATUS_OK,
+ $this->getSecureMimeType($preview['file']->getMimeType())
+ );
+ } elseif ($preview['type'] === 'icon') {
+ return new RedirectResponse($preview['iconUrl']);
+ }
+ } catch (Exception $e) {
+ $this->logger->error('getMediaFilePreview error', ['exception' => $e]);
+ return new DataResponse('', Http::STATUS_NOT_FOUND);
+ }
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * Serve the media files metadata in the editor
+ * @param int $documentId
+ * @param int $sessionId
+ * @param string $sessionToken
+ * @param string $mediaFileName
+ * @param string|null $shareToken
+ * @return DataResponse
+ */
+ public function getMediaFileMetadata(int $documentId, int $sessionId, string $sessionToken,
+ string $mediaFileName, ?string $shareToken = null): DataResponse {
+ if (!$this->sessionService->isValidSession($documentId, $sessionId, $sessionToken)) {
+ return new DataResponse('', Http::STATUS_FORBIDDEN);
+ }
+
+ try {
+ if ($shareToken) {
+ $metadata = $this->imageService->getMediaFileMetadataPublic($documentId, $mediaFileName, $shareToken);
+ } else {
+ $userId = $this->getUserIdFromSession($documentId, $sessionId, $sessionToken);
+ $metadata = $this->imageService->getMediaFileMetadataPrivate($documentId, $mediaFileName, $userId);
+ }
+ if ($metadata === null) {
+ return new DataResponse('', Http::STATUS_NOT_FOUND);
+ }
+ return new DataResponse($metadata);
+ } catch (Exception $e) {
+ $this->logger->error('getMediaFileMetadata error', ['exception' => $e]);
return new DataResponse('', Http::STATUS_NOT_FOUND);
}
}
diff --git a/lib/Service/ImageService.php b/lib/Service/ImageService.php
index 901575355..f305a5e39 100644
--- a/lib/Service/ImageService.php
+++ b/lib/Service/ImageService.php
@@ -31,12 +31,14 @@ use OCA\Text\Controller\ImageController;
use OCP\Constants;
use OCP\Files\Folder;
use OCP\Files\File;
+use OCP\Files\IMimeTypeDetector;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\IPreview;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IShare;
+use OCP\Util;
use Throwable;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
@@ -57,10 +59,6 @@ class ImageService {
*/
private $rootFolder;
/**
- * @var IClientService
- */
- private $clientService;
- /**
* @var LoggerInterface
*/
private $logger;
@@ -68,21 +66,25 @@ class ImageService {
* @var IPreview
*/
private $previewManager;
+ /**
+ * @var IMimeTypeDetector
+ */
+ private $mimeTypeDetector;
public function __construct(IRootFolder $rootFolder,
LoggerInterface $logger,
ShareManager $shareManager,
IPreview $previewManager,
- IClientService $clientService) {
+ IMimeTypeDetector $mimeTypeDetector) {
$this->rootFolder = $rootFolder;
$this->shareManager = $shareManager;
- $this->clientService = $clientService;
$this->logger = $logger;
$this->previewManager = $previewManager;
+ $this->mimeTypeDetector = $mimeTypeDetector;
}
/**
- * Get image content or preview from file id
+ * Get image content or preview from file name
* @param int $documentId
* @param string $imageFileName
* @param string $userId
@@ -91,9 +93,9 @@ class ImageService {
* @throws \OCP\Files\InvalidPathException
* @throws \OCP\Files\NotPermittedException
*/
- public function getImage(int $documentId, string $imageFileName, string $userId) {
+ public function getImageFile(int $documentId, string $imageFileName, string $userId) {
$textFile = $this->getTextFile($documentId, $userId);
- return $this->getImagePreview($imageFileName, $textFile);
+ return $this->getImageFilePreview($imageFileName, $textFile);
}
/**
@@ -107,9 +109,9 @@ class ImageService {
* @throws \OCP\Files\InvalidPathException
* @throws \OC\User\NoUserException
*/
- public function getImagePublic(int $documentId, string $imageFileName, string $shareToken) {
+ public function getImageFilePublic(int $documentId, string $imageFileName, string $shareToken) {
$textFile = $this->getTextFilePublic($documentId, $shareToken);
- return $this->getImagePreview($imageFileName, $textFile);
+ return $this->getImageFilePreview($imageFileName, $textFile);
}
/**
@@ -121,10 +123,10 @@ class ImageService {
* @throws \OCP\Files\InvalidPathException
* @throws \OC\User\NoUserException
*/
- private function getImagePreview(string $imageFileName, File $textFile) {
+ private function getImageFilePreview(string $imageFileName, File $textFile) {
$attachmentFolder = $this->getAttachmentDirectoryForFile($textFile, true);
$imageFile = $attachmentFolder->get($imageFileName);
- if ($imageFile instanceof File) {
+ if ($imageFile instanceof File && in_array($imageFile->getMimetype(), ImageController::IMAGE_MIME_TYPES)) {
if ($this->previewManager->isMimeSupported($imageFile->getMimeType())) {
return $this->previewManager->getPreview($imageFile, 1024, 1024);
}
@@ -134,6 +136,168 @@ class ImageService {
}
/**
+ * Get media file from file name
+ * @param int $documentId
+ * @param string $mediaFileName
+ * @param string $userId
+ * @return File|\OCP\Files\Node|ISimpleFile|null
+ * @throws NotFoundException
+ * @throws \OCP\Files\InvalidPathException
+ * @throws \OCP\Files\NotPermittedException
+ */
+ public function getMediaFile(int $documentId, string $mediaFileName, string $userId) {
+ $textFile = $this->getTextFile($documentId, $userId);
+ return $this->getMediaFullFile($mediaFileName, $textFile);
+ }
+
+ /**
+ * Get image content or preview from file id in public context
+ * @param int $documentId
+ * @param string $mediaFileName
+ * @param string $shareToken
+ * @return File|\OCP\Files\Node|ISimpleFile|null
+ * @throws NotFoundException
+ * @throws NotPermittedException
+ * @throws \OCP\Files\InvalidPathException
+ * @throws \OC\User\NoUserException
+ */
+ public function getMediaFilePublic(int $documentId, string $mediaFileName, string $shareToken) {
+ $textFile = $this->getTextFilePublic($documentId, $shareToken);
+ return $this->getMediaFullFile($mediaFileName, $textFile);
+ }
+
+ /**
+ * @param string $mediaFileName
+ * @param File $textFile
+ * @return File|null
+ * @throws NotFoundException
+ * @throws NotPermittedException
+ * @throws \OCP\Files\InvalidPathException
+ * @throws \OC\User\NoUserException
+ */
+ private function getMediaFullFile(string $mediaFileName, File $textFile): ?File {
+ $attachmentFolder = $this->getAttachmentDirectoryForFile($textFile, true);
+ $mediaFile = $attachmentFolder->get($mediaFileName);
+ if ($mediaFile instanceof File) {
+ return $mediaFile;
+ }
+ return null;
+ }
+
+ /**
+ * @param int $documentId
+ * @param string $mediaFileName
+ * @param string $userId
+ * @return array|null
+ * @throws NotFoundException
+ * @throws NotPermittedException
+ * @throws \OCP\Files\InvalidPathException
+ * @throws \OC\User\NoUserException
+ */
+ public function getMediaFilePreview(int $documentId, string $mediaFileName, string $userId): ?array {
+ $textFile = $this->getTextFile($documentId, $userId);
+ return $this->getMediaFilePreviewFile($mediaFileName, $textFile);
+ }
+ /**
+ * @param int $documentId
+ * @param string $mediaFileName
+ * @param string $shareToken
+ * @return array|null
+ * @throws NotFoundException
+ * @throws NotPermittedException
+ * @throws \OCP\Files\InvalidPathException
+ * @throws \OC\User\NoUserException
+ */
+ public function getMediaFilePreviewPublic(int $documentId, string $mediaFileName, string $shareToken): ?array {
+ $textFile = $this->getTextFilePublic($documentId, $shareToken);
+ return $this->getMediaFilePreviewFile($mediaFileName, $textFile);
+ }
+
+ /**
+ * Get media preview or mimetype icon address
+ * @param string $mediaFileName
+ * @param File $textFile
+ * @return array|null
+ * @throws NotFoundException
+ * @throws NotPermittedException
+ * @throws \OCP\Files\InvalidPathException
+ * @throws \OC\User\NoUserException
+ */
+ private function getMediaFilePreviewFile(string $mediaFileName, File $textFile): ?array {
+ $attachmentFolder = $this->getAttachmentDirectoryForFile($textFile, true);
+ $mediaFile = $attachmentFolder->get($mediaFileName);
+ if ($mediaFile instanceof File) {
+ if ($this->previewManager->isMimeSupported($mediaFile->getMimeType())) {
+ try {
+ return [
+ 'type' => 'file',
+ 'file' => $this->previewManager->getPreview($mediaFile, 1024, 1024),
+ ];
+ } catch (NotFoundException $e) {
+ // the preview might not be found even if the mimetype is supported
+ }
+ }
+ // fallback: mimetype icon URL
+ return [
+ 'type' => 'icon',
+ 'iconUrl' => $this->mimeTypeDetector->mimeTypeIcon($mediaFile->getMimeType()),
+ ];
+ }
+ return null;
+ }
+
+ /**
+ * @param int $documentId
+ * @param string $mediaFileName
+ * @param string $userId
+ * @return array|null
+ * @throws NotFoundException
+ * @throws NotPermittedException
+ * @throws \OCP\Files\InvalidPathException
+ * @throws \OC\User\NoUserException
+ */
+ public function getMediaFileMetadataPrivate(int $documentId, string $mediaFileName, string $userId): ?array {
+ $textFile = $this->getTextFile($documentId, $userId);
+ return $this->getMediaFileMetadata($mediaFileName, $textFile);
+ }
+
+ /**
+ * @param int $documentId
+ * @param string $mediaFileName
+ * @param string $shareToken
+ * @return array|null
+ * @throws NotFoundException
+ * @throws NotPermittedException
+ * @throws \OCP\Files\InvalidPathException
+ * @throws \OC\User\NoUserException
+ */
+ public function getMediaFileMetadataPublic(int $documentId, string $mediaFileName, string $shareToken): ?array {
+ $textFile = $this->getTextFilePublic($documentId, $shareToken);
+ return $this->getMediaFileMetadata($mediaFileName, $textFile);
+ }
+
+ /**
+ * @param string $mediaFileName
+ * @param File $textFile
+ * @return array|null
+ * @throws NotFoundException
+ * @throws NotPermittedException
+ * @throws \OCP\Files\InvalidPathException
+ * @throws \OC\User\NoUserException
+ */
+ private function getMediaFileMetadata(string $mediaFileName, File $textFile): ?array {
+ $attachmentFolder = $this->getAttachmentDirectoryForFile($textFile, true);
+ $mediaFile = $attachmentFolder->get($mediaFileName);
+ if ($mediaFile instanceof File) {
+ return [
+ 'size' => Util::humanFileSize($mediaFile->getSize()),
+ 'mtime' => $mediaFile->getMTime(),
+ ];
+ }
+ return null;
+ }
+
+ /**
* Save an uploaded image in the attachment folder
*
* @param int $documentId
@@ -221,21 +385,15 @@ class ImageService {
* @throws \OCP\Files\InvalidPathException
*/
private function copyImageFile(File $imageFile, Folder $saveDir, File $textFile): array {
- $mimeType = $imageFile->getMimeType();
- if (in_array($mimeType, ImageController::IMAGE_MIME_TYPES, true)) {
- $fileName = $this->getUniqueFileName($saveDir, $imageFile->getName());
- $targetPath = $saveDir->getPath() . '/' . $fileName;
- $targetFile = $imageFile->copy($targetPath);
- // get file type and name
- return [
- 'name' => $fileName,
- 'dirname' => $saveDir->getName(),
- 'id' => $targetFile->getId(),
- 'documentId' => $textFile->getId(),
- ];
- }
+ $fileName = $this->getUniqueFileName($saveDir, $imageFile->getName());
+ $targetPath = $saveDir->getPath() . '/' . $fileName;
+ $targetFile = $imageFile->copy($targetPath);
return [
- 'error' => 'Unsupported file type',
+ 'name' => $fileName,
+ 'dirname' => $saveDir->getName(),
+ 'id' => $targetFile->getId(),
+ 'documentId' => $textFile->getId(),
+ 'mimetype' => $targetFile->getMimetype(),
];
}
@@ -390,49 +548,6 @@ class ImageService {
}
/**
- * Download a file and write it to a resource
- * @param string $url
- * @param $resource
- * @return array
- */
- private function simpleDownload(string $url, $resource): array {
- $client = $this->clientService->newClient();
- try {
- $options = [
- // does not work with sink if SSE is enabled
- // 'sink' => $resource,
- // rather use stream and write to the file ourselves
- 'stream' => true,
- 'timeout' => 0,
- 'headers' => [
- 'User-Agent' => 'Nextcloud Text',
- ],
- ];
-
- $response = $client->get($url, $options);
- $body = $response->getBody();
- while (!feof($body)) {
- // write ~5 MB chunks
- $chunk = fread($body, 5000000);
- fwrite($resource, $chunk);
- }
-
- return ['Content-Type' => $response->getHeader('Content-Type')];
- } catch (ServerException | ClientException $e) {
- //$response = $e->getResponse();
- //if ($response->getStatusCode() === 401) {
- $this->logger->warning('Impossible to download image', ['exception' => $e]);
- return ['error' => 'Impossible to download image'];
- } catch (ConnectException $e) {
- $this->logger->error('Connection error', ['exception' => $e]);
- return ['error' => 'Connection error'];
- } catch (Throwable | Exception $e) {
- $this->logger->error('Unknown download error', ['exception' => $e]);
- return ['error' => 'Unknown download error'];
- }
- }
-
- /**
* Actually delete attachment files which are not pointed in the markdown content
*
* @param int $fileId
@@ -498,7 +613,7 @@ class ImageService {
$matches = [];
// matches ![ANY_CONSIDERED_CORRECT_BY_PHP-MARKDOWN](.attachments.DOCUMENT_ID/ANY_FILE_NAME) and captures FILE_NAME
preg_match_all(
- '/\!\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[\])*\])*\])*\])*\])*\])*\]\(\.attachments\.'.$fileId.'\/([^)&]+)\)/',
+ '/\!\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[\])*\])*\])*\])*\])*\])*\]\(\.attachments\.' . $fileId . '\/([^)&]+)\)/',
$content,
$matches,
PREG_SET_ORDER
diff --git a/src/components/Editor/MediaHandler.vue b/src/components/Editor/MediaHandler.vue
index 7004b5b16..1a0f939d9 100644
--- a/src/components/Editor/MediaHandler.vue
+++ b/src/components/Editor/MediaHandler.vue
@@ -132,16 +132,14 @@ export default {
})
},
async uploadImageFile(file, position = null) {
- if (!IMAGE_MIMES.includes(file.type)) {
- showError(t('text', 'Image file format not supported'))
- return
- }
-
this.state.isUploadingImages = true
return this.$syncService.uploadImage(file)
.then((response) => {
- this.insertAttachmentImage(response.data?.name, response.data?.id, position, response.data?.dirname)
+ this.insertAttachment(
+ response.data?.name, response.data?.id, file.type,
+ position, response.data?.dirname
+ )
})
.catch((error) => {
console.error(error)
@@ -165,7 +163,10 @@ export default {
this.state.isUploadingImages = true
return this.$syncService.insertImageFile(imagePath).then((response) => {
- this.insertAttachmentImage(response.data?.name, response.data?.id, null, response.data?.dirname)
+ this.insertAttachment(
+ response.data?.name, response.data?.id, response.data?.mimetype,
+ null, response.data?.dirname
+ )
}).catch((error) => {
console.error(error)
showError(error?.response?.data?.error || error.message)
@@ -173,7 +174,31 @@ export default {
this.state.isUploadingImages = false
})
},
- insertAttachmentImage(name, fileId, position = null, dirname = '') {
+ insertAttachment(name, fileId, mimeType, position = null, dirname = '') {
+ if (IMAGE_MIMES.includes(mimeType)) {
+ this.insertAttachmentImage(name, fileId, mimeType, position, dirname)
+ return
+ }
+ this.insertAttachmentMedia(name, fileId, mimeType, position, dirname)
+ },
+ insertAttachmentMedia(name, fileId, mimeType, position = null, dirname = '') {
+ // inspired by the fixedEncodeURIComponent function suggested in
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
+ const src = dirname + '/'
+ + encodeURIComponent(name).replace(/[!'()*]/g, (c) => {
+ return '%' + c.charCodeAt(0).toString(16).toUpperCase()
+ })
+ // simply get rid of brackets to make sure link text is valid
+ // as it does not need to be unique and matching the real file name
+ const alt = name.replaceAll(/[[\]]/g, '')
+
+ const chain = position
+ ? this.$editor.chain().focus(position)
+ : this.$editor.chain()
+
+ chain.setImage({ src, alt }).focus().run()
+ },
+ insertAttachmentImage(name, fileId, mimeType, position = null, dirname = '') {
// inspired by the fixedEncodeURIComponent function suggested in
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
const src = dirname + '/'
diff --git a/src/nodes/ImageView.vue b/src/nodes/ImageView.vue
index eb812e6d0..8519ebd58 100644
--- a/src/nodes/ImageView.vue
+++ b/src/nodes/ImageView.vue
@@ -34,10 +34,25 @@
@mouseleave="showIcons = false">
<transition name="fade">
<template v-if="!failed">
- <img v-show="loaded"
- :src="imageUrl"
- class="image__main"
- @load="onLoaded">
+ <div v-if="isMediaAttachment"
+ class="media">
+ <img v-show="loaded"
+ :src="imageUrl"
+ class="image__main"
+ @load="onLoaded">
+ <span class="name">
+ {{ alt }}
+ </span>
+ <span class="size">
+ {{ attachmentMetadata.size }}
+ </span>
+ </div>
+ <div v-else>
+ <img v-show="loaded"
+ :src="imageUrl"
+ class="image__main"
+ @load="onLoaded">
+ </div>
</template>
<template v-else>
<ImageIcon class="image__main image__main--broken-icon" :size="100" />
@@ -45,7 +60,8 @@
</transition>
<transition name="fade">
<div v-show="loaded" class="image__caption">
- <input ref="altInput"
+ <input v-show="!isMediaAttachment"
+ ref="altInput"
type="text"
class="image__caption__input"
:value="alt"
@@ -143,9 +159,14 @@ export default {
showIcons: false,
imageUrl: null,
errorMessage: null,
+ attachmentType: null,
+ attachmentMetadata: {},
}
},
computed: {
+ isMediaAttachment() {
+ return this.attachmentType !== 'image'
+ },
canDisplayImage() {
if (!this.isSupportedImage) {
return false
@@ -212,24 +233,31 @@ export default {
},
methods: {
async init() {
- const [url, fallback] = this.$imageResolver.resolve(this.src)
- return this.loadImage(url).catch((e) => {
- if (fallback) {
- return this.loadImage(fallback)
+ const candidates = this.$imageResolver.resolve(this.src)
+ return this.load(candidates)
+ },
+ async load(candidates) {
+ const candidate = candidates.shift()
+ return this.loadImage(candidate.url, candidate.type, candidate.name).catch((e) => {
+ if (candidates.length > 0) {
+ return this.load(candidates)
// TODO if fallback works, rewrite the url with correct document ID
}
-
return Promise.reject(e)
})
},
-
- async loadImage(imageUrl) {
+ async loadImage(imageUrl, attachmentType, name = null) {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => {
this.imageUrl = imageUrl
this.imageLoaded = true
this.loaded = true
+ this.attachmentType = attachmentType
+ console.debug('SUCCESS type', attachmentType)
+ if (attachmentType === 'media') {
+ this.loadMediaMetadata(name)
+ }
resolve(imageUrl)
}
img.onerror = (e) => {
@@ -238,6 +266,12 @@ export default {
img.src = imageUrl
})
},
+ loadMediaMetadata(name) {
+ this.$imageResolver.getMediaMetadata(name).then((response) => {
+ console.debug('GOTCHAAAAAA', response.data)
+ this.attachmentMetadata = response.data
+ })
+ },
onImageLoadFailure(err) {
this.failed = true
this.imageLoaded = false
@@ -306,6 +340,20 @@ export default {
max-height: calc(100vh - 50px - 50px);
}
+ .media {
+ display: flex;
+ align-items: center;
+ img {
+ width: 32px;
+ height: 32px;
+ }
+ .name {
+ flex-grow: 1;
+ text-align: left;
+ margin-left: 8px;
+ }
+ }
+
.image__error-message {
display: block;
text-align: center;
diff --git a/src/services/ImageResolver.js b/src/services/ImageResolver.js
index fdad0b062..b3db6e3e0 100644
--- a/src/services/ImageResolver.js
+++ b/src/services/ImageResolver.js
@@ -21,6 +21,7 @@
*/
import { generateUrl, generateRemoteUrl } from '@nextcloud/router'
+import axios from '@nextcloud/axios'
import pathNormalize from 'path-normalize'
export default class ImageResolver {
@@ -50,35 +51,70 @@ export default class ImageResolver {
resolve(src) {
if (this.#session && src.startsWith('text://')) {
const imageFileName = getQueryVariable(src, 'imageFileName')
- return [this.#getAttachmentUrl(imageFileName)]
+ return [{
+ type: 'image',
+ url: this.#getImageAttachmentUrl(imageFileName),
+ }]
}
if (this.#session && src.startsWith(`.attachments.${this.#session?.documentId}/`)) {
const imageFileName = decodeURIComponent(src.replace(`.attachments.${this.#session?.documentId}/`, '').split('?')[0])
- return [this.#getAttachmentUrl(imageFileName)]
+ return [
+ {
+ type: 'image',
+ url: this.#getImageAttachmentUrl(imageFileName),
+ },
+ {
+ type: 'media',
+ url: this.#getMediaPreviewUrl(imageFileName),
+ name: imageFileName,
+ },
+ ]
}
if (isDirectUrl(src)) {
- return [src]
+ return [{
+ type: 'image',
+ url: src,
+ }]
}
if (hasPreview(src)) { // && this.#mime !== 'image/gif') {
- return [this.#previewUrl(src)]
+ return [{
+ type: 'image',
+ url: this.#previewUrl(src),
+ }]
}
// if it starts with '.attachments.1234/'
if (src.match(/^\.attachments\.\d+\//)) {
const imageFileName = this.#relativePath(src)
.replace(/\.attachments\.\d+\//, '')
- const attachmentUrl = this.#getAttachmentUrl(imageFileName)
- // try the webdav url and attachment API if the fails
- return [this.#davUrl(src), attachmentUrl]
+ // try the webdav url and attachment API if it fails
+ return [
+ {
+ type: 'image',
+ url: this.#davUrl(src),
+ },
+ {
+ type: 'image',
+ url: this.#getImageAttachmentUrl(imageFileName),
+ },
+ {
+ type: 'media',
+ url: this.#getMediaPreviewUrl(imageFileName),
+ name: imageFileName,
+ },
+ ]
}
- return [this.#davUrl(src)]
+ return [{
+ type: 'image',
+ url: this.#davUrl(src),
+ }]
}
- #getAttachmentUrl(imageFileName) {
+ #getImageAttachmentUrl(imageFileName) {
if (!this.#session) {
return this.#davUrl(
`${this.#attachmentDirectory}/${imageFileName}`
@@ -99,6 +135,40 @@ export default class ImageResolver {
})
}
+ #getMediaPreviewUrl(mediaFileName) {
+ if (this.#user || !this.#shareToken) {
+ return generateUrl('/apps/text/mediaPreview?documentId={documentId}&sessionId={sessionId}&sessionToken={sessionToken}&mediaFileName={mediaFileName}', {
+ ...this.#textApiParams(),
+ mediaFileName,
+ })
+ }
+
+ return generateUrl('/apps/text/mediaPreview?documentId={documentId}&sessionId={sessionId}&sessionToken={sessionToken}&mediaFileName={mediaFileName}&shareToken={shareToken}', {
+ ...this.#textApiParams(),
+ mediaFileName,
+ shareToken: this.#shareToken,
+ })
+ }
+
+ #getMediaMetadataUrl(mediaFileName) {
+ if (this.#user || !this.#shareToken) {
+ return generateUrl('/apps/text/mediaMetadata?documentId={documentId}&sessionId={sessionId}&sessionToken={sessionToken}&mediaFileName={mediaFileName}', {
+ ...this.#textApiParams(),
+ mediaFileName,
+ })
+ }
+
+ return generateUrl('/apps/text/mediaMetadata?documentId={documentId}&sessionId={sessionId}&sessionToken={sessionToken}&mediaFileName={mediaFileName}&shareToken={shareToken}', {
+ ...this.#textApiParams(),
+ mediaFileName,
+ shareToken: this.#shareToken,
+ })
+ }
+
+ getMediaMetadata(mediaFileName) {
+ return axios.get(this.#getMediaMetadataUrl(mediaFileName))
+ }
+
#textApiParams() {
if (this.#session) {
return {
diff --git a/src/services/SyncService.js b/src/services/SyncService.js
index 24c50c844..fdd692f77 100644
--- a/src/services/SyncService.js
+++ b/src/services/SyncService.js
@@ -284,18 +284,6 @@ class SyncService {
})
}
- insertImageLink(imageLink) {
- const params = {
- documentId: this.document.id,
- sessionId: this.session.id,
- sessionToken: this.session.token,
- shareToken: this.options.shareToken || '',
- link: imageLink,
- }
- const url = endpointUrl('image/link')
- return axios.post(url, params)
- }
-
insertImageFile(imagePath) {
const params = {
documentId: this.document.id,