mirror of
https://github.com/getgrav/grav-plugin-admin.git
synced 2026-07-07 16:12:09 +02:00
Merge branch 'release/1.10.0'
This commit is contained in:
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,7 +1,7 @@
|
||||
# Linguist Normalizer
|
||||
*.yaml linguistic-language=PHP
|
||||
*.twig linguistic-language=PHP
|
||||
**/gulpfile.js linguist-vendored
|
||||
**/gulpfile.babel.js linguist-vendored
|
||||
**/webpack.conf.js linguist-vendored
|
||||
**/js/*.js linguist-vendored
|
||||
**/js/*.json linguist-vendored
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@ tests/_output/*
|
||||
tests/_support/_generated/*
|
||||
tests/cache/*
|
||||
tests/error.log
|
||||
/crowdin.yaml
|
||||
|
||||
351
CHANGELOG.md
351
CHANGELOG.md
@@ -1,3 +1,354 @@
|
||||
# v1.10.0
|
||||
## 01/19/2021
|
||||
|
||||
1. [](#new)
|
||||
* Requires **Grav 1.7 and PHP 7.3.6**
|
||||
* Read about this release in the [Grav 1.7 Released](https://getgrav.org/blog/grav-1.7-released) blog post
|
||||
* Read the full list of changes in the [Changelog on GitHub](https://github.com/getgrav/grav-plugin-admin/blob/1.10.0/CHANGELOG.md)
|
||||
* Please read [Grav 1.7 Upgrade Guide](https://learn.getgrav.org/17/advanced/grav-development/grav-17-upgrade-guide) before upgrading!
|
||||
1. [](#improved)
|
||||
* Various notifications improvements
|
||||
1. [](#bugfix)
|
||||
* Fixed missed highlight on the selected page in Parents field
|
||||
* Fixed notifications that would not be remembered as hidden
|
||||
* Fixed taxonomy field not listing existing options in Flex Pages
|
||||
* Fixed taxonomy field not working outside pages
|
||||
* Fixed fatal error when moving a page using the old implementation [#2019](https://github.com/getgrav/grav-plugin-admin/issues/2019)
|
||||
* Fixed evaluating default value in `hidden` field (thanks @NicoHood)
|
||||
|
||||
# v1.10.0-rc.20
|
||||
## 12/14/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Cookies now explicitly set `SameSite` to `Lax` unless otherwise specified [#1998](https://github.com/getgrav/grav-plugin-admin/issues/1998)
|
||||
* Exposed **Cookies** class (`Grav.default.Utils.Cookies`) for developers that need it in Admin.
|
||||
1. [](#bugfix)
|
||||
* Fixed Plugins references in Themes details page.
|
||||
* Fixed issue preventing purchase of Themes within Admin and redirecting instead.
|
||||
* Regression: Values inside Fieldset do not display [#1995](https://github.com/getgrav/grav-plugin-admin/issues/1995)
|
||||
|
||||
# v1.10.0-rc.19
|
||||
## 12/02/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Just keeping sync with Grav rc.19
|
||||
|
||||
# v1.10.0-rc.18
|
||||
## 12/02/2020
|
||||
|
||||
1. [](#new)
|
||||
* Retired "Secure Delete" and "Warn on page delete". You are now always warned and asked to confirm a deletion.
|
||||
1. [](#improved)
|
||||
* Auto-link a plugin/theme license in details if it starts with `http`
|
||||
* Allow to fallback to `docs:` instead of `readme:`
|
||||
* Forward a `sid` to GPM when downloading a premium package
|
||||
* Better support for array field key/value when either key or value is stored empty [#1972](https://github.com/getgrav/grav-plugin-admin/issues/1972)
|
||||
* Remember the open state of the sidebar [#1973](https://github.com/getgrav/grav-plugin-admin/issues/1973)
|
||||
* Upgraded node dependencies to latest version. Improved speed of JS compilation.
|
||||
* Added modal to confirm updating Grav as well as cool down counter before enabling Update button [#1257](https://github.com/getgrav/grav-plugin-admin/issues/1257)
|
||||
* Better handling of offline/intranet mode when the repository index is missing. Faster admin. [#1916](https://github.com/getgrav/grav-plugin-admin/issues/1916)
|
||||
* Statistics is now Page View Statistics [#1885](https://github.com/getgrav/grav-plugin-admin/issues/1885)
|
||||
* It is now possible to use regex as values for "Hide page types in Admin" and "Hide modular page types in Admin" settings [#1828](https://github.com/getgrav/grav-plugin-admin/issues/1828)
|
||||
* Default to `disabled` state for all cron-jobs
|
||||
1. [](#bugfix)
|
||||
* Fixed Safari issue with new ACL picker field [#1955](https://github.com/getgrav/grav-plugin-admin/issues/1955)
|
||||
* Stop propagation of ACL add button in ACL picker [flex-objects#83](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/83)
|
||||
* Fixed missing special groups `authors` and `defaults` for pages
|
||||
* Fixed Page Move action and selection highlight in Parents selector modal [flex-objects#80](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/80)
|
||||
* Fixed folder auto-naming in Add Module [#1937](https://github.com/getgrav/grav-plugin-admin/issues/1937)
|
||||
* Fixed remodal issue triggering close when selecting a dropdown item ending outside of scope [#1682](https://github.com/getgrav/grav-plugin-admin/issues/1682)
|
||||
* Reworked how collapsed lists work so the tooltip is not cut off [#1928](https://github.com/getgrav/grav-plugin-admin/issues/1928)
|
||||
* Fixed KeepAlive issue where too large of a session value would fire the keep alive immediately [#1860](https://github.com/getgrav/grav-plugin-admin/issues/1860)
|
||||
* Fixed stringable objects breaking the inputs
|
||||
* Fixed filepicker, pagemediaselect fields with `multiple: true` and `array: true` [#1580](https://github.com/getgrav/grav-plugin-admin/issues/1580)
|
||||
|
||||
# v1.10.0-rc.17
|
||||
## 10/07/2020
|
||||
|
||||
1. [](#new)
|
||||
* Support premium themes
|
||||
1. [](#improved)
|
||||
* Improved some error messages for better readability
|
||||
* Strip tags from browser title
|
||||
1. [](#bugfix)
|
||||
* More multi-site routing fixes
|
||||
* Fixed issue that would force a page reload when failing to install/update a plugin or theme.
|
||||
* Fixed proxy/browser caching issues in admin pages
|
||||
|
||||
# v1.10.0-rc.16
|
||||
## 09/01/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Made all the `onAdmin*` CRUD events to pass `object` (and backwards compatible `page`) to make them easier to use
|
||||
* Updated vendor libraries including `SCSSPHP` to v1.2
|
||||
1. [](#bugfix)
|
||||
* Fixed issue with File field being used in Theme/Plugins
|
||||
* Fixed bad redirection after successful admin login in subdirectory multisite [#1487](https://github.com/getgrav/grav-plugin-admin/issues/1487)
|
||||
|
||||
# v1.10.0-rc.15
|
||||
## 07/22/2020
|
||||
|
||||
1. [](#bugfix)
|
||||
* Disabled the EXIF library for Dropzone for fixing the orientation as it was getting applied twice [#1923](https://github.com/getgrav/grav-plugin-admin/issues/1923)
|
||||
* Forked Dropzone fo fix issue with Resize + Exif orientation [#1923](https://github.com/getgrav/grav-plugin-admin/issues/1923)
|
||||
* Fixed URI encode for the preview of images names
|
||||
|
||||
# v1.10.0-rc.14
|
||||
## 07/09/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Completely removed old Google font support for upgrade compatibility
|
||||
1. [](#bugfix)
|
||||
* Fixed bad `use` reference to `UserObject`
|
||||
|
||||
# v1.10.0-rc.13
|
||||
## 07/01/2020
|
||||
|
||||
1. [](#improved)
|
||||
* Improved color picker field
|
||||
* Trim login route for safety
|
||||
* Composer update to grab latest vendor libs
|
||||
|
||||
# v1.10.0-rc.12
|
||||
## 06/08/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added ability to set a preferred markdown editor in user profile
|
||||
* Added new `onAdminListContentEditors` event to add a custom editor to the list of available
|
||||
1. [](#bugfix)
|
||||
* Fixed issue deleting file from a plugin's configuration
|
||||
* Use `Pages::find()` instead of `Pages::dispatch()` as we do not want to redirect out of admin
|
||||
* Fixed broken `parent` field when using the old pages
|
||||
* Fixed broken `file` field preview when using streams in the path
|
||||
|
||||
# v1.10.0-rc.11
|
||||
## 05/14/2020
|
||||
|
||||
1. [](#new)
|
||||
* Major enhancements to "White Label" functionality including ability to export/import presets
|
||||
* New horizontal scroller for theme presets
|
||||
* Codemirror Fontsize / Preset / Font preference options
|
||||
1. [](#improved)
|
||||
* Fixed lots of styling issues related to "White Label" presets
|
||||
* Changed out "One Light" theme for new "Firewatch Light" theme
|
||||
* New scrolling system based on `SimpleBar` + native CSS scrollbar styling
|
||||
|
||||
# v1.10.0-rc.10
|
||||
## 04/30/2020
|
||||
|
||||
1. [](#new)
|
||||
* Addd new `taskConvertUrls` method for use with 3rd party editors
|
||||
|
||||
# v1.10.0-rc.9
|
||||
## 04/27/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added new "White Label" functionality to customize admin colors + logos
|
||||
* Added badge count for children in the Parents field
|
||||
1. [](#improved)
|
||||
* Added markdown support to `text` in `section` field
|
||||
1. [](#bugfix)
|
||||
* Prevent loading Pages in Parents field if they don't have children
|
||||
* Fixed custom folder in `mediapicker` field not working with streams
|
||||
* Fixed language redirect adding extra language prefix in Flex
|
||||
* Fixed `Invalid input in "Parent"` when saving page in raw mode [#1869](https://github.com/getgrav/grav-plugin-admin/issues/1869)
|
||||
|
||||
# v1.10.0-rc.8
|
||||
## 03/19/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added `has-children` flag in parent field data response
|
||||
* Added `RESET` en lang string
|
||||
1. [](#bugfix)
|
||||
* Fixed parent field not working with regular pages
|
||||
|
||||
# v1.10.0-rc.7
|
||||
## 03/05/2020
|
||||
|
||||
1. [](#new)
|
||||
* Enable admin cache by default (for existing sites, check `Plugins > Admin Panel > Enable Admin Caching`)
|
||||
1. [](#improved)
|
||||
* Removed old `scss.sh` and `watch.sh` scripts, use `gulp watch-css`
|
||||
* Added keysOnly parameter to `AdminPlugin::pagesTypes()` and `AdminPlugin::pagesModularTypes()` methods
|
||||
* Added ignore parameter to `Admin::types()` and `Admin::modularTypes()` methods
|
||||
* Improved configuration fields for hiding page types in Admin
|
||||
1. [](#bugfix)
|
||||
* Fixed minor UI padding in Flex pages [#1825](https://github.com/getgrav/grav-plugin-admin/issues/1825)
|
||||
* Fixed `column` and `section` fields loosing user entered value when form submit fails
|
||||
* Fixed `order` field not working with a new Flex Page
|
||||
|
||||
# v1.10.0-rc.6
|
||||
## 02/11/2020
|
||||
|
||||
1. [](#new)
|
||||
* Pass phpstan level 1 tests
|
||||
* Updated semver library to v1.5
|
||||
* Require flex-objects plugin
|
||||
1. [](#improved)
|
||||
* Added some debugging messages (turned off by default)
|
||||
|
||||
# v1.10.0-rc.5
|
||||
## 02/03/2020
|
||||
|
||||
1. [](#new)
|
||||
* No changes, just keeping things in sync with Grav RC version
|
||||
|
||||
# v1.10.0-rc.4
|
||||
## 02/03/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added message to dashboard to install Flex Objects plugin if it is missing
|
||||
* Updated `permissions` field to use new `$grav['permissions']`
|
||||
* DEPRECATED `onAdminRegisterPermissions` event, use `PermissionsRegisterEvent::class` event instead
|
||||
* DEPRECATED `Admin::setPermissions()` and `Admin::addPermissions()`, use `PermissionsRegisterEvent::class` event instead
|
||||
* DEPRECATED `Admin::getPermissions()`, use `$grav['permissions']->getInstances()` instead
|
||||
1. [](#improved)
|
||||
* Added `field.show_label` and `field.label` display logic from frontend forms
|
||||
1. [](#bugfix)
|
||||
* Fixed user profile when using `Flex Users` only in admin
|
||||
* Fixed saving data with empty field, default value (from config, plugin, theme) was used instead
|
||||
* Fixed JS bug is using empty Grav URI param key
|
||||
* Fixed bug in toggleable field being disabled with empty value (`''` `0`, `false`, `[]`...)
|
||||
* Fixed `admin_route()` twig function to work properly with Grav 1.7.0-rc.4, which fixes `Route` base
|
||||
* Fixed misleading 'Show sensitive data' configuration option wording [#1818](https://github.com/getgrav/grav-plugin-admin/issues/1818)
|
||||
|
||||
# v1.10.0-rc.3
|
||||
## 01/02/2020
|
||||
|
||||
1. [](#new)
|
||||
* Added ability to display **Changelogs** for `Grav`, `Plugins` and `Themes`
|
||||
* Added raw root page support for `Flex Pages`
|
||||
|
||||
# v1.10.0-rc.2
|
||||
## 12/04/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added support for hiding parts of admin by `Deny` permissions (`Flex Users` only)
|
||||
* Optimized `parent` field for Flex Pages
|
||||
1. [](#improved)
|
||||
* Improved `permissions` field to add support for displaying calculated permissions
|
||||
* Grav 1.7: Updated deprecated `$page->modular()` method calls to `$page->isModule()`
|
||||
* Output the current process user name in Scheduler instructions
|
||||
* Translations: rename MODULAR to MODULE everywhere
|
||||
1. [](#bugfix)
|
||||
* Fixed `permissions` field with nested permissions
|
||||
* Fixed Save Shortcut (CTRL + S / CMD + S) not working with new Flex Pages [#1787](https://github.com/getgrav/grav-plugin-admin/issues/1787)
|
||||
|
||||
# v1.10.0-rc.1
|
||||
## 11/06/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added a new `onAdminLogFiles()` event for 3rd party plugins to register log files for log viewer [#1765](https://github.com/getgrav/grav-plugin-admin/issues/1765)
|
||||
1. [](#improved)
|
||||
* Improved delete button UI [#1769](https://github.com/getgrav/grav-plugin-admin/issues/1769)
|
||||
* Ability to configure display of 3rd party dashboard widgets [#1766](https://github.com/getgrav/grav-plugin-admin/issues/1766)
|
||||
1. [](#bugfix)
|
||||
* Fixed administrator user creation when `Flex Users` is enabled
|
||||
* Fixed minor button alignment in FF [#1760](https://github.com/getgrav/grav-plugin-admin/issues/1760)
|
||||
|
||||
# v1.10.0-beta.10
|
||||
## 10/03/2019
|
||||
|
||||
1. [](#bugfix)
|
||||
* Regression: Fixed language assignments for the pages without set language
|
||||
|
||||
# v1.10.0-beta.9
|
||||
## 09/26/2019
|
||||
|
||||
1. [](#bugfix)
|
||||
* Make pages field to work with Flex Pages
|
||||
|
||||
# v1.10.0-beta.8
|
||||
## 09/19/2019
|
||||
|
||||
1. [](#new)
|
||||
* Add ability to Sanitize SVGs on file upload
|
||||
* Add ability to Sanitize SVGs in Page media
|
||||
1. [](#improved)
|
||||
* YAML linter report now supports multi-language
|
||||
* Better colors/placement of toolbar buttons in page edit view
|
||||
1. [](#bugfix)
|
||||
* Fixed missing language for AJAX requests
|
||||
* Fixed redirect with absolute language URL
|
||||
* Fixed issue with user avatar reference not being deleted when image removed
|
||||
|
||||
# v1.10.0-beta.7
|
||||
## 08/30/2019
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed regression: Do not require Flex Objects plugin [grav#2653](https://github.com/getgrav/grav/issues/2653)
|
||||
|
||||
# v1.10.0-beta.6
|
||||
## 08/29/2019
|
||||
|
||||
1. [](#improved)
|
||||
* Optimized admin for speed (only load frontend pages on demand)
|
||||
* Updated navigation menu to be fully controlled and overrideable by `onAdminMenu` event
|
||||
* Lots of Flex Page speed improvements
|
||||
|
||||
# v1.10.0-beta.5
|
||||
## 08/11/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added `data()` twig function to create data object from an array
|
||||
1. [](#improved)
|
||||
* Better support for `array` field into `list` field
|
||||
* Made RAW blueprints (expert mode) to work properly with Flex Form
|
||||
* Better support for `clockwork` logs
|
||||
1. [](#bugfix)
|
||||
* Fixed issue with nested `list` fields both utilizing the custom `key` functionality
|
||||
* Regression: Page Preview not working, bad url [#1715](https://github.com/getgrav/grav-plugin-admin/issues/1715)
|
||||
* Fixed '+New Folder' to work with new parent picker
|
||||
* Fixed missing XSS check field when editing modular page as raw
|
||||
* Fixed minor CSS layout issue [#1717](https://github.com/getgrav/grav-plugin-admin/issues/1717)
|
||||
|
||||
# v1.10.0-beta.4
|
||||
## 07/01/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added `Admin::redirect()` method to allow redirects to be used outside of controllers
|
||||
* Added `$admin->adminRoute()` method and `admin_route()` twig function to create language aware admin page links
|
||||
* Renamed `Admin::route()` to `Admin::getCurrentRoute()` and deprecated the old call
|
||||
1. [](#improved)
|
||||
* Much improved multi-language support for pages
|
||||
* Admin redirects should now work better with multiple languages enabled
|
||||
1. [](#bugfix)
|
||||
* Fixed default language being renamed to `page.en.md` (English) instead of keeping existing `page.md` filename
|
||||
* Fixed possibility to override already existing translation by `Save As Language`
|
||||
* Fixed missing default translation if page used plain `.md` file extension without language code
|
||||
* Fixed wrong translation showing up as page fallback language
|
||||
* Integrated Admin 1.9.8 bug fixes
|
||||
|
||||
# v1.10.0-beta.3
|
||||
## 06/24/2019
|
||||
|
||||
1. [](#improved)
|
||||
* Smarter handling of symlinks in parent field
|
||||
1. [](#bugfix)
|
||||
* Fixed issue with windows paths in `parent` field [#1699](https://github.com/getgrav/grav-plugin-admin/issues/1699)
|
||||
|
||||
# v1.10.0-beta.2
|
||||
## 06/21/2019
|
||||
|
||||
1. [](#improved)
|
||||
* Moved Remodal in-house and added support for stackable modals [#1698](https://github.com/getgrav/grav-plugin-admin/issues/1698), [#1699](https://github.com/getgrav/grav-plugin-admin/issues/1699)
|
||||
1. [](#bugfix)
|
||||
* Fixed missing check for maximum allowed files in `files` field
|
||||
|
||||
# v1.10.0-beta.1
|
||||
## 06/14/2019
|
||||
|
||||
1. [](#new)
|
||||
* New Parent/Move field using Ajax for better performance
|
||||
* Improvements to cache clearing when admin cache is enabled
|
||||
* Require Grav v1.7
|
||||
* Use PSR-4 for plugin classes
|
||||
* Added support for Twig 2.11 (compatible with Twig 1.40+)
|
||||
1. [](#improved)
|
||||
* Various admin performance improvements
|
||||
1. [](#bugfix)
|
||||
* Fixed admin caching issues
|
||||
|
||||
# v1.9.19
|
||||
## 12/14/2020
|
||||
|
||||
|
||||
41
README.md
41
README.md
@@ -105,6 +105,47 @@ The **standard free version**, is very powerful, and has more functionality than
|
||||
|
||||
We also intend to release in the near future a more feature-rich **pro version** that will include enhanced functionality, as well as some additional nice-to-have capabilities. This pro version will be a **paid** plugin the price of which is not yet 100% finalized.
|
||||
|
||||
# Admin Events
|
||||
|
||||
## General events
|
||||
|
||||
- onAdminRegisterPermissions - (admin)
|
||||
- onAdminThemeInitialized
|
||||
- onAdminPage - (page)
|
||||
- onAdminMenu
|
||||
- onAdminTwigTemplatePaths - (paths)
|
||||
|
||||
## Page specific events
|
||||
|
||||
- onAdminDashboard
|
||||
- onAdminTools - (tools)
|
||||
- onAdminLogFiles - (logs)
|
||||
- onAdminGenerateReports - (reports)
|
||||
|
||||
## Tasks
|
||||
|
||||
- onAdminControllerInit - (controller)
|
||||
- onAdminTaskExecute - (controller, method)
|
||||
|
||||
## Editing
|
||||
|
||||
- onAdminData
|
||||
- onAdminSave - (object)
|
||||
- onAdminAfterSave - (object)
|
||||
|
||||
## Pages
|
||||
|
||||
- onAdminPageTypes - (types)
|
||||
- onAdminModularPageTypes
|
||||
- onAdminSave - (page)
|
||||
- onAdminAfterSaveAs - (path)
|
||||
- onAdminAfterSave - (page)
|
||||
- onAdminAfterDelete - (page)
|
||||
- onAdminAfterAddMedia - (page)
|
||||
- onAdminAfterDelMedia - (page)
|
||||
- onAdminCreatePageFrontmatter - (header, data)
|
||||
|
||||
|
||||
# Running Tests
|
||||
|
||||
First install the dev dependencies by running `composer update` from the Grav root.
|
||||
|
||||
6
UPGRADE.md
Normal file
6
UPGRADE.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Upgrading to Admin 1.10
|
||||
|
||||
Twig:
|
||||
|
||||
* **Admin link**: When linking to another admin page, use `{{ admin_route('/config/site') }}` instead of any other method, such as `{{ base_url_relative }}/config/site` (fixes multi-language issues)
|
||||
|
||||
411
admin.php
411
admin.php
@@ -3,6 +3,7 @@ namespace Grav\Plugin;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Grav\Common\Cache;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
@@ -18,6 +19,10 @@ use Grav\Common\Session;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\User\Interfaces\UserCollectionInterface;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Yaml;
|
||||
use Grav\Events\PermissionsRegisterEvent;
|
||||
use Grav\Framework\Acl\PermissionsReader;
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use Grav\Framework\Session\Exceptions\SessionException;
|
||||
use Grav\Plugin\Admin\Admin;
|
||||
use Grav\Plugin\Admin\Popularity;
|
||||
@@ -25,8 +30,10 @@ use Grav\Plugin\Admin\Router;
|
||||
use Grav\Plugin\Admin\Themes;
|
||||
use Grav\Plugin\Admin\AdminController;
|
||||
use Grav\Plugin\Admin\Twig\AdminTwigExtension;
|
||||
use Grav\Plugin\Admin\WhiteLabel;
|
||||
use Grav\Plugin\Form\Form;
|
||||
use Grav\Plugin\Login\Login;
|
||||
use Pimple\Container;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
class AdminPlugin extends Plugin
|
||||
@@ -87,6 +94,8 @@ class AdminPlugin extends Plugin
|
||||
'onShutdown' => ['onShutdown', 1000],
|
||||
'onAdminDashboard' => ['onAdminDashboard', 0],
|
||||
'onAdminTools' => ['onAdminTools', 0],
|
||||
'onAdminSave' => ['onAdminSave', 0],
|
||||
PermissionsRegisterEvent::class => ['onRegisterPermissions', 1000],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -115,6 +124,33 @@ class AdminPlugin extends Plugin
|
||||
],
|
||||
'file' => [
|
||||
'array' => true
|
||||
],
|
||||
'permissions' => [
|
||||
'ignore_empty' => true,
|
||||
'validate' => [
|
||||
'type' => 'array'
|
||||
],
|
||||
'filter' => [
|
||||
'type' => 'flatten_array',
|
||||
'value_type' => 'bool',
|
||||
]
|
||||
],
|
||||
'acl_picker' => [
|
||||
'ignore_empty' => true,
|
||||
'validate' => [
|
||||
'type' => 'array'
|
||||
],
|
||||
'filter' => [
|
||||
'type' => 'array',
|
||||
'key_type' => 'string',
|
||||
'value_type' => 'bool',
|
||||
]
|
||||
],
|
||||
'taxonomy' => [
|
||||
'multiple' => true,
|
||||
'validate' => [
|
||||
'type' => 'array'
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -158,6 +194,10 @@ class AdminPlugin extends Plugin
|
||||
|
||||
// Only activate admin if we're inside the admin path.
|
||||
if ($this->isAdminPath()) {
|
||||
$pages = $this->grav['pages'];
|
||||
if (method_exists($pages, 'disablePages')) {
|
||||
$pages->disablePages();
|
||||
}
|
||||
try {
|
||||
$this->grav['session']->init();
|
||||
} catch (SessionException $e) {
|
||||
@@ -200,7 +240,7 @@ class AdminPlugin extends Plugin
|
||||
}
|
||||
|
||||
// Turn on Twig autoescaping
|
||||
if (method_exists($this->grav['twig'], 'setAutoescape') && $this->grav['uri']->param('task') !== 'processmarkdown') {
|
||||
if ($this->grav['uri']->param('task') !== 'processmarkdown') {
|
||||
$this->grav['twig']->setAutoescape(true);
|
||||
}
|
||||
|
||||
@@ -216,6 +256,9 @@ class AdminPlugin extends Plugin
|
||||
$this->grav['themes'] = function () {
|
||||
return new Themes($this->grav);
|
||||
};
|
||||
|
||||
// Initialize white label functionality
|
||||
$this->grav['admin-whitelabel'] = new WhiteLabel();
|
||||
}
|
||||
|
||||
// We need popularity no matter what
|
||||
@@ -244,6 +287,25 @@ class AdminPlugin extends Plugin
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force compile during save if admin plugin save
|
||||
*
|
||||
* @param Event $event
|
||||
*/
|
||||
public function onAdminSave(Event $event)
|
||||
{
|
||||
$obj = $event['object'];
|
||||
|
||||
if ($obj instanceof Data && $obj->blueprints()->getFilename() === 'admin/blueprints') {
|
||||
|
||||
[$status, $msg] = $this->grav['admin-whitelabel']->compilePresetScss($obj);
|
||||
if (!$status) {
|
||||
$this->grav['messages']->add($msg, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* [onPageInitialized:0]
|
||||
*/
|
||||
@@ -270,9 +332,9 @@ class AdminPlugin extends Plugin
|
||||
$form = $event['form'];
|
||||
$action = $event['action'];
|
||||
|
||||
Admin::DEBUG && Admin::addDebugMessage('Admin Form: ' . $action);
|
||||
switch ($action) {
|
||||
case 'register_admin_user':
|
||||
|
||||
if (Admin::doAnyUsersExist()) {
|
||||
throw new \RuntimeException('A user account already exists, please create an admin account manually.');
|
||||
}
|
||||
@@ -320,7 +382,6 @@ class AdminPlugin extends Plugin
|
||||
$data['fullname'] = $data['fullname'] ?? $inflector->titleize($username);
|
||||
$data['title'] = $data['title'] ?? 'Administrator';
|
||||
$data['state'] = 'enabled';
|
||||
$data['access'] = ['admin' => ['login' => true, 'super' => true], 'site' => ['login' => true]];
|
||||
|
||||
/** @var UserCollectionInterface $users */
|
||||
$users = $this->grav['accounts'];
|
||||
@@ -328,14 +389,15 @@ class AdminPlugin extends Plugin
|
||||
// Create user object and save it
|
||||
$user = $users->load($username);
|
||||
$user->update($data);
|
||||
$user->set('access', ['admin' => ['login' => true, 'super' => true], 'site' => ['login' => true]]);
|
||||
$user->save();
|
||||
|
||||
//Login user
|
||||
// Login user
|
||||
$this->grav['session']->user = $user;
|
||||
unset($this->grav['user']);
|
||||
$this->grav['user'] = $user;
|
||||
$user->authenticated = true;
|
||||
$user->authorized = $user->authorize('admin.login');
|
||||
$user->authorized = $user->authorize('admin.login') ?? false;
|
||||
|
||||
$messages = $this->grav['messages'];
|
||||
$messages->add($this->grav['language']->translate('PLUGIN_ADMIN.LOGIN_LOGGED_IN'), 'info');
|
||||
@@ -370,11 +432,27 @@ class AdminPlugin extends Plugin
|
||||
*/
|
||||
public function onAdminDashboard()
|
||||
{
|
||||
$this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = ['template' => 'dashboard-maintenance'];
|
||||
$this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = ['template' => 'dashboard-statistics'];
|
||||
$this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = ['template' => 'dashboard-notifications'];
|
||||
$this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = ['template' => 'dashboard-feed'];
|
||||
$this->grav['twig']->plugins_hooked_dashboard_widgets_main[] = ['template' => 'dashboard-pages'];
|
||||
$lang = $this->grav['language'];
|
||||
$this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = [
|
||||
'name' => $lang->translate('PLUGIN_ADMIN.MAINTENANCE'),
|
||||
'template' => 'dashboard-maintenance',
|
||||
];
|
||||
$this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = [
|
||||
'name' => $lang->translate('PLUGIN_ADMIN.VIEWS_STATISTICS'),
|
||||
'template' => 'dashboard-statistics',
|
||||
];
|
||||
$this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = [
|
||||
'name' => $lang->translate('PLUGIN_ADMIN.NOTIFICATIONS'),
|
||||
'template' => 'dashboard-notifications',
|
||||
];
|
||||
$this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = [
|
||||
'name' => $lang->translate('PLUGIN_ADMIN.NEWS_FEED'),
|
||||
'template' => 'dashboard-feed',
|
||||
];
|
||||
$this->grav['twig']->plugins_hooked_dashboard_widgets_main[] = [
|
||||
'name' => $lang->translate('PLUGIN_ADMIN.LATEST_PAGE_UPDATES'),
|
||||
'template' => 'dashboard-pages',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -387,12 +465,13 @@ class AdminPlugin extends Plugin
|
||||
*/
|
||||
public function onAdminTools(Event $event)
|
||||
{
|
||||
$lang = $this->grav['language'];
|
||||
$event['tools'] = array_merge($event['tools'], [
|
||||
'backups' => [['admin.maintenance', 'admin.super'], $this->grav['language']->translate('PLUGIN_ADMIN.BACKUPS')],
|
||||
'scheduler' => [['admin.super'], $this->grav['language']->translate('PLUGIN_ADMIN.SCHEDULER')],
|
||||
'logs' => [['admin.super'], $this->grav['language']->translate('PLUGIN_ADMIN.LOGS')],
|
||||
'reports' => [['admin.super'], $this->grav['language']->translate('PLUGIN_ADMIN.REPORTS')],
|
||||
'direct-install' => [['admin.super'], $this->grav['language']->translate('PLUGIN_ADMIN.DIRECT_INSTALL')],
|
||||
'backups' => [['admin.maintenance', 'admin.super'], $lang->translate('PLUGIN_ADMIN.BACKUPS')],
|
||||
'scheduler' => [['admin.super'], $lang->translate('PLUGIN_ADMIN.SCHEDULER')],
|
||||
'logs' => [['admin.super'], $lang->translate('PLUGIN_ADMIN.LOGS')],
|
||||
'reports' => [['admin.super'], $lang->translate('PLUGIN_ADMIN.REPORTS')],
|
||||
'direct-install' => [['admin.super'], $lang->translate('PLUGIN_ADMIN.DIRECT_INSTALL')],
|
||||
]);
|
||||
|
||||
return $event;
|
||||
@@ -408,6 +487,7 @@ class AdminPlugin extends Plugin
|
||||
// Force SSL with redirect if required
|
||||
if ($config->get('system.force_ssl')) {
|
||||
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
|
||||
Admin::DEBUG && Admin::addDebugMessage('Admin SSL forced on, redirect');
|
||||
$url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
|
||||
$this->grav->redirect($url);
|
||||
}
|
||||
@@ -415,9 +495,6 @@ class AdminPlugin extends Plugin
|
||||
|
||||
$this->session = $this->grav['session'];
|
||||
|
||||
// Set original route for the home page.
|
||||
$home = '/' . trim($this->config->get('system.home.alias'), '/');
|
||||
|
||||
// set session variable if it's passed via the url
|
||||
if ($this->uri->param('mode') === 'expert') {
|
||||
$this->session->expert = true;
|
||||
@@ -428,37 +505,19 @@ class AdminPlugin extends Plugin
|
||||
$this->session->expert = $this->session->expert ?? false;
|
||||
}
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->grav['pages'];
|
||||
|
||||
$this->grav['admin']->routes = $pages->routes();
|
||||
|
||||
// Remove default route from routes.
|
||||
if (isset($this->grav['admin']->routes['/'])) {
|
||||
unset($this->grav['admin']->routes['/']);
|
||||
}
|
||||
|
||||
$page = $pages->dispatch('/', true);
|
||||
|
||||
// If page is null, the default page does not exist, and we cannot route to it
|
||||
if ($page) {
|
||||
$page->route($home);
|
||||
}
|
||||
|
||||
// Make local copy of POST.
|
||||
$post = $this->grav['uri']->post();
|
||||
|
||||
// Initialize Page Types
|
||||
Pages::types();
|
||||
|
||||
// Handle tasks.
|
||||
$this->admin->task = $task = $this->grav['task'] ?? $this->grav['action'];
|
||||
if ($task) {
|
||||
Admin::DEBUG && Admin::addDebugMessage("Admin task: {$task}");
|
||||
$this->initializeController($task, $post);
|
||||
} elseif ($this->template === 'logs' && $this->route) {
|
||||
// Display RAW error message.
|
||||
echo $this->admin->logEntry();
|
||||
exit();
|
||||
$response = new Response(200, [], $this->admin->logEntry());
|
||||
|
||||
$this->grav->close($response);
|
||||
}
|
||||
|
||||
$self = $this;
|
||||
@@ -466,8 +525,6 @@ class AdminPlugin extends Plugin
|
||||
// make sure page is not frozen!
|
||||
unset($this->grav['page']);
|
||||
|
||||
$this->admin->pagesCount();
|
||||
|
||||
// Replace page service with admin.
|
||||
$this->grav['page'] = function () use ($self) {
|
||||
$page = new Page();
|
||||
@@ -481,12 +538,15 @@ class AdminPlugin extends Plugin
|
||||
$page = $event['page'];
|
||||
|
||||
if ($page->slug()) {
|
||||
Admin::DEBUG && Admin::addDebugMessage('Admin page: from event');
|
||||
return $page;
|
||||
}
|
||||
}
|
||||
|
||||
// Look in the pages provided by the Admin plugin itself
|
||||
if (file_exists(__DIR__ . "/pages/admin/{$self->template}.md")) {
|
||||
Admin::DEBUG && Admin::addDebugMessage("Admin page: {$self->template}");
|
||||
|
||||
$page->init(new \SplFileInfo(__DIR__ . "/pages/admin/{$self->template}.md"));
|
||||
$page->slug(basename($self->template));
|
||||
|
||||
@@ -505,6 +565,8 @@ class AdminPlugin extends Plugin
|
||||
$path = $locator->findResource("plugins://{$plugin->name}/admin/pages/{$self->template}.md");
|
||||
|
||||
if ($path) {
|
||||
Admin::DEBUG && Admin::addDebugMessage("Admin page: plugin {$plugin->name}/{$self->template}");
|
||||
|
||||
$page->init(new \SplFileInfo($path));
|
||||
$page->slug(basename($self->template));
|
||||
|
||||
@@ -517,6 +579,7 @@ class AdminPlugin extends Plugin
|
||||
|
||||
if (empty($this->grav['page'])) {
|
||||
if ($this->grav['user']->authenticated) {
|
||||
Admin::DEBUG && Admin::addDebugMessage('Admin page: fire onPageNotFound event');
|
||||
$event = new Event(['page' => null]);
|
||||
$event->page = null;
|
||||
$event = $this->grav->fireEvent('onPageNotFound', $event);
|
||||
@@ -524,6 +587,7 @@ class AdminPlugin extends Plugin
|
||||
$page = $event->page;
|
||||
|
||||
if (!$page || !$page->routable()) {
|
||||
Admin::DEBUG && Admin::addDebugMessage('Admin page: 404 Not Found');
|
||||
$error_file = $this->grav['locator']->findResource('plugins://admin/pages/admin/error.md');
|
||||
$page = new Page();
|
||||
$page->init(new \SplFileInfo($error_file));
|
||||
@@ -534,6 +598,7 @@ class AdminPlugin extends Plugin
|
||||
unset($this->grav['page']);
|
||||
$this->grav['page'] = $page;
|
||||
} else {
|
||||
Admin::DEBUG && Admin::addDebugMessage('Admin page: login');
|
||||
// Not Found and not logged in: Display login page.
|
||||
$login_file = $this->grav['locator']->findResource('plugins://admin/pages/admin/login.md');
|
||||
$page = new Page();
|
||||
@@ -557,6 +622,14 @@ class AdminPlugin extends Plugin
|
||||
$assets = $this->grav['assets'];
|
||||
$assets->setJsPipeline(false);
|
||||
$assets->setCssPipeline(false);
|
||||
|
||||
// Compile a missing preset.css file
|
||||
$preset_css = 'asset://admin-preset.css';
|
||||
$preset_path = $this->grav['locator']->findResource($preset_css);
|
||||
if (!$preset_path) {
|
||||
$this->grav['admin-whitelabel']->compilePresetScss($this->config->get('plugins.admin.whitelabel'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -581,6 +654,7 @@ class AdminPlugin extends Plugin
|
||||
$page = $this->grav['page'];
|
||||
|
||||
$twig->twig_vars['location'] = $this->template;
|
||||
$twig->twig_vars['nav_route'] = trim($this->template . '/' . $this->route, '/') . '/';
|
||||
$twig->twig_vars['base_url_relative_frontend'] = $twig->twig_vars['base_url_relative'] ?: '/';
|
||||
$twig->twig_vars['admin_route'] = trim($this->admin_route, '/');
|
||||
$twig->twig_vars['template_route'] = $this->template;
|
||||
@@ -590,14 +664,45 @@ class AdminPlugin extends Plugin
|
||||
$theme_url = '/' . ltrim($this->grav['locator']->findResource('plugin://admin/themes/' . $this->theme,
|
||||
false), '/');
|
||||
$twig->twig_vars['theme_url'] = $theme_url;
|
||||
$twig->twig_vars['preset_url'] = $twig->twig_vars['preset_url'] ?? $theme_url;
|
||||
$twig->twig_vars['base_url'] = $twig->twig_vars['base_url_relative'];
|
||||
$twig->twig_vars['base_path'] = GRAV_ROOT;
|
||||
$twig->twig_vars['admin'] = $this->admin;
|
||||
$twig->twig_vars['user'] = $this->admin->user;
|
||||
$twig->twig_vars['admin_version'] = $this->version;
|
||||
$twig->twig_vars['logviewer'] = new LogViewer();
|
||||
$twig->twig_vars['form_max_filesize'] = Utils::getUploadLimit() / 1024 / 1024;
|
||||
|
||||
// Start white label functionality
|
||||
$twig->twig_vars['whitelabel_presets'] = $this->getPresets();
|
||||
|
||||
$custom_logo = $this->config->get('plugins.admin.whitelabel.logo_custom', false);
|
||||
$custom_login_logo = $this->config->get('plugins.admin.whitelabel.logo_login', false);
|
||||
$custom_footer = $this->config->get('plugins.admin.whitelabel.custom_footer', false);
|
||||
|
||||
if ($custom_logo && is_array($custom_logo)) {
|
||||
$custom_logo = array_keys($custom_logo);
|
||||
$path = array_shift($custom_logo);
|
||||
$twig->twig_vars['custom_admin_logo'] = $path;
|
||||
}
|
||||
|
||||
if ($custom_login_logo && is_array($custom_login_logo)) {
|
||||
$custom_login_logo = array_keys($custom_login_logo);
|
||||
$path = array_shift($custom_login_logo);
|
||||
$twig->twig_vars['custom_login_logo'] = $path;
|
||||
}
|
||||
|
||||
if ($custom_footer) {
|
||||
$footer = Utils::processMarkdown($custom_footer);
|
||||
$twig->twig_vars['custom_admin_footer'] = $footer;
|
||||
}
|
||||
|
||||
$custom_css = $this->config->get('plugins.admin.whitelabel.custom_css', false);
|
||||
|
||||
if ($custom_css) {
|
||||
$this->grav['assets']->addInlineCss($custom_css);
|
||||
}
|
||||
// End white label functionality
|
||||
|
||||
$fa_icons_file = CompiledYamlFile::instance($this->grav['locator']->findResource('plugin://admin/themes/grav/templates/forms/fields/iconpicker/icons' . YAML_EXT));
|
||||
$fa_icons = $fa_icons_file->content();
|
||||
$fa_icons = array_map(function ($icon) {
|
||||
@@ -625,16 +730,22 @@ class AdminPlugin extends Plugin
|
||||
}
|
||||
}
|
||||
|
||||
// Gather Plugin-hooked nav items
|
||||
// Gather all nav items
|
||||
$this->grav['twig']->plugins_hooked_nav = [];
|
||||
$this->grav->fireEvent('onAdminMenu');
|
||||
uasort($this->grav['twig']->plugins_hooked_nav, function ($a, $b) {
|
||||
$ac = $a['priority'] ?? 0;
|
||||
$bc = $b['priority'] ?? 0;
|
||||
|
||||
return $bc <=> $ac;
|
||||
});
|
||||
|
||||
// Gather Plugin-hooked dashboard items
|
||||
$this->grav->fireEvent('onAdminDashboard');
|
||||
|
||||
switch ($this->template) {
|
||||
case 'dashboard':
|
||||
$twig->twig_vars['popularity'] = $this->popularity;
|
||||
|
||||
// Gather Plugin-hooked dashboard items
|
||||
$this->grav->fireEvent('onAdminDashboard');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -659,8 +770,6 @@ class AdminPlugin extends Plugin
|
||||
*/
|
||||
public function onTwigExtensions()
|
||||
{
|
||||
require_once __DIR__ . '/classes/Twig/AdminTwigExtension.php';
|
||||
|
||||
$this->grav['twig']->twig->addExtension(new AdminTwigExtension);
|
||||
}
|
||||
|
||||
@@ -688,10 +797,13 @@ class AdminPlugin extends Plugin
|
||||
{
|
||||
$type = $e['type'] ?? null;
|
||||
switch ($type) {
|
||||
case 'config':
|
||||
$e['type'] = $this->admin->authorize(['admin.configuration.system', 'admin.super']) ? 'config/system' : 'config/site';
|
||||
break;
|
||||
case 'tools/scheduler':
|
||||
$e['type'] = 'config/scheduler';
|
||||
break;
|
||||
case 'tools':
|
||||
case 'tools':
|
||||
case 'tools/backups':
|
||||
$e['type'] = 'config/backups';
|
||||
break;
|
||||
@@ -719,30 +831,74 @@ class AdminPlugin extends Plugin
|
||||
/**
|
||||
* Initial stab at registering permissions (WIP)
|
||||
*
|
||||
* @param Event $e
|
||||
* @param PermissionsRegisterEvent $event
|
||||
*/
|
||||
public function onAdminRegisterPermissions(Event $e)
|
||||
public function onRegisterPermissions(PermissionsRegisterEvent $event): void
|
||||
{
|
||||
$admin = $e['admin'];
|
||||
$permissions = [
|
||||
'admin.super' => 'boolean',
|
||||
'admin.login' => 'boolean',
|
||||
'admin.cache' => 'boolean',
|
||||
'admin.configuration' => 'boolean',
|
||||
'admin.configuration_system' => 'boolean',
|
||||
'admin.configuration_site' => 'boolean',
|
||||
'admin.configuration_media' => 'boolean',
|
||||
'admin.configuration_info' => 'boolean',
|
||||
'admin.settings' => 'boolean',
|
||||
'admin.pages' => 'boolean',
|
||||
'admin.maintenance' => 'boolean',
|
||||
'admin.statistics' => 'boolean',
|
||||
'admin.plugins' => 'boolean',
|
||||
'admin.themes' => 'boolean',
|
||||
'admin.tools' => 'boolean',
|
||||
'admin.users' => 'boolean',
|
||||
$actions = PermissionsReader::fromYaml("plugin://{$this->name}/permissions.yaml");
|
||||
|
||||
$permissions = $event->permissions;
|
||||
$permissions->addActions($actions);
|
||||
}
|
||||
|
||||
public function onAdminMenu()
|
||||
{
|
||||
// Dashboard
|
||||
$this->grav['twig']->plugins_hooked_nav['PLUGIN_ADMIN.DASHBOARD'] = [
|
||||
'route' => 'dashboard',
|
||||
'icon' => 'fa-th',
|
||||
'authorize' => ['admin.login', 'admin.super'],
|
||||
'priority' => 10
|
||||
];
|
||||
|
||||
// Configuration
|
||||
$this->grav['twig']->plugins_hooked_nav['PLUGIN_ADMIN.CONFIGURATION'] = [
|
||||
'route' => 'config',
|
||||
'icon' => 'fa-wrench',
|
||||
'authorize' => [
|
||||
'admin.configuration.system',
|
||||
'admin.configuration.site',
|
||||
'admin.super'],
|
||||
'priority' => 9
|
||||
];
|
||||
|
||||
// Pages
|
||||
$count = new Container(['count' => function () { return $this->admin->pagesCount(); }]);
|
||||
$this->grav['twig']->plugins_hooked_nav['PLUGIN_ADMIN.PAGES'] = [
|
||||
'route' => 'pages',
|
||||
'icon' => 'fa-file-text-o',
|
||||
'authorize' => ['admin.pages', 'admin.super'],
|
||||
'badge' => $count,
|
||||
'priority' => 5
|
||||
];
|
||||
|
||||
// Plugins
|
||||
$count = new Container(['updates' => 0, 'count' => function () { return count($this->admin->plugins()); }]);
|
||||
$this->grav['twig']->plugins_hooked_nav['PLUGIN_ADMIN.PLUGINS'] = [
|
||||
'route' => 'plugins',
|
||||
'icon' => 'fa-plug',
|
||||
'authorize' => ['admin.plugins', 'admin.super'],
|
||||
'badge' => $count,
|
||||
'priority' => -8
|
||||
];
|
||||
|
||||
// Themes
|
||||
$count = new Container(['updates' => 0, 'count' => function () { return count($this->admin->themes()); }]);
|
||||
$this->grav['twig']->plugins_hooked_nav['PLUGIN_ADMIN.THEMES'] = [
|
||||
'route' => 'themes',
|
||||
'icon' => 'fa-tint',
|
||||
'authorize' => ['admin.themes', 'admin.super'],
|
||||
'badge' => $count,
|
||||
'priority' => -9
|
||||
];
|
||||
|
||||
// Tools
|
||||
$this->grav['twig']->plugins_hooked_nav['PLUGIN_ADMIN.TOOLS'] = [
|
||||
'route' => 'tools',
|
||||
'icon' => 'fa-briefcase',
|
||||
'authorize' => $this->admin::toolsPermissions(),
|
||||
'priority' => -10
|
||||
];
|
||||
$admin->addPermissions($permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -758,53 +914,71 @@ class AdminPlugin extends Plugin
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to replace Pages::Types()
|
||||
* and to provide an event to manipulate the data
|
||||
* Helper function to replace Pages::Types() and to provide an event to manipulate the data
|
||||
*
|
||||
* Dispatches 'onAdminPageTypes' event
|
||||
* with 'types' data member which is a
|
||||
* reference to the data
|
||||
* Dispatches 'onAdminPageTypes' event with 'types' data member which is a reference to the data
|
||||
*
|
||||
* @param bool $keysOnly
|
||||
* @return array
|
||||
*/
|
||||
public static function pagesTypes()
|
||||
public static function pagesTypes(bool $keysOnly = false)
|
||||
{
|
||||
$types = Pages::types();
|
||||
|
||||
// First filter by configuration
|
||||
$hideTypes = Grav::instance()['config']->get('plugins.admin.hide_page_types', []);
|
||||
foreach ((array) $hideTypes as $type) {
|
||||
unset($types[$type]);
|
||||
foreach ((array) $hideTypes as $hide) {
|
||||
if (isset($types[$hide])) {
|
||||
unset($types[$hide]);
|
||||
} else {
|
||||
foreach ($types as $key => $type) {
|
||||
$match = preg_match('#' . $hide . '#i', $key);
|
||||
if ($match) {
|
||||
unset($types[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow manipulating of the data by event
|
||||
$e = new Event(['types' => &$types]);
|
||||
Grav::instance()->fireEvent('onAdminPageTypes', $e);
|
||||
|
||||
return $types;
|
||||
return $keysOnly ? array_keys($types) : $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to replace Pages::modularTypes()
|
||||
* and to provide an event to manipulate the data
|
||||
* Helper function to replace Pages::modularTypes() and to provide an event to manipulate the data
|
||||
*
|
||||
* Dispatches 'onAdminModularPageTypes' event
|
||||
* with 'types' data member which is a
|
||||
* reference to the data
|
||||
* Dispatches 'onAdminModularPageTypes' event with 'types' data member which is a reference to the data
|
||||
*
|
||||
* @param bool $keysOnly
|
||||
* @return array
|
||||
*/
|
||||
public static function pagesModularTypes()
|
||||
public static function pagesModularTypes(bool $keysOnly = false)
|
||||
{
|
||||
$types = Pages::modularTypes();
|
||||
|
||||
// First filter by configuration
|
||||
$hideTypes = (array) Grav::instance()['config']->get('plugins.admin.hide_modular_page_types', []);
|
||||
foreach ($hideTypes as $type) {
|
||||
unset($types[$type]);
|
||||
foreach ((array) $hideTypes as $hide) {
|
||||
if (isset($types[$hide])) {
|
||||
unset($types[$hide]);
|
||||
} else {
|
||||
foreach ($types as $key => $type) {
|
||||
$match = preg_match('#' . $hide . '#i', $key);
|
||||
if ($match) {
|
||||
unset($types[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow manipulating of the data by event
|
||||
$e = new Event(['types' => &$types]);
|
||||
Grav::instance()->fireEvent('onAdminModularPageTypes', $e);
|
||||
|
||||
return $types;
|
||||
return $keysOnly ? array_keys($types) : $types;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -830,6 +1004,8 @@ class AdminPlugin extends Plugin
|
||||
|
||||
protected function initializeController($task, $post)
|
||||
{
|
||||
Admin::DEBUG && Admin::addDebugMessage('Admin controller: execute');
|
||||
|
||||
$controller = new AdminController();
|
||||
$controller->initialize($this->grav, $this->template, $task, $this->route, $post);
|
||||
$controller->execute();
|
||||
@@ -850,16 +1026,12 @@ class AdminPlugin extends Plugin
|
||||
'onTwigTemplatePaths' => ['onTwigTemplatePaths', 1000],
|
||||
'onTwigSiteVariables' => ['onTwigSiteVariables', 1000],
|
||||
'onAssetsInitialized' => ['onAssetsInitialized', 1000],
|
||||
'onAdminRegisterPermissions' => ['onAdminRegisterPermissions', 0],
|
||||
'onOutputGenerated' => ['onOutputGenerated', 0],
|
||||
'onAdminAfterSave' => ['onAdminAfterSave', 0],
|
||||
'onAdminData' => ['onAdminData', 0],
|
||||
'onAdminMenu' => ['onAdminMenu', 1000],
|
||||
]);
|
||||
|
||||
// Autoload classes
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
|
||||
// Check for required plugins
|
||||
if (!$this->grav['config']->get('plugins.login.enabled') || !$this->grav['config']->get('plugins.form.enabled') || !$this->grav['config']->get('plugins.email.enabled')) {
|
||||
throw new \RuntimeException('One of the required plugins is missing or not enabled');
|
||||
@@ -955,8 +1127,8 @@ class AdminPlugin extends Plugin
|
||||
'EDITOR',
|
||||
'PREVIEW',
|
||||
'FULLSCREEN',
|
||||
'MODULAR',
|
||||
'NON_MODULAR',
|
||||
'MODULE',
|
||||
'NON_MODULE',
|
||||
'VISIBLE',
|
||||
'NON_VISIBLE',
|
||||
'ROUTABLE',
|
||||
@@ -1034,4 +1206,49 @@ class AdminPlugin extends Plugin
|
||||
|
||||
$assets->addInlineJs($translations);
|
||||
}
|
||||
|
||||
public static function themeOptions()
|
||||
{
|
||||
static $options = [];
|
||||
|
||||
if (empty($options)) {
|
||||
$theme_files = glob(__dir__ . '/themes/grav/css/codemirror/themes/*.css');
|
||||
foreach ($theme_files as $theme_file) {
|
||||
$theme = basename(basename($theme_file, '.css'));
|
||||
$options[$theme] = Inflector::titleize($theme);
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function getPresets()
|
||||
{
|
||||
$filename = $this->grav['locator']->findResource('plugin://admin/presets.yaml', false);
|
||||
|
||||
$file = CompiledYamlFile::instance($filename);
|
||||
$presets = (array)$file->content();
|
||||
|
||||
$custom_presets = $this->config->get('plugins.admin.whitelabel.custom_presets');
|
||||
|
||||
if (isset($custom_presets)) {
|
||||
$custom_presets = Yaml::parse($custom_presets);
|
||||
|
||||
if (is_array($custom_presets)) {
|
||||
if (isset($custom_presets['name']) && isset($custom_presets['colors']) && isset($custom_presets['accents'])) {
|
||||
$preset = [Inflector::hyphenize($custom_presets['name']) => $custom_presets];
|
||||
$presets = $preset + $presets;
|
||||
} else {
|
||||
foreach ($custom_presets as $value) {
|
||||
if (isset($value['name']) && isset($value['colors']) && isset($value['accents'])) {
|
||||
$preset = [Inflector::hyphenize($value['name']) => $value];
|
||||
$presets = $preset + $presets;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $presets;
|
||||
}
|
||||
}
|
||||
|
||||
51
admin.yaml
51
admin.yaml
@@ -1,19 +1,18 @@
|
||||
enabled: true
|
||||
route: '/admin'
|
||||
cache_enabled: false
|
||||
cache_enabled: true
|
||||
theme: grav
|
||||
logo_text: ''
|
||||
body_classes: ''
|
||||
content_padding: true
|
||||
twofa_enabled: true
|
||||
log_viewer_files: ['grav', 'email']
|
||||
sidebar:
|
||||
activate: tab
|
||||
hover_delay: 100
|
||||
size: auto
|
||||
dashboard:
|
||||
days_of_stats: 7
|
||||
widgets:
|
||||
widgets_display:
|
||||
dashboard-maintenance: true
|
||||
dashboard-statistics: true
|
||||
dashboard-notifications: true
|
||||
@@ -24,14 +23,10 @@ pages:
|
||||
show_modular: true
|
||||
session:
|
||||
timeout: 1800
|
||||
warnings:
|
||||
delete_page: true
|
||||
secure_delete: false
|
||||
edit_mode: normal
|
||||
frontend_preview_target: inline
|
||||
show_github_msg: true
|
||||
pages_list_display_field: title
|
||||
google_fonts: false
|
||||
admin_icons: line-awesome
|
||||
enable_auto_updates_check: true
|
||||
notifications:
|
||||
@@ -46,3 +41,45 @@ popularity:
|
||||
daily: 30
|
||||
monthly: 12
|
||||
visitors: 20
|
||||
whitelabel:
|
||||
quicktray_recompile: false
|
||||
codemirror_theme: paper
|
||||
codemirror_fontsize: md
|
||||
codemirror_md_font: sans
|
||||
logo_custom:
|
||||
logo_login:
|
||||
color_scheme:
|
||||
accents:
|
||||
primary-accent: button
|
||||
secondary-accent: notice
|
||||
tertiary-accent: critical
|
||||
colors:
|
||||
logo-bg: '#323640'
|
||||
logo-link: '#FFFFFF'
|
||||
nav-bg: '#3D424E'
|
||||
nav-text: '#B7B9BD'
|
||||
nav-link: '#ffffff'
|
||||
nav-selected-bg: '#323640'
|
||||
nav-selected-link: '#ffffff'
|
||||
nav-hover-bg: '#434753'
|
||||
nav-hover-link: '#ffffff'
|
||||
toolbar-bg: '#ffffff'
|
||||
toolbar-text: '#3D424E'
|
||||
page-bg: '#F6F6F6'
|
||||
page-text: '#6f7b8a'
|
||||
page-link: '#0090D9'
|
||||
content-bg: '#ffffff'
|
||||
content-text: '#6f7b8a'
|
||||
content-link: '#0090D9'
|
||||
content-link2: '#da4b46'
|
||||
content-header: '#414147'
|
||||
content-tabs-bg: '#e6e6e6'
|
||||
content-tabs-text: '#808080'
|
||||
button-bg: '#0090D9'
|
||||
button-text: '#ffffff'
|
||||
notice-bg: '#06A599'
|
||||
notice-text: '#ffffff'
|
||||
update-bg: '#77559D'
|
||||
update-text: '#ffffff'
|
||||
critical-bg: '#F45857'
|
||||
critical-text: '#ffffff'
|
||||
|
||||
1158
blueprints.yaml
1158
blueprints.yaml
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@ form:
|
||||
|
||||
section:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.ADD_MODULAR_CONTENT
|
||||
title: PLUGIN_ADMIN.ADD_MODULE_CONTENT
|
||||
|
||||
title:
|
||||
type: text
|
||||
@@ -35,7 +35,7 @@ form:
|
||||
name:
|
||||
type: select
|
||||
classes: fancy
|
||||
label: PLUGIN_ADMIN.MODULAR_TEMPLATE
|
||||
label: PLUGIN_ADMIN.MODULE_TEMPLATE
|
||||
help: PLUGIN_ADMIN.PAGE_FILE_HELP
|
||||
default: default
|
||||
data-options@: '\Grav\Plugin\AdminPlugin::pagesModularTypes'
|
||||
|
||||
@@ -18,6 +18,9 @@ form:
|
||||
title: PLUGIN_ADMIN.CONTENT
|
||||
|
||||
fields:
|
||||
xss_check:
|
||||
type: xss
|
||||
|
||||
frontmatter:
|
||||
classes: frontmatter
|
||||
type: editor
|
||||
@@ -39,7 +42,7 @@ form:
|
||||
header.media_order:
|
||||
type: pagemedia
|
||||
label: PLUGIN_ADMIN.PAGE_MEDIA
|
||||
|
||||
|
||||
options:
|
||||
type: tab
|
||||
title: PLUGIN_ADMIN.OPTIONS
|
||||
@@ -83,7 +86,7 @@ form:
|
||||
name:
|
||||
type: select
|
||||
classes: fancy
|
||||
label: PLUGIN_ADMIN.MODULAR_TEMPLATE
|
||||
label: PLUGIN_ADMIN.MODULE_TEMPLATE
|
||||
default: default
|
||||
data-options@: '\Grav\Plugin\AdminPlugin::pagesModularTypes'
|
||||
validate:
|
||||
|
||||
@@ -2,6 +2,4 @@ form:
|
||||
validation: loose
|
||||
fields:
|
||||
route:
|
||||
type: parents
|
||||
label: PLUGIN_ADMIN.PARENT
|
||||
classes: fancy
|
||||
type: hidden
|
||||
|
||||
@@ -20,7 +20,7 @@ form:
|
||||
fields:
|
||||
xss_check:
|
||||
type: xss
|
||||
|
||||
|
||||
frontmatter:
|
||||
classes: frontmatter
|
||||
type: editor
|
||||
@@ -37,7 +37,7 @@ form:
|
||||
lint: true
|
||||
|
||||
content:
|
||||
type: markdown
|
||||
type: codemirror
|
||||
|
||||
header.media_order:
|
||||
type: pagemedia
|
||||
@@ -81,8 +81,6 @@ form:
|
||||
type: parents
|
||||
label: PLUGIN_ADMIN.PARENT
|
||||
classes: fancy
|
||||
validate:
|
||||
required: true
|
||||
|
||||
name:
|
||||
type: select
|
||||
|
||||
34
blueprints/admin/pages/root_raw.yaml
Normal file
34
blueprints/admin/pages/root_raw.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
rules:
|
||||
slug:
|
||||
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
|
||||
min: 1
|
||||
max: 200
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
|
||||
tabs:
|
||||
type: tabs
|
||||
active: 1
|
||||
|
||||
fields:
|
||||
content:
|
||||
type: tab
|
||||
title: PLUGIN_ADMIN.CONTENT
|
||||
|
||||
fields:
|
||||
frontmatter:
|
||||
classes: frontmatter
|
||||
type: editor
|
||||
label: PLUGIN_ADMIN.FRONTMATTER
|
||||
autofocus: true
|
||||
codemirror:
|
||||
mode: 'yaml'
|
||||
indentUnit: 4
|
||||
autofocus: true
|
||||
indentWithTabs: false
|
||||
lineNumbers: true
|
||||
styleActiveLine: true
|
||||
gutters: ['CodeMirror-lint-markers']
|
||||
lint: true
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Plugin\Admin;
|
||||
|
||||
use Grav\Common\Processors\ProcessorBase;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class Router extends ProcessorBase
|
||||
{
|
||||
public $id = 'admin_router';
|
||||
public $title = 'Admin Panel';
|
||||
|
||||
/**
|
||||
* Admin router.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$response = $handler->handle($request);
|
||||
$this->stopTimer();
|
||||
|
||||
// Never allow admin pages to be rendered in <frame>, <iframe>, <embed> or <object> for improved security.
|
||||
return $response->withHeader('X-Frame-Options', 'NONE');
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,16 @@ namespace Grav\Plugin\Admin;
|
||||
|
||||
use DateTime;
|
||||
use Grav\Common\Data;
|
||||
use Grav\Common\Data\Data as GravData;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Flex\Types\Users\UserObject;
|
||||
use Grav\Common\GPM\GPM;
|
||||
use Grav\Common\GPM\Licenses;
|
||||
use Grav\Common\GPM\Response;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Helpers\YamlLinter;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Language\LanguageCodes;
|
||||
use Grav\Common\Page\Collection;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
@@ -21,9 +25,18 @@ use Grav\Common\Session;
|
||||
use Grav\Common\Themes;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\User\Interfaces\UserCollectionInterface;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Common\User\User;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Acl\Action;
|
||||
use Grav\Framework\Acl\Permissions;
|
||||
use Grav\Framework\Collection\ArrayCollection;
|
||||
use Grav\Framework\Flex\Flex;
|
||||
use Grav\Framework\Flex\Interfaces\FlexInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Route\Route;
|
||||
use Grav\Framework\Route\RouteFactory;
|
||||
use Grav\Plugin\AdminPlugin;
|
||||
use Grav\Plugin\Login\Login;
|
||||
use Grav\Plugin\Login\TwoFactorAuth\TwoFactorAuth;
|
||||
use PicoFeed\Parser\MalformedXmlException;
|
||||
@@ -41,8 +54,9 @@ define('LOGIN_REDIRECT_COOKIE', 'grav-login-redirect');
|
||||
|
||||
class Admin
|
||||
{
|
||||
const MEDIA_PAGINATION_INTERVAL = 20;
|
||||
const TMP_COOKIE_NAME = 'tmp-admin-message';
|
||||
public const DEBUG = 1;
|
||||
public const MEDIA_PAGINATION_INTERVAL = 20;
|
||||
public const TMP_COOKIE_NAME = 'tmp-admin-message';
|
||||
|
||||
/** @var Grav */
|
||||
public $grav;
|
||||
@@ -74,8 +88,11 @@ class Admin
|
||||
/** @var bool */
|
||||
public $multilang;
|
||||
|
||||
/** @var string */
|
||||
public $language;
|
||||
|
||||
/** @var array */
|
||||
public $languages_enabled;
|
||||
public $languages_enabled = [];
|
||||
|
||||
/** @var Uri $uri */
|
||||
protected $uri;
|
||||
@@ -95,9 +112,6 @@ class Admin
|
||||
/** @var int */
|
||||
protected $pages_count;
|
||||
|
||||
/** @var array */
|
||||
protected $permissions;
|
||||
|
||||
/** @var bool */
|
||||
protected $load_additional_files_in_background = false;
|
||||
|
||||
@@ -126,29 +140,74 @@ class Admin
|
||||
$this->route = $route;
|
||||
$this->uri = $grav['uri'];
|
||||
$this->session = $grav['session'];
|
||||
$this->user = $grav['user'];
|
||||
$this->permissions = [];
|
||||
$language = $grav['language'];
|
||||
|
||||
/** @var FlexInterface|null $flex */
|
||||
$flex = $grav['flex_objects'] ?? null;
|
||||
|
||||
/** @var UserInterface $user */
|
||||
$user = $grav['user'];
|
||||
|
||||
// Convert old user to Flex User if Flex Objects plugin has been enabled.
|
||||
if ($flex && !$user instanceof FlexObjectInterface) {
|
||||
$managed = !method_exists($flex, 'isManaged') || $flex->isManaged('user-accounts');
|
||||
$directory = $managed ? $flex->getDirectory('user-accounts') : null;
|
||||
|
||||
/** @var UserObject|null $test */
|
||||
$test = $directory ? $directory->getObject($user->username) : null;
|
||||
if ($test) {
|
||||
$test = clone $test;
|
||||
$test->access = $user->access;
|
||||
$test->groups = $user->groups;
|
||||
$test->authenticated = $user->authenticated;
|
||||
$test->authorized = $user->authorized;
|
||||
$user = $test;
|
||||
}
|
||||
}
|
||||
$this->user = $user;
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
|
||||
$this->multilang = $language->enabled();
|
||||
|
||||
// Load utility class
|
||||
if ($language->enabled()) {
|
||||
$this->multilang = true;
|
||||
if ($this->multilang) {
|
||||
$this->language = $language->getActive() ?? '';
|
||||
$this->languages_enabled = (array)$this->grav['config']->get('system.languages.supported', []);
|
||||
|
||||
//Set the currently active language for the admin
|
||||
$language = $this->grav['uri']->param('lang');
|
||||
if (!$language) {
|
||||
if (!$this->session->admin_lang) {
|
||||
$this->session->admin_lang = $this->grav['language']->getLanguage();
|
||||
}
|
||||
$language = $this->session->admin_lang;
|
||||
$languageCode = $this->grav['uri']->param('lang');
|
||||
if (null === $languageCode && !$this->session->admin_lang) {
|
||||
$this->session->admin_lang = $language->getActive() ?? '';
|
||||
}
|
||||
$this->grav['language']->setActive($language ?: 'en');
|
||||
} else {
|
||||
$this->grav['language']->setActive('en');
|
||||
$this->multilang = false;
|
||||
$this->language = '';
|
||||
}
|
||||
|
||||
// Set admin route language.
|
||||
RouteFactory::setLanguage($this->language);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array $data
|
||||
*/
|
||||
public static function addDebugMessage(string $message, $data = [])
|
||||
{
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = Grav::instance()['debugger'];
|
||||
$debugger->addMessage($message, 'debug', $data);
|
||||
}
|
||||
|
||||
public static function contentEditor()
|
||||
{
|
||||
$options = [
|
||||
'default' => 'Default',
|
||||
'codemirror' => 'CodeMirror'
|
||||
];
|
||||
$event = new Event(['options' => &$options]);
|
||||
Grav::instance()->fireEvent('onAdminListContentEditors', $event);
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,7 +221,6 @@ class Admin
|
||||
|
||||
$path = Grav::instance()['locator']->findResource('plugins://admin/languages');
|
||||
|
||||
/** @var \DirectoryIterator $directory */
|
||||
foreach (new \DirectoryIterator($path) as $file) {
|
||||
if ($file->isDir() || $file->isDot() || Utils::startsWith($file->getFilename(), '.')) {
|
||||
continue;
|
||||
@@ -180,6 +238,11 @@ class Admin
|
||||
return $languages;
|
||||
}
|
||||
|
||||
public function getLanguage(): string
|
||||
{
|
||||
return $this->language ?: $this->grav['language']->getLanguage() ?: 'en';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the found configuration blueprints
|
||||
*
|
||||
@@ -273,21 +336,74 @@ class Admin
|
||||
*/
|
||||
public static function getLastPageRoute()
|
||||
{
|
||||
return Grav::instance()['session']->lastPageRoute ?: self::route();
|
||||
/** @var Session $session */
|
||||
$session = Grav::instance()['session'];
|
||||
$route = $session->lastPageRoute;
|
||||
if ($route) {
|
||||
return $route;
|
||||
}
|
||||
|
||||
/** @var Admin $admin */
|
||||
$admin = Grav::instance()['admin'];
|
||||
|
||||
return $admin->getCurrentRoute();
|
||||
}
|
||||
|
||||
public function getAdminRoute(string $path = '', $languageCode = null): Route
|
||||
{
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
$languageCode = $languageCode ?? $language->getActive();
|
||||
$languagePrefix = $languageCode ? '/' . $languageCode : '';
|
||||
|
||||
$root = $this->grav['uri']->rootUrl();
|
||||
$subRoute = rtrim($this->grav['pages']->base(), '/');
|
||||
$adminRoute = rtrim($this->grav['config']->get('plugins.admin.route'), '/');
|
||||
|
||||
$parts = [
|
||||
'path' => $path,
|
||||
'query' => '',
|
||||
'query_params' => [],
|
||||
'grav' => [
|
||||
// TODO: Make URL to be /admin/en, not /en/admin.
|
||||
'root' => preg_replace('`//+`', '/', $root . $subRoute . $languagePrefix . $adminRoute),
|
||||
'language' => '', //$languageCode,
|
||||
'route' => ltrim($path, '/'),
|
||||
'params' => ''
|
||||
],
|
||||
];
|
||||
|
||||
return RouteFactory::createFromParts($parts);
|
||||
}
|
||||
|
||||
public function adminUrl(string $route = '', $languageCode = null)
|
||||
{
|
||||
return $this->getAdminRoute($route, $languageCode)->toString(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static helper method to return current route.
|
||||
*
|
||||
* @return string
|
||||
* @deprecated 1.10 Use $admin->getCurrentRoute() instead
|
||||
*/
|
||||
public static function route()
|
||||
{
|
||||
$pages = Grav::instance()['pages'];
|
||||
$route = '/' . ltrim(Grav::instance()['admin']->route, '/');
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Admin 1.9.7, use $admin->getCurrentRoute() instead', E_USER_DEPRECATED);
|
||||
|
||||
$admin = Grav::instance()['admin'];
|
||||
|
||||
return $admin->getCurrentRoute();
|
||||
}
|
||||
|
||||
public function getCurrentRoute()
|
||||
{
|
||||
$pages = static::enablePages();
|
||||
|
||||
$route = '/' . ltrim($this->route, '/');
|
||||
|
||||
/** @var PageInterface $page */
|
||||
$page = $pages->dispatch($route);
|
||||
$page = $pages->find($route);
|
||||
$parent_route = null;
|
||||
if ($page) {
|
||||
/** @var PageInterface $parent */
|
||||
@@ -298,6 +414,73 @@ class Admin
|
||||
return $parent_route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the route stored in $this->redirect
|
||||
*
|
||||
* Route may or may not be prefixed by /en or /admin or /en/admin.
|
||||
*
|
||||
* @param string $redirect
|
||||
* @param int$redirectCode
|
||||
*/
|
||||
public function redirect($redirect, $redirectCode = 303)
|
||||
{
|
||||
// No redirect, do nothing.
|
||||
if (!$redirect) {
|
||||
return;
|
||||
}
|
||||
|
||||
Admin::DEBUG && Admin::addDebugMessage("Admin redirect: {$redirectCode} {$redirect}");
|
||||
|
||||
$redirect = '/' . ltrim(preg_replace('`//+`', '/', $redirect), '/');
|
||||
$base = $this->base;
|
||||
$root = Grav::instance()['uri']->rootUrl();
|
||||
if ($root === '/') {
|
||||
$root = '';
|
||||
}
|
||||
|
||||
$pattern = '`^((' . preg_quote($root, '`') . ')?(/[^/]+)?)' . preg_quote($base, '`') . '`ui';
|
||||
// Check if we already have an admin path: /admin, /en/admin, /root/admin or /root/en/admin.
|
||||
if (preg_match($pattern, $redirect)) {
|
||||
$redirect = preg_replace('|^' . preg_quote($root, '|') . '|', '', $redirect);
|
||||
|
||||
$this->grav->redirect($redirect, $redirectCode);
|
||||
}
|
||||
|
||||
if ($this->isMultilang()) {
|
||||
// Check if URL does not have language prefix.
|
||||
if (!Utils::pathPrefixedByLangCode($redirect)) {
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
|
||||
// Prefix path with language prefix: /en
|
||||
// TODO: Use /admin/en instead of /en/admin in the future.
|
||||
$redirect = $language->getLanguageURLPrefix($this->grav['session']->admin_lang) . $base . $redirect;
|
||||
} else {
|
||||
// TODO: Use /admin/en instead of /en/admin in the future.
|
||||
//$redirect = preg_replace('`^(/[^/]+)/admin`', '\\1', $redirect);
|
||||
|
||||
// Check if we already have language prefixed admin path: /en/admin
|
||||
$this->grav->redirect($redirect, $redirectCode);
|
||||
}
|
||||
} else {
|
||||
// TODO: Use /admin/en instead of /en/admin in the future.
|
||||
// Prefix path with /admin
|
||||
$redirect = $base . $redirect;
|
||||
}
|
||||
|
||||
$this->grav->redirect($redirect, $redirectCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if multilang is active
|
||||
*
|
||||
* @return bool True if multilang is active
|
||||
*/
|
||||
protected function isMultilang()
|
||||
{
|
||||
return count($this->grav['config']->get('system.languages.supported', [])) > 1;
|
||||
}
|
||||
|
||||
public static function getTempDir()
|
||||
{
|
||||
try {
|
||||
@@ -314,11 +497,12 @@ class Admin
|
||||
$files = [];
|
||||
$grav = Grav::instance();
|
||||
|
||||
$pages = $grav['pages'];
|
||||
$pages = static::enablePages();
|
||||
|
||||
$route = '/' . ltrim($grav['admin']->route, '/');
|
||||
|
||||
/** @var PageInterface $page */
|
||||
$page = $pages->dispatch($route);
|
||||
$page = $pages->find($route);
|
||||
$parent_route = null;
|
||||
if ($page) {
|
||||
$media = $page->media()->all();
|
||||
@@ -383,11 +567,15 @@ class Admin
|
||||
|
||||
// Check rate limit for both IP and user, but allow each IP a single try even if user is already rate limited.
|
||||
if ($rateLimiter->isRateLimited($ipKey, 'ip') || ($attempts && $rateLimiter->isRateLimited($userKey))) {
|
||||
Admin::DEBUG && Admin::addDebugMessage('Admin login: rate limit, redirecting', $credentials);
|
||||
|
||||
$this->setMessage(static::translate(['PLUGIN_LOGIN.TOO_MANY_LOGIN_ATTEMPTS', $rateLimiter->getInterval()]), 'error');
|
||||
|
||||
$this->grav->redirect('/');
|
||||
}
|
||||
|
||||
Admin::DEBUG && Admin::addDebugMessage('Admin login', $credentials);
|
||||
|
||||
// Fire Login process.
|
||||
$event = $login->login(
|
||||
$credentials,
|
||||
@@ -396,6 +584,8 @@ class Admin
|
||||
);
|
||||
$user = $event->getUser();
|
||||
|
||||
Admin::DEBUG && Admin::addDebugMessage('Admin login: user', $user);
|
||||
|
||||
if ($user->authenticated) {
|
||||
$rateLimiter->resetRateLimit($ipKey, 'ip')->resetRateLimit($userKey);
|
||||
if ($user->authorized) {
|
||||
@@ -420,7 +610,9 @@ class Admin
|
||||
$this->setMessage(static::translate($message), $event->getMessageType());
|
||||
}
|
||||
|
||||
$redirect = $event->getRedirect();
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->grav['pages'];
|
||||
$redirect = $pages->baseRoute() . $event->getRedirect();
|
||||
|
||||
$this->grav->redirect($redirect, $event->getRedirectCode());
|
||||
}
|
||||
@@ -430,6 +622,10 @@ class Admin
|
||||
*/
|
||||
public function twoFa($data, $post)
|
||||
{
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->grav['pages'];
|
||||
$baseRoute = $pages->baseRoute();
|
||||
|
||||
/** @var Login $login */
|
||||
$login = $this->grav['login'];
|
||||
|
||||
@@ -446,14 +642,16 @@ class Admin
|
||||
|
||||
$this->grav['session']->setFlashCookieObject(Admin::TMP_COOKIE_NAME, ['message' => $this->translate('PLUGIN_ADMIN.2FA_FAILED'), 'status' => 'error']);
|
||||
|
||||
$this->grav->redirect($this->uri->route(), 303);
|
||||
$this->grav->redirect($baseRoute . $this->uri->route(), 303);
|
||||
}
|
||||
|
||||
$this->setMessage($this->translate('PLUGIN_ADMIN.LOGIN_LOGGED_IN'), 'info');
|
||||
|
||||
$user->authorized = true;
|
||||
|
||||
$this->grav->redirect($post['redirect']);
|
||||
$redirect = $baseRoute . $post['redirect'];
|
||||
|
||||
$this->grav->redirect($redirect);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -541,7 +739,7 @@ class Admin
|
||||
} else {
|
||||
$languages = (array)$grav['language']->getDefault();
|
||||
}
|
||||
$languages = $grav['user']->authenticated ? [ $grav['user']->language ] : $languages;
|
||||
$languages = $grav['user']->authenticated ? [$grav['user']->language] : $languages;
|
||||
} else {
|
||||
$languages = (array)$languages;
|
||||
}
|
||||
@@ -582,8 +780,14 @@ class Admin
|
||||
{
|
||||
$action = (array)$action;
|
||||
|
||||
$user = $this->user;
|
||||
|
||||
foreach ($action as $a) {
|
||||
if ($this->user->authorize($a)) {
|
||||
// Ignore 'admin.super' if it's not the only value to be checked.
|
||||
if ($a === 'admin.super' && count($action) > 1 && $user instanceof FlexObjectInterface) {
|
||||
continue;
|
||||
}
|
||||
if ($user->authorize($a)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -682,11 +886,13 @@ class Admin
|
||||
|
||||
$file = File::instance($filename);
|
||||
|
||||
$pages = static::enablePages();
|
||||
|
||||
$obj = new \stdClass();
|
||||
$obj->title = $file->basename();
|
||||
$obj->path = $file->filename();
|
||||
$obj->file = $file;
|
||||
$obj->page = $this->grav['pages']->get(dirname($obj->path));
|
||||
$obj->page = $pages->get(dirname($obj->path));
|
||||
|
||||
$fileInfo = pathinfo($obj->title);
|
||||
$filename = str_replace(['@3x', '@2x'], '', $fileInfo['filename']);
|
||||
@@ -775,8 +981,7 @@ class Admin
|
||||
*/
|
||||
public function routes($unique = false)
|
||||
{
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->grav['pages'];
|
||||
$pages = static::enablePages();
|
||||
|
||||
if ($unique) {
|
||||
$routes = array_unique($pages->routes());
|
||||
@@ -795,7 +1000,8 @@ class Admin
|
||||
public function pagesCount()
|
||||
{
|
||||
if (!$this->pages_count) {
|
||||
$this->pages_count = count($this->grav['pages']->all());
|
||||
$pages = static::enablePages();
|
||||
$this->pages_count = count($pages->all());
|
||||
}
|
||||
|
||||
return $this->pages_count;
|
||||
@@ -804,21 +1010,35 @@ class Admin
|
||||
/**
|
||||
* Get all template types
|
||||
*
|
||||
* @param array|null $ignore
|
||||
* @return array
|
||||
*/
|
||||
public function types()
|
||||
public function types(?array $ignore = [])
|
||||
{
|
||||
return Pages::types();
|
||||
if (null === $ignore) {
|
||||
return AdminPlugin::pagesTypes();
|
||||
}
|
||||
|
||||
$types = Pages::types();
|
||||
|
||||
return $ignore ? array_diff_key($types, array_flip($ignore)) : $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all modular template types
|
||||
*
|
||||
* @param array|null $ignore
|
||||
* @return array
|
||||
*/
|
||||
public function modularTypes()
|
||||
public function modularTypes(?array $ignore = [])
|
||||
{
|
||||
return Pages::modularTypes();
|
||||
if (null === $ignore) {
|
||||
return AdminPlugin::pagesModularTypes();
|
||||
}
|
||||
|
||||
$types = Pages::modularTypes();
|
||||
|
||||
return $ignore ? array_diff_key($types, array_flip($ignore)) : $types;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -828,8 +1048,10 @@ class Admin
|
||||
*/
|
||||
public function accessLevels()
|
||||
{
|
||||
if (method_exists($this->grav['pages'], 'accessLevels')) {
|
||||
return $this->grav['pages']->accessLevels();
|
||||
$pages = static::enablePages();
|
||||
|
||||
if (method_exists($pages, 'accessLevels')) {
|
||||
return $pages->accessLevels();
|
||||
}
|
||||
|
||||
return [];
|
||||
@@ -1032,8 +1254,14 @@ class Admin
|
||||
*/
|
||||
public function latestPages($count = 10)
|
||||
{
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->grav['pages'];
|
||||
/** @var Flex $flex */
|
||||
$flex = $this->grav['flex_objects'] ?? null;
|
||||
$directory = $flex ? $flex->getDirectory('pages') : null;
|
||||
if ($directory) {
|
||||
return $directory->getIndex()->sort(['timestamp' => 'DESC'])->slice(0, $count);
|
||||
}
|
||||
|
||||
$pages = static::enablePages();
|
||||
|
||||
$latest = [];
|
||||
|
||||
@@ -1042,7 +1270,7 @@ class Admin
|
||||
}
|
||||
|
||||
foreach ($pages->routes() as $url => $path) {
|
||||
$page = $pages->dispatch($url, true);
|
||||
$page = $pages->find($url, true);
|
||||
if ($page && $page->routable()) {
|
||||
$latest[$page->route()] = ['modified' => $page->modified(), 'page' => $page];
|
||||
}
|
||||
@@ -1145,8 +1373,7 @@ class Admin
|
||||
if (function_exists('phpinfo')) {
|
||||
ob_start();
|
||||
phpinfo();
|
||||
$pinfo = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$pinfo = ob_get_clean();
|
||||
|
||||
$pinfo = preg_replace('%^.*<body>(.*)</body>.*$%ms', '$1', $pinfo);
|
||||
|
||||
@@ -1184,22 +1411,21 @@ class Admin
|
||||
];
|
||||
|
||||
if (!isset($guess[$date])) {
|
||||
$guess[$date] = 'd-m-Y H:i';
|
||||
foreach ($date_formats as $date_format) {
|
||||
foreach ($time_formats as $time_format) {
|
||||
if ($this->validateDate($date, "$date_format $time_format")) {
|
||||
$guess[$date] = "$date_format $time_format";
|
||||
$full_format = "{$date_format} {$time_format}";
|
||||
if ($this->validateDate($date, $full_format)) {
|
||||
$guess[$date] = $full_format;
|
||||
break 2;
|
||||
}
|
||||
if ($this->validateDate($date, "$time_format $date_format")) {
|
||||
$guess[$date] = "$time_format $date_format";
|
||||
$full_format = "{$time_format} {$date_format}";
|
||||
if ($this->validateDate($date, $full_format)) {
|
||||
$guess[$date] = $full_format;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($guess[$date])) {
|
||||
$guess[$date] = 'd-m-Y H:i';
|
||||
}
|
||||
}
|
||||
|
||||
return $guess[$date];
|
||||
@@ -1299,40 +1525,65 @@ class Admin
|
||||
* Gets the entire permissions array
|
||||
*
|
||||
* @return array
|
||||
* @deprecated 1.10 Use $grav['permissions']->getInstances() instead.
|
||||
*/
|
||||
public function getPermissions()
|
||||
{
|
||||
return $this->permissions;
|
||||
user_error(__METHOD__ . '() is deprecated since Admin 1.10, use $grav[\'permissions\']->getInstances() instead', E_USER_DEPRECATED);
|
||||
|
||||
$grav = $this->grav;
|
||||
/** @var Permissions $permissions */
|
||||
$permissions = $grav['permissions'];
|
||||
|
||||
return array_fill_keys(array_keys($permissions->getInstances()), 'boolean');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the entire permissions array
|
||||
*
|
||||
* @param array $permissions
|
||||
* @deprecated 1.10 Use PermissionsRegisterEvent::class event instead.
|
||||
*/
|
||||
public function setPermissions($permissions)
|
||||
{
|
||||
$this->permissions = $permissions;
|
||||
user_error(__METHOD__ . '() is deprecated since Admin 1.10, use PermissionsRegisterEvent::class event instead', E_USER_DEPRECATED);
|
||||
|
||||
$this->addPermissions($permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a permission to the permissions array
|
||||
*
|
||||
* @param array $permissions
|
||||
* @deprecated 1.10 Use RegisterPermissionsEvent::class event instead.
|
||||
*/
|
||||
public function addPermissions($permissions)
|
||||
{
|
||||
$this->permissions = array_merge($this->permissions, $permissions);
|
||||
user_error(__METHOD__ . '() is deprecated since Admin 1.10, use RegisterPermissionsEvent::class event instead', E_USER_DEPRECATED);
|
||||
|
||||
$grav = $this->grav;
|
||||
/** @var Permissions $object */
|
||||
$object = $grav['permissions'];
|
||||
foreach ($permissions as $name => $type) {
|
||||
if (!$object->hasAction($name)) {
|
||||
$action = new Action($name);
|
||||
$object->addAction($action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getNotifications($force = false)
|
||||
{
|
||||
$last_checked = null;
|
||||
$filename = $this->grav['locator']->findResource('user://data/notifications/' . md5($this->grav['user']->username) . YAML_EXT, true, true);
|
||||
$userStatus = $this->grav['locator']->findResource('user://data/notifications/' . $this->grav['user']->username . YAML_EXT, true, true);
|
||||
|
||||
$notifications_file = CompiledYamlFile::instance($filename);
|
||||
$notifications_content = (array)$notifications_file->content();
|
||||
|
||||
$userStatus_file = CompiledYamlFile::instance($userStatus);
|
||||
$userStatus_content = (array)$userStatus_file->content();
|
||||
|
||||
$last_checked = $notifications_content['last_checked'] ?? null;
|
||||
$notifications = $notifications_content['data'] ?? array();
|
||||
$timeout = $this->grav['config']->get('system.session.timeout', 1800);
|
||||
@@ -1361,16 +1612,16 @@ class Admin
|
||||
foreach ($notification['dependencies'] as $dependency => $constraints) {
|
||||
if ($dependency === 'grav') {
|
||||
if (!Semver::satisfies(GRAV_VERSION, $constraints)) {
|
||||
continue;
|
||||
continue 2;
|
||||
}
|
||||
} else {
|
||||
$packages = array_merge($this->plugins()->toArray(), $this->themes()->toArray());
|
||||
if (!isset($packages[$dependency])) {
|
||||
continue;
|
||||
continue 2;
|
||||
} else {
|
||||
$version = $packages[$dependency]['version'];
|
||||
if (!Semver::satisfies($version, $constraints)) {
|
||||
continue;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1395,6 +1646,27 @@ class Admin
|
||||
$notifications_file->save();
|
||||
}
|
||||
|
||||
foreach ($notifications as $location => $list) {
|
||||
$notifications[$location] = array_filter($list, function ($notification) use ($userStatus_content) {
|
||||
$element = $userStatus_content[$notification['id']] ?? null;
|
||||
if (isset($element)) {
|
||||
if (isset($notification['reappear_after'])) {
|
||||
$now = new \DateTime();
|
||||
$hidden_on = new \DateTime($element);
|
||||
$hidden_on->modify($notification['reappear_after']);
|
||||
|
||||
if ($now >= $hidden_on) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return $notifications;
|
||||
}
|
||||
@@ -1581,8 +1853,7 @@ class Admin
|
||||
*/
|
||||
public function getPage($path)
|
||||
{
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->grav['pages'];
|
||||
$pages = static::enablePages();
|
||||
|
||||
if ($path && $path[0] !== '/') {
|
||||
$path = "/{$path}";
|
||||
@@ -1591,7 +1862,7 @@ class Admin
|
||||
// Fix for entities in path causing looping...
|
||||
$path = urldecode($path);
|
||||
|
||||
$page = $path ? $pages->dispatch($path, true) : $pages->root();
|
||||
$page = $path ? $pages->find($path, true) : $pages->root();
|
||||
|
||||
if (!$page) {
|
||||
$slug = basename($path);
|
||||
@@ -1647,7 +1918,7 @@ class Admin
|
||||
$header['body_classes'] = 'modular';
|
||||
}
|
||||
|
||||
$name = $page->modular() ? str_replace('modular/', '', $data['name']) : $data['name'];
|
||||
$name = $page->isModule() ? str_replace('modular/', '', $data['name']) : $data['name'];
|
||||
$page->name($name . '.md');
|
||||
|
||||
// Fire new event to allow plugins to manipulate page frontmatter
|
||||
@@ -1671,8 +1942,7 @@ class Admin
|
||||
{
|
||||
$reports = new ArrayCollection();
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->grav['pages'];
|
||||
$pages = static::enablePages();
|
||||
|
||||
// Default to XSS Security Report
|
||||
$result = Security::detectXssFromPages($pages, true);
|
||||
@@ -1742,10 +2012,10 @@ class Admin
|
||||
|
||||
if (!$page_files) {
|
||||
$page_files = [];
|
||||
$pages = $this->grav['pages'];
|
||||
$pages = static::enablePages();
|
||||
|
||||
if ($param_page) {
|
||||
$page = $pages->dispatch($param_page);
|
||||
$page = $pages->find($param_page);
|
||||
|
||||
$page_files = $this->getFiles('images', $page, $page_files, $filtered);
|
||||
$page_files = $this->getFiles('videos', $page, $page_files, $filtered);
|
||||
@@ -1946,11 +2216,12 @@ class Admin
|
||||
*/
|
||||
public function pages()
|
||||
{
|
||||
/** @var Collection $pages */
|
||||
$pages = $this->grav['pages']->all();
|
||||
$pages = static::enablePages();
|
||||
|
||||
$collection = $pages->all();
|
||||
|
||||
$pagesWithFiles = [];
|
||||
foreach ($pages as $page) {
|
||||
foreach ($collection as $page) {
|
||||
if (count($page->media()->all())) {
|
||||
$pagesWithFiles[] = $page;
|
||||
}
|
||||
@@ -1959,6 +2230,43 @@ class Admin
|
||||
return $pagesWithFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Pages
|
||||
*/
|
||||
public static function enablePages()
|
||||
{
|
||||
static $pages;
|
||||
|
||||
if ($pages) {
|
||||
return $pages;
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
$admin = $grav['admin'];
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
$pages->enablePages();
|
||||
|
||||
// If page is null, the default page does not exist, and we cannot route to it
|
||||
$page = $pages->find('/', true);
|
||||
if ($page) {
|
||||
// Set original route for the home page.
|
||||
$home = '/' . trim($grav['config']->get('system.home.alias'), '/');
|
||||
|
||||
$page->route($home);
|
||||
}
|
||||
|
||||
$admin->routes = $pages->routes();
|
||||
|
||||
// Remove default route from routes.
|
||||
if (isset($admin->routes['/'])) {
|
||||
unset($admin->routes['/']);
|
||||
}
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return HTTP_REFERRER if set
|
||||
*
|
||||
@@ -1969,8 +2277,41 @@ class Admin
|
||||
return $_SERVER['HTTP_REFERER'] ?? null;
|
||||
}
|
||||
|
||||
public static function enablePages()
|
||||
|
||||
/**
|
||||
* Get Grav system log files
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLogFiles()
|
||||
{
|
||||
return Grav::instance()['pages'];
|
||||
$logs = new GravData(['grav.log' => 'Grav System Log', 'email.log' => 'Email Log']);
|
||||
Grav::instance()->fireEvent('onAdminLogFiles', new Event(['logs' => &$logs]));
|
||||
return $logs->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get changelog for a given GPM package based on slug
|
||||
*
|
||||
* @param null $slug
|
||||
* @return array
|
||||
*/
|
||||
public function getChangelog($slug = null)
|
||||
{
|
||||
$gpm = $this->gpm();
|
||||
$changelog = [];
|
||||
|
||||
if (!empty($slug)) {
|
||||
$package = $gpm->findPackage($slug);
|
||||
} else {
|
||||
$package = $gpm->grav;
|
||||
}
|
||||
|
||||
|
||||
if ($package) {
|
||||
$changelog = $package->getChangelog();
|
||||
}
|
||||
|
||||
return $changelog;
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,28 @@
|
||||
|
||||
namespace Grav\Plugin\Admin;
|
||||
|
||||
use Grav\Common\Cache;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Media\Interfaces\MediaInterface;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Media;
|
||||
use Grav\Common\Security;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Plugin;
|
||||
use Grav\Common\Theme;
|
||||
use Grav\Framework\Controller\Traits\ControllerResponseTrait;
|
||||
use Grav\Framework\RequestHandler\Exception\RequestException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\File\File;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
/**
|
||||
* Class AdminController
|
||||
@@ -24,6 +32,8 @@ use RocketTheme\Toolbox\File\File;
|
||||
*/
|
||||
class AdminBaseController
|
||||
{
|
||||
use ControllerResponseTrait;
|
||||
|
||||
/**
|
||||
* @var Grav
|
||||
*/
|
||||
@@ -107,16 +117,30 @@ class AdminBaseController
|
||||
|
||||
if (method_exists($this, $method)) {
|
||||
try {
|
||||
$success = $this->{$method}();
|
||||
$response = $this->{$method}();
|
||||
} catch (RequestException $e) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->grav['debugger'];
|
||||
$debugger->addException($e);
|
||||
|
||||
$response = $this->createErrorResponse($e);
|
||||
} catch (\RuntimeException $e) {
|
||||
$success = true;
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->grav['debugger'];
|
||||
$debugger->addException($e);
|
||||
|
||||
$response = true;
|
||||
$this->admin->setMessage($e->getMessage(), 'error');
|
||||
}
|
||||
} else {
|
||||
$success = $this->grav->fireEvent('onAdminTaskExecute',
|
||||
$response = $this->grav->fireEvent('onAdminTaskExecute',
|
||||
new Event(['controller' => $this, 'method' => $method]));
|
||||
}
|
||||
|
||||
if ($response instanceof ResponseInterface) {
|
||||
$this->close($response);
|
||||
}
|
||||
|
||||
// Grab redirect parameter.
|
||||
$redirect = $this->post['_redirect'] ?? null;
|
||||
unset($this->post['_redirect']);
|
||||
@@ -126,7 +150,7 @@ class AdminBaseController
|
||||
$this->setRedirect($redirect);
|
||||
}
|
||||
|
||||
return $success;
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function validateNonce()
|
||||
@@ -208,24 +232,23 @@ class AdminBaseController
|
||||
/**
|
||||
* Sends JSON response and terminates the call.
|
||||
*
|
||||
* @param array $response
|
||||
* @param array $json
|
||||
* @param int $code
|
||||
* @return bool
|
||||
*/
|
||||
protected function sendJsonResponse(array $response, $code = 200)
|
||||
protected function sendJsonResponse(array $json, $code = 200): void
|
||||
{
|
||||
// Make sure nothing extra gets written to the response.
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
// JSON response.
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/json');
|
||||
header('Cache-Control: no-cache, no-store, must-revalidate');
|
||||
$response = $this->createJsonResponse($json, $code);
|
||||
|
||||
echo json_encode($response);
|
||||
exit();
|
||||
$this->close($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
*/
|
||||
protected function close(ResponseInterface $response): void
|
||||
{
|
||||
$this->grav->close($response);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -376,6 +399,11 @@ class AdminBaseController
|
||||
return false;
|
||||
}
|
||||
|
||||
// Special Sanitization for SVG
|
||||
if (Utils::contains($mime, 'svg', false)) {
|
||||
Security::sanitizeSVG($tmp);
|
||||
}
|
||||
|
||||
$upload->file->tmp_name = $tmp;
|
||||
|
||||
// Retrieve the current session of the uploaded files for the field
|
||||
@@ -467,6 +495,21 @@ class AdminBaseController
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is allowed to perform the given task with its associated permissions.
|
||||
* Throws exception if the check fails.
|
||||
*
|
||||
* @param string $task The task to execute
|
||||
* @param array $permissions The permissions given
|
||||
* @throws RequestException
|
||||
*/
|
||||
public function checkTaskAuthorization($task = '', $permissions = [])
|
||||
{
|
||||
if (!$this->admin->authorize($permissions)) {
|
||||
throw new RequestException($this->getRequest(), $this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' ' . $task . '.', 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the permissions needed to access a given view
|
||||
*
|
||||
@@ -481,11 +524,11 @@ class AdminBaseController
|
||||
case 'configuration':
|
||||
case 'config':
|
||||
case 'system':
|
||||
$permissions[] = 'admin.configuration';
|
||||
$permissions[] = 'admin.configuration.system';
|
||||
break;
|
||||
case 'settings':
|
||||
case 'site':
|
||||
$permissions[] = 'admin.settings';
|
||||
$permissions[] = 'admin.configuration.site';
|
||||
break;
|
||||
case 'plugins':
|
||||
$permissions[] = 'admin.plugins';
|
||||
@@ -503,6 +546,9 @@ class AdminBaseController
|
||||
case 'pages':
|
||||
$permissions[] = 'admin.pages';
|
||||
break;
|
||||
default:
|
||||
$permissions[] = 'admin.configuration.' . $type;
|
||||
$permissions[] = 'admin.configuration_' . $type;
|
||||
}
|
||||
|
||||
return $permissions;
|
||||
@@ -609,48 +655,12 @@ class AdminBaseController
|
||||
|
||||
/**
|
||||
* Redirect to the route stored in $this->redirect
|
||||
*
|
||||
* Route may or may not be prefixed by /en or /admin or /en/admin.
|
||||
*/
|
||||
public function redirect()
|
||||
{
|
||||
if (!$this->redirect) {
|
||||
return;
|
||||
}
|
||||
|
||||
$base = $this->admin->base;
|
||||
$this->redirect = '/' . ltrim($this->redirect, '/');
|
||||
$multilang = $this->isMultilang();
|
||||
|
||||
$redirect = '';
|
||||
if ($multilang) {
|
||||
// if base path does not already contain the lang code, add it
|
||||
$langPrefix = '/' . $this->grav['session']->admin_lang;
|
||||
if (!Utils::startsWith($base, $langPrefix . '/')) {
|
||||
$base = $langPrefix . $base;
|
||||
}
|
||||
|
||||
// now the first 4 chars of base contain the lang code.
|
||||
// if redirect path already contains the lang code, and is != than the base lang code, then use redirect path as-is
|
||||
if (Utils::pathPrefixedByLangCode($base) && Utils::pathPrefixedByLangCode($this->redirect)
|
||||
&& !Utils::startsWith($this->redirect, $base)
|
||||
) {
|
||||
$redirect = $this->redirect;
|
||||
} else {
|
||||
if (!Utils::startsWith($this->redirect, $base)) {
|
||||
$this->redirect = $base . $this->redirect;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!Utils::startsWith($this->redirect, $base)) {
|
||||
$this->redirect = $base . $this->redirect;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$redirect) {
|
||||
$redirect = $this->redirect;
|
||||
}
|
||||
|
||||
$this->grav->redirect($redirect, $this->redirectCode);
|
||||
$this->admin->redirect($this->redirect, $this->redirectCode);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -817,6 +827,12 @@ class AdminBaseController
|
||||
|
||||
$media = $data->getMedia();
|
||||
} else {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
if ($locator->isStream($folder)) {
|
||||
$folder = $locator->findResource($folder);
|
||||
}
|
||||
|
||||
// Set destination
|
||||
$folder = Folder::getRelativePath(rtrim($folder, '/'));
|
||||
$folder = $this->admin->getPagePathFromToken($folder);
|
||||
@@ -974,10 +990,10 @@ class AdminBaseController
|
||||
$this->taskRemoveMedia($destination . '/' . $filename);
|
||||
|
||||
if ($page) {
|
||||
$keys = explode('.', preg_replace('/^header./', '', $field));
|
||||
$header = (array)$page->header();
|
||||
$keys = explode('.', preg_replace('/^header./', '', $field));
|
||||
$header = (array)$page->header();
|
||||
$data_path = implode('.', $keys);
|
||||
$data = Utils::getDotNotation($header, $data_path);
|
||||
$data = Utils::getDotNotation($header, $data_path);
|
||||
|
||||
if (isset($data[$path])) {
|
||||
unset($data[$path]);
|
||||
@@ -986,6 +1002,10 @@ class AdminBaseController
|
||||
}
|
||||
|
||||
$page->save();
|
||||
} elseif ($type === 'user') {
|
||||
$user = Grav::instance()['user'];
|
||||
unset($user->avatar);
|
||||
$user->save();
|
||||
} else {
|
||||
|
||||
$blueprint_prefix = $type === 'config' ? '' : $type . '.';
|
||||
@@ -1019,6 +1039,8 @@ class AdminBaseController
|
||||
}
|
||||
}
|
||||
|
||||
Cache::clearCache('invalidate');
|
||||
|
||||
$this->admin->json_response = [
|
||||
'status' => 'success',
|
||||
'message' => $this->admin::translate('PLUGIN_ADMIN.REMOVE_SUCCESSFUL')
|
||||
@@ -1116,4 +1138,32 @@ class AdminBaseController
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param string $type
|
||||
* @return $this
|
||||
*/
|
||||
protected function setMessage($message, $type = 'info')
|
||||
{
|
||||
$this->admin->setMessage($message, $type);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Config
|
||||
*/
|
||||
protected function getConfig(): Config
|
||||
{
|
||||
return $this->grav['config'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ServerRequestInterface
|
||||
*/
|
||||
protected function getRequest(): ServerRequestInterface
|
||||
{
|
||||
return $this->grav['request'];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
407
classes/plugin/Controllers/AbstractController.php
Normal file
407
classes/plugin/Controllers/AbstractController.php
Normal file
@@ -0,0 +1,407 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Grav\Plugin\Admin\Controllers;
|
||||
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Inflector;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Form\Interfaces\FormInterface;
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use Grav\Framework\RequestHandler\Exception\NotFoundException;
|
||||
use Grav\Framework\RequestHandler\Exception\PageExpiredException;
|
||||
use Grav\Framework\RequestHandler\Exception\RequestException;
|
||||
use Grav\Framework\Route\Route;
|
||||
use Grav\Framework\Session\SessionInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\Session\Message;
|
||||
|
||||
abstract class AbstractController implements RequestHandlerInterface
|
||||
{
|
||||
/** @var string */
|
||||
protected $nonce_action = 'admin-form';
|
||||
|
||||
/** @var string */
|
||||
protected $nonce_name = 'admin-nonce';
|
||||
|
||||
/** @var ServerRequestInterface */
|
||||
protected $request;
|
||||
|
||||
/** @var Grav */
|
||||
protected $grav;
|
||||
|
||||
/** @var string */
|
||||
protected $type;
|
||||
|
||||
/** @var string */
|
||||
protected $key;
|
||||
|
||||
/**
|
||||
* Handle request.
|
||||
*
|
||||
* Fires event: admin.[directory].[task|action].[command]
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @return Response
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$attributes = $request->getAttributes();
|
||||
$this->request = $request;
|
||||
$this->grav = $attributes['grav'] ?? Grav::instance();
|
||||
$this->type = $attributes['type'] ?? null;
|
||||
$this->key = $attributes['key'] ?? null;
|
||||
|
||||
/** @var Route $route */
|
||||
$route = $attributes['route'];
|
||||
$post = $this->getPost();
|
||||
|
||||
if ($this->isFormSubmit()) {
|
||||
$form = $this->getForm();
|
||||
$this->nonce_name = $attributes['nonce_name'] ?? $form->getNonceName();
|
||||
$this->nonce_action = $attributes['nonce_action'] ?? $form->getNonceAction();
|
||||
}
|
||||
|
||||
try {
|
||||
$task = $request->getAttribute('task') ?? $post['task'] ?? $route->getParam('task');
|
||||
if ($task) {
|
||||
if (empty($attributes['forwarded'])) {
|
||||
$this->checkNonce($task);
|
||||
}
|
||||
$type = 'task';
|
||||
$command = $task;
|
||||
} else {
|
||||
$type = 'action';
|
||||
$command = $request->getAttribute('action') ?? $post['action'] ?? $route->getParam('action') ?? 'display';
|
||||
}
|
||||
$command = strtolower($command);
|
||||
|
||||
$event = new Event(
|
||||
[
|
||||
'controller' => $this,
|
||||
'response' => null
|
||||
]
|
||||
);
|
||||
|
||||
$this->grav->fireEvent("admin.{$this->type}.{$type}.{$command}", $event);
|
||||
|
||||
$response = $event['response'];
|
||||
if (!$response) {
|
||||
/** @var Inflector $inflector */
|
||||
$inflector = $this->grav['inflector'];
|
||||
$method = $type . $inflector::camelize($command);
|
||||
if ($method && method_exists($this, $method)) {
|
||||
$response = $this->{$method}($request);
|
||||
} else {
|
||||
throw new NotFoundException($request);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->grav['debugger'];
|
||||
$debugger->addException($e);
|
||||
|
||||
$response = $this->createErrorResponse($e);
|
||||
}
|
||||
|
||||
if ($response instanceof Response) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $this->createJsonResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get request.
|
||||
*
|
||||
* @return ServerRequestInterface
|
||||
*/
|
||||
public function getRequest(): ServerRequestInterface
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $name
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPost(string $name = null, $default = null)
|
||||
{
|
||||
$body = $this->request->getParsedBody();
|
||||
|
||||
if ($name) {
|
||||
return $body[$name] ?? $default;
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a form has been submitted.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFormSubmit(): bool
|
||||
{
|
||||
return (bool)$this->getPost('__form-name__');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get form.
|
||||
*
|
||||
* @param string|null $type
|
||||
* @return FormInterface
|
||||
*/
|
||||
public function getForm(string $type = null): FormInterface
|
||||
{
|
||||
$object = $this->getObject();
|
||||
if (!$object) {
|
||||
throw new \RuntimeException('Not Found', 404);
|
||||
}
|
||||
|
||||
$formName = $this->getPost('__form-name__');
|
||||
$uniqueId = $this->getPost('__unique_form_id__') ?: $formName;
|
||||
|
||||
$form = $object->getForm($type ?? 'edit');
|
||||
if ($uniqueId) {
|
||||
$form->setUniqueId($uniqueId);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FlexObjectInterface
|
||||
*/
|
||||
abstract public function getObject();
|
||||
|
||||
/**
|
||||
* Get Grav instance.
|
||||
*
|
||||
* @return Grav
|
||||
*/
|
||||
public function getGrav(): Grav
|
||||
{
|
||||
return $this->grav;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session.
|
||||
*
|
||||
* @return SessionInterface
|
||||
*/
|
||||
public function getSession(): SessionInterface
|
||||
{
|
||||
return $this->getGrav()['session'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the current admin page.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function createDisplayResponse(): ResponseInterface
|
||||
{
|
||||
return new Response(418);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create custom HTML response.
|
||||
*
|
||||
* @param string $content
|
||||
* @param int $code
|
||||
* @return Response
|
||||
*/
|
||||
public function createHtmlResponse(string $content, int $code = null): ResponseInterface
|
||||
{
|
||||
return new Response($code ?: 200, [], $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create JSON response.
|
||||
*
|
||||
* @param array $content
|
||||
* @return Response
|
||||
*/
|
||||
public function createJsonResponse(array $content): ResponseInterface
|
||||
{
|
||||
$code = $content['code'] ?? 200;
|
||||
if ($code >= 301 && $code <= 307) {
|
||||
$code = 200;
|
||||
}
|
||||
|
||||
return new Response($code, ['Content-Type' => 'application/json'], json_encode($content));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create redirect response.
|
||||
*
|
||||
* @param string $url
|
||||
* @param int $code
|
||||
* @return Response
|
||||
*/
|
||||
public function createRedirectResponse(string $url, int $code = null): ResponseInterface
|
||||
{
|
||||
if (null === $code || $code < 301 || $code > 307) {
|
||||
$code = $this->grav['config']->get('system.pages.redirect_default_code', 302);
|
||||
}
|
||||
|
||||
$accept = $this->getAccept(['application/json', 'text/html']);
|
||||
|
||||
if ($accept === 'application/json') {
|
||||
return $this->createJsonResponse(['code' => $code, 'status' => 'redirect', 'redirect' => $url]);
|
||||
}
|
||||
|
||||
return new Response($code, ['Location' => $url]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create error response.
|
||||
*
|
||||
* @param \Exception $exception
|
||||
* @return Response
|
||||
*/
|
||||
public function createErrorResponse(\Exception $exception): ResponseInterface
|
||||
{
|
||||
$validCodes = [
|
||||
400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418,
|
||||
422, 423, 424, 425, 426, 428, 429, 431, 451, 500, 501, 502, 503, 504, 505, 506, 507, 508, 511
|
||||
];
|
||||
|
||||
if ($exception instanceof RequestException) {
|
||||
$code = $exception->getHttpCode();
|
||||
$reason = $exception->getHttpReason();
|
||||
} else {
|
||||
$code = $exception->getCode();
|
||||
$reason = null;
|
||||
}
|
||||
|
||||
if (!in_array($code, $validCodes, true)) {
|
||||
$code = 500;
|
||||
}
|
||||
|
||||
$message = $exception->getMessage();
|
||||
$response = [
|
||||
'code' => $code,
|
||||
'status' => 'error',
|
||||
'message' => $message
|
||||
];
|
||||
|
||||
$accept = $this->getAccept(['application/json', 'text/html']);
|
||||
|
||||
if ($accept === 'text/html') {
|
||||
$method = $this->getRequest()->getMethod();
|
||||
|
||||
// On POST etc, redirect back to the previous page.
|
||||
if ($method !== 'GET' && $method !== 'HEAD') {
|
||||
$this->setMessage($message, 'error');
|
||||
$referer = $this->request->getHeaderLine('Referer');
|
||||
return $this->createRedirectResponse($referer, 303);
|
||||
}
|
||||
|
||||
// TODO: improve error page
|
||||
return $this->createHtmlResponse($response['message']);
|
||||
}
|
||||
|
||||
return new Response($code, ['Content-Type' => 'application/json'], json_encode($response), '1.1', $reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a string.
|
||||
*
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
public function translate(string $string): string
|
||||
{
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
|
||||
return $language->translate($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set message to be shown in the admin.
|
||||
*
|
||||
* @param string $message
|
||||
* @param string $type
|
||||
* @return $this
|
||||
*/
|
||||
public function setMessage($message, $type = 'info')
|
||||
{
|
||||
/** @var Message $messages */
|
||||
$messages = $this->grav['messages'];
|
||||
$messages->add($message, $type);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if request nonce is valid.
|
||||
*
|
||||
* @param string $task
|
||||
* @throws PageExpiredException If nonce is not valid.
|
||||
*/
|
||||
protected function checkNonce(string $task): void
|
||||
{
|
||||
$nonce = null;
|
||||
|
||||
if (\in_array(strtoupper($this->request->getMethod()), ['POST', 'PUT', 'PATCH', 'DELETE'])) {
|
||||
$nonce = $this->getPost($this->nonce_name);
|
||||
}
|
||||
|
||||
if (!$nonce) {
|
||||
$nonce = $this->grav['uri']->param($this->nonce_name);
|
||||
}
|
||||
|
||||
if (!$nonce) {
|
||||
$nonce = $this->grav['uri']->query($this->nonce_name);
|
||||
}
|
||||
|
||||
if (!$nonce || !Utils::verifyNonce($nonce, $this->nonce_action)) {
|
||||
throw new PageExpiredException($this->request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the best matching mime type for the request.
|
||||
*
|
||||
* @param string[] $compare
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getAccept(array $compare): ?string
|
||||
{
|
||||
$accepted = [];
|
||||
foreach ($this->request->getHeader('Accept') as $accept) {
|
||||
foreach (explode(',', $accept) as $item) {
|
||||
if (!$item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$split = explode(';q=', $item);
|
||||
$mime = array_shift($split);
|
||||
$priority = array_shift($split) ?? 1.0;
|
||||
|
||||
$accepted[$mime] = $priority;
|
||||
}
|
||||
}
|
||||
|
||||
arsort($accepted);
|
||||
|
||||
// TODO: add support for image/* etc
|
||||
$list = array_intersect($compare, array_keys($accepted));
|
||||
if (!$list && (isset($accepted['*/*']) || isset($accepted['*']))) {
|
||||
return reset($compare) ?: null;
|
||||
}
|
||||
|
||||
return reset($list) ?: null;
|
||||
}
|
||||
}
|
||||
59
classes/plugin/Router.php
Normal file
59
classes/plugin/Router.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Plugin\Admin;
|
||||
|
||||
use Grav\Common\Processors\ProcessorBase;
|
||||
use Grav\Framework\Route\Route;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class Router extends ProcessorBase
|
||||
{
|
||||
public $id = 'admin_router';
|
||||
public $title = 'Admin Panel';
|
||||
|
||||
/**
|
||||
* Handle routing to the dashboard, group and build objects.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
|
||||
$context = $request->getAttributes();
|
||||
|
||||
/** @var Route $route */
|
||||
$route = $context['route'];
|
||||
$normalized = mb_strtolower(trim($route->getRoute(), '/'));
|
||||
$parts = explode('/', $normalized);
|
||||
array_shift($parts);
|
||||
$key = array_shift($parts);
|
||||
$path = implode('/', $parts);
|
||||
|
||||
$request = $request->withAttribute('admin', ['path' => $path, 'parts' => $parts]);
|
||||
|
||||
$response = null;
|
||||
/*
|
||||
if ($key === '__TODO__') {
|
||||
|
||||
$controller = new TodoController();
|
||||
|
||||
$response = $controller->handle($request);
|
||||
}
|
||||
*/
|
||||
|
||||
if (!$response) {
|
||||
// Fallback to the old admin behavior.
|
||||
$response = $handler->handle($request);
|
||||
}
|
||||
|
||||
$this->stopTimer();
|
||||
|
||||
// Never allow admin pages to be rendered in <frame>, <iframe>, <embed> or <object> for improved security.
|
||||
return $response->withHeader('X-Frame-Options', 'NONE');
|
||||
}
|
||||
}
|
||||
55
classes/plugin/ScssCompiler.php
Normal file
55
classes/plugin/ScssCompiler.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace Grav\Plugin\Admin;
|
||||
|
||||
use ScssPhp\ScssPhp\Compiler;
|
||||
|
||||
class ScssCompiler
|
||||
{
|
||||
protected $compiler;
|
||||
|
||||
public function compiler()
|
||||
{
|
||||
if ($this->compiler === null) {
|
||||
$this->reset();
|
||||
}
|
||||
return $this->compiler;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->compiler = new Compiler();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setVariables(array $variables)
|
||||
{
|
||||
$this->compiler()->setVariables($variables);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setImportPaths(array $paths)
|
||||
{
|
||||
$this->compiler()->setImportPaths($paths);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function compile(string $input_file, string $output_file)
|
||||
{
|
||||
$input = file_get_contents($input_file);
|
||||
$output = $this->compiler()->compile($input);
|
||||
file_put_contents($output_file, $output);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function compileAll(array $input_paths, string $output_file)
|
||||
{
|
||||
$input = '';
|
||||
foreach ($input_paths as $input_file) {
|
||||
$input .= trim(file_get_contents($input_file)) . "\n\n";
|
||||
}
|
||||
$output = $this->compiler()->compile($input);
|
||||
file_put_contents($output_file, $output);
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
52
classes/plugin/ScssList.php
Normal file
52
classes/plugin/ScssList.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Plugin\Admin;
|
||||
|
||||
class ScssList
|
||||
{
|
||||
/** @var string[] */
|
||||
protected $list = [];
|
||||
|
||||
/**
|
||||
* ScssList constructor.
|
||||
* @param string|null $item
|
||||
*/
|
||||
public function __construct($item = null)
|
||||
{
|
||||
if ($item) {
|
||||
$this->add($item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
return $this->list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $item
|
||||
* @return void
|
||||
*/
|
||||
public function add($item): void
|
||||
{
|
||||
if ($item) {
|
||||
$this->list[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $item
|
||||
* @return void
|
||||
*/
|
||||
public function remove($item): void
|
||||
{
|
||||
$pos = array_search($item, $this->list, true);
|
||||
if ($pos) {
|
||||
unset($this->list[$pos]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,21 +2,23 @@
|
||||
|
||||
namespace Grav\Plugin\Admin\Twig;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Yaml;
|
||||
use Grav\Common\Language\Language;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
use Grav\Plugin\Admin\Admin;
|
||||
|
||||
class AdminTwigExtension extends \Twig_Extension
|
||||
class AdminTwigExtension extends AbstractExtension
|
||||
{
|
||||
/**
|
||||
* @var Grav
|
||||
*/
|
||||
/** @var Grav */
|
||||
protected $grav;
|
||||
|
||||
/**
|
||||
* @var Language $lang
|
||||
*/
|
||||
/** @var Language $lang */
|
||||
protected $lang;
|
||||
|
||||
public function __construct()
|
||||
@@ -25,22 +27,25 @@ class AdminTwigExtension extends \Twig_Extension
|
||||
$this->lang = $this->grav['user']->language;
|
||||
}
|
||||
|
||||
public function getFilters()
|
||||
public function getFilters(): array
|
||||
{
|
||||
return [
|
||||
new \Twig_SimpleFilter('tu', [$this, 'tuFilter']),
|
||||
new \Twig_SimpleFilter('toYaml', [$this, 'toYamlFilter']),
|
||||
new \Twig_SimpleFilter('fromYaml', [$this, 'fromYamlFilter']),
|
||||
new \Twig_SimpleFilter('adminNicetime', [$this, 'adminNicetimeFilter']),
|
||||
new \Twig_SimpleFilter('nested', [$this, 'nestedFilter']),
|
||||
new TwigFilter('tu', [$this, 'tuFilter']),
|
||||
new TwigFilter('toYaml', [$this, 'toYamlFilter']),
|
||||
new TwigFilter('fromYaml', [$this, 'fromYamlFilter']),
|
||||
new TwigFilter('adminNicetime', [$this, 'adminNicetimeFilter']),
|
||||
new TwigFilter('nested', [$this, 'nestedFilter']),
|
||||
new TwigFilter('flatten', [$this, 'flattenFilter']),
|
||||
];
|
||||
}
|
||||
|
||||
public function getFunctions()
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new \Twig_SimpleFunction('getPageUrl', [$this, 'getPageUrl'], ['needs_context' => true]),
|
||||
new \Twig_SimpleFunction('clone', [$this, 'cloneFunc']),
|
||||
new TwigFunction('admin_route', [$this, 'adminRouteFunc']),
|
||||
new TwigFunction('getPageUrl', [$this, 'getPageUrl']),
|
||||
new TwigFunction('clone', [$this, 'cloneFunc']),
|
||||
new TwigFunction('data', [$this, 'dataFunc']),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -61,26 +66,30 @@ class AdminTwigExtension extends \Twig_Extension
|
||||
return $current;
|
||||
}
|
||||
|
||||
public function flattenFilter($array)
|
||||
{
|
||||
return Utils::arrayFlattenDotNotation($array);
|
||||
}
|
||||
|
||||
public function cloneFunc($obj)
|
||||
{
|
||||
return clone $obj;
|
||||
}
|
||||
|
||||
public function getPageUrl($context, PageInterface $page)
|
||||
public function adminRouteFunc(string $route = '', string $languageCode = null)
|
||||
{
|
||||
$page_route = trim($page->rawRoute(), '/');
|
||||
$page_lang = $page->language();
|
||||
$base_url = $context['base_url'];
|
||||
$base_url_simple = $context['base_url_simple'];
|
||||
$admin_lang = Grav::instance()['session']->admin_lang ?: 'en';
|
||||
/** @var Admin $admin */
|
||||
$admin = Grav::instance()['admin'];
|
||||
|
||||
if ($page_lang && $page_lang !== $admin_lang) {
|
||||
$page_url = $base_url_simple . '/' . $page_lang . '/' . $context['admin_route'] . '/pages/' . $page_route;
|
||||
} else {
|
||||
$page_url = $base_url . '/pages/' . $page_route;
|
||||
}
|
||||
return $admin->getAdminRoute($route, $languageCode)->toString(true);
|
||||
}
|
||||
|
||||
return $page_url;
|
||||
public function getPageUrl(PageInterface $page)
|
||||
{
|
||||
/** @var Admin $admin */
|
||||
$admin = Grav::instance()['admin'];
|
||||
|
||||
return $admin->getAdminRoute('/pages' . $page->rawRoute(), $page->language())->toString(true);
|
||||
}
|
||||
|
||||
public static function tuFilter()
|
||||
@@ -115,4 +124,8 @@ class AdminTwigExtension extends \Twig_Extension
|
||||
return Grav::instance()['admin']->adminNiceTime($date, $long_strings);
|
||||
}
|
||||
|
||||
public function dataFunc(array $data, $blueprints = null)
|
||||
{
|
||||
return new Data($data, $blueprints);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Plugin\Admin;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\User\Interfaces\UserCollectionInterface;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
|
||||
/**
|
||||
* Admin utils class
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
class Utils
|
||||
{
|
||||
/**
|
||||
* Matches an email to a user
|
||||
*
|
||||
* @param string $email
|
||||
*
|
||||
* @return UserInterface
|
||||
*/
|
||||
public static function findUserByEmail(string $email)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var UserCollectionInterface $users */
|
||||
$users = $grav['accounts'];
|
||||
|
||||
return $users->find($email, ['email']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a slug of the given string
|
||||
*
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
public static function slug(string $str)
|
||||
{
|
||||
if (function_exists('transliterator_transliterate')) {
|
||||
$str = transliterator_transliterate('Any-Latin; NFD; [:Nonspacing Mark:] Remove; NFC; [:Punctuation:] Remove;', $str);
|
||||
} else {
|
||||
$str = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str);
|
||||
}
|
||||
|
||||
$str = strtolower($str);
|
||||
$str = preg_replace('/[-\s]+/', '-', $str);
|
||||
$str = preg_replace('/[^a-z0-9-]/i', '', $str);
|
||||
$str = trim($str, '-');
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
<?php
|
||||
|
||||
namespace Grav\Plugin\Admin;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\User\Interfaces\UserCollectionInterface;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
|
||||
/**
|
||||
* Admin utils class
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
class Utils
|
||||
{
|
||||
/**
|
||||
* Matches an email to a user
|
||||
*
|
||||
* @param string $email
|
||||
*
|
||||
* @return UserInterface
|
||||
*/
|
||||
public static function findUserByEmail(string $email)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var UserCollectionInterface $users */
|
||||
$users = $grav['accounts'];
|
||||
|
||||
return $users->find($email, ['email']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a slug of the given string
|
||||
*
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
public static function slug(string $str)
|
||||
{
|
||||
if (function_exists('transliterator_transliterate')) {
|
||||
$str = transliterator_transliterate('Any-Latin; NFD; [:Nonspacing Mark:] Remove; NFC; [:Punctuation:] Remove;', $str);
|
||||
} else {
|
||||
$str = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str);
|
||||
}
|
||||
|
||||
$str = strtolower($str);
|
||||
$str = preg_replace('/[-\s]+/', '-', $str);
|
||||
$str = preg_replace('/[^a-z0-9-]/i', '', $str);
|
||||
$str = trim($str, '-');
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
91
classes/plugin/WhiteLabel.php
Normal file
91
classes/plugin/WhiteLabel.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
namespace Grav\Plugin\Admin;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Framework\File\File;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class WhiteLabel
|
||||
{
|
||||
protected $grav;
|
||||
protected $scss;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->grav = Grav::instance();
|
||||
$this->scss = new ScssCompiler();
|
||||
}
|
||||
|
||||
public function compilePresetScss($config, $options = [
|
||||
'input' => 'plugin://admin/themes/grav/scss/preset.scss',
|
||||
'output' => 'asset://admin-preset.css'
|
||||
])
|
||||
{
|
||||
if (is_array($config)) {
|
||||
$color_scheme = $config['color_scheme'];
|
||||
} else {
|
||||
$color_scheme = $config->get('whitelabel.color_scheme');
|
||||
}
|
||||
|
||||
if ($color_scheme) {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
|
||||
// Use ScssList object to make it easier ot handle in event
|
||||
$scss_list = new ScssList($locator->findResource($options['input']));
|
||||
$output_css = $locator->findResource(($options['output']), true, true);
|
||||
|
||||
Grav::instance()->fireEvent('onAdminCompilePresetSCSS', new Event(['scss' => $scss_list]));
|
||||
|
||||
// Convert bak to regular array now we have run the event
|
||||
$input_scss = $scss_list->all();
|
||||
|
||||
$imports = [$locator->findResource('plugin://admin/themes/grav/scss')];
|
||||
foreach ($input_scss as $scss) {
|
||||
$input_path = dirname($scss);
|
||||
if (!in_array($input_path, $imports)) {
|
||||
$imports[] = $input_path;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$compiler = $this->scss->reset();
|
||||
|
||||
$compiler->setVariables($color_scheme['colors'] + $color_scheme['accents']);
|
||||
$compiler->setImportPaths($imports);
|
||||
$compiler->compileAll($input_scss, $output_css);
|
||||
} catch (\Exception $e) {
|
||||
return [false, $e->getMessage()];
|
||||
}
|
||||
|
||||
|
||||
return [true, 'Recompiled successfully'];
|
||||
|
||||
}
|
||||
return [false, ' Could not be recompiled, missing color scheme...'];
|
||||
}
|
||||
|
||||
public function exportPresetScsss($config, $location = 'asset://admin-theme-export.yaml')
|
||||
{
|
||||
|
||||
if (isset($config['color_scheme'])) {
|
||||
|
||||
$color_scheme = $config['color_scheme'];
|
||||
|
||||
$body = Yaml::dump($color_scheme);
|
||||
|
||||
$file = new File($location);
|
||||
$file->save($body);
|
||||
// todo: handle errors/exceptions?
|
||||
|
||||
return [true, 'File created successfully'];
|
||||
|
||||
} else {
|
||||
return [false, ' Could not export, missing color scheme...'];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,28 +20,31 @@
|
||||
"docs": "https://github.com/getgrav/grav-plugin-admin/blob/master/README.md"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1.3",
|
||||
"php": "^7.3.6 || ^8.0",
|
||||
"ext-json": "*",
|
||||
"composer/semver": "^1.4",
|
||||
"p3k/picofeed": "@stable"
|
||||
"p3k/picofeed": "@stable",
|
||||
"scssphp/scssphp": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^2.4",
|
||||
"fzaninotto/faker": "^1.8",
|
||||
"symfony/yaml": "~4.1",
|
||||
"symfony/console": "~4.1",
|
||||
"symfony/finder": "~4.1",
|
||||
"symfony/event-dispatcher": "~4.1"
|
||||
"symfony/yaml": "~4.4",
|
||||
"symfony/console": "~4.4",
|
||||
"symfony/finder": "~4.4",
|
||||
"symfony/event-dispatcher": "~4.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Grav\\Plugin\\Admin\\": "classes/plugin"
|
||||
},
|
||||
"classmap": [
|
||||
"classes/",
|
||||
"admin.php"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.1.3"
|
||||
"php": "7.3.6"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
458
composer.lock
generated
458
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -34,7 +34,6 @@ PLUGIN_ADMIN:
|
||||
NEXT: "Next"
|
||||
PREVIOUS: "Previous"
|
||||
ADD_PAGE: "Add Page"
|
||||
ADD_MODULAR: "Add Modular"
|
||||
MOVE: "Move"
|
||||
DELETE: "Delete"
|
||||
UNSET: "Unset"
|
||||
@@ -44,7 +43,6 @@ PLUGIN_ADMIN:
|
||||
EXPERT: "Expert"
|
||||
EXPAND_ALL: "Expand All"
|
||||
COLLAPSE_ALL: "Collapse All"
|
||||
SECURE_DELETE_DESC: "Type DELETE and confirm"
|
||||
ERROR: "Error"
|
||||
CLOSE: "Close"
|
||||
CANCEL: "Cancel"
|
||||
@@ -55,6 +53,7 @@ PLUGIN_ADMIN:
|
||||
MODAL_CHANGED_DETECTED_DESC: "You have unsaved changes. Are you sure you want to leave without saving?"
|
||||
MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Confirmation Required"
|
||||
MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Are you sure you want to delete this file? This action cannot be undone."
|
||||
MODAL_UPDATE_GRAV_CONFIRMATION_REQUIRED_DESC: "You are about to upgrade Grav to the latest version available. Would you like to continue?"
|
||||
ADD_FILTERS: "Add Filters"
|
||||
SEARCH_PAGES: "Search Pages"
|
||||
VERSION: "Version"
|
||||
@@ -112,6 +111,7 @@ PLUGIN_ADMIN:
|
||||
BACKUP_DATE: "Backup Date"
|
||||
|
||||
STATISTICS: "Statistics"
|
||||
VIEWS_STATISTICS: "Page View Statistics"
|
||||
TODAY: "Today"
|
||||
WEEK: "Week"
|
||||
MONTH: "Month"
|
||||
@@ -305,11 +305,9 @@ PLUGIN_ADMIN:
|
||||
FOLDER: "Folder"
|
||||
ASCENDING: "Ascending"
|
||||
DESCENDING: "Descending"
|
||||
ADD_MODULAR_CONTENT: "Add Modular Content"
|
||||
PAGE_TITLE: "Page Title"
|
||||
PAGE_TITLE_HELP: "The title of the page"
|
||||
PAGE: "Page"
|
||||
MODULAR_TEMPLATE: "Modular Template"
|
||||
FRONTMATTER: "Frontmatter"
|
||||
FILENAME: "Filename"
|
||||
PARENT_PAGE: "Parent Page"
|
||||
@@ -342,8 +340,6 @@ PLUGIN_ADMIN:
|
||||
LANGUAGES: "Languages"
|
||||
SUPPORTED: "Supported"
|
||||
SUPPORTED_HELP: "Comma separated list of 2 letter language codes (for example 'en,fr,de')"
|
||||
TRANSLATIONS_ENABLED: "Translations enabled"
|
||||
TRANSLATIONS_ENABLED_HELP: "Support translations in Grav, plugins and extensions"
|
||||
TRANSLATIONS_FALLBACK: "Translations fallback"
|
||||
TRANSLATIONS_FALLBACK_HELP: "Fallback through supported translations if active language doesn't exist"
|
||||
ACTIVE_LANGUAGE_IN_SESSION: "Active language in session"
|
||||
@@ -443,6 +439,8 @@ PLUGIN_ADMIN:
|
||||
CACHE_ALL_HELP: "Run all images through Grav's cache system even if they have no media manipulations"
|
||||
IMAGES_DEBUG: "Image debug watermark"
|
||||
IMAGES_DEBUG_HELP: "Show an overlay over images indicating the pixel depth of the image when working with retina for example"
|
||||
IMAGES_LOADING: "Image loading behavior"
|
||||
IMAGES_LOADING_HELP: "The loading attribute allows a browser to defer loading offscreen images and iframes until users scroll near them. loading supports three values: auto, lazy, eager"
|
||||
IMAGES_SEOFRIENDLY: "SEO-Friendly Image names"
|
||||
IMAGES_SEOFRIENDLY_HELP: "When enabled, the image name is displayed first, then a smaller hash to reflect processed operations"
|
||||
UPLOAD_LIMIT: "File upload limit"
|
||||
@@ -505,6 +503,8 @@ PLUGIN_ADMIN:
|
||||
PLUGIN_STATUS: "Plugin status"
|
||||
INCLUDE_DEFAULT_LANG: "Include default language"
|
||||
INCLUDE_DEFAULT_LANG_HELP: "This will prepend all URLs in the default language with the default language. e.g. `/en/blog/my-post`"
|
||||
INCLUDE_DEFAULT_LANG_FILE_EXTENSION: "Include default language in file extension"
|
||||
INCLUDE_DEFAULT_LANG_HELP_FILE_EXTENSION: "If enabled, it will prepend the default language to the file extension (e.g. `.en.md`). Disable it to keep the default language using `.md` file extension."
|
||||
PAGES_FALLBACK_ONLY: "Pages fallback only"
|
||||
PAGES_FALLBACK_ONLY_HELP: "Only 'fallback' to find page content through supported languages, default behavior is to display any language found if active language is missing"
|
||||
ALLOW_URL_TAXONOMY_FILTERS: "URL Taxonomy Filters"
|
||||
@@ -617,10 +617,7 @@ PLUGIN_ADMIN:
|
||||
EDITOR: "Editor"
|
||||
PREVIEW: "Preview"
|
||||
FULLSCREEN: "Fullscreen"
|
||||
MODULAR: "Modular"
|
||||
MODULAR_SETUP: "Modular Setup"
|
||||
NON_ROUTABLE: "Non-Routable"
|
||||
NON_MODULAR: "Non-Modular"
|
||||
NON_VISIBLE: "Non-Visible"
|
||||
NON_PUBLISHED: "Non-Published"
|
||||
CHARACTERS: "characters"
|
||||
@@ -771,11 +768,11 @@ PLUGIN_ADMIN:
|
||||
SCHEDULER: "Scheduler"
|
||||
SCHEDULER_INSTALL_INSTRUCTIONS: "Install Instructions"
|
||||
SCHEDULER_INSTALLED_READY: "Installed and Ready"
|
||||
SCHEDULER_CRON_NA: "Cron Not Available"
|
||||
SCHEDULER_NOT_ENABLED: "Not Enabled"
|
||||
SCHEDULER_CRON_NA: "Cron Not Available for user: <b>%s</b>"
|
||||
SCHEDULER_NOT_ENABLED: "Not Enabled for user: <b>%s</b>"
|
||||
SCHEDULER_SETUP: "Scheduler Setup"
|
||||
SCHEDULER_INSTRUCTIONS: "The <b>Grav Scheduler</b> allows you to create and schedule custom jobs. It also provides a method for Grav plugins to integrate programmatically and dynamically add jobs to be run periodically."
|
||||
SCHEDULER_POST_INSTRUCTIONS: "To enable the Scheduler's functionality, you must add the <b>Grav Scheduler</b> to your system's crontab file. Run the command above from the terminal to add it automatically. Once saved, refresh this page to see the status Ready."
|
||||
SCHEDULER_POST_INSTRUCTIONS: "To enable the Scheduler's functionality, you must add the <b>Grav Scheduler</b> to your system's crontab file for the <b>%s</b> user. Run the command above from the terminal to add it automatically. Once saved, refresh this page to see the status."
|
||||
SCHEDULER_JOBS: "Custom Scheduler Jobs"
|
||||
SCHEDULER_STATUS: "Scheduler Status"
|
||||
SCHEDULER_RUNAT: "Run At"
|
||||
@@ -830,11 +827,140 @@ PLUGIN_ADMIN:
|
||||
DEBUGGER_PROVIDER_HELP: "Default is PHP Debug Bar, but Clockwork browser extension provides for a less intrusive approach"
|
||||
DEBUGGER_DEBUGBAR: "PHP Debug Bar"
|
||||
DEBUGGER_CLOCKWORK: "Clockwork Browser Extension"
|
||||
PAGE_ROUTE_NOT_FOUND: "Page route not found"
|
||||
PAGE_ROUTE_FOUND: "Page route found"
|
||||
NO_ROUTE_PROVIDED: "No route provided"
|
||||
CONTENT_LANGUAGE_FALLBACKS: "Content Language Fallback"
|
||||
CONTENT_LANGUAGE_FALLBACKS_HELP: "By default if the content isn't translated, Grav will display content in the default language. Use this setting to override that behavior per language basis."
|
||||
CONTENT_LANGUAGE_FALLBACK: "Fallback Languages"
|
||||
CONTENT_LANGUAGE_FALLBACK_HELP: "Please enter a list of language codes. Note that if you omit default language code, it will not be used."
|
||||
CONTENT_FALLBACK_LANGUAGE_HELP: "Specify language code which you want to customize."
|
||||
EXPERIMENTAL: "Experimental"
|
||||
PAGES_TYPE: "Frontend Page Type"
|
||||
PAGES_TYPE_HELP: "This option enables Flex Object pages on the front-end. Admin Flex Pages requires Flex Objects plugin"
|
||||
ACCOUNTS_TYPE: "Accounts Type"
|
||||
ACCOUNTS_TYPE_HELP: "Flex Object system to store user accounts"
|
||||
ACCOUNTS_STORAGE: "Account Storage"
|
||||
ACCOUNTS_STORAGE_HELP: "The storage mechanism to be used for Flex Object Account type. Files is the traditional approach where account are stored in a YAML file in a single folder, while Folder creates a new folder for each account"
|
||||
FLEX: "Flex Object (EXPERIMENTAL)"
|
||||
REGULAR: "Regular"
|
||||
FILE: "File"
|
||||
SANITIZE_SVG: "Sanitize SVG"
|
||||
SANITIZE_SVG_HELP: "Removes any XSS code from SVG"
|
||||
ACCOUNTS: "Accounts"
|
||||
USER_ACCOUNTS: "User Accounts"
|
||||
USER_GROUPS: "User Groups"
|
||||
GROUP_NAME: "Group Name"
|
||||
DISPLAY_NAME: "Display Name"
|
||||
ICON: "Icon"
|
||||
ACCESS: "Access"
|
||||
NO_ACCESS: "No Access"
|
||||
SUPER_USER: "Super User"
|
||||
ALLOWED: "Allowed"
|
||||
DENIED: "Denied"
|
||||
MODULE: "Module"
|
||||
NON_MODULE: "Non-Module"
|
||||
ADD_MODULE: "Add Module"
|
||||
MODULE_SETUP: "Module Setup"
|
||||
MODULE_TEMPLATE: "Module Template"
|
||||
ADD_MODULE_CONTENT: "Add Module Content"
|
||||
CHANGELOG: "Changelog"
|
||||
|
||||
PAGE_ACCESS: "Page Access"
|
||||
PAGE PERMISSIONS: "Page Permissions"
|
||||
PAGE_ACCESS_HELP: "User with following access permissions can access the page."
|
||||
PAGE_VISIBILITY_REQUIRES_ACCESS: "Menu Visibility Requires Access"
|
||||
PAGE_VISIBILITY_REQUIRES_ACCESS_HELP: "Set to Yes if page should be shown in menus only if user can access them."
|
||||
PAGE_INHERIT_PERMISSIONS: "Inherit Permissions"
|
||||
PAGE_INHERIT_PERMISSIONS_HELP: "Inherit ACL from parent page."
|
||||
PAGE_AUTHORS: "Page Authors"
|
||||
PAGE_AUTHORS_HELP: "Members of Page Authors have owner level access to this page defined in special 'Authors' page group."
|
||||
PAGE_GROUPS: "Page Groups"
|
||||
PAGE_GROUPS_HELP: "Members of Page Groups have special access to this page."
|
||||
READ: "Read"
|
||||
PUBLISH: "Publish"
|
||||
LIST: "List"
|
||||
ACCESS_SITE: "Site"
|
||||
ACCESS_SITE_LOGIN: "Login to Site"
|
||||
ACCESS_ADMIN: "Admin"
|
||||
ACCESS_ADMIN_LOGIN: "Login to Admin"
|
||||
ACCESS_ADMIN_SUPER: "Super User"
|
||||
ACCESS_ADMIN_CACHE: "Clear Cache"
|
||||
ACCESS_ADMIN_CONFIGURATION: "Configuration"
|
||||
ACCESS_ADMIN_CONFIGURATION_SYSTEM: "Manage System Configuration"
|
||||
ACCESS_ADMIN_CONFIGURATION_SITE: "Manage Site Configuration"
|
||||
ACCESS_ADMIN_CONFIGURATION_MEDIA: "Manage Media Configuration"
|
||||
ACCESS_ADMIN_CONFIGURATION_INFO: "See Server Information"
|
||||
ACCESS_ADMIN_SETTINGS: "Settings"
|
||||
ACCESS_ADMIN_PAGES: "Manage Pages"
|
||||
ACCESS_ADMIN_MAINTENANCE: "Site Maintenance"
|
||||
ACCESS_ADMIN_STATISTICS: "Site Statistics"
|
||||
ACCESS_ADMIN_PLUGINS: "Manage Plugins"
|
||||
ACCESS_ADMIN_THEMES: "Manage Themes"
|
||||
ACCESS_ADMIN_TOOLS: "Access to Tools"
|
||||
ACCESS_ADMIN_USERS: "Manage Users"
|
||||
USERS: "Users"
|
||||
ACL: "Access Control"
|
||||
FLEX_CACHING: "Flex Caching"
|
||||
FLEX_INDEX_CACHE_ENABLED: "Enable Index Caching"
|
||||
FLEX_INDEX_CACHE_LIFETIME: "Index Cache Lifetime (seconds)"
|
||||
FLEX_OBJECT_CACHE_ENABLED: "Enable Object Caching"
|
||||
FLEX_OBJECT_CACHE_LIFETIME: "Object Cache Lifetime (seconds)"
|
||||
FLEX_RENDER_CACHE_ENABLED: "Enable Render Caching"
|
||||
FLEX_RENDER_CACHE_LIFETIME: "Render Cache Lifetime (seconds)"
|
||||
DEBUGGER_CENSORED: "Censor Sensitive Data"
|
||||
DEBUGGER_CENSORED_HELP: "Clockwork Provider ONLY: If Yes, censor potentially sensitive information (POST parameters, cookies, files, configuration and most array/object data in log messages)"
|
||||
LANGUAGE_TRANSLATIONS: "Translations"
|
||||
LANGUAGE_TRANSLATIONS_HELP: "If false, translation keys are used instead of translated strings. This feature can be used to help to fix bad translations or to find hardcoded English strings."
|
||||
STRICT_BLUEPRINT_COMPAT: "Blueprint Compatibility"
|
||||
STRICT_BLUEPRINT_COMPAT_HELP: "Enables backward compatible strict support for blueprints. If turned off, the new behavior makes the form validation to fail if there is extra data which is not defined in the blueprint."
|
||||
RESET: "Reset"
|
||||
LOGOS: "Logos"
|
||||
CUSTOMIZATION: "Customization"
|
||||
PRESETS: "Presets"
|
||||
COLOR_SCHEME_LABEL: "Color Scheme"
|
||||
COLOR_SCHEME_HELP: "Choose a color scheme from a list of predefined combinations, or add your own style"
|
||||
COLOR_SCHEME_NAME: "Custom Color Scheme Name"
|
||||
COLOR_SCHEME_NAME_HELP: "Give a name to your custom theme for exporting and sharing"
|
||||
COLOR_SCHEME_NAME_PLACEHOLDER: "Shades of Blue"
|
||||
PRIMARY_ACCENT_LABEL: "Primary Accent"
|
||||
PRIMARY_ACCENT_HELP: "Select which color set the primary accent should use for it's color scheme"
|
||||
SECONDARY_ACCENT_LABEL: "Secondary Accent"
|
||||
SECONDARY_ACCENT_HELP: "Select which color set the secondary accent should use for it's color scheme"
|
||||
TERTIARY_ACCENT_LABEL: "Tertiary Accent"
|
||||
TERTIARY_ACCENT_HELP: "Select which color set the tertiary accent should use for it's color scheme"
|
||||
WEB_FONTS_LABEL: "Web Fonts"
|
||||
WEB_FONTS_HELP: "Use custom web fonts"
|
||||
HEADER_FONT_LABEL: "Header Font"
|
||||
HEADER_FONT_HELP: "Font used for headers, side nav and section titles"
|
||||
BODY_FONT_LABEL: "Body Font"
|
||||
BODY_FONT_HELP: "Primary font used throughout the body of the theme"
|
||||
CUSTOM_CSS_LABEL: "Custom CSS"
|
||||
CUSTOM_CSS_PLACEHOLDER: "This is some placeholder text"
|
||||
CUSTOM_CSS_HELP: "Custom CSS that will be added to every admin page"
|
||||
CUSTOM_FOOTER: "Custom Footer"
|
||||
CUSTOM_FOOTER_HELP: "You can use HTML and/or Markdown syntax here"
|
||||
LOGIN_SCREEN_CUSTOM_LOGO_LABEL: "Login Custom Logo"
|
||||
LOGIN_SCREEN_CUSTOM_LOGO_HELP: ""
|
||||
TOP_LEFT_CUSTOM_LOGO_LABEL: "Primary Custom Logo"
|
||||
TOP_LEFT_CUSTOM_LOGO_HELP: ""
|
||||
LOAD_PRESET: "Load Preset"
|
||||
RECOMPILE: "Recompile"
|
||||
EXPORT: "Export"
|
||||
QUICKTRAY_RECOMPILE: "QuickTray Recompile Icon"
|
||||
QUICKTRAY_RECOMPILE_HELP: "Will recompile the preset SCSS to pickup any changes or new plugins"
|
||||
CODEMIRROR: "CodeMirror Editor"
|
||||
CODEMIRROR_THEME: "Editor Theme"
|
||||
CODEMIRROR_THEME_DESC: "**NOTE:** Use the [CodeMirror Themes Demo](https://codemirror.net/demo/theme.html?target=_blank) to see these in action. **_Paper_** is the default Grav theme."
|
||||
CODEMIRROR_FONTSIZE: "Editor Font Size"
|
||||
CODEMIRROR_MD_FONT: "Markdown Editor Font"
|
||||
CUSTOM_PRESETS: "Custom Presets"
|
||||
CUSTOM_PRESETS_HELP: "Drag-n-drop a theme .yaml file here, or you can create an array of presets with text based keys"
|
||||
GENERAL: "General"
|
||||
CONTENT_EDITOR: "Content Editor"
|
||||
CONTENT_EDITOR_HELP: "Custom editors can be preferred for content editing"
|
||||
BAD_FILENAME: "Bad filename"
|
||||
SHOW_SENSITIVE: "Show Sensitive Data"
|
||||
SHOW_SENSITIVE_HELP: "Clockwork Provider ONLY: Censor potentially sensitive information (POST parameters, cookies, files, configuration and most array/object data in log messages)"
|
||||
VALID_LINK_ATTRIBUTES: "Valid Link Attributes"
|
||||
VALID_LINK_ATTRIBUTES_HELP: "Attributes that will be automatically added to the media HTML element"
|
||||
PAGE_ROUTE_NOT_FOUND: "Page route not found"
|
||||
PAGE_ROUTE_FOUND: "Page route found"
|
||||
NO_ROUTE_PROVIDED: "No route provided"
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ PLUGIN_ADMIN:
|
||||
EXPERT: "Esperto"
|
||||
EXPAND_ALL: "Expandir Tudo"
|
||||
COLLAPSE_ALL: "Recolher Tudo"
|
||||
SECURE_DELETE_DESC: "Digite DELETE e confirme"
|
||||
ERROR: "Erro"
|
||||
CLOSE: "Fechar"
|
||||
CANCEL: "Cancelar"
|
||||
|
||||
9
pages/admin/changelog.md
Normal file
9
pages/admin/changelog.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Changelog
|
||||
|
||||
access:
|
||||
admin.plugins: true
|
||||
admin.maintenance: true
|
||||
admin.themes: true
|
||||
admin.super: true
|
||||
---
|
||||
@@ -3,6 +3,7 @@ title: Config
|
||||
expires: 0
|
||||
|
||||
access:
|
||||
admin.configuration: true
|
||||
admin.configuration.system: true
|
||||
admin.configuration.site: true
|
||||
admin.super: true
|
||||
---
|
||||
|
||||
@@ -4,6 +4,6 @@ template: config
|
||||
expires: 0
|
||||
|
||||
access:
|
||||
admin.settings: true
|
||||
admin.configuration.info: true
|
||||
admin.super: true
|
||||
---
|
||||
|
||||
@@ -4,6 +4,6 @@ template: config
|
||||
expires: 0
|
||||
|
||||
access:
|
||||
admin.settings: true
|
||||
admin.configuration.site: true
|
||||
admin.super: true
|
||||
---
|
||||
|
||||
@@ -4,6 +4,6 @@ template: config
|
||||
expires: 0
|
||||
|
||||
access:
|
||||
admin.configuration: true
|
||||
admin.configuration.system: true
|
||||
admin.super: true
|
||||
---
|
||||
|
||||
46
permissions.yaml
Normal file
46
permissions.yaml
Normal file
@@ -0,0 +1,46 @@
|
||||
actions:
|
||||
site:
|
||||
label: PLUGIN_ADMIN.ACCESS_SITE
|
||||
actions:
|
||||
login:
|
||||
label: PLUGIN_ADMIN.ACCESS_SITE_LOGIN
|
||||
|
||||
admin:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN
|
||||
actions:
|
||||
login:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_LOGIN
|
||||
super:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_SUPER
|
||||
cache:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_CACHE
|
||||
|
||||
configuration:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_CONFIGURATION
|
||||
actions:
|
||||
system:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_CONFIGURATION_SYSTEM
|
||||
site:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_CONFIGURATION_SITE
|
||||
media:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_CONFIGURATION_MEDIA
|
||||
info:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_CONFIGURATION_INFO
|
||||
pages:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_PAGES
|
||||
maintenance:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_MAINTENANCE
|
||||
statistics:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_STATISTICS
|
||||
plugins:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_PLUGINS
|
||||
themes:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_THEMES
|
||||
tools:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_TOOLS
|
||||
users:
|
||||
label: PLUGIN_ADMIN.ACCESS_ADMIN_USERS
|
||||
|
||||
types:
|
||||
default:
|
||||
type: access
|
||||
360
presets.yaml
Normal file
360
presets.yaml
Normal file
@@ -0,0 +1,360 @@
|
||||
default:
|
||||
name: Default
|
||||
accents:
|
||||
primary-accent: 'button'
|
||||
secondary-accent: 'notice'
|
||||
tertiary-accent: 'critical'
|
||||
colors:
|
||||
logo-bg: '#323640'
|
||||
logo-link: '#FFFFFF'
|
||||
nav-bg: '#3D424E'
|
||||
nav-text: '#B7B9BD'
|
||||
nav-link: '#ffffff'
|
||||
nav-selected-bg: '#323640'
|
||||
nav-selected-link: '#ffffff'
|
||||
nav-hover-bg: '#434753'
|
||||
nav-hover-link: '#ffffff'
|
||||
toolbar-bg: '#ffffff'
|
||||
toolbar-text: '#3D424E'
|
||||
page-bg: '#F6F6F6'
|
||||
page-text: '#6f7b8a'
|
||||
page-link: '#0090D9'
|
||||
content-bg: '#ffffff'
|
||||
content-text: '#6f7b8a'
|
||||
content-link: '#0090D9'
|
||||
content-link2: '#da4b46'
|
||||
content-header: '#414147'
|
||||
content-tabs-bg: '#e6e6e6'
|
||||
content-tabs-text: '#808080'
|
||||
button-bg: '#0090D9'
|
||||
button-text: '#ffffff'
|
||||
notice-bg: '#06A599'
|
||||
notice-text: '#ffffff'
|
||||
update-bg: '#77559D'
|
||||
update-text: '#ffffff'
|
||||
critical-bg: '#F45857'
|
||||
critical-text: '#ffffff'
|
||||
original:
|
||||
name: Original
|
||||
accents:
|
||||
primary-accent: 'button'
|
||||
secondary-accent: 'notice'
|
||||
tertiary-accent: 'critical'
|
||||
colors:
|
||||
logo-bg: '#1e333e'
|
||||
logo-link: '#ffffff'
|
||||
nav-bg: '#253a47'
|
||||
nav-text: '#afc7d5'
|
||||
nav-link: '#d1dee7'
|
||||
nav-selected-bg: '#2d4d5b'
|
||||
nav-selected-link: '#ffffff'
|
||||
nav-hover-bg: '#1e333e'
|
||||
nav-hover-link: '#ffffff'
|
||||
toolbar-bg: '#349886'
|
||||
toolbar-text: '#ffffff'
|
||||
page-bg: '#314d5b'
|
||||
page-text: '#81a5b5'
|
||||
page-link: '#aad9ed'
|
||||
content-bg: '#eeeeee'
|
||||
content-text: '#737c81'
|
||||
content-link: '#0082ba'
|
||||
content-link2: '#da4b46'
|
||||
content-header: '#314d5b'
|
||||
content-tabs-bg: '#D4D4D4'
|
||||
content-tabs-text: '#737c81'
|
||||
button-bg: '#41bea8'
|
||||
button-text: '#ffffff'
|
||||
notice-bg: '#00a6cf'
|
||||
notice-text: '#ffffff'
|
||||
update-bg: '#8f5aad'
|
||||
update-text: '#ffffff'
|
||||
critical-bg: '#da4b46'
|
||||
critical-text: '#ffffff'
|
||||
modern:
|
||||
name: Modern
|
||||
accents:
|
||||
primary-accent: 'logo'
|
||||
secondary-accent: 'update'
|
||||
tertiary-accent: 'critical'
|
||||
colors:
|
||||
logo-bg: '#3494cf'
|
||||
logo-link: '#ffffff'
|
||||
nav-bg: '#354052'
|
||||
nav-text: '#8c98ad'
|
||||
nav-link: '#b4bed6'
|
||||
nav-selected-bg: '#30394A'
|
||||
nav-selected-link: '#ffffff'
|
||||
nav-hover-bg: '#3c485e'
|
||||
nav-hover-link: '#ffffff'
|
||||
toolbar-bg: '#ffffff'
|
||||
toolbar-text: '#636468'
|
||||
page-bg: '#EDF2F6'
|
||||
page-text: '#929699'
|
||||
page-link: '#3EA7E6'
|
||||
content-bg: '#ffffff'
|
||||
content-text: '#787b7d'
|
||||
content-link: '#3EA7E6'
|
||||
content-link2: '#3cb8ba'
|
||||
content-header: '#636468'
|
||||
content-tabs-bg: '#f0f0f0'
|
||||
content-tabs-text: '#929699'
|
||||
button-bg: '#3EA7E6'
|
||||
button-text: '#ffffff'
|
||||
notice-bg: '#3EA7E6'
|
||||
notice-text: '#ffffff'
|
||||
update-bg: '#A652AB'
|
||||
update-text: '#ffffff'
|
||||
critical-bg: '#b84256'
|
||||
critical-text: '#ffffff'
|
||||
modern-dark:
|
||||
name: Modern Dark
|
||||
accents:
|
||||
primary-accent: 'logo'
|
||||
secondary-accent: 'update'
|
||||
tertiary-accent: 'critical'
|
||||
colors:
|
||||
logo-bg: '#3494cf'
|
||||
logo-link: '#ffffff'
|
||||
nav-bg: '#354052'
|
||||
nav-text: '#8c98ad'
|
||||
nav-link: '#b4bed6'
|
||||
nav-selected-bg: '#30394A'
|
||||
nav-selected-link: '#ffffff'
|
||||
nav-hover-bg: '#3c485e'
|
||||
nav-hover-link: '#ffffff'
|
||||
toolbar-bg: '#2c79b0'
|
||||
toolbar-text: '#ffffff'
|
||||
page-bg: '#30394A'
|
||||
page-text: '#8c98ad'
|
||||
page-link: '#ffffff'
|
||||
content-bg: '#354052'
|
||||
content-text: '#8c98ad'
|
||||
content-link: '#3EA7E6'
|
||||
content-link2: '#3cb8ba'
|
||||
content-header: '#b4bed6'
|
||||
content-tabs-bg: '#4a5873'
|
||||
content-tabs-text: '#d1ddf0'
|
||||
button-bg: '#3EA7E6'
|
||||
button-text: '#ffffff'
|
||||
notice-bg: '#3EA7E6'
|
||||
notice-text: '#ffffff'
|
||||
update-bg: '#A652AB'
|
||||
update-text: '#ffffff'
|
||||
critical-bg: '#b84256'
|
||||
critical-text: '#ffffff'
|
||||
modern-lite:
|
||||
name: Modern Light
|
||||
accents:
|
||||
primary-accent: 'update'
|
||||
secondary-accent: 'logo'
|
||||
tertiary-accent: 'critical'
|
||||
colors:
|
||||
logo-bg: '#3696d1'
|
||||
logo-link: '#ffffff'
|
||||
nav-bg: '#ffffff'
|
||||
nav-text: '#70798a'
|
||||
nav-link: '#5b6270'
|
||||
nav-selected-bg: '#edf2f6'
|
||||
nav-selected-link: '#4b505c'
|
||||
nav-hover-bg: '#cadbeb'
|
||||
nav-hover-link: '#5b6270'
|
||||
toolbar-bg: '#ffffff'
|
||||
toolbar-text: '#636468'
|
||||
page-bg: '#EDF2F6'
|
||||
page-text: '#929699'
|
||||
page-link: '#3EA7E6'
|
||||
content-bg: '#ffffff'
|
||||
content-text: '#787b7d'
|
||||
content-link: '#3EA7E6'
|
||||
content-link2: '#3cb8ba'
|
||||
content-header: '#636468'
|
||||
content-tabs-bg: '#f0f0f0'
|
||||
content-tabs-text: '#929699'
|
||||
button-bg: '#3EA7E6'
|
||||
button-text: '#ffffff'
|
||||
notice-bg: '#f24d77'
|
||||
notice-text: '#ffffff'
|
||||
update-bg: '#3cb8ba'
|
||||
update-text: '#ffffff'
|
||||
critical-bg: '#f45857'
|
||||
critical-text: '#ffffff'
|
||||
one-dark:
|
||||
name: One Dark
|
||||
accents:
|
||||
primary-accent: nav
|
||||
secondary-accent: nav
|
||||
tertiary-accent: tabs
|
||||
colors:
|
||||
logo-bg: '#2A2C33'
|
||||
logo-link: '#d8dadf'
|
||||
nav-bg: '#333842'
|
||||
nav-text: '#858991'
|
||||
nav-link: '#CED0D4'
|
||||
nav-selected-bg: '#282c34'
|
||||
nav-selected-link: '#d8dadf'
|
||||
nav-hover-bg: '#3A404B'
|
||||
nav-hover-link: '#d8dadf'
|
||||
toolbar-bg: '#2A2C33'
|
||||
toolbar-text: '#9ea5b3'
|
||||
page-bg: '#1E2127'
|
||||
page-text: '#ABB2BF'
|
||||
page-link: '#61AFEE'
|
||||
content-bg: '#282C34'
|
||||
content-text: '#abb2bf'
|
||||
content-link: '#61AFEE'
|
||||
content-link2: '#b960d4'
|
||||
content-header: '#727C8C'
|
||||
content-tabs-bg: '#25272E'
|
||||
content-tabs-text: '#9ea5b3'
|
||||
button-bg: '#383F4C'
|
||||
button-text: '#d8dadf'
|
||||
notice-bg: '#18a4ba'
|
||||
notice-text: '#dfe3ed'
|
||||
update-bg: '#4fa814'
|
||||
update-text: '#dfe3ed'
|
||||
critical-bg: '#d13e46'
|
||||
critical-text: '#dfe3ed'
|
||||
firewatch-light:
|
||||
name: 'Firewatch Light'
|
||||
colors:
|
||||
logo-bg: '#f3efe7'
|
||||
logo-link: '#075289'
|
||||
nav-bg: '#f3efe7'
|
||||
nav-text: '#6f6a5e'
|
||||
nav-link: '#075289'
|
||||
nav-selected-bg: '#eeeae3'
|
||||
nav-selected-link: '#06497a'
|
||||
nav-hover-bg: '#FAF8F5'
|
||||
nav-hover-link: '#075289'
|
||||
toolbar-bg: '#f3efe7'
|
||||
toolbar-text: '#6f6a5e'
|
||||
page-bg: '#e8e5dd'
|
||||
page-text: '#6f6a5e'
|
||||
page-link: '#718ECD'
|
||||
content-bg: '#f3efe7'
|
||||
content-text: '#6f6a5e'
|
||||
content-link: '#718ecd'
|
||||
content-link2: '#896723'
|
||||
content-header: '#635e53'
|
||||
content-tabs-bg: '#D6D3CC'
|
||||
content-tabs-text: '#6f6a5e'
|
||||
button-bg: '#075289'
|
||||
button-text: '#faf8f5'
|
||||
notice-bg: '#718ECD'
|
||||
notice-text: '#faf8f5'
|
||||
update-bg: '#DD672C'
|
||||
update-text: '#faf8f5'
|
||||
critical-bg: '#B0555C'
|
||||
critical-text: '#faf8f5'
|
||||
accents:
|
||||
primary-accent: notice
|
||||
secondary-accent: button
|
||||
tertiary-accent: critical
|
||||
remedy:
|
||||
name: Remedy
|
||||
accents:
|
||||
primary-accent: update
|
||||
secondary-accent: notice
|
||||
tertiary-accent: tabs
|
||||
colors:
|
||||
logo-bg: '#282726'
|
||||
logo-link: '#f9e7c4'
|
||||
nav-bg: '#2C2B2A'
|
||||
nav-text: '#938977'
|
||||
nav-link: '#938977'
|
||||
nav-selected-bg: '#272625'
|
||||
nav-selected-link: '#f9e7c4'
|
||||
nav-hover-bg: '#333130'
|
||||
nav-hover-link: '#f9e7c4'
|
||||
toolbar-bg: '#282726'
|
||||
toolbar-text: '#f9e7c4'
|
||||
page-bg: '#272625'
|
||||
page-text: '#f9e7c4'
|
||||
page-link: '#B294BB'
|
||||
content-bg: '#353433'
|
||||
content-text: '#F9E7C4'
|
||||
content-link: '#b294bb'
|
||||
content-link2: '#81a2be'
|
||||
content-header: '#F9E7C4'
|
||||
content-tabs-bg: '#272625'
|
||||
content-tabs-text: '#938977'
|
||||
button-bg: '#5a7185'
|
||||
button-text: '#f9e7c4'
|
||||
notice-bg: '#5a7185'
|
||||
notice-text: '#f9e7c4'
|
||||
update-bg: '#83638c'
|
||||
update-text: '#f9e7c4'
|
||||
critical-bg: '#943136'
|
||||
critical-text: '#f9e7c4'
|
||||
ocean:
|
||||
name: Ocean
|
||||
accents:
|
||||
primary-accent: update
|
||||
secondary-accent: button
|
||||
tertiary-accent: notice
|
||||
colors:
|
||||
logo-bg: '#007FD4'
|
||||
logo-link: '#ffffff'
|
||||
nav-bg: '#007acc'
|
||||
nav-text: '#B5D6F4'
|
||||
nav-link: '#b5d6f4'
|
||||
nav-selected-bg: '#006bbd'
|
||||
nav-selected-link: '#c9e3ff'
|
||||
nav-hover-bg: '#0072c9'
|
||||
nav-hover-link: '#c9e3ff'
|
||||
toolbar-bg: '#006bbd'
|
||||
toolbar-text: '#c9e3ff'
|
||||
page-bg: '#e9f3ff'
|
||||
page-text: '#2a587a'
|
||||
page-link: '#3EA7E6'
|
||||
content-bg: '#f5faff'
|
||||
content-text: '#2A587A'
|
||||
content-link: '#006bbd'
|
||||
content-link2: '#519173'
|
||||
content-header: '#636468'
|
||||
content-tabs-bg: '#eef6ff'
|
||||
content-tabs-text: '#41627a'
|
||||
button-bg: '#027FD4'
|
||||
button-text: '#ffffff'
|
||||
notice-bg: '#8e5b8f'
|
||||
notice-text: '#ffffff'
|
||||
update-bg: '#519173'
|
||||
update-text: '#ffffff'
|
||||
critical-bg: '#f45857'
|
||||
critical-text: '#ffffff'
|
||||
cobalt2:
|
||||
name: Cobalt2
|
||||
accents:
|
||||
primary-accent: notice
|
||||
secondary-accent: update
|
||||
tertiary-accent: tabs
|
||||
colors:
|
||||
logo-bg: '#0e1d2b'
|
||||
logo-link: '#ffffff'
|
||||
nav-bg: '#122739'
|
||||
nav-text: '#88929c'
|
||||
nav-link: '#ffffff'
|
||||
nav-selected-bg: '#0E1D2B'
|
||||
nav-selected-link: '#ffc600'
|
||||
nav-hover-bg: '#142B40'
|
||||
nav-hover-link: '#ffffff'
|
||||
toolbar-bg: '#122739'
|
||||
toolbar-text: '#acb8c5'
|
||||
page-bg: '#0e1d2b'
|
||||
page-text: '#E1EFFE'
|
||||
page-link: '#0088FF'
|
||||
content-bg: '#193549'
|
||||
content-text: '#E1EFFE'
|
||||
content-link: '#0088ff'
|
||||
content-link2: '#ffc600'
|
||||
content-header: '#ffc600'
|
||||
content-tabs-bg: '#173043'
|
||||
content-tabs-text: '#acb8c5'
|
||||
button-bg: '#0476e0'
|
||||
button-text: '#ffffff'
|
||||
notice-bg: '#0476e0'
|
||||
notice-text: '#ffffff'
|
||||
update-bg: '#ffc600'
|
||||
update-text: '#0e1d2b'
|
||||
critical-bg: '#d13e46'
|
||||
critical-text: '#dfe3ed'
|
||||
3
themes/grav/.babelrc
Normal file
3
themes/grav/.babelrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": [ "@babel/preset-env" ]
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
|
||||
@@ -34,13 +34,13 @@ export default class Cache {
|
||||
enable() {
|
||||
this.element
|
||||
.removeAttr('disabled')
|
||||
.find('> .fa').removeClass('fa-refresh fa-spin').addClass('fa-trash');
|
||||
.find('> .fa').removeClass('fa-refresh fa-spin fa-retweet').addClass('fa-retweet');
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.element
|
||||
.attr('disabled', 'disabled')
|
||||
.find('> .fa').removeClass('fa-trash').addClass('fa-refresh fa-spin');
|
||||
.find('> .fa').removeClass('fa-retweet').addClass('fa-refresh fa-spin');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
89
themes/grav/app/forms/fields/acl-picker.js
Normal file
89
themes/grav/app/forms/fields/acl-picker.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
const body = $('body');
|
||||
|
||||
body.on('change', '[data-acl_picker] select', (event) => {
|
||||
const target = $(event.currentTarget);
|
||||
const value = target.val();
|
||||
const item = target.closest('.permissions-item');
|
||||
const inputs = item.find('input[type="checkbox"], input[type="radio"]');
|
||||
const hidden = item.find('input[type="hidden"][name]');
|
||||
const wrapper = target.closest('[data-acl_picker_id]');
|
||||
const type = item.data('fieldType');
|
||||
|
||||
if (type === 'access') {
|
||||
inputs.each((index, input) => {
|
||||
input = $(input);
|
||||
const name = input.prop('name');
|
||||
input.prop('name', name.replace(/(.*)(\[[^\]]*\])/, `$1[${value}]`));
|
||||
});
|
||||
} else if (type === 'permissions') {
|
||||
const crudpContainer = item.find('[data-field-name]');
|
||||
inputs.each((index, input) => {
|
||||
input = $(input);
|
||||
const rand = Math.round(Math.random() * 500);
|
||||
const name = crudpContainer.data('fieldName');
|
||||
const id = input.prop('id').split('_').slice(0, -1).join('_') + `_${value}+${rand}`;
|
||||
// const key = input.data('crudpKey');
|
||||
hidden.prop('name', name.replace(/(.*)(\[[^\]]*\])/, `$1[${value}]`));
|
||||
input.prop('id', id);
|
||||
input.next('label').prop('for', id);
|
||||
});
|
||||
}
|
||||
|
||||
wrapper.find('.permissions-item .button.add-item')[!value ? 'addClass' : 'removeClass']('disabled').prop('disabled', !value ? 'disabled' : null);
|
||||
});
|
||||
|
||||
body.on('input', 'input[data-crudp-key]', (event) => {
|
||||
const target = $(event.currentTarget);
|
||||
const container = target.closest('.crudp-container');
|
||||
const hidden = container.find('input[type="hidden"][name]');
|
||||
const key = target.data('crudpKey');
|
||||
const json = JSON.parse(hidden.val() || '{}');
|
||||
json[key] = target.val();
|
||||
hidden.val(JSON.stringify(json));
|
||||
});
|
||||
|
||||
body.on('click', '[data-acl_picker] .remove-item', (event) => {
|
||||
event.preventDefault();
|
||||
const target = $(event.currentTarget);
|
||||
const container = target.closest('.permissions-item');
|
||||
const wrapper = target.closest('[data-acl_picker_id]');
|
||||
container.remove();
|
||||
|
||||
const empty = wrapper.find('.permissions-item').length === 1;
|
||||
|
||||
// show the initial + button
|
||||
if (empty) {
|
||||
wrapper.find('.permissions-item.empty-list').removeClass('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
body.on('click', '[data-acl_picker] .add-item', (event) => {
|
||||
event.preventDefault();
|
||||
const target = $(event.currentTarget);
|
||||
const item = target.closest('.permissions-item');
|
||||
const wrapper = target.closest('[data-acl_picker_id]');
|
||||
const ID = wrapper.data('acl_picker_id');
|
||||
const template = document.querySelector(`template[data-id="acl_picker-${ID}"]`);
|
||||
|
||||
const clone = $(template.content.firstElementChild).clone();
|
||||
clone.insertAfter(item);
|
||||
|
||||
// randomize ids
|
||||
clone.find('.switch-toggle input[type="radio"]').each((index, input) => {
|
||||
input = $(input);
|
||||
const id = input.prop('id');
|
||||
const label = input.next('label');
|
||||
const rand = (Date.now().toString(36) + Math.random().toString(36).substr(2, 5)).toLowerCase();
|
||||
|
||||
input.prop('id', `${id}${rand}`);
|
||||
label.prop('for', `${id}${rand}`);
|
||||
});
|
||||
|
||||
// hide the initial + button
|
||||
wrapper.find('.permissions-item.empty-list').addClass('hidden');
|
||||
|
||||
// disable all + buttons until one is selected
|
||||
wrapper.find('.permissions-item .button.add-item').addClass('disabled').prop('disabled', 'disabled');
|
||||
});
|
||||
@@ -87,7 +87,13 @@ export default class ArrayField {
|
||||
|
||||
container.data('array-sort', new Sortable(container.get(0), {
|
||||
handle: '.fa-bars',
|
||||
animation: 150
|
||||
animation: 150,
|
||||
onUpdate: () => {
|
||||
const item = container.find('[data-grav-array-type="row"]:first');
|
||||
this._setTemplate(item);
|
||||
const template = item.data('array-template');
|
||||
this.refreshNames(template);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
@@ -105,7 +111,13 @@ export default class ArrayField {
|
||||
let escaped_name = !template.isValueOnly() ? keyElement.val() : this.getIndexFor(element);
|
||||
escaped_name = escaped_name.toString().replace(/\[/g, '%5B').replace(/]/g, '%5D');
|
||||
let name = `${template.getName()}[${escaped_name}]`;
|
||||
valueElement.attr('name', !valueElement.val() ? template.getName() : name);
|
||||
|
||||
if (!template.isValueOnly() && (!keyElement.val() && !valueElement.val())) {
|
||||
valueElement.attr('name', '');
|
||||
} else {
|
||||
// valueElement.attr('name', !valueElement.val() ? template.getName() : name);
|
||||
valueElement.attr('name', name);
|
||||
}
|
||||
|
||||
this.refreshNames(template);
|
||||
}
|
||||
@@ -154,8 +166,8 @@ export default class ArrayField {
|
||||
|
||||
inputs.each((index, input) => {
|
||||
input = $(input);
|
||||
let name = input.attr('name');
|
||||
name = name.replace(/\[\d+\]$/, `[${index}]`);
|
||||
const preserved_name = input.closest('[data-grav-array-name]');
|
||||
const name = `${preserved_name.attr('data-grav-array-name')}[${index}]`;
|
||||
input.attr('name', name);
|
||||
});
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ export default class CollectionsField {
|
||||
// special case to preserve array field index keys
|
||||
if (prop === 'name' && element.data('gravArrayType')) {
|
||||
const match_index = element.attr(prop).match(/\[[0-9]{1,}\]$/);
|
||||
const pattern = element.closest('[data-grav-array-name]').data('gravArrayName');
|
||||
const pattern = element[0].closest('[data-grav-array-name]').dataset.gravArrayName;
|
||||
if (match_index && pattern) {
|
||||
array_index = match_index[0];
|
||||
element.attr(prop, `${pattern}${match_index[0]}`);
|
||||
|
||||
@@ -82,24 +82,22 @@ export default class ColorpickerField {
|
||||
this.wrapper.addClass('cp-visible');
|
||||
this.updateFromInput();
|
||||
|
||||
let mainContainer = $('#admin-main .content-wrapper').data('scrollbar').getViewElement();
|
||||
this.wrapper.on(MOUSEDOWN, '.cp-grid, .cp-slider, .cp-opacity-slider', this.bound('bodyDown'));
|
||||
body.on(MOUSEMOVE, this.bound('bodyMove'));
|
||||
body.on(MOUSEDOWN, this.bound('bodyClick'));
|
||||
body.on(MOUSEUP, this.bound('targetReset'));
|
||||
$(mainContainer).on('scroll', this.bound('reposition'));
|
||||
$('#admin-main > .content-wrapper').on('scroll', this.bound('reposition'));
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (!this.built) { return; }
|
||||
this.wrapper.removeClass('cp-visible');
|
||||
|
||||
let mainContainer = $('#admin-main .content-wrapper').data('scrollbar').getViewElement();
|
||||
this.wrapper.undelegate(MOUSEDOWN, '.cp-grid, .cp-slider, .cp-opacity-slider', this.bound('bodyDown'));
|
||||
body.off(MOUSEMOVE, this.bound('bodyMove'));
|
||||
body.off(MOUSEDOWN, this.bound('bodyClick'));
|
||||
body.off(MOUSEUP, this.bound('targetReset'));
|
||||
$(mainContainer).off('scroll', this.bound('reposition'));
|
||||
$('#admin-main > .content-wrapper').on('scroll', this.bound('reposition'));
|
||||
}
|
||||
|
||||
build() {
|
||||
|
||||
@@ -83,63 +83,3 @@ export default class CronField {
|
||||
}
|
||||
|
||||
export let Instance = new CronField();
|
||||
|
||||
/*
|
||||
|
||||
// cron-selector
|
||||
$(document).ready(function() {
|
||||
|
||||
$('.cron-selector').each(function(index, wrapper) {
|
||||
wrapper = $(wrapper);
|
||||
const input = wrapper.closest('[data-grav-field]').find('input');
|
||||
|
||||
wrapper.jqCron({
|
||||
enabled_minute: true,
|
||||
multiple_dom: true,
|
||||
multiple_month: true,
|
||||
multiple_mins: true,
|
||||
multiple_dow: true,
|
||||
multiple_time_hours: true,
|
||||
multiple_time_minutes: true,
|
||||
default_period: 'week',
|
||||
default_value: input.val() || '3 * * * *',
|
||||
no_reset_button: false,
|
||||
bind_to: input,
|
||||
bind_method: {
|
||||
set: function($element, value) {
|
||||
$element.val(value);
|
||||
}
|
||||
},
|
||||
texts: {
|
||||
en: {
|
||||
empty: 'every',
|
||||
empty_minutes: 'every',
|
||||
empty_time_hours: 'every hour',
|
||||
empty_time_minutes: 'every minute',
|
||||
empty_day_of_week: 'every day of the week',
|
||||
empty_day_of_month: 'every day of the month',
|
||||
empty_month: 'every month',
|
||||
name_minute: 'minute',
|
||||
name_hour: 'hour',
|
||||
name_day: 'day',
|
||||
name_week: 'week',
|
||||
name_month: 'month',
|
||||
name_year: 'year',
|
||||
text_period: 'Every <b />',
|
||||
text_mins: ' at <b /> minute(s) past the hour',
|
||||
text_time: ' at <b />:<b />',
|
||||
text_dow: ' on <b />',
|
||||
text_month: ' of <b />',
|
||||
text_dom: ' on <b />',
|
||||
error1: 'The tag %s is not supported !',
|
||||
error2: 'Bad number of elements',
|
||||
error3: 'The jquery_element should be set into jqCron settings',
|
||||
error4: 'Unrecognized expression',
|
||||
weekdays: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
|
||||
months: ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import $ from 'jquery';
|
||||
import { config } from 'grav-config';
|
||||
import 'eonasdan-bootstrap-datetimepicker';
|
||||
import '../../utils/bootstrap-datetimepicker';
|
||||
|
||||
export default class DateTimeField {
|
||||
|
||||
|
||||
@@ -118,8 +118,8 @@ export default class EditorField {
|
||||
this.editors = this.editors.add(textarea);
|
||||
if (theme && !~ThemesMap.indexOf(theme)) {
|
||||
ThemesMap.push(theme);
|
||||
let themeCSS = `https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.12.0/theme/${theme}.min.css`;
|
||||
$('head').append($('<link rel="stylesheet" type="text/css" />').attr('href', themeCSS));
|
||||
// let themeCSS = `https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.12.0/theme/${theme}.min.css`;
|
||||
// $('head').append($('<link rel="stylesheet" type="text/css" />').attr('href', themeCSS));
|
||||
}
|
||||
|
||||
if (options.mode === 'yaml') {
|
||||
|
||||
@@ -38,6 +38,8 @@ export default class FilePickerField {
|
||||
let folder = '';
|
||||
let thumbs = {};
|
||||
|
||||
let onDemand = field.closest('[data-ondemand]').length > 0;
|
||||
|
||||
if (!field.length || field.get(0).selectize) { return; }
|
||||
|
||||
let getData = function getData(field, callback, mode = 'all') {
|
||||
@@ -118,7 +120,9 @@ export default class FilePickerField {
|
||||
},
|
||||
|
||||
onInitialize: function() {
|
||||
this.load((callback) => getData(field, (data) => callback(data), 'selected'));
|
||||
if (!onDemand) {
|
||||
this.load((callback) => getData(field, (data) => callback(data), 'selected'));
|
||||
}
|
||||
},
|
||||
|
||||
onLoad: function(/* data */) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import $ from 'jquery';
|
||||
import Dropzone from 'dropzone';
|
||||
import EXIF from 'exif-js';
|
||||
// import EXIF from 'exif-js';
|
||||
import request from '../../utils/request';
|
||||
import { config, translations } from 'grav-config';
|
||||
|
||||
@@ -71,7 +71,7 @@ const DropzoneMediaConfig = {
|
||||
</div>`.trim()
|
||||
};
|
||||
|
||||
global.EXIF = EXIF;
|
||||
// global.EXIF = EXIF;
|
||||
|
||||
const ACCEPT_FUNC = function(file, done, settings) {
|
||||
const resolution = settings.resolution;
|
||||
@@ -375,7 +375,7 @@ const addNode = (container) => {
|
||||
instances.push(new FilesField({ container, options }));
|
||||
};
|
||||
|
||||
export let Instances = (() => {
|
||||
export let Instance = (() => {
|
||||
$('.dropzone.files-upload').each((i, container) => addNode(container));
|
||||
$('body').on('mutation._grav', onAddedNodes);
|
||||
|
||||
|
||||
52
themes/grav/app/forms/fields/indeterminate.js
Normal file
52
themes/grav/app/forms/fields/indeterminate.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
if (document.querySelector('#pages-filters')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const wrapper = event.target.closest('.checkboxes.indeterminate');
|
||||
|
||||
if (wrapper) {
|
||||
event.preventDefault();
|
||||
const checkbox = wrapper.querySelector('input[type="checkbox"]:not([disabled])');
|
||||
const checkStatus = wrapper.dataset._checkStatus;
|
||||
wrapper.classList.remove('status-checked', 'status-unchecked', 'status-indeterminate');
|
||||
|
||||
switch (checkStatus) {
|
||||
// checked, going indeterminate
|
||||
case '1':
|
||||
wrapper.dataset._checkStatus = '2';
|
||||
checkbox.indeterminate = true;
|
||||
checkbox.checked = false;
|
||||
checkbox.value = 0;
|
||||
wrapper.classList.add('status-indeterminate');
|
||||
break;
|
||||
|
||||
// indeterminate, going unchecked
|
||||
case '2':
|
||||
wrapper.dataset._checkStatus = '0';
|
||||
checkbox.indeterminate = false;
|
||||
checkbox.checked = false;
|
||||
checkbox.value = '';
|
||||
wrapper.classList.add('status-unchecked');
|
||||
break;
|
||||
|
||||
// unchecked, going checked
|
||||
case '0':
|
||||
default:
|
||||
wrapper.dataset._checkStatus = '1';
|
||||
checkbox.indeterminate = false;
|
||||
checkbox.checked = true;
|
||||
checkbox.value = 1;
|
||||
wrapper.classList.add('status-checked');
|
||||
break;
|
||||
}
|
||||
|
||||
// const input = new CustomEvent('input', { detail: { target: checkbox }});
|
||||
// document.dispatchEvent(input);
|
||||
$(checkbox).trigger('input');
|
||||
}
|
||||
});
|
||||
|
||||
(document.querySelectorAll('input[type="checkbox"][indeterminate="true"]') || []).forEach((input) => { input.indeterminate = true; });
|
||||
@@ -6,13 +6,17 @@ 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 MultilevelField, { Instance as MultilevelInstance } from './multilevel';
|
||||
import SelectUniqueField, { Instance as SelectUniqueInstance } from './selectunique';
|
||||
import IconpickerField, { Instance as IconpickerInstance } from './iconpicker';
|
||||
import CronField, { Instance as CronFieldInstance } from './cron';
|
||||
import TextField, { Instance as TextFieldInstance } from './text';
|
||||
import ParentsField, { Instance as ParentsFieldInstance } from './parents';
|
||||
import ParentsField, { Instances as ParentsFieldInstance } from './parents';
|
||||
|
||||
import './acl-picker';
|
||||
import './permissions';
|
||||
import './indeterminate';
|
||||
import './mediapicker';
|
||||
import './multilevel';
|
||||
import './text';
|
||||
|
||||
export default {
|
||||
FilepickerField: {
|
||||
@@ -51,23 +55,13 @@ export default {
|
||||
SelectUniqueField,
|
||||
Instance: SelectUniqueInstance
|
||||
},
|
||||
MediapickerField: {
|
||||
MediapickerField,
|
||||
Instance: MediapickerInstance
|
||||
},
|
||||
MultilevelField: {
|
||||
MultilevelField,
|
||||
Instance: MultilevelInstance
|
||||
},
|
||||
IconpickerField: {
|
||||
IconpickerField,
|
||||
Instance: IconpickerInstance
|
||||
},
|
||||
CronField: {CronField,
|
||||
Insance: CronFieldInstance
|
||||
},
|
||||
TextField: {TextField,
|
||||
Insance: TextFieldInstance
|
||||
CronField: {
|
||||
CronField,
|
||||
Instance: CronFieldInstance
|
||||
},
|
||||
ParentsField: {
|
||||
ParentsField,
|
||||
|
||||
@@ -1,30 +1,10 @@
|
||||
import $ from 'jquery';
|
||||
import Scrollbar from '../../utils/scrollbar';
|
||||
import { Instance as pagesTree } from '../../pages/tree';
|
||||
|
||||
const queries = {
|
||||
tree: '.pages-list-container .mediapicker-scroll',
|
||||
thumb: '.thumbs-list-container .mediapicker-scroll'
|
||||
};
|
||||
|
||||
$(function() {
|
||||
let modal = '';
|
||||
let body = $('body');
|
||||
|
||||
let treescrolls = [];
|
||||
let thumbscrolls = [];
|
||||
|
||||
$(queries.tree).each((index, element) => {
|
||||
treescrolls.push(new Scrollbar(element, { autoshow: true }));
|
||||
});
|
||||
|
||||
$(queries.thumb).each((index, element) => {
|
||||
thumbscrolls.push(new Scrollbar(element, { autoshow: true }));
|
||||
});
|
||||
|
||||
// let treescroll = new Scrollbar('.pages-list-container .mediapicker-scroll', { autoshow: true });
|
||||
// let thumbscroll = new Scrollbar('.thumbs-list-container .mediapicker-scroll', { autoshow: true });
|
||||
|
||||
// Thumb Resizer
|
||||
$(document).on('input change', '.media-container .media-range', function(event) {
|
||||
const target = $(event.currentTarget);
|
||||
@@ -34,16 +14,6 @@ $(function() {
|
||||
cards.each(function() {
|
||||
$(this).css('width', width);
|
||||
});
|
||||
|
||||
treescrolls.forEach((tree) => tree.update());
|
||||
thumbscrolls.forEach((thumb) => thumb.update());
|
||||
});
|
||||
|
||||
$(document).on('opened', '.remodal', function() {
|
||||
setTimeout(function() {
|
||||
treescrolls.forEach((tree) => tree.update());
|
||||
thumbscrolls.forEach((thumb) => thumb.update());
|
||||
}, 10);
|
||||
});
|
||||
|
||||
body.on('click', '[data-mediapicker-modal-trigger]', function(event) {
|
||||
@@ -57,20 +27,9 @@ $(function() {
|
||||
modal = $.remodal.lookup[modal_element.data('remodal')];
|
||||
}
|
||||
|
||||
$(queries.tree).filter((index, item) => !$(item).data('scrollbar')).each((index, item) => {
|
||||
treescrolls.push(new Scrollbar(item, { autoshow: true }));
|
||||
});
|
||||
|
||||
$(queries.thumb).filter((index, item) => !$(item).data('scrollbar')).each((index, item) => {
|
||||
thumbscrolls.push(new Scrollbar(item, { autoshow: true }));
|
||||
});
|
||||
|
||||
modal.open();
|
||||
modal.dataField = element.find('input');
|
||||
|
||||
treescrolls.forEach((tree) => tree.update());
|
||||
thumbscrolls.forEach((thumb) => thumb.update());
|
||||
|
||||
// load all media
|
||||
modal_element.find('.js__files').trigger('fillView');
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { config as gravConfig } from 'grav-config';
|
||||
let XHRUUID = 0;
|
||||
export const Instances = {};
|
||||
|
||||
export class Parents {
|
||||
export default class Parents {
|
||||
constructor(container, field, data) {
|
||||
this.container = $(container);
|
||||
this.fieldName = field.attr('name');
|
||||
|
||||
46
themes/grav/app/forms/fields/permissions.js
Normal file
46
themes/grav/app/forms/fields/permissions.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
const body = $('body');
|
||||
const radioSelector = '.permission-container.parent-section input[type="radio"]';
|
||||
|
||||
const handleParent = (event) => {
|
||||
const target = $(event.currentTarget);
|
||||
const value = target.val();
|
||||
const container = target.closest('.parent-section');
|
||||
const fieldset = container.next('fieldset');
|
||||
const radios = fieldset.find(`input[type="radio"][value="${value}"]`);
|
||||
|
||||
if (container.data('isLocked') !== false) {
|
||||
container.data('isUpdating', true);
|
||||
radios.each((index, radio) => {
|
||||
const ID = radio.id;
|
||||
$(radio).siblings(`[for="${ID}"]`).trigger('click');
|
||||
});
|
||||
container.data('isUpdating', false);
|
||||
}
|
||||
};
|
||||
|
||||
const boundHandleParent = handleParent.bind(handleParent);
|
||||
|
||||
body.on('click', '.permission-container.parent-section label', (event) => {
|
||||
const target = $(event.currentTarget);
|
||||
const container = target.closest('.parent-section');
|
||||
container.data('isLocked', true);
|
||||
});
|
||||
|
||||
body.on('input', radioSelector, boundHandleParent);
|
||||
|
||||
body.on('input', '.permissions-container input[type="radio"][data-parent-id]', (event) => {
|
||||
const target = $(event.currentTarget);
|
||||
const parent = $(`[for="${target.data('parentId')}"]`);
|
||||
const container = target.closest('fieldset').prev('.permission-container.parent-section');
|
||||
|
||||
if (container.data('isUpdating') === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
body.off('input', radioSelector, boundHandleParent);
|
||||
container.data('isLocked', false);
|
||||
parent.trigger('click');
|
||||
body.on('input', radioSelector, boundHandleParent);
|
||||
});
|
||||
@@ -30,6 +30,11 @@ export default class SelectizeField {
|
||||
|
||||
add(element) {
|
||||
element = $(element);
|
||||
|
||||
if (element.closest('template').length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let tag = element.prop('tagName').toLowerCase();
|
||||
let isInput = tag === 'input' || tag === 'select';
|
||||
|
||||
@@ -48,7 +53,10 @@ export default class SelectizeField {
|
||||
}
|
||||
|
||||
_onAddedNodes(event, target/* , record, instance */) {
|
||||
let fields = $(target).find('select.fancy, input.fancy, [data-grav-selectize]');
|
||||
let fields = $(target).find('select.fancy, input.fancy, [data-grav-selectize]').filter((index, element) => {
|
||||
return !$(element).closest('template').length;
|
||||
});
|
||||
|
||||
if (!fields.length) { return; }
|
||||
|
||||
fields.each((index, field) => this.add(field));
|
||||
|
||||
@@ -31,7 +31,7 @@ export default class Form {
|
||||
|
||||
_attachShortcuts() {
|
||||
// CTRL + S / CMD + S - shortcut for [Save] when available
|
||||
let saveTask = $('#titlebar [name="task"][value="save"]');
|
||||
let saveTask = $('#titlebar [name="task"][value="save"][form="blueprints"]');
|
||||
|
||||
if (saveTask.length) {
|
||||
$(global).on('keydown', function(event) {
|
||||
|
||||
@@ -1,47 +1,44 @@
|
||||
// polyfills
|
||||
import '@babel/polyfill';
|
||||
|
||||
import $ from 'jquery';
|
||||
import './utils/remodal';
|
||||
import 'simplebar';
|
||||
import GPM, { Instance as gpm } from './utils/gpm';
|
||||
import KeepAlive from './utils/keepalive';
|
||||
import Updates, { Instance as updates, Notifications, Feed } from './updates';
|
||||
import Dashboard from './dashboard';
|
||||
import Pages from './pages';
|
||||
import Forms from './forms';
|
||||
import Scrollbar, { Instance as contentScrollbar } from './utils/scrollbar';
|
||||
import Cookies from './utils/cookies';
|
||||
import './plugins';
|
||||
import './themes';
|
||||
import { Filter as MediaFilter, Instance as MediaFilterInstance} from './media';
|
||||
import MediaFilter, { Instance as MediaFilterInstance} from './media';
|
||||
import toastr from './utils/toastr';
|
||||
import request from './utils/request';
|
||||
import './utils/2fa';
|
||||
import './tools';
|
||||
import './whitelabel';
|
||||
|
||||
// bootstrap jQuery extensions
|
||||
import './utils/bootstrap-transition';
|
||||
import './utils/bootstrap-collapse';
|
||||
import './utils/bootstrap-dropdown';
|
||||
|
||||
// tabs memory
|
||||
import './utils/tabs-memory';
|
||||
|
||||
// changelog
|
||||
import './utils/changelog';
|
||||
|
||||
// Main Sidebar
|
||||
import Sidebar, { Instance as sidebar } from './utils/sidebar';
|
||||
|
||||
// starts the keep alive, auto runs every X seconds
|
||||
KeepAlive.start();
|
||||
|
||||
// Sidebar auto-refresh
|
||||
global.setInterval(() => {
|
||||
contentScrollbar.update();
|
||||
sidebar.scroller.update();
|
||||
}, 150);
|
||||
|
||||
// global event to catch sidebar_state changes
|
||||
$(global).on('sidebar_state._grav', () => {
|
||||
/* Should not be needed since Gemini Scrollbar v1.4.0
|
||||
* - Auto-update scrollbar on resize
|
||||
*
|
||||
* $('.admin-menu-wrapper').data('scrollbar').update();
|
||||
* $('#admin-main .content-wrapper').data('scrollbar').update();
|
||||
*/
|
||||
|
||||
Object.keys(Dashboard.Chart.Instances).forEach((chart) => {
|
||||
setTimeout(() => Dashboard.Chart.Instances[chart].chart.update(), 10);
|
||||
});
|
||||
@@ -56,10 +53,6 @@ export default {
|
||||
Dashboard,
|
||||
Pages,
|
||||
Forms,
|
||||
Scrollbar: {
|
||||
Scrollbar,
|
||||
Instance: contentScrollbar
|
||||
},
|
||||
Updates: {
|
||||
Updates,
|
||||
Notifications,
|
||||
@@ -74,5 +67,6 @@ export default {
|
||||
MediaFilter,
|
||||
Instance: MediaFilterInstance
|
||||
},
|
||||
Utils: { request, toastr }
|
||||
Scrollbar: { Scrollbar: { deprecated: true }, Instance: { deprecated: true } },
|
||||
Utils: { request, toastr, Cookies }
|
||||
};
|
||||
|
||||
@@ -166,14 +166,10 @@ $('body').on('click', '.js__reset-pages-filter', (event) => {
|
||||
var enableInfiniteScrolling = function enableInfiniteScrolling() {
|
||||
$('.spinning-wheel').hide();
|
||||
var view = $('.mediapicker-scroll').last();
|
||||
var gemini = view.data('scrollbar');
|
||||
if (gemini) {
|
||||
gemini = gemini.getViewElement();
|
||||
}
|
||||
|
||||
if (!gemini || !gemini.length && !view.length) { return; }
|
||||
if (!view.length) { return; }
|
||||
|
||||
$(gemini || view).on('scroll', function() {
|
||||
$(view).on('scroll', function() {
|
||||
if (($(this).scrollTop() + $(this).innerHeight() + 100) >= $(this)[0].scrollHeight) {
|
||||
fillView();
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ import '../utils/storage';
|
||||
/* @formatter:off */
|
||||
/* eslint-disable */
|
||||
const options = [
|
||||
{ flag: translations.PLUGIN_ADMIN.MODULAR, key: 'Modular', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.MODULE, key: 'Module', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.VISIBLE, key: 'Visible', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.ROUTABLE, key: 'Routable', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.PUBLISHED, key: 'Published', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.NON_MODULAR, key: 'NonModular', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.NON_MODULE, key: 'NonModule', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.NON_VISIBLE, key: 'NonVisible', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.NON_ROUTABLE, key: 'NonRoutable', cat: 'mode' },
|
||||
{ flag: translations.PLUGIN_ADMIN.NON_PUBLISHED, key: 'NonPublished', cat: 'mode' }
|
||||
|
||||
@@ -4,8 +4,8 @@ import request from '../../utils/request';
|
||||
import { config } from 'grav-config';
|
||||
|
||||
let custom = false;
|
||||
let folder = $('[data-remodal-id="modal"] input[name="data[folder]"], [data-remodal-id="modular"] input[name="data[folder]"]');
|
||||
let title = $('[data-remodal-id="modal"] input[name="data[title]"], [data-remodal-id="modular"] input[name="data[title]"]');
|
||||
let folder = $('[data-remodal-id="modal"] input[name="data[folder]"], [data-remodal-id="module"] input[name="data[folder]"]');
|
||||
let title = $('[data-remodal-id="modal"] input[name="data[title]"], [data-remodal-id="module"] input[name="data[title]"]');
|
||||
let getFields = (type, target) => {
|
||||
target = $(target);
|
||||
let query = `[data-remodal-id="${target.closest('[data-remodal-id]').data('remodal-id')}"]`;
|
||||
|
||||
@@ -139,9 +139,9 @@ export default class PageMedia extends FilesField {
|
||||
let file = target.parent('.dz-preview').find('.dz-filename');
|
||||
let filename = encodeURI(file.text());
|
||||
let URL = target.closest('[data-media-path]').data('media-path');
|
||||
let original = this.dropzone.files.filter((file) => encodeURIComponent(file.name) === filename).shift();
|
||||
let original = this.dropzone.files.filter((file) => encodeURI(file.name) === filename).shift();
|
||||
|
||||
original = original.extras.original || encodeURIComponent(original.name);
|
||||
original = original && ((original.extras && original.extras.original) || encodeURI(original.name));
|
||||
|
||||
target.attr('href', `${URL}/${original}`);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
$('[data-page-move] button[name="task"][value="save"]').on('click', function() {
|
||||
let route = $('form#blueprints:first select[name="data[route]"]');
|
||||
$(document).on('click', '[data-page-move] button[name="task"][value="save"]', (event) => {
|
||||
/* let route = $('form#blueprints:first select[name="data[route]"]');
|
||||
let moveTo = $('[data-page-move] select').val();
|
||||
|
||||
if (route.length && route.val() !== moveTo) {
|
||||
@@ -9,5 +9,55 @@ $('[data-page-move] button[name="task"][value="save"]').on('click', function() {
|
||||
route.val(moveTo);
|
||||
|
||||
if (selectize) selectize.setValue(moveTo);
|
||||
}
|
||||
}*/
|
||||
|
||||
const modal = $(event.currentTarget).closest('[data-remodal-id]');
|
||||
const parents = modal.data('parents') || {};
|
||||
const finder = parents.finder;
|
||||
|
||||
if (!parents || !finder) { return true; }
|
||||
|
||||
const field = parents.field;
|
||||
const parentLabel = parents.parentLabel;
|
||||
const parentName = parents.parentName;
|
||||
const selection = finder.findLastActive().item[0];
|
||||
const value = selection._item[finder.config.valueKey];
|
||||
const name = selection._item[finder.config.labelKey];
|
||||
|
||||
field.val(value);
|
||||
parentLabel.text(value);
|
||||
parentName.text(name);
|
||||
finder.config.defaultPath = value;
|
||||
|
||||
$('<div />').css({
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
zIndex: 15000
|
||||
}).appendTo($('body'));
|
||||
});
|
||||
|
||||
/*
|
||||
$(document).on('click', '[data-remodal-id="parents"] [data-parents-select]', (event) => {
|
||||
const modal = $(event.currentTarget).closest('[data-remodal-id]');
|
||||
const parents = modal.data('parents');
|
||||
const finder = parents.finder;
|
||||
const field = parents.field;
|
||||
const parentLabel = parents.parentLabel;
|
||||
const parentName = parents.parentName;
|
||||
const selection = finder.findLastActive().item[0];
|
||||
const value = selection._item[finder.config.valueKey];
|
||||
const name = selection._item[finder.config.labelKey];
|
||||
|
||||
field.val(value);
|
||||
parentLabel.text(value);
|
||||
parentName.text(name);
|
||||
finder.config.defaultPath = value;
|
||||
|
||||
const remodal = $.remodal.lookup[$(`[data-remodal-id="${modal.data('remodalId')}"]`).data('remodal')];
|
||||
remodal.close();
|
||||
});
|
||||
*/
|
||||
|
||||
@@ -64,11 +64,6 @@ export default class PagesTree {
|
||||
}
|
||||
});
|
||||
|
||||
const scroller = elements.closest('.mediapicker-scroll');
|
||||
if (scroller.length && scroller.data('scrollbar')) {
|
||||
scroller.data('scrollbar').update();
|
||||
}
|
||||
|
||||
if (!dontStore) { this.save(); }
|
||||
}
|
||||
|
||||
@@ -99,11 +94,6 @@ export default class PagesTree {
|
||||
}
|
||||
});
|
||||
|
||||
const scroller = elements.closest('.mediapicker-scroll');
|
||||
if (scroller.length && scroller.data('scrollbar')) {
|
||||
scroller.data('scrollbar').update();
|
||||
}
|
||||
|
||||
if (!dontStore) { this.save(); }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import $ from 'jquery';
|
||||
import unique from 'mout/array/unique';
|
||||
import { config, translations } from 'grav-config';
|
||||
import formatBytes from '../utils/formatbytes';
|
||||
import { Instance as gpm } from '../utils/gpm';
|
||||
import Notifications from './notifications';
|
||||
|
||||
import Feed from './feed';
|
||||
import './check';
|
||||
import './update';
|
||||
@@ -13,6 +13,7 @@ export default class Updates {
|
||||
constructor(payload = {}) {
|
||||
this.setPayload(payload);
|
||||
this.task = `task${config.param_sep}`;
|
||||
this.updateURL = '';
|
||||
}
|
||||
|
||||
setPayload(payload = {}) {
|
||||
@@ -47,7 +48,8 @@ export default class Updates {
|
||||
let bar = '';
|
||||
|
||||
if (!payload.isSymlink) {
|
||||
bar += `<button data-maintenance-update="${config.base_url_relative}/update.json/${task}updategrav/admin-nonce${config.param_sep}${config.admin_nonce}" class="button button-small secondary" id="grav-update-button">${translations.PLUGIN_ADMIN.UPDATE_GRAV_NOW}</button>`;
|
||||
this.updateURL = `${config.base_url_relative}/update.json/${task}updategrav/admin-nonce${config.param_sep}${config.admin_nonce}`;
|
||||
bar += `<button data-remodal-target="update-grav" class="button button-small secondary pointer-events-none" id="grav-update-button">${translations.PLUGIN_ADMIN.UPDATE_GRAV_NOW} <span class="cnt-down">(5s)</span></button>`;
|
||||
} else {
|
||||
bar += `<span class="hint--left" style="float: right;" data-hint="${translations.PLUGIN_ADMIN.GRAV_SYMBOLICALLY_LINKED}"><i class="fa fa-fw fa-link"></i></span>`;
|
||||
}
|
||||
@@ -65,14 +67,22 @@ export default class Updates {
|
||||
element
|
||||
.addClass('grav')
|
||||
.html(`${bar}`)
|
||||
.slideDown(150)
|
||||
.slideDown(150, function() {
|
||||
var c = 5;
|
||||
var x = setInterval(function() {
|
||||
c -= 1;
|
||||
element.find('.pointer-events-none .cnt-down').text('(' + c + 's)');
|
||||
}, 1000);
|
||||
|
||||
setTimeout(function() {
|
||||
clearInterval(x);
|
||||
element.find('.pointer-events-none .cnt-down').remove();
|
||||
element.find('.pointer-events-none').removeClass('pointer-events-none');
|
||||
}, 5000);
|
||||
})
|
||||
.parent('#messages').addClass('default-box-shadow');
|
||||
}
|
||||
|
||||
$('#grav-update-button').on('click', function() {
|
||||
$(this).html(`${translations.PLUGIN_ADMIN.UPDATING_PLEASE_WAIT} ${formatBytes(payload.assets['grav-update'].size)}..`);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ export default notifications;
|
||||
if (canFetchNotifications()) {
|
||||
notifications.fetch();
|
||||
|
||||
/* Hide a notification and store it hidden
|
||||
/* Hide a notification and store it hidden */
|
||||
// <a href="#" data-notification-action="hide-notification" data-notification-id="${notification.id}" class="close hide-notification"><i class="fa fa-close"></i></a>
|
||||
$(document).on('click', '[data-notification-action="hide-notification"]', (event) => {
|
||||
let notification_id = $(event.target).parents('.hide-notification').data('notification-id');
|
||||
@@ -149,7 +149,6 @@ if (canFetchNotifications()) {
|
||||
|
||||
$(event.target).parents('.single-notification').hide();
|
||||
});
|
||||
*/
|
||||
|
||||
$(document).on('click', '[data-notification-action="hide-notification"]', (event) => {
|
||||
const target = $(event.currentTarget);
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import $ from 'jquery';
|
||||
import { translations } from 'grav-config';
|
||||
import formatBytes from '../utils/formatbytes';
|
||||
import request from '../utils/request';
|
||||
import { Instance as Update } from './index';
|
||||
|
||||
// Dashboard update and Grav update
|
||||
$('body').on('click', '[data-maintenance-update]', function() {
|
||||
let element = $(this);
|
||||
let url = element.data('maintenanceUpdate');
|
||||
$(document).on('click.remodal', '[data-remodal-id="update-grav"] [data-remodal-action="confirm"]', () => {
|
||||
const element = $('#grav-update-button');
|
||||
element.html(`${translations.PLUGIN_ADMIN.UPDATING_PLEASE_WAIT} ${formatBytes(Update.payload.grav.assets['grav-update'].size)}..`);
|
||||
|
||||
element.attr('disabled', 'disabled').find('> .fa').removeClass('fa-cloud-download').addClass('fa-refresh fa-spin');
|
||||
|
||||
request(url, (response) => {
|
||||
request(Update.updateURL, (response) => {
|
||||
if (response.type === 'updategrav') {
|
||||
$('[data-gpm-grav]').remove();
|
||||
$('#footer .grav-version').html(response.version);
|
||||
|
||||
2632
themes/grav/app/utils/bootstrap-datetimepicker.js
vendored
Normal file
2632
themes/grav/app/utils/bootstrap-datetimepicker.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
169
themes/grav/app/utils/bootstrap-dropdown.js
vendored
Normal file
169
themes/grav/app/utils/bootstrap-dropdown.js
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
import jQuery from 'jquery';
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: dropdown.js v3.4.1
|
||||
* https://getbootstrap.com/docs/3.4/javascript/#dropdowns
|
||||
* ========================================================================
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/v3-dev/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
+(function($) {
|
||||
'use strict';
|
||||
|
||||
// DROPDOWN CLASS DEFINITION
|
||||
// =========================
|
||||
|
||||
const backdrop = '.dropdown-backdrop';
|
||||
const toggle = '[data-toggle="dropdown"]';
|
||||
const Dropdown = function(element) {
|
||||
$(element).on('click.bs.dropdown', this.toggle);
|
||||
};
|
||||
|
||||
Dropdown.VERSION = '3.4.1';
|
||||
|
||||
function getParent($this) {
|
||||
let selector = $this.attr('data-target');
|
||||
|
||||
if (!selector) {
|
||||
selector = $this.attr('href');
|
||||
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, ''); // strip for ie7
|
||||
}
|
||||
|
||||
const $parent = selector !== '#' ? $(document).find(selector) : null;
|
||||
|
||||
return $parent && $parent.length ? $parent : $this.parent();
|
||||
}
|
||||
|
||||
function clearMenus(e) {
|
||||
if (e && e.which === 3) { return; }
|
||||
$(backdrop).remove();
|
||||
$(toggle).each(function() {
|
||||
const $this = $(this);
|
||||
const $parent = getParent($this);
|
||||
const relatedTarget = { relatedTarget: this };
|
||||
|
||||
if (!$parent.hasClass('open')) { return; }
|
||||
|
||||
if (e && e.type === 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) { return; }
|
||||
|
||||
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget));
|
||||
|
||||
if (e.isDefaultPrevented()) { return; }
|
||||
|
||||
$this.attr('aria-expanded', 'false');
|
||||
$parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget));
|
||||
});
|
||||
}
|
||||
|
||||
Dropdown.prototype.toggle = function(e) {
|
||||
const $this = $(this);
|
||||
|
||||
if ($this.is('.disabled, :disabled')) { return; }
|
||||
|
||||
const $parent = getParent($this);
|
||||
const isActive = $parent.hasClass('open');
|
||||
|
||||
clearMenus();
|
||||
|
||||
if (!isActive) {
|
||||
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
|
||||
// if mobile we use a backdrop because click events don't delegate
|
||||
$(document.createElement('div'))
|
||||
.addClass('dropdown-backdrop')
|
||||
.insertAfter($(this))
|
||||
.on('click', clearMenus);
|
||||
}
|
||||
|
||||
const relatedTarget = { relatedTarget: this };
|
||||
$parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget));
|
||||
|
||||
if (e.isDefaultPrevented()) { return; }
|
||||
|
||||
$this
|
||||
.trigger('focus')
|
||||
.attr('aria-expanded', 'true');
|
||||
|
||||
$parent
|
||||
.toggleClass('open')
|
||||
.trigger($.Event('shown.bs.dropdown', relatedTarget));
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
Dropdown.prototype.keydown = function(e) {
|
||||
if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return;
|
||||
|
||||
const $this = $(this);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if ($this.is('.disabled, :disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $parent = getParent($this);
|
||||
const isActive = $parent.hasClass('open');
|
||||
|
||||
if (!isActive && e.which !== 27 || isActive && e.which === 27) {
|
||||
if (e.which === 27) {
|
||||
$parent.find(toggle).trigger('focus');
|
||||
}
|
||||
|
||||
return $this.trigger('click');
|
||||
}
|
||||
|
||||
const desc = ' li:not(.disabled):visible a';
|
||||
const $items = $parent.find('.dropdown-menu' + desc);
|
||||
|
||||
if (!$items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let index = $items.index(e.target);
|
||||
|
||||
if (e.which === 38 && index > 0) { index--; } // up
|
||||
if (e.which === 40 && index < $items.length - 1) { index++; } // down
|
||||
if (!~index) { index = 0; }
|
||||
|
||||
$items.eq(index).trigger('focus');
|
||||
};
|
||||
|
||||
// DROPDOWN PLUGIN DEFINITION
|
||||
// ==========================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function() {
|
||||
const $this = $(this);
|
||||
let data = $this.data('bs.dropdown');
|
||||
|
||||
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)));
|
||||
if (typeof option === 'string') data[option].call($this);
|
||||
});
|
||||
}
|
||||
|
||||
const old = $.fn.dropdown;
|
||||
|
||||
$.fn.dropdown = Plugin;
|
||||
$.fn.dropdown.Constructor = Dropdown;
|
||||
|
||||
// DROPDOWN NO CONFLICT
|
||||
// ====================
|
||||
|
||||
$.fn.dropdown.noConflict = function() {
|
||||
$.fn.dropdown = old;
|
||||
return this;
|
||||
};
|
||||
|
||||
// APPLY TO STANDARD DROPDOWN ELEMENTS
|
||||
// ===================================
|
||||
|
||||
$(document)
|
||||
.on('click.bs.dropdown.data-api', clearMenus)
|
||||
.on('click.bs.dropdown.data-api', '.dropdown form', function(e) { e.stopPropagation(); })
|
||||
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
|
||||
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
|
||||
.on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown);
|
||||
}(jQuery));
|
||||
25
themes/grav/app/utils/changelog.js
Normal file
25
themes/grav/app/utils/changelog.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/* eslint-disable */
|
||||
import $ from 'jquery';
|
||||
|
||||
let TRIGGER = null;
|
||||
|
||||
$(document).on('click', '[data-remodal-changelog]', (event) => {
|
||||
TRIGGER = event.currentTarget;
|
||||
});
|
||||
|
||||
$(document).on('opened', '[data-remodal-id="changelog"]', () => {
|
||||
const instance = $.remodal.lookup[$('[data-remodal-id=changelog]').data('remodal')];
|
||||
instance.$modal.html('<div class="changelog-overflow center" style="padding:5rem 0;text-align:center;"><i class="fa fa-spinner fa-spin fa-3x fa-fw"></i></div>');
|
||||
if (!TRIGGER) { return true; }
|
||||
|
||||
const url = $(TRIGGER).data('remodalChangelog');
|
||||
|
||||
$.ajax({url: url}).done(function(data) {
|
||||
instance.$modal.html(data);
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('closed', '[data-remodal-id="changelog"]', () => {
|
||||
const instance = $.remodal.lookup[$('[data-remodal-id=changelog]').data('remodal')];
|
||||
instance.$modal.html('');
|
||||
});
|
||||
165
themes/grav/app/utils/cookies.js
Normal file
165
themes/grav/app/utils/cookies.js
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Cookies.js - 1.2.3-grav
|
||||
* https://github.com/ScottHamper/Cookies
|
||||
*
|
||||
* With SameSite support by Grav
|
||||
*
|
||||
* This is free and unencumbered software released into the public domain.
|
||||
*/
|
||||
|
||||
const factory = function(window) {
|
||||
if (typeof window.document !== 'object') {
|
||||
throw new Error('Cookies.js requires a `window` with a `document` object');
|
||||
}
|
||||
|
||||
const Cookies = (key, value, options) => {
|
||||
alert('a');
|
||||
return arguments.length === 1
|
||||
? Cookies.get(key)
|
||||
: Cookies.set(key, value, options);
|
||||
};
|
||||
|
||||
// Allows for setter injection in unit tests
|
||||
Cookies._document = window.document;
|
||||
|
||||
// Used to ensure cookie keys do not collide with
|
||||
// built-in `Object` properties
|
||||
Cookies._cacheKeyPrefix = 'cookey.'; // Hurr hurr, :)
|
||||
|
||||
Cookies._maxExpireDate = new Date('Fri, 31 Dec 9999 23:59:59 UTC');
|
||||
|
||||
Cookies.defaults = {
|
||||
path: '/',
|
||||
secure: false,
|
||||
sameSite: 'Lax'
|
||||
};
|
||||
|
||||
Cookies.get = (key) => {
|
||||
if (Cookies._cachedDocumentCookie !== Cookies._document.cookie) {
|
||||
Cookies._renewCache();
|
||||
}
|
||||
|
||||
const value = Cookies._cache[Cookies._cacheKeyPrefix + key];
|
||||
|
||||
return value === undefined ? undefined : decodeURIComponent(value);
|
||||
};
|
||||
|
||||
Cookies.set = (key, value, options) => {
|
||||
options = Cookies._getExtendedOptions(options);
|
||||
options.expires = Cookies._getExpiresDate(value === undefined ? -1 : options.expires);
|
||||
|
||||
Cookies._document.cookie = Cookies._generateCookieString(key, value, options);
|
||||
|
||||
return Cookies;
|
||||
};
|
||||
|
||||
Cookies.expire = (key, options) => {
|
||||
return Cookies.set(key, undefined, options);
|
||||
};
|
||||
|
||||
Cookies._getExtendedOptions = (options) => {
|
||||
return {
|
||||
path: options && options.path || Cookies.defaults.path,
|
||||
domain: options && options.domain || Cookies.defaults.domain,
|
||||
expires: options && options.expires || Cookies.defaults.expires,
|
||||
secure: options && options.secure !== undefined ? options.secure : Cookies.defaults.secure,
|
||||
sameSite: options && options.sameSite || Cookies.defaults.sameSite
|
||||
};
|
||||
};
|
||||
|
||||
Cookies._isValidDate = (date) => {
|
||||
return Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date.getTime());
|
||||
};
|
||||
|
||||
Cookies._getExpiresDate = (expires, now) => {
|
||||
now = now || new Date();
|
||||
|
||||
if (typeof expires === 'number') {
|
||||
expires = expires === Infinity
|
||||
? Cookies._maxExpireDate
|
||||
: new Date(now.getTime() + expires * 1000);
|
||||
} else if (typeof expires === 'string') {
|
||||
expires = new Date(expires);
|
||||
}
|
||||
|
||||
if (expires && !Cookies._isValidDate(expires)) {
|
||||
throw new Error('`expires` parameter cannot be converted to a valid Date instance');
|
||||
}
|
||||
|
||||
return expires;
|
||||
};
|
||||
|
||||
Cookies._generateCookieString = (key, value, options) => {
|
||||
key = key.replace(/[^#$&+\^`|]/g, encodeURIComponent);
|
||||
key = key.replace(/\(/g, '%28').replace(/\)/g, '%29');
|
||||
value = (value + '').replace(/[^!#$&-+\--:<-\[\]-~]/g, encodeURIComponent);
|
||||
options = options || {};
|
||||
|
||||
let cookieString = key + '=' + value;
|
||||
cookieString += options.path ? ';path=' + options.path : '';
|
||||
cookieString += options.domain ? ';domain=' + options.domain : '';
|
||||
cookieString += options.expires ? ';expires=' + options.expires.toUTCString() : '';
|
||||
cookieString += options.secure ? ';secure' : '';
|
||||
cookieString += options.sameSite ? ';SameSite=' + options.sameSite : '';
|
||||
|
||||
return cookieString;
|
||||
};
|
||||
|
||||
Cookies._getCacheFromString = (documentCookie) => {
|
||||
let cookieCache = {};
|
||||
const cookiesArray = documentCookie ? documentCookie.split('; ') : [];
|
||||
|
||||
for (let i = 0; i < cookiesArray.length; i++) {
|
||||
const cookieKvp = Cookies._getKeyValuePairFromCookieString(cookiesArray[i]);
|
||||
|
||||
if (cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] === undefined) {
|
||||
cookieCache[Cookies._cacheKeyPrefix + cookieKvp.key] = cookieKvp.value;
|
||||
}
|
||||
}
|
||||
|
||||
return cookieCache;
|
||||
};
|
||||
|
||||
Cookies._getKeyValuePairFromCookieString = (cookieString) => {
|
||||
// "=" is a valid character in a cookie value according to RFC6265, so cannot `split('=')`
|
||||
let separatorIndex = cookieString.indexOf('=');
|
||||
|
||||
// IE omits the "=" when the cookie value is an empty string
|
||||
separatorIndex = separatorIndex < 0 ? cookieString.length : separatorIndex;
|
||||
|
||||
const key = cookieString.substr(0, separatorIndex);
|
||||
let decodedKey;
|
||||
try {
|
||||
decodedKey = decodeURIComponent(key);
|
||||
} catch (e) {
|
||||
if (console && typeof console.error === 'function') {
|
||||
console.error('Could not decode cookie with key "' + key + '"', e);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
key: decodedKey,
|
||||
value: cookieString.substr(separatorIndex + 1) // Defer decoding value until accessed
|
||||
};
|
||||
};
|
||||
|
||||
Cookies._renewCache = () => {
|
||||
Cookies._cache = Cookies._getCacheFromString(Cookies._document.cookie);
|
||||
Cookies._cachedDocumentCookie = Cookies._document.cookie;
|
||||
};
|
||||
|
||||
Cookies._areEnabled = () => {
|
||||
const testKey = 'cookies.js';
|
||||
const areEnabled = Cookies.set(testKey, 1).get(testKey) === '1';
|
||||
Cookies.expire(testKey);
|
||||
return areEnabled;
|
||||
};
|
||||
|
||||
Cookies.enabled = Cookies._areEnabled();
|
||||
|
||||
return Cookies;
|
||||
};
|
||||
|
||||
global.Cookies = (global && typeof global.document === 'object') ? factory(global) : factory;
|
||||
|
||||
export default global.Cookies;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { parseJSON, parseStatus, userFeedbackError } from './response';
|
||||
import { config } from 'grav-config';
|
||||
import { EventEmitter } from 'events';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
export default class GPM extends EventEmitter {
|
||||
constructor(action = 'getUpdates') {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { config } from 'grav-config';
|
||||
import { userFeedbackError } from './response';
|
||||
|
||||
const MAX_SAFE_DELAY = 2147483647;
|
||||
|
||||
class KeepAlive {
|
||||
constructor() {
|
||||
this.active = false;
|
||||
@@ -8,7 +10,7 @@ class KeepAlive {
|
||||
|
||||
start() {
|
||||
let timeout = config.admin_timeout / 1.5 * 1000;
|
||||
this.timer = setInterval(() => this.fetch(), timeout);
|
||||
this.timer = setInterval(() => this.fetch(), Math.min(timeout, MAX_SAFE_DELAY));
|
||||
this.active = true;
|
||||
}
|
||||
|
||||
|
||||
820
themes/grav/app/utils/remodal.js
Normal file
820
themes/grav/app/utils/remodal.js
Normal file
@@ -0,0 +1,820 @@
|
||||
/* Remodal from https://github.com/vodkabears/Remodal
|
||||
* With Stackable option from https://github.com/antstorm/Remodal patch
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
!(function(root, factory) {
|
||||
return factory(root, $);
|
||||
})(this, function(global, $) {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Name of the plugin
|
||||
* @private
|
||||
* @const
|
||||
* @type {String}
|
||||
*/
|
||||
var PLUGIN_NAME = 'remodal';
|
||||
|
||||
/**
|
||||
* Namespace for CSS and events
|
||||
* @private
|
||||
* @const
|
||||
* @type {String}
|
||||
*/
|
||||
var NAMESPACE = window.REMODAL_GLOBALS && window.REMODAL_GLOBALS.NAMESPACE || PLUGIN_NAME;
|
||||
|
||||
/**
|
||||
* Animationstart event with vendor prefixes
|
||||
* @private
|
||||
* @const
|
||||
* @type {String}
|
||||
*/
|
||||
var ANIMATIONSTART_EVENTS = $.map(
|
||||
['animationstart', 'webkitAnimationStart', 'MSAnimationStart', 'oAnimationStart'],
|
||||
|
||||
function(eventName) {
|
||||
return eventName + '.' + NAMESPACE;
|
||||
}
|
||||
|
||||
).join(' ');
|
||||
|
||||
/**
|
||||
* Animationend event with vendor prefixes
|
||||
* @private
|
||||
* @const
|
||||
* @type {String}
|
||||
*/
|
||||
var ANIMATIONEND_EVENTS = $.map(
|
||||
['animationend', 'webkitAnimationEnd', 'MSAnimationEnd', 'oAnimationEnd'],
|
||||
|
||||
function(eventName) {
|
||||
return eventName + '.' + NAMESPACE;
|
||||
}
|
||||
|
||||
).join(' ');
|
||||
|
||||
/**
|
||||
* Default settings
|
||||
* @private
|
||||
* @const
|
||||
* @type {Object}
|
||||
*/
|
||||
var DEFAULTS = $.extend({
|
||||
hashTracking: true,
|
||||
closeOnConfirm: true,
|
||||
closeOnCancel: true,
|
||||
closeOnEscape: true,
|
||||
closeOnOutsideClick: true,
|
||||
modifier: '',
|
||||
stack: false,
|
||||
appendTo: null
|
||||
}, window.REMODAL_GLOBALS && window.REMODAL_GLOBALS.DEFAULTS);
|
||||
|
||||
/**
|
||||
* States of the Remodal
|
||||
* @private
|
||||
* @const
|
||||
* @enum {String}
|
||||
*/
|
||||
var STATES = {
|
||||
CLOSING: 'closing',
|
||||
CLOSED: 'closed',
|
||||
OPENING: 'opening',
|
||||
OPENED: 'opened'
|
||||
};
|
||||
|
||||
/**
|
||||
* Reasons of the state change.
|
||||
* @private
|
||||
* @const
|
||||
* @enum {String}
|
||||
*/
|
||||
var STATE_CHANGE_REASONS = {
|
||||
CONFIRMATION: 'confirmation',
|
||||
CANCELLATION: 'cancellation'
|
||||
};
|
||||
|
||||
/**
|
||||
* Is animation supported?
|
||||
* @private
|
||||
* @const
|
||||
* @type {Boolean}
|
||||
*/
|
||||
var IS_ANIMATION = (function() {
|
||||
var style = document.createElement('div').style;
|
||||
|
||||
return style.animationName !== undefined ||
|
||||
style.WebkitAnimationName !== undefined ||
|
||||
style.MozAnimationName !== undefined ||
|
||||
style.msAnimationName !== undefined ||
|
||||
style.OAnimationName !== undefined;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Is iOS?
|
||||
* @private
|
||||
* @const
|
||||
* @type {Boolean}
|
||||
*/
|
||||
var IS_IOS = /iPad|iPhone|iPod/.test(navigator.platform);
|
||||
|
||||
/**
|
||||
* Current modal
|
||||
* @private
|
||||
* @type {Remodal}
|
||||
*/
|
||||
var openModals = [];
|
||||
|
||||
/**
|
||||
* Scrollbar position
|
||||
* @private
|
||||
* @type {Number}
|
||||
*/
|
||||
var scrollTop;
|
||||
|
||||
/**
|
||||
* Returns an animation duration
|
||||
* @private
|
||||
* @param {jQuery} $elem
|
||||
* @returns {Number}
|
||||
*/
|
||||
function getAnimationDuration($elem) {
|
||||
if (
|
||||
IS_ANIMATION &&
|
||||
$elem.css('animation-name') === 'none' &&
|
||||
$elem.css('-webkit-animation-name') === 'none' &&
|
||||
$elem.css('-moz-animation-name') === 'none' &&
|
||||
$elem.css('-o-animation-name') === 'none' &&
|
||||
$elem.css('-ms-animation-name') === 'none'
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var duration = $elem.css('animation-duration') ||
|
||||
$elem.css('-webkit-animation-duration') ||
|
||||
$elem.css('-moz-animation-duration') ||
|
||||
$elem.css('-o-animation-duration') ||
|
||||
$elem.css('-ms-animation-duration') ||
|
||||
'0s';
|
||||
|
||||
var delay = $elem.css('animation-delay') ||
|
||||
$elem.css('-webkit-animation-delay') ||
|
||||
$elem.css('-moz-animation-delay') ||
|
||||
$elem.css('-o-animation-delay') ||
|
||||
$elem.css('-ms-animation-delay') ||
|
||||
'0s';
|
||||
|
||||
var iterationCount = $elem.css('animation-iteration-count') ||
|
||||
$elem.css('-webkit-animation-iteration-count') ||
|
||||
$elem.css('-moz-animation-iteration-count') ||
|
||||
$elem.css('-o-animation-iteration-count') ||
|
||||
$elem.css('-ms-animation-iteration-count') ||
|
||||
'1';
|
||||
|
||||
var max;
|
||||
var len;
|
||||
var num;
|
||||
var i;
|
||||
|
||||
duration = duration.split(', ');
|
||||
delay = delay.split(', ');
|
||||
iterationCount = iterationCount.split(', ');
|
||||
|
||||
// The 'duration' size is the same as the 'delay' size
|
||||
for (i = 0, len = duration.length, max = Number.NEGATIVE_INFINITY; i < len; i++) {
|
||||
num = parseFloat(duration[i]) * parseInt(iterationCount[i], 10) + parseFloat(delay[i]);
|
||||
|
||||
if (num > max) {
|
||||
max = num;
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a scrollbar width
|
||||
* @private
|
||||
* @returns {Number}
|
||||
*/
|
||||
function getScrollbarWidth() {
|
||||
if ($(document).height() <= $(window).height()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var outer = document.createElement('div');
|
||||
var inner = document.createElement('div');
|
||||
var widthNoScroll;
|
||||
var widthWithScroll;
|
||||
|
||||
outer.style.visibility = 'hidden';
|
||||
outer.style.width = '100px';
|
||||
document.body.appendChild(outer);
|
||||
|
||||
widthNoScroll = outer.offsetWidth;
|
||||
|
||||
// Force scrollbars
|
||||
outer.style.overflow = 'scroll';
|
||||
|
||||
// Add inner div
|
||||
inner.style.width = '100%';
|
||||
outer.appendChild(inner);
|
||||
|
||||
widthWithScroll = inner.offsetWidth;
|
||||
|
||||
// Remove divs
|
||||
outer.parentNode.removeChild(outer);
|
||||
|
||||
return widthNoScroll - widthWithScroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks the screen
|
||||
* @private
|
||||
*/
|
||||
function lockScreen() {
|
||||
if (IS_IOS) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $html = $('html');
|
||||
var lockedClass = namespacify('is-locked');
|
||||
var paddingRight;
|
||||
var $body;
|
||||
|
||||
if (!$html.hasClass(lockedClass)) {
|
||||
$body = $(document.body);
|
||||
|
||||
// Zepto does not support '-=', '+=' in the `css` method
|
||||
paddingRight = parseInt($body.css('padding-right'), 10) + getScrollbarWidth();
|
||||
|
||||
$body.css('padding-right', paddingRight + 'px');
|
||||
$html.addClass(lockedClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks the screen
|
||||
* @private
|
||||
*/
|
||||
function unlockScreen() {
|
||||
if (IS_IOS) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $html = $('html');
|
||||
var lockedClass = namespacify('is-locked');
|
||||
var paddingRight;
|
||||
var $body;
|
||||
|
||||
if ($html.hasClass(lockedClass)) {
|
||||
$body = $(document.body);
|
||||
|
||||
// Zepto does not support '-=', '+=' in the `css` method
|
||||
paddingRight = parseInt($body.css('padding-right'), 10) - getScrollbarWidth();
|
||||
|
||||
$body.css('padding-right', paddingRight + 'px');
|
||||
$html.removeClass(lockedClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a state for an instance
|
||||
* @private
|
||||
* @param {Remodal} instance
|
||||
* @param {STATES} state
|
||||
* @param {Boolean} isSilent If true, Remodal does not trigger events
|
||||
* @param {String} Reason of a state change.
|
||||
*/
|
||||
function setState(instance, state, isSilent, reason) {
|
||||
|
||||
var newState = namespacify('is', state);
|
||||
var allStates = [namespacify('is', STATES.CLOSING),
|
||||
namespacify('is', STATES.OPENING),
|
||||
namespacify('is', STATES.CLOSED),
|
||||
namespacify('is', STATES.OPENED)].join(' ');
|
||||
|
||||
instance.$bg
|
||||
.removeClass(allStates)
|
||||
.addClass(newState);
|
||||
|
||||
instance.$overlay
|
||||
.removeClass(allStates)
|
||||
.addClass(newState);
|
||||
|
||||
instance.$wrapper
|
||||
.removeClass(allStates)
|
||||
.addClass(newState);
|
||||
|
||||
instance.$modal
|
||||
.removeClass(allStates)
|
||||
.addClass(newState);
|
||||
|
||||
instance.state = state;
|
||||
!isSilent && instance.$modal.trigger({
|
||||
type: state,
|
||||
reason: reason
|
||||
}, [{ reason: reason }]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes with the animation
|
||||
* @param {Function} doBeforeAnimation
|
||||
* @param {Function} doAfterAnimation
|
||||
* @param {Remodal} instance
|
||||
*/
|
||||
function syncWithAnimation(doBeforeAnimation, doAfterAnimation, instance) {
|
||||
var runningAnimationsCount = 0;
|
||||
|
||||
var handleAnimationStart = function(e) {
|
||||
if (e.target !== this) {
|
||||
return;
|
||||
}
|
||||
|
||||
runningAnimationsCount++;
|
||||
};
|
||||
|
||||
var handleAnimationEnd = function(e) {
|
||||
if (e.target !== this) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (--runningAnimationsCount === 0) {
|
||||
|
||||
// Remove event listeners
|
||||
$.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
|
||||
instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
|
||||
});
|
||||
|
||||
doAfterAnimation();
|
||||
}
|
||||
};
|
||||
|
||||
$.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
|
||||
instance[elemName]
|
||||
.on(ANIMATIONSTART_EVENTS, handleAnimationStart)
|
||||
.on(ANIMATIONEND_EVENTS, handleAnimationEnd);
|
||||
});
|
||||
|
||||
doBeforeAnimation();
|
||||
|
||||
// If the animation is not supported by a browser or its duration is 0
|
||||
if (
|
||||
getAnimationDuration(instance.$bg) === 0 &&
|
||||
getAnimationDuration(instance.$overlay) === 0 &&
|
||||
getAnimationDuration(instance.$wrapper) === 0 &&
|
||||
getAnimationDuration(instance.$modal) === 0
|
||||
) {
|
||||
|
||||
// Remove event listeners
|
||||
$.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
|
||||
instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
|
||||
});
|
||||
|
||||
doAfterAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes immediately
|
||||
* @private
|
||||
* @param {Remodal} instance
|
||||
*/
|
||||
function halt(instance) {
|
||||
if (instance.state === STATES.CLOSED) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.each(['$bg', '$overlay', '$wrapper', '$modal'], function(index, elemName) {
|
||||
instance[elemName].off(ANIMATIONSTART_EVENTS + ' ' + ANIMATIONEND_EVENTS);
|
||||
});
|
||||
|
||||
removeModal(instance);
|
||||
instance.$bg.removeClass(instance.settings.modifier);
|
||||
instance.$overlay.removeClass(instance.settings.modifier).hide();
|
||||
instance.$wrapper.hide();
|
||||
|
||||
if (openModals.length === 0) {
|
||||
unlockScreen();
|
||||
}
|
||||
|
||||
setState(instance, STATES.CLOSED, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string with options
|
||||
* @private
|
||||
* @param str
|
||||
* @returns {Object}
|
||||
*/
|
||||
function parseOptions(str) {
|
||||
var obj = {};
|
||||
var arr;
|
||||
var len;
|
||||
var val;
|
||||
var i;
|
||||
|
||||
// Remove spaces before and after delimiters
|
||||
str = str.replace(/\s*:\s*/g, ':').replace(/\s*,\s*/g, ',');
|
||||
|
||||
// Parse a string
|
||||
arr = str.split(',');
|
||||
for (i = 0, len = arr.length; i < len; i++) {
|
||||
arr[i] = arr[i].split(':');
|
||||
val = arr[i][1];
|
||||
|
||||
// Convert a string value if it is like a boolean
|
||||
if (typeof val === 'string' || val instanceof String) {
|
||||
val = val === 'true' || (val === 'false' ? false : val);
|
||||
}
|
||||
|
||||
// Convert a string value if it is like a number
|
||||
if (typeof val === 'string' || val instanceof String) {
|
||||
val = !isNaN(val) ? +val : val;
|
||||
}
|
||||
|
||||
obj[arr[i][0]] = val;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string separated by dashes and prefixed with NAMESPACE
|
||||
* @private
|
||||
* @param {...String}
|
||||
* @returns {String}
|
||||
*/
|
||||
function namespacify() {
|
||||
var result = NAMESPACE;
|
||||
|
||||
for (var i = 0; i < arguments.length; ++i) {
|
||||
result += '-' + arguments[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the hashchange event
|
||||
* @private
|
||||
* @listens hashchange
|
||||
*/
|
||||
function handleHashChangeEvent() {
|
||||
var id = location.hash.replace('#', '');
|
||||
var instance;
|
||||
var $elem;
|
||||
|
||||
var current = currentModal();
|
||||
|
||||
if (!id) {
|
||||
|
||||
// Check if we have currently opened modal and animation was completed
|
||||
if (current && current.state === STATES.OPENED && current.settings.hashTracking) {
|
||||
current.close();
|
||||
}
|
||||
} else {
|
||||
|
||||
if (!current || current.id !== id) {
|
||||
// Catch syntax error if your hash is bad
|
||||
try {
|
||||
$elem = $(
|
||||
'[data-' + PLUGIN_NAME + '-id="' + id + '"]'
|
||||
);
|
||||
} catch (err) {
|
||||
}
|
||||
|
||||
if ($elem && $elem.length) {
|
||||
instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)];
|
||||
|
||||
if (instance && instance.settings.hashTracking) {
|
||||
instance.open();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function currentModal() {
|
||||
return openModals[openModals.length - 1];
|
||||
}
|
||||
|
||||
function removeModal(remodal) {
|
||||
var index = openModals.indexOf(remodal);
|
||||
|
||||
if (index >= 0) {
|
||||
openModals.slice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remodal constructor
|
||||
* @constructor
|
||||
* @param {jQuery} $modal
|
||||
* @param {Object} options
|
||||
*/
|
||||
function Remodal($modal, options) {
|
||||
var $body = $(document.body);
|
||||
var $appendTo = $body;
|
||||
var remodal = this;
|
||||
|
||||
remodal.id = $modal.attr('data-' + PLUGIN_NAME + '-id');
|
||||
remodal.settings = $.extend({}, DEFAULTS, options);
|
||||
remodal.index = $[PLUGIN_NAME].lookup.push(remodal) - 1;
|
||||
remodal.state = STATES.CLOSED;
|
||||
|
||||
// remodal.$overlay = $('.' + namespacify('overlay'));
|
||||
|
||||
if (remodal.settings.appendTo !== null && remodal.settings.appendTo.length) {
|
||||
$appendTo = $(remodal.settings.appendTo);
|
||||
}
|
||||
|
||||
if (!remodal.$overlay) {
|
||||
remodal.$overlay = $('<div>').addClass(namespacify('overlay') + ' ' + namespacify('is', STATES.CLOSED)).hide();
|
||||
$appendTo.append(remodal.$overlay);
|
||||
}
|
||||
|
||||
remodal.$bg = $('.' + namespacify('bg')).addClass(namespacify('is', STATES.CLOSED));
|
||||
|
||||
remodal.$modal = $modal
|
||||
.addClass(
|
||||
NAMESPACE + ' ' +
|
||||
namespacify('is-initialized') + ' ' +
|
||||
remodal.settings.modifier + ' ' +
|
||||
namespacify('is', STATES.CLOSED))
|
||||
.attr('tabindex', '-1');
|
||||
|
||||
remodal.$wrapper = $('<div>')
|
||||
.addClass(
|
||||
namespacify('wrapper') + ' ' +
|
||||
remodal.settings.modifier + ' ' +
|
||||
namespacify('is', STATES.CLOSED))
|
||||
.hide()
|
||||
.append(remodal.$modal);
|
||||
$appendTo.append(remodal.$wrapper);
|
||||
|
||||
// Add the event listener for the close button
|
||||
remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="close"]', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
remodal.close();
|
||||
});
|
||||
|
||||
// Add the event listener for the cancel button
|
||||
remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="cancel"]', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
remodal.$modal.trigger(STATE_CHANGE_REASONS.CANCELLATION);
|
||||
|
||||
if (remodal.settings.closeOnCancel) {
|
||||
remodal.close(STATE_CHANGE_REASONS.CANCELLATION);
|
||||
}
|
||||
});
|
||||
|
||||
// Add the event listener for the confirm button
|
||||
remodal.$wrapper.on('click.' + NAMESPACE, '[data-' + PLUGIN_NAME + '-action="confirm"]', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
remodal.$modal.trigger(STATE_CHANGE_REASONS.CONFIRMATION);
|
||||
|
||||
if (remodal.settings.closeOnConfirm) {
|
||||
remodal.close(STATE_CHANGE_REASONS.CONFIRMATION);
|
||||
}
|
||||
});
|
||||
|
||||
// Add the event listener for the overlay
|
||||
remodal.$wrapper.on('click.' + NAMESPACE, function(e) {
|
||||
var $target = $(e.target);
|
||||
var isWrapper = $target.hasClass(namespacify('wrapper'));
|
||||
var isWithin = $target.closest('.' + namespacify('is', STATES.OPENED)).length;
|
||||
|
||||
if (!isWrapper && isWithin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (remodal.settings.closeOnOutsideClick) {
|
||||
remodal.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a modal window
|
||||
* @public
|
||||
*/
|
||||
Remodal.prototype.open = function() {
|
||||
var remodal = this;
|
||||
var current;
|
||||
var modalCount;
|
||||
|
||||
// Check if the animation was completed
|
||||
if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING) {
|
||||
return;
|
||||
}
|
||||
|
||||
// id = remodal.$modal.attr('data-' + PLUGIN_NAME + '-id');
|
||||
|
||||
if (remodal.id && remodal.settings.hashTracking) {
|
||||
scrollTop = $(window).scrollTop();
|
||||
location.hash = remodal.id;
|
||||
}
|
||||
|
||||
if (!remodal.settings.stack) {
|
||||
current = currentModal();
|
||||
if (current && current !== remodal) {
|
||||
halt(current);
|
||||
}
|
||||
}
|
||||
|
||||
modalCount = openModals.push(remodal);
|
||||
remodal.$overlay.css('z-index', function(_, value) { return parseInt(value, 10) + modalCount; });
|
||||
remodal.$wrapper.css('z-index', function(_, value) { return parseInt(value, 10) + modalCount; });
|
||||
|
||||
lockScreen();
|
||||
remodal.$bg.addClass(remodal.settings.modifier);
|
||||
remodal.$overlay.addClass(remodal.settings.modifier).show();
|
||||
remodal.$wrapper.show().scrollTop(0);
|
||||
remodal.$modal.focus();
|
||||
|
||||
syncWithAnimation(
|
||||
function() {
|
||||
setState(remodal, STATES.OPENING);
|
||||
},
|
||||
|
||||
function() {
|
||||
setState(remodal, STATES.OPENED);
|
||||
},
|
||||
|
||||
remodal);
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes a modal window
|
||||
* @public
|
||||
* @param {String} reason
|
||||
*/
|
||||
Remodal.prototype.close = function(reason) {
|
||||
var remodal = this;
|
||||
var current;
|
||||
|
||||
// Check if the animation was completed
|
||||
if (remodal.state === STATES.OPENING || remodal.state === STATES.CLOSING || remodal.state === STATES.CLOSED) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeModal(remodal);
|
||||
|
||||
if (
|
||||
remodal.settings.hashTracking &&
|
||||
remodal.id === location.hash.substr(1)
|
||||
) {
|
||||
current = currentModal();
|
||||
if (current) {
|
||||
location.hash = current.id;
|
||||
} else {
|
||||
location.hash = '';
|
||||
$(window).scrollTop(scrollTop);
|
||||
}
|
||||
}
|
||||
|
||||
syncWithAnimation(
|
||||
function() {
|
||||
setState(remodal, STATES.CLOSING, false, reason);
|
||||
},
|
||||
|
||||
function() {
|
||||
remodal.$bg.removeClass(remodal.settings.modifier);
|
||||
remodal.$overlay.removeClass(remodal.settings.modifier).hide();
|
||||
remodal.$wrapper.hide();
|
||||
|
||||
if (openModals.length === 0) {
|
||||
unlockScreen();
|
||||
}
|
||||
|
||||
setState(remodal, STATES.CLOSED, false, reason);
|
||||
},
|
||||
|
||||
remodal);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a current state of a modal
|
||||
* @public
|
||||
* @returns {STATES}
|
||||
*/
|
||||
Remodal.prototype.getState = function() {
|
||||
return this.state;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys a modal
|
||||
* @public
|
||||
*/
|
||||
Remodal.prototype.destroy = function() {
|
||||
var lookup = $[PLUGIN_NAME].lookup;
|
||||
var instanceCount;
|
||||
|
||||
halt(this);
|
||||
this.$wrapper.remove();
|
||||
|
||||
delete lookup[this.index];
|
||||
instanceCount = $.grep(lookup, function(instance) {
|
||||
return !!instance;
|
||||
}).length;
|
||||
|
||||
if (instanceCount === 0) {
|
||||
this.$overlay.remove();
|
||||
this.$bg.removeClass(
|
||||
namespacify('is', STATES.CLOSING) + ' ' +
|
||||
namespacify('is', STATES.OPENING) + ' ' +
|
||||
namespacify('is', STATES.CLOSED) + ' ' +
|
||||
namespacify('is', STATES.OPENED));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Special plugin object for instances
|
||||
* @public
|
||||
* @type {Object}
|
||||
*/
|
||||
$[PLUGIN_NAME] = {
|
||||
lookup: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Plugin constructor
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
* @returns {JQuery}
|
||||
*/
|
||||
$.fn[PLUGIN_NAME] = function(opts) {
|
||||
var instance;
|
||||
var $elem;
|
||||
|
||||
this.each(function(index, elem) {
|
||||
$elem = $(elem);
|
||||
|
||||
if ($elem.data(PLUGIN_NAME) == null) {
|
||||
instance = new Remodal($elem, opts);
|
||||
$elem.data(PLUGIN_NAME, instance.index);
|
||||
|
||||
if (
|
||||
instance.settings.hashTracking &&
|
||||
instance.id === location.hash.substr(1)
|
||||
) {
|
||||
instance.open();
|
||||
}
|
||||
} else {
|
||||
instance = $[PLUGIN_NAME].lookup[$elem.data(PLUGIN_NAME)];
|
||||
}
|
||||
});
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
// data-remodal-target opens a modal window with the special Id
|
||||
$(document).on('click', '[data-' + PLUGIN_NAME + '-target]', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var elem = e.currentTarget;
|
||||
var id = elem.getAttribute('data-' + PLUGIN_NAME + '-target');
|
||||
var $target = $('[data-' + PLUGIN_NAME + '-id="' + id + '"]');
|
||||
|
||||
$[PLUGIN_NAME].lookup[$target.data(PLUGIN_NAME)].open();
|
||||
});
|
||||
|
||||
// Auto initialization of modal windows
|
||||
// They should have the 'remodal' class attribute
|
||||
// Also you can write the `data-remodal-options` attribute to pass params into the modal
|
||||
$(document).find('.' + NAMESPACE).each(function(i, container) {
|
||||
var $container = $(container);
|
||||
var options = $container.data(PLUGIN_NAME + '-options');
|
||||
|
||||
if (!options) {
|
||||
options = {};
|
||||
} else if (typeof options === 'string' || options instanceof String) {
|
||||
options = parseOptions(options);
|
||||
}
|
||||
|
||||
$container[PLUGIN_NAME](options);
|
||||
});
|
||||
|
||||
// Handles the keydown event
|
||||
$(document).on('keydown.' + NAMESPACE, function(e) {
|
||||
var current = currentModal();
|
||||
|
||||
if (current && current.settings.closeOnEscape && current.state === STATES.OPENED && e.keyCode === 27) {
|
||||
current.close();
|
||||
}
|
||||
});
|
||||
|
||||
// Handles the hashchange event
|
||||
$(window).on('hashchange.' + NAMESPACE, handleHashChangeEvent);
|
||||
});
|
||||
});
|
||||
@@ -1,40 +1 @@
|
||||
import $ from 'jquery';
|
||||
import GeminiScrollbar from 'gemini-scrollbar';
|
||||
|
||||
const defaults = {
|
||||
autoshow: false,
|
||||
createElements: true,
|
||||
forceGemini: false
|
||||
};
|
||||
|
||||
export default class Scrollbar {
|
||||
constructor(element, options) {
|
||||
this.element = $(element);
|
||||
this.created = false;
|
||||
if (!this.element.length) { return; }
|
||||
|
||||
this.options = Object.assign({}, defaults, options, { element: this.element[0] });
|
||||
|
||||
this.element.css('overflow', 'auto');
|
||||
this.instance = new GeminiScrollbar(this.options);
|
||||
this.create();
|
||||
this.element.data('scrollbar', this.instance);
|
||||
}
|
||||
|
||||
create() {
|
||||
this.instance.create();
|
||||
this.created = true;
|
||||
}
|
||||
|
||||
update() {
|
||||
if (!this.created) { return false; }
|
||||
this.instance.update();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (!this.created) { return false; }
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export let Instance = new Scrollbar('#admin-main .content-wrapper');
|
||||
import 'simplebar';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import $ from 'jquery';
|
||||
import Scrollbar from './scrollbar';
|
||||
import Map from 'es6-map';
|
||||
import Cookies from '../utils/cookies';
|
||||
|
||||
const MOBILE_BREAKPOINT = 48 - 0.062;
|
||||
const DESKTOP_BREAKPOINT = 75 + 0.063;
|
||||
@@ -17,7 +17,6 @@ export default class Sidebar {
|
||||
this.isOpen = false;
|
||||
this.body = $('body');
|
||||
this.matchMedia = global.matchMedia(MOBILE_QUERY);
|
||||
this.scroller = new Scrollbar('.admin-menu-wrapper', { autoshow: true });
|
||||
this.enable();
|
||||
}
|
||||
|
||||
@@ -79,7 +78,6 @@ export default class Sidebar {
|
||||
if (event) { event.preventDefault(); }
|
||||
let overlay = $('#overlay');
|
||||
let sidebar = $('#admin-sidebar');
|
||||
let scrollbar = $('#admin-menu').data('scrollbar');
|
||||
|
||||
this.body.addClass('sidebar-mobile-open');
|
||||
overlay.css('display', 'block');
|
||||
@@ -94,15 +92,12 @@ export default class Sidebar {
|
||||
sidebar.css({ display: 'block', opacity: 1 });
|
||||
this.isOpen = true;
|
||||
}
|
||||
|
||||
if (scrollbar) { scrollbar.update(); }
|
||||
}
|
||||
|
||||
close(event, quick = false) {
|
||||
if (event) { event.preventDefault(); }
|
||||
let overlay = $('#overlay');
|
||||
let sidebar = $('#admin-sidebar');
|
||||
let scrollbar = $('#admin-menu').data('scrollbar');
|
||||
|
||||
this.body.removeClass('sidebar-mobile-open');
|
||||
overlay.css('display', 'none');
|
||||
@@ -118,8 +113,6 @@ export default class Sidebar {
|
||||
sidebar.css({ opacity: 0, display: 'none' });
|
||||
this.isOpen = false;
|
||||
}
|
||||
|
||||
if (scrollbar) { scrollbar.update(); }
|
||||
}
|
||||
|
||||
toggle(event) {
|
||||
@@ -131,6 +124,7 @@ export default class Sidebar {
|
||||
if (event) { event.preventDefault(); }
|
||||
clearTimeout(this.timeout);
|
||||
let isDesktop = global.matchMedia(DESKTOP_QUERY).matches;
|
||||
let cookie = null;
|
||||
|
||||
if (isDesktop) {
|
||||
this.body.removeClass('sidebar-open');
|
||||
@@ -143,6 +137,14 @@ export default class Sidebar {
|
||||
|
||||
this.body.toggleClass(`sidebar-${isDesktop ? 'closed' : 'open'}`);
|
||||
$(global).trigger('sidebar_state._grav', isDesktop);
|
||||
|
||||
if (isDesktop) {
|
||||
cookie = !this.body.hasClass('sidebar-closed');
|
||||
} else {
|
||||
cookie = this.body.hasClass('sidebar-open');
|
||||
}
|
||||
|
||||
Cookies.set('grav-admin-sidebar', cookie, { expires: Infinity });
|
||||
}
|
||||
|
||||
checkMatch(data) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import $ from 'jquery';
|
||||
import Cookies from 'cookies-js';
|
||||
import Cookies from '../utils/cookies';
|
||||
import { Instance as Editors } from '../forms/fields/editor';
|
||||
|
||||
let Data = JSON.parse(Cookies.get('grav-tabs-state') || '{}');
|
||||
|
||||
25
themes/grav/app/whitelabel/compile.js
Normal file
25
themes/grav/app/whitelabel/compile.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { config } from 'grav-config';
|
||||
import request from '../utils/request';
|
||||
|
||||
export default ({ preview = false, exportScss = false, color_scheme = {}, fonts = {}, callback = () => {} } = {}) => {
|
||||
let task = exportScss ? 'exportScss' : 'compileScss';
|
||||
// console.log(config);
|
||||
const URI = `${config.base_url_relative}.json/task:${task}`;
|
||||
request(URI, {
|
||||
method: 'post',
|
||||
body: Object.assign({}, preview ? { preview } : null, color_scheme)
|
||||
}, callback);
|
||||
};
|
||||
|
||||
export const prepareElement = (element) => {
|
||||
element.data('busy_right_now', true);
|
||||
if (!element.data('current_icon')) {
|
||||
element.data('current_icon', element.find('.fa').attr('class'));
|
||||
}
|
||||
element.find('.fa').attr('class', 'fa fa-fw fa-spin fa-refresh');
|
||||
};
|
||||
|
||||
export const resetElement = (element) => {
|
||||
element.data('busy_right_now', false);
|
||||
element.find('.fa').attr('class', element.data('current_icon'));
|
||||
};
|
||||
93
themes/grav/app/whitelabel/index.js
Normal file
93
themes/grav/app/whitelabel/index.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import $ from 'jquery';
|
||||
import Compile, { prepareElement, resetElement } from './compile';
|
||||
import Forms from '../forms';
|
||||
import { hex2rgb } from '../utils/colors';
|
||||
import './presets';
|
||||
|
||||
const body = $('body');
|
||||
const FormState = Forms.FormState.Instance;
|
||||
const compiler = (element, preview = false, exportScss = false, callback = () => {}) => {
|
||||
prepareElement(element);
|
||||
|
||||
let fields = FormState.collect();
|
||||
Compile({
|
||||
preview,
|
||||
exportScss,
|
||||
color_scheme: !fields ? [] : fields.filter((value, key) => key.match(/^data\[whitelabel]\[color_scheme]/)).toJS(),
|
||||
callback: (response) => {
|
||||
callback.call(callback, response);
|
||||
resetElement(element);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
body.on('click', '[data-preview-scss]', (event) => {
|
||||
event && event.preventDefault();
|
||||
let element = $(event.currentTarget);
|
||||
if (element.data('busy_right_now')) { return false; }
|
||||
|
||||
compiler(element, true, false, (response) => {
|
||||
if (response.files) {
|
||||
Object.keys(response.files).forEach((key) => {
|
||||
let file = $(`#admin-pro-preview-${key}`);
|
||||
let timestamp = Date.now();
|
||||
if (!file.length) {
|
||||
file = $(`<link id="admin-pro-preview-${key}" type="text/css" rel="stylesheet" />`);
|
||||
$('head').append(file);
|
||||
|
||||
if (!$('[data-reset-scss]').length) {
|
||||
let reset = $('<button class="button" data-reset-scss style="margin-left: 5px;"><i class="fa fa-fw fa-history"></i> Reset</button>');
|
||||
reset.insertAfter(element);
|
||||
}
|
||||
}
|
||||
|
||||
file.attr('href', `${response.files[key]}?${timestamp}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
body.on('click', '[data-recompile-scss]', (event) => {
|
||||
event && event.preventDefault();
|
||||
let element = $(event.currentTarget);
|
||||
if (element.data('busy_right_now')) { return false; }
|
||||
|
||||
compiler(element, true, false);
|
||||
});
|
||||
|
||||
body.on('click', '[data-export-scss]', (event) => {
|
||||
event && event.preventDefault();
|
||||
let element = $(event.currentTarget);
|
||||
if (element.data('busy_right_now')) { return false; }
|
||||
|
||||
compiler(element, true, true, (response) => {
|
||||
if (response.files) {
|
||||
Object.keys(response.files).forEach((key) => {
|
||||
if (key === 'download') {
|
||||
let element = document.createElement('a');
|
||||
element.setAttribute('href', response.files[key]);
|
||||
element.setAttribute('download', '');
|
||||
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
body.on('change._grav_colorpicker', '[data-grav-colorpicker]', (event, input, hex, opacity) => {
|
||||
let RGB = hex2rgb(hex);
|
||||
let YIQ = ((RGB.r * 299) + (RGB.g * 587) + (RGB.b * 114)) / 1000;
|
||||
let contrast = YIQ >= 128 || opacity <= 0.50 ? 'dark' : 'light';
|
||||
|
||||
input.parent().removeClass('dark-text light-text').addClass(`${contrast}-text`);
|
||||
});
|
||||
|
||||
body.ready(() => {
|
||||
$('[data-grav-colorpicker]').trigger('keyup');
|
||||
});
|
||||
169
themes/grav/app/whitelabel/presets.js
Normal file
169
themes/grav/app/whitelabel/presets.js
Normal file
@@ -0,0 +1,169 @@
|
||||
import $ from 'jquery';
|
||||
import Forms from '../forms';
|
||||
|
||||
let body = $('body');
|
||||
let fields = [];
|
||||
const FormState = Forms.FormState.Instance;
|
||||
const setField = (field, value) => {
|
||||
let name = field.prop('name');
|
||||
let tag = field.prop('tagName').toLowerCase();
|
||||
let type = field.prop('type');
|
||||
|
||||
fields.push(name);
|
||||
switch (tag) {
|
||||
case 'select':
|
||||
field.val(value);
|
||||
field.data('selectize').setValue(value);
|
||||
field.trigger('change');
|
||||
break;
|
||||
case 'input':
|
||||
if (type === 'radio') {
|
||||
let strValue = value ? '1' : '0';
|
||||
field.filter((index, radio) => $(radio).val() === strValue).prop('checked', true);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (type === 'checkbox') {
|
||||
field.prop('checked', value);
|
||||
break;
|
||||
}
|
||||
field.val(value);
|
||||
field.trigger('keyup');
|
||||
}
|
||||
};
|
||||
|
||||
body.on('click', '[data-preset-values]', (event) => {
|
||||
let target = $(event.currentTarget);
|
||||
let data = target.data('preset-values');
|
||||
|
||||
Object.keys(data).forEach((section) => {
|
||||
if (typeof data[section] === 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(data[section]).forEach((key) => {
|
||||
let field = $(`[name="data[whitelabel][color_scheme][${section}][${key}]"], [name="data[${section}][${key}]"]`);
|
||||
let value = data[section][key];
|
||||
setField(field, value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
body.on('click', '[data-reset-scss]', (event) => {
|
||||
event && event.preventDefault();
|
||||
let element = $(event.currentTarget);
|
||||
let links = $('link[id^=admin-pro-preview-]');
|
||||
|
||||
element.remove();
|
||||
links.remove();
|
||||
|
||||
fields.forEach((field) => {
|
||||
let value = FormState.loadState.get(field);
|
||||
setField($(`[name="${field}"]`), value);
|
||||
});
|
||||
fields = [];
|
||||
});
|
||||
|
||||
// Horizontal Scroll Functionality
|
||||
$.fn.hscrollarrows = function() {
|
||||
return this.each(function() {
|
||||
|
||||
let navNext = $('<a class="nav-next hide"></a>');
|
||||
let navPrev = $('<a class="nav-prev hide"></a>');
|
||||
let scrollTime = null;
|
||||
let resizeTime = null;
|
||||
let scrolling = false;
|
||||
|
||||
let elm_w = 0;
|
||||
let elem_data_w = 0;
|
||||
let max_scroll = 0;
|
||||
let inc_scroll = 0;
|
||||
|
||||
let calcData = function() {
|
||||
elm_w = elem.width();
|
||||
elem_data_w = elem_data.get(0).scrollWidth;
|
||||
max_scroll = elem_data_w - elm_w;
|
||||
inc_scroll = elm_w * 0.3; // 20%
|
||||
};
|
||||
|
||||
let revalidate = function() {
|
||||
calcData();
|
||||
stateNavs();
|
||||
};
|
||||
|
||||
let run = function() {
|
||||
calcData();
|
||||
setupNavs();
|
||||
};
|
||||
|
||||
let setupNavs = function() {
|
||||
|
||||
elem.parent().prepend(navNext);
|
||||
elem.parent().prepend(navPrev);
|
||||
navNext.on('click', next);
|
||||
navPrev.on('click', prev);
|
||||
stateNavs();
|
||||
|
||||
$(elem).scroll(function() {
|
||||
if (!scrolling) {
|
||||
clearTimeout(scrollTime);
|
||||
scrollTime = setTimeout(function() {
|
||||
stateNavs();
|
||||
}, 250);
|
||||
}
|
||||
});
|
||||
|
||||
$(window).resize(function() {
|
||||
clearTimeout(resizeTime);
|
||||
resizeTime = setTimeout(function() {
|
||||
revalidate();
|
||||
}, 250);
|
||||
});
|
||||
};
|
||||
|
||||
let stateNavs = function() {
|
||||
let current_scroll = elem.scrollLeft();
|
||||
if (current_scroll < max_scroll) {
|
||||
navNext.removeClass('hide');
|
||||
} else {
|
||||
navNext.addClass('hide');
|
||||
}
|
||||
if (current_scroll > 0) {
|
||||
navPrev.removeClass('hide');
|
||||
} else {
|
||||
navPrev.addClass('hide');
|
||||
}
|
||||
scrolling = false;
|
||||
};
|
||||
|
||||
let next = function() {
|
||||
let current_scroll = elem.scrollLeft();
|
||||
if (current_scroll < max_scroll) {
|
||||
scrolling = true;
|
||||
elem.stop().animate({
|
||||
scrollLeft: (current_scroll + inc_scroll)
|
||||
}, stateNavs);
|
||||
}
|
||||
};
|
||||
|
||||
let prev = function() {
|
||||
let current_scroll = elem.scrollLeft();
|
||||
if (current_scroll > 0) {
|
||||
scrolling = true;
|
||||
elem.stop().animate({
|
||||
scrollLeft: (current_scroll - inc_scroll)
|
||||
}, stateNavs);
|
||||
}
|
||||
};
|
||||
|
||||
let elem = $(this);
|
||||
let elem_data = $(':first-child', elem);
|
||||
run();
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
$(document).ready(() => {
|
||||
$('.jquery-horizontal-scroll').hscrollarrows();
|
||||
});
|
||||
2
themes/grav/css-compiled/fonts.css
vendored
2
themes/grav/css-compiled/fonts.css
vendored
@@ -1,3 +1,3 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500|Inconsolata:400,700|&subset=latin-ext");body,h5,h6,.badge,.note,.grav-mdeditor-preview,input,select,textarea,button,.selectize-input{font-family:"Roboto","Helvetica","Tahoma","Geneva","Arial",sans-serif}h1,h2,h3,h4,.form-tabs>label,.label{font-family:"Roboto","Helvetica","Tahoma","Geneva","Arial",sans-serif}code,kbd,pre,samp,body .CodeMirror{font-family:"Inconsolata","Monaco","Consolas","Lucida Console",monospace !important}
|
||||
@import url("https://fonts.googleapis.com/css?family=Josefin+Slab:400|Roboto:300,400,500|Inconsolata:400,700&subset=latin-ext");body,h5,h6,.badge,.note,.grav-mdeditor-preview,input,select,textarea,button,.selectize-input{font-family:"Roboto","Helvetica","Tahoma","Geneva","Arial",sans-serif}h1,h2,h3,h4,.form-tabs>label,.label{font-family:"Josefin Slab","Helvetica","Tahoma","Geneva","Arial",sans-serif}code,kbd,pre,samp,body .CodeMirror{font-family:"Inconsolata","Monaco","Consolas","Lucida Console",monospace !important}
|
||||
|
||||
/*# sourceMappingURL=../css-compiled/fonts.css.map */
|
||||
@@ -1,11 +1 @@
|
||||
{
|
||||
"version": 3,
|
||||
"file": "../scss/fonts.css",
|
||||
"sources": [
|
||||
"../scss/fonts.scss",
|
||||
"../hdr0",
|
||||
"../scss/configuration/fonts/_support.scss"
|
||||
],
|
||||
"mappings": "AE+DA,OAAO,CAAC,uGAAI,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,QAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CAAE,AAAA,EAAE,CACd,AAAa,UAAH,CAAG,KAAK,CAAE,AAAA,MAAM,AAAC,CACvB,WAAW,CAAE,QAAU,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": []
|
||||
}
|
||||
{"version":3,"file":"fonts.css","sources":["fonts.scss","configuration/fonts/_support.scss"],"sourcesContent":["$fonts-header: 'Josefin Slab' !default;\n$fonts-default: 'Roboto' !default;\n$fonts-mono: 'Inconsolata' !default;\n\n$font-definitions: (\n 'Josefin Slab': '400',\n Roboto: '300,400,500',\n Inconsolata: '400,700'\n);\n\n@import \"configuration/fonts/support\";\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: \"https://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 + \"&subset=latin-ext\";\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 .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,uHAAI,CAtBR,AAAA,IAAI,CAAE,EAAE,CAAE,EAAE,CACZ,MAAM,CAAE,KAAK,CAAE,sBAAsB,CACrC,KAAK,CAAE,MAAM,CAAE,QAAQ,CAAE,MAAM,CAAE,gBAAgB,AAAC,CAC9C,WAAW,CAAE,QAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,EAAE,CAAE,EAAE,CAAE,EAAE,CAAE,EAAE,CACd,UAAU,CAAG,KAAK,CAAE,MAAM,AAAC,CACvB,WAAW,CAAE,cAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,IAAI,CAAE,GAAG,CAAE,GAAG,CAAE,IAAI,CACpB,IAAI,CAAC,WAAW,AAAC,CACb,WAAW,CAAE,aAAU,CAAE,QAAQ,CAAE,UAAU,CAAE,gBAAgB,CAAE,SAAS,CAAC,UAAU,CACxF"}
|
||||
5
themes/grav/css-compiled/nucleus.css
vendored
5
themes/grav/css-compiled/nucleus.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
themes/grav/css-compiled/paper.css.map
Normal file
1
themes/grav/css-compiled/paper.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"paper.css","sources":["paper.scss"],"sourcesContent":["// CodeMirror Theme\n.cm-s-paper {\n $paper-fg: #666;\n $tag: darken($paper-fg, 10%);\n\n &.CodeMirror {\n color: $paper-fg;\n line-height: 1.4;\n font-size: 16px;\n }\n\n .cm-link {color: #0082ba;}\n .cm-comment {color: lighten($paper-fg,10%);}\n .cm-header {color: darken($paper-fg,5%);}\n .cm-strong {color: darken($paper-fg,10%);}\n .cm-em {color: darken($paper-fg, 10%);}\n .cm-string {color: #0082BA;}\n .cm-tag {color: $tag}\n .cm-bracket {color: lighten($tag, 10%);}\n .cm-hr {color: lighten($paper-fg,40%);font-weight: bold;}\n\n .cm-keyword {color: #0082ba;}\n .cm-atom {color: #9055af;}\n .cm-meta {color: $paper-fg;}\n\n .cm-number {color: #7f8c8d;}\n .cm-def {color: #00f;}\n .cm-variable {color: black;}\n //.cm-variable-2 {color: #555;}\n //.cm-variable-3 {color: #085;}\n .cm-property {color: black;}\n .cm-operator {color: black;}\n\n\n .cm-string-2 {color: #f50;}\n .cm-meta {color: #555;}\n .cm-error {color: #f00;}\n .cm-qualifier {color: #555;}\n .cm-builtin {color: #555;}\n\n\n .cm-attribute {color: #7f8c8d;}\n\n .cm-quote {color: #888;}\n\n\n .cm-header-1 {font-size: 140%;}\n .cm-header-2 {font-size: 120%;}\n .cm-header-3 {font-size: 110%;}\n\n .cm-negative {color: #d44;}\n .cm-positive {color: #292;}\n .cm-header, .cm-strong {font-weight: bold;}\n .cm-em {font-style: italic;}\n .cm-link {text-decoration: underline;}\n\n .cm-invalidchar {color: #f00;}\n}\n"],"names":[],"mappings":"AACA,AAII,WAJO,AAIN,WAAW,AAAC,CACT,KAAK,CAJE,IAAI,CAKX,WAAW,CAAE,GAAG,CAChB,SAAS,CAAE,IAAI,CAClB,AARL,AAUI,WAVO,CAUP,QAAQ,AAAC,CAAC,KAAK,CAAE,OAAO,CAAG,AAV/B,AAWI,WAXO,CAWP,WAAW,AAAC,CAAC,KAAK,CAAE,IAAsB,CAAG,AAXjD,AAYI,WAZO,CAYP,UAAU,AAAC,CAAC,KAAK,CAAE,OAAoB,CAAG,AAZ9C,AAaI,WAbO,CAaP,UAAU,AAAC,CAAC,KAAK,CAAE,OAAqB,CAAG,AAb/C,AAcI,WAdO,CAcP,MAAM,AAAC,CAAC,KAAK,CAAE,OAAsB,CAAG,AAd5C,AAeI,WAfO,CAeP,UAAU,AAAC,CAAC,KAAK,CAAE,OAAO,CAAG,AAfjC,AAgBI,WAhBO,CAgBP,OAAO,AAAC,CAAC,KAAK,CAdR,OAAsB,CAcN,AAhB1B,AAiBI,WAjBO,CAiBP,WAAW,AAAC,CAAC,KAAK,CAAE,IAAkB,CAAG,AAjB7C,AAkBI,WAlBO,CAkBP,MAAM,AAAC,CAAC,KAAK,CAAE,IAAsB,CAAC,WAAW,CAAE,IAAI,CAAG,AAlB9D,AAoBI,WApBO,CAoBP,WAAW,AAAC,CAAC,KAAK,CAAE,OAAO,CAAG,AApBlC,AAqBI,WArBO,CAqBP,QAAQ,AAAC,CAAC,KAAK,CAAE,OAAO,CAAG,AArB/B,AAsBI,WAtBO,CAsBP,QAAQ,AAAC,CAAC,KAAK,CArBJ,IAAI,CAqBc,AAtBjC,AAwBI,WAxBO,CAwBP,UAAU,AAAC,CAAC,KAAK,CAAE,OAAO,CAAG,AAxBjC,AAyBI,WAzBO,CAyBP,OAAO,AAAC,CAAC,KAAK,CAAE,IAAI,CAAG,AAzB3B,AA0BI,WA1BO,CA0BP,YAAY,AAAC,CAAC,KAAK,CAAE,KAAK,CAAG,AA1BjC,AA6BI,WA7BO,CA6BP,YAAY,AAAC,CAAC,KAAK,CAAE,KAAK,CAAG,AA7BjC,AA8BI,WA9BO,CA8BP,YAAY,AAAC,CAAC,KAAK,CAAE,KAAK,CAAG,AA9BjC,AAiCI,WAjCO,CAiCP,YAAY,AAAC,CAAC,KAAK,CAAE,IAAI,CAAG,AAjChC,AAkCI,WAlCO,CAkCP,QAAQ,AAAC,CAAC,KAAK,CAAE,IAAI,CAAG,AAlC5B,AAmCI,WAnCO,CAmCP,SAAS,AAAC,CAAC,KAAK,CAAE,IAAI,CAAG,AAnC7B,AAoCI,WApCO,CAoCP,aAAa,AAAC,CAAC,KAAK,CAAE,IAAI,CAAG,AApCjC,AAqCI,WArCO,CAqCP,WAAW,AAAC,CAAC,KAAK,CAAE,IAAI,CAAG,AArC/B,AAwCI,WAxCO,CAwCP,aAAa,AAAC,CAAC,KAAK,CAAE,OAAO,CAAG,AAxCpC,AA0CI,WA1CO,CA0CP,SAAS,AAAC,CAAC,KAAK,CAAE,IAAI,CAAG,AA1C7B,AA6CI,WA7CO,CA6CP,YAAY,AAAC,CAAC,SAAS,CAAE,IAAI,CAAG,AA7CpC,AA8CI,WA9CO,CA8CP,YAAY,AAAC,CAAC,SAAS,CAAE,IAAI,CAAG,AA9CpC,AA+CI,WA/CO,CA+CP,YAAY,AAAC,CAAC,SAAS,CAAE,IAAI,CAAG,AA/CpC,AAiDI,WAjDO,CAiDP,YAAY,AAAC,CAAC,KAAK,CAAE,IAAI,CAAG,AAjDhC,AAkDI,WAlDO,CAkDP,YAAY,AAAC,CAAC,KAAK,CAAE,IAAI,CAAG,AAlDhC,AAmDI,WAnDO,CAmDP,UAAU,CAnDd,WAAW,CAmDK,UAAU,AAAC,CAAC,WAAW,CAAE,IAAI,CAAG,AAnDhD,AAoDI,WApDO,CAoDP,MAAM,AAAC,CAAC,UAAU,CAAE,MAAM,CAAG,AApDjC,AAqDI,WArDO,CAqDP,QAAQ,AAAC,CAAC,eAAe,CAAE,SAAS,CAAG,AArD3C,AAuDI,WAvDO,CAuDP,eAAe,AAAC,CAAC,KAAK,CAAE,IAAI,CAAG"}
|
||||
5
themes/grav/css-compiled/preset.css
vendored
5
themes/grav/css-compiled/preset.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
5
themes/grav/css-compiled/simple-fonts.css
vendored
5
themes/grav/css-compiled/simple-fonts.css
vendored
@@ -1,3 +1,2 @@
|
||||
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,body .CodeMirror pre{font-family:"Helvetica Neue", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif}code,kbd,pre,samp,.mono{font-family:"Monaco", "Consolas", "Lucida Console", monospace}
|
||||
|
||||
/*# sourceMappingURL=../css-compiled/simple-fonts.css.map */
|
||||
#admin-menu li,.badge,.fontfamily-sans .CodeMirror pre,.form-tabs>label,.grav-mdeditor-preview,.label,.note,.selectize-input,body,button,h1,h2,h3,h4,h5,h6,input,select,textarea{font-family:"Helvetica Neue",Helvetica,Tahoma,Geneva,Arial,sans-serif}.CodeMirror pre,.mono,code,kbd,pre,samp{font-family:SFMono-Regular,Consolas,"Liberation Mono",Menlo,Courier,monospace}
|
||||
/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNpbXBsZS1mb250cy5zY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUtBLGVBSkEsT0FHQSxpQ0FDZ0IsaUJBSkQsdUJBSXFCLE9BSjVCLE1BQ3lCLGlCQUZqQyxLQUV5QixPQUN6QixHQUFJLEdBQUksR0FBSSxHQUhOLEdBQUksR0FFVixNQUFPLE9BQVEsU0FJWCxZQUFhLGdCQUFBLENBQUEsU0FBQSxDQUFBLE1BQUEsQ0FBQSxNQUFBLENBQUEsS0FBQSxDQUFBLFdBQ2hCLGdCQUdxQixNQUF0QixLQUFNLElBQUssSUFBSyxLQUNaLFlBQWEsY0FBQSxDQUFBLFFBQUEsQ0FBQSxpQkFBQSxDQUFBLEtBQUEsQ0FBQSxPQUFBLENBQUEiLCJmaWxlIjoic2ltcGxlLWZvbnRzLmNzcyIsInNvdXJjZXNDb250ZW50IjpbImJvZHksIGg1LCBoNixcbi5iYWRnZSwgLm5vdGUsIC5ncmF2LW1kZWRpdG9yLXByZXZpZXcsXG5pbnB1dCwgc2VsZWN0LCB0ZXh0YXJlYSwgYnV0dG9uLCAuc2VsZWN0aXplLWlucHV0LFxuaDEsIGgyLCBoMywgaDQsXG4uZm9udGZhbWlseS1zYW5zIC5Db2RlTWlycm9yIHByZSxcbiNhZG1pbi1tZW51IGxpLCAuZm9ybS10YWJzID4gbGFiZWwsIC5sYWJlbCwge1xuICAgIGZvbnQtZmFtaWx5OiBcIkhlbHZldGljYSBOZXVlXCIsIFwiSGVsdmV0aWNhXCIsIFwiVGFob21hXCIsIFwiR2VuZXZhXCIsIFwiQXJpYWxcIiwgc2Fucy1zZXJpZjtcbn1cbi8vLmNrLWVkaXRvciBzaG9ydGNvZGUsIC5jay1lZGl0b3IgdHdpZyxcbi5Db2RlTWlycm9yIHByZSxcbmNvZGUsIGtiZCwgcHJlLCBzYW1wLCAubW9ubyAge1xuICAgIGZvbnQtZmFtaWx5OiBcIlNGTW9uby1SZWd1bGFyXCIsIENvbnNvbGFzLCBcIkxpYmVyYXRpb24gTW9ub1wiLCBNZW5sbywgQ291cmllciwgbW9ub3NwYWNlO1xufVxuIl19 */
|
||||
|
||||
@@ -1,10 +1 @@
|
||||
{
|
||||
"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,CAC1C,AAAiB,IAAb,CAAC,WAAW,CAAC,GAAG,AAAC,CACjB,WAAW,CAAE,sEAAuE,CACvF,AACD,AAAA,IAAI,CAAE,AAAA,GAAG,CAAE,AAAA,GAAG,CAAE,AAAA,IAAI,CAAE,AAAA,KAAK,AAAE,CACzB,WAAW,CAAE,iDAAkD,CAClE",
|
||||
"names": []
|
||||
}
|
||||
{"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.fontfamily-sans .CodeMirror pre,\n#admin-menu li, .form-tabs > label, .label, {\n font-family: \"Helvetica Neue\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n}\n//.ck-editor shortcode, .ck-editor twig,\n.CodeMirror pre,\ncode, kbd, pre, samp, .mono {\n font-family: \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n}\n"],"names":[],"mappings":"AAAA,AAAA,IAAI,CAAE,EAAE,CAAE,EAAE,CACZ,MAAM,CAAE,KAAK,CAAE,sBAAsB,CACrC,KAAK,CAAE,MAAM,CAAE,QAAQ,CAAE,MAAM,CAAE,gBAAgB,CACjD,EAAE,CAAE,EAAE,CAAE,EAAE,CAAE,EAAE,CACd,gBAAgB,CAAC,WAAW,CAAC,GAAG,CAChC,WAAW,CAAC,EAAE,CAAE,UAAU,CAAG,KAAK,CAAE,MAAM,AAAE,CACxC,WAAW,CAAE,sEAAsE,CACtF,AAED,AAAA,WAAW,CAAC,GAAG,CACf,IAAI,CAAE,GAAG,CAAE,GAAG,CAAE,IAAI,CAAE,KAAK,AAAE,CACzB,WAAW,CAAE,wEAAwE,CACxF"}
|
||||
7
themes/grav/css-compiled/template.css
vendored
7
themes/grav/css-compiled/template.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
|
||||
Name: Base16 Default Light
|
||||
Author: Chris Kempson (http://chriskempson.com)
|
||||
|
||||
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-chrome-devtools)
|
||||
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
|
||||
|
||||
*/
|
||||
|
||||
.cm-s-base16-light.CodeMirror {background: #f5f5f5; color: #202020;}
|
||||
.cm-s-base16-light div.CodeMirror-selected {background: #e0e0e0 !important;}
|
||||
.cm-s-base16-light .CodeMirror-gutters {background: #f5f5f5; border-right: 0px;}
|
||||
.cm-s-base16-light .CodeMirror-guttermarker { color: #ac4142; }
|
||||
.cm-s-base16-light .CodeMirror-guttermarker-subtle { color: #b0b0b0; }
|
||||
.cm-s-base16-light .CodeMirror-linenumber {color: #b0b0b0;}
|
||||
.cm-s-base16-light .CodeMirror-cursor {border-left: 1px solid #505050 !important;}
|
||||
|
||||
.cm-s-base16-light span.cm-comment {color: #8f5536;}
|
||||
.cm-s-base16-light span.cm-atom {color: #aa759f;}
|
||||
.cm-s-base16-light span.cm-number {color: #aa759f;}
|
||||
|
||||
.cm-s-base16-light span.cm-property, .cm-s-base16-light span.cm-attribute {color: #90a959;}
|
||||
.cm-s-base16-light span.cm-keyword {color: #ac4142;}
|
||||
.cm-s-base16-light span.cm-string {color: #f4bf75;}
|
||||
|
||||
.cm-s-base16-light span.cm-variable {color: #90a959;}
|
||||
.cm-s-base16-light span.cm-variable-2 {color: #6a9fb5;}
|
||||
.cm-s-base16-light span.cm-def {color: #d28445;}
|
||||
.cm-s-base16-light span.cm-bracket {color: #202020;}
|
||||
.cm-s-base16-light span.cm-tag {color: #ac4142;}
|
||||
.cm-s-base16-light span.cm-link {color: #aa759f;}
|
||||
.cm-s-base16-light span.cm-error {background: #ac4142; color: #505050;}
|
||||
|
||||
.cm-s-base16-light .CodeMirror-activeline-background {background: #DDDCDC !important;}
|
||||
.cm-s-base16-light .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;}
|
||||
@@ -41,11 +41,11 @@
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror div.CodeMirror-cursor {
|
||||
.CodeMirror .CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
.CodeMirror .CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.CodeMirror.cm-fat-cursor div.CodeMirror-cursor {
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
.cm-s-paper .cm-keyword {color: #555;}
|
||||
.cm-s-paper .cm-atom {color: #7f8c8d;}
|
||||
.cm-s-paper .cm-number {color: #7f8c8d;}
|
||||
.cm-s-paper .cm-def {color: #00f;}
|
||||
.cm-s-paper .cm-variable {color: black;}
|
||||
.cm-s-paper .cm-variable-2 {color: #555;}
|
||||
.cm-s-paper .cm-variable-3 {color: #085;}
|
||||
.cm-s-paper .cm-property {color: black;}
|
||||
.cm-s-paper .cm-operator {color: black;}
|
||||
.cm-s-paper .cm-comment {color: #959595;}
|
||||
.cm-s-paper .cm-string {color: #7f8c8d;}
|
||||
.cm-s-paper .cm-string-2 {color: #f50;}
|
||||
.cm-s-paper .cm-meta {color: #555;}
|
||||
.cm-s-paper .cm-error {color: #f00;}
|
||||
.cm-s-paper .cm-qualifier {color: #555;}
|
||||
.cm-s-paper .cm-builtin {color: #555;}
|
||||
.cm-s-paper .cm-bracket {color: #997;}
|
||||
.cm-s-paper .cm-tag {color: #7f8c8d;}
|
||||
.cm-s-paper .cm-attribute {color: #7f8c8d;}
|
||||
.cm-s-paper .cm-header {color: #000;}
|
||||
.cm-s-paper .cm-quote {color: #888;}
|
||||
.cm-s-paper .cm-hr {color: #999;}
|
||||
.cm-s-paper .cm-link {color: #7f8c8d;}
|
||||
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
|
||||
.cm-invalidchar {color: #f00;}
|
||||
|
||||
.cm-s-paper .cm-header-1 {font-size: 140%;}
|
||||
.cm-s-paper .cm-header-2 {font-size: 120%;}
|
||||
.cm-s-paper .cm-header-3 {font-size: 110%;}
|
||||
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
41
themes/grav/css/codemirror/themes/3024-day.css
vendored
Normal file
41
themes/grav/css/codemirror/themes/3024-day.css
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
|
||||
Name: 3024 day
|
||||
Author: Jan T. Sott (http://github.com/idleberg)
|
||||
|
||||
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
|
||||
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
|
||||
|
||||
*/
|
||||
|
||||
.cm-s-3024-day.CodeMirror { background: #f7f7f7; color: #3a3432; }
|
||||
.cm-s-3024-day div.CodeMirror-selected { background: #d6d5d4; }
|
||||
|
||||
.cm-s-3024-day .CodeMirror-line::selection, .cm-s-3024-day .CodeMirror-line > span::selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d6d5d4; }
|
||||
.cm-s-3024-day .CodeMirror-line::-moz-selection, .cm-s-3024-day .CodeMirror-line > span::-moz-selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d9d9d9; }
|
||||
|
||||
.cm-s-3024-day .CodeMirror-gutters { background: #f7f7f7; border-right: 0px; }
|
||||
.cm-s-3024-day .CodeMirror-guttermarker { color: #db2d20; }
|
||||
.cm-s-3024-day .CodeMirror-guttermarker-subtle { color: #807d7c; }
|
||||
.cm-s-3024-day .CodeMirror-linenumber { color: #807d7c; }
|
||||
|
||||
.cm-s-3024-day .CodeMirror-cursor { border-left: 1px solid #5c5855; }
|
||||
|
||||
.cm-s-3024-day span.cm-comment { color: #cdab53; }
|
||||
.cm-s-3024-day span.cm-atom { color: #a16a94; }
|
||||
.cm-s-3024-day span.cm-number { color: #a16a94; }
|
||||
|
||||
.cm-s-3024-day span.cm-property, .cm-s-3024-day span.cm-attribute { color: #01a252; }
|
||||
.cm-s-3024-day span.cm-keyword { color: #db2d20; }
|
||||
.cm-s-3024-day span.cm-string { color: #fded02; }
|
||||
|
||||
.cm-s-3024-day span.cm-variable { color: #01a252; }
|
||||
.cm-s-3024-day span.cm-variable-2 { color: #01a0e4; }
|
||||
.cm-s-3024-day span.cm-def { color: #e8bbd0; }
|
||||
.cm-s-3024-day span.cm-bracket { color: #3a3432; }
|
||||
.cm-s-3024-day span.cm-tag { color: #db2d20; }
|
||||
.cm-s-3024-day span.cm-link { color: #a16a94; }
|
||||
.cm-s-3024-day span.cm-error { background: #db2d20; color: #5c5855; }
|
||||
|
||||
.cm-s-3024-day .CodeMirror-activeline-background { background: #e8f2ff; }
|
||||
.cm-s-3024-day .CodeMirror-matchingbracket { text-decoration: underline; color: #a16a94 !important; }
|
||||
39
themes/grav/css/codemirror/themes/3024-night.css
vendored
Normal file
39
themes/grav/css/codemirror/themes/3024-night.css
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
|
||||
Name: 3024 night
|
||||
Author: Jan T. Sott (http://github.com/idleberg)
|
||||
|
||||
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
|
||||
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
|
||||
|
||||
*/
|
||||
|
||||
.cm-s-3024-night.CodeMirror { background: #090300; color: #d6d5d4; }
|
||||
.cm-s-3024-night div.CodeMirror-selected { background: #3a3432; }
|
||||
.cm-s-3024-night .CodeMirror-line::selection, .cm-s-3024-night .CodeMirror-line > span::selection, .cm-s-3024-night .CodeMirror-line > span > span::selection { background: rgba(58, 52, 50, .99); }
|
||||
.cm-s-3024-night .CodeMirror-line::-moz-selection, .cm-s-3024-night .CodeMirror-line > span::-moz-selection, .cm-s-3024-night .CodeMirror-line > span > span::-moz-selection { background: rgba(58, 52, 50, .99); }
|
||||
.cm-s-3024-night .CodeMirror-gutters { background: #090300; border-right: 0px; }
|
||||
.cm-s-3024-night .CodeMirror-guttermarker { color: #db2d20; }
|
||||
.cm-s-3024-night .CodeMirror-guttermarker-subtle { color: #5c5855; }
|
||||
.cm-s-3024-night .CodeMirror-linenumber { color: #5c5855; }
|
||||
|
||||
.cm-s-3024-night .CodeMirror-cursor { border-left: 1px solid #807d7c; }
|
||||
|
||||
.cm-s-3024-night span.cm-comment { color: #cdab53; }
|
||||
.cm-s-3024-night span.cm-atom { color: #a16a94; }
|
||||
.cm-s-3024-night span.cm-number { color: #a16a94; }
|
||||
|
||||
.cm-s-3024-night span.cm-property, .cm-s-3024-night span.cm-attribute { color: #01a252; }
|
||||
.cm-s-3024-night span.cm-keyword { color: #db2d20; }
|
||||
.cm-s-3024-night span.cm-string { color: #fded02; }
|
||||
|
||||
.cm-s-3024-night span.cm-variable { color: #01a252; }
|
||||
.cm-s-3024-night span.cm-variable-2 { color: #01a0e4; }
|
||||
.cm-s-3024-night span.cm-def { color: #e8bbd0; }
|
||||
.cm-s-3024-night span.cm-bracket { color: #d6d5d4; }
|
||||
.cm-s-3024-night span.cm-tag { color: #db2d20; }
|
||||
.cm-s-3024-night span.cm-link { color: #a16a94; }
|
||||
.cm-s-3024-night span.cm-error { background: #db2d20; color: #807d7c; }
|
||||
|
||||
.cm-s-3024-night .CodeMirror-activeline-background { background: #2F2F2F; }
|
||||
.cm-s-3024-night .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }
|
||||
32
themes/grav/css/codemirror/themes/abcdef.css
vendored
Normal file
32
themes/grav/css/codemirror/themes/abcdef.css
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
.cm-s-abcdef.CodeMirror { background: #0f0f0f; color: #defdef; }
|
||||
.cm-s-abcdef div.CodeMirror-selected { background: #515151; }
|
||||
.cm-s-abcdef .CodeMirror-line::selection, .cm-s-abcdef .CodeMirror-line > span::selection, .cm-s-abcdef .CodeMirror-line > span > span::selection { background: rgba(56, 56, 56, 0.99); }
|
||||
.cm-s-abcdef .CodeMirror-line::-moz-selection, .cm-s-abcdef .CodeMirror-line > span::-moz-selection, .cm-s-abcdef .CodeMirror-line > span > span::-moz-selection { background: rgba(56, 56, 56, 0.99); }
|
||||
.cm-s-abcdef .CodeMirror-gutters { background: #555; border-right: 2px solid #314151; }
|
||||
.cm-s-abcdef .CodeMirror-guttermarker { color: #222; }
|
||||
.cm-s-abcdef .CodeMirror-guttermarker-subtle { color: azure; }
|
||||
.cm-s-abcdef .CodeMirror-linenumber { color: #FFFFFF; }
|
||||
.cm-s-abcdef .CodeMirror-cursor { border-left: 1px solid #00FF00; }
|
||||
|
||||
.cm-s-abcdef span.cm-keyword { color: darkgoldenrod; font-weight: bold; }
|
||||
.cm-s-abcdef span.cm-atom { color: #77F; }
|
||||
.cm-s-abcdef span.cm-number { color: violet; }
|
||||
.cm-s-abcdef span.cm-def { color: #fffabc; }
|
||||
.cm-s-abcdef span.cm-variable { color: #abcdef; }
|
||||
.cm-s-abcdef span.cm-variable-2 { color: #cacbcc; }
|
||||
.cm-s-abcdef span.cm-variable-3, .cm-s-abcdef span.cm-type { color: #def; }
|
||||
.cm-s-abcdef span.cm-property { color: #fedcba; }
|
||||
.cm-s-abcdef span.cm-operator { color: #ff0; }
|
||||
.cm-s-abcdef span.cm-comment { color: #7a7b7c; font-style: italic;}
|
||||
.cm-s-abcdef span.cm-string { color: #2b4; }
|
||||
.cm-s-abcdef span.cm-meta { color: #C9F; }
|
||||
.cm-s-abcdef span.cm-qualifier { color: #FFF700; }
|
||||
.cm-s-abcdef span.cm-builtin { color: #30aabc; }
|
||||
.cm-s-abcdef span.cm-bracket { color: #8a8a8a; }
|
||||
.cm-s-abcdef span.cm-tag { color: #FFDD44; }
|
||||
.cm-s-abcdef span.cm-attribute { color: #DDFF00; }
|
||||
.cm-s-abcdef span.cm-error { color: #FF0000; }
|
||||
.cm-s-abcdef span.cm-header { color: aquamarine; font-weight: bold; }
|
||||
.cm-s-abcdef span.cm-link { color: blueviolet; }
|
||||
|
||||
.cm-s-abcdef .CodeMirror-activeline-background { background: #314151; }
|
||||
5
themes/grav/css/codemirror/themes/ambiance-mobile.css
vendored
Normal file
5
themes/grav/css/codemirror/themes/ambiance-mobile.css
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.cm-s-ambiance.CodeMirror {
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
74
themes/grav/css/codemirror/themes/ambiance.css
vendored
Normal file
74
themes/grav/css/codemirror/themes/ambiance.css
vendored
Normal file
File diff suppressed because one or more lines are too long
42
themes/grav/css/codemirror/themes/ayu-dark.css
vendored
Normal file
42
themes/grav/css/codemirror/themes/ayu-dark.css
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
/* Based on https://github.com/dempfi/ayu */
|
||||
|
||||
.cm-s-ayu-dark.CodeMirror { background: #0a0e14; color: #b3b1ad; }
|
||||
.cm-s-ayu-dark div.CodeMirror-selected { background: #273747; }
|
||||
.cm-s-ayu-dark .CodeMirror-line::selection, .cm-s-ayu-dark .CodeMirror-line > span::selection, .cm-s-ayu-dark .CodeMirror-line > span > span::selection { background: rgba(39, 55, 71, 99); }
|
||||
.cm-s-ayu-dark .CodeMirror-line::-moz-selection, .cm-s-ayu-dark .CodeMirror-line > span::-moz-selection, .cm-s-ayu-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 55, 71, 99); }
|
||||
.cm-s-ayu-dark .CodeMirror-gutters { background: #0a0e14; border-right: 0px; }
|
||||
.cm-s-ayu-dark .CodeMirror-guttermarker { color: white; }
|
||||
.cm-s-ayu-dark .CodeMirror-guttermarker-subtle { color: #3d424d; }
|
||||
.cm-s-ayu-dark .CodeMirror-linenumber { color: #3d424d; }
|
||||
.cm-s-ayu-dark .CodeMirror-cursor { border-left: 1px solid #e6b450; }
|
||||
|
||||
.cm-s-ayu-dark span.cm-comment { color: #626a73; }
|
||||
.cm-s-ayu-dark span.cm-atom { color: #ae81ff; }
|
||||
.cm-s-ayu-dark span.cm-number { color: #e6b450; }
|
||||
|
||||
.cm-s-ayu-dark span.cm-comment.cm-attribute { color: #ffb454; }
|
||||
.cm-s-ayu-dark span.cm-comment.cm-def { color: rgba(57, 186, 230, 80); }
|
||||
.cm-s-ayu-dark span.cm-comment.cm-tag { color: #39bae6; }
|
||||
.cm-s-ayu-dark span.cm-comment.cm-type { color: #5998a6; }
|
||||
|
||||
.cm-s-ayu-dark span.cm-property, .cm-s-ayu-dark span.cm-attribute { color: #ffb454; }
|
||||
.cm-s-ayu-dark span.cm-keyword { color: #ff8f40; }
|
||||
.cm-s-ayu-dark span.cm-builtin { color: #e6b450; }
|
||||
.cm-s-ayu-dark span.cm-string { color: #c2d94c; }
|
||||
|
||||
.cm-s-ayu-dark span.cm-variable { color: #b3b1ad; }
|
||||
.cm-s-ayu-dark span.cm-variable-2 { color: #f07178; }
|
||||
.cm-s-ayu-dark span.cm-variable-3 { color: #39bae6; }
|
||||
.cm-s-ayu-dark span.cm-type { color: #ff8f40; }
|
||||
.cm-s-ayu-dark span.cm-def { color: #ffee99; }
|
||||
.cm-s-ayu-dark span.cm-bracket { color: #f8f8f2; }
|
||||
.cm-s-ayu-dark span.cm-tag { color: rgba(57, 186, 230, 80); }
|
||||
.cm-s-ayu-dark span.cm-header { color: #c2d94c; }
|
||||
.cm-s-ayu-dark span.cm-link { color: #39bae6; }
|
||||
.cm-s-ayu-dark span.cm-error { color: #ff3333; }
|
||||
|
||||
.cm-s-ayu-dark .CodeMirror-activeline-background { background: #01060e; }
|
||||
.cm-s-ayu-dark .CodeMirror-matchingbracket {
|
||||
text-decoration: underline;
|
||||
color: white !important;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user