Introduce the mediapicker field (#1125)

Added a new `mediapicker` form field which allows to select a media from any page
+ 
Provided an option to control how parent select field displays
This commit is contained in:
Flavio Copes
2017-06-02 16:28:52 +02:00
committed by GitHub
parent e6fa905824
commit f700837e92
38 changed files with 1432 additions and 63 deletions

View File

@@ -1,8 +1,11 @@
# v1.5.0-rc.3
## 05/xx/2017
1. [](#new)
* Added a new `mediapicker` form field which allows to select a media from any page [#1125](https://github.com/getgrav/grav-plugin-admin/pull/1125)
1. [](#improved)
* Various form styling improvements
* Provided an option to control how parent select field displays
# v1.5.0-rc.2
## 05/22/2017
@@ -14,7 +17,7 @@
* Use new unified `Utils::getPagePathFromToken()` method rather
1. [](#bugfix)
* Fix for undefined `include_metadata` error
# v1.5.0-rc.1
## 05/16/2017

View File

@@ -84,7 +84,11 @@ class AdminPlugin extends Plugin
{
if (!Grav::instance()['config']->get('plugins.admin-pro.enabled')) {
return [
'onPluginsInitialized' => [['setup', 100000], ['onPluginsInitialized', 1001]],
'onPluginsInitialized' => [
['setup', 100000],
['onPluginsInitialized', 1001]
],
'onPageInitialized' => ['onPageInitialized', 0],
'onShutdown' => ['onShutdown', 1000],
'onFormProcessed' => ['onFormProcessed', 0],
'onAdminDashboard' => ['onAdminDashboard', 0],
@@ -95,6 +99,17 @@ class AdminPlugin extends Plugin
return [];
}
public function onPageInitialized()
{
$page = $this->grav['page'];
$template = $this->grav['uri']->param('tmpl');
if ($template) {
$page->template($template);
}
}
/**
* If the admin path matches, initialize the Login plugin configuration and set the admin
* as active.
@@ -516,6 +531,10 @@ class AdminPlugin extends Plugin
$this->popularity->trackHit();
}
}
if ($this->grav['admin']->shouldLoadAdditionalFilesInBackground()) {
$this->grav['admin']->loadAdditionalFilesInBackground();
}
}
/**

View File

@@ -17,6 +17,8 @@ widgets:
dashboard-notifications: true
dashboard-feed: true
dashboard-pages: true
pages:
show_parents: both
session:
timeout: 1800
warnings:

View File

@@ -136,6 +136,16 @@ form:
frontend_tab: Separate tab (always the same)
_self: Current tab
pages.show_parents:
type: select
size: medium
label: Parent dropdown
highlight: 1
options:
both: Show slug and folder
folder: Show folder
fullpath: Show fullpath
google_fonts:
type: toggle
label: Use Google Fonts

View File

@@ -24,6 +24,8 @@ form:
.mime:
type: text
label: PLUGIN_ADMIN.MIME_TYPE
validate:
type: lower
.image:
type: textarea
yaml: true

View File

@@ -32,6 +32,8 @@ define('LOGIN_REDIRECT_COOKIE', 'grav-login-redirect');
class Admin
{
const MEDIA_PAGINATION_INTERVAL = 20;
/**
* @var Grav
*/
@@ -95,6 +97,16 @@ class Admin
*/
protected $permissions;
/**
* @var bool
*/
protected $load_additional_files_in_background = false;
/**
* @var bool
*/
protected $loading_additional_files_in_background = false;
/**
* Constructor.
*
@@ -532,6 +544,28 @@ class Admin
?: $this->grav['locator']->findResource("config://{$type}.yaml", true, true);
$file = CompiledYamlFile::instance($filename);
$obj->file($file);
$data[$type] = $obj;
} elseif (preg_match('|media-manager/|', $type)) {
$filename = base64_decode(preg_replace('|media-manager/|', '', $type));
$file = File::instance($filename);
$obj = new \StdClass();
$obj->title = $file->basename();
$obj->path = $file->filename();
$obj->file = $file;
$obj->page = $this->grav['pages']->get(dirname($obj->path));
$filename = pathinfo($obj->title)['filename'];
$filename = str_replace(['@3x', '@2x'], '', $filename);
if (isset(pathinfo($obj->title)['extension'])) {
$filename .= '.' . pathinfo($obj->title)['extension'];
}
if ($obj->page && isset($obj->page->media()[$filename])) {
$obj->metadata = new Data($obj->page->media()[$filename]->metadata());
}
$data[$type] = $obj;
} else {
throw new \RuntimeException("Data type '{$type}' doesn't exist!");
@@ -540,6 +574,17 @@ class Admin
return $data[$type];
}
protected function hasErrorMessage()
{
$msgs = $this->grav['messages']->all();
foreach ($msgs as $msg) {
if (isset($msg['scope']) && $msg['scope'] === 'error') {
return true;
}
}
return false;
}
/**
* Returns blueprints for the given type.
*
@@ -1373,4 +1418,260 @@ class Admin
return [$this->base, $this->location, $this->route];
}
/**
* Get the files list
*
* @todo allow pagination
* @return array
*/
public function files($filtered = true, $page_index = 0)
{
$param_type = $this->grav['uri']->param('type');
$param_date = $this->grav['uri']->param('date');
$param_page = $this->grav['uri']->param('page');
$param_page = str_replace('\\', '/', $param_page);
$files_cache_key = 'media-manager-files';
if ($param_type) {
$files_cache_key .= "-{$param_type}";
}
if ($param_date) {
$files_cache_key .= "-{$param_date}";
}
if ($param_page) {
$files_cache_key .= "-{$param_page}";
}
$page_files = null;
$cache_enabled = $this->grav['config']->get('plugins.admin.cache_enabled');
if (!$cache_enabled) {
$this->grav['cache']->setEnabled(true);
}
$page_files = $this->grav['cache']->fetch(md5($files_cache_key));
if (!$cache_enabled) {
$this->grav['cache']->setEnabled(false);
}
if (!$page_files) {
$page_files = [];
$pages = $this->grav['pages'];
if ($param_page) {
$page = $pages->dispatch($param_page);
$page_files = $this->getFiles('images', $page, $page_files, $filtered);
$page_files = $this->getFiles('videos', $page, $page_files, $filtered);
$page_files = $this->getFiles('audios', $page, $page_files, $filtered);
$page_files = $this->getFiles('files', $page, $page_files, $filtered);
} else {
$allPages = $pages->all();
if ($allPages) foreach ($allPages as $page) {
$page_files = $this->getFiles('images', $page, $page_files, $filtered);
$page_files = $this->getFiles('videos', $page, $page_files, $filtered);
$page_files = $this->getFiles('audios', $page, $page_files, $filtered);
$page_files = $this->getFiles('files', $page, $page_files, $filtered);
}
}
if (count($page_files) >= self::MEDIA_PAGINATION_INTERVAL) {
$this->shouldLoadAdditionalFilesInBackground(true);
}
if (!$cache_enabled) {
$this->grav['cache']->setEnabled(true);
}
$this->grav['cache']->save(md5($files_cache_key), $page_files, 600); //cache for 10 minutes
if (!$cache_enabled) {
$this->grav['cache']->setEnabled(false);
}
}
if (count($page_files) >= self::MEDIA_PAGINATION_INTERVAL) {
$page_files = array_slice($page_files, $page_index * self::MEDIA_PAGINATION_INTERVAL, self::MEDIA_PAGINATION_INTERVAL);
}
return $page_files;
}
public function shouldLoadAdditionalFilesInBackground($status = null)
{
if ($status) {
$this->load_additional_files_in_background = true;
}
return $this->load_additional_files_in_background;
}
public function loadAdditionalFilesInBackground($status = null)
{
if (!$this->loading_additional_files_in_background) {
$this->loading_additional_files_in_background = true;
$this->files(false, false);
$this->shouldLoadAdditionalFilesInBackground(false);
$this->loading_additional_files_in_background = false;
}
}
private function getFiles($type, $page, $page_files, $filtered)
{
$page_files = $this->getMediaOfType($type, $page, $page_files);
if ($filtered) {
$page_files = $this->filterByType($page_files);
$page_files = $this->filterByDate($page_files);
}
return $page_files;
}
/**
* Get all the media of a type ('images' | 'audios' | 'videos' | 'files')
*
* @param string $type
* @param Page\Page $page
* @param array $files
*
* @return array
*/
private function getMediaOfType($type, $page, $page_files) {
if ($page) {
// $path = $page->path();
$media = $page->media();
$mediaOfType = $media->$type();
foreach($mediaOfType as $title => $file) {
$page_files[] = [
'title' => $title,
'type' => $type,
'page_route' => $page->route(),
'file' => $file->higherQualityAlternative()
];
}
return $page_files;
} else {
return [];
}
}
/**
* Filter media by type
*
* @param array $filesFiltered
*
* @return array
*/
private function filterByType($filesFiltered)
{
$filter_type = $this->grav['uri']->param('type');
if (!$filter_type) {
return $filesFiltered;
}
$filesFiltered = array_filter($filesFiltered, function ($file) use ($filter_type) {
return $file['type'] == $filter_type;
});
return $filesFiltered;
}
/**
* Filter media by date
*
* @param array $filesFiltered
*
* @return array
*/
private function filterByDate($filesFiltered)
{
$filter_date = $this->grav['uri']->param('date');
if (!$filter_date) {
return $filesFiltered;
}
$year = substr($filter_date, 0, 4);
$month = substr($filter_date, 5, 2);
$filesFilteredByDate = [];
foreach($filesFiltered as $file) {
$filedate = $this->fileDate($file['file']);
$fileYear = $filedate->format('Y');
$fileMonth = $filedate->format('m');
if ($fileYear == $year && $fileMonth == $month) {
$filesFilteredByDate[] = $file;
}
}
return $filesFilteredByDate;
}
/**
* Return the DateTime object representation of a file modified date
*
* @param File $file
*
* @return DateTime
*/
private function fileDate($file) {
$datetime = new \DateTime();
$datetime->setTimestamp($file->toArray()['modified']);
return $datetime;
}
/**
* Get the files dates list to be used in the Media Files filter
*
* @return array
*/
public function filesDates()
{
$files = $this->files(false);
$dates = [];
foreach ($files as $file) {
$datetime = $this->fileDate($file['file']);
$year = $datetime->format('Y');
$month = $datetime->format('m');
if (!isset($dates[$year])) {
$dates[$year] = [];
}
if (!isset($dates[$year][$month])) {
$dates[$year][$month] = 1;
} else {
$dates[$year][$month]++;
}
}
return $dates;
}
/**
* Get the pages list to be used in the Media Files filter
*
* @return array
*/
public function pages()
{
$pages = $this->grav['pages']->all();
$pagesWithFiles = [];
if ($pages) foreach ($pages as $page) {
if (count($page->media()->all())) {
$pagesWithFiles[] = $page;
}
}
return $pagesWithFiles;
}
}

View File

@@ -924,7 +924,7 @@ class AdminBaseController
$fileParts = pathinfo($filename);
foreach (scandir($fileParts['dirname']) as $file) {
$regex_pattern = "/" . preg_quote($fileParts['filename']) . "@\d+x\." . $fileParts['extension'] . "$|" . preg_quote($fileParts['basename']) . ".meta.yaml$/";
$regex_pattern = "/" . preg_quote($fileParts['filename']) . "@\d+x\." . $fileParts['extension'] . "(?:\.meta\.yaml)?$|" . preg_quote($fileParts['basename']) . "\.meta\.yaml$/";
if (preg_match($regex_pattern, $file)) {
$path = $fileParts['dirname'] . '/' . $file;
@unlink($path);

View File

@@ -1520,8 +1520,21 @@ class AdminController extends AdminBaseController
$media_list = [];
$media = new Media($page->path());
$include_metadata = Grav::instance()['config']->get('system.media.auto_metadata_exif', false);
foreach ($media->all() as $name => $medium) {
$media_list[$name] = ['url' => $medium->display($medium->get('extension') === 'svg' ? 'source' : 'thumbnail')->cropZoom(400, 300)->url(), 'size' => $medium->get('size')];
$metadata = [];
if ($include_metadata) {
$img_metadata = $medium->metadata();
if ($img_metadata) {
$metadata = $img_metadata;
}
}
$media_list[$name] = ['url' => $medium->display($medium->get('extension') === 'svg' ? 'source' : 'thumbnail')->cropZoom(400, 300)->url(), 'size' => $medium->get('size'), 'metadata' => $metadata];
}
$this->admin->json_response = ['status' => 'success', 'results' => $media_list];
@@ -1631,13 +1644,28 @@ class AdminController extends AdminBaseController
}
// reinitialize media to trigger availability
$page->media();
$media = $page->media();
// Add metadata if needed
$include_metadata = Grav::instance()['config']->get('system.media.auto_metadata_exif', false);
$filename = $fileParts['basename'];
$filename = str_replace(['@3x', '@2x'], '', $filename);
$metadata = [];
if ($include_metadata && isset($media[$filename])) {
$img_metadata = $media[$filename]->metadata();
if ($img_metadata) {
$metadata = $img_metadata;
}
}
$this->grav->fireEvent('onAdminAfterAddMedia', new Event(['page' => $page]));
$this->admin->json_response = [
'status' => 'success',
'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_UPLOADED_SUCCESSFULLY')
'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_UPLOADED_SUCCESSFULLY'),
'metadata' => $metadata,
];
return true;
@@ -1697,7 +1725,7 @@ class AdminController extends AdminBaseController
// Remove Extra Files
foreach (scandir($page->path()) as $file) {
if (preg_match("/{$fileParts['filename']}@\d+x\.{$fileParts['extension']}$|{$filename}.meta.yaml$/", $file)) {
if (preg_match("/{$fileParts['filename']}@\d+x\.{$fileParts['extension']}(?:\.meta\.yaml)?$|{$filename}\.meta\.yaml$/", $file)) {
$result = unlink($page->path() . '/' . $file);
if (!$result) {

View File

@@ -6,6 +6,7 @@ import DateTimeField, { Instance as DateTimeFieldInstance } from './datetime';
import EditorField, { Instance as EditorFieldInstance } from './editor';
import ColorpickerField, { Instance as ColorpickerFieldInstance } from './colorpicker';
import FilesField, { Instance as FilesFieldInstance } from './files';
import MediapickerField, { Instance as MediapickerInstance } from './mediapicker';
import SelectUniqueField, { Instance as SelectUniqueInstance } from './selectunique';
export default {
@@ -44,6 +45,10 @@ export default {
SelectUniqueField: {
SelectUniqueField,
Instance: SelectUniqueInstance
},
MediapickerField: {
MediapickerField,
Instance: MediapickerInstance
}
};

View File

@@ -0,0 +1,49 @@
import $ from 'jquery';
import Scrollbar from '../../utils/scrollbar';
$(function() {
var modal = '';
var treescroll = new Scrollbar('.pages-list-container .mediapicker-scroll', { autoshow: true });
var thumbscroll = new Scrollbar('.thumbs-list-container .mediapicker-scroll', { autoshow: true });
// Thumb Resizer
$('.media-container .media-range').on('input change', function() {
var cards = $('.media-container div.card-item');
var width = $(this).val() + 'px';
cards.each(function() {
$(this).css('width', width);
});
});
$(document).on('opened', '.remodal', function() {
setTimeout(function() {
treescroll.update();
thumbscroll.update();
}, 10);
});
$('body').on('click', '[data-mediapicker-modal-trigger]', function() {
var modal_identifier = $(this).data('grav-mediapicker-unique-identifier');
var modal_element = $('body').find('[data-remodal-unique-identifier="' + modal_identifier + '"]');
modal = $.remodal.lookup[modal_element.data('remodal')];
modal.open();
// load all media
modal_element.find('.js__files').trigger('fillView');
});
/* handle media modal click actions */
$('body').on('click', '[data-remodal-mediapicker] .media-container.in-modal .admin-media-details a', (event) => {
event.preventDefault();
event.stopPropagation();
var val = $(event.target).parents('.js__media-element').data('file-url');
var string = val.replace(/ /g, '%20');
var modal_identifier = $(event.target).parents('[data-remodal-mediapicker]').data('remodal-unique-identifier');
$('body').find('[data-grav-mediapicker-unique-identifier="' + modal_identifier + '"] input').val(string);
modal.close();
});
});

View File

@@ -8,6 +8,7 @@ import Forms from './forms';
import Scrollbar, { Instance as contentScrollbar } from './utils/scrollbar';
import './plugins';
import './themes';
import { Filter as MediaFilter, Instance as MediaFilterInstance} from './media';
// bootstrap jQuery extensions
import 'bootstrap/js/transition';
@@ -65,5 +66,9 @@ export default {
Sidebar: {
Sidebar,
Instance: sidebar
},
MediaFilter: {
MediaFilter,
Instance: MediaFilterInstance
}
};

View File

@@ -0,0 +1,219 @@
import $ from 'jquery';
import { config, uri_params } from 'grav-config';
import request from '../utils/request';
export default class Filter {
constructor() {
this.URI = `${config.base_url_relative}/media-manager/`;
}
filter(name, value) {
let filtered = [];
let keys = Object.keys(uri_params);
if (!~keys.indexOf(name)) { keys.push(name); }
keys.forEach((key) => {
let filter = Filter.cleanValue(key === name ? value : uri_params[key]);
if (filter !== '*') {
filtered.push(`${key}${config.param_sep}${filter}`);
}
});
global.location = this.URI + filtered.join('/');
}
static cleanValue(value) {
return encodeURIComponent(value.replace('/', '\\'));
}
}
export let Instance = new Filter();
var isLoading = false;
var filters = {};
var global_index = 1;
var files_ended = false;
const MEDIA_PAGINATION_INTERVAL = 20;
/* handle changing file type / date filter */
$('body').on('change', '.thumbs-list-container select.filter', (event) => {
let target = $(event.currentTarget);
let filterName = target.data('name');
let filterValue = target.val();
if (filterValue) {
filters[filterName] = filterValue;
} else {
delete filters[filterName];
}
filterFiles();
});
/* initialize media uploader */
if ($('.thumbs-list-container .dropzone')[0]) {
$('.thumbs-list-container .dropzone')[0].dropzone.on('queuecomplete', function() {
let body = {};
if (filters.page) { body.page = filters.page; }
if (filters.date) { body.date = filters.date; }
if (filters.type) { body.type = filters.type; }
$('.dropzone')[0].dropzone.files.forEach(function(file) { file.previewElement.remove(); });
$('.dropzone').first().removeClass('dz-started');
request(config.base_url_relative + '/media-manager.json/task:clearMediaCache', { method: 'post', body }, () => {
filterFiles();
});
});
}
/* handle loading media */
var loadMedia = function loadMedia(filters, callback) {
var url = config.base_url_relative + '/media.json/tmpl:media-list-content/index:' + global_index;
if (filters.page) {
url += '/page:' + (filters.page).split('/').join('%5C');
}
if (filters.type && filters.type !== '*') {
url += '/type:' + filters.type;
}
if (filters.date && filters.date !== '*') {
url += '/date:' + filters.date;
}
if (!isLoading) {
isLoading = true;
$('.spinning-wheel').show();
$.get(url, function(content) {
$('.js__files').append(content);
$('.spinning-wheel').hide();
isLoading = false;
global_index++;
callback(content);
});
}
};
var cleanFilesList = function cleanFilesList() {
$('.js__files .card-item').remove();
};
var resetActiveStateInSidebar = function resetActiveStateInSidebar() {
$('.pages-list-container .row').removeClass('active'); // clear active state in sidebar
};
var showEmptyState = function showEmptyState() {
$('.thumbs-list-container').append('<p class="card-item empty-space">No media found</p>');
};
var filterFiles = function filterFiles() {
cleanFilesList();
global_index = 0;
files_ended = false;
$('.empty-space').remove();
loadMedia(filters, function(content) {
if (!$(content).length) {
showEmptyState();
} else {
if (!filters.page && (!filters.date || filters.date === '*') && (!filters.type || filters.type === '*')) {
$('.js__files').trigger('fillView');
}
}
});
};
/* handle changing page */
$('body').on('click', '.pages-list-container .js__page-link', (event) => {
var page = $(event.target).data('page');
filters['page'] = page;
$('.media-list-title .page-indicator').html(page); // set indication
$('.js__reset-pages-filter').removeClass('hidden'); // activate reset pages icon
resetActiveStateInSidebar();
$(event.target).parents('.row').addClass('active'); // set active state in sidebar
$('.js__file-uploader').removeClass('hidden');
// customize processing URL, as the page changes dynamically
if ($('.dropzone')[0]) {
$('.dropzone')[0].dropzone.on('processing', function(file) {
this.options.url = `${config.base_url_relative}/media-manager${page}.json/task${config.param_sep}addmedia`;
});
}
$('.js__button-clear-media-cache').addClass('hidden');
filterFiles();
disableInfiniteScrolling(); // only infinite scroll on main list, not inside single pages
});
/* handle clearing page filter */
$('body').on('click', '.js__reset-pages-filter', (event) => {
$('.media-list-title .page-indicator').html('All Pages'); // set indication
cleanFilesList();
resetActiveStateInSidebar();
$('.js__reset-pages-filter').addClass('hidden'); // remove reset pages icon
$('.js__file-uploader').addClass('hidden');
$('.js__button-clear-media-cache').removeClass('hidden');
delete filters['page'];
filterFiles();
});
/* handle infinite loading */
var enableInfiniteScrolling = function enableInfiniteScrolling() {
$('.spinning-wheel').hide();
var view = $('.mediapicker-scroll');
view.scroll(function() {
if (($(this).scrollTop() + $(this).innerHeight() + 100) >= $(this)[0].scrollHeight) {
fillView();
}
});
};
var loadNextBatch = function loadNextBatch(callback) {
if (files_ended) {
return;
}
loadMedia({}, function(content) {
if (!$(content).length || ((content.split('card-item').length - 1) < MEDIA_PAGINATION_INTERVAL)) {
files_ended = true;
} else {
if (callback) {
callback();
}
}
});
};
var fillView = function fillView() {
if (!$('.js__files').find('.card-item').last().offset()) {
setTimeout(function() {
// retry later
fillView();
}, 300);
return;
}
if ($('.js__files').find('.card-item').last().offset().top < $('.media-container').height()) {
loadNextBatch(function() {
fillView();
});
}
};
/* disable infinite loading */
var disableInfiniteScrolling = function disableInfiniteScrolling() {
$('.spinning-wheel').hide();
$('.content-wrapper').unbind('scroll');
};
$('.js__files').on('fillView', function(event) {
// the first batch got the max number of media files, try loading more
if (($('.js__files')[0].innerHTML.split('card-item').length - 1) === MEDIA_PAGINATION_INTERVAL) {
fillView();
enableInfiniteScrolling();
}
});

View File

@@ -1,3 +1,3 @@
@import url("//fonts.googleapis.com/css?family=Montserrat:400|Lato:300,400,700|Inconsolata:400,700");body,h5,h6,.badge,.note,.grav-mdeditor-preview,input,select,textarea,button,.selectize-input{font-family:"Lato","Helvetica","Tahoma","Geneva","Arial",sans-serif}h1,h2,h3,h4,#admin-menu li,.form-tabs>label,.label{font-family:"Montserrat","Helvetica","Tahoma","Geneva","Arial",sans-serif}code,kbd,pre,samp,body .CodeMirror{font-family:"Inconsolata","Monaco","Consolas","Lucida Console",monospace !important}
/*# sourceMappingURL=fonts.css.map */
/*# sourceMappingURL=../css-compiled/fonts.css.map */

View File

@@ -1 +1,11 @@
{"version":3,"file":"fonts.css","sources":["fonts.scss","configuration/fonts/_support.scss"],"sourcesContent":["$fonts-default: 'Lato' !default;\n$fonts-header: 'Montserrat' !default;\n$fonts-mono: 'Inconsolata' !default;\n\n$font-definitions: (\n Montserrat: '400',\n Lato: '300,400,700',\n Inconsolata: '400,700'\n);\n\n@import \"configuration/fonts/support\";\n\n\n\n\n","@function str-replace($string, $search, $replace: '') {\n $index: str-index($string, $search);\n\n @if $index {\n @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);\n }\n\n @return $string;\n}\n\n@function admin-font-faces($fonts) {\n $url: \"//fonts.googleapis.com/css?family=\";\n $nb: 0;\n\n @each $fontname, $weights in $fonts {\n\n @if $fontname == $fonts-default or\n $fontname == $fonts-header or\n $fontname == $fonts-mono {\n\n $nb: $nb + 1;\n $nb-word: 0;\n\n $fontname: str-replace(\"#{$fontname}\", \" \", \"+\");\n\n $url: $url + $fontname;\n\n @if $weights != null {\n $url: $url + \":\" + $weights;\n }\n\n @if $nb < 3 {\n $url: $url + \"|\";\n }\n }\n }\n\n @return $url;\n}\n\n@mixin body-fonts($font) {\n body, h5, h6,\n .badge, .note, .grav-mdeditor-preview,\n input, select, textarea, button, .selectize-input {\n font-family: \"#{$font}\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n }\n}\n\n@mixin header-fonts($font) {\n h1, h2, h3, h4,\n #admin-menu li, .form-tabs > label, .label {\n font-family: \"#{$font}\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n }\n}\n\n@mixin mono-fonts($font) {\n code, kbd, pre, samp,\n body .CodeMirror {\n font-family: \"#{$font}\", \"Monaco\", \"Consolas\", \"Lucida Console\", monospace !important;\n }\n}\n$font-url: admin-font-faces($font-definitions);\n\n@import url(\"#{$font-url}\");\n\n@include body-fonts($fonts-default);\n\n@include header-fonts($fonts-header);\n\n@include mono-fonts($fonts-mono);\n\n\n\n\n\n"],"names":[],"mappings":"AC+DA,OAAO,CAAC,4FAAI,CAtBR,AAAA,IAAI,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACZ,AAAA,MAAM,CAAE,AAAA,KAAK,CAAE,AAAA,sBAAsB,CACrC,AAAA,KAAK,CAAE,AAAA,MAAM,CAAE,AAAA,QAAQ,CAAE,AAAA,MAAM,CAAE,AAAA,gBAAgB,AAAC,CAC9C,WAAW,CAAE,MAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACd,AAAY,WAAD,CAAC,EAAE,CAAE,AAAa,UAAH,CAAG,KAAK,CAAE,AAAA,MAAM,AAAC,CACvC,WAAW,CAAE,YAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,IAAI,CAAE,AAAA,GAAG,CAAE,AAAA,GAAG,CAAE,AAAA,IAAI,CACpB,AAAK,IAAD,CAAC,WAAW,AAAC,CACb,WAAW,CAAE,aAAU,CAAE,QAAQ,CAAE,UAAU,CAAE,gBAAgB,CAAE,SAAS,CAAC,UAAU,CACxF"}
{
"version": 3,
"file": "../scss/fonts.css",
"sources": [
"../scss/fonts.scss",
"../hdr0",
"../scss/configuration/fonts/_support.scss"
],
"mappings": "AE+DA,OAAO,CAAC,4FAAI,CAtBR,AAAA,IAAI,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACZ,AAAA,MAAM,CAAE,AAAA,KAAK,CAAE,AAAA,sBAAsB,CACrC,AAAA,KAAK,CAAE,AAAA,MAAM,CAAE,AAAA,QAAQ,CAAE,AAAA,MAAM,CAAE,AAAA,gBAAgB,AAAC,CAC9C,WAAW,CAAE,MAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACd,AAAY,WAAD,CAAC,EAAE,CAAE,AAAa,UAAH,CAAG,KAAK,CAAE,AAAA,MAAM,AAAC,CACvC,WAAW,CAAE,YAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,IAAI,CAAE,AAAA,GAAG,CAAE,AAAA,GAAG,CAAE,AAAA,IAAI,CACpB,AAAK,IAAD,CAAC,WAAW,AAAC,CACb,WAAW,CAAE,aAAU,CAAE,QAAQ,CAAE,UAAU,CAAE,gBAAgB,CAAE,SAAS,CAAC,UAAU,CACxF",
"names": []
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,3 @@
body,h5,h6,.badge,.note,.grav-mdeditor-preview,input,select,textarea,button,.selectize-input,h1,h2,h3,h4,#admin-menu li,.form-tabs>label,.label{font-family:"Helvetica Neue", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif}code,kbd,pre,samp,body .CodeMirror{font-family:"Monaco", "Consolas", "Lucida Console", monospace}
/*# sourceMappingURL=simple-fonts.css.map */
/*# sourceMappingURL=../css-compiled/simple-fonts.css.map */

View File

@@ -1 +1,10 @@
{"version":3,"file":"simple-fonts.css","sources":["simple-fonts.scss"],"sourcesContent":["body, h5, h6,\n.badge, .note, .grav-mdeditor-preview,\ninput, select, textarea, button, .selectize-input,\nh1, h2, h3, h4,\n#admin-menu li, .form-tabs > label, .label {\n font-family: \"Helvetica Neue\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n}\ncode, kbd, pre, samp,\nbody .CodeMirror {\n font-family: \"Monaco\", \"Consolas\", \"Lucida Console\", monospace;\n}\n"],"names":[],"mappings":"AAAA,AAAA,IAAI,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACZ,AAAA,MAAM,CAAE,AAAA,KAAK,CAAE,AAAA,sBAAsB,CACrC,AAAA,KAAK,CAAE,AAAA,MAAM,CAAE,AAAA,QAAQ,CAAE,AAAA,MAAM,CAAE,AAAA,gBAAgB,CACjD,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACd,AAAY,WAAD,CAAC,EAAE,CAAE,AAAa,UAAH,CAAG,KAAK,CAAE,AAAA,MAAM,AAAC,CACvC,WAAW,CAAE,sEAAsE,CACtF,AACD,AAAA,IAAI,CAAE,AAAA,GAAG,CAAE,AAAA,GAAG,CAAE,AAAA,IAAI,CACpB,AAAK,IAAD,CAAC,WAAW,AAAC,CACb,WAAW,CAAE,iDAAiD,CACjE"}
{
"version": 3,
"file": "../scss/simple-fonts.css",
"sources": [
"../scss/simple-fonts.scss",
"../hdr0"
],
"mappings": "AAAA,AAAA,IAAI,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACZ,AAAA,MAAM,CAAE,AAAA,KAAK,CAAE,AAAA,sBAAsB,CACrC,AAAA,KAAK,CAAE,AAAA,MAAM,CAAE,AAAA,QAAQ,CAAE,AAAA,MAAM,CAAE,AAAA,gBAAgB,CACjD,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACd,AAAY,WAAD,CAAC,EAAE,CAAE,AAAa,UAAH,CAAG,KAAK,CAAE,AAAA,MAAM,AAAC,CACvC,WAAW,CAAE,sEAAuE,CACvF,AACD,AAAA,IAAI,CAAE,AAAA,GAAG,CAAE,AAAA,GAAG,CAAE,AAAA,IAAI,CACpB,AAAK,IAAD,CAAC,WAAW,AAAC,CACb,WAAW,CAAE,iDAAkD,CAClE",
"names": []
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -86,6 +86,9 @@
// Notifications
@import "template/notifications";
// Media
@import "template/media";
// Custom
@import "template/custom";

View File

@@ -638,3 +638,4 @@ textarea.frontmatter {
}
}

View File

@@ -0,0 +1,310 @@
/* Media */
$dark-text: #333;
.pages-list-container {
width: 30%;
flex: none!important;
padding: 0!important;
.pages-list {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.depth-0 .row {
padding-left: .5rem;
}
.depth-1 .row {
padding-left: 1rem;
}
.page-item {
.page-link {
cursor: pointer;
}
}
.row.active {
background-color: rgba(#00a6cf, 0.1);
}
}
}
.media-page-dropzone {
width: 100%;
margin-bottom: 20px;
}
.media-container {
&.size-2-3 {
flex: none!important;
width: 70%;
}
.filters {
padding-bottom: 30px;
width: 100%;
.filter-wrapper {
margin-left: 10px;
margin-right: 10px;
&:after {
margin-top: -25px;
border: 0px none;
content: "\f078";
font-family: "FontAwesome";
right: 12px;
top: 50%;
line-height: 0;
color: #9BA2A6;
pointer-events: none;
float: right;
padding-right: 10px;
}
}
.filter {
option {
padding: 8px;
}
}
}
.files {
padding-left: 30px;
padding-right: 30px;
}
.admin-media-title {
color: $dark-text;
margin: 0px;
font-size: .8rem;
line-height: 1.3;
}
.card-item {
width: 49%;
&.empty-space {
background: rgba(255,255,255,0.4);
border: 1px solid rgba(0,0,0,0.1) !important;
color: rgba(0,0,0,0.3);
max-width: none;
display: block;
text-align: center;
top: 20%;
height: 10rem !important;
width: 50% !important;
margin: 0 auto !important;
font-size: 2rem;
line-height: 8rem;
transform: translateY(-50%);
}
}
.media-details {
text-align: center;
img {
width: 50%;
}
h2 {
margin: 0;
font-size: 1.8rem;
}
h5 {
color: #9BA2A6;
font-size: 1.1rem;
margin: 0;
}
}
h1 {
padding: 0 2rem 0.5rem!important;
}
.admin-form-wrapper {
width: 100%;
padding-bottom: 20px;
}
}
.spinning-wheel {
text-align: center;
}
/* Media in-page modal */
.media-container.in-modal {
h1 {
font-size: 30px!important;
margin-top: 0px;
}
a {
color: #333;
}
.pages-list li {
list-style-type: none;
margin-left: 0;
}
}
//Mediapicker field
.remodal.remodal-mediapicker {
max-width: 70vw;
padding: 0;
}
.media-container {
height: 70vh;
.grid {
justify-content: flex-start;
}
&.in-modal {
.gm-scrollbar {
background: rgba(255,255,255,0.2);
.thumb {
background-color: rgba(0,0,0,0.2) !important;
&:hover, &.active {
background-color: rgba(0,0,0,0.3) !important;
}
}
}
}
h5 {
border-bottom: 1px solid #ddd;
padding: 0.5rem 15px;
margin: 0;
height: 55px;
}
.filter-wrapper {
float: right;
margin-right: 5px;
margin-top: 10px;
}
.filter-date {
width: 150px;
}
.filter-type {
width: 100px;
margin-right: 15px;
}
.pages-list-container {
position: relative;
.pages-list {
border-top: 0;
padding-right: 10px;
font-size: 90%;
.row {
transition: none !important;
line-height: 2rem;
}
@for $i from 1 to 10 {
.depth-#{$i} .row {
padding-left: 1rem * ($i + 1);
}
}
}
.mediapicker-scroll {
position: absolute;
top: 55px;
bottom: 3px;
height: inherit;
}
}
.thumbs-list-container {
position: relative;
.media-range {
position: absolute;
right: 15px;
bottom: 5px;
}
.mediapicker-scroll {
position: absolute;
top: 55px;
bottom: 30px;
height: inherit;
}
.gm-scrollbar.-horizontal {
display: none;
}
}
#admin-media {
padding: 15px;
height: 100%;
align-items: flex-start;
align-content: flex-start;
.card-item {
width: 100px;
padding: 2px;
margin: 0 5px 5px 0;
border: 0;
img {
display: block;
}
}
.admin-media-details {
position: relative;
.admin-media-title {
position: absolute;
width: 100%;
bottom: 0;
background: rgba(0,0,0,0.3);
color: #fff;
font-size: 10px;
overflow: hidden;
line-height: 2;
text-indent: 2px;
white-space: nowrap;
}
}
}
}

View File

@@ -3,7 +3,7 @@ $ir_slider_height: 20px;
$ir_counter_width: 60px;
$ir_margin: 10px 0;
input[type=range] {
input[type=range].rangefield {
display: inline-block;
vertical-align: middle;
-webkit-appearance: none;

View File

@@ -0,0 +1,20 @@
{% extends "forms/fields/text/text.html.twig" %}
{% set originalValue = value %}
{% set value = (value is null ? field.default : value) %}
{% set unique_identifier = random_string() %}
{% block global_attributes %}
{{ parent() }}
data-mediapicker-modal-trigger
data-grav-mediapicker-unique-identifier="{{ unique_identifier }}"
{% endblock %}
{% block contents %}
{{ parent() }}
<div class="remodal remodal-mediapicker" data-remodal-mediapicker data-remodal-unique-identifier="{{ unique_identifier }}" data-remodal-options="hashTracking: false">
{% include 'partials/media-list-wrapper.html.twig' with { is_modal: true } %}
</div>
{% endblock %}

View File

@@ -2,7 +2,17 @@
{% block input %}
{% set last_page_route = admin.page.getLastPageRoute %}
{% set defaults = {show_root:true, show_all:true, show_slug:true, default:last_page_route} %}
{% set show_slug_val = true %}
{% set show_fullpath_val = false %}
{% set show_parents = config.get('plugins.admin.pages.show_parents') %}
{% if show_parents == 'folder' %}
{% set show_slug_val = false %}
{% elseif show_parents == 'fullpath' %}
{% set show_fullpath_val = true %}
{% endif %}
{% set defaults = {show_root:true, show_all:true, show_slug:show_slug_val, show_fullpath:show_fullpath_val, default:last_page_route} %}
{% set field = field|merge(defaults) %}
{{ parent() }}
{% endblock %}

View File

@@ -0,0 +1,18 @@
{% set index = uri.param('index') %}
{% for file in admin.files(true, index) %}
{% set the_file = file.file %}
{% if the_file.display() is not empty %}
<div class="card-item">
<div class="admin-media-details">
<a href="{% if is_modal %}#{% else %}{{ base_url_relative }}/media-manager/{{base64_encode(the_file.filepath)}}{% endif %}" class="js__media-element" data-file-url="{{ file.page_route ~ '/' ~ file.title }}">
{% set thumbnail = the_file.display(the_file.extension == 'svg' ? 'source' : 'thumbnail') %}
{% if thumbnail %}
<img src="{{thumbnail.cropZoom(250, 250).url}}" />
<h4 class="admin-media-title">{{ file.title }}</h4>
{% endif %}
</a>
</div>
</div>
{% endif %}
{% endfor %}

View File

@@ -0,0 +1,16 @@
<div class="grid media-container {% if is_modal %}in-modal{% endif %}">
{% set default_site_lang = grav.config.system.languages|first|first %}
{% include 'partials/media-list-wrapper__sidebar.html.twig' %}
<div class="thumbs-list-container block size-3-4">
{% include 'partials/media-list-wrapper__list__filters.html.twig' %}
<h5 class="media-list-title"><span class="page-indicator">All Pages</span> <a class="hidden js__reset-pages-filter" href="#"><i class="fa fa-fw fa-times"></i></a></h5>
<div class="mediapicker-scroll">
{% include 'partials/media-list-wrapper__list.html.twig' with { is_modal: is_modal } %}
</div>
<input name="thumb-size" class="media-range" type="range" min="50" max="250" value="100" />
</div>
</div>

View File

@@ -0,0 +1,23 @@
<div id="admin-media" class="files js__files card-row grid fixed-blocks pure-g">
{% if not is_modal %}
{% include 'partials/media-list-wrapper__list__dropzone.html.twig' ignore missing %}
{% endif %}
{% if admin.files is empty %}
<div class="empty-state">
{% if (uri.param('type') or uri.param('date')) %}
<h2>Filtering by {{ uri.param('type') }} {{ uri.param('date') }}</h2>
{% endif %}
<h2>No media files found</h2>
<p>You need to add media to a page in order to display it here.</p>
</div>
{% else %}
{% include 'media-list-content.html.twig' with { is_modal: is_modal } %} {# not a partial as used by AJAX #}
{% endif %}
{{ nonce_field('admin-form', 'admin-nonce')|raw }}
</div>
{% include 'partials/spinning-wheel.html.twig' %}

View File

@@ -0,0 +1,28 @@
<div>
<div class="filter-wrapper filter-type">
<select class="filter js__filter" data-name="type" data-grav-selectize>
<option value="*" {% if not grav.uri.param('type') %}selected{% endif %}>All Files</option>
<option value="images" {% if grav.uri.param('type') == 'images' %}selected{% endif %}>Images</option>
<option value="videos"{% if grav.uri.param('type') == 'videos' %}selected{% endif %}>Videos</option>
<option value="audios"{% if grav.uri.param('type') == 'audios' %}selected{% endif %}>Audio</option>
<option value="files"{% if grav.uri.param('type') == 'files' %}selected{% endif %}>Files</option>
</select>
</div>
<div class="filter-wrapper filter-date">
<select class="filter js__filter" data-name="date" data-grav-selectize>
<option value="*">All Dates</option>
{% set theDate = date() %}
{% for year, months in admin.filesDates %}
<optgroup label="{{year}}">
{% for month, number in months %}
<option value="{{year}}-{{month}}" {% if grav.uri.param('date') == year~'-'~month %}selected{% endif %}>
{{theDate.setDate(theDate.format('Y'), month, theDate.format('d'))|date('M')}}
({{number}})</option>
{% endfor %}
</optgroup>
{% endfor %}
</select>
</div>
</div>

View File

@@ -0,0 +1,46 @@
<div class="pages-list-container clear block size-1-4">
<h5>{{ "PLUGIN_ADMIN.PAGES"|tu|e }}</h5>
<div class="mediapicker-scroll">
<ul class="pages-list depth-0">
{{ _self.loop(pages, 0, _context) }}
</ul>
</div>
</div>
{% macro loop(page, depth, twig_vars) %}
{% set separator = twig_vars['config'].system.param_sep %}
{% set base_url = twig_vars['base_url_relative'] %}
{% set base_url_simple = twig_vars['base_url_simple'] %}
{% set admin_route = twig_vars['admin_route'] %}
{% set admin_lang = twig_vars['admin_lang'] %}
{% set warn = twig_vars['warn'] %}
{% for p in page.children() %}
{% set page_route = p.rawRoute|trim('/') %}
{% if p.language and p.language != admin_lang %}
{% set page_url = base_url_simple ~ '/' ~ p.language ~ '/' ~ admin_route ~ '/pages/' ~ page_route %}
{% else %}
{% set page_url = base_url ~ '/pages/' ~ page_route %}
{% endif %}
<li class="page-item" data-nav-id="{{ p.route }}">
<div class="row">
<span {{ p.children(0).count > 0 ? 'data-toggle="children"' : ''}} class="hint--bottom">
<i class="page-icon fa fa-fw fa-circle-o {{ p.children(0).count > 0 ? 'children-closed' : ''}} {{ p.modular ? 'modular' : (not p.routable ? 'not-routable' : (not p.visible ? 'not-visible' : (not p.page ? 'folder' : ''))) }}"></i>
</span>
<span data-hint="{{ p.header.routes.default ?: p.route }}" class="hint--bottom">
<a data-page="{{ p.route }}" class="js__page-link page-link" href="#">{{ p.title }}</a>
</span>
<span class="page-home">{{ p.home ? '<i class="fa fa-home"></i>' }}</span>
</div>
{% if p.children().count > 0 %}
<ul class="depth-{{ depth + 1 }}" style="display:none;">
{{ _self.loop(p, depth + 1, twig_vars) }}
</ul>
{% endif %}
</li>
{% endfor %}
{% endmacro %}

View File

@@ -0,0 +1,3 @@
<div class="spinning-wheel" style="display: none">
{{ "PLUGIN_ADMIN.LOADING"|tu|e }} <i class="fa fa-spinner fa-spin"></i>
</div>