1
0
mirror of https://github.com/typemill/typemill.git synced 2025-07-26 00:31:38 +02:00

Medialib finished

This commit is contained in:
trendschau
2023-07-13 15:11:46 +02:00
parent dd0607169a
commit d0772caf86
18 changed files with 1075 additions and 1520 deletions

View File

@@ -1 +1 @@
["# ToDos Version 2","[TOC]","## Visual Editor","* FIXED: File is not published from tmp to media\/files if you save the block.","## Raw Editor","* DONE ready","## Medialib","* Please do next","## Posts","* Setup","## Plugins","* Asset Class","## Frontend","* DONE\n* DONE: Test restrictions","## ToDos","Biig blocks:","* Media Library\n* Posts\n* Recover Password","Small features:","* Sitemap and ping\n* Captcha\n* Clear Cache\n* Security Log\n* Backend fields\n* Proxy\n* DONE: Session handling: csrf fail and session start error if restrictions are active\n* Editor: Warn if open another block","Cleanups:","* Events\n* Error messages\n* Translations","## Select userroles","* Userroles for file restriction: in vue-blox-components loaded via api\n* Userroles for userfields: in php model user getUserFields()\n* Userroles for meta: in php controller apiAuthorMeta getMeta()\n* Plugins and themes: in php model extension getThemeDefinitions()","## License Check","* On activation in apiControllerExtension. It checks the license in yaml.\n* In plugin php code with setPremiumLicense\n* In static plugins, it checks manual premium list and method setPremiumLicense and more "]
["# ToDos Version 2","[TOC]","## Visual Editor","* FIXED: File is not published from tmp to media\/files if you save the block.","## Raw Editor","* DONE ready","## Medialib","* DONE","## Posts","* Setup","## Plugins","* Asset Class","## Frontend","* DONE\n* DONE: Test restrictions","## ToDos","Biig blocks:","* DONE: Media Library\n* Posts\n* Recover Password","Small features:","* Sitemap and ping\n* Captcha\n* Clear Cache\n* Security Log\n* Backend fields\n* Proxy\n* DONE: Session handling: csrf fail and session start error if restrictions are active\n* Editor: Warn if open another block\n* Image generation on the fly\n* Assets","Cleanups:","* Events\n* Error messages\n* Translations","## Select userroles","* Userroles for file restriction: in vue-blox-components loaded via api\n* Userroles for userfields: in php model user getUserFields()\n* Userroles for meta: in php controller apiAuthorMeta getMeta()\n* Plugins and themes: in php model extension getThemeDefinitions()","## License Check","* On activation in apiControllerExtension. It checks the license in yaml.\n* In plugin php code with setPremiumLicense\n* In static plugins, it checks manual premium list and method setPremiumLicense and more "]

View File

@@ -1 +1,2 @@
media/files/typemill-v2-navigation.gif: member
media/files/markdown.png: member

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -32,8 +32,6 @@ abstract class Controller
$this->routeParser = $container->get('routeParser');
# $this->csrf = $container->get('csrf');
$this->c->get('dispatcher')->dispatch(new OnTwigLoaded(false), 'onTwigLoaded');
}

View File

