diff --git a/.gitignore b/.gitignore index dff251e..548aed9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ cache/sitemap.xml cache/timer.yaml +media/tmp/.gitkeep system/vendor cypress data/navigation diff --git a/cache/timer.yaml b/cache/timer.yaml index 9d86c44..66f4e85 100644 --- a/cache/timer.yaml +++ b/cache/timer.yaml @@ -1 +1 @@ -licenseupdate: 1745608151 +licenseupdate: 1745867608 diff --git a/media/live/web-optimized-1200x800-97kb.webp b/media/live/web-optimized-1200x800-97kb.webp new file mode 100644 index 0000000..dca48d1 Binary files /dev/null and b/media/live/web-optimized-1200x800-97kb.webp differ diff --git a/media/original/web-optimized-1200x800-97kb.webp b/media/original/web-optimized-1200x800-97kb.webp new file mode 100644 index 0000000..f8630f5 Binary files /dev/null and b/media/original/web-optimized-1200x800-97kb.webp differ diff --git a/media/thumbs/web-optimized-1200x800-97kb.webp b/media/thumbs/web-optimized-1200x800-97kb.webp new file mode 100644 index 0000000..5e80061 Binary files /dev/null and b/media/thumbs/web-optimized-1200x800-97kb.webp differ diff --git a/media/tmp/.gitkeep b/media/tmp/.gitkeep index e69de29..45adbb2 100644 --- a/media/tmp/.gitkeep +++ b/media/tmp/.gitkeep @@ -0,0 +1 @@ +.gitkeep \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiImage.php b/system/typemill/Controllers/ControllerApiImage.php index 4dfae6e..f98cd6a 100644 --- a/system/typemill/Controllers/ControllerApiImage.php +++ b/system/typemill/Controllers/ControllerApiImage.php @@ -6,17 +6,13 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use Typemill\Models\Media; use Typemill\Models\StorageWrapper; +use Typemill\Models\Navigation; +use Typemill\Models\User; use Typemill\Extensions\ParsedownExtension; use Typemill\Static\Translations; class ControllerApiImage extends Controller { - - # MISSING - # - # return error messages and display in image component - # check if resized is bigger than original, then use original - public function getPagemedia(Request $request, Response $response, $args) { $url = $request->getQueryParams()['url'] ?? false; @@ -61,6 +57,145 @@ class ControllerApiImage extends Controller return $response->withHeader('Content-Type', 'application/json'); } + public function getUnusedMedia(Request $request, Response $response, $args) + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + # load all images + $imagelist = $storage->getImageList(); + + # load all files + $filelist = $storage->getFileList(); + + # get navigation + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $langattr); + + $fullNavigation = $navigation->getHomepageItem($urlinfo['baseurl']); + $fullNavigation->folderContent = $draftNavigation; + + $usedmediaList = $this->getMediaFromPages([$fullNavigation], $storage, $media = []); + + # get media from users + $userModel = new User(); + $userList = $userModel->getAllUsers(); + $usedmediaList = $this->getMediaFromUsers($storage, $usedmediaList, $userList); + + # get media from settings + $settingsfile = $storage->getFile('settingsFolder', '', 'settings.yaml'); + $settingsmedia = $this->findMediaInText($settingsfile); + if(isset($settingsmedia[2]) && !empty($settingsmedia[2])) + { + $usedmediaList = array_merge($usedmediaList, $settingsmedia[2]); + } + + + if(empty($usedmediaList)) + { + + } + + $usedMedia = []; + foreach($usedmediaList as $name) + { + $usedMedia[$name] = true; + } + + $unusedMedia = []; + foreach($imagelist as $key => $item) + { + if(!isset($usedMedia[$item['name']])) + { + $unusedMedia[] = $item; + } + } + + foreach($filelist as $key => $item) + { + if(!isset($usedMedia[$item['name']])) + { + $unusedMedia[] = $item; + } + } + + $response->getBody()->write(json_encode([ + 'used' => $usedMedia, + 'unused' => $unusedMedia + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + protected function getMediaFromUsers($storage, $usedmediaList, $userList) + { + foreach($userList as $username) + { + $userfile = $storage->getFile('settingsFolder', 'users', $username . '.yaml'); + $usermedia = $this->findMediaInText($userfile); + if(isset($usermedia[2]) && !empty($usermedia[2])) + { + $usedmediaList = array_merge($usedmediaList, $usermedia[2]); + } + } + + return $usedmediaList; + } + + protected function getMediaFromPages($navigation, $storage, $usedMedia) + { + foreach($navigation as $item) + { + $pagemedia = []; + $path = $item->pathWithoutType; + $draftmd = $storage->getFile('contentFolder', '', $path . '.txt'); + if($draftmd) + { + $markdownArray = json_decode($draftmd); + $parsedown = new ParsedownExtension(); + $markdown = $parsedown->arrayBlocksToMarkdown($markdownArray); + $draftmedia = $this->findMediaInText($markdown); + if(isset($draftmedia[2]) && !empty($draftmedia[2])) + { + $pagemedia = array_merge($pagemedia, $draftmedia[2]); + } + } + + $livemd = $storage->getFile('contentFolder', '', $path . '.md'); + if($livemd) + { + $livemedia = $this->findMediaInText($livemd); + if(isset($livemedia[2]) && !empty($livemedia[2])) + { + $pagemedia = array_merge($pagemedia, $livemedia[2]); + } + } + + $meta = $storage->getFile('contentFolder', '', $path . '.yaml'); + if($meta) + { + $metamedia = $this->findMediaInText($meta); + if(isset($metamedia[2]) && !empty($metamedia[2])) + { + $pagemedia = array_merge($pagemedia, $metamedia[2]); + } + } + + if(!empty($pagemedia)) + { + $usedMedia = array_merge($usedMedia, $pagemedia); + } + + if($item->elementType == 'folder' && !empty($item->folderContent)) + { + $usedMedia = $this->getMediaFromPages($item->folderContent, $storage, $usedMedia); + } + } + + return $usedMedia; + } + protected function findMediaInText($text) { preg_match_all('/media\/(live|files)\/(.+?\.[a-zA-Z]{2,4})/', $text, $matches); diff --git a/system/typemill/author/js/vue-medialib.js b/system/typemill/author/js/vue-medialib.js index f29621d..0b4561e 100644 --- a/system/typemill/author/js/vue-medialib.js +++ b/system/typemill/author/js/vue-medialib.js @@ -41,22 +41,31 @@ const medialib = {
- - +
+ - - + + + + + +
@@ -77,8 +86,14 @@ const medialib = {
+
+
{{error}}
+
+
!!!
+
The media listed below are not used in content files, user files, or settings. We do not check for usage in any other places, so please be careful and double-check before deleting any media.
+
+
-
{{error}}
item.name === name); + if(index !== -1) + { + this.imagedata.splice(index, 1); + } + this.showImages(this.active); + } + else if(this.active == 'unusedmedia') + { + const index = this.unuseddata.findIndex(item => item.name === name); + if(index !== -1) + { + this.unuseddata.splice(index, 1); + } + this.showUnusedMedia(); + this.refresh = true; + } + else + { + const index = this.filedata.findIndex(item => item.name === name); + if(index !== -1) + { + this.filedata.splice(index, 1); + } + this.showFiles(this.active); + } + }, + deleteMedia(media) + { + if(media.src_live) + { + this.deleteImage(media); + } + else + { + this.deleteFile(media); + } + }, loadFiles(filetype) { var fileself = this; @@ -551,6 +632,34 @@ const medialib = { } }); }, + loadUnusedMedia() + { + var mediaself = this; + + tmaxios.get('/api/v1/unusedmedia',{ + params: { + 'url': data.urlinfo.route, + } + }) + .then(function (response) + { + mediaself.unuseddata = response.data.unused; + mediaself.showUnusedMedia(); + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + + mediaself.error = message; + } + }); + }, loadPageMedia() { this.error = false; @@ -640,33 +749,6 @@ const medialib = { } }); }, - deleteMedia(media) - { - if(media.src_live) - { - this.deleteImage(media); - } - else - { - this.deleteFile(media); - } - }, - removeImage(name) - { - const index = this.imagedata.findIndex(item => item.name === name); - if(index !== -1) - { - this.imagedata.splice(index, 1); - } - }, - removeFile(name) - { - const index = this.filedata.findIndex(item => item.name === name); - if(index !== -1) - { - this.filedata.splice(index, 1); - } - }, deleteImage(image) { imageself = this; @@ -679,8 +761,7 @@ const medialib = { }) .then(function (response) { - imageself.showImages(); - imageself.removeImage(image.name); + imageself.removeMedia(image.name); }) .catch(function (error) { @@ -708,8 +789,7 @@ const medialib = { }) .then(function (response) { - fileself.showFiles(fileself.active); - fileself.removeFile(file.name); + fileself.removeMedia(file.name); }) .catch(function (error) { @@ -782,7 +862,6 @@ const medialib = { }) .then((response) => { - console.info(file); var type = 'files'; if (file.type.startsWith('video/')) { diff --git a/system/typemill/routes/api.php b/system/typemill/routes/api.php index 08ed8ea..d22832f 100644 --- a/system/typemill/routes/api.php +++ b/system/typemill/routes/api.php @@ -47,6 +47,7 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) { # IMAGES $group->get('/pagemedia', ControllerApiImage::class . ':getPagemedia')->setName('api.image.pagemedia')->add(new ApiAuthorization($acl, 'mycontent', 'read')); # author + $group->get('/unusedmedia', ControllerApiImage::class . ':getUnusedMedia')->setName('api.image.unusedmedia')->add(new ApiAuthorization($acl, 'mycontent', 'read')); # author $group->get('/images', ControllerApiImage::class . ':getImages')->setName('api.image.images')->add(new ApiAuthorization($acl, 'mycontent', 'read')); # author $group->post('/image', ControllerApiImage::class . ':saveImage')->setName('api.image.create')->add(new ApiAuthorization($acl, 'mycontent', 'create')); # author $group->put('/image', ControllerApiImage::class . ':publishImage')->setName('api.image.publish')->add(new ApiAuthorization($acl, 'mycontent', 'create')); # author