1
0
mirror of https://github.com/typemill/typemill.git synced 2025-08-04 13:17:29 +02:00

V2.17 add unused media button

This commit is contained in:
trendschau
2025-04-29 16:17:50 +02:00
parent 0a2d06e1c4
commit f5f2df299d
9 changed files with 277 additions and 60 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
cache/sitemap.xml
cache/timer.yaml
media/tmp/.gitkeep
system/vendor
cypress
data/navigation

2
cache/timer.yaml vendored
View File

@@ -1 +1 @@
licenseupdate: 1745608151
licenseupdate: 1745867608

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1 @@
.gitkeep

View File

@@ -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);

View File

@@ -41,22 +41,31 @@ const medialib = {
</button>
</div>
<div class="relative inline-block">
<!-- Hidden File Input -->
<input
ref="uploadInput"
type="file"
class="hidden"
@change="onFileChange($event)"
accept="*/*"
/>
<div class="flex">
<button
@click.prevent="loadUnusedMedia()"
:class="isActive('unusedmedia')"
class="px-2 py-1 mr-2 hover:bg-stone-700 hover:dark:bg-stone-900 hover:text-stone-50 transition duration-100">
<svg class="icon icon-eye-blocked"><use xlink:href="#icon-eye-blocked"></use></svg>
</button>
<!-- Upload Button -->
<button
@click.prevent="$refs.uploadInput.click()"
class="px-2 py-2 bg-stone-600 text-white hover:bg-stone-700 hover:dark:bg-stone-900 hover:text-stone-50 transition duration-100 flex items-center"
>
<svg class="icon icon-upload w-4 h-4"><use xlink:href="#icon-upload"></use></svg>
</button>
<!-- Hidden File Input -->
<input
ref="uploadInput"
type="file"
class="hidden"
@change="onFileChange($event)"
accept="*/*"
/>
<!-- Upload Button -->
<button
@click.prevent="$refs.uploadInput.click()"
class="px-2 py-2 bg-stone-600 text-white hover:bg-stone-700 hover:dark:bg-stone-900 hover:text-stone-50 transition duration-100 flex items-center"
>
<svg class="icon icon-upload w-4 h-4"><use xlink:href="#icon-upload"></use></svg>
</button>
</div>
</div>
</div>
<div v-if="totalPages > 1">
@@ -77,8 +86,14 @@ const medialib = {
</div>
</div>
<div class="w-3/4">
<div class="px-5">
<div v-if="error" class="w-full px-5 mb-4 p-2 text-center bg-rose-500 text-stone-50">{{error}}</div>
<div v-if="active == 'unusedmedia'" class="px-5 flex">
<div class="px-5 mb-4 p-2 bg-rose-500 text-stone-50">!!!</div>
<div class=" px-5 mb-4 p-2 bg-stone-200">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.</div>
</div>
</div>
<div class="flex flex-wrap justify-start px-5 relative">
<div v-if="error" class="w-full mb-4 p-2 text-center bg-rose-500 text-stone-50">{{error}}</div>
<TransitionGroup name="list">
<div
v-for = "(media, index) in paginatedItems"
@@ -303,8 +318,9 @@ const medialib = {
filedata: false, /* holds the files */
imagedata: false, /* holds the images */
pagedata: false, /* holds the page media */
unuseddata: false, /* holds media that are not in use */
mediadetails: false, /* holds the details of a single media file */
pagedata: false, /* holds the page media */
showmediadetails: false,
showmedialist: false, /* show list of media files */
@@ -320,6 +336,7 @@ const medialib = {
error: false,
load: false,
refresh: false,
adminurl: false,
baseurl: data.urlinfo.baseurl,
}
@@ -354,7 +371,19 @@ const medialib = {
computed: {
filteredItems()
{
const medialist = this.active === 'images' ? this.imagedata : this.filedata;
var medialist = false;
if (this.active === 'images')
{
medialist = this.imagedata;
}
else if(this.active === 'files')
{
medialist = this.filedata;
}
else if(this.active === 'unusedmedia')
{
medialist = this.unuseddata;
}
if (!medialist) return {};
@@ -455,7 +484,6 @@ const medialib = {
},
reset()
{
/* this.active = false; */
this.error = false;
this.showmedialist = false;
this.showmediadetails = false;
@@ -465,7 +493,7 @@ const medialib = {
},
showImages()
{
if(!this.imagedata)
if(!this.imagedata || this.refresh)
{
this.loadImages();
return;
@@ -476,7 +504,7 @@ const medialib = {
},
showFiles(filetype)
{
if(!this.filedata)
if(!this.filedata || this.refresh)
{
this.loadFiles(filetype);
return;
@@ -485,6 +513,17 @@ const medialib = {
this.active = filetype;
this.showmedialist = true;
},
showUnusedMedia()
{
if(!this.unuseddata)
{
this.loadUnusedMedia();
return;
}
this.reset();
this.active = 'unusedmedia';
this.showmedialist = true;
},
showUpload()
{
this.reset();
@@ -523,6 +562,48 @@ const medialib = {
this.$emit('addFromMedialibEvent', media);
}
},
removeMedia(name)
{
if(this.active === 'images')
{
const index = this.imagedata.findIndex(item => 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/'))
{

View File

@@ -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