Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
change image target in markdown content to relative path, handle it i…
…n ImageView

Signed-off-by: Julien Veyssier <[email protected]>
  • Loading branch information
Julien Veyssier committed May 23, 2022
commit cad9a1d7f62fb5d3e2ba5eb053497ad207f5aee4
24 changes: 20 additions & 4 deletions lib/Service/ImageService.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ public function uploadImage(int $documentId, string $newFileName, $newFileResour
$savedFile = $saveDir->newFile($fileName, $newFileResource);
return [
'name' => $fileName,
'dirname' => $saveDir->getName(),
'id' => $savedFile->getId(),
'documentId' => $textFile->getId(),
];
Expand Down Expand Up @@ -183,6 +184,7 @@ public function uploadImagePublic(?int $documentId, string $newFileName, $newFil
$savedFile = $saveDir->newFile($fileName, $newFileResource);
return [
'name' => $fileName,
'dirname' => $saveDir->getName(),
'id' => $savedFile->getId(),
'documentId' => $textFile->getId(),
];
Expand Down Expand Up @@ -227,6 +229,7 @@ private function copyImageFile(File $imageFile, Folder $saveDir, File $textFile)
// get file type and name
return [
'name' => $fileName,
'dirname' => $saveDir->getName(),
'id' => $targetFile->getId(),
'documentId' => $textFile->getId(),
];
Expand Down Expand Up @@ -457,7 +460,7 @@ public function cleanupAttachments(int $fileId): int {
$attachmentsByName[$attNode->getName()] = $attNode;
}

$contentAttachmentNames = $this->getAttachmentNamesFromContent($textFile->getContent());
$contentAttachmentNames = $this->getAttachmentNamesFromContent($textFile->getContent(), $fileId);

$toDelete = array_diff(array_keys($attachmentsByName), $contentAttachmentNames);
foreach ($toDelete as $name) {
Expand All @@ -476,20 +479,33 @@ public function cleanupAttachments(int $fileId): int {
* @param string $content
* @return array
*/
public static function getAttachmentNamesFromContent(string $content): array {
$matches = [];
public static function getAttachmentNamesFromContent(string $content, int $fileId): array {
$oldMatches = [];
preg_match_all(
// simple version with .+ between the brackets
// '/\!\[.+\]\(text:\/\/image\?[^)]*imageFileName=([^)&]+)\)/',
// complex version of php-markdown
'/\!\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[\])*\])*\])*\])*\])*\])*\]\(text:\/\/image\?[^)]*imageFileName=([^)&]+)\)/',
$content,
$oldMatches,
PREG_SET_ORDER
);
$oldNames = array_map(static function (array $match) {
return urldecode($match[1]);
}, $oldMatches);

$matches = [];
preg_match_all(
'/\!\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[(?>[^\[\]]+|\[\])*\])*\])*\])*\])*\])*\]\(\.attachments\.'.$fileId.'\/([^)&]+)\)/',
$content,
$matches,
PREG_SET_ORDER
);
return array_map(static function (array $match) {
$names = array_map(static function (array $match) {
return urldecode($match[1]);
}, $matches);

return array_merge($names, $oldNames);
}

/**
Expand Down
12 changes: 9 additions & 3 deletions src/components/EditorWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ export default {
}

return this.$syncService.uploadImage(file).then((response) => {
this.insertAttachmentImage(response.data?.name, response.data?.id, position)
this.insertAttachmentImage(response.data?.name, response.data?.id, position, response.data?.dirname)
}).catch((error) => {
console.error(error)
showError(error?.response?.data?.error)
Expand All @@ -625,21 +625,27 @@ export default {
insertImagePath(imagePath) {
this.uploadingImages = true
this.$syncService.insertImageFile(imagePath).then((response) => {
this.insertAttachmentImage(response.data?.name, response.data?.id)
this.insertAttachmentImage(response.data?.name, response.data?.id, null, response.data?.dirname)
}).catch((error) => {
console.error(error)
showError(error?.response?.data?.error)
}).then(() => {
this.uploadingImages = false
})
},
insertAttachmentImage(name, fileId, position = null) {
insertAttachmentImage(name, fileId, 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 = 'text://image?imageFileName='
+ encodeURIComponent(name).replace(/[!'()*]/g, (c) => {
return '%' + c.charCodeAt(0).toString(16).toUpperCase()
})
*/
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, '')
Expand Down
49 changes: 28 additions & 21 deletions src/nodes/ImageView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -153,28 +153,12 @@ export default {
},
imageUrl() {
if (this.src.startsWith('text://')) {
const documentId = this.currentSession?.documentId
const sessionId = this.currentSession?.id
const sessionToken = this.currentSession?.token
const imageFileName = getQueryVariable(this.src, 'imageFileName')
if (getCurrentUser() || !this.token) {
return generateUrl('/apps/text/image?documentId={documentId}&sessionId={sessionId}&sessionToken={sessionToken}&imageFileName={imageFileName}',
{
documentId,
sessionId,
sessionToken,
imageFileName,
})
} else {
return generateUrl('/apps/text/image?documentId={documentId}&sessionId={sessionId}&sessionToken={sessionToken}&imageFileName={imageFileName}&shareToken={shareToken}',
{
documentId,
sessionId,
sessionToken,
imageFileName,
shareToken: this.token,
})
}
return this.getTextApiUrl(imageFileName)
}
if (this.src.startsWith(`.attachments.${this.currentSession?.documentId}/`)) {
const imageFileName = decodeURIComponent(this.src.replace(`.attachments.${this.currentSession?.documentId}/`, '').split('?')[0])
return this.getTextApiUrl(imageFileName)
}
if (this.isRemoteUrl || this.isPreviewUrl || this.isDataUrl) {
return this.src
Expand Down Expand Up @@ -301,6 +285,29 @@ export default {
this.editor.commands.scrollIntoView()
})
},
getTextApiUrl(imageFileName) {
const documentId = this.currentSession?.documentId
const sessionId = this.currentSession?.id
const sessionToken = this.currentSession?.token
if (getCurrentUser() || !this.token) {
return generateUrl('/apps/text/image?documentId={documentId}&sessionId={sessionId}&sessionToken={sessionToken}&imageFileName={imageFileName}',
{
documentId,
sessionId,
sessionToken,
imageFileName,
})
} else {
return generateUrl('/apps/text/image?documentId={documentId}&sessionId={sessionId}&sessionToken={sessionToken}&imageFileName={imageFileName}&shareToken={shareToken}',
{
documentId,
sessionId,
sessionToken,
imageFileName,
shareToken: this.token,
})
}
},
},
}
</script>
Expand Down
52 changes: 35 additions & 17 deletions tests/TextTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,54 @@
use OCP\Files\Folder;

class TextTest extends \PHPUnit\Framework\TestCase {
private static $attachmentNames = [
'aaa.png',
'aaa (2).png',
'aaa 2).png',
'aaa (2.png',
'aaa ((2.png',
'aaa 2)).png',
'a[a]a.png',
'a(a)a.png',
'a](a.png',
',;:!?.§-_a_',
'a`a`.png',
];

public function testDummy() {
$app = new Application();
$this->assertEquals('text', $app::APP_NAME);
}

public function testGetAttachmentNamesFromContent() {
$contentNames = [
'aaa.png',
'aaa (2).png',
'aaa 2).png',
'aaa (2.png',
'aaa ((2.png',
'aaa 2)).png',
'a[a]a.png',
'a(a)a.png',
'a](a.png',
',;:!?.§-_a_',
'a`a`.png',
];
public function testGetOldAttachmentNamesFromContent() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something for a separate PR but we should move those tests into tests/unit/Service/ImageServiceTest.php

$content = "some content\n";
foreach ($contentNames as $name) {
foreach (self::$attachmentNames as $name) {
// this is how it's generated in MenuBar.vue
$linkText = preg_replace('/[[\]]/', '', $name);
$encodedName = urlencode($name);
$content .= '![' . $linkText . '](text://image?imageFileName=' . $encodedName . ")\n";
}
$content .= 'some content';

$computedNames = ImageService::getAttachmentNamesFromContent($content);
foreach ($contentNames as $contentName) {
$computedNames = ImageService::getAttachmentNamesFromContent($content, 33);
foreach (self::$attachmentNames as $contentName) {
$this->assertContains($contentName, $computedNames);
}
}


public function testGetAttachmentNamesFromContent() {
$content = "some content\n";
foreach (self::$attachmentNames as $name) {
// this is how it's generated in MenuBar.vue
$linkText = preg_replace('/[[\]]/', '', $name);
$encodedName = urlencode($name);
$content .= '![' . $linkText . '](.attachments.33/' . $encodedName . ")\n";
}
$content .= 'some content';

$computedNames = ImageService::getAttachmentNamesFromContent($content, 33);
foreach (self::$attachmentNames as $contentName) {
$this->assertContains($contentName, $computedNames);
}
}
Expand Down