diff --git a/content/00-welcome/05-todos.txt b/content/00-welcome/05-todos.txt index 9a4099c..9e30b0f 100644 --- a/content/00-welcome/05-todos.txt +++ b/content/00-welcome/05-todos.txt @@ -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 "] \ No newline at end of file +["# 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 "] \ No newline at end of file diff --git a/media/files/filerestrictions.yaml b/media/files/filerestrictions.yaml index 2b22b99..08e86cd 100644 --- a/media/files/filerestrictions.yaml +++ b/media/files/filerestrictions.yaml @@ -1 +1,2 @@ media/files/typemill-v2-navigation.gif: member +media/files/markdown.png: member diff --git a/media/tmp/screenshot-typemill-startpage.png b/media/tmp/screenshot-typemill-startpage.png new file mode 100644 index 0000000..3aef5e2 Binary files /dev/null and b/media/tmp/screenshot-typemill-startpage.png differ diff --git a/system/typemill/Controllers/Controller.php b/system/typemill/Controllers/Controller.php index 60d546e..3d85cbd 100644 --- a/system/typemill/Controllers/Controller.php +++ b/system/typemill/Controllers/Controller.php @@ -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'); } diff --git a/system/typemill/Controllers/ControllerApiAuthorMeta.php b/system/typemill/Controllers/ControllerApiAuthorMeta.php index ec4dd07..45f4460 100644 --- a/system/typemill/Controllers/ControllerApiAuthorMeta.php +++ b/system/typemill/Controllers/ControllerApiAuthorMeta.php @@ -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 '
'; - 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) { diff --git a/system/typemill/Controllers/ControllerApiFile.php b/system/typemill/Controllers/ControllerApiFile.php index 08ce8dc..7aa7294 100644 --- a/system/typemill/Controllers/ControllerApiFile.php +++ b/system/typemill/Controllers/ControllerApiFile.php @@ -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 diff --git a/system/typemill/Controllers/ControllerApiImage.php b/system/typemill/Controllers/ControllerApiImage.php index db0b12b..315d2ca 100644 --- a/system/typemill/Controllers/ControllerApiImage.php +++ b/system/typemill/Controllers/ControllerApiImage.php @@ -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'] = '{#' . $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); } - - } diff --git a/system/typemill/Models/ProcessFile.php b/system/typemill/Models/ProcessFile.php index a8d18e1..cb9f0eb 100644 --- a/system/typemill/Models/ProcessFile.php +++ b/system/typemill/Models/ProcessFile.php @@ -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; - } - } \ No newline at end of file diff --git a/system/typemill/Models/ProcessImage.php b/system/typemill/Models/ProcessImage.php index 638d52a..d57eac0 100644 --- a/system/typemill/Models/ProcessImage.php +++ b/system/typemill/Models/ProcessImage.php @@ -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() { diff --git a/system/typemill/Models/Storage.php b/system/typemill/Models/Storage.php index 8657acf..f61f710 100644 --- a/system/typemill/Models/Storage.php +++ b/system/typemill/Models/Storage.php @@ -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); + } + } + } diff --git a/system/typemill/author/css/custom.css b/system/typemill/author/css/custom.css index 1921edd..5e96bd6 100644 --- a/system/typemill/author/css/custom.css +++ b/system/typemill/author/css/custom.css @@ -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; -} -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/system/typemill/author/css/output.css b/system/typemill/author/css/output.css index 9e979fc..5737b3a 100644 --- a/system/typemill/author/css/output.css +++ b/system/typemill/author/css/output.css @@ -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; } diff --git a/system/typemill/author/js/vue-blox-components.js b/system/typemill/author/js/vue-blox-components.js index 1a56b2e..8791ecb 100644 --- a/system/typemill/author/js/vue-blox-components.js +++ b/system/typemill/author/js/vue-blox-components.js @@ -1294,6 +1294,9 @@ bloxeditor.component('inline-formats', { bloxeditor.component('image-component', { props: ['markdown', 'disabled', 'index'], + components: { + medialib: medialib + }, template: `- + +@@ -1303,13 +1306,14 @@ bloxeditor.component('image-component', {