@@ -6,10 +6,8 @@ use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Routing\RouteContext;
use Typemill\Models\Validation;
use Typemill\Models\Content;
use Typemill\Models\Navigation;
use Typemill\Models\Meta;
use Typemill\Static\Slug;
class ControllerApiAuthorMeta extends Controller
{
@@ -97,7 +95,7 @@ class ControllerApiAuthorMeta extends Controller
return $response->withHeader('Content-Type', 'application/json');
}
public function updateMetaData(Request $request, Response $response, $args)
public function updateMeta(Request $request, Response $response, $args)
{
$validRights = $this->validateRights($request->getAttribute('c_userrole'), 'content', 'update');
if(!$validRights)
@@ -325,160 +323,6 @@ class ControllerApiAuthorMeta extends Controller
}
/*
# get the standard meta-definitions and the meta-definitions from plugins (same for all sites)
public function aggregateMetaDefinitions($folder = null)
{
$metatabs = $this->meta->getMetaDefinitions();
# the fields for user or role based access
if(!isset($this->settings['pageaccess']) || $this->settings['pageaccess'] === NULL )
{
unset($metatabs['meta']['fields']['fieldsetrights']);
}
# add radio buttons to choose posts or pages for folder.
if(!$folder)
{
unset($metatabs['meta']['fields']['contains']);
}
echo '<pre>';
print_r($metatabs);
die();
# loop through all plugins
if(!empty($this->settings['plugins']))
{
foreach($this->settings['plugins'] as $name => $plugin)
{
if($plugin['active'])
{
$pluginSettings = \Typemill\Settings::getObjectSettings('plugins', $name);
if($pluginSettings && isset($pluginSettings['metatabs']))
{
$metatabs = array_merge_recursive($metatabs, $pluginSettings['metatabs']);
}
}
}
}
# add the meta from theme settings here
$themeSettings = \Typemill\Settings::getObjectSettings('themes', $this->settings['theme']);
if($themeSettings && isset($themeSettings['metatabs']))
{
$metatabs = array_merge_recursive($metatabs, $themeSettings['metatabs']);
}
# dispatch meta
# $metatabs = $this->c->dispatcher->dispatch('onMetaDefinitionsLoaded', new OnMetaDefinitionsLoaded($metatabs))->getData();
return $metatabs;
}
*/
public function publishArticle(Request $request, Response $response, $args)
{
$validRights = $this->validateRights($request->getAttribute('c_userrole'), 'content', 'update');
if(!$validRights)
{
$response->getBody()->write(json_encode([
'message' => 'You do not have enough rights.',
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
$params = $request->getParsedBody();
$validate = new Validation();
$validInput = $validate->articlePublish($params);
if($validInput !== true)
{
$errors = $validate->returnFirstValidationErrors($validInput);
$response->getBody()->write(json_encode([
'message' => reset($errors),
'errors' => $errors
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
$navigation = new Navigation();
$urlinfo = $this->c->get('urlinfo');
$item = $this->getItem($navigation, $params['url'], $urlinfo);
if(!$item)
{
$response->getBody()->write(json_encode([
'message' => 'page not found',
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
}
# publish content
$content = new Content($urlinfo['baseurl']);
$draftMarkdown = $content->getDraftMarkdown($item);
$content->publishMarkdown($item, $draftMarkdown);
# refresh navigation and item
$navigation->clearNavigation();
$draftNavigation = $navigation->getDraftNavigation($urlinfo, $this->settings['langattr']);
$draftNavigation = $navigation->setActiveNaviItems($draftNavigation, $item->keyPathArray);
$item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray);
$response->getBody()->write(json_encode([
'navigation' => $draftNavigation,
'item' => $item
]));
return $response->withHeader('Content-Type', 'application/json');
}
# get the standard meta-definitions and the meta-definitions from plugins (same for all sites)
public function getMetaDefinitions(Request $request, Response $response, $args)
{
$validRights = $this->validateRights($request->getAttribute('c_userrole'), 'content', 'update');
if(!$validRights)
{
$response->getBody()->write(json_encode([
'message' => 'You do not have enough rights.',
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
$metatabs = $this->aggregateMetaDefinitions();
$response->getBody()->write(json_encode([
'definitions' => $metatabs
]));
return $response->withHeader('Content-Type', 'application/json');
}
# we have to flatten field definitions for tabs if there are fieldsets in it
public function flattenTabFields($tabfields, $flattab, $fieldset = null)
{

View File

@@ -9,6 +9,57 @@ use Typemill\Models\StorageWrapper;
class ControllerApiFile extends Controller
{
public function getFiles(Request $request, Response $response, $args)
{
$url = $request->getQueryParams()['url'] ?? false;
$path = $request->getQueryParams()['path'] ?? false;
$storage = new StorageWrapper('\Typemill\Models\Storage');
$filelist = $storage->getFileList();
$response->getBody()->write(json_encode([
'files' => $filelist,
]));
return $response->withHeader('Content-Type', 'application/json');
}
public function getFile(Request $request, Response $response, $args)
{
$name = $request->getQueryParams()['name'] ?? false;
# VALIDATE NAME
if(!$name)
{
$response->getBody()->write(json_encode([
'message' => 'Filename is missing.',
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
$storage = new StorageWrapper('\Typemill\Models\Storage');
$filedetails = $storage->getFileDetails($name);
if(!$filedetails)
{
$response->getBody()->write(json_encode([
'message' => 'No File found.',
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
$response->getBody()->write(json_encode([
'file' => $filedetails,
]));
return $response->withHeader('Content-Type', 'application/json');
}
public function getFileRestrictions(Request $request, Response $response, $args)
{
$params = $request->getQueryParams();
@@ -238,96 +289,39 @@ class ControllerApiFile extends Controller
return $response->withHeader('Content-Type', 'application/json');
}
public function getMediaLibFiles(Request $request, Response $response, $args)
{
# get params from call
$this->params = $request->getParsedBody();
$this->uri = $request->getUri()->withUserInfo('');
$fileProcessor = new ProcessFile();
if(!$fileProcessor->checkFolders())
{
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
}
$filelist = $fileProcessor->scanFilesFlat();
$response->getBody()->write(json_encode([
'files' => $filelist
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
}
public function getFile(Request $request, Response $response, $args)
{
# get params from call
$this->params = $request->getParams();
$this->uri = $request->getUri()->withUserInfo('');
$this->setStructureDraft();
$fileProcessor = new ProcessFile();
if(!$fileProcessor->checkFolders())
{
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
}
$fileDetails = $fileProcessor->getFileDetails($this->params['name'], $this->structureDraft);
if($fileDetails)
{
return $response->withJson(['file' => $fileDetails]);
}
return $response->withJson(['errors' => 'file not found or file name invalid'],404);
}
public function deleteFile(Request $request, Response $response, $args)
{
# get params from call
$this->params = $request->getParams();
$this->uri = $request->getUri()->withUserInfo('');
$params = $request->getParsedBody();
# minimum permission is that user is allowed to delete content
if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'delete'))
if(!isset($params['name']))
{
return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete files.'), 403);
$response->getBody()->write(json_encode([
'message' => 'Filename is missing.'
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
if(!isset($this->params['name']))
$storage = new StorageWrapper('\Typemill\Models\Storage');
$deleted = $storage->deleteMediaFile($params['name']);
if($deleted)
{
return $response->withJson(['errors' => 'file name is missing'],500);
$response->getBody()->write(json_encode([
'message' => 'File deleted successfully.'
]));
return $response->withHeader('Content-Type', 'application/json');
}
$fileProcessor = new ProcessFile();
$response->getBody()->write(json_encode([
'message' => $storage->getError()
]));
if($fileProcessor->deleteFile($this->params['name']))
{
return $response->withJson(['errors' => false]);
}
return $response->withJson(['errors' => 'could not delete the file'],500);
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
# https://www.sitepoint.com/mime-types-complete-list/
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
# https://wiki.selfhtml.org/wiki/MIME-Type/%C3%9Cbersicht

View File

@@ -6,6 +6,7 @@ use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Typemill\Models\ProcessImage;
use Typemill\Models\StorageWrapper;
use Typemill\Extensions\ParsedownExtension;
# use Typemill\Models\ProcessFile;
@@ -21,6 +22,110 @@ class ControllerApiImage extends Controller
# 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;
$path = $request->getQueryParams()['path'] ?? false;
$pagemedia = [];
if(!$path)
{
$response->getBody()->write(json_encode([
'message' => 'Path is missing.',
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
$storage = new StorageWrapper('\Typemill\Models\Storage');
$markdown = $storage->getFile('contentFolder', '', $path . '.txt');
if($markdown)
{
$markdownArray = json_decode($markdown);
$parsedown = new ParsedownExtension();
$markdown = $parsedown->arrayBlocksToMarkdown($markdownArray);
}
else
{
$markdown = $storage->getFile('contentFolder', '', $path . '.md');
}
$mdmedia = $this->findMediaInText($markdown);
$meta = $storage->getFile('contentFolder', '', $path . '.yaml');
$mtmedia = $this->findMediaInText($meta);
$pagemedia = array_merge($mdmedia[2], $mtmedia[2]);
$response->getBody()->write(json_encode([
'pagemedia' => $pagemedia
]));
return $response->withHeader('Content-Type', 'application/json');
}
protected function findMediaInText($text)
{
preg_match_all('/media\/(live|files)\/(.+?\.[a-zA-Z]{2,4})/', $text, $matches);
return $matches;
}
public function getImages(Request $request, Response $response, $args)
{
$url = $request->getQueryParams()['url'] ?? false;
$path = $request->getQueryParams()['path'] ?? false;
$pagemedia = [];
$storage = new StorageWrapper('\Typemill\Models\Storage');
$imagelist = $storage->getImageList();
$response->getBody()->write(json_encode([
'images' => $imagelist
]));
return $response->withHeader('Content-Type', 'application/json');
}
public function getImage(Request $request, Response $response, $args)
{
$name = $request->getQueryParams()['name'] ?? false;
# VALIDATE NAME
if(!$name)
{
$response->getBody()->write(json_encode([
'message' => 'Imagename is missing.',
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
$storage = new StorageWrapper('\Typemill\Models\Storage');
$imagedetails = $storage->getImageDetails($name);
if(!$imagedetails)
{
$response->getBody()->write(json_encode([
'message' => 'No image found.',
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
$response->getBody()->write(json_encode([
'image' => $imagedetails,
]));
return $response->withHeader('Content-Type', 'application/json');
}
public function saveImage(Request $request, Response $response, $args)
{
$params = $request->getParsedBody();
@@ -283,127 +388,38 @@ class ControllerApiImage extends Controller
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(500);
$imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData);
$desiredSizes = ['live' => ['width' => 560, 'height' => 315]];
$imageProcessor = new ProcessImage($this->settings['images']);
if(!$imageProcessor->checkFolders())
{
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
}
$tmpImage = $imageProcessor->createImage($imageData64, $videoID, $desiredSizes);
if(!$tmpImage)
{
return $response->withJson(array('errors' => 'could not create temporary image'));
}
$imageUrl = $imageProcessor->publishImage();
if($imageUrl)
{
$this->params['markdown'] = '![' . $class . '-video](' . $imageUrl . ' "click to load video"){#' . $videoID. ' .' . $class . '}';
$request = $request->withParsedBody($this->params);
$block = new ControllerAuthorBlockApi($this->c);
if($this->params['new'])
{
return $block->addBlock($request, $response, $args);
}
return $block->updateBlock($request, $response, $args);
}
return $response->withJson(array('errors' => 'could not store the preview image'));
}
public function getMediaLibImages(Request $request, Response $response, $args)
{
# get params from call
$this->params = $request->getParsedBody();
$this->uri = $request->getUri()->withUserInfo('');
$imageProcessor = new ProcessImage($this->settings['images']);
if(!$imageProcessor->checkFolders('images'))
{
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
}
$imagelist = $imageProcessor->scanMediaFlat();
$response->getBody()->write(json_encode([
'images' => $imagelist
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
}
public function getImage(Request $request, Response $response, $args)
{
# get params from call
$this->params = $request->getParsedBody();
$this->uri = $request->getUri()->withUserInfo('');
$this->setStructureDraft();
$imageProcessor = new ProcessImage($this->settings['images']);
if(!$imageProcessor->checkFolders('images'))
{
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
}
$imageDetails = $imageProcessor->getImageDetails($this->params['name'], $this->structureDraft);
if($imageDetails)
{
return $response->withJson(['image' => $imageDetails]);
}
return $response->withJson(['errors' => 'Image not found or image name not valid.'], 404);
}
public function deleteImage(Request $request, Response $response, $args)
{
# get params from call
$this->params = $request->getParams();
$this->uri = $request->getUri()->withUserInfo('');
$params = $request->getParsedBody();
# minimum permission is that user is allowed to delete content
if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'delete'))
if(!isset($params['name']))
{
return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete images.'), 403);
$response->getBody()->write(json_encode([
'message' => 'Imagename is missing.'
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
if(!isset($this->params['name']))
$storage = new StorageWrapper('\Typemill\Models\Storage');
$deleted = $storage->deleteImage($params['name']);
if($deleted)
{
return $response->withJson(['errors' => 'image name is missing'],500);
$response->getBody()->write(json_encode([
'message' => 'Image deleted successfully.'
]));
return $response->withHeader('Content-Type', 'application/json');
}
$imageProcessor = new ProcessImage($this->settings['images']);
if(!$imageProcessor->checkFolders('images'))
{
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
}
$response->getBody()->write(json_encode([
'message' => $storage->getError()
]));
if($imageProcessor->deleteImage($this->params['name']))
{
return $response->withJson(['errors' => false]);
}
return $response->withJson(['errors' => 'Oops, looks like we could not delete all sizes of that image.'], 500);
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
}
}

View File

@@ -62,90 +62,4 @@ class ProcessFile extends ProcessAssets
return $this->getFullName();
}
public function deleteFile($name)
{
# validate name
$name = basename($name);
if(file_exists($this->fileFolder . $name) && unlink($this->fileFolder . $name))
{
return true;
}
return false;
}
public function deleteFileWithName($name)
{
# e.g. delete $name = 'logo';
$name = basename($name);
if($name != '' && !in_array($name, array(".","..")))
{
foreach(glob($this->fileFolder . $name) as $file)
{
unlink($file);
}
}
}
/*
* scans content of a folder (without recursion)
* vars: folder path as string
* returns: one-dimensional array with names of folders and files
*/
public function scanFilesFlat()
{
$files = scandir($this->fileFolder);
$filelist = array();
foreach ($files as $key => $name)
{
if (!in_array($name, array(".","..","filerestrictions.yaml")) && file_exists($this->fileFolder . $name))
{
$filelist[] = [
'name' => $name,
'timestamp' => filemtime($this->fileFolder . $name),
'info' => pathinfo($this->fileFolder . $name),
'url' => 'media/files/' . $name,
];
}
}
$filelist = Helpers::array_sort($filelist, 'timestamp', SORT_DESC);
return $filelist;
}
public function getFileDetails($name, $structure)
{
$name = basename($name);
if (!in_array($name, array(".","..")) && file_exists($this->fileFolder . $name))
{
$filedetails = [
'name' => $name,
'timestamp' => filemtime($this->fileFolder . $name),
'bytes' => filesize($this->fileFolder . $name),
'info' => pathinfo($this->fileFolder . $name),
'url' => 'media/files/' . $name,
'pages' => $this->findPagesWithUrl($structure, $name, $result = [])
];
return $filedetails;
}
return false;
}
}

View File

@@ -229,7 +229,6 @@ class ProcessImage extends ProcessAssets
}
# publish image function is moved to storage model
@@ -241,62 +240,10 @@ class ProcessImage extends ProcessAssets
# MOVE TO STORAGE ??
public function deleteImage($name)
{
# validate name
$name = basename($name);
if(!file_exists($this->originalFolder . $name) OR !unlink($this->originalFolder . $name))
{
$this->errors[] = "We could not delete the original image";
}
if(!file_exists($this->liveFolder . $name) OR !unlink($this->liveFolder . $name))
{
$this->errors[] = "We could not delete the live image";
}
if(!file_exists($this->thumbFolder . $name) OR !unlink($this->thumbFolder . $name))
{
$this->errors[] = "we could not delete the thumb image";
}
# delete custom images (resized and grayscaled) array_map('unlink', glob("some/dir/*.txt"));
$pathinfo = pathinfo($name);
foreach(glob($this->customFolder . $pathinfo['filename'] . '\-*.' . $pathinfo['extension']) as $image)
{
# you could check if extension is the same here
if(!unlink($image))
{
$this->errors[] = "we could not delete a custom image (grayscale or resized)";
}
}
if(empty($this->errors))
{
return true;
}
return false;
}
# in use ??
public function deleteImageWithName($name)
{
die("processImage model deleteImageWithName please check method.");
# e.g. delete $name = 'logo...';
$name = basename($name);
@@ -321,6 +268,8 @@ class ProcessImage extends ProcessAssets
# in use ??
public function copyImage($name,$sourcefolder,$targetfolder)
{
die("processImage model copyImage please check method.");
copy($sourcefolder . $name, $targetfolder . $name);
}
@@ -389,78 +338,6 @@ class ProcessImage extends ProcessAssets
/*
* scans content of a folder (without recursion)
* vars: folder path as string
* returns: one-dimensional array with names of folders and files
*/
public function scanMediaFlat()
{
$thumbs = array_diff(scandir($this->thumbFolder), array('..', '.'));
$imagelist = array();
foreach ($thumbs as $key => $name)
{
if (file_exists($this->liveFolder . $name))
{
$imagelist[] = [
'name' => $name,
'timestamp' => filemtime($this->liveFolder . $name),
'src_thumb' => 'media/thumbs/' . $name,
'src_live' => 'media/live/' . $name,
];
}
}
$imagelist = Helpers::array_sort($imagelist, 'timestamp', SORT_DESC);
return $imagelist;
}
# get details from existing image for media library
public function getImageDetails($name, $structure)
{
$name = basename($name);
if (!in_array($name, array(".","..")) && file_exists($this->liveFolder . $name))
{
$imageinfo = getimagesize($this->liveFolder . $name);
if(!$imageinfo && pathinfo($this->liveFolder . $name, PATHINFO_EXTENSION) == 'svg')
{
$imagedetails = [
'name' => $name,
'timestamp' => filemtime($this->liveFolder . $name),
'bytes' => filesize($this->liveFolder . $name),
'width' => '---',
'height' => '---',
'type' => 'svg',
'src_thumb' => 'media/thumbs/' . $name,
'src_live' => 'media/live/' . $name,
'pages' => $this->findPagesWithUrl($structure, $name, $result = [])
];
}
else
{
$imagedetails = [
'name' => $name,
'timestamp' => filemtime($this->liveFolder . $name),
'bytes' => filesize($this->liveFolder . $name),
'width' => $imageinfo[0],
'height' => $imageinfo[1],
'type' => $imageinfo['mime'],
'src_thumb' => 'media/thumbs/' . $name,
'src_live' => 'media/live/' . $name,
'pages' => $this->findPagesWithUrl($structure, $name, $result = [])
];
}
return $imagedetails;
}
return false;
}
public function generateThumbs()
{

View File

@@ -2,6 +2,8 @@
namespace Typemill\Models;
use Typemill\Static\Helpers;
class Storage
{
public $error = false;
@@ -332,6 +334,11 @@ class Storage
return false;
}
##################
## IMAGES ##
##################
public function createUniqueImageName($filename, $extension)
{
$defaultfilename = $filename;
@@ -347,36 +354,6 @@ class Storage
return $filename;
}
public function publishFile($name)
{
$pathinfo = pathinfo($name);
if(!$pathinfo)
{
$this->error = 'Could not read pathinfo.';
return false;
}
$filename = $pathinfo['filename'] . '.' . $pathinfo['extension'];
$filepath = $this->tmpFolder . $filename;
if(!file_exists($this->tmpFolder . $filename))
{
$this->error = "We did not find the file in the tmp-folder or could not read it.";
return false;
}
$success = rename($this->tmpFolder . $filename, $this->fileFolder . $filename);
if($success === true)
{
# return true;
return 'media/files/' . $filename;
}
return false;
}
public function publishImage($name, $noresize = false)
{
$pathinfo = pathinfo($name);
@@ -489,10 +466,221 @@ class Storage
}
public function getImageList()
{
$thumbs = array_diff(scandir($this->thumbsFolder), array('..', '.'));
$imagelist = array();
foreach ($thumbs as $key => $name)
{
if (file_exists($this->liveFolder . $name))
{
$imagelist[] = [
'name' => $name,
'timestamp' => filemtime($this->liveFolder . $name),
'src_thumb' => 'media/thumbs/' . $name,
'src_live' => 'media/live/' . $name,
];
}
}
$imagelist = Helpers::array_sort($imagelist, 'timestamp', SORT_DESC);
return $imagelist;
}
# get details from existing image for media library
public function getImageDetails($name)
{
$name = basename($name);
if (!in_array($name, array(".","..")) && file_exists($this->liveFolder . $name))
{
$imageinfo = getimagesize($this->liveFolder . $name);
if(!$imageinfo && pathinfo($this->liveFolder . $name, PATHINFO_EXTENSION) == 'svg')
{
$imagedetails = [
'name' => $name,
'timestamp' => filemtime($this->liveFolder . $name),
'bytes' => filesize($this->liveFolder . $name),
'width' => '---',
'height' => '---',
'type' => 'svg',
'src_thumb' => 'media/thumbs/' . $name,
'src_live' => 'media/live/' . $name,
];
}
else
{
$imagedetails = [
'name' => $name,
'timestamp' => filemtime($this->liveFolder . $name),
'bytes' => filesize($this->liveFolder . $name),
'width' => $imageinfo[0],
'height' => $imageinfo[1],
'type' => $imageinfo['mime'],
'src_thumb' => 'media/thumbs/' . $name,
'src_live' => 'media/live/' . $name,
];
}
return $imagedetails;
}
return false;
}
public function deleteImage($name)
{
# validate name
$name = basename($name);
if(!file_exists($this->liveFolder . $name) OR !unlink($this->liveFolder . $name))
{
$this->error .= "We could not delete the live image. ";
}
if(!file_exists($this->thumbsFolder . $name) OR !unlink($this->thumbsFolder . $name))
{
$this->error .= "We could not delete the thumb image. ";
}
# delete custom images (resized and grayscaled) array_map('unlink', glob("some/dir/*.txt"));
$pathinfo = pathinfo($name);
foreach(glob($this->originalFolder . $pathinfo['filename'] . '\.*') as $image)
{
# you could check if extension is the same here
if(!unlink($image))
{
$this->error = "We could not delete the original image in $this->originalFolder $image. ";
}
}
foreach(glob($this->customFolder . $pathinfo['filename'] . '\-*.' . $pathinfo['extension']) as $image)
{
# you could check if extension is the same here
if(!unlink($image))
{
$this->error .= "we could not delete a custom image (grayscale or resized). ";
}
}
if(!$this->error)
{
return true;
}
return false;
}
##################
## FILES ##
##################
public function publishFile($name)
{
$pathinfo = pathinfo($name);
if(!$pathinfo)
{
$this->error = 'Could not read pathinfo.';
return false;
}
$filename = $pathinfo['filename'] . '.' . $pathinfo['extension'];
$filepath = $this->tmpFolder . $filename;
if(!file_exists($this->tmpFolder . $filename))
{
$this->error = "We did not find the file in the tmp-folder or could not read it.";
return false;
}
$success = rename($this->tmpFolder . $filename, $this->fileFolder . $filename);
if($success === true)
{
# return true;
return 'media/files/' . $filename;
}
return false;
}
public function getFileList()
{
$files = scandir($this->fileFolder);
$filelist = array();
foreach ($files as $key => $name)
{
if (!in_array($name, array(".","..","filerestrictions.yaml")) && file_exists($this->fileFolder . $name))
{
$filelist[] = [
'name' => $name,
'timestamp' => filemtime($this->fileFolder . $name),
'bytes' => filesize($this->fileFolder . $name),
'info' => pathinfo($this->fileFolder . $name),
'url' => 'media/files/' . $name,
];
}
}
$filelist = Helpers::array_sort($filelist, 'timestamp', SORT_DESC);
return $filelist;
}
public function getFileDetailsBREAK($name)
{
$name = basename($name);
if (!in_array($name, array(".","..")) && file_exists($this->fileFolder . $name))
{
$filedetails = [
'name' => $name,
'timestamp' => filemtime($this->fileFolder . $name),
'bytes' => filesize($this->fileFolder . $name),
'info' => pathinfo($this->fileFolder . $name),
'url' => 'media/files/' . $name,
];
return $filedetails;
}
return false;
}
public function deleteMediaFile($name)
{
# validate name
$name = basename($name);
if(file_exists($this->fileFolder . $name) && unlink($this->fileFolder . $name))
{
return true;
}
return false;
}
public function deleteFileWithName($name)
{
# e.g. delete $name = 'logo';
$name = basename($name);
if($name != '' && !in_array($name, array(".","..")))
{
foreach(glob($this->fileFolder . $name) as $file)
{
unlink($file);
}
}
}

View File

@@ -15,6 +15,11 @@
position: relative;
}
/********************
* VUE *
********************/
[v-cloak] {
display: none;
}
@@ -22,12 +27,25 @@
.initial-leave-active {
transition: opacity 0.2s ease;
}
.initial-enter-from,
.initial-leave-to {
opacity: 0;
}
.fade-enter-active {
transition: opacity 0.2s ease;
}
.fade-enter-from{
opacity: 0;
}
.list-enter-active {
transition: opacity 0.2s ease;
}
.list-enter-from{
opacity: 0;
}
/* CODEAREA */
.codearea{
@@ -449,57 +467,6 @@
font-weight:300;
}
/*** BLOX EDITOR ***
.edit .blox-editor .h1,
.edit .blox-editor .h2,
.edit .blox-editor .h3,
.edit .blox-editor .h4,
.edit .blox-editor .h5,
.edit .blox-editor .h6{
line-height: 1em;
margin-top: 0;
margin-bottom: 0;
}
.edit .blox-editor .h1{
font-weight: 700;
font-size: 2.2em;
padding-top: 0.6em;
padding-bottom: 0.6em;
}
.edit .blox-editor .h2{
font-weight: 700;
font-size: 1.6em;
padding-top: 1.3em;
padding-bottom: 0.6em;
}
.edit .blox-editor .h3{
font-weight: 700;
font-size: 1.3em;
text-transform: none;
padding-top: 1.2em;
padding-bottom: 0.6em;
}
.edit .blox-editor .h4{
font-weight: 700;
font-size: 1.1em;
padding-top: 1.2em;
padding-bottom: 0.6em;
}
.edit .blox-editor .h5{
font-weight: 700;
font-size: 1em;
padding-top: 1.2em;
padding-bottom: 0.6em;
}
.edit .blox-editor .h6{
font-size: 1em;
font-style: italic;
font-weight:300;
padding-top: 1em;
padding-bottom: 0.6em;
}
/************************
** INLINE FORMATG BAR **
@@ -561,320 +528,4 @@
.blox-editor input.urlinput:focus{
outline: 0px;
border: 0px;
}
/*
.title input{
font-size: 2.2em;
font-weight: 700;
padding: 20px;
}
.imageupload{
width: 50%;
position: relative;
display: inline-block;
border-right: 1px dotted grey;
box-sizing:border-box;
}
.imageselect{
width: 50%;
position: relative;
display: inline-block;
box-sizing:border-box;
border:0px;
padding: 0 0 0 0;
margin: 0 0 0 0;
min-height: 70px;
background: #f9f8f6;
font-family: Helvetica, Calibri, Arial, sans-serif;
}
.dropbox{
min-height: 70px;
background: #f9f8f6;
padding: 0px;
box-sizing: border-box;
margin-bottom: 10px;
}
.dropbox p{
position: relative;
cursor: pointer;
line-height: 70px;
width: 100%;
text-align: center;
box-sizing:border-box;
padding: 0;
margin: 0;
}
.dropbox input, .dropbox select{
background-color: #fff;
width: 80%;
margin: 2px 0;
display: inline-block;
}
.video input{
width: 75%;
margin: 15px 0;
}
.dropbox select{
background-image: linear-gradient(45deg, transparent 50%, #444 50%), linear-gradient(135deg, #444 50%, transparent 50%), linear-gradient(to right, #fff, #fff);
}
.dropbox label{
width: 20%;
display: inline-block;
}
.video label{
text-align: right;
padding-right: 10px;
box-sizing: border-box;
}
.dropbox .imgmeta{
padding: 30px;
box-sizing: border-box;
}
.dropbox .input-file{
opacity: 0;
width: 100%;
height: 70px;
position: absolute;
cursor: pointer;
z-index: 1;
}
.medialib{
margin: auto;
width: 100%;
height: 80%;
overflow: auto;
background: #f9f8f6;
max-width: 1200px;
}
.imagecard{
margin: 10px;
box-shadow:0 2px 5px rgba(22,23,26,.05);
display: inline-block;
vertical-align: top;
background: #fff;
}
sup{}
cite{}
abbr{}
hr{
background: #ddd;
height: 2px;
margin: 20px 0;
border: 0px;
}
.setupWrapper a, .setupWrapper a:link, .setupWrapper a:visited
{
text-decoration: none;
color: #444;
}
.setupWrapper a:focus, .setupWrapper a:hover, .setupWrapper a:active
{
color: #e0474c;
}
.mbfix{ margin-bottom: 0px!important; }
.slugbutton{
right: 20px;
height: 52px;
width: 150px;
}
.blox-body{
position: relative;
padding: 18px 20px
}
.blox-overlay{
position:absolute;
display: block;
z-index: 10;
box-sizing: border-box;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: #FFF;
background: rgba(255,255,255,0.8);
}
.blox-editor{
position: relative;
}
.blox-buttons{
position: absolute;
bottom: -15px;
text-align: right;
right: 25px;
width: 200px;
z-index: 99;
}
.blox-buttons button{
display: inline-block;
box-sizing: border-box;
margin: 2px;
padding: 3px 6px;
width: 80px;
text-align: center;
color: #444;
background: #f9f8f6;
border: 2px solid #fff;
border-radius: 2px;
font-size: 0.9em;
}
.blox-buttons button.edit{
background: #70c1b3;
color: #fff;
}
.blox-buttons button.edit:hover{
background: #4D978A;
}
.blox-buttons button.cancel:hover{
background: #e0474c;
color: #fff;
}
.blox-buttons button.edit:disabled, .blox-buttons button.cancel:disabled{
background: #eee;
color: #444;
border: 1px solid #eee;
}
.sideaction{
position: absolute;
top: 0px;
font-size: 0.8em;
right: -22px;
}
.sideaction button{
display: block;
font-weight: 300;
font-size: 0.9em;
background: #fff;
color: #fff;
width: 20px;
height: 20px;
line-height: 25px;
text-align: center;
padding: 0px;
margin: 1px;
border: 0px;
border-radius: 1px;
}
.blox-wrapper{
position: relative;
}
.editactive .sideaction button,
.blox-wrapper:hover button.add,
.blox-wrapper:hover button.delete{
background-color: #f9f8f6;
color: #666;
}
.sideaction:hover ~ .background-helper {
background-color: #f9f8f6;
}
.editactive .background-helper{
background-color: transparent!important;
}
.blox-wrapper button.add:hover{
background: #66b0a3;
color: #fff;
}
.blox-wrapper button.delete:hover{
background: #e0474c;
color: #fff;
}
.blox-editor textarea{
font-family: arial;
line-height: 1.5em;
font-size: 16px;
padding-left: 20px;
padding-right: 20px;
box-sizing: border-box;
min-height: 40px;
}
.blox-editor textarea:focus, .blox-editor input:focus{
box-shadow: none;
outline: none;
}
.blox-editor input.mdcontent.h2,.blox-editor input.mdcontent.h3,.blox-editor input.mdcontent.h4,.blox-editor input.mdcontent.h5,.blox-editor input.mdcontent.h6{
padding-left: 35px;
height: auto;
}
.blox-editor input.mdcontent.h2{
font-size: 1.6em;
font-weight: 700;
}
.blox-editor input.mdcontent.h3{
font-size: 1.3em;
font-weight: 700;
}
.blox-editor input.mdcontent.h4{
font-size: 1.1em;
font-weight: 700;
}
.blox-editor input.mdcontent.h5{
font-size: 1em;
font-weight: 700;
}
.blox-editor input.mdcontent.h6{
font-size: 1em;
font-weight: 300;
font-style: italic;
}
button.hdown{
position: absolute;
padding: 8px;
top: 1px;
bottom: 1px;
left: 0px;
font-size: 1em;
font-weight: 700;
border: 0px solid #fff;
border-right: 1px solid #fff;
}
button.hdown.headline{
color: #f9f8f6;
background: #66b0a3;
}
.blox-editor .contenttype {
position: absolute;
top: 15px;
left: -25px;
color: #666;
}
.visible{
display: block;
}
.hidden{
visibility: hidden;
}
.hidden .blox:hover{
background: #fff;
}
.component{
position: absolute;
width: 100%;
z-index:9;
}
*/
}

View File

@@ -690,6 +690,10 @@ video {
left: 3rem;
}
.right-0 {
right: 0px;
}
.-bottom-3 {
bottom: -0.75rem;
}
@@ -706,20 +710,16 @@ video {
left: 50%;
}
.right-0 {
right: 0px;
}
.bottom-3 {
bottom: 0.75rem;
}
.top-1 {
top: 0.25rem;
.top-12 {
top: 3rem;
}
.right-1 {
right: 0.25rem;
.top-10 {
top: 2.5rem;
}
.z-20 {
@@ -776,16 +776,16 @@ video {
margin-bottom: 0.25rem;
}
.my-4 {
margin-top: 1rem;
margin-bottom: 1rem;
}
.my-3 {
margin-top: 0.75rem;
margin-bottom: 0.75rem;
}
.my-4 {
margin-top: 1rem;
margin-bottom: 1rem;
}
.mt-6 {
margin-top: 1.5rem;
}
@@ -794,10 +794,6 @@ video {
margin-bottom: 0.25rem;
}
.mb-16 {
margin-bottom: 4rem;
}
.mb-2 {
margin-bottom: 0.5rem;
}
@@ -830,10 +826,18 @@ video {
margin-top: 0.5rem;
}
.mb-16 {
margin-bottom: 4rem;
}
.mr-3 {
margin-right: 0.75rem;
}
.mb-8 {
margin-bottom: 2rem;
}
.mt-5 {
margin-top: 1.25rem;
}
@@ -846,6 +850,22 @@ video {
margin-left: 0.5rem;
}
.ml-5 {
margin-left: 1.25rem;
}
.mr-5 {
margin-right: 1.25rem;
}
.mb-10 {
margin-bottom: 2.5rem;
}
.mt-8 {
margin-top: 2rem;
}
.mb-3 {
margin-bottom: 0.75rem;
}
@@ -858,10 +878,6 @@ video {
margin-top: 1rem;
}
.mt-8 {
margin-top: 2rem;
}
.mt-7 {
margin-top: 1.75rem;
}
@@ -870,10 +886,6 @@ video {
margin-right: 1rem;
}
.mb-8 {
margin-bottom: 2rem;
}
.block {
display: block;
}
@@ -934,6 +946,14 @@ video {
height: 2rem;
}
.h-full {
height: 100%;
}
.h-32 {
height: 8rem;
}
.h-0 {
height: 0px;
}
@@ -1030,6 +1050,14 @@ video {
width: 60%;
}
.w-3\/4 {
width: 75%;
}
.w-60 {
width: 15rem;
}
.w-0 {
width: 0px;
}
@@ -1042,10 +1070,6 @@ video {
width: 91.666667%;
}
.w-3\/4 {
width: 75%;
}
.max-w-md {
max-width: 28rem;
}
@@ -1058,6 +1082,10 @@ video {
max-width: 56rem;
}
.max-w-7xl {
max-width: 80rem;
}
.max-w-6xl {
max-width: 72rem;
}
@@ -1136,6 +1164,10 @@ video {
align-items: center;
}
.justify-start {
justify-content: flex-start;
}
.justify-end {
justify-content: flex-end;
}
@@ -1202,30 +1234,30 @@ video {
border-right-width: 8px;
}
.border-l {
border-left-width: 1px;
.border-b-2 {
border-bottom-width: 2px;
}
.border-t {
border-top-width: 1px;
}
.border-r {
border-right-width: 1px;
}
.border-r-2 {
border-right-width: 2px;
}
.border-b-2 {
border-bottom-width: 2px;
}
.border-b {
border-bottom-width: 1px;
}
.border-r {
border-right-width: 1px;
}
.border-l {
border-left-width: 1px;
}
.border-l-4 {
border-left-width: 4px;
}
@@ -1246,10 +1278,6 @@ video {
border-bottom-width: 4px;
}
.border-l-2 {
border-left-width: 2px;
}
.border-solid {
border-style: solid;
}
@@ -1263,11 +1291,6 @@ video {
border-color: rgb(209 213 219 / var(--tw-border-opacity));
}
.border-stone-100 {
--tw-border-opacity: 1;
border-color: rgb(245 245 244 / var(--tw-border-opacity));
}
.border-stone-200 {
--tw-border-opacity: 1;
border-color: rgb(231 229 228 / var(--tw-border-opacity));
@@ -1300,12 +1323,7 @@ video {
.border-stone-100 {
--tw-border-opacity: 1;
border-color: rgb(250 204 21 / var(--tw-border-opacity));
}
.border-rose-500 {
--tw-border-opacity: 1;
border-color: rgb(244 63 94 / var(--tw-border-opacity));
border-color: rgb(245 245 244 / var(--tw-border-opacity));
}
.border-rose-500 {
@@ -1397,11 +1415,6 @@ video {
background-color: rgb(20 184 166 / var(--tw-bg-opacity));
}
.bg-rose-500 {
--tw-bg-opacity: 1;
background-color: rgb(244 63 94 / var(--tw-bg-opacity));
}
.bg-red-100 {
--tw-bg-opacity: 1;
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
@@ -1417,6 +1430,15 @@ video {
background-color: rgb(87 83 78 / var(--tw-bg-opacity));
}
.bg-rose-500 {
--tw-bg-opacity: 1;
background-color: rgb(244 63 94 / var(--tw-bg-opacity));
}
.bg-black\/75 {
background-color: rgb(0 0 0 / 0.75);
}
.bg-yellow-500 {
--tw-bg-opacity: 1;
background-color: rgb(234 179 8 / var(--tw-bg-opacity));
@@ -1426,12 +1448,12 @@ video {
--tw-bg-opacity: 0.9;
}
.bg-clip-padding {
background-clip: padding-box;
.bg-cover {
background-size: cover;
}
.bg-center {
background-position: center;
.bg-clip-padding {
background-clip: padding-box;
}
.p-3 {
@@ -1483,6 +1505,16 @@ video {
padding-bottom: 0.75rem;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.px-12 {
padding-left: 3rem;
padding-right: 3rem;
@@ -1503,11 +1535,6 @@ video {
padding-right: 0.5rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.px-1 {
padding-left: 0.25rem;
padding-right: 0.25rem;
@@ -1523,14 +1550,19 @@ video {
padding-right: 2rem;
}
.px-5 {
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.px-16 {
padding-left: 4rem;
padding-right: 4rem;
}
.py-12 {
padding-top: 3rem;
padding-bottom: 3rem;
.py-16 {
padding-top: 4rem;
padding-bottom: 4rem;
}
.py-4 {
@@ -1538,21 +1570,6 @@ video {
padding-bottom: 1rem;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.px-20 {
padding-left: 5rem;
padding-right: 5rem;
}
.py-16 {
padding-top: 4rem;
padding-bottom: 4rem;
}
.pr-6 {
padding-right: 1.5rem;
}
@@ -1617,14 +1634,22 @@ video {
padding-top: 0.5rem;
}
.pt-4 {
padding-top: 1rem;
.pt-6 {
padding-top: 1.5rem;
}
.pb-3 {
padding-bottom: 0.75rem;
}
.pt-3 {
padding-top: 0.75rem;
}
.pt-4 {
padding-top: 1rem;
}
.text-left {
text-align: left;
}
@@ -1684,11 +1709,6 @@ video {
line-height: 1.75rem;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;
@@ -1812,11 +1832,6 @@ video {
color: rgb(244 63 94 / var(--tw-text-opacity));
}
.text-cyan-500 {
--tw-text-opacity: 1;
color: rgb(6 182 212 / var(--tw-text-opacity));
}
.underline {
-webkit-text-decoration-line: underline;
text-decoration-line: underline;
@@ -1890,6 +1905,12 @@ video {
transition-duration: 150ms;
}
.transition-opacity {
transition-property: opacity;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.\!transition {
transition-property: color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-text-decoration-color, -webkit-backdrop-filter !important;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter !important;
@@ -1910,8 +1931,9 @@ video {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.hover\:border-b-4:hover {
border-bottom-width: 4px;
.hover\:border-stone-700:hover {
--tw-border-opacity: 1;
border-color: rgb(68 64 60 / var(--tw-border-opacity));
}
.hover\:border-stone-200:hover {
@@ -1919,11 +1941,6 @@ video {
border-color: rgb(231 229 228 / var(--tw-border-opacity));
}
.hover\:border-stone-700:hover {
--tw-border-opacity: 1;
border-color: rgb(68 64 60 / var(--tw-border-opacity));
}
.hover\:border-rose-500:hover {
--tw-border-opacity: 1;
border-color: rgb(244 63 94 / var(--tw-border-opacity));
@@ -1974,6 +1991,11 @@ video {
background-color: rgb(214 211 209 / var(--tw-bg-opacity));
}
.hover\:bg-rose-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(190 18 60 / var(--tw-bg-opacity));
}
.hover\:bg-stone-50:hover {
--tw-bg-opacity: 1;
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
@@ -1994,21 +2016,6 @@ video {
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
}
.hover\:bg-rose-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(190 18 60 / var(--tw-bg-opacity));
}
.hover\:bg-cyan-500:hover {
--tw-bg-opacity: 1;
background-color: rgb(6 182 212 / var(--tw-bg-opacity));
}
.hover\:bg-stone-50:hover {
--tw-bg-opacity: 1;
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
}
.hover\:text-stone-50:hover {
--tw-text-opacity: 1;
color: rgb(250 250 249 / var(--tw-text-opacity));
@@ -2034,6 +2041,10 @@ video {
text-decoration-line: underline;
}
.hover\:opacity-100:hover {
opacity: 1;
}
.focus\:border-blue-600:focus {
--tw-border-opacity: 1;
border-color: rgb(37 99 235 / var(--tw-border-opacity));
@@ -2084,19 +2095,14 @@ video {
cursor: not-allowed;
}
.disabled\:bg-stone-50:disabled {
--tw-bg-opacity: 1;
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
}
.disabled\:bg-stone-200:disabled {
--tw-bg-opacity: 1;
background-color: rgb(231 229 228 / var(--tw-bg-opacity));
}
.disabled\:text-stone-900:disabled {
--tw-text-opacity: 1;
color: rgb(28 25 23 / var(--tw-text-opacity));
.disabled\:bg-stone-50:disabled {
--tw-bg-opacity: 1;
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
}
.disabled\:text-stone-800:disabled {
@@ -2104,6 +2110,11 @@ video {
color: rgb(41 37 36 / var(--tw-text-opacity));
}
.disabled\:text-stone-900:disabled {
--tw-text-opacity: 1;
color: rgb(28 25 23 / var(--tw-text-opacity));
}
.group:hover .group-hover\:visible {
visibility: visible;
}

View File

@@ -1294,6 +1294,9 @@ bloxeditor.component('inline-formats', {
bloxeditor.component('image-component', {
props: ['markdown', 'disabled', 'index'],
components: {
medialib: medialib
},
template: `<div class="dropbox pb-6">
<input type="hidden" ref="markdown" :value="markdown" :disabled="disabled" @input="updatemarkdown" />
<div class="flex">
@@ -1303,13 +1306,14 @@ bloxeditor.component('image-component', {
</div>
<button class="imageselect w-1/2 text-center p-6" @click.prevent="openmedialib()"><svg class="icon icon-image"><use xlink:href="#icon-image"></use></svg> select from medialib</button>
</div>
<!--
<transition name="fade-editor">
<div v-if="showmedialib" class="modalWindow">
<medialib parentcomponent="images"></medialib>
<Transition name="initial" appear>
<div v-if="showmedialib" class="fixed top-0 left-0 right-0 bottom-0 bg-stone-100 z-50">
<button class="w-full bg-stone-200 hover:bg-rose-500 hover:text-white p-2 transition duration-100" @click.prevent="showmedialib = false">{{ $filters.translate('close library') }}</button>
<medialib parentcomponent="images" @addFromMedialibEvent="addFromMedialibFunction"></medialib>
</div>
</transition>
-->
</Transition>
<div class="absolute top-3 -left-5 text-stone-400">
<svg class="icon icon-image">
<use xlink:href="#icon-image"></use>
@@ -1490,6 +1494,19 @@ bloxeditor.component('image-component', {
}
},
methods: {
closemedialib()
{
this.showmedialib = false;
},
addFromMedialibFunction(value)
{
this.imgfile = value;
this.imgpreview = data.urlinfo.baseurl + '/' + value;
this.showmedialib = false;
this.saveimage = false;
this.createmarkdown();
},
updatemarkdown(event)
{
this.$emit('updateMarkdownEvent', event.target.value);
@@ -1619,19 +1636,10 @@ bloxeditor.component('image-component', {
errors = this.$filters.translate('Maximum size of image caption is 140 characters');
}
}
/*
if(this.noresize === true)
{
imgmarkdown = imgmarkdown + '|noresize';
}
*/
if(errors)
{
console.info(errors);
// this.$parent.freezePage();
// publishController.errors.message = errors;
}
else
{
@@ -1692,7 +1700,7 @@ bloxeditor.component('image-component', {
{
this.showresize = false;
this.noresize = false;
this.showmedialib = true;
this.showmedialib = true;
},
isChecked(classname)
{
@@ -1806,6 +1814,9 @@ bloxeditor.component('image-component', {
bloxeditor.component('file-component', {
props: ['markdown', 'disabled', 'index'],
components: {
medialib: medialib
},
template: `<div class="dropbox">
<input type="hidden" ref="markdown" :value="markdown" :disabled="disabled" @input="updatemarkdown" />
<div class="flex">
@@ -1825,13 +1836,14 @@ bloxeditor.component('file-component', {
{{ $filters.translate('select from medialib') }}
</button>
</div>
<!--
<transition name="fade-editor">
<div v-if="showmedialib" class="modalWindow">
<medialib parentcomponent="files"></medialib>
<Transition name="initial" appear>
<div v-if="showmedialib" class="fixed top-0 left-0 right-0 bottom-0 bg-stone-100 z-50">
<button class="w-full bg-stone-200 hover:bg-rose-500 hover:text-white p-2 transition duration-100" @click.prevent="showmedialib = false">{{ $filters.translate('close library') }}</button>
<medialib parentcomponent="files" @addFromMedialibEvent="addFromMedialibFunction"></medialib>
</div>
</transition>
-->
</Transition>
<div class="absolute top-3 -left-5 text-stone-400">
<svg class="icon icon-paperclip">
<use xlink:href="#icon-paperclip"></use>
@@ -1906,6 +1918,18 @@ bloxeditor.component('file-component', {
this.getrestriction();
},
methods: {
addFromMedialibFunction(file)
{
this.showmedialib = false;
this.savefile = false;
this.fileurl = file.url;
this.filemeta = true;
this.filetitle = file.name;
this.fileextension = file.info.extension;
this.createmarkdown();
this.getrestriction(file.url);
},
openmedialib: function()
{
this.showmedialib = true;
@@ -2168,8 +2192,6 @@ bloxeditor.component('video-component', {
},
parseUrl(url)
{
alert("parse: " + url);
let urlparts = url.split('?');
let urlParams = new URLSearchParams(urlparts[1]);
@@ -2189,7 +2211,6 @@ bloxeditor.component('video-component', {
},
updatemarkdown(url)
{
alert("update: " + url);
this.edited = true;
this.url = url;
this.parseUrl(url);

View File

@@ -1,229 +1,320 @@
const medialib = {
props: ['parentcomponent'],
template: `<div class="medialib">
<div class="mt3">
<div class="w-30 dib v-top ph4 pv3">
<button class="f6 link br0 ba ph3 pv2 mb2 w-100 dim white bn bg-tm-red" @click.prevent="closemedialib()">{{ $filters.translate('close library') }}</button>
<div class="w-100 relative">
<div><input v-model="search" class="w-100 border-box pa2 mb3 br0 ba b--light-silver"><svg class="icon icon-search absolute top-1 right-1 pa1 gray"><use xlink:href="#icon-search"></use></svg></div>
</div>
<button @click.prevent="showImages()" class="link br0 ba ph3 pv2 mv2 mr1" :class="isImagesActive()">{{ $filters.translate('Images') }}</button>
<button @click.prevent="showFiles()" class="link br0 ba ph3 pv2 mv2 ml1" :class="isFilesActive()">{{ $filters.translate('Files') }}</button>
template: `<div class="max-w-7xl mx-auto p-8 overflow-auto h-full">
<div class="flex">
<div class="w-1/4">
<div class="w-full relative">
<div class="flex">
<input v-model="search" class="h-12 px-2 py-3 border border-stone-300 bg-stone-200">
<div class="w-1/4 h-12 px-2 py-3 center bg-stone-700 hover:bg-stone-900 text-white">
<svg class="icon icon-search">
<use xlink:href="#icon-search"></use>
</svg>
</div>
</div>
</div>
<div v-if="showimages">
<h3 class="border-b-2 border-stone-700 pt-6 pb-3">Images</h3>
<div class="my-3">
<button @click.prevent="showImages('pageImages')" :class="isActive('pageImages')" class="px-2 py-1 mr-2 hover:bg-stone-700 hover:text-stone-50 transition duration-100">{{ $filters.translate('this page') }}</button>
<button @click.prevent="showImages('allImages')" :class="isActive('allImages')" class="px-2 py-1 hover:bg-stone-700 hover:text-stone-50 transition duration-100">{{ $filters.translate('all pages') }}</button>
</div>
</div>
<div v-if="showfiles">
<h3 class="border-b-2 border-stone-700 pt-3 pb-3">Files</h3>
<div class="my-3">
<button @click.prevent="showFiles('pageFiles')" :class="isActive('pageFiles')" class="px-2 py-1 mr-2 hover:bg-stone-700 hover:text-stone-50 transition duration-100">{{ $filters.translate('this page') }}</button>
<button @click.prevent="showFiles('allFiles')" :class="isActive('allFiles')" class="px-2 py-1 mr-2 hover:bg-stone-700 hover:text-stone-50 transition duration-100">{{ $filters.translate('all pages') }}</button>
</div>
</div>
</div>
<div class="w-70 dib v-top center">
<div v-if="errors" class="w-95 mv3 white bg-tm-red tc f5 lh-copy pa3">{{errors}}</div>
<transition-group name="list">
<div class="w-29 ma3 dib v-top bg-white shadow-tm overflow-hidden" v-for="(image, index) in filteredImages" :key="image.name" v-if="showimages">
<a href="#" @click.prevent="selectImage(image)" :style="getBackgroundImage(image)" class="link mw5 dt hide-child cover bg-center">
<span class="white dtc v-mid center w-100 h-100 child bg-black-80 pa5"><svg class="icon icon-check baseline"><use xlink:href="#icon-check"></use></svg> click to select</span>
</a>
<div>
<div class="w-70 dib v-top pl3 pv3 f6 truncate"><strong>{{ image.name }}</strong></div>
<button @click.prevent="showImageDetails(image,index)" class="w-15 center dib link bn v-mid pv3 bg-white hover-bg-tm-green hover-white"><svg class="icon icon-info baseline"><use xlink:href="#icon-info"></use></svg></button>
<button @click.prevent="deleteImage(image,index)" class="w-15 center dib link bn v-mid pv3 bg-white hover-bg-tm-red hover-white"><svg class="icon icon-trash-o baseline"><use xlink:href="#icon-trash-o"></use></svg></button>
<div class="w-3/4">
<div v-if="errors" class="w-full mb-4 p-2 bg-rose-500 text-stone-50">{{errors}}</div>
<div class="flex flex-wrap justify-start px-5">
<TransitionGroup name="list">
<div v-for="(image, index) in filteredImages" :key="image.name" v-if="showimages" class="w-60 ml-5 mr-5 mb-10 shadow-md overflow-hidden bg-stone-50">
<a href="#" @click.prevent="selectImage(image)" :style="getBackgroundImage(image)" class="inline-block bg-cover">
<span class="transition-opacity duration-100 opacity-0 hover:opacity-100 flex items-center justify-center h-32 bg-black/75 text-white">
<svg class="icon icon-check">
<use xlink:href="#icon-check"></use>
</svg> click to select
</span>
</a>
<div class="flex">
<div class="w-3/4 truncate p-3">{{ image.name }}</div>
<div class="w-1/4 flex">
<button @click.prevent="showImageDetails(image,index)" class="w-1/2 bg-stone-50 hover:bg-teal-500 hover:text-white transition duration-100">
<svg class="icon icon-info">
<use xlink:href="#icon-info"></use>
</svg>
</button>
<button @click.prevent="deleteImage(image,index)" class="w-1/2 hover:bg-rose-500 hover:text-white transition duration-100">
<svg class="icon icon-trash-o">
<use xlink:href="#icon-trash-o"></use>
</svg>
</button>
</div>
</div>
</div>
</div>
</transition-group>
<div class="w-95 dib v-top bg-white mv3 relative" v-if="showimagedetails">
<div class="flex flex-wrap item-start">
<div class="w-50">
<div class="w6 h6 bg-black-40 dtc v-mid bg-chess">
<img :src="getImageUrl(imagedetaildata.src_live)" class="mw6 max-h6 dt center">
</div>
</div>
<div class="w-50 pa3 lh-copy f7 relative">
<div class="black-30 mt3 mb1">Name</div><div class="b">{{ imagedetaildata.name}}</div>
<div class="black-30 mt3 mb1">URL</div><div class="b">{{ getImageUrl(imagedetaildata.src_live)}}</div>
<div class="flex flex-wrap item-start">
<div class="w-50">
<div class="black-30 mt3 mb1">Size</div><div class="b">{{ getSize(imagedetaildata.bytes) }}</div>
</div>
<div class="w-50">
<div class="black-30 mt3 mb1">Dimensions</div><div class="b">{{ imagedetaildata.width }}x{{ imagedetaildata.height }} px</div>
</div>
<div class="w-50">
<div class="black-30 mt3 mb1">Type</div><div class="b">{{ imagedetaildata.type }}</div>
</div>
<div class="w-50">
<div class="black-30 mt3 mb1">Date</div><div class="b">{{ getDate(imagedetaildata.timestamp) }}</div>
</div>
</div>
<div class="absolute w-90 bottom-0 flex justify-between">
<button @click.prevent="selectImage(imagedetaildata)" class="w-50 mr1 pa2 link bn bg-light-gray hover-bg-tm-green hover-white"><svg class="icon icon-check baseline"><use xlink:href="#icon-check"></use></svg> select</button>
<button @click.prevent="deleteImage(imagedetaildata, detailindex)" class="w-50 ml1 pa2 link bn bg-light-gray hover-bg-tm-red hover-white"><svg class="icon icon-trash-o baseline"><use xlink:href="#icon-trash-o"></use></svg> delete</button>
</div>
</div>
</div>
<button class="f7 link br0 ba ph3 pv2 dim white bn bg-tm-red absolute top-0 right-0" @click.prevent="showImages()">close details</button>
<div class="pa3">
<h4>Image used in:</h4>
<ul class="ma0 pa0" v-if="imagedetaildata.pages && imagedetaildata.pages.length > 0">
<li class="list pa1" v-for="page in imagedetaildata.pages">
<a class="link tm-red" :href="adminurl + page">{{ page }}</a>
</li>
</ul>
<div v-else>No pages found.</div>'+
</div>
</TransitionGroup>
</div>
<transition-group name="list">
<div class="w-29 ma3 dib v-top bg-white shadow-tm overflow-hidden" v-for="(file, index) in filteredFiles" :key="file.name" v-if="showfiles">
<a href="#" @click.prevent="selectFile(file)" class="w-100 link cover bg-tm-green bg-center relative dt">
<div class="absolute w-100 tc white f1 top-3 h0 ttu" v-html="file.info.extension"></div>
<div class="link dt hide-child w-100">
<span class="white dtc v-top center w-100 h-100 child pt6 pb3 tc"><svg class="icon icon-check baseline"><use xlink:href="#icon-check"></use></svg> click to select</span>
</div>
</a>
<div>
<div class="w-70 dib v-top pl3 pv3 f6 truncate"><strong>{{ file.name }}</strong></div>
<button @click.prevent="showFileDetails(file,index)" class="w-15 center dib link bn v-mid pv3 bg-white hover-bg-tm-green hover-white"><svg class="icon icon-info baseline"><use xlink:href="#icon-info"></use></svg></button>
<button @click.prevent="deleteFile(file,index)" class="w-15 center dib link bn v-mid pv3 bg-white hover-bg-tm-red hover-white"><svg class="icon icon-trash-o baseline"><use xlink:href="#icon-trash-o"></use></svg></button>
</div>
</div>
</transition-group>
<div class="w-95 dib v-top bg-white mv3 relative" v-if="showfiledetails">
<div class="flex flex-wrap item-start">
<div class="w-50">
<div class="w6 h6 bg-black-40 dtc v-mid bg-tm-green tc">
<div class="w-100 dt center white f1 ttu">{{ filedetaildata.info.extension }}</div>
</div>
</div>
<div class="w-50 pa3 lh-copy f7 relative">
<div class="black-30 mt3 mb1">Name</div><div class="b">{{ filedetaildata.name}}</div>
<div class="black-30 mt3 mb1">URL</div><div class="b">{{ filedetaildata.url}}</div>
<div class="flex flex-wrap item-start">
<div class="w-50">
<div class="black-30 mt3 mb1">Size</div><div class="b">{{ getSize(filedetaildata.bytes) }}</div>
</div>
<div class="w-50">
<div class="black-30 mt3 mb1">Type</div><div class="b">{{ filedetaildata.info.extension }}</div>
</div>
<div class="w-50">
<div class="black-30 mt3 mb1">Date</div><div class="b">{{ getDate(filedetaildata.timestamp) }}</div>
<Transition name="fade">
<div class="px-5" v-if="showimagedetails">
<div class="flex flex-wrap item-start relative">
<div class="w-1/2 bg-stone-50">
<div class="w-80 h-80 table-cell align-middle bg-chess">
<img :src="getImageUrl(imagedetaildata.src_live)" class="max-w-xs max-h-80 table mx-auto">
</div>
</div>
<div class="absolute w-90 bottom-0 flex justify-between">
<button @click.prevent="selectFile(filedetaildata)" class="w-50 mr1 pa2 link bn bg-light-gray hover-bg-tm-green hover-white"><svg class="icon icon-check baseline"><use xlink:href="#icon-check"></use></svg> select</button>
<button @click.prevent="deleteFile(filedetaildata, detailindex)" class="w-50 ml1 pa2 link bn bg-light-gray hover-bg-tm-red hover-white"><svg class="icon icon-trash-o baseline"><use xlink:href="#icon-trash-o"></use></svg> delete</button>
<div class="w-1/2 bg-stone-50 p-4 text-xs">
<div class="text-stone-500 mt-3 mb-1">Name</div>
<div class="font-bold">{{ imagedetaildata.name}}</div>
<div class="text-stone-500 mt-3 mb-1">URL</div>
<div class="font-bold">{{ getImageUrl(imagedetaildata.src_live)}}</div>
<div class="flex flex-wrap item-start">
<div class="w-1/2">
<div class="text-stone-500 mt-3 mb-1">Size</div>
<div class="font-bold">{{ getSize(imagedetaildata.bytes) }}</div>
</div>
<div class="w-1/2">
<div class="text-stone-500 mt-3 mb-1">Dimensions</div>
<div class="font-bold">{{ imagedetaildata.width }}x{{ imagedetaildata.height }} px</div>
</div>
<div class="w-1/2">
<div class="text-stone-500 mt-3 mb-1">Type</div>
<div class="font-bold">{{ imagedetaildata.type }}</div>
</div>
<div class="w-1/2">
<div class="text-stone-500 mt-3 mb-1">Date</div>
<div class="font-bold">{{ getDate(imagedetaildata.timestamp) }}</div>
</div>
</div>
<div class="w-full flex justify-between mt-8">
<button @click.prevent="selectImage(imagedetaildata)" class="w-1/2 p-2 mr-2 bg-stone-200 hover:bg-teal-500 hover:text-white transition duration-100">
<svg class="icon icon-check">
<use xlink:href="#icon-check"></use>
</svg> select
</button>
<button @click.prevent="deleteImage(imagedetaildata, detailindex)" class="w-1/2 p-2 bg-stone-200 hover:bg-rose-500 hover:text-white transition duration-100">
<svg class="icon icon-trash-o baseline">
<use xlink:href="#icon-trash-o"></use>
</svg> delete
</button>
</div>
</div>
<button class="text-xs px-3 py-2 text-stone-50 bg-rose-500 hover:bg-rose-700 absolute top-0 right-0" @click.prevent="showImages()">close details</button>
</div>
</div>
<button class="f7 link br0 ba ph3 pv2 dim white bn bg-tm-red absolute top-0 right-0" @click.prevent="showFiles()">close details</button>
<div class="pa3">
<h4>File used in:</h4>
<ul class="ma0 pa0" v-if="filedetaildata.pages && filedetaildata.pages.length > 0">
<li class="list pa1" v-for="page in filedetaildata.pages">
<a class="link tm-red" :href="adminurl + page">{{ page }}</a>
</li>
</ul>
<div v-else>No pages found.</div>'+
</div>
</Transition>
<div class="flex flex-wrap justify-start px-5">
<TransitionGroup name="list">
<div v-for="(file, index) in filteredFiles" :key="file.name" v-if="showfiles" class="w-60 ml-5 mr-5 mb-10 shadow-md overflow-hidden bg-stone-50">
<a href="#" @click.prevent="selectFile(file)" class="w-full bg-teal-500 inline-block bg-cover relative">
<div class="absolute top-10 w-full text-white text-4xl uppercase text-center">{{ file.info.extension }}</div>
<span class="relative transition-opacity duration-100 opacity-0 hover:opacity-100 flex items-center justify-center h-32 bg-black/75 text-white">
<svg class="icon icon-check">
<use xlink:href="#icon-check"></use>
</svg> click to select
</span>
</a>
<div class="flex">
<div class="w-3/4 truncate p-3">{{ file.name }}</div>
<div class="w-1/4 flex">
<button @click.prevent="showFileDetails(file,index)" class="w-1/2 bg-stone-50 hover:bg-teal-500 hover:text-white transition duration-100">
<svg class="icon icon-info">
<use xlink:href="#icon-info"></use>
</svg>
</button>
<button @click.prevent="deleteFile(file,index)" class="w-1/2 hover:bg-rose-500 hover:text-white transition duration-100">
<svg class="icon icon-trash-o">
<use xlink:href="#icon-trash-o"></use>
</svg>
</button>
</div>
</div>
</div>
</TransitionGroup>
</div>
<Transition name="fade">
<div class="px-5" v-if="showfiledetails">
<div class="flex flex-wrap item-start relative">
<div class="w-1/2 bg-stone-50">
<div class="w-80 h-80 table-cell align-middle bg-teal-500">
<div class="w-full text-white text-4xl uppercase text-center">{{ filedetaildata.info.extension }}</div>
</div>
</div>
<div class="w-1/2 bg-stone-50 p-4 text-xs">
<div class="text-stone-500 mt-3 mb-1">Name</div>
<div class="font-bold">{{ filedetaildata.name}}</div>
<div class="text-stone-500 mt-3 mb-1">URL</div>
<div class="font-bold">{{ filedetaildata.url }}</div>
<div class="flex flex-wrap item-start">
<div class="w-1/2">
<div class="text-stone-500 mt-3 mb-1">Size</div>
<div class="font-bold">{{ getSize(filedetaildata.bytes) }}</div>
</div>
<div class="w-1/2">
<div class="text-stone-500 mt-3 mb-1">Type</div>
<div class="font-bold">{{ filedetaildata.info.extension }}</div>
</div>
<div class="w-1/2">
<div class="text-stone-500 mt-3 mb-1">Date</div>
<div class="font-bold">{{ getDate(filedetaildata.timestamp) }}</div>
</div>
</div>
<div class="w-full flex justify-between mt-8">
<button @click.prevent="selectFile(filedetaildata)" class="w-1/2 p-2 mr-2 bg-stone-200 hover:bg-teal-500 hover:text-white transition duration-100">
<svg class="icon icon-check">
<use xlink:href="#icon-check"></use>
</svg> select
</button>
<button @click.prevent="deleteFile(filedetaildata, detailindex)" class="w-1/2 p-2 bg-stone-200 hover:bg-rose-500 hover:text-white transition duration-100">
<svg class="icon icon-trash-o baseline">
<use xlink:href="#icon-trash-o"></use>
</svg> delete
</button>
</div>
</div>
<button class="text-xs px-3 py-2 text-stone-50 bg-rose-500 hover:bg-rose-700 absolute top-0 right-0" @click.prevent="showFiles('all')">close details</button>
</div>
</div>
</Transition>
</div>
</div>
</div>`,
data: function(){
return {
imagedata: false,
showimages: true,
imagedetaildata: false,
showimagedetails: false,
filedata: false,
showfiles: false,
filedetaildata: false,
showfiledetails: false,
detailindex: false,
load: false,
baseurl: myaxios.defaults.baseURL,
adminurl: false,
search: '',
errors: false,
active: false,
imagedata: false,
pagemedia: false,
showimages: true,
imagedetaildata: false,
showimagedetails: false,
filedata: false,
showfiles: false,
filedetaildata: false,
showfiledetails: false,
detailindex: false,
load: false,
adminurl: false,
baseurl: data.urlinfo.baseurl,
search: '',
errors: false,
}
},
mounted: function(){
this.errors = false;
var self = this;
tmaxios.get('/api/v1/pagemedia',{
params: {
'url': data.urlinfo.route,
'path': data.item.pathWithoutType
}
})
.then(function (response)
{
self.pagemedia = response.data.pagemedia;
})
.catch(function (error)
{
if(error.response)
{
self.errors = error.response.data.errors;
}
});
if(this.parentcomponent == 'files')
{
this.showFiles();
this.active = 'pageFiles';
}
if(this.parentcomponent == 'images')
{
this.showImages();
this.active = 'pageImages';
}
this.errors = false;
var self = this;
myaxios.get('/api/v1/medialib/images',{
params: {
'url': document.getElementById("path").value,
}
})
.then(function (response)
{
self.imagedata = response.data.images;
})
.catch(function (error)
{
if(error.response)
{
self.errors = error.response.data.errors;
}
});
},
computed: {
filteredImages() {
computed: {
filteredImages() {
var searchimages = this.search;
var filteredImages = {};
var images = this.imagedata;
if(images)
{
Object.keys(images).forEach(function(key) {
var searchindex = key + ' ' + images[key].name;
if(searchindex.toLowerCase().indexOf(searchimages.toLowerCase()) !== -1)
{
filteredImages[key] = images[key];
}
});
}
return filteredImages;
},
filteredFiles() {
var searchimages = this.search;
var filteredImages = {};
var images = this.imagedata;
var pagemedia = this.pagemedia;
var active = this.active;
var searchfiles = this.search;
var filteredFiles = {};
var files = this.filedata;
if(files)
{
Object.keys(files).forEach(function(key) {
var searchindex = key + ' ' + files[key].name;
if(searchindex.toLowerCase().indexOf(searchfiles.toLowerCase()) !== -1)
{
filteredFiles[key] = files[key];
}
});
}
return filteredFiles;
}
},
if(images)
{
if(active == 'pageImages')
{
Object.keys(images).forEach(function(key) {
var imagename = images[key].name;
if(pagemedia.indexOf(imagename) !== -1)
{
filteredImages[key] = images[key];
}
});
}
else
{
Object.keys(images).forEach(function(key) {
var searchindex = key + ' ' + images[key].name;
if(searchindex.toLowerCase().indexOf(searchimages.toLowerCase()) !== -1)
{
filteredImages[key] = images[key];
}
});
}
}
return filteredImages;
},
filteredFiles() {
var searchfiles = this.search;
var filteredFiles = {};
var files = this.filedata;
var pagemedia = this.pagemedia;
var active = this.active;
if(files)
{
if(active == 'pageFiles')
{
Object.keys(files).forEach(function(key) {
var filename = files[key].name;
if(pagemedia.indexOf(filename) !== -1)
{
filteredFiles[key] = files[key];
}
});
}
else
{
Object.keys(files).forEach(function(key) {
var searchindex = key + ' ' + files[key].name;
if(searchindex.toLowerCase().indexOf(searchfiles.toLowerCase()) !== -1)
{
filteredFiles[key] = files[key];
}
});
}
}
return filteredFiles;
},
},
methods: {
isImagesActive: function()
isActive(activestring)
{
if(this.showimages)
if(this.active == activestring)
{
return 'bg-tm-green white';
return 'bg-stone-700 text-stone-50';
}
return 'bg-light-gray black';
return 'bg-stone-200';
},
isFilesActive: function()
{
if(this.showfiles)
{
return 'bg-tm-green white';
}
return 'bg-light-gray black';
},
closemedialib: function()
{
this.$parent.showmedialib = false;
},
getBackgroundImage: function(image)
getBackgroundImage(image)
{
return 'background-image: url(' + this.baseurl + '/' + image.src_thumb + ');width:250px';
},
@@ -231,238 +322,184 @@ const medialib = {
{
return this.baseurl + '/' + relativeUrl;
},
showImages: function()
showImages(pagesOrAll)
{
this.errors = false;
this.showimages = true;
this.showfiles = false;
this.showimagedetails = false;
this.showfiledetails = false;
this.imagedetaildata = false;
this.detailindex = false;
},
showFiles: function()
{
this.showimages = false;
this.showfiles = true;
this.showimagedetails = false;
this.showfiledetails = false;
this.imagedetaildata = false;
this.filedetaildata = false;
this.detailindex = false;
this.active = pagesOrAll;
this.errors = false;
this.showimages = true;
this.showfiles = false;
this.showimagedetails = false;
this.showfiledetails = false;
this.imagedetaildata = false;
this.detailindex = false;
if(!this.imagedata)
{
this.errors = false;
if(!this.files)
var imageself = this;
tmaxios.get('/api/v1/images',{
params: {
'url': data.urlinfo.route,
'path': data.item.pathWithoutType
}
})
.then(function (response)
{
imageself.imagedata = response.data.images;
})
.catch(function (error)
{
if(error.response)
{
imageself.errors = error.response.data.errors;
}
});
}
},
showFiles(pagesOrAll)
{
this.active = pagesOrAll;
this.showimages = false;
this.showfiles = true;
this.showimagedetails = false;
this.showfiledetails = false;
this.imagedetaildata = false;
this.filedetaildata = false;
this.detailindex = false;
if(!this.filedata)
{
this.errors = false;
var filesself = this;
myaxios.get('/api/v1/medialib/files',{
params: {
'url': document.getElementById("path").value,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
}
tmaxios.get('/api/v1/files',{
params: {
'url': data.urlinfo.route,
}
})
.then(function (response)
{
filesself.filedata = response.data.files;
})
.catch(function (error)
{
if(error.response)
{
filesself.errors = error.response.data.errors;
}
});
.then(function (response)
{
filesself.filedata = response.data.files;
})
.catch(function (error)
{
if(error.response)
{
filesself.errors = error.response.data.errors;
}
});
}
},
showImageDetails: function(image,index)
showImageDetails(image,index)
{
this.errors = false;
this.showimages = false;
this.showfiles = false;
this.showimagedetails = true;
this.detailindex = index;
this.adminurl = myaxios.defaults.baseURL + '/tm/content/visual';
this.errors = false;
this.showimages = false;
this.showfiles = false;
this.showimagedetails = true;
this.showfiledetails = false;
this.detailindex = index;
this.adminurl = this.baseurl + '/tm/content/visual';
var imageself = this;
myaxios.get('/api/v1/image',{
params: {
'url': document.getElementById("path").value,
'name': image.name,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
}
})
.then(function (response)
{
imageself.imagedetaildata = response.data.image;
})
.catch(function (error)
{
if(error.response)
{
imageself.errors = error.response.data.errors;
}
});
},
showFileDetails: function(file,index)
{
this.errors = false;
this.showimages = false;
this.showfiles = false;
this.showimagedetails = false;
this.showfiledetails = true;
this.detailindex = index;
this.adminurl = myaxios.defaults.baseURL + '/tm/content/visual';
var fileself = this;
myaxios.get('/api/v1/file',{
params: {
'url': document.getElementById("path").value,
'name': file.name,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
}
})
.then(function (response)
{
fileself.filedetaildata = response.data.file;
})
.catch(function (error)
{
if(error.response)
{
fileself.errors = error.response.data.errors;
}
});
},
selectImage: function(image)
{
this.showImages();
if(this.parentcomponent == 'images')
{
var imgmarkdown = {target: {value: '![alt]('+ image.src_live +')' }};
this.$parent.imgfile = image.src_live;
this.$parent.imgpreview = this.baseurl + '/' + image.src_live;
this.$parent.imgmeta = true;
this.$parent.showmedialib = false;
this.$parent.createmarkdown(image.src_live);
/* this.$parent.updatemarkdown(imgmarkdown, image.src_live); */
}
if(this.parentcomponent == 'files')
{
var filemarkdown = {target: {value: '[' + image.name + '](' + image.src_live +'){.tm-download}' }};
this.$parent.filemeta = true;
this.$parent.filetitle = image.name;
this.$parent.showmedialib = false;
this.$parent.updatemarkdown(filemarkdown, image.src_live);
}
},
selectFile: function(file)
{
/* if image component is open */
if(this.parentcomponent == 'images')
{
var imgextensions = ['png','jpg', 'jpeg', 'gif', 'svg', 'webp'];
if(imgextensions.indexOf(file.info.extension) == -1)
{
this.errors = "you cannot insert a file into an image component";
return;
tmaxios.get('/api/v1/image',{
params: {
'url': data.urlinfo.route,
'name': image.name,
}
var imgmarkdown = {target: {value: '![alt]('+ file.url +')' }};
this.$parent.imgfile = file.url;
this.$parent.imgpreview = this.baseurl + '/' + file.url;
this.$parent.imgmeta = true;
this.$parent.showmedialib = false;
this.$parent.createmarkdown(file.url);
/* this.$parent.updatemarkdown(imgmarkdown, file.url);*/
}
if(this.parentcomponent == 'files')
})
.then(function (response)
{
var filemarkdown = {target: {value: '['+ file.name +']('+ file.url +'){.tm-download file-' + file.info.extension + '}' }};
imageself.imagedetaildata = response.data.image;
})
.catch(function (error)
{
if(error.response)
{
imageself.errors = error.response.data.errors;
}
});
},
showFileDetails(file,index)
{
this.errors = false;
this.showimages = false;
this.showfiles = false;
this.showimagedetails = false;
this.showfiledetails = true;
this.filedetaildata = file;
this.detailindex = index;
this.adminurl = this.baseurl + '/tm/content/visual';
},
selectImage(image)
{
this.$emit('addFromMedialibEvent', image.src_live);
},
selectFile(file)
{
let extension = file.info.extension.toUpperCase();
let size = this.getSize(file.bytes);
file.name = file.name + ' (' + extension + ', ' + size + ')';
this.$parent.showmedialib = false;
this.$parent.filemeta = true;
this.$parent.filetitle = file.info.filename + ' (' + file.info.extension.toUpperCase() + ')';
this.$parent.updatemarkdown(filemarkdown, file.url);
}
this.showFiles();
},
removeImage: function(index)
this.$emit('addFromMedialibEvent', file);
},
removeImage(index)
{
this.imagedata.splice(index,1);
},
removeFile: function(index)
removeFile(index)
{
this.filedata.splice(index,1);
},
deleteImage: function(image, index)
deleteImage(image, index)
{
imageself = this;
myaxios.delete('/api/v1/image',{
data: {
'url': document.getElementById("path").value,
'name': image.name,
'index': index,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
}
tmaxios.delete('/api/v1/image',{
data: {
'url': data.urlinfo.route,
'name': image.name,
'index': index,
}
})
.then(function (response)
{
.then(function (response)
{
imageself.showImages();
imageself.removeImage(index);
})
.catch(function (error)
{
if(error.response)
{
imageself.errors = error.response.data.errors;
}
});
imageself.removeImage(index);
})
.catch(function (error)
{
if(error.response)
{
imageself.errors = error.response.data.errors;
}
});
},
deleteFile: function(file, index)
deleteFile(file, index)
{
fileself = this;
myaxios.delete('/api/v1/file',{
data: {
'url': document.getElementById("path").value,
'name': file.name,
'index': index,
'csrf_name': document.getElementById("csrf_name").value,
'csrf_value': document.getElementById("csrf_value").value,
}
tmaxios.delete('/api/v1/file',{
data: {
'url': data.urlinfo.route,
'name': file.name,
'index': index,
}
})
.then(function (response)
{
fileself.showFiles();
fileself.removeFile(index);
})
.catch(function (error)
{
if(error.response)
{
fileself.errors = error.response.data.errors;
}
});
.then(function (response)
{
fileself.showFiles();
fileself.removeFile(index);
})
.catch(function (error)
{
if(error.response)
{
fileself.errors = error.response.data.errors;
}
});
},
getDate(timestamp)
{
@@ -480,12 +517,12 @@ const medialib = {
},
getSize(bytes)
{
var i = Math.floor(Math.log(bytes) / Math.log(1024)),
sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
var i = Math.floor(Math.log(bytes) / Math.log(1024)),
sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i];
return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i];
},
isChecked: function(classname)
isChecked(classname)
{
if(this.imgclass == classname)
{

View File

@@ -121,7 +121,7 @@ const app = Vue.createApp({
this.saved = false;
self = this;
tmaxios.post('/api/v1/metadata',{
tmaxios.post('/api/v1/meta',{
'url': data.urlinfo.route,
'tab': self.currentTab,
'data': self.formData[self.currentTab]

View File

@@ -60,6 +60,7 @@
<script src="{{ base_url() }}/system/typemill/author/js/vue.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vue-eventbus.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vue-shared.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vue-medialib.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/sortable.min.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vuedraggable.umd.min.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vue-contentnavi.js?v={{ settings.version }}"></script>

View File

@@ -65,6 +65,7 @@
<script src="{{ base_url() }}/system/typemill/author/js/vue.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vue-eventbus.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vue-shared.js?v={{ settings.version }}"></script>
<script src="{{ base_url() }}/system/typemill/author/js/vue-medialib.js?v={{ settings.version }}"></script>
<script>
/* kixote.mount('#kixote'); */
</script>

View File

@@ -40,18 +40,21 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) {
$group->delete('/user', ControllerApiSystemUsers::class . ':deleteUser')->setName('api.user.delete')->add(new ApiAuthorization($acl, 'account', 'delete')); # member
# IMAGES
$group->get('/pagemedia', ControllerApiImage::class . ':getPagemedia')->setName('api.image.pagemedia')->add(new ApiAuthorization($acl, 'mycontent', 'read'));
$group->get('/images', ControllerApiImage::class . ':getImages')->setName('api.image.images')->add(new ApiAuthorization($acl, 'mycontent', 'read'));
$group->post('/image', ControllerApiImage::class . ':saveImage')->setName('api.image.create')->add(new ApiAuthorization($acl, 'mycontent', 'create'));
$group->put('/image', ControllerApiImage::class . ':publishImage')->setName('api.image.publish')->add(new ApiAuthorization($acl, 'mycontent', 'create'));
# $group->get('/image', ControllerApiMedia::class . ':getImage')->setName('api.image.get');
# $group->delete('/image', ControllerApiMedia::class . ':deleteImage')->setName('api.image.delete');
$group->get('/image', ControllerApiImage::class . ':getImage')->setName('api.image.get')->add(new ApiAuthorization($acl, 'mycontent', 'read'));
$group->delete('/image', ControllerApiImage::class . ':deleteImage')->setName('api.image.delete')->add(new ApiAuthorization($acl, 'mycontent', 'delete'));
# FILES
$group->get('/filerestrictions', ControllerApiFile::class . ':getFileRestrictions')->setName('api.file.getrestrictions')->add(new ApiAuthorization($acl, 'mycontent', 'create'));
$group->post('/filerestrictions', ControllerApiFile::class . ':updateFileRestrictions')->setName('api.file.updaterestrictions')->add(new ApiAuthorization($acl, 'mycontent', 'create'));
$group->post('/file', ControllerApiFile::class . ':uploadFile')->setName('api.file.upload')->add(new ApiAuthorization($acl, 'mycontent', 'create'));
$group->put('/file', ControllerApiFile::class . ':publishFile')->setName('api.file.publish')->add(new ApiAuthorization($acl, 'mycontent', 'update'));
# $group->get('/api/v1/file', ControllerAuthorMediaApi::class . ':getFile')->setName('api.file.get')->add(new RestrictApiAccess($container['router']));
# $app->delete('/api/v1/file', ControllerAuthorMediaApi::class . ':deleteFile')->setName('api.file.delete')->add(new RestrictApiAccess($container['router']));
$group->get('/files', ControllerApiFile::class . ':getFiles')->setName('api.files.get')->add(new ApiAuthorization($acl, 'mycontent', 'read'));
$group->get('/file', ControllerApiFile::class . ':getFile')->setName('api.file.get')->add(new ApiAuthorization($acl, 'mycontent', 'read'));
$group->delete('/file', ControllerApiFile::class . ':deleteFile')->setName('api.file.delete')->add(new ApiAuthorization($acl, 'mycontent', 'read'));
# ARTICLE
$group->post('/article/sort', ControllerApiAuthorArticle::class . ':sortArticle')->setName('api.article.sort')->add(new ApiAuthorization($acl, 'content', 'create')); # author
@@ -75,9 +78,7 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) {
$group->get('/shortcodedata', ControllerApiAuthorShortcode::class . ':getShortcodeData')->setName('api.shortcodedata.get')->add(new ApiAuthorization($acl, 'mycontent', 'view'));
# META
$group->get('/metadata', ControllerApiAuthorMeta::class . ':getMetaData')->setName('api.metadata.get')->add(new ApiAuthorization($acl, 'mycontent', 'view'));
$group->get('/metadefinitions', ControllerApiAuthorMeta::class . ':getMetaDefinitions')->setName('api.definitions.get')->add(new ApiAuthorization($acl, 'mycontent', 'view'));
$group->post('/metadata', ControllerApiAuthorMeta::class . ':updateMetaData')->setName('api.metadata.update')->add(new ApiAuthorization($acl, 'mycontent', 'update'));
$group->get('/meta', ControllerApiAuthorMeta::class . ':getMeta')->setName('api.meta.get')->add(new ApiAuthorization($acl, 'mycontent', 'view'));
$group->post('/meta', ControllerApiAuthorMeta::class . ':updateMeta')->setName('api.metadata.update')->add(new ApiAuthorization($acl, 'mycontent', 'update'));
})->add(new ApiAuthentication());