Merge branch 'develop' into table-widget

This commit is contained in:
alekseybobkov 2014-12-17 22:15:23 -08:00
commit 3ac21e3c89
135 changed files with 4919 additions and 10316 deletions

View File

@ -1,3 +1,27 @@
* **Build 17x** (2014-12-xx)
- Improved asset caching (`cms.enableAssetCache`), when enabled the server will send a *304 Not Modified* header.
* **Build 171** (2014-12-17)
- Add new methods `propertyName()` and `paramName()` to Component base class for accessing names of external properties.
* **Build 169** (2014-12-16)
- Native `alert` and `confirm` functions have been styled in the back-end.
- Back-end user groups have a new description field, this is shown in the group list and when creating a new administrator.
- Back-end user groups can now be marked to add new administrators by default. This affects when a group is created (if checked, all administrators are added to the group) and creating a new administrator (the default groups are checked by default).
* **Build 168** (2014-12-14)
- Config item `cms.customErrorPage` is deprecated, the setting `app.debug` should be used instead.
- Config item `cms.enableAssetMinify` can now be set to **null**, in which case assets are only minified if debug mode (`app.debug`) is disabled.
* **Build 167** (2014-12-06)
- Settings pages now have a *Reset to default* button.
- The field `authorUrl` has been renamed to `homepage` in theme.yaml files.
- Adds Theme customization feature (see Themes > Development docs).
* **Build 166** (2014-11-27)
- Plugin details method now support "homepage" property (see Plugins > Registration & Versions docs).
- Fixes a bug in the Datepicker using `time` mode.
* **Build 162** (2014-11-10)
- Fixes an issue where the *Pages* tab is shown in the CMS when permission is denied.
- Updates are no longer shown on the Dashboard if permission is denied.

View File

@ -11,6 +11,9 @@ return array(
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
| You can create a CMS page with route "/error" to set the contents
| of this page. Otherwise a default error page is shown.
|
*/
'debug' => true,

View File

@ -111,19 +111,6 @@ return array(
'urlCacheTtl' => 10,
/*
|--------------------------------------------------------------------------
| Determines if a friendly error page should be used.
|--------------------------------------------------------------------------
|
| If this value is set to true, a friendly error page is used when an
| exception is encountered. You must create a CMS page with route "/error"
| to set the contents of this page. Otherwise the default error page is shown.
|
*/
'customErrorPage' => false,
/*
|--------------------------------------------------------------------------
| Determines if the asset caching is enabled.
@ -144,12 +131,13 @@ return array(
|--------------------------------------------------------------------------
|
| If the minification is enabled, combined assets are compressed (minified).
| It is recommended to disable the minification during the development, and
| enable it in the production mode.
| It is recommended to disable the minification during development, and
| enable it in production mode. If set to null, assets are minified
| when debug mode (app.debug) is disabled.
|
*/
'enableAssetMinify' => false,
'enableAssetMinify' => null,
/*
|--------------------------------------------------------------------------

View File

@ -75,19 +75,6 @@ return array(
'urlCacheTtl' => 1,
/*
|--------------------------------------------------------------------------
| Determines if a friendly error page should be used.
|--------------------------------------------------------------------------
|
| If this value is set to true, a friendly error page is used when an
| exception is encountered. You must create a CMS page with route "/error"
| to set the contents of this page. Otherwise the default error page is shown.
|
*/
'customErrorPage' => true,
/*
|--------------------------------------------------------------------------
| Determines if the asset caching is enabled.

View File

@ -36,16 +36,7 @@
"tests/UiTestCase.php"
]
},
"___scripts": {
"post-install-cmd": [
"php artisan optimize"
],
"pre-update-cmd": [
"php artisan clear-compiled"
],
"post-update-cmd": [
"php artisan optimize"
],
"scripts": {
"post-create-project-cmd": [
"php artisan key:generate"
]

View File

@ -45,6 +45,10 @@ class ServiceProvider extends ModuleServiceProvider
'label' => 'Date picker',
'code' => 'datepicker'
]);
$manager->registerFormWidget('Backend\FormWidgets\TimePicker', [
'label' => 'Time picker',
'code' => 'timepicker'
]);
$manager->registerFormWidget('Backend\FormWidgets\ColorPicker', [
'label' => 'Color picker',
'code' => 'colorpicker'

View File

@ -8865,6 +8865,9 @@ label {
.form-preview > .form-group:last-child {
padding-bottom: 0;
}
.form-preview.form-flush {
border-top: none;
}
.form-elements:before,
.form-tabless-fields:before,
.form-elements:after,
@ -15012,3 +15015,570 @@ div[data-control="balloon-selector"]:not(.control-disabled) ul li:hover {
display: block !important;
}
}
@-webkit-keyframes showSweetAlert {
0% {
transform: scale(0.7);
-webkit-transform: scale(0.7);
}
45% {
transform: scale(1.05);
-webkit-transform: scale(1.05);
}
80% {
transform: scale(0.95);
-webkit-tranform: scale(0.95);
}
100% {
transform: scale(1);
-webkit-transform: scale(1);
}
}
@keyframes showSweetAlert {
0% {
transform: scale(0.7);
-webkit-transform: scale(0.7);
}
45% {
transform: scale(1.05);
-webkit-transform: scale(1.05);
}
80% {
transform: scale(0.95);
-webkit-tranform: scale(0.95);
}
100% {
transform: scale(1);
-webkit-transform: scale(1);
}
}
@-webkit-keyframes hideSweetAlert {
0% {
transform: scale(1);
-webkit-transform: scale(1);
}
100% {
transform: scale(0.5);
-webkit-transform: scale(0.5);
}
}
@keyframes hideSweetAlert {
0% {
transform: scale(1);
-webkit-transform: scale(1);
}
100% {
transform: scale(0.5);
-webkit-transform: scale(0.5);
}
}
.showSweetAlert {
-webkit-animation: showSweetAlert 0.3s;
animation: showSweetAlert 0.3s;
}
.hideSweetAlert {
-webkit-animation: hideSweetAlert 0.2s;
animation: hideSweetAlert 0.2s;
}
@-webkit-keyframes animateSuccessTip {
0% {
width: 0;
left: 1px;
top: 19px;
}
54% {
width: 0;
left: 1px;
top: 19px;
}
70% {
width: 50px;
left: -8px;
top: 37px;
}
84% {
width: 17px;
left: 21px;
top: 48px;
}
100% {
width: 25px;
left: 14px;
top: 45px;
}
}
@keyframes animateSuccessTip {
0% {
width: 0;
left: 1px;
top: 19px;
}
54% {
width: 0;
left: 1px;
top: 19px;
}
70% {
width: 50px;
left: -8px;
top: 37px;
}
84% {
width: 17px;
left: 21px;
top: 48px;
}
100% {
width: 25px;
left: 14px;
top: 45px;
}
}
@-webkit-keyframes animateSuccessLong {
0% {
width: 0;
right: 46px;
top: 54px;
}
65% {
width: 0;
right: 46px;
top: 54px;
}
84% {
width: 55px;
right: 0px;
top: 35px;
}
100% {
width: 47px;
right: 8px;
top: 38px;
}
}
@keyframes animateSuccessLong {
0% {
width: 0;
right: 46px;
top: 54px;
}
65% {
width: 0;
right: 46px;
top: 54px;
}
84% {
width: 55px;
right: 0px;
top: 35px;
}
100% {
width: 47px;
right: 8px;
top: 38px;
}
}
@-webkit-keyframes rotatePlaceholder {
0% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
5% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
12% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg);
}
100% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg);
}
}
@keyframes rotatePlaceholder {
0% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
5% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
12% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg);
}
100% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg);
}
}
.animateSuccessTip {
-webkit-animation: animateSuccessTip 0.75s;
animation: animateSuccessTip 0.75s;
}
.animateSuccessLong {
-webkit-animation: animateSuccessLong 0.75s;
animation: animateSuccessLong 0.75s;
}
.icon.success.animate::after {
-webkit-animation: rotatePlaceholder 4.25s ease-in;
animation: rotatePlaceholder 4.25s ease-in;
}
@-webkit-keyframes animateErrorIcon {
0% {
transform: rotateX(100deg);
-webkit-transform: rotateX(100deg);
opacity: 0;
}
100% {
transform: rotateX(0deg);
-webkit-transform: rotateX(0deg);
opacity: 1;
}
}
@keyframes animateErrorIcon {
0% {
transform: rotateX(100deg);
-webkit-transform: rotateX(100deg);
opacity: 0;
}
100% {
transform: rotateX(0deg);
-webkit-transform: rotateX(0deg);
opacity: 1;
}
}
.animateErrorIcon {
-webkit-animation: animateErrorIcon 0.5s;
animation: animateErrorIcon 0.5s;
}
@-webkit-keyframes animateXMark {
0% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0;
}
50% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0;
}
80% {
transform: scale(1.15);
-webkit-transform: scale(1.15);
margin-top: -6px;
}
100% {
transform: scale(1);
-webkit-transform: scale(1);
margin-top: 0;
opacity: 1;
}
}
@keyframes animateXMark {
0% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0;
}
50% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0;
}
80% {
transform: scale(1.15);
-webkit-transform: scale(1.15);
margin-top: -6px;
}
100% {
transform: scale(1);
-webkit-transform: scale(1);
margin-top: 0;
opacity: 1;
}
}
.animateXMark {
-webkit-animation: animateXMark 0.5s;
animation: animateXMark 0.5s;
}
@-webkit-keyframes pulseWarning {
0% {
border-color: #F8D486;
}
100% {
border-color: #F8BB86;
}
}
@keyframes pulseWarning {
0% {
border-color: #F8D486;
}
100% {
border-color: #F8BB86;
}
}
.pulseWarning {
-webkit-animation: pulseWarning 0.75s infinite alternate;
animation: pulseWarning 0.75s infinite alternate;
}
@-webkit-keyframes pulseWarningIns {
0% {
background-color: #F8D486;
}
100% {
background-color: #F8BB86;
}
}
@keyframes pulseWarningIns {
0% {
background-color: #F8D486;
}
100% {
background-color: #F8BB86;
}
}
.pulseWarningIns {
-webkit-animation: pulseWarningIns 0.75s infinite alternate;
animation: pulseWarningIns 0.75s infinite alternate;
}
.sweet-overlay {
background-color: rgba(0, 0, 0, 0.4);
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: none;
z-index: 8050;
}
.sweet-alert {
background-color: #ffffff;
width: 478px;
padding: 17px;
border-radius: 5px;
text-align: center;
position: fixed;
left: 50%;
top: 50%;
margin-left: -256px;
margin-top: -200px;
overflow: hidden;
display: none;
z-index: 9050;
}
.sweet-alert h4 {
margin: 30px 0;
}
@media all and (max-width: 767px) {
.sweet-alert {
width: auto;
margin-left: 0;
margin-right: 0;
left: 15px;
right: 15px;
}
}
.sweet-alert .icon {
width: 80px;
height: 80px;
border: 4px solid gray;
border-radius: 50%;
margin: 20px auto;
position: relative;
box-sizing: content-box;
}
.sweet-alert .icon.error {
border-color: #d43f3a;
}
.sweet-alert .icon.error .x-mark {
position: relative;
display: block;
}
.sweet-alert .icon.error .line {
position: absolute;
height: 5px;
width: 47px;
background-color: #d9534f;
display: block;
top: 37px;
border-radius: 2px;
}
.sweet-alert .icon.error .line.left {
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
left: 17px;
}
.sweet-alert .icon.error .line.right {
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
right: 16px;
}
.sweet-alert .icon.warning {
border-color: #eea236;
}
.sweet-alert .icon.warning .body {
position: absolute;
width: 5px;
height: 47px;
left: 50%;
top: 10px;
border-radius: 2px;
margin-left: -2px;
background-color: #f0ad4e;
}
.sweet-alert .icon.warning .dot {
position: absolute;
width: 7px;
height: 7px;
border-radius: 50%;
margin-left: -3px;
left: 50%;
bottom: 10px;
background-color: #f0ad4e;
}
.sweet-alert .icon.info {
border-color: #46b8da;
}
.sweet-alert .icon.info::before {
content: "";
position: absolute;
width: 5px;
height: 29px;
left: 50%;
bottom: 17px;
border-radius: 2px;
margin-left: -2px;
background-color: #5bc0de;
}
.sweet-alert .icon.info::after {
content: "";
position: absolute;
width: 7px;
height: 7px;
border-radius: 50%;
margin-left: -3px;
top: 19px;
background-color: #5bc0de;
}
.sweet-alert .icon.success {
border-color: #4cae4c;
}
.sweet-alert .icon.success::before,
.sweet-alert .icon.success::after {
content: '';
border-radius: 50%;
position: absolute;
width: 60px;
height: 120px;
background: white;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
.sweet-alert .icon.success::before {
border-radius: 120px 0 0 120px;
top: -7px;
left: -33px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-transform-origin: 60px 60px;
transform-origin: 60px 60px;
}
.sweet-alert .icon.success::after {
border-radius: 0 120px 120px 0;
top: -11px;
left: 30px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-transform-origin: 0px 60px;
transform-origin: 0px 60px;
}
.sweet-alert .icon.success .placeholder {
width: 80px;
height: 80px;
border: 4px solid rgba(92, 184, 92, 0.2);
border-radius: 50%;
box-sizing: content-box;
position: absolute;
left: -4px;
top: -4px;
z-index: 2;
}
.sweet-alert .icon.success .fix {
width: 5px;
height: 90px;
background-color: #ffffff;
position: absolute;
left: 28px;
top: 8px;
z-index: 1;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.sweet-alert .icon.success .line {
height: 5px;
background-color: #5cb85c;
display: block;
border-radius: 2px;
position: absolute;
z-index: 2;
}
.sweet-alert .icon.success .line.tip {
width: 25px;
left: 14px;
top: 46px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
.sweet-alert .icon.success .line.long {
width: 47px;
right: 8px;
top: 38px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.sweet-alert .icon.custom {
background-size: contain;
border-radius: 0;
border: none;
background-position: center center;
background-repeat: no-repeat;
}
.sweet-alert .btn-default:focus {
border-color: #e3e3e3;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(227, 227, 227, 0.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(227, 227, 227, 0.6);
}
.sweet-alert .btn-success:focus {
border-color: #4cae4c;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(76, 174, 76, 0.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(76, 174, 76, 0.6);
}
.sweet-alert .btn-info:focus {
border-color: #46b8da;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(70, 184, 218, 0.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(70, 184, 218, 0.6);
}
.sweet-alert .btn-danger:focus {
border-color: #d43f3a;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(212, 63, 58, 0.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(212, 63, 58, 0.6);
}
.sweet-alert .btn-warning:focus {
border-color: #eea236;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(238, 162, 54, 0.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(238, 162, 54, 0.6);
}
.sweet-alert button::-moz-focus-inner {
border: 0;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 B

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -29,7 +29,7 @@
* - placeholder - placholder text, for text and dropdown properties
* - depends - a list of properties the property depend on, for dropdown lists
* - options - an option list for dropdown lists, optional. If not provided the options are loaded with AJAX.
* - showExternalParameter - specifies the visibility of the external parameter feature for the property. Default: true.
* - showExternalParam - specifies the visibility of the external parameter feature for the property. Default: true.
* Example of the configuration string (a single property):
* [{"property":"max-width","title":"Max width","type":"string"}]
*
@ -39,7 +39,7 @@
*
* Any HTML element that wraps the inspectable element can have the data-inspector-external-parameters property that enables the external
* parameters support. External parameters saved with the special syntax {{ paramName }}. The external parameter feature can be toggled
* on per-property basis with the showExternalParameter option, visible by default.
* on per-property basis with the showExternalParam option, visible by default.
*
* Events
* - change - the event is triggered on the inspectable element when it's properties are updated.
@ -161,7 +161,7 @@
if (this.itemType == 'property' && this.groupIndex !== undefined)
result += self.groupExpanded(this.group) ? ' expanded' : ' collapsed'
if (this.itemType == 'property' && !this.showExternalParameter)
if (this.itemType == 'property' && !this.showExternalParam)
result += ' no-external-parameter'
return result

View File

@ -116,7 +116,6 @@
},
error: function(jqXHR, textStatus, errorThrown) {
this.error(jqXHR, textStatus, errorThrown).done(function(){
alert(jqXHR.responseText.length ? jqXHR.responseText : jqXHR.statusText)
self.hide()
self.triggerEvent('popupError') // Deprecated
self.triggerEvent('error.oc.popup')

View File

@ -28,7 +28,7 @@
tr.each(function(){
var link = $(this).find(options.target).filter(function(){
return !$(this).closest('td').hasClass(options.excludeClass)
return !$(this).closest('td').hasClass(options.excludeClass) && !$(this).hasClass(options.excludeClass)
}).first()
if (!link.length) return

View File

@ -12,6 +12,8 @@
SidePanelTab.prototype.init = function() {
var self = this
this.tabOpenDelay = 200
this.tabOpenTimeout = undefined
this.$sideNavItems = $('#layout-sidenav ul li')
this.$sidePanelItems = $('[data-content-id]', this.$el)
this.sideNavWidth = $('#layout-sidenav ul li').outerWidth()
@ -51,10 +53,19 @@
})
self.$sideNavItems.mouseenter(function(){
if ($(window).width() < self.options.breakpoint || !self.panelFixed())
self.displayTab(this)
if ($(window).width() < self.options.breakpoint || !self.panelFixed()) {
var _this = this
self.tabOpenTimeout = setTimeout(function () {
self.displayTab(_this)
}, self.tabOpenDelay)
}
})
self.$sideNavItems.mouseleave(function (){
clearTimeout(self.tabOpenTimeout)
})
$(window).resize(function() {
self.updatePanelPosition()
self.updateActiveTab()

View File

@ -2,6 +2,42 @@
* October General Utilities
*/
/*
* Implement "Sweet Alert"
*/
$(window).on('ajaxErrorMessage', function(event, message){
swal({
title: message,
// type: 'error',
confirmButtonClass: 'btn-default'
})
// Prevent the default alert() message
event.preventDefault()
})
$(window).on('ajaxConfirmMessage', function(event, message){
swal({
title: message,
// type: 'warning',
showCancelButton: true,
confirmButtonClass: 'btn-primary'
}, function(isConfirm){
isConfirm
? event.promise.resolve()
: event.promise.reject()
})
// Prevent the default confirm() message
event.preventDefault()
return true
})
/*
* Path helpers
*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -36,8 +36,14 @@ label {
>.form-group:last-child {
padding-bottom: 0;
}
// Form to sit flush to the element above
&.form-flush {
border-top: none;
}
}
//
// Nice forms
//

View File

@ -45,3 +45,6 @@
@import "controls/treeview.less";
@import "controls/callout.less";
@import "controls/sidenav-tree.less";
// Vendor
@import "../vendor/sweet-alert/sweet-alert.less";

View File

@ -0,0 +1,255 @@
@-webkit-keyframes showSweetAlert {
0% {
transform: scale(0.7);
-webkit-transform: scale(0.7); }
45% {
transform: scale(1.05);
-webkit-transform: scale(1.05); }
80% {
transform: scale(0.95);
-webkit-tranform: scale(0.95); }
100% {
transform: scale(1);
-webkit-transform: scale(1); } }
@keyframes showSweetAlert {
0% {
transform: scale(0.7);
-webkit-transform: scale(0.7); }
45% {
transform: scale(1.05);
-webkit-transform: scale(1.05); }
80% {
transform: scale(0.95);
-webkit-tranform: scale(0.95); }
100% {
transform: scale(1);
-webkit-transform: scale(1); } }
@-webkit-keyframes hideSweetAlert {
0% {
transform: scale(1);
-webkit-transform: scale(1); }
100% {
transform: scale(0.5);
-webkit-transform: scale(0.5); } }
@keyframes hideSweetAlert {
0% {
transform: scale(1);
-webkit-transform: scale(1); }
100% {
transform: scale(0.5);
-webkit-transform: scale(0.5); } }
.showSweetAlert {
-webkit-animation: showSweetAlert 0.3s;
animation: showSweetAlert 0.3s; }
.hideSweetAlert {
-webkit-animation: hideSweetAlert 0.2s;
animation: hideSweetAlert 0.2s; }
@-webkit-keyframes animateSuccessTip {
0% {
width: 0;
left: 1px;
top: 19px; }
54% {
width: 0;
left: 1px;
top: 19px; }
70% {
width: 50px;
left: -8px;
top: 37px; }
84% {
width: 17px;
left: 21px;
top: 48px; }
100% {
width: 25px;
left: 14px;
top: 45px; } }
@keyframes animateSuccessTip {
0% {
width: 0;
left: 1px;
top: 19px; }
54% {
width: 0;
left: 1px;
top: 19px; }
70% {
width: 50px;
left: -8px;
top: 37px; }
84% {
width: 17px;
left: 21px;
top: 48px; }
100% {
width: 25px;
left: 14px;
top: 45px; } }
@-webkit-keyframes animateSuccessLong {
0% {
width: 0;
right: 46px;
top: 54px; }
65% {
width: 0;
right: 46px;
top: 54px; }
84% {
width: 55px;
right: 0px;
top: 35px; }
100% {
width: 47px;
right: 8px;
top: 38px; } }
@keyframes animateSuccessLong {
0% {
width: 0;
right: 46px;
top: 54px; }
65% {
width: 0;
right: 46px;
top: 54px; }
84% {
width: 55px;
right: 0px;
top: 35px; }
100% {
width: 47px;
right: 8px;
top: 38px; } }
@-webkit-keyframes rotatePlaceholder {
0% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg); }
5% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg); }
12% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg); }
100% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg); } }
@keyframes rotatePlaceholder {
0% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg); }
5% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg); }
12% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg); }
100% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg); } }
.animateSuccessTip {
-webkit-animation: animateSuccessTip 0.75s;
animation: animateSuccessTip 0.75s; }
.animateSuccessLong {
-webkit-animation: animateSuccessLong 0.75s;
animation: animateSuccessLong 0.75s; }
.icon.success.animate::after {
-webkit-animation: rotatePlaceholder 4.25s ease-in;
animation: rotatePlaceholder 4.25s ease-in; }
@-webkit-keyframes animateErrorIcon {
0% {
transform: rotateX(100deg);
-webkit-transform: rotateX(100deg);
opacity: 0; }
100% {
transform: rotateX(0deg);
-webkit-transform: rotateX(0deg);
opacity: 1; } }
@keyframes animateErrorIcon {
0% {
transform: rotateX(100deg);
-webkit-transform: rotateX(100deg);
opacity: 0; }
100% {
transform: rotateX(0deg);
-webkit-transform: rotateX(0deg);
opacity: 1; } }
.animateErrorIcon {
-webkit-animation: animateErrorIcon 0.5s;
animation: animateErrorIcon 0.5s; }
@-webkit-keyframes animateXMark {
0% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0; }
50% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0; }
80% {
transform: scale(1.15);
-webkit-transform: scale(1.15);
margin-top: -6px; }
100% {
transform: scale(1);
-webkit-transform: scale(1);
margin-top: 0;
opacity: 1; } }
@keyframes animateXMark {
0% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0; }
50% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0; }
80% {
transform: scale(1.15);
-webkit-transform: scale(1.15);
margin-top: -6px; }
100% {
transform: scale(1);
-webkit-transform: scale(1);
margin-top: 0;
opacity: 1; } }
.animateXMark {
-webkit-animation: animateXMark 0.5s;
animation: animateXMark 0.5s; }
@-webkit-keyframes pulseWarning {
0% {
border-color: #F8D486; }
100% {
border-color: #F8BB86; } }
@keyframes pulseWarning {
0% {
border-color: #F8D486; }
100% {
border-color: #F8BB86; } }
.pulseWarning {
-webkit-animation: pulseWarning 0.75s infinite alternate;
animation: pulseWarning 0.75s infinite alternate; }
@-webkit-keyframes pulseWarningIns {
0% {
background-color: #F8D486; }
100% {
background-color: #F8BB86; } }
@keyframes pulseWarningIns {
0% {
background-color: #F8D486; }
100% {
background-color: #F8BB86; } }
.pulseWarningIns {
-webkit-animation: pulseWarningIns 0.75s infinite alternate;
animation: pulseWarningIns 0.75s infinite alternate; }

View File

@ -0,0 +1,7 @@
/*
SweetAlert for Bootstrap
https://github.com/lipis/bootstrap-sweetalert
*/
@import "../../../../system/assets/vendor/bootstrap/variables";
@import "../../../../system/assets/vendor/bootstrap/mixins";
@import "sweet-alert";

View File

@ -0,0 +1,564 @@
@-webkit-keyframes showSweetAlert {
0% {
transform: scale(0.7);
-webkit-transform: scale(0.7);
}
45% {
transform: scale(1.05);
-webkit-transform: scale(1.05);
}
80% {
transform: scale(0.95);
-webkit-tranform: scale(0.95);
}
100% {
transform: scale(1);
-webkit-transform: scale(1);
}
}
@keyframes showSweetAlert {
0% {
transform: scale(0.7);
-webkit-transform: scale(0.7);
}
45% {
transform: scale(1.05);
-webkit-transform: scale(1.05);
}
80% {
transform: scale(0.95);
-webkit-tranform: scale(0.95);
}
100% {
transform: scale(1);
-webkit-transform: scale(1);
}
}
@-webkit-keyframes hideSweetAlert {
0% {
transform: scale(1);
-webkit-transform: scale(1);
}
100% {
transform: scale(0.5);
-webkit-transform: scale(0.5);
}
}
@keyframes hideSweetAlert {
0% {
transform: scale(1);
-webkit-transform: scale(1);
}
100% {
transform: scale(0.5);
-webkit-transform: scale(0.5);
}
}
.showSweetAlert {
-webkit-animation: showSweetAlert 0.3s;
animation: showSweetAlert 0.3s;
}
.hideSweetAlert {
-webkit-animation: hideSweetAlert 0.2s;
animation: hideSweetAlert 0.2s;
}
@-webkit-keyframes animateSuccessTip {
0% {
width: 0;
left: 1px;
top: 19px;
}
54% {
width: 0;
left: 1px;
top: 19px;
}
70% {
width: 50px;
left: -8px;
top: 37px;
}
84% {
width: 17px;
left: 21px;
top: 48px;
}
100% {
width: 25px;
left: 14px;
top: 45px;
}
}
@keyframes animateSuccessTip {
0% {
width: 0;
left: 1px;
top: 19px;
}
54% {
width: 0;
left: 1px;
top: 19px;
}
70% {
width: 50px;
left: -8px;
top: 37px;
}
84% {
width: 17px;
left: 21px;
top: 48px;
}
100% {
width: 25px;
left: 14px;
top: 45px;
}
}
@-webkit-keyframes animateSuccessLong {
0% {
width: 0;
right: 46px;
top: 54px;
}
65% {
width: 0;
right: 46px;
top: 54px;
}
84% {
width: 55px;
right: 0px;
top: 35px;
}
100% {
width: 47px;
right: 8px;
top: 38px;
}
}
@keyframes animateSuccessLong {
0% {
width: 0;
right: 46px;
top: 54px;
}
65% {
width: 0;
right: 46px;
top: 54px;
}
84% {
width: 55px;
right: 0px;
top: 35px;
}
100% {
width: 47px;
right: 8px;
top: 38px;
}
}
@-webkit-keyframes rotatePlaceholder {
0% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
5% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
12% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg);
}
100% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg);
}
}
@keyframes rotatePlaceholder {
0% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
5% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
12% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg);
}
100% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg);
}
}
.animateSuccessTip {
-webkit-animation: animateSuccessTip 0.75s;
animation: animateSuccessTip 0.75s;
}
.animateSuccessLong {
-webkit-animation: animateSuccessLong 0.75s;
animation: animateSuccessLong 0.75s;
}
.icon.success.animate::after {
-webkit-animation: rotatePlaceholder 4.25s ease-in;
animation: rotatePlaceholder 4.25s ease-in;
}
@-webkit-keyframes animateErrorIcon {
0% {
transform: rotateX(100deg);
-webkit-transform: rotateX(100deg);
opacity: 0;
}
100% {
transform: rotateX(0deg);
-webkit-transform: rotateX(0deg);
opacity: 1;
}
}
@keyframes animateErrorIcon {
0% {
transform: rotateX(100deg);
-webkit-transform: rotateX(100deg);
opacity: 0;
}
100% {
transform: rotateX(0deg);
-webkit-transform: rotateX(0deg);
opacity: 1;
}
}
.animateErrorIcon {
-webkit-animation: animateErrorIcon 0.5s;
animation: animateErrorIcon 0.5s;
}
@-webkit-keyframes animateXMark {
0% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0;
}
50% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0;
}
80% {
transform: scale(1.15);
-webkit-transform: scale(1.15);
margin-top: -6px;
}
100% {
transform: scale(1);
-webkit-transform: scale(1);
margin-top: 0;
opacity: 1;
}
}
@keyframes animateXMark {
0% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0;
}
50% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0;
}
80% {
transform: scale(1.15);
-webkit-transform: scale(1.15);
margin-top: -6px;
}
100% {
transform: scale(1);
-webkit-transform: scale(1);
margin-top: 0;
opacity: 1;
}
}
.animateXMark {
-webkit-animation: animateXMark 0.5s;
animation: animateXMark 0.5s;
}
@-webkit-keyframes pulseWarning {
0% {
border-color: #F8D486;
}
100% {
border-color: #F8BB86;
}
}
@keyframes pulseWarning {
0% {
border-color: #F8D486;
}
100% {
border-color: #F8BB86;
}
}
.pulseWarning {
-webkit-animation: pulseWarning 0.75s infinite alternate;
animation: pulseWarning 0.75s infinite alternate;
}
@-webkit-keyframes pulseWarningIns {
0% {
background-color: #F8D486;
}
100% {
background-color: #F8BB86;
}
}
@keyframes pulseWarningIns {
0% {
background-color: #F8D486;
}
100% {
background-color: #F8BB86;
}
}
.pulseWarningIns {
-webkit-animation: pulseWarningIns 0.75s infinite alternate;
animation: pulseWarningIns 0.75s infinite alternate;
}
.sweet-overlay {
background-color: rgba(0, 0, 0, 0.4);
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: none;
z-index: 1040;
}
.sweet-alert {
background-color: #ffffff;
width: 478px;
padding: 17px;
border-radius: 5px;
text-align: center;
position: fixed;
left: 50%;
top: 50%;
margin-left: -256px;
margin-top: -200px;
overflow: hidden;
display: none;
z-index: 2000;
}
@media all and (max-width: 767px) {
.sweet-alert {
width: auto;
margin-left: 0;
margin-right: 0;
left: 15px;
right: 15px;
}
}
.sweet-alert .icon {
width: 80px;
height: 80px;
border: 4px solid gray;
border-radius: 50%;
margin: 20px auto;
position: relative;
box-sizing: content-box;
}
.sweet-alert .icon.error {
border-color: #d43f3a;
}
.sweet-alert .icon.error .x-mark {
position: relative;
display: block;
}
.sweet-alert .icon.error .line {
position: absolute;
height: 5px;
width: 47px;
background-color: #d9534f;
display: block;
top: 37px;
border-radius: 2px;
}
.sweet-alert .icon.error .line.left {
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
left: 17px;
}
.sweet-alert .icon.error .line.right {
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
right: 16px;
}
.sweet-alert .icon.warning {
border-color: #eea236;
}
.sweet-alert .icon.warning .body {
position: absolute;
width: 5px;
height: 47px;
left: 50%;
top: 10px;
border-radius: 2px;
margin-left: -2px;
background-color: #f0ad4e;
}
.sweet-alert .icon.warning .dot {
position: absolute;
width: 7px;
height: 7px;
border-radius: 50%;
margin-left: -3px;
left: 50%;
bottom: 10px;
background-color: #f0ad4e;
}
.sweet-alert .icon.info {
border-color: #46b8da;
}
.sweet-alert .icon.info::before {
content: "";
position: absolute;
width: 5px;
height: 29px;
left: 50%;
bottom: 17px;
border-radius: 2px;
margin-left: -2px;
background-color: #5bc0de;
}
.sweet-alert .icon.info::after {
content: "";
position: absolute;
width: 7px;
height: 7px;
border-radius: 50%;
margin-left: -3px;
top: 19px;
background-color: #5bc0de;
}
.sweet-alert .icon.success {
border-color: #4cae4c;
}
.sweet-alert .icon.success::before,
.sweet-alert .icon.success::after {
content: '';
border-radius: 50%;
position: absolute;
width: 60px;
height: 120px;
background: white;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
.sweet-alert .icon.success::before {
border-radius: 120px 0 0 120px;
top: -7px;
left: -33px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-transform-origin: 60px 60px;
transform-origin: 60px 60px;
}
.sweet-alert .icon.success::after {
border-radius: 0 120px 120px 0;
top: -11px;
left: 30px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-transform-origin: 0px 60px;
transform-origin: 0px 60px;
}
.sweet-alert .icon.success .placeholder {
width: 80px;
height: 80px;
border: 4px solid rgba(92, 184, 92, 0.2);
border-radius: 50%;
box-sizing: content-box;
position: absolute;
left: -4px;
top: -4px;
z-index: 2;
}
.sweet-alert .icon.success .fix {
width: 5px;
height: 90px;
background-color: #ffffff;
position: absolute;
left: 28px;
top: 8px;
z-index: 1;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.sweet-alert .icon.success .line {
height: 5px;
background-color: #5cb85c;
display: block;
border-radius: 2px;
position: absolute;
z-index: 2;
}
.sweet-alert .icon.success .line.tip {
width: 25px;
left: 14px;
top: 46px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
.sweet-alert .icon.success .line.long {
width: 47px;
right: 8px;
top: 38px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.sweet-alert .icon.custom {
background-size: contain;
border-radius: 0;
border: none;
background-position: center center;
background-repeat: no-repeat;
}
.sweet-alert .btn-default:focus {
border-color: #cccccc;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(204, 204, 204, 0.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(204, 204, 204, 0.6);
}
.sweet-alert .btn-success:focus {
border-color: #4cae4c;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(76, 174, 76, 0.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(76, 174, 76, 0.6);
}
.sweet-alert .btn-info:focus {
border-color: #46b8da;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(70, 184, 218, 0.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(70, 184, 218, 0.6);
}
.sweet-alert .btn-danger:focus {
border-color: #d43f3a;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(212, 63, 58, 0.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(212, 63, 58, 0.6);
}
.sweet-alert .btn-warning:focus {
border-color: #eea236;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(238, 162, 54, 0.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(238, 162, 54, 0.6);
}
.sweet-alert button::-moz-focus-inner {
border: 0;
}

View File

@ -0,0 +1,39 @@
<!-- Note: this file is only intended for development use! -->
<div class="sweet-overlay"></div>
<!-- SweetAlert box -->
<div class="sweet-alert">
<div class="icon error">
<span class="x-mark">
<span class="line left"></span>
<span class="line right"></span>
</span>
</div>
<div class="icon warning">
<span class="body"></span>
<span class="dot"></span>
</div>
<div class="icon info"></div>
<div class="icon success">
<span class="line tip"></span>
<span class="line long"></span>
<div class="placeholder"></div>
<div class="fix"></div>
</div>
<div class="icon custom"></div>
<h2>Title</h2>
<p class="text-muted">Text</p>
<p>
<button class="cancel btn btn-lg btn-default">Cancel</button>
<button class="confirm btn btn-lg">OK</button>
</p>
</div>

View File

@ -0,0 +1,711 @@
// SweetAlert
// 2014 (c) - Tristan Edwards
// github.com/t4t5/sweetalert
(function(window, document) {
var modalClass = '.sweet-alert',
overlayClass = '.sweet-overlay',
alertTypes = ['error', 'warning', 'info', 'success'],
defaultParams = {
title: '',
text: '',
type: null,
allowOutsideClick: false,
showCancelButton: false,
closeOnConfirm: true,
closeOnCancel: true,
confirmButtonText: 'OK',
confirmButtonClass: 'btn-primary',
cancelButtonText: 'Cancel',
cancelButtonClass: 'btn-default',
imageUrl: null,
imageSize: null,
timer: null
};
/*
* Manipulate DOM
*/
var getModal = function() {
return document.querySelector(modalClass);
},
getOverlay = function() {
return document.querySelector(overlayClass);
},
hasClass = function(elem, className) {
return new RegExp(' ' + className + ' ').test(' ' + elem.className + ' ');
},
addClass = function(elem, className) {
if (!hasClass(elem, className)) {
elem.className += ' ' + className;
}
},
removeClass = function(elem, className) {
var newClass = ' ' + elem.className.replace(/[\t\r\n]/g, ' ') + ' ';
if (hasClass(elem, className)) {
while (newClass.indexOf(' ' + className + ' ') >= 0) {
newClass = newClass.replace(' ' + className + ' ', ' ');
}
elem.className = newClass.replace(/^\s+|\s+$/g, '');
}
},
escapeHtml = function(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
},
_show = function(elem) {
elem.style.opacity = '';
elem.style.display = 'block';
},
show = function(elems) {
if (elems && !elems.length) {
return _show(elems);
}
for (var i = 0; i < elems.length; ++i) {
_show(elems[i]);
}
},
_hide = function(elem) {
elem.style.opacity = '';
elem.style.display = 'none';
},
hide = function(elems) {
if (elems && !elems.length) {
return _hide(elems);
}
for (var i = 0; i < elems.length; ++i) {
_hide(elems[i]);
}
},
isDescendant = function(parent, child) {
var node = child.parentNode;
while (node !== null) {
if (node === parent) {
return true;
}
node = node.parentNode;
}
return false;
},
getTopMargin = function(elem) {
elem.style.left = '-9999px';
elem.style.display = 'block';
var height = elem.clientHeight;
var padding = parseInt(getComputedStyle(elem).getPropertyValue('padding'), 10);
elem.style.left = '';
elem.style.display = 'none';
return ('-' + parseInt(height / 2 + padding) + 'px');
},
fadeIn = function(elem, interval) {
if(+elem.style.opacity < 1) {
interval = interval || 16;
elem.style.opacity = 0;
elem.style.display = 'block';
var last = +new Date();
var tick = function() {
elem.style.opacity = +elem.style.opacity + (new Date() - last) / 100;
last = +new Date();
if (+elem.style.opacity < 1) {
setTimeout(tick, interval);
}
};
tick();
}
},
fadeOut = function(elem, interval) {
interval = interval || 16;
elem.style.opacity = 1;
var last = +new Date();
var tick = function() {
elem.style.opacity = +elem.style.opacity - (new Date() - last) / 100;
last = +new Date();
if (+elem.style.opacity > 0) {
setTimeout(tick, interval);
} else {
elem.style.display = 'none';
}
};
tick();
},
fireClick = function(node) {
// Taken from http://www.nonobtrusive.com/2011/11/29/programatically-fire-crossbrowser-click-event-with-javascript/
// Then fixed for today's Chrome browser.
if (MouseEvent) {
// Up-to-date approach
var mevt = new MouseEvent('click', {
view: window,
bubbles: false,
cancelable: true
});
node.dispatchEvent(mevt);
} else if ( document.createEvent ) {
// Fallback
var evt = document.createEvent('MouseEvents');
evt.initEvent('click', false, false);
node.dispatchEvent(evt);
} else if( document.createEventObject ) {
node.fireEvent('onclick') ;
} else if (typeof node.onclick === 'function' ) {
node.onclick();
}
},
stopEventPropagation = function(e) {
// In particular, make sure the space bar doesn't scroll the main window.
if (typeof e.stopPropagation === 'function') {
e.stopPropagation();
e.preventDefault();
} else if (window.event && window.event.hasOwnProperty('cancelBubble')) {
window.event.cancelBubble = true;
}
};
// Remember state in cases where opening and handling a modal will fiddle with it.
var previousActiveElement,
previousDocumentClick,
previousWindowKeyDown,
lastFocusedButton;
/*
* Add modal + overlay to DOM
*/
window.sweetAlertInitialize = function() {
var sweetHTML = '<div class="sweet-overlay" tabIndex="-1"></div><div class="sweet-alert" tabIndex="-1"><div class="icon error"><span class="x-mark"><span class="line left"></span><span class="line right"></span></span></div><div class="icon warning"> <span class="body"></span> <span class="dot"></span> </div> <div class="icon info"></div> <div class="icon success"> <span class="line tip"></span> <span class="line long"></span> <div class="placeholder"></div> <div class="fix"></div> </div> <div class="icon custom"></div> <h4>Title</h4><p class="text-muted">Text</p><p><button class="cancel btn" tabIndex="2">Cancel</button> <button class="confirm btn" tabIndex="1">OK</button></p></div>',
sweetWrap = document.createElement('div');
sweetWrap.innerHTML = sweetHTML;
// For readability: check sweet-alert.html
document.body.appendChild(sweetWrap);
// For development use only!
/*jQuery.ajax({
url: '../lib/sweet-alert.html', // Change path depending on file location
dataType: 'html'
})
.done(function(html) {
jQuery('body').append(html);
});*/
}
/*
* Global sweetAlert function
*/
window.sweetAlert = window.swal = function() {
if (arguments[0] === undefined) {
window.console.error('sweetAlert expects at least 1 attribute!');
return false;
}
var params = extend({}, defaultParams);
switch (typeof arguments[0]) {
case 'string':
params.title = arguments[0];
params.text = arguments[1] || '';
params.type = arguments[2] || '';
break;
case 'object':
if (arguments[0].title === undefined) {
window.console.error('Missing "title" argument!');
return false;
}
params.title = arguments[0].title;
params.text = arguments[0].text || defaultParams.text;
params.type = arguments[0].type || defaultParams.type;
params.allowOutsideClick = arguments[0].allowOutsideClick || defaultParams.allowOutsideClick;
params.showCancelButton = arguments[0].showCancelButton !== undefined ? arguments[0].showCancelButton : defaultParams.showCancelButton;
params.closeOnConfirm = arguments[0].closeOnConfirm !== undefined ? arguments[0].closeOnConfirm : defaultParams.closeOnConfirm;
params.closeOnCancel = arguments[0].closeOnCancel !== undefined ? arguments[0].closeOnCancel : defaultParams.closeOnCancel;
params.timer = arguments[0].timer || defaultParams.timer;
// Show "Confirm" instead of "OK" if cancel button is visible
params.confirmButtonText = (defaultParams.showCancelButton) ? 'Confirm' : defaultParams.confirmButtonText;
params.confirmButtonText = arguments[0].confirmButtonText || defaultParams.confirmButtonText;
params.confirmButtonClass = arguments[0].confirmButtonClass || defaultParams.confirmButtonClass;
params.cancelButtonText = arguments[0].cancelButtonText || defaultParams.cancelButtonText;
params.cancelButtonClass = arguments[0].cancelButtonClass || defaultParams.cancelButtonClass;
params.imageUrl = arguments[0].imageUrl || defaultParams.imageUrl;
params.imageSize = arguments[0].imageSize || defaultParams.imageSize;
params.doneFunction = arguments[1] || null;
break;
default:
window.console.error('Unexpected type of argument! Expected "string" or "object", got ' + typeof arguments[0]);
return false;
}
setParameters(params);
fixVerticalPosition();
openModal();
// Modal interactions
var modal = getModal();
// Mouse interactions
var onButtonEvent = function(e) {
var target = e.target || e.srcElement,
targetedConfirm = (target.className.indexOf('confirm') > -1),
modalIsVisible = hasClass(modal, 'visible'),
doneFunctionExists = (params.doneFunction && modal.getAttribute('data-has-done-function') === 'true');
switch (e.type) {
case ("click"):
if (targetedConfirm && doneFunctionExists && modalIsVisible) { // Clicked "confirm"
params.doneFunction(true);
if (params.closeOnConfirm) {
closeModal();
}
} else if (doneFunctionExists && modalIsVisible) { // Clicked "cancel"
// Check if callback function expects a parameter (to track cancel actions)
var functionAsStr = String(params.doneFunction).replace(/\s/g, '');
var functionHandlesCancel = functionAsStr.substring(0, 9) === "function(" && functionAsStr.substring(9, 10) !== ")";
if (functionHandlesCancel) {
params.doneFunction(false);
}
if (params.closeOnCancel) {
closeModal();
}
} else {
closeModal();
}
break;
}
};
var $buttons = modal.querySelectorAll('button');
for (var i = 0; i < $buttons.length; i++) {
$buttons[i].onclick = onButtonEvent;
}
// Remember the current document.onclick event.
previousDocumentClick = document.onclick;
document.onclick = function(e) {
var target = e.target || e.srcElement;
var clickedOnModal = (modal === target),
clickedOnModalChild = isDescendant(modal, e.target),
modalIsVisible = hasClass(modal, 'visible'),
outsideClickIsAllowed = modal.getAttribute('data-allow-ouside-click') === 'true';
if (!clickedOnModal && !clickedOnModalChild && modalIsVisible && outsideClickIsAllowed) {
closeModal();
}
};
// Keyboard interactions
var $okButton = modal.querySelector('button.confirm'),
$cancelButton = modal.querySelector('button.cancel'),
$modalButtons = modal.querySelectorAll('button:not([type=hidden])');
function handleKeyDown(e) {
var keyCode = e.keyCode || e.which;
if ([9,13,32,27].indexOf(keyCode) === -1) {
// Don't do work on keys we don't care about.
return;
}
var $targetElement = e.target || e.srcElement;
var btnIndex = -1; // Find the button - note, this is a nodelist, not an array.
for (var i = 0; i < $modalButtons.length; i++) {
if ($targetElement === $modalButtons[i]) {
btnIndex = i;
break;
}
}
if (keyCode === 9) {
// TAB
if (btnIndex === -1) {
// No button focused. Jump to the confirm button.
$targetElement = $okButton;
} else {
// Cycle to the next button
if (btnIndex === $modalButtons.length - 1) {
$targetElement = $modalButtons[0];
} else {
$targetElement = $modalButtons[btnIndex + 1];
}
}
stopEventPropagation(e);
$targetElement.focus();
} else {
if (keyCode === 13 || keyCode === 32) {
if (btnIndex === -1) {
// ENTER/SPACE clicked outside of a button.
$targetElement = $okButton;
} else {
// Do nothing - let the browser handle it.
$targetElement = undefined;
}
} else if (keyCode === 27 && !($cancelButton.hidden || $cancelButton.style.display === 'none')) {
// ESC to cancel only if there's a cancel button displayed (like the alert() window).
$targetElement = $cancelButton;
} else {
// Fallback - let the browser handle it.
$targetElement = undefined;
}
if ($targetElement !== undefined) {
fireClick($targetElement, e);
}
}
}
previousWindowKeyDown = window.onkeydown;
window.onkeydown = handleKeyDown;
function handleOnBlur(e) {
var $targetElement = e.target || e.srcElement,
$focusElement = e.relatedTarget,
modalIsVisible = hasClass(modal, 'visible');
if (modalIsVisible) {
var btnIndex = -1; // Find the button - note, this is a nodelist, not an array.
if ($focusElement !== null) {
// If we picked something in the DOM to focus to, let's see if it was a button.
for (var i = 0; i < $modalButtons.length; i++) {
if ($focusElement === $modalButtons[i]) {
btnIndex = i;
break;
}
}
if (btnIndex === -1) {
// Something in the dom, but not a visible button. Focus back on the button.
$targetElement.focus();
}
} else {
// Exiting the DOM (e.g. clicked in the URL bar);
lastFocusedButton = $targetElement;
}
}
}
$okButton.onblur = handleOnBlur;
$cancelButton.onblur = handleOnBlur;
window.onfocus = function() {
// When the user has focused away and focused back from the whole window.
window.setTimeout(function() {
// Put in a timeout to jump out of the event sequence. Calling focus() in the event
// sequence confuses things.
if (lastFocusedButton !== undefined) {
lastFocusedButton.focus();
lastFocusedButton = undefined;
}
}, 0);
};
};
/**
* Set default params for each popup
* @param {Object} userParams
*/
window.swal.setDefaults = function(userParams) {
if (!userParams) {
throw new Error('userParams is required');
}
if (typeof userParams !== 'object') {
throw new Error('userParams has to be a object');
}
extend(defaultParams, userParams);
};
/*
* Set type, text and actions on modal
*/
function setParameters(params) {
var modal = getModal();
var $title = modal.querySelector('h4'),
$text = modal.querySelector('p'),
$cancelBtn = modal.querySelector('button.cancel'),
$confirmBtn = modal.querySelector('button.confirm');
// Title
$title.innerHTML = escapeHtml(params.title).split("\n").join("<br>");
// Text
$text.innerHTML = escapeHtml(params.text || '').split("\n").join("<br>");
if (params.text) {
show($text);
}
// Icon
hide(modal.querySelectorAll('.icon'));
if (params.type) {
var validType = false;
for (var i = 0; i < alertTypes.length; i++) {
if (params.type === alertTypes[i]) {
validType = true;
break;
}
}
if (!validType) {
window.console.error('Unknown alert type: ' + params.type);
return false;
}
var $icon = modal.querySelector('.icon.' + params.type);
show($icon);
// Animate icon
switch (params.type) {
case "success":
addClass($icon, 'animate');
addClass($icon.querySelector('.tip'), 'animateSuccessTip');
addClass($icon.querySelector('.long'), 'animateSuccessLong');
break;
case "error":
addClass($icon, 'animateErrorIcon');
addClass($icon.querySelector('.x-mark'), 'animateXMark');
break;
case "warning":
addClass($icon, 'pulseWarning');
addClass($icon.querySelector('.body'), 'pulseWarningIns');
addClass($icon.querySelector('.dot'), 'pulseWarningIns');
break;
}
}
// Custom image
if (params.imageUrl) {
var $customIcon = modal.querySelector('.icon.custom');
$customIcon.style.backgroundImage = 'url(' + params.imageUrl + ')';
show($customIcon);
var _imgWidth = 80,
_imgHeight = 80;
if (params.imageSize) {
var imgWidth = params.imageSize.split('x')[0];
var imgHeight = params.imageSize.split('x')[1];
if (!imgWidth || !imgHeight) {
window.console.error("Parameter imageSize expects value with format WIDTHxHEIGHT, got " + params.imageSize);
} else {
_imgWidth = imgWidth;
_imgHeight = imgHeight;
$customIcon.css({
'width': imgWidth + 'px',
'height': imgHeight + 'px'
});
}
}
$customIcon.setAttribute('style', $customIcon.getAttribute('style') + 'width:' + _imgWidth + 'px; height:' + _imgHeight + 'px');
}
// Cancel button
modal.setAttribute('data-has-cancel-button', params.showCancelButton);
if (params.showCancelButton) {
$cancelBtn.style.display = 'inline-block';
} else {
hide($cancelBtn);
}
// Edit text on cancel and confirm buttons
if (params.cancelButtonText) {
$cancelBtn.innerHTML = escapeHtml(params.cancelButtonText);
}
if (params.confirmButtonText) {
$confirmBtn.innerHTML = escapeHtml(params.confirmButtonText);
}
// Reset confirm buttons to default class (Ugly fix)
$confirmBtn.className = 'confirm btn'
// Set confirm button to selected class
addClass($confirmBtn, params.confirmButtonClass);
// Set cancel button to selected class
addClass($cancelBtn, params.cancelButtonClass);
// Allow outside click?
modal.setAttribute('data-allow-ouside-click', params.allowOutsideClick);
// Done-function
var hasDoneFunction = (params.doneFunction) ? true : false;
modal.setAttribute('data-has-done-function', hasDoneFunction);
// Close timer
modal.setAttribute('data-timer', params.timer);
}
/*
* Set hover, active and focus-states for buttons (source: http://www.sitepoint.com/javascript-generate-lighter-darker-color)
*/
function colorLuminance(hex, lum) {
// Validate hex string
hex = String(hex).replace(/[^0-9a-f]/gi, '');
if (hex.length < 6) {
hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
}
lum = lum || 0;
// Convert to decimal and change luminosity
var rgb = "#", c, i;
for (i = 0; i < 3; i++) {
c = parseInt(hex.substr(i*2,2), 16);
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
rgb += ("00"+c).substr(c.length);
}
return rgb;
}
function extend(a, b){
for (var key in b) {
if (b.hasOwnProperty(key)) {
a[key] = b[key];
}
}
return a;
}
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? parseInt(result[1], 16) + ', ' + parseInt(result[2], 16) + ', ' + parseInt(result[3], 16) : null;
}
// Add box-shadow style to button (depending on its chosen bg-color)
function setFocusStyle($button, bgColor) {
var rgbColor = hexToRgb(bgColor);
$button.style.boxShadow = '0 0 2px rgba(' + rgbColor +', 0.8), inset 0 0 0 1px rgba(0, 0, 0, 0.05)';
}
/*
* Animations
*/
function openModal() {
var modal = getModal();
fadeIn(getOverlay(), 10);
show(modal);
addClass(modal, 'showSweetAlert');
removeClass(modal, 'hideSweetAlert');
previousActiveElement = document.activeElement;
var $okButton = modal.querySelector('button.confirm');
$okButton.focus();
setTimeout(function() {
addClass(modal, 'visible');
}, 500);
var timer = modal.getAttribute('data-timer');
if (timer !== "null" && timer !== "") {
setTimeout(function() {
closeModal();
}, timer);
}
}
function closeModal() {
var modal = getModal();
fadeOut(getOverlay(), 5);
fadeOut(modal, 5);
removeClass(modal, 'showSweetAlert');
addClass(modal, 'hideSweetAlert');
removeClass(modal, 'visible');
// Reset icon animations
var $successIcon = modal.querySelector('.icon.success');
removeClass($successIcon, 'animate');
removeClass($successIcon.querySelector('.tip'), 'animateSuccessTip');
removeClass($successIcon.querySelector('.long'), 'animateSuccessLong');
var $errorIcon = modal.querySelector('.icon.error');
removeClass($errorIcon, 'animateErrorIcon');
removeClass($errorIcon.querySelector('.x-mark'), 'animateXMark');
var $warningIcon = modal.querySelector('.icon.warning');
removeClass($warningIcon, 'pulseWarning');
removeClass($warningIcon.querySelector('.body'), 'pulseWarningIns');
removeClass($warningIcon.querySelector('.dot'), 'pulseWarningIns');
// Reset the page to its previous state
window.onkeydown = previousWindowKeyDown;
document.onclick = previousDocumentClick;
if (previousActiveElement) {
previousActiveElement.focus();
}
lastFocusedButton = undefined;
}
/*
* Set "margin-top"-property on modal based on its computed height
*/
function fixVerticalPosition() {
var modal = getModal();
modal.style.marginTop = getTopMargin(getModal());
}
/*
* If library is injected after page has loaded
*/
(function () {
if (document.readyState === "complete" || document.readyState === "interactive" && document.body) {
sweetAlertInitialize();
} else {
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', function factorial() {
document.removeEventListener('DOMContentLoaded', arguments.callee, false);
sweetAlertInitialize();
}, false);
} else if (document.attachEvent) {
document.attachEvent('onreadystatechange', function() {
if (document.readyState === 'complete') {
document.detachEvent('onreadystatechange', arguments.callee);
sweetAlertInitialize();
}
});
}
}
})();
})(window, document);

View File

@ -0,0 +1,258 @@
// SweetAlert
// 2014 (c) - Tristan Edwards
// github.com/t4t5/sweetalert
@import "sweet-alert-animations";
.sweet-overlay {
background-color: fade(#000, 40%);
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: none;
z-index: @zindex-modal + 7000;
}
.sweet-alert {
@width: 478px;
@padding: 17px;
background-color: @body-bg;
width: @width;
padding: @padding;
border-radius: 5px;
text-align: center;
position: fixed;
left: 50%;
top: 50%;
margin-left: -(@width / 2 + @padding);
margin-top: -200px;
overflow: hidden;
display: none;
z-index: @zindex-modal + 8000;
h4 {
margin: 30px 0;
}
@media all and (max-width: @screen-xs-max) {
width: auto;
margin-left: 0;
margin-right: 0;
left: (@grid-gutter-width / 2);
right: (@grid-gutter-width / 2);
}
.icon {
width: 80px;
height: 80px;
border: 4px solid gray;
border-radius: 50%;
margin: 20px auto;
position: relative;
box-sizing: content-box;
&.error {
border-color: @btn-danger-border;
.x-mark {
position: relative;
display: block;
}
.line {
position: absolute;
height: 5px;
width: 47px;
background-color: @btn-danger-bg;
display: block;
top: 37px;
border-radius: 2px;
&.left {
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
left: 17px;
}
&.right {
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
right: 16px;
}
}
}
&.warning {
border-color: @btn-warning-border;
.body { // Exclamation mark body
position: absolute;
width: 5px;
height: 47px;
left: 50%;
top: 10px;
border-radius: 2px;
margin-left: -2px;
background-color: @btn-warning-bg;
}
.dot { // Exclamation mark dot
position: absolute;
width: 7px;
height: 7px;
border-radius: 50%;
margin-left: -3px;
left: 50%;
bottom: 10px;
background-color: @btn-warning-bg;
}
}
&.info {
border-color: @btn-info-border;
&::before { // i-letter body
content: "";
position: absolute;
width: 5px;
height: 29px;
left: 50%;
bottom: 17px;
border-radius: 2px;
margin-left: -2px;
background-color: @btn-info-bg;
}
&::after { // i-letter dot
content: "";
position: absolute;
width: 7px;
height: 7px;
border-radius: 50%;
margin-left: -3px;
top: 19px;
background-color: @btn-info-bg;
}
}
&.success {
border-color: @btn-success-border;
&::before, &::after { // Emulate moving circular line
content: '';
border-radius: 50%;
position: absolute;
width: 60px;
height: 120px;
background: white;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
&::before {
border-radius: 120px 0 0 120px;
top: -7px;
left: -33px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-transform-origin: 60px 60px;
transform-origin: 60px 60px;
}
&::after {
border-radius: 0 120px 120px 0;
top: -11px;
left: 30px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-transform-origin: 0px 60px;
transform-origin: 0px 60px;
}
.placeholder { // Ring
width: 80px;
height: 80px;
border: 4px solid fade(@brand-success, 20%);
border-radius: 50%;
box-sizing: content-box;
position: absolute;
left: -4px;
top: -4px;
z-index: 2;
}
.fix { // Hide corners left from animation
width: 5px;
height: 90px;
background-color: @body-bg;
position: absolute;
left: 28px;
top: 8px;
z-index: 1;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.line {
height: 5px;
background-color: @btn-success-bg;
display: block;
border-radius: 2px;
position: absolute;
z-index: 2;
&.tip {
width: 25px;
left: 14px;
top: 46px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
&.long {
width: 47px;
right: 8px;
top: 38px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
}
}
}
&.custom {
background-size: contain;
border-radius: 0;
border: none;
background-position: center center;
background-repeat: no-repeat;
}
}
.btn-default {
.form-control-focus(@btn-default-border);
}
.btn-success {
.form-control-focus(@btn-success-border);
}
.btn-info {
.form-control-focus(@btn-info-border);
}
.btn-danger {
.form-control-focus(@btn-danger-border);
}
.btn-warning {
.form-control-focus(@btn-warning-border);
}
button::-moz-focus-inner {
border: 0;
}
}

View File

@ -7,6 +7,7 @@ use Event;
use Input;
use Redirect;
use Backend;
use Backend\Classes\FormField;
use Backend\Classes\ControllerBehavior;
use October\Rain\Support\Util;
use October\Rain\Router\Helper as RouterHelper;
@ -22,6 +23,21 @@ use Exception;
*/
class FormController extends ControllerBehavior
{
/**
* @var string Default context for "create" pages.
*/
const CONTEXT_CREATE = 'create';
/**
* @var string Default context for "update" pages.
*/
const CONTEXT_UPDATE = 'update';
/**
* @var string Default context for "preview" pages.
*/
const CONTEXT_PREVIEW = 'preview';
/**
* @var Backend\Classes\WidgetBase Reference to the widget object.
*/
@ -49,6 +65,11 @@ class FormController extends ControllerBehavior
*/
protected $modelsToSave = [];
/**
* @var Model The initialized model used by the form.
*/
protected $model;
/**
* Behavior constructor
* @param Backend\Classes\Controller $controller
@ -74,7 +95,22 @@ class FormController extends ControllerBehavior
{
$context = $this->formGetContext();
$config = $this->makeConfig($this->config->form);
/*
* Each page can supply a unique form definition, if desired
*/
$formFields = $this->config->form;
if ($context == self::CONTEXT_CREATE) {
$formFields = $this->getConfig('create[form]', $formFields);
}
elseif ($context == self::CONTEXT_UPDATE) {
$formFields = $this->getConfig('update[form]', $formFields);
}
elseif ($context == self::CONTEXT_PREVIEW) {
$formFields = $this->getConfig('preview[form]', $formFields);
}
$config = $this->makeConfig($formFields);
$config->model = $model;
$config->arrayName = class_basename($model);
$config->context = $context;
@ -110,6 +146,7 @@ class FormController extends ControllerBehavior
}
$this->prepareVars($model);
$this->model = $model;
}
/**
@ -133,14 +170,14 @@ class FormController extends ControllerBehavior
public function create($context = null)
{
try {
$this->context = strlen($context) ? $context : $this->getConfig('create[context]', 'create');
$this->context = strlen($context) ? $context : $this->getConfig('create[context]', self::CONTEXT_CREATE);
$this->controller->pageTitle = $this->controller->pageTitle ?: $this->getLang(
'create[title]',
'backend::lang.form.create_title'
);
$model = $this->controller->formCreateModelObject();
$this->initForm($model);
$this->controller->vars['formModel'] = $model;
}
catch (Exception $ex) {
$this->controller->handleError($ex);
@ -153,7 +190,7 @@ class FormController extends ControllerBehavior
*/
public function create_onSave($context = null)
{
$this->context = strlen($context) ? $context : $this->getConfig('create[context]', 'create');
$this->context = strlen($context) ? $context : $this->getConfig('create[context]', self::CONTEXT_CREATE);
$model = $this->controller->formCreateModelObject();
$this->initForm($model);
@ -188,15 +225,14 @@ class FormController extends ControllerBehavior
public function update($recordId = null, $context = null)
{
try {
$this->context = strlen($context) ? $context : $this->getConfig('update[context]', 'update');
$this->context = strlen($context) ? $context : $this->getConfig('update[context]', self::CONTEXT_UPDATE);
$this->controller->pageTitle = $this->controller->pageTitle ?: $this->getLang(
'update[title]',
'backend::lang.form.update_title'
);
$model = $this->controller->formFindModelObject($recordId);
$this->initForm($model);
$this->controller->vars['formModel'] = $model;
}
catch (Exception $ex) {
$this->controller->handleError($ex);
@ -210,14 +246,14 @@ class FormController extends ControllerBehavior
*/
public function update_onSave($recordId = null, $context = null)
{
$this->context = strlen($context) ? $context : $this->getConfig('update[context]', 'update');
$this->context = strlen($context) ? $context : $this->getConfig('update[context]', self::CONTEXT_UPDATE);
$model = $this->controller->formFindModelObject($recordId);
$this->initForm($model);
$this->controller->formBeforeSave($model);
$this->controller->formBeforeUpdate($model);
$modelsToSave =$this->prepareModelsToSave($model, $this->formWidget->getSaveData());
$modelsToSave = $this->prepareModelsToSave($model, $this->formWidget->getSaveData());
foreach ($modelsToSave as $modelToSave) {
$modelToSave->save(null, $this->formWidget->getSessionKey());
}
@ -239,7 +275,7 @@ class FormController extends ControllerBehavior
*/
public function update_onDelete($recordId = null)
{
$this->context = $this->getConfig('update[context]', 'update');
$this->context = $this->getConfig('update[context]', self::CONTEXT_UPDATE);
$model = $this->controller->formFindModelObject($recordId);
$this->initForm($model);
@ -267,15 +303,14 @@ class FormController extends ControllerBehavior
public function preview($recordId = null, $context = null)
{
try {
$this->context = strlen($context) ? $context : $this->getConfig('preview[context]', 'preview');
$this->context = strlen($context) ? $context : $this->getConfig('preview[context]', self::CONTEXT_PREVIEW);
$this->controller->pageTitle = $this->controller->pageTitle ?: $this->getLang(
'preview[title]',
'backend::lang.form.preview_title'
);
$model = $this->controller->formFindModelObject($recordId);
$this->initForm($model);
$this->controller->vars['formModel'] = $model;
}
catch (Exception $ex) {
$this->controller->handleError($ex);
@ -300,6 +335,15 @@ class FormController extends ControllerBehavior
return $this->formWidget->render($options);
}
/**
* Returns the model initialized by this form behavior.
* @return Model
*/
public function formGetModel()
{
return $this->model;
}
/**
* Returns the form context from the postback or configuration.
* @return string
@ -674,7 +718,7 @@ class FormController extends ControllerBehavior
) {
$this->setModelAttributes($model->{$attribute}, $value);
}
else {
elseif ($value !== FormField::NO_SAVE_DATA) {
$model->{$attribute} = $value;
}
}

View File

@ -450,6 +450,21 @@ class RelationController extends ControllerBehavior
return $results->lists($foreignKeyName);
}
//
// Overrides
//
/**
* Controller override: Extend the query used for populating the list
* after the default query is processed.
* @param October\Rain\Database\Builder $query
* @param string $field
* @param string $manageMode
*/
public function relationExtendQuery($query, $field, $manageMode)
{
}
//
// AJAX
@ -505,8 +520,9 @@ class RelationController extends ControllerBehavior
$this->beforeAjax();
if (($checkedIds = post('checked')) && is_array($checkedIds)) {
foreach ($checkedIds as $relationId) {
if (!$obj = $this->relationObject->find($relationId)) {
$relatedModel = $this->relationObject->getRelated();
foreach ($checkedIds as $relationId) {
if (!$obj = $relatedModel->find($relationId)) {
continue;
}
@ -588,7 +604,7 @@ class RelationController extends ControllerBehavior
/*
* Check for existing relation
*/
$foreignKeyName = $this->relationModel->getKeyName();
$foreignKeyName = $this->relationModel->getQualifiedKeyName();
$existing = $this->relationObject->where($foreignKeyName, $foreignId)->count();
if (!$existing) {
@ -625,25 +641,42 @@ class RelationController extends ControllerBehavior
protected function makeToolbarWidget()
{
if ($this->readOnly) {
return;
$defaultConfig = [];
/*
* Add buttons to toolbar
*/
$defaultButtons = null;
if (!$this->readOnly) {
$defaultButtons = '~/modules/backend/behaviors/relationcontroller/partials/_toolbar.htm';
}
$defaultConfig = [
'buttons' => '@/modules/backend/behaviors/relationcontroller/partials/_toolbar.htm',
];
$defaultConfig['buttons'] = $this->getConfig('view[toolbarButtons]', $defaultButtons);
/*
* Make config
*/
$toolbarConfig = $this->makeConfig($this->getConfig('toolbar', $defaultConfig));
$toolbarConfig->alias = $this->alias . 'Toolbar';
/*
* Add search to toolbar
*/
if ($this->viewMode == 'multi' && $this->getConfig('view[showSearch]')) {
$useSearch = $this->viewMode == 'multi' && $this->getConfig('view[showSearch]');
if ($useSearch) {
$toolbarConfig->search = [
'prompt' => 'backend::lang.list.search_prompt'
];
}
/*
* No buttons, no search should mean no toolbar
*/
if (empty($toolbarConfig->search) && empty($toolbarConfig->buttons))
return;
$toolbarWidget = $this->makeWidget('Backend\Widgets\Toolbar', $toolbarConfig);
$toolbarWidget->cssClasses[] = 'list-header';
@ -662,15 +695,16 @@ class RelationController extends ControllerBehavior
$config->showSorting = $this->getConfig('view[showSorting]', true);
$config->defaultSort = $this->getConfig('view[defaultSort]');
$config->recordsPerPage = $this->getConfig('view[recordsPerPage]');
$config->showCheckboxes = $this->getConfig('view[showCheckboxes]', !$this->readOnly);
if (!$this->readOnly) {
$config->recordOnClick = sprintf(
"$.oc.relationBehavior.clickManageListRecord(:id, '%s', '%s')",
$this->field,
$this->relationGetSessionKey()
);
$config->showCheckboxes = true;
}
$defaultOnClick = sprintf(
"$.oc.relationBehavior.clickManageListRecord(:id, '%s', '%s')",
$this->field,
$this->relationGetSessionKey()
);
$config->recordOnClick = $this->getConfig('view[recordOnClick]', $defaultOnClick);
$config->recordUrl = $this->getConfig('view[recordUrl]', null);
if ($emptyMessage = $this->getConfig('emptyMessage')) {
$config->noRecordsMessage = $emptyMessage;
@ -681,6 +715,8 @@ class RelationController extends ControllerBehavior
*/
$widget = $this->makeWidget('Backend\Widgets\Lists', $config);
$widget->bindEvent('list.extendQuery', function ($query) {
$this->controller->relationExtendQuery($query, $this->field, $this->manageMode);
$this->relationObject->setQuery($query);
if ($this->model->exists) {
$this->relationObject->addConstraints();
@ -800,6 +836,7 @@ class RelationController extends ControllerBehavior
*/
if ($this->manageMode == 'pivot' || $this->manageMode == 'list') {
$widget->bindEvent('list.extendQuery', function ($query) {
$this->controller->relationExtendQuery($query, $this->field, $this->manageMode);
/*
* Where not in the current list of related records

View File

@ -10,27 +10,41 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title">
<?= e(trans('backend::lang.relation.create_name', ['name'=>$relationLabel])) ?>
<?php if ($this->readOnly): ?>
<?= e(trans('backend::lang.relation.preview_name', ['name'=>$relationLabel])) ?>
<?php else: ?>
<?= e(trans('backend::lang.relation.update_name', ['name'=>$relationLabel])) ?>
<?php endif ?>
</h4>
</div>
<div class="modal-body">
<?= $relationManageWidget->render() ?>
<?= $relationManageWidget->render(['preview' => $this->readOnly]) ?>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary">
<?= e(trans('backend::lang.relation.update')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.relation.cancel')) ?>
</button>
<?php if ($this->readOnly): ?>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.relation.close')) ?>
</button>
<?php else: ?>
<button
type="submit"
class="btn btn-primary">
<?= e(trans('backend::lang.relation.update')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.relation.cancel')) ?>
</button>
<?php endif ?>
</div>
<?= Form::close() ?>
<?php else: ?>
@ -67,4 +81,4 @@
</div>
<?= Form::close() ?>
<?php endif ?>
<?php endif ?>

View File

@ -418,14 +418,8 @@ class Controller extends Extendable
500
);
}
catch (ApplicationException $ex) {
return Response::make($ex->getMessage(), 500);
}
catch (Exception $ex) {
return Response::make(
sprintf('"%s" on line %s of %s', $ex->getMessage(), $ex->getLine(), $ex->getFile()),
500
);
return Response::make(ApplicationException::getDetailedMessage($ex));
}
}
@ -527,8 +521,9 @@ class Controller extends Extendable
*/
public function handleError($exception)
{
$this->fatalError = $exception->getMessage();
$this->vars['fatalError'] = $exception->getMessage();
$errorMessage = ApplicationException::getDetailedMessage($exception);
$this->fatalError = $errorMessage;
$this->vars['fatalError'] = $errorMessage;
}
//

View File

@ -12,6 +12,11 @@ use HTML;
*/
class FormField
{
/**
* @var int Value returned when the form field should not contribute any save data.
*/
const NO_SAVE_DATA = -1;
/**
* @var string Form field name.
*/

View File

@ -42,11 +42,6 @@ abstract class FormWidgetBase extends WidgetBase
*/
public $previewMode = false;
/**
* @var int Value returned when the widget should not contribute any save data.
*/
const NO_SAVE_DATA = -1;
/**
* Constructor
* @param $controller Controller Active controller object.
@ -110,7 +105,12 @@ abstract class FormWidgetBase extends WidgetBase
public function getLoadData()
{
list($model, $attribute) = $this->getModelArrayAttribute($this->valueFrom);
return $model->{$attribute};
if (!is_null($model)) {
return $model->{$attribute};
}
return null;
}
/**

View File

@ -11,7 +11,7 @@ use Backend\Classes\Controller;
* @author Alexey Bobkov, Samuel Georges
*
*/
class Groups extends Controller
class UserGroups extends Controller
{
public $implement = [
'Backend.Behaviors.FormController',

View File

@ -4,6 +4,7 @@ use Backend;
use Redirect;
use BackendMenu;
use BackendAuth;
use Backend\Models\UserGroup;
use Backend\Classes\Controller;
use System\Classes\SettingsManager;
@ -85,6 +86,7 @@ class Users extends Controller
/**
* Add available permission fields to the User form.
* Mark default groups as checked for new Users.
*/
protected function formExtendFields($form)
{
@ -121,5 +123,15 @@ class Users extends Controller
}
$form->addTabFields($permissionFields);
/*
* Mark default groups
*/
if (!$form->model->exists) {
$defaultGroupIds = UserGroup::where('is_new_user_default', true)->lists('id');
$groupField = $form->getField('groups');
$groupField->value = $defaultGroupIds;
}
}
}

View File

@ -1,3 +0,0 @@
<div data-control="toolbar">
<a href="<?= Backend::url('backend/groups/create') ?>" class="btn btn-primary oc-icon-plus"><?= e(trans('backend::lang.user.group.new')) ?></a>
</div>

View File

@ -1,16 +0,0 @@
# ===================================
# Form Behavior Config
# ===================================
name: backend::lang.user.group.name
form: @/modules/backend/models/group/fields.yaml
modelClass: Backend\Models\UserGroup
defaultRedirect: backend/groups
create:
redirect: backend/groups/update/:id
redirectClose: backend/groups
update:
redirect: backend/groups
redirectClose: backend/groups

View File

@ -21,6 +21,7 @@ $requiredExtensions = [
'fileinfo' => extension_loaded('fileinfo'),
'Zip' => class_exists('ZipArchive'),
'cURL' => function_exists('curl_init') && defined('CURLOPT_FOLLOWLOCATION'),
'OpenSSL' => function_exists('openssl_random_pseudo_bytes'),
];
foreach ($writablePaths as $path) {

View File

@ -0,0 +1,3 @@
<div data-control="toolbar">
<a href="<?= Backend::url('backend/usergroups/create') ?>" class="btn btn-primary oc-icon-plus"><?= e(trans('backend::lang.user.group.new')) ?></a>
</div>

View File

@ -0,0 +1,16 @@
# ===================================
# Form Behavior Config
# ===================================
name: backend::lang.user.group.name
form: @/modules/backend/models/usergroup/fields.yaml
modelClass: Backend\Models\UserGroup
defaultRedirect: backend/usergroups
create:
redirect: backend/usergroups/update/:id
redirectClose: backend/usergroups
update:
redirect: backend/usergroups
redirectClose: backend/usergroups

View File

@ -3,9 +3,9 @@
# ===================================
title: backend::lang.user.group.list_title
list: @/modules/backend/models/group/columns.yaml
list: @/modules/backend/models/usergroup/columns.yaml
modelClass: Backend\Models\UserGroup
recordUrl: backend/groups/update/:id
recordUrl: backend/usergroups/update/:id
noRecordsMessage: backend::lang.list.no_records
recordsPerPage: 5
showCheckboxes: true

View File

@ -1,7 +1,7 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('backend/users') ?>"><?= e(trans('backend::lang.user.menu_label')) ?></a></li>
<li><a href="<?= Backend::url('backend/groups') ?>"><?= e(trans('backend::lang.user.group.menu_label')) ?></a></li>
<li><a href="<?= Backend::url('backend/usergroups') ?>"><?= e(trans('backend::lang.user.group.menu_label')) ?></a></li>
<li><?= e(trans($this->pageTitle)) ?></li>
</ul>
<?php Block::endPut() ?>
@ -40,5 +40,5 @@
<?php else: ?>
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
<p><a href="<?= Backend::url('backend/groups') ?>" class="btn btn-default"><?= e(trans('backend::lang.user.group.return')) ?></a></p>
<p><a href="<?= Backend::url('backend/usergroups') ?>" class="btn btn-default"><?= e(trans('backend::lang.user.group.return')) ?></a></p>
<?php endif ?>

View File

@ -1,7 +1,7 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('backend/users') ?>"><?= e(trans('backend::lang.user.menu_label')) ?></a></li>
<li><a href="<?= Backend::url('backend/groups') ?>"><?= e(trans('backend::lang.user.group.menu_label')) ?></a></li>
<li><a href="<?= Backend::url('backend/usergroups') ?>"><?= e(trans('backend::lang.user.group.menu_label')) ?></a></li>
<li><?= e(trans($this->pageTitle)) ?></li>
</ul>
<?php Block::endPut() ?>
@ -49,5 +49,5 @@
<?php else: ?>
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
<p><a href="<?= Backend::url('backend/groups') ?>" class="btn btn-default"><?= e(trans('backend::lang.user.group.return')) ?></a></p>
<p><a href="<?= Backend::url('backend/usergroups') ?>" class="btn btn-default"><?= e(trans('backend::lang.user.group.return')) ?></a></p>
<?php endif ?>

View File

@ -1,6 +1,6 @@
<div data-control="toolbar">
<a href="<?= Backend::url('backend/users/create') ?>" class="btn btn-primary oc-icon-plus"><?= e(trans('backend::lang.user.new')) ?></a>
<a href="<?= Backend::url('backend/groups') ?>" class="btn btn-default oc-icon-group"><?= e(trans('backend::lang.user.group.list_title')) ?></a>
<a href="<?= Backend::url('backend/usergroups') ?>" class="btn btn-default oc-icon-group"><?= e(trans('backend::lang.user.group.list_title')) ?></a>
<?php /* @todo
<div class="btn-group">
<button

View File

@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class DbBackendAddDescriptionField extends Migration
{
public function up()
{
Schema::table('backend_user_groups', function (Blueprint $table) {
$table->string('code')->nullable()->index();
$table->text('description')->nullable();
$table->boolean('is_new_user_default')->default(false);
});
}
public function down()
{
Schema::table('backend_user_groups', function (Blueprint $table) {
$table->dropColumn('code');
$table->dropColumn('description');
$table->dropColumn('is_new_user_default');
});
}
}

View File

@ -26,7 +26,10 @@ class SeedSetupAdmin extends Seeder
public function run()
{
$group = UserGroup::create([
'name' => 'Admins'
'name' => 'Admins',
'code' => 'admins',
'description' => 'Default group for administrators',
'is_new_user_default' => true
]);
$user = User::create([
@ -42,4 +45,5 @@ class SeedSetupAdmin extends Seeder
$user->addGroup($group);
}
}

View File

@ -11,6 +11,8 @@ use Backend\Classes\FormWidgetBase;
*/
class DatePicker extends FormWidgetBase
{
const TIME_PREFIX = '___time_';
/**
* {@inheritDoc}
*/
@ -36,7 +38,7 @@ class DatePicker extends FormWidgetBase
*/
public function init()
{
$this->mode = $this->getConfig('mode', $this->mode);
$this->mode = strtolower($this->getConfig('mode', $this->mode));
$this->minDate = $this->getConfig('minDate', $this->minDate);
$this->maxDate = $this->getConfig('maxDate', $this->maxDate);
}
@ -57,19 +59,44 @@ class DatePicker extends FormWidgetBase
{
$this->vars['name'] = $this->formField->getName();
$value = $this->getLoadData();
$this->vars['timeName'] = self::TIME_PREFIX.$this->formField->getName(false);
$this->vars['timeValue'] = null;
if ($this->mode != 'datetime' && $value) {
if (is_string($value)) {
$value = substr($value, 0, 10);
if ($value = $this->getLoadData()) {
/*
* Date / Time
*/
if ($this->mode == 'datetime') {
if (is_object($value)) {
$value = $value->toDateTimeString();
}
$dateTime = explode(' ', $value);
$value = $dateTime[0];
$this->vars['timeValue'] = isset($dateTime[1]) ? substr($dateTime[1], 0, 5) : '';
}
elseif (is_object($value)) {
$value = $value->toDateString();
/*
* Date
*/
elseif ($this->mode == 'date') {
if (is_string($value)) {
$value = substr($value, 0, 10);
}
elseif (is_object($value)) {
$value = $value->toDateString();
}
}
elseif ($this->mode == 'time') {
if (is_object($value)) {
$value = $value->toTimeString();
}
}
}
$this->vars['value'] = $value ?: '';
$this->vars['showTime'] = $this->mode == 'datetime' || $this->mode == 'time';
$this->vars['mode'] = $this->mode;
$this->vars['minDate'] = $this->minDate;
$this->vars['maxDate'] = $this->maxDate;
}
@ -80,11 +107,14 @@ class DatePicker extends FormWidgetBase
public function loadAssets()
{
$this->addCss('vendor/pikaday/css/pikaday.css', 'core');
$this->addCss('vendor/clockpicker/css/jquery-clockpicker.css', 'core');
$this->addCss('css/datepicker.css', 'core');
$this->addJs('vendor/moment/moment.js', 'core');
$this->addJs('vendor/pikaday/js/pikaday.js', 'core');
$this->addJs('vendor/pikaday/js/pikaday.jquery.js', 'core');
$this->addJs('vendor/clockpicker/js/jquery-clockpicker.js', 'core');
$this->addJs('js/datepicker.js', 'core');
$this->addJs('js/timepicker.js', 'core');
}
/**
@ -92,6 +122,17 @@ class DatePicker extends FormWidgetBase
*/
public function getSaveData($value)
{
return strlen($value) ? $value : null;
if (!strlen($value)) {
return null;
}
if ($this->mode == 'datetime') {
$value .= ' ' . post(self::TIME_PREFIX.$this->formField->getName(false)) . ':00';
}
elseif ($this->mode == 'time') {
$value .= ':00';
}
return $value;
}
}

View File

@ -5,6 +5,7 @@ use Input;
use Validator;
use System\Models\File;
use System\Classes\SystemException;
use Backend\Classes\FormField;
use Backend\Classes\FormWidgetBase;
use October\Rain\Support\ValidationException;
use Exception;
@ -196,7 +197,7 @@ class FileUpload extends FormWidgetBase
*/
public function getSaveData($value)
{
return FormWidgetBase::NO_SAVE_DATA;
return FormField::NO_SAVE_DATA;
}
/**

View File

@ -73,7 +73,7 @@ class Relation extends FormWidgetBase
if (!$this->model->hasRelation($this->relationName)) {
throw new SystemException(Lang::get(
'backend::lang.model.missing_relation',
['class'=>get_class($this->controller), 'relation'=>$this->relationName]
['class'=>get_class($this->model), 'relation'=>$this->relationName]
));
}
}

View File

@ -3,7 +3,6 @@
*
* Data attributes:
* - data-control="datepicker" - enables the plugin on an element
* - data-show-time="value" - allow the time to be chosen
* - data-min-date="value" - minimum date to allow
* - data-max-date="value" - maximum date to allow
* - data-year-range="value" - range of years to display
@ -11,7 +10,7 @@
* JavaScript API:
* $('a#someElement').datePicker({ option: 'value' })
*
* Dependences:
* Dependences:
* - Pikaday plugin (pikaday.js)
* - Pikaday jQuery addon (pikaday.jquery.js)
*/
@ -36,7 +35,6 @@
changeMonitor.pause()
this.$input.pikaday({
showTime: options.showTime,
minDate: new Date(options.minDate),
maxDate: new Date(options.maxDate),
yearRange: options.yearRange,
@ -56,7 +54,6 @@
}
DatePicker.DEFAULTS = {
showTime: false,
minDate: '2000-01-01',
maxDate: '2020-12-31',
yearRange: 10

View File

@ -0,0 +1,75 @@
/*
* TimePicker plugin
*
* Data attributes:
* - data-control="timepicker" - enables the plugin on an element
*
* JavaScript API:
* $('a#someElement').timePicker({ option: 'value' })
*
* Dependences:
* - Clockpicker plugin (jquery-clockpicker.js)
*/
+function ($) { "use strict";
// DATEPICKER CLASS DEFINITION
// ============================
var TimePicker = function(element, options) {
var self = this
this.options = options
this.$el = $(element)
this.$input = this.$el.find('input:first')
// Init
var $form = this.$el.closest('form'),
changeMonitor = $form.data('oc.changeMonitor')
if (changeMonitor !== undefined)
changeMonitor.pause()
this.$input.clockpicker()
if (changeMonitor !== undefined)
changeMonitor.resume()
}
TimePicker.DEFAULTS = {
}
// DATEPICKER PLUGIN DEFINITION
// ============================
var old = $.fn.timePicker
$.fn.timePicker = function (option) {
var args = Array.prototype.slice.call(arguments, 1)
return this.each(function () {
var $this = $(this)
var data = $this.data('oc.timepicker')
var options = $.extend({}, TimePicker.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('oc.timepicker', (data = new TimePicker(this, options)))
else if (typeof option == 'string') data[option].apply(data, args)
})
}
$.fn.timePicker.Constructor = TimePicker
// DATEPICKER NO CONFLICT
// =================
$.fn.timePicker.noConflict = function () {
$.fn.timePicker = old
return this
}
// DATEPICKER DATA-API
// ===============
$(document).on('render', function() {
$('[data-control="timepicker"]').timePicker()
});
}(window.jQuery);

View File

@ -0,0 +1,316 @@
/*!
* ClockPicker v0.0.7 for jQuery (http://weareoutman.github.io/clockpicker/)
* Copyright 2014 Wang Shenwei.
* Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
*
* Bootstrap v3.1.1 (http://getbootstrap.com)
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/* Picked from bootstrap: .popover, .btn, .text-primary */
.clockpicker-popover {
position: absolute;
top: 0;
left: 0;
z-index: 1010;
display: none;
max-width: 276px;
padding: 1px;
text-align: left;
white-space: normal;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, .2);
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
}
.clockpicker-popover.top {
margin-top: -10px;
}
.clockpicker-popover.right {
margin-left: 10px;
}
.clockpicker-popover.bottom {
margin-top: 10px;
}
.clockpicker-popover.left {
margin-left: -10px;
}
.clockpicker-popover .popover-title {
padding: 8px 14px;
margin: 0;
font-size: 14px;
font-weight: normal;
line-height: 18px;
background-color: #f7f7f7;
border-bottom: 1px solid #ebebeb;
border-radius: 5px 5px 0 0;
}
.clockpicker-popover .popover-content {
padding: 9px 14px;
}
.clockpicker-popover > .arrow,
.clockpicker-popover > .arrow:after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
/* The following are set in WordPress (wp-admin/css/revisions.css) - reset them to initial values */
overflow:visible;
margin:0;
padding:0;
z-index:auto;
background-color:transparent;
-webkit-box-shadow:none;
box-shadow:none;
bottom:auto;
left:auto;
right:auto;
top:auto;
-webkit-transform:none;
-ms-transform:none;
transform:none;
}
.clockpicker-popover > .arrow {
border-width: 11px;
}
.clockpicker-popover > .arrow:after {
content: "";
border-width: 10px;
}
.clockpicker-popover.top > .arrow {
bottom: -11px;
left: 50%;
margin-left: -11px;
border-top-color: #999;
border-top-color: rgba(0, 0, 0, .25);
border-bottom-width: 0;
}
.clockpicker-popover.top > .arrow:after {
bottom: 1px;
margin-left: -10px;
content: " ";
border-top-color: #fff;
border-bottom-width: 0;
}
.clockpicker-popover.right > .arrow {
top: 50%;
left: -11px;
margin-top: -11px;
border-right-color: #999;
border-right-color: rgba(0, 0, 0, .25);
border-left-width: 0;
}
.clockpicker-popover.right > .arrow:after {
bottom: -10px;
left: 1px;
content: " ";
border-right-color: #fff;
border-left-width: 0;
}
.clockpicker-popover.bottom > .arrow {
top: -11px;
left: 50%;
margin-left: -11px;
border-top-width: 0;
border-bottom-color: #999;
border-bottom-color: rgba(0, 0, 0, .25);
}
.clockpicker-popover.bottom > .arrow:after {
top: 1px;
margin-left: -10px;
content: " ";
border-top-width: 0;
border-bottom-color: #fff;
}
.clockpicker-popover.left > .arrow {
top: 50%;
right: -11px;
margin-top: -11px;
border-right-width: 0;
border-left-color: #999;
border-left-color: rgba(0, 0, 0, .25);
}
.clockpicker-popover.left > .arrow:after {
right: 1px;
bottom: -10px;
content: " ";
border-right-width: 0;
border-left-color: #fff;
}
/*!
* ClockPicker v{package.version} for Bootstrap (http://weareoutman.github.io/clockpicker/)
* Copyright 2014 Wang Shenwei.
* Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
*/
.clockpicker .input-group-addon {
cursor: pointer;
}
.clockpicker-moving {
cursor: move;
}
.clockpicker-align-left.popover > .arrow {
left: 25px;
}
.clockpicker-align-top.popover > .arrow {
top: 17px;
}
.clockpicker-align-right.popover > .arrow {
left: auto;
right: 25px;
}
.clockpicker-align-bottom.popover > .arrow {
top: auto;
bottom: 6px;
}
.clockpicker-popover .popover-title {
background-color: #fff;
color: #999;
font-size: 24px;
font-weight: bold;
line-height: 30px;
text-align: center;
}
.clockpicker-popover .popover-title span {
cursor: pointer;
}
.clockpicker-popover .popover-content {
background-color: #f8f8f8;
padding: 12px;
}
.popover-content:last-child {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
.clockpicker-plate {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 50%;
width: 200px;
height: 200px;
overflow: visible;
position: relative;
/* Disable text selection highlighting. Thanks to Hermanya */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.clockpicker-canvas,
.clockpicker-dial {
width: 200px;
height: 200px;
position: absolute;
left: -1px;
top: -1px;
}
.clockpicker-minutes {
visibility: hidden;
}
.clockpicker-tick {
border-radius: 50%;
color: #666;
line-height: 26px;
text-align: center;
width: 26px;
height: 26px;
position: absolute;
cursor: pointer;
}
.clockpicker-tick.active,
.clockpicker-tick:hover {
background-color: rgb(192, 229, 247);
background-color: rgba(0, 149, 221, .25);
}
.clockpicker-button {
background-image: none;
background-color: #fff;
border-width: 1px 0 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
margin: 0;
padding: 10px 0;
text-align: center;
}
.clockpicker-button:hover {
background-image: none;
background-color: #ebebeb;
}
.clockpicker-button:focus {
outline: none!important;
}
.clockpicker-dial {
-webkit-transition: -webkit-transform 350ms, opacity 350ms;
-moz-transition: -moz-transform 350ms, opacity 350ms;
-ms-transition: -ms-transform 350ms, opacity 350ms;
-o-transition: -o-transform 350ms, opacity 350ms;
transition: transform 350ms, opacity 350ms;
}
.clockpicker-dial-out {
opacity: 0;
}
.clockpicker-hours.clockpicker-dial-out {
-webkit-transform: scale(1.2, 1.2);
-moz-transform: scale(1.2, 1.2);
-ms-transform: scale(1.2, 1.2);
-o-transform: scale(1.2, 1.2);
transform: scale(1.2, 1.2);
}
.clockpicker-minutes.clockpicker-dial-out {
-webkit-transform: scale(.8, .8);
-moz-transform: scale(.8, .8);
-ms-transform: scale(.8, .8);
-o-transform: scale(.8, .8);
transform: scale(.8, .8);
}
.clockpicker-canvas {
-webkit-transition: opacity 175ms;
-moz-transition: opacity 175ms;
-ms-transition: opacity 175ms;
-o-transition: opacity 175ms;
transition: opacity 175ms;
}
.clockpicker-canvas-out {
opacity: 0.25;
}
.clockpicker-canvas-bearing,
.clockpicker-canvas-fg {
stroke: none;
fill: rgb(0, 149, 221);
}
.clockpicker-canvas-bg {
stroke: none;
fill: rgb(192, 229, 247);
}
.clockpicker-canvas-bg-trans {
fill: rgba(0, 149, 221, .25);
}
.clockpicker-canvas line {
stroke: rgb(0, 149, 221);
stroke-width: 1;
stroke-linecap: round;
/*shape-rendering: crispEdges;*/
}
.clockpicker-button.am-button {
margin: 1px;
padding: 5px;
border: 1px solid rgba(0, 0, 0, .2);
border-radius: 4px;
}
.clockpicker-button.pm-button {
margin: 1px 1px 1px 136px;
padding: 5px;
border: 1px solid rgba(0, 0, 0, .2);
border-radius: 4px;
}

View File

@ -0,0 +1,729 @@
/*!
* ClockPicker v0.0.7 (http://weareoutman.github.io/clockpicker/)
* Copyright 2014 Wang Shenwei.
* Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
*/
;(function(){
var $ = window.jQuery,
$win = $(window),
$doc = $(document),
$body;
// Can I use inline svg ?
var svgNS = 'http://www.w3.org/2000/svg',
svgSupported = 'SVGAngle' in window && (function(){
var supported,
el = document.createElement('div');
el.innerHTML = '<svg/>';
supported = (el.firstChild && el.firstChild.namespaceURI) == svgNS;
el.innerHTML = '';
return supported;
})();
// Can I use transition ?
var transitionSupported = (function(){
var style = document.createElement('div').style;
return 'transition' in style ||
'WebkitTransition' in style ||
'MozTransition' in style ||
'msTransition' in style ||
'OTransition' in style;
})();
// Listen touch events in touch screen device, instead of mouse events in desktop.
var touchSupported = 'ontouchstart' in window,
mousedownEvent = 'mousedown' + ( touchSupported ? ' touchstart' : ''),
mousemoveEvent = 'mousemove.clockpicker' + ( touchSupported ? ' touchmove.clockpicker' : ''),
mouseupEvent = 'mouseup.clockpicker' + ( touchSupported ? ' touchend.clockpicker' : '');
// Vibrate the device if supported
var vibrate = navigator.vibrate ? 'vibrate' : navigator.webkitVibrate ? 'webkitVibrate' : null;
function createSvgElement(name) {
return document.createElementNS(svgNS, name);
}
function leadingZero(num) {
return (num < 10 ? '0' : '') + num;
}
// Get a unique id
var idCounter = 0;
function uniqueId(prefix) {
var id = ++idCounter + '';
return prefix ? prefix + id : id;
}
// Clock size
var dialRadius = 100,
outerRadius = 80,
// innerRadius = 80 on 12 hour clock
innerRadius = 54,
tickRadius = 13,
diameter = dialRadius * 2,
duration = transitionSupported ? 350 : 1;
// Popover template
var tpl = [
'<div class="popover clockpicker-popover">',
'<div class="arrow"></div>',
'<div class="popover-title">',
'<span class="clockpicker-span-hours text-primary"></span>',
' : ',
'<span class="clockpicker-span-minutes"></span>',
'<span class="clockpicker-span-am-pm"></span>',
'</div>',
'<div class="popover-content">',
'<div class="clockpicker-plate">',
'<div class="clockpicker-canvas"></div>',
'<div class="clockpicker-dial clockpicker-hours"></div>',
'<div class="clockpicker-dial clockpicker-minutes clockpicker-dial-out"></div>',
'</div>',
'<span class="clockpicker-am-pm-block">',
'</span>',
'</div>',
'</div>'
].join('');
// ClockPicker
function ClockPicker(element, options) {
var popover = $(tpl),
plate = popover.find('.clockpicker-plate'),
hoursView = popover.find('.clockpicker-hours'),
minutesView = popover.find('.clockpicker-minutes'),
amPmBlock = popover.find('.clockpicker-am-pm-block'),
isInput = element.prop('tagName') === 'INPUT',
input = isInput ? element : element.find('input'),
addon = element.find('.input-group-addon'),
self = this,
timer;
this.id = uniqueId('cp');
this.element = element;
this.options = options;
this.isAppended = false;
this.isShown = false;
this.currentView = 'hours';
this.isInput = isInput;
this.input = input;
this.addon = addon;
this.popover = popover;
this.plate = plate;
this.hoursView = hoursView;
this.minutesView = minutesView;
this.amPmBlock = amPmBlock;
this.spanHours = popover.find('.clockpicker-span-hours');
this.spanMinutes = popover.find('.clockpicker-span-minutes');
this.spanAmPm = popover.find('.clockpicker-span-am-pm');
this.amOrPm = "PM";
// Setup for for 12 hour clock if option is selected
if (options.twelvehour) {
var amPmButtonsTemplate = ['<div class="clockpicker-am-pm-block">',
'<button type="button" class="btn btn-sm btn-default clockpicker-button clockpicker-am-button">',
'AM</button>',
'<button type="button" class="btn btn-sm btn-default clockpicker-button clockpicker-pm-button">',
'PM</button>',
'</div>'].join('');
var amPmButtons = $(amPmButtonsTemplate);
//amPmButtons.appendTo(plate);
////Not working b/c they are not shown when this runs
//$('clockpicker-am-button')
// .on("click", function() {
// self.amOrPm = "AM";
// $('.clockpicker-span-am-pm').empty().append('AM');
// });
//
//$('clockpicker-pm-button')
// .on("click", function() {
// self.amOrPm = "PM";
// $('.clockpicker-span-am-pm').empty().append('PM');
// });
$('<button type="button" class="btn btn-sm btn-default clockpicker-button am-button">' + "AM" + '</button>')
.on("click", function() {
self.amOrPm = "AM";
$('.clockpicker-span-am-pm').empty().append('AM');
}).appendTo(this.amPmBlock);
$('<button type="button" class="btn btn-sm btn-default clockpicker-button pm-button">' + "PM" + '</button>')
.on("click", function() {
self.amOrPm = 'PM';
$('.clockpicker-span-am-pm').empty().append('PM');
}).appendTo(this.amPmBlock);
}
if (! options.autoclose) {
// If autoclose is not setted, append a button
$('<button type="button" class="btn btn-sm btn-default btn-block clockpicker-button">' + options.donetext + '</button>')
.click($.proxy(this.done, this))
.appendTo(popover);
}
// Placement and arrow align - make sure they make sense.
if ((options.placement === 'top' || options.placement === 'bottom') && (options.align === 'top' || options.align === 'bottom')) options.align = 'left';
if ((options.placement === 'left' || options.placement === 'right') && (options.align === 'left' || options.align === 'right')) options.align = 'top';
popover.addClass(options.placement);
popover.addClass('clockpicker-align-' + options.align);
this.spanHours.click($.proxy(this.toggleView, this, 'hours'));
this.spanMinutes.click($.proxy(this.toggleView, this, 'minutes'));
// Show or toggle
input.on('focus.clockpicker click.clockpicker', $.proxy(this.show, this));
addon.on('click.clockpicker', $.proxy(this.toggle, this));
// Build ticks
var tickTpl = $('<div class="clockpicker-tick"></div>'),
i, tick, radian, radius;
// Hours view
if (options.twelvehour) {
for (i = 1; i < 13; i += 1) {
tick = tickTpl.clone();
radian = i / 6 * Math.PI;
radius = outerRadius;
tick.css('font-size', '120%');
tick.css({
left: dialRadius + Math.sin(radian) * radius - tickRadius,
top: dialRadius - Math.cos(radian) * radius - tickRadius
});
tick.html(i === 0 ? '00' : i);
hoursView.append(tick);
tick.on(mousedownEvent, mousedown);
}
} else {
for (i = 0; i < 24; i += 1) {
tick = tickTpl.clone();
radian = i / 6 * Math.PI;
var inner = i > 0 && i < 13;
radius = inner ? innerRadius : outerRadius;
tick.css({
left: dialRadius + Math.sin(radian) * radius - tickRadius,
top: dialRadius - Math.cos(radian) * radius - tickRadius
});
if (inner) {
tick.css('font-size', '120%');
}
tick.html(i === 0 ? '00' : i);
hoursView.append(tick);
tick.on(mousedownEvent, mousedown);
}
}
// Minutes view
for (i = 0; i < 60; i += 5) {
tick = tickTpl.clone();
radian = i / 30 * Math.PI;
tick.css({
left: dialRadius + Math.sin(radian) * outerRadius - tickRadius,
top: dialRadius - Math.cos(radian) * outerRadius - tickRadius
});
tick.css('font-size', '120%');
tick.html(leadingZero(i));
minutesView.append(tick);
tick.on(mousedownEvent, mousedown);
}
// Clicking on minutes view space
plate.on(mousedownEvent, function(e){
if ($(e.target).closest('.clockpicker-tick').length === 0) {
mousedown(e, true);
}
});
// Mousedown or touchstart
function mousedown(e, space) {
var offset = plate.offset(),
isTouch = /^touch/.test(e.type),
x0 = offset.left + dialRadius,
y0 = offset.top + dialRadius,
dx = (isTouch ? e.originalEvent.touches[0] : e).pageX - x0,
dy = (isTouch ? e.originalEvent.touches[0] : e).pageY - y0,
z = Math.sqrt(dx * dx + dy * dy),
moved = false;
// When clicking on minutes view space, check the mouse position
if (space && (z < outerRadius - tickRadius || z > outerRadius + tickRadius)) {
return;
}
e.preventDefault();
// Set cursor style of body after 200ms
var movingTimer = setTimeout(function(){
$body.addClass('clockpicker-moving');
}, 200);
// Place the canvas to top
if (svgSupported) {
plate.append(self.canvas);
}
// Clock
self.setHand(dx, dy, ! space, true);
// Mousemove on document
$doc.off(mousemoveEvent).on(mousemoveEvent, function(e){
e.preventDefault();
var isTouch = /^touch/.test(e.type),
x = (isTouch ? e.originalEvent.touches[0] : e).pageX - x0,
y = (isTouch ? e.originalEvent.touches[0] : e).pageY - y0;
if (! moved && x === dx && y === dy) {
// Clicking in chrome on windows will trigger a mousemove event
return;
}
moved = true;
self.setHand(x, y, false, true);
});
// Mouseup on document
$doc.off(mouseupEvent).on(mouseupEvent, function(e){
$doc.off(mouseupEvent);
e.preventDefault();
var isTouch = /^touch/.test(e.type),
x = (isTouch ? e.originalEvent.changedTouches[0] : e).pageX - x0,
y = (isTouch ? e.originalEvent.changedTouches[0] : e).pageY - y0;
if ((space || moved) && x === dx && y === dy) {
self.setHand(x, y);
}
if (self.currentView === 'hours') {
self.toggleView('minutes', duration / 2);
} else {
if (options.autoclose) {
self.minutesView.addClass('clockpicker-dial-out');
setTimeout(function(){
self.done();
}, duration / 2);
}
}
plate.prepend(canvas);
// Reset cursor style of body
clearTimeout(movingTimer);
$body.removeClass('clockpicker-moving');
// Unbind mousemove event
$doc.off(mousemoveEvent);
});
}
if (svgSupported) {
// Draw clock hands and others
var canvas = popover.find('.clockpicker-canvas'),
svg = createSvgElement('svg');
svg.setAttribute('class', 'clockpicker-svg');
svg.setAttribute('width', diameter);
svg.setAttribute('height', diameter);
var g = createSvgElement('g');
g.setAttribute('transform', 'translate(' + dialRadius + ',' + dialRadius + ')');
var bearing = createSvgElement('circle');
bearing.setAttribute('class', 'clockpicker-canvas-bearing');
bearing.setAttribute('cx', 0);
bearing.setAttribute('cy', 0);
bearing.setAttribute('r', 2);
var hand = createSvgElement('line');
hand.setAttribute('x1', 0);
hand.setAttribute('y1', 0);
var bg = createSvgElement('circle');
bg.setAttribute('class', 'clockpicker-canvas-bg');
bg.setAttribute('r', tickRadius);
var fg = createSvgElement('circle');
fg.setAttribute('class', 'clockpicker-canvas-fg');
fg.setAttribute('r', 3.5);
g.appendChild(hand);
g.appendChild(bg);
g.appendChild(fg);
g.appendChild(bearing);
svg.appendChild(g);
canvas.append(svg);
this.hand = hand;
this.bg = bg;
this.fg = fg;
this.bearing = bearing;
this.g = g;
this.canvas = canvas;
}
raiseCallback(this.options.init);
}
function raiseCallback(callbackFunction) {
if (callbackFunction && typeof callbackFunction === "function") {
callbackFunction();
}
}
// Default options
ClockPicker.DEFAULTS = {
'default': '', // default time, 'now' or '13:14' e.g.
fromnow: 0, // set default time to * milliseconds from now (using with default = 'now')
placement: 'bottom', // clock popover placement
align: 'left', // popover arrow align
donetext: '完成', // done button text
autoclose: false, // auto close when minute is selected
twelvehour: false, // change to 12 hour AM/PM clock from 24 hour
vibrate: true // vibrate the device when dragging clock hand
};
// Show or hide popover
ClockPicker.prototype.toggle = function(){
this[this.isShown ? 'hide' : 'show']();
};
// Set popover position
ClockPicker.prototype.locate = function(){
var element = this.element,
popover = this.popover,
offset = element.offset(),
width = element.outerWidth(),
height = element.outerHeight(),
placement = this.options.placement,
align = this.options.align,
styles = {},
self = this;
popover.show();
// Place the popover
switch (placement) {
case 'bottom':
styles.top = offset.top + height;
break;
case 'right':
styles.left = offset.left + width;
break;
case 'top':
styles.top = offset.top - popover.outerHeight();
break;
case 'left':
styles.left = offset.left - popover.outerWidth();
break;
}
// Align the popover arrow
switch (align) {
case 'left':
styles.left = offset.left;
break;
case 'right':
styles.left = offset.left + width - popover.outerWidth();
break;
case 'top':
styles.top = offset.top;
break;
case 'bottom':
styles.top = offset.top + height - popover.outerHeight();
break;
}
popover.css(styles);
};
// Show popover
ClockPicker.prototype.show = function(e){
// Not show again
if (this.isShown) {
return;
}
raiseCallback(this.options.beforeShow);
var self = this;
// Initialize
if (! this.isAppended) {
// Append popover to body
$body = $(document.body).append(this.popover);
// Reset position when resize
$win.on('resize.clockpicker' + this.id, function(){
if (self.isShown) {
self.locate();
}
});
this.isAppended = true;
}
// Get the time
var value = ((this.input.prop('value') || this.options['default'] || '') + '').split(':');
if (value[0] === 'now') {
var now = new Date(+ new Date() + this.options.fromnow);
value = [
now.getHours(),
now.getMinutes()
];
}
this.hours = + value[0] || 0;
this.minutes = + value[1] || 0;
this.spanHours.html(leadingZero(this.hours));
this.spanMinutes.html(leadingZero(this.minutes));
// Toggle to hours view
this.toggleView('hours');
// Set position
this.locate();
this.isShown = true;
// Hide when clicking or tabbing on any element except the clock, input and addon
$doc.on('click.clockpicker.' + this.id + ' focusin.clockpicker.' + this.id, function(e){
var target = $(e.target);
if (target.closest(self.popover).length === 0 &&
target.closest(self.addon).length === 0 &&
target.closest(self.input).length === 0) {
self.hide();
}
});
// Hide when ESC is pressed
$doc.on('keyup.clockpicker.' + this.id, function(e){
if (e.keyCode === 27) {
self.hide();
}
});
raiseCallback(this.options.afterShow);
};
// Hide popover
ClockPicker.prototype.hide = function(){
raiseCallback(this.options.beforeHide);
this.isShown = false;
// Unbinding events on document
$doc.off('click.clockpicker.' + this.id + ' focusin.clockpicker.' + this.id);
$doc.off('keyup.clockpicker.' + this.id);
this.popover.hide();
raiseCallback(this.options.afterHide);
};
// Toggle to hours or minutes view
ClockPicker.prototype.toggleView = function(view, delay){
var raiseAfterHourSelect = false;
if (view === 'minutes' && $(this.hoursView).css("visibility") === "visible") {
raiseCallback(this.options.beforeHourSelect);
raiseAfterHourSelect = true;
}
var isHours = view === 'hours',
nextView = isHours ? this.hoursView : this.minutesView,
hideView = isHours ? this.minutesView : this.hoursView;
this.currentView = view;
this.spanHours.toggleClass('text-primary', isHours);
this.spanMinutes.toggleClass('text-primary', ! isHours);
// Let's make transitions
hideView.addClass('clockpicker-dial-out');
nextView.css('visibility', 'visible').removeClass('clockpicker-dial-out');
// Reset clock hand
this.resetClock(delay);
// After transitions ended
clearTimeout(this.toggleViewTimer);
this.toggleViewTimer = setTimeout(function(){
hideView.css('visibility', 'hidden');
}, duration);
if (raiseAfterHourSelect) {
raiseCallback(this.options.afterHourSelect);
}
};
// Reset clock hand
ClockPicker.prototype.resetClock = function(delay){
var view = this.currentView,
value = this[view],
isHours = view === 'hours',
unit = Math.PI / (isHours ? 6 : 30),
radian = value * unit,
radius = isHours && value > 0 && value < 13 ? innerRadius : outerRadius,
x = Math.sin(radian) * radius,
y = - Math.cos(radian) * radius,
self = this;
if (svgSupported && delay) {
self.canvas.addClass('clockpicker-canvas-out');
setTimeout(function(){
self.canvas.removeClass('clockpicker-canvas-out');
self.setHand(x, y);
}, delay);
} else {
this.setHand(x, y);
}
};
// Set clock hand to (x, y)
ClockPicker.prototype.setHand = function(x, y, roundBy5, dragging){
var radian = Math.atan2(x, - y),
isHours = this.currentView === 'hours',
unit = Math.PI / (isHours || roundBy5 ? 6 : 30),
z = Math.sqrt(x * x + y * y),
options = this.options,
inner = isHours && z < (outerRadius + innerRadius) / 2,
radius = inner ? innerRadius : outerRadius,
value;
if (options.twelvehour) {
radius = outerRadius;
}
// Radian should in range [0, 2PI]
if (radian < 0) {
radian = Math.PI * 2 + radian;
}
// Get the round value
value = Math.round(radian / unit);
// Get the round radian
radian = value * unit;
// Correct the hours or minutes
if (options.twelvehour) {
if (isHours) {
if (value === 0) {
value = 12;
}
} else {
if (roundBy5) {
value *= 5;
}
if (value === 60) {
value = 0;
}
}
} else {
if (isHours) {
if (value === 12) {
value = 0;
}
value = inner ? (value === 0 ? 12 : value) : value === 0 ? 0 : value + 12;
} else {
if (roundBy5) {
value *= 5;
}
if (value === 60) {
value = 0;
}
}
}
// Once hours or minutes changed, vibrate the device
if (this[this.currentView] !== value) {
if (vibrate && this.options.vibrate) {
// Do not vibrate too frequently
if (! this.vibrateTimer) {
navigator[vibrate](10);
this.vibrateTimer = setTimeout($.proxy(function(){
this.vibrateTimer = null;
}, this), 100);
}
}
}
this[this.currentView] = value;
this[isHours ? 'spanHours' : 'spanMinutes'].html(leadingZero(value));
// If svg is not supported, just add an active class to the tick
if (! svgSupported) {
this[isHours ? 'hoursView' : 'minutesView'].find('.clockpicker-tick').each(function(){
var tick = $(this);
tick.toggleClass('active', value === + tick.html());
});
return;
}
// Place clock hand at the top when dragging
if (dragging || (! isHours && value % 5)) {
this.g.insertBefore(this.hand, this.bearing);
this.g.insertBefore(this.bg, this.fg);
this.bg.setAttribute('class', 'clockpicker-canvas-bg clockpicker-canvas-bg-trans');
} else {
// Or place it at the bottom
this.g.insertBefore(this.hand, this.bg);
this.g.insertBefore(this.fg, this.bg);
this.bg.setAttribute('class', 'clockpicker-canvas-bg');
}
// Set clock hand and others' position
var cx = Math.sin(radian) * radius,
cy = - Math.cos(radian) * radius;
this.hand.setAttribute('x2', cx);
this.hand.setAttribute('y2', cy);
this.bg.setAttribute('cx', cx);
this.bg.setAttribute('cy', cy);
this.fg.setAttribute('cx', cx);
this.fg.setAttribute('cy', cy);
};
// Hours and minutes are selected
ClockPicker.prototype.done = function() {
raiseCallback(this.options.beforeDone);
this.hide();
var last = this.input.prop('value'),
value = leadingZero(this.hours) + ':' + leadingZero(this.minutes);
if (this.options.twelvehour) {
value = value + this.amOrPm;
}
this.input.prop('value', value);
if (value !== last) {
this.input.triggerHandler('change');
if (! this.isInput) {
this.element.trigger('change');
}
}
if (this.options.autoclose) {
this.input.trigger('blur');
}
raiseCallback(this.options.afterDone);
};
// Remove clockpicker from input
ClockPicker.prototype.remove = function() {
this.element.removeData('clockpicker');
this.input.off('focus.clockpicker click.clockpicker');
this.addon.off('click.clockpicker');
if (this.isShown) {
this.hide();
}
if (this.isAppended) {
$win.off('resize.clockpicker' + this.id);
this.popover.remove();
}
};
// Extends $.fn.clockpicker
$.fn.clockpicker = function(option){
var args = Array.prototype.slice.call(arguments, 1);
return this.each(function(){
var $this = $(this),
data = $this.data('clockpicker');
if (! data) {
var options = $.extend({}, ClockPicker.DEFAULTS, $this.data(), typeof option == 'object' && option);
$this.data('clockpicker', new ClockPicker($this, options));
} else {
// Manual operatsions. show, hide, remove, e.g.
if (typeof data[option] === 'function') {
data[option].apply(data, args);
}
}
});
};
}());

View File

@ -1,19 +1,82 @@
<?php if ($this->previewMode): ?>
<div class="form-control"><?= $value ?></div>
<?php else: ?>
<div
id="<?= $this->getId() ?>"
class="field-datepicker"
data-control="datepicker"
data-show-time="<?= $showTime ? 'true' : 'false' ?>"
data-min-date="<?= $minDate ?>"
data-max-date="<?= $maxDate ?>">
<input
type="text"
id="<?= $this->getId('input') ?>"
name="<?= $name ?>"
value="<?= $value ?>"
class="form-control align-right"
autocomplete="off">
</div>
<?php if ($mode == 'date'): ?>
<div
id="<?= $this->getId() ?>"
class="field-datepicker"
data-control="datepicker"
data-min-date="<?= $minDate ?>"
data-max-date="<?= $maxDate ?>">
<input
type="text"
id="<?= $this->getId('input') ?>"
name="<?= $name ?>"
value="<?= $value ?>"
class="form-control align-right"
autocomplete="off">
</div>
<?php elseif ($mode == 'datetime'): ?>
<div class="row">
<div class="col-md-6">
<!-- Date -->
<div
id="<?= $this->getId() ?>"
class="field-datepicker"
data-control="datepicker"
data-min-date="<?= $minDate ?>"
data-max-date="<?= $maxDate ?>">
<input
type="text"
id="<?= $this->getId('input') ?>"
name="<?= $name ?>"
value="<?= $value ?>"
class="form-control align-right"
autocomplete="off">
</div>
</div>
<div class="col-md-6">
<!-- Time -->
<div
id="<?= $this->getId() ?>-time"
data-control="timepicker"
class="field-timepicker clockpicker">
<input
type="text"
id="<?= $this->getId('input') ?>-time"
name="<?= $timeName ?>"
value="<?= $timeValue ?>"
class="form-control align-right"
autocomplete="off"
data-autoclose="true"
data-placement="bottom"
data-align="right">
</div>
</div>
</div>
<?php elseif ($mode == 'time'): ?>
<div
id="<?= $this->getId() ?>"
data-control="timepicker"
class="field-timepicker clockpicker">
<input
type="text"
id="<?= $this->getId('input') ?>"
name="<?= $name ?>"
value="<?= $value ?>"
class="form-control align-right"
autocomplete="off"
data-autoclose="true"
data-placement="bottom"
data-align="right">
</div>
<?php endif ?>
<?php endif ?>

View File

@ -66,7 +66,7 @@
<?php endif ?>
<a
href="{{path}}"
class="uploader-file-link oc-icon-paper-clip"
class="uploader-file-link oc-icon-paperclip"
target="_blank"></a>
</div>
</div>
@ -77,4 +77,4 @@
<!-- Existing images -->
<script> $('#<?= $this->getId() ?>').data('populatedData', <?= $fileList ?>); </script>
</div>
</div>

View File

@ -97,6 +97,10 @@ return [
'group' => [
'name' => 'Group',
'name_field' => 'Name',
'description_field' => 'Description',
'is_new_user_default_field' => 'Add new administrators to this group by default',
'code_field' => 'Code',
'code_comment' => 'Enter a unique code if you want to access it with the API.',
'menu_label' => 'Groups',
'list_title' => 'Manage Groups',
'new' => 'New Administrator Group',
@ -155,6 +159,9 @@ return [
'delete' => 'Delete',
'deleting' => 'Deleting...',
'deleting_name' => 'Deleting :name...',
'reset_default' => 'Reset to default',
'resetting' => 'Resetting',
'resetting_name' => 'Resetting :name',
'undefined_tab' => 'Misc',
'field_off' => 'Off',
'field_on' => 'On',
@ -189,11 +196,14 @@ return [
'add_selected' => "Add selected",
'add_a_new' => "Add a new :name",
'cancel' => "Cancel",
'close' => "Close",
'add_name' => "Add :name",
'create' => "Create",
'create_name' => "Create :name",
'update' => "Update",
'update_name' => "Update :name",
'preview' => "Preview",
'preview_name' => "Preview :name",
'remove' => "Remove",
'remove_name' => "Remove :name",
'delete' => "Delete",

View File

@ -155,6 +155,9 @@ return [
'delete' => 'Törlés',
'deleting' => 'Törlés...',
'deleting_name' => 'A(z) :name törlése...',
'reset_default' => 'Alaphelyzet',
'resetting' => 'Visszaállítás',
'resetting_name' => 'A(z) :name visszaállítása',
'undefined_tab' => 'Egyebek',
'field_off' => 'Ki',
'field_on' => 'Be',
@ -210,8 +213,8 @@ return [
],
'warnings' => [
'tips' => 'Rendszerkonfigurációs tippek',
'tips_description' => 'Vannak olyan problémák, melyekre a rendszer megfelelő konfigurálása érdekében oda kell figyelnie.',
'permissions' => 'A(z) :name nevű könyvtár vagy alkönyvtárai a PHP számára nem írhatóak. Adjon megfelelő engedélyeket a webkiszolgálónak erre a könyvtárra.',
'tips_description' => 'Olyan problémák vannak, melyekre figyeljen oda a rendszer megfelelő konfigurálása érdekében.',
'permissions' => 'A(z) :name könyvtár vagy alkönyvtárai a PHP számára nem írhatóak. Adjon megfelelő engedélyeket a webkiszolgálónak erre a könyvtárra.',
'extension' => 'A(z) :name PHP-kiterjesztés nincs telepítve. Telepítse ezt a függvénytárat, és aktiválja a kiterjesztést.'
],
'editor' => [

View File

@ -65,6 +65,7 @@ return [
'status' => [
'widget_title_default' => 'Статус системы',
'online' => 'Онлайн',
'maintenance' => 'в разработке',
'update_available' => '{0} нет новый обновлений!|{1} доступно новое обновление!|[2,Inf] доступны новые обновления!',
]
],
@ -154,6 +155,9 @@ return [
'delete' => 'Удалить',
'deleting' => 'Удаление...',
'deleting_name' => 'Удаление :name...',
'reset_default' => 'Сбросить настройки',
'resetting' => 'Сброс',
'resetting_name' => 'Сброс :name',
'undefined_tab' => 'Разное',
'field_off' => 'Выкл',
'field_on' => 'Вкл',

View File

@ -9,7 +9,7 @@
<link href="<?= Backend::skinAsset('assets/vendor/select2/select2.css') ?>" rel="stylesheet">
<link href="<?= Backend::skinAsset('assets/css/october.css') ?>?v<?= System\Models\Parameters::get('system::core.build', 1) ?>" rel="stylesheet">
<script src="<?= Backend::skinAsset('assets/js/vendor/jquery-2.0.3.min.js') ?>"></script>
<script src="<?= Backend::skinAsset('assets/js/vendor/jquery.min.js') ?>"></script>
<script src="<?= URL::asset('modules/system/assets/js/framework.js') ?>"></script>
<script src="<?= Backend::skinAsset('assets/js/vendor/modernizr.min.js') ?>"></script>
@ -23,6 +23,7 @@
<script src="<?= Backend::skinAsset('assets/vendor/select2/select2.js') ?>"></script>
<script src="<?= Backend::skinAsset('assets/vendor/mustache/mustache.min.js') ?>"></script>
<script src="<?= Backend::skinAsset('assets/vendor/dropzone/dropzone.js') ?>"></script>
<script src="<?= Backend::skinAsset('assets/vendor/sweet-alert/sweet-alert.js') ?>"></script>
<script src="<?= URL::asset('modules/system/assets/vendor/bootstrap/js/tooltip.js') ?>"></script>
<script src="<?= URL::asset('modules/system/assets/vendor/bootstrap/js/modal.js') ?>"></script>

View File

@ -6,7 +6,7 @@
<title><?= e(trans('backend::lang.auth.title')) ?></title>
<link href="<?= URL::to('modules/backend/assets/css/october.css') ?>" rel="stylesheet">
<script src="<?= URL::to('modules/backend/assets/js/vendor/jquery-2.0.3.min.js') ?>"></script>
<script src="<?= URL::to('modules/backend/assets/js/vendor/jquery.min.js') ?>"></script>
<script src="<?= URL::to('modules/system/assets/js/framework.js') ?>"></script>
<script src="<?= URL::to('modules/backend/assets/js/october.utils.js') ?>"></script>
<script src="<?= URL::to('modules/backend/assets/js/vendor/modernizr.min.js') ?>"></script>

View File

@ -111,4 +111,13 @@ class User extends UserBase
$message->to($this->email, $this->full_name);
});
}
public function getGroupsOptions()
{
$result = [];
foreach (UserGroup::all() as $group) {
$result[$group->id] = [$group->name, $group->description];
}
return $result;
}
}

View File

@ -19,7 +19,7 @@ class UserGroup extends GroupBase
* @var array Validation rules
*/
public $rules = [
'name' => 'required|between:4,16|unique:backend_user_groups',
'name' => 'required|between:2,128|unique:backend_user_groups',
];
/**
@ -28,4 +28,16 @@ class UserGroup extends GroupBase
public $belongsToMany = [
'users' => ['Backend\Models\User', 'table' => 'backend_users_groups']
];
public function afterCreate()
{
if ($this->is_new_user_default) {
$this->addAllUsersToGroup();
}
}
public function addAllUsersToGroup()
{
$this->users()->sync(User::lists('id'));
}
}

View File

@ -1,10 +0,0 @@
# ===================================
# Field Definitions
# ===================================
fields:
name:
label: backend::lang.user.group.name_field
tabs:
stretch: true

View File

@ -41,7 +41,6 @@ tabs:
permissions[superuser]:
context: [create, update]
tab: Permissions
span: left
label: backend::lang.user.superuser
type: checkbox
comment: backend::lang.user.superuser_comment
@ -49,10 +48,9 @@ tabs:
groups:
context: [create, update]
tab: Permissions
span: right
label: backend::lang.user.groups
commentAbove: backend::lang.user.groups_comment
type: relation
type: checkboxlist
secondaryTabs:
fields:

View File

@ -5,4 +5,8 @@
columns:
name:
label: backend::lang.user.group.name_field
searchable: yes
description:
label: backend::lang.user.group.description_field
searchable: yes

View File

@ -0,0 +1,26 @@
# ===================================
# Field Definitions
# ===================================
fields:
name:
label: backend::lang.user.group.name_field
span: auto
code:
label: backend::lang.user.group.code_field
comment: backend::lang.user.group.code_comment
span: auto
description:
label: backend::lang.user.group.description_field
type: textarea
size: tiny
is_new_user_default:
label: backend::lang.user.group.is_new_user_default_field
type: checkbox
tabs:
stretch: true

View File

@ -710,7 +710,7 @@ class Form extends WidgetBase
}
$fieldName = $field->fieldName;
$defaultValue = (!$this->model->exists) && strlen($field->defaults) ? $field->defaults : null;
$defaultValue = (!$this->model->exists) && !empty($field->defaults) ? $field->defaults : null;
/*
* Array field name, eg: field[key][key2][key3]
@ -803,14 +803,11 @@ class Form extends WidgetBase
* Give widgets an opportunity to process the data.
*/
foreach ($this->formWidgets as $field => $widget) {
$widgetValue = array_key_exists($field, $data)
? $data[$field]
: null;
$parts = Str::evalHtmlArray($field);
$dotted = implode('.', $parts);
$data[$field] = $widget->getSaveData($widgetValue);
if ($data[$field] === FormWidgetBase::NO_SAVE_DATA) {
unset($data[$field]);
}
$widgetValue = $widget->getSaveData(array_get($data, $dotted));
array_set($data, $dotted, $widgetValue);
}
/*

View File

@ -273,7 +273,7 @@ class Lists extends WidgetBase
/**
* Applies any filters to the model.
*/
protected function prepareModel()
public function prepareModel()
{
$query = $this->model->newQuery();
$primaryTable = $this->model->getTable();
@ -416,8 +416,8 @@ class Lists extends WidgetBase
* Apply sorting
*/
if ($sortColumn = $this->getSortColumn()) {
if (($column = array_get($this->columns, $sortColumn)) && $column->sqlSelect) {
$sortColumn = $column->sqlSelect;
if (($column = array_get($this->columns, $sortColumn)) && $column->valueFrom) {
$sortColumn = $column->valueFrom;
}
$query->orderBy($sortColumn, $this->sortDirection);
@ -681,13 +681,14 @@ class Lists extends WidgetBase
*/
public function getColumnValue($record, $column)
{
$columnName = $column->columnName;
/*
* Handle taking name from model attribute.
* Handle taking value from model relation.
*/
if ($column->valueFrom) {
if ($column->valueFrom && $column->relation) {
$columnName = $column->relation;
if (!array_key_exists($columnName, $record->getRelations())) {
$value = null;
}
@ -698,14 +699,20 @@ class Lists extends WidgetBase
$value = $record->{$columnName}->{$column->valueFrom};
}
else {
$value = $record->{$column->valueFrom};
$value = null;
}
}
/*
* Handle taking value from model attribute.
*/
elseif ($column->valueFrom) {
$value = $record->{$column->valueFrom};
}
/*
* Otherwise, if the column is a relation, it will be a custom select,
* so prevent the Model from attempting to load the relation
* if the value is NULL.
*/
}
else {
if ($record->hasRelation($columnName) && array_key_exists($columnName, $record->attributes)) {
$value = $record->attributes[$columnName];

View File

@ -5,48 +5,86 @@
<!-- Checkbox List -->
<?php if (count($fieldOptions)): ?>
<?php if (count($fieldOptions) > 10): ?>
<!-- Quick selection -->
<small>
<?= e(trans('backend::lang.form.select')) ?>:
<a href="javascript:;" onclick="jQuery('#<?= $field->getId('scrollable') ?> input[type=checkbox]').prop('checked', true)"><?= e(trans('backend::lang.form.select_all')) ?></a>,
<a href="javascript:;" onclick="jQuery('#<?= $field->getId('scrollable') ?> input[type=checkbox]').prop('checked', false)"><?= e(trans('backend::lang.form.select_none')) ?></a>
</small>
<?php if ($this->previewMode): ?>
<!-- Read-only -->
<!-- Scrollable Checkbox list -->
<div class="field-checkboxlist-scrollable" id="<?= $field->getId('scrollable') ?>">
<div class="control-scrollbar" data-control="scrollbar">
<?php endif ?>
<?php $index = 0; foreach ($fieldOptions as $value => $option): ?>
<?php
$index++;
$checkboxId = 'checkbox_'.$field->getId().'_'.$index;
if (!in_array($value, $checkedValues)) continue;
if (is_string($option)) $option = [$option];
?>
<div class="checkbox custom-checkbox">
<input
type="checkbox"
id="<?= $checkboxId ?>"
name="<?= $field->getName() ?>[]"
value="<?= $value ?>"
disabled="disabled"
checked="checked">
<?php $index = 0; foreach ($fieldOptions as $value => $option): ?>
<?php
$index++;
$checkboxId = 'checkbox_'.$field->getId().'_'.$index;
if (is_string($option)) $option = [$option];
?>
<div class="checkbox custom-checkbox">
<input
type="checkbox"
id="<?= $checkboxId ?>"
name="<?= $field->getName() ?>[]"
value="<?= $value ?>"
<?= $this->previewMode ? 'readonly="readonly"' : '' ?>
<?= in_array($value, $checkedValues) ? 'checked="checked"' : '' ?>>
<label for="<?= $checkboxId ?>">
<?= e(trans($option[0])) ?>
</label>
<?php if (isset($option[1])): ?>
<p class="help-block"><?= e(trans($option[1])) ?></p>
<?php endif ?>
</div>
<?php endforeach ?>
<?php if (count($fieldOptions) > 10): ?>
<label for="<?= $checkboxId ?>">
<?= e(trans($option[0])) ?>
</label>
<?php if (isset($option[1])): ?>
<p class="help-block"><?= e(trans($option[1])) ?></p>
<?php endif ?>
</div>
</div>
<?php endforeach ?>
<?php else: ?>
<!-- Editable -->
<?php if (count($fieldOptions) > 10): ?>
<!-- Quick selection -->
<small>
<?= e(trans('backend::lang.form.select')) ?>:
<a href="javascript:;" onclick="jQuery('#<?= $field->getId('scrollable') ?> input[type=checkbox]').prop('checked', true)"><?= e(trans('backend::lang.form.select_all')) ?></a>,
<a href="javascript:;" onclick="jQuery('#<?= $field->getId('scrollable') ?> input[type=checkbox]').prop('checked', false)"><?= e(trans('backend::lang.form.select_none')) ?></a>
</small>
<!-- Scrollable Checkbox list -->
<div class="field-checkboxlist-scrollable" id="<?= $field->getId('scrollable') ?>">
<div class="control-scrollbar" data-control="scrollbar">
<?php endif ?>
<input
type="hidden"
name="<?= $field->getName() ?>"
value="0" />
<?php $index = 0; foreach ($fieldOptions as $value => $option): ?>
<?php
$index++;
$checkboxId = 'checkbox_'.$field->getId().'_'.$index;
if (is_string($option)) $option = [$option];
?>
<div class="checkbox custom-checkbox">
<input
type="checkbox"
id="<?= $checkboxId ?>"
name="<?= $field->getName() ?>[]"
value="<?= $value ?>"
<?= in_array($value, $checkedValues) ? 'checked="checked"' : '' ?>>
<label for="<?= $checkboxId ?>">
<?= e(trans($option[0])) ?>
</label>
<?php if (isset($option[1])): ?>
<p class="help-block"><?= e(trans($option[1])) ?></p>
<?php endif ?>
</div>
<?php endforeach ?>
<?php if (count($fieldOptions) > 10): ?>
</div>
</div>
<?php endif ?>
<?php endif ?>
<?php else: ?>
<!-- No options specified -->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -484,13 +484,13 @@
var popup = $form.data('oc.popup')
$(popup.$target).on('click', 'button[data-action=reload]', function(){
$(popup.$content).on('click', 'button[data-action=reload]', function(){
popup.hide()
reloadForm(form)
})
$(popup.$target).on('click', 'button[data-action=save]', function(){
$(popup.$content).on('click', 'button[data-action=save]', function(){
popup.hide()
$('input[name=templateForceSave]', $form).val(1)

View File

@ -5,7 +5,6 @@ use Lang;
use Cache;
use Config;
use Validator;
use System\Classes\SystemException;
use System\Classes\ApplicationException;
use October\Rain\Support\ValidationException;
use RecursiveDirectoryIterator;
@ -81,7 +80,7 @@ class CmsObject implements ArrayAccess
public static function loadCached($theme, $fileName)
{
if (!FileHelper::validatePath($fileName, static::getMaxAllowedPathNesting())) {
throw new SystemException(Lang::get('cms::lang.cms_object.invalid_file', ['name'=>$fileName]));
throw new ApplicationException(Lang::get('cms::lang.cms_object.invalid_file', ['name'=>$fileName]));
}
if (!strlen(File::extension($fileName))) {
@ -157,7 +156,7 @@ class CmsObject implements ArrayAccess
public static function load($theme, $fileName)
{
if (!FileHelper::validatePath($fileName, static::getMaxAllowedPathNesting())) {
throw new SystemException(Lang::get('cms::lang.cms_object.invalid_file', ['name'=>$fileName]));
throw new ApplicationException(Lang::get('cms::lang.cms_object.invalid_file', ['name'=>$fileName]));
}
if (!strlen(File::extension($fileName))) {
@ -391,7 +390,7 @@ class CmsObject implements ArrayAccess
{
$fullPath = static::getFilePath($this->theme, $this->fileName);
if (File::isFile($fullPath) && !is_dir($fullPath) && !@unlink($fullPath)) {
throw new SystemException(Lang::get('cms::lang.cms_object.error_deleting', ['name'=>$this->fileName]));
throw new ApplicationException(Lang::get('cms::lang.cms_object.error_deleting', ['name'=>$this->fileName]));
}
}

View File

@ -73,9 +73,9 @@ abstract class ComponentBase extends Extendable
protected $page;
/**
* @var array Cache of linked Component objects, used for page links.
* @var array A collection of external property names used by this component.
*/
// protected $pageLinkCache = [];
protected $externalPropertyNames = [];
/**
* Component constructor. Takes in the page or layout code section object
@ -167,6 +167,80 @@ abstract class ComponentBase extends Extendable
return $this->alias;
}
//
// External properties
//
/*
* Description on how to access external property names.
*
* # When
* pageNumber = "7"
* $this->propertyName('pageNumber'); // Returns NULL
* $this->paramName('pageNumber'); // Returns NULL
*
* # When
* pageNumber = "{{ :page }}"
*
* $this->propertyName('pageNumber'); // Returns ":page"
* $this->paramName('pageNumber'); // Returns "page"
*
* # When
* pageNumber = "{{ page }}"
*
* $this->propertyName('pageNumber'); // Returns "page"
* $this->paramName('pageNumber'); // Returns NULL
*/
/**
* Sets names used by external properties.
* @param array $names The key should be the property name,
* the value should be the external property name.
* @return void
*/
public function setExternalPropertyNames(array $names)
{
$this->externalPropertyNames = $names;
}
/**
* Sets an external property name.
* @param string $name Property name
* @param string $extName External property name
*/
public function setExternalPropertyName($name, $extName)
{
return $this->externalPropertyNames[$name] = $extName;
}
/**
* Returns the external property name when the property value is an external property reference.
* Otherwise the default value specified is returned.
* @param string $name The property name
* @param mixed $default
* @return string
*/
public function propertyName($name, $default = null)
{
return array_get($this->externalPropertyNames, $name, $default);
}
/**
* Returns the external property name when the property value is a routing parameter reference.
* Otherwise the default value specified is returned.
* @param string $name The property name
* @param mixed $default
* @return string
*/
public function paramName($name, $default = null)
{
if (($extName = $this->propertyName($name)) && substr($extName, 0, 1) == ':') {
return substr($extName, 1);
}
return $default;
}
/**
* @deprecated Returns a defined property or parameter value.
* @todo Remove this method if year >= 2015

View File

@ -26,7 +26,7 @@ class ComponentHelpers
'type' => 'string',
'validationPattern' => '^[a-zA-Z]+[0-9a-z\_]*$',
'validationMessage' => Lang::get('cms::lang.component.validation_message'),
'showExternalParameter' => false
'showExternalParam' => false
];
$result[] = $property;
@ -36,7 +36,7 @@ class ComponentHelpers
'property' => $name,
'title' => array_get($params, 'title', $name),
'type' => array_get($params, 'type', 'string'),
'showExternalParameter' => array_get($params, 'showExternalParameter', true)
'showExternalParam' => array_get($params, 'showExternalParam', true)
];
foreach ($params as $name => $value) {

View File

@ -164,9 +164,9 @@ class Controller extends BaseController
* Maintenance mode
*/
if (
MaintenanceSettings::isConfigured()
&& MaintenanceSettings::get('is_enabled', false)
&& !BackendAuth::getUser()
MaintenanceSettings::isConfigured() &&
MaintenanceSettings::get('is_enabled', false) &&
!BackendAuth::getUser()
) {
$page = Page::loadCached($this->theme, MaintenanceSettings::get('cms_page'));
}
@ -174,12 +174,16 @@ class Controller extends BaseController
/*
* Extensibility
*/
if ($event = $this->fireEvent('page.beforeDisplay', [$url, $page], true)) {
return $event;
}
if ($event = Event::fire('cms.page.beforeDisplay', [$this, $url, $page], true)) {
return $event;
if (
($event = $this->fireEvent('page.beforeDisplay', [$url, $page], true)) ||
($event = Event::fire('cms.page.beforeDisplay', [$this, $url, $page], true))
) {
if ($event instanceof Page) {
$page = $event;
}
else {
return $event;
}
}
/*
@ -217,10 +221,11 @@ class Controller extends BaseController
* The 'this' variable is reserved for default variables.
*/
$this->vars['this'] = [
'controller' => $this,
'layout' => $this->layout,
'page' => $this->page,
'layout' => $this->layout,
'theme' => $this->theme,
'param' => $this->router->getParameters(),
'controller' => $this,
'environment' => App::environment(),
];
@ -248,11 +253,10 @@ class Controller extends BaseController
/*
* Extensibility
*/
if ($event = $this->fireEvent('page.init', [$url, $page], true)) {
return $event;
}
if ($event = Event::fire('cms.page.init', [$this, $url, $page], true)) {
if (
($event = $this->fireEvent('page.init', [$url, $page], true)) ||
($event = Event::fire('cms.page.init', [$this, $url, $page], true))
) {
return $event;
}
@ -310,11 +314,10 @@ class Controller extends BaseController
/*
* Extensibility
*/
if ($event = $this->fireEvent('page.display', [$url, $page], true)) {
return $event;
}
if ($event = Event::fire('cms.page.display', [$this, $url, $page], true)) {
if (
($event = $this->fireEvent('page.display', [$url, $page], true)) ||
($event = Event::fire('cms.page.display', [$this, $url, $page], true))
) {
return $event;
}
@ -428,7 +431,7 @@ class Controller extends BaseController
$this->vars[$alias] = $this->page->components[$alias] = $componentObj;
}
$this->setComponentPropertiesFromParameters($componentObj);
$this->setComponentPropertiesFromParams($componentObj);
$componentObj->init();
$componentObj->onInit(); // Deprecated: Remove ithis line if year >= 2015
return $componentObj;
@ -500,7 +503,7 @@ class Controller extends BaseController
$responseContents['X_OCTOBER_REDIRECT'] = $result->getTargetUrl();
}
return Response::make()->setContent($responseContents);
return Response::make($responseContents, $this->statusCode);
}
catch (ValidationException $ex) {
/*
@ -510,24 +513,8 @@ class Controller extends BaseController
$responseContents['X_OCTOBER_ERROR_MESSAGE'] = $ex->getMessage();
return Response::make($responseContents, 406);
}
catch (ApplicationException $ex) {
return Response::make($ex->getMessage(), 500);
}
catch (Exception $ex) {
/*
* Display a "dumbed down" error if custom page is activated
* otherwise display a more detailed error.
*/
if (Config::get('cms.customErrorPage', false)) {
return Response::make($ex->getMessage(), 500);
}
return Response::make(sprintf(
'"%s" on line %s of %s',
$ex->getMessage(),
$ex->getLine(),
$ex->getFile()
), 500);
return Response::make(ApplicationException::getDetailedMessage($ex), 500);
}
}
@ -601,11 +588,10 @@ class Controller extends BaseController
/*
* Extensibility
*/
if ($event = $this->fireEvent('page.start', [], true)) {
return $event;
}
if ($event = Event::fire('cms.page.start', [$this], true)) {
if (
($event = $this->fireEvent('page.start', [], true)) ||
($event = Event::fire('cms.page.start', [$this], true))
) {
return $event;
}
@ -614,9 +600,9 @@ class Controller extends BaseController
*/
if ($this->layoutObj) {
CmsException::mask($this->layout, 300);
$response = (($result = $this->layoutObj->onStart())
|| ($result = $this->layout->runComponents())
|| ($result = $this->layoutObj->onBeforePageStart())) ? $result: null;
$response = (($result = $this->layoutObj->onStart()) ||
($result = $this->layout->runComponents()) ||
($result = $this->layoutObj->onBeforePageStart())) ? $result: null;
CmsException::unmask();
if ($response) {
@ -628,9 +614,9 @@ class Controller extends BaseController
* Run page functions
*/
CmsException::mask($this->page, 300);
$response = (($result = $this->pageObj->onStart())
|| ($result = $this->page->runComponents())
|| ($result = $this->pageObj->onEnd())) ? $result : null;
$response = (($result = $this->pageObj->onStart()) ||
($result = $this->page->runComponents()) ||
($result = $this->pageObj->onEnd())) ? $result : null;
CmsException::unmask();
if ($response) {
@ -649,11 +635,10 @@ class Controller extends BaseController
/*
* Extensibility
*/
if ($event = $this->fireEvent('page.end', [], true)) {
return $event;
}
if ($event = Event::fire('cms.page.end', [$this], true)) {
if (
($event = $this->fireEvent('page.end', [], true)) ||
($event = Event::fire('cms.page.end', [$this], true))
) {
return $event;
}
@ -671,11 +656,10 @@ class Controller extends BaseController
/*
* Extensibility
*/
if ($event = $this->fireEvent('page.render', [$contents], true)) {
return $event;
}
if ($event = Event::fire('cms.page.render', [$this, $contents], true)) {
if (
($event = $this->fireEvent('page.render', [$contents], true)) ||
($event = Event::fire('cms.page.render', [$this, $contents], true))
) {
return $event;
}
@ -809,7 +793,7 @@ class Controller extends BaseController
'obj' => $componentObj
]);
$this->setComponentPropertiesFromParameters($componentObj, $parameters);
$this->setComponentPropertiesFromParams($componentObj, $parameters);
$componentObj->init();
$componentObj->onInit(); // Deprecated: Remove ithis line if year >= 2015
}
@ -855,10 +839,10 @@ class Controller extends BaseController
/*
* Extensibility
*/
if ($event = $this->fireEvent('page.beforeRenderContent', [$name], true)) {
$content = $event;
}
elseif ($event = Event::fire('cms.page.beforeRenderContent', [$this, $name], true)) {
if (
($event = $this->fireEvent('page.beforeRenderContent', [$name], true)) ||
($event = Event::fire('cms.page.beforeRenderContent', [$this, $name], true))
) {
$content = $event;
}
/*
@ -873,11 +857,10 @@ class Controller extends BaseController
/*
* Extensibility
*/
if ($event = $this->fireEvent('page.renderContent', [$name, $fileContent], true)) {
return $event;
}
if ($event = Event::fire('cms.page.renderContent', [$this, $name, $fileContent], true)) {
if (
($event = $this->fireEvent('page.renderContent', [$name, $fileContent], true)) ||
($event = Event::fire('cms.page.renderContent', [$this, $name, $fileContent], true))
) {
return $event;
}
@ -1119,7 +1102,7 @@ class Controller extends BaseController
* @param array $parameters Specifies the partial parameters.
* @return Returns updated properties.
*/
protected function setComponentPropertiesFromParameters($component, $parameters = [])
protected function setComponentPropertiesFromParams($component, $parameters = [])
{
$properties = $component->getProperties();
$routerParameters = $this->router->getParameters();
@ -1143,6 +1126,7 @@ class Controller extends BaseController
}
$component->setProperty($propertyName, $newPropertyValue);
$component->setExternalPropertyName($propertyName, $paramName);
}
}
}

View File

@ -20,7 +20,7 @@ class Page extends CmsCompoundObject
protected $settingsValidationRules = [
'title' => 'required',
'url' => ['required', 'regex:/^\/[a-z0-9\/\:_\-\*\[\]\+\?\|\.]*$/i']
'url' => ['required', 'regex:/^\/[a-z0-9\/\:_\-\*\[\]\+\?\|\.\^\$]*$/i']
];
/**
@ -104,7 +104,7 @@ class Page extends CmsCompoundObject
public static function url($page, $params = [], $absolute = true)
{
/*
* Reuse existing controller or create a new one,
* Reuse existing controller or create a new one,
* assuming that the method is called not during the front-end
* request processing.
*/
@ -181,11 +181,13 @@ class Page extends CmsCompoundObject
return;
}
$page = self::loadCached($theme, $item->reference);
$pageUrl = self::url($item->reference);
$result = [];
$result['url'] = $pageUrl;
$result['isActive'] = $pageUrl == $url;
$result['mtime'] = $page ? $page->mtime : null;
}
return $result;

View File

@ -10,6 +10,7 @@ use DbDongle;
use October\Rain\Support\Yaml;
use System\Models\Parameters;
use System\Classes\SystemException;
use Cms\Models\ThemeData;
use DirectoryIterator;
/**
@ -47,10 +48,13 @@ class Theme
/**
* Loads the theme.
* @return self
*/
public function load($dirName)
public static function load($dirName)
{
$this->dirName = $dirName;
$theme = new static;
$theme->setDirName($dirName);
return $theme;
}
/**
@ -67,6 +71,15 @@ class Theme
return base_path().Config::get('cms.themesDir').'/'.$dirName;
}
/**
* Sets the theme directory name.
* @return void
*/
public function setDirName($dirName)
{
$this->dirName = $dirName;
}
/**
* Returns the theme directory name.
* @return string
@ -83,8 +96,8 @@ class Theme
*/
public static function exists($dirName)
{
$theme = new static;
$path = $theme->getPath($dirName);
$theme = static::load($dirName);
$path = $theme->getPath();
return File::isDirectory($path);
}
@ -121,7 +134,7 @@ class Theme
->pluck('value')
;
if ($dbResult !== null) {
if ($dbResult !== null && static::exists($dbResult)) {
$activeTheme = $dbResult;
}
}
@ -135,8 +148,8 @@ class Theme
throw new SystemException(Lang::get('cms::lang.theme.active.not_set'));
}
$theme = new static;
$theme->load($activeTheme);
$theme = static::load($activeTheme);
if (!File::isDirectory($theme->getPath())) {
return self::$activeThemeCache = null;
}
@ -184,8 +197,8 @@ class Theme
throw new SystemException(Lang::get('cms::lang.theme.edit.not_set'));
}
$theme = new static;
$theme->load($editTheme);
$theme = static::load($editTheme);
if (!File::isDirectory($theme->getPath())) {
return self::$editThemeCache = null;
}
@ -210,8 +223,8 @@ class Theme
continue;
}
$theme = new static;
$theme->load($fileinfo->getFilename());
$theme = static::load($fileinfo->getFilename());
$result[] = $theme;
}
@ -245,12 +258,7 @@ class Theme
*/
public function getConfigValue($name, $default = null)
{
$config = $this->getConfig();
if (isset($config[$name])) {
return $config[$name];
}
return $default;
return array_get($this->getConfig(), $name, $default);
}
/**
@ -280,4 +288,44 @@ class Theme
Cache::forget(self::ACTIVE_KEY);
Cache::forget(self::EDIT_KEY);
}
/**
* Returns true if this theme has form fields that supply customization data.
* @return bool
*/
public function hasCustomData()
{
return $this->getConfigValue('form', false);
}
/**
* Implements the getter functionality.
* @param string $name
* @return void
*/
public function __get($name)
{
if ($this->hasCustomData()) {
$theme = ThemeData::forTheme($this);
return $theme->{$name};
}
return null;
}
/**
* Determine if an attribute exists on the object.
* @param string $key
* @return void
*/
public function __isset($key)
{
if ($this->hasCustomData()) {
$theme = ThemeData::forTheme($this);
return isset($theme->{$key});
}
return false;
}
}

View File

@ -1,11 +1,16 @@
<?php namespace Cms\Controllers;
use Config;
use BackendMenu;
use Lang;
use Input;
use Config;
use Backend;
use Redirect;
use BackendMenu;
use Backend\Classes\Controller;
use System\Classes\SettingsManager;
use Cms\Models\ThemeData;
use Cms\Classes\Theme as CmsTheme;
use Exception;
/**
* Theme selector controller
@ -16,9 +21,13 @@ use Cms\Classes\Theme as CmsTheme;
*/
class Themes extends Controller
{
public $requiredPermissions = ['cms.manage_themes'];
public $implement = [
'Backend.Behaviors.FormController'
];
public $bodyClass = 'compact-container';
public $formConfig = 'config_form.yaml';
public $requiredPermissions = ['cms.manage_themes'];
/**
* Constructor.
@ -36,6 +45,7 @@ class Themes extends Controller
public function index()
{
$this->bodyClass = 'compact-container';
}
public function index_onSetActiveTheme()
@ -46,4 +56,67 @@ class Themes extends Controller
'#theme-list' => $this->makePartial('theme_list')
];
}
//
// Theme customization
//
public function update($dirName)
{
try {
$model = $this->getThemeData($dirName);
$this->asExtension('FormController')->update($model->id);
}
catch (Exception $ex) {
$this->handleError($ex);
}
}
public function update_onSave($dirName)
{
$model = $this->getThemeData($dirName);
$this->asExtension('FormController')->update_onSave($model->id);
}
public function update_onResetDefault($dirName)
{
$model = $this->getThemeData($dirName);
$model->delete();
$redirectUrl = Backend::url('cms/themes/update/'.$dirName);
return Redirect::to($redirectUrl);
}
protected function getThemeData($dirName)
{
if (!$theme = CmsTheme::load($dirName))
throw new Exception(Lang::get('Unable to find theme with name :name', $dirName));
$model = ThemeData::forTheme($theme);
return $model;
}
/**
* Add form fields defined in theme.yaml
*/
protected function formExtendFields($form)
{
$model = $form->model;
if (!$theme = CmsTheme::load($model->theme))
throw new Exception(Lang::get('Unable to find theme with name :name', $this->theme));
if ($fields = $theme->getConfigValue('form.fields')) {
$form->addFields($fields);
}
if ($fields = $theme->getConfigValue('form.tabs.fields')) {
$form->addTabFields($fields);
}
if ($fields = $theme->getConfigValue('form.secondaryTabs.fields')) {
$form->addSecondaryTabFields($fields);
}
}
}

View File

@ -6,7 +6,7 @@
<?php foreach ($themes as $index => $theme): ?>
<?php
$isLast = $index == $cnt-1;
$isActive = $activeTheme->getDirName() == $theme->getDirName();
$isActive = $activeTheme && $activeTheme->getDirName() == $theme->getDirName();
$author = $theme->getConfigValue('author');
?>
<div class="layout-row <?= $isLast ? 'last' : null ?> min-size <?= $isActive ? 'active' : null ?>">
@ -16,7 +16,7 @@
<div class="layout-cell min-height theme-description">
<h3><?= e($theme->getConfigValue('name', $theme->getDirName())) ?></h3>
<?php if (strlen($author)): ?>
<p class="author">by <a href="<?= e($theme->getConfigValue('authorUrl', '#')) ?>"><?= e($author) ?></a></p>
<p class="author">by <a href="<?= e($theme->getConfigValue('homepage', '#')) ?>"><?= e($author) ?></a></p>
<?php endif ?>
<p class="description"><?= e($theme->getConfigValue('description', 'The theme description is not provided.')) ?></p>
<div class="controls">
@ -35,9 +35,18 @@
data-request-data="theme: '<?= e($theme->getDirName()) ?>'"
data-stripe-load-indicator
class="btn btn-primary">
<i class="icon-check"></i>
<?= e(trans('cms::lang.theme.activate_button')) ?>
</button>
<?php endif ?>
<?php if ($theme->hasCustomData()): ?>
<a
href="<?= Backend::url('cms/themes/update/'.$theme->getDirName()) ?>"
class="btn btn-default">
<i class="icon-pencil"></i>
<?= e(trans('cms::lang.theme.customize_button')) ?>
</a>
<?php endif ?>
</div>
</div>
</div>

View File

@ -0,0 +1,21 @@
# ===================================
# Form Behavior Config
# ===================================
# Record name
name: Theme
# Fields are defined by extension
form: []
# Model Class name
modelClass: Cms\Models\ThemeData
# Default redirect location
defaultRedirect: cms/themes
# Update page
update:
title: Customize Theme
redirect: cms/themes
redirectClose: cms/themes

View File

@ -0,0 +1,59 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('cms/themes') ?>">Themes</a></li>
<li><?= e(trans($this->pageTitle)) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<?= Form::open(['class'=>'layout']) ?>
<div class="layout-row">
<?= $this->formRender() ?>
</div>
<div class="form-buttons">
<div class="loading-indicator-container">
<button
type="submit"
data-request="onSave"
data-request-data="redirect:0"
data-hotkey="ctrl+s, cmd+s"
data-load-indicator="Saving theme..."
class="btn btn-primary">
<u>S</u>ave
</button>
<button
type="button"
data-request="onSave"
data-request-data="close:1"
data-hotkey="ctrl+enter, cmd+enter"
data-load-indicator="Saving theme..."
class="btn btn-default">
Save and Close
</button>
<span class="btn-text">
or <a href="<?= Backend::url('cms/themes') ?>">Cancel</a>
</span>
<button
type="button"
class="btn btn-danger pull-right"
data-request="onResetDefault"
data-load-indicator="<?= e(trans('backend::lang.form.resetting')) ?>"
data-request-confirm="<?= e(trans('backend::lang.form.action_confirm')) ?>">
<?= e(trans('backend::lang.form.reset_default')) ?>
</button>
</div>
</div>
<?= Form::close() ?>
<?php else: ?>
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
<p><a href="<?= Backend::url('cms/themes') ?>" class="btn btn-default">Return to themes list</a></p>
<?php endif ?>

View File

@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class DbCmsThemeData extends Migration
{
public function up()
{
Schema::create('cms_theme_data', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('theme')->nullable()->index();
$table->mediumtext('data')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('cms_theme_data');
}
}

View File

@ -34,7 +34,7 @@ return [
'menu_label' => 'Seiten',
'no_list_records' => 'Keine Seiten gefunden',
'new' => 'Neue Seite',
'invalid_url' => 'Ungültiges URL-Format. Die URL muss mit einem Slash beginnen und darf nur Ziffern, lateinische Zeichen und die folgenden Symbole beinhalten: ._-[]:?|/+*',
'invalid_url' => 'Ungültiges URL-Format. Die URL muss mit einem Slash beginnen und darf nur Ziffern, lateinische Zeichen und die folgenden Symbole beinhalten: ._-[]:?|/+*^$',
'delete_confirm_multiple' => 'Wollen Sie die ausgewählten Seiten wirklich löschen?',
'delete_confirm_single' => 'Wollen Sie diese Seite wirklich löschen?',
'no_layout' => '-- Kein Layout --'

View File

@ -27,6 +27,7 @@ return [
'find_more_themes' => 'Find more themes on OctoberCMS Theme Marketplace.',
'activate_button' => 'Activate',
'active_button' => 'Activate',
'customize_button' => 'Customize',
],
'maintenance' => [
'settings_menu' => 'Maintenance mode',
@ -47,7 +48,7 @@ return [
'unsaved_label' => 'Unsaved page(s)',
'no_list_records' => 'No pages found',
'new' => 'New page',
'invalid_url' => 'Invalid URL format. The URL should start with the forward slash symbol and can contain digits, Latin letters and the following symbols: ._-[]:?|/+*',
'invalid_url' => 'Invalid URL format. The URL should start with the forward slash symbol and can contain digits, Latin letters and the following symbols: ._-[]:?|/+*^$',
'delete_confirm_multiple' => 'Do you really want to delete selected pages?',
'delete_confirm_single' => 'Do you really want delete this page?',
'no_layout' => '-- no layout --'

View File

@ -40,7 +40,7 @@ return [
'menu_label' => 'Páginas',
'no_list_records' => 'No se encontraron páginas',
'new' => 'Nueva página',
'invalid_url' => 'Formato de URL inválido. El URL debe comenzar con el símbolo de barra diagonal y puede contener dígitos, letras latinas y los siguientes símbolos: _-[]:?|/+*',
'invalid_url' => 'Formato de URL inválido. El URL debe comenzar con el símbolo de barra diagonal y puede contener dígitos, letras latinas y los siguientes símbolos: _-[]:?|/+*^$',
'delete_confirm_multiple' => '¿Realmente quiere eliminar las páginas seleccionadas?',
'delete_confirm_single' => '¿Realmente quieres eliminar esta página?',
'no_layout' => '-- ninguna disposición --'

View File

@ -40,7 +40,7 @@
'menu_label' => 'Páginas',
'no_list_records' => 'No se encontraron páginas',
'new' => 'Nueva página',
'invalid_url' => 'Formato de URL inválido. El URL debe comenzar con el símbolo de barra diagonal y puede contener dígitos, letras latinas y los siguientes símbolos: _-[]:?|/+*',
'invalid_url' => 'Formato de URL inválido. El URL debe comenzar con el símbolo de barra diagonal y puede contener dígitos, letras latinas y los siguientes símbolos: _-[]:?|/+*^$',
'delete_confirm_multiple' => '¿Realmente quiere eliminar las páginas seleccionadas?',
'delete_confirm_single' => '¿Realmente quieres eliminar esta página?',
'no_layout' => '-- ninguna disposición --'

View File

@ -40,7 +40,7 @@ return [
'menu_label' => 'صفخات',
'no_list_records' => 'صفحه ای یافت نشد',
'new' => 'New page',
'invalid_url' => 'قالب آدرس صحیح نمی باشد. آدرس باید با اسلش شروع شده و می تواند شامل اعداد، خروف لاتین و این کاراکتر ها باشد: ._-[]:?|/+*',
'invalid_url' => 'قالب آدرس صحیح نمی باشد. آدرس باید با اسلش شروع شده و می تواند شامل اعداد، خروف لاتین و این کاراکتر ها باشد: ._-[]:?|/+*^$',
'delete_confirm_multiple' => 'آیا از حذف صفحات انتخاب شده اطمینان دارید؟',
'delete_confirm_single' => 'آیا از حذف این صفحه اطمینان دارید؟',
'no_layout' => '-- بدون طرح بندی --'

View File

@ -40,7 +40,7 @@ return [
'menu_label' => 'Pages',
'no_list_records' => 'Aucune page trouvée',
'new' => 'Nouvelle page',
'invalid_url' => 'Format d\'URL invalide. L\'URL doit commencer par un / et peut contenit des chiffres, des lettres et les symboles suivants: ._-[]:?|/+*',
'invalid_url' => 'Format d\'URL invalide. L\'URL doit commencer par un / et peut contenit des chiffres, des lettres et les symboles suivants: ._-[]:?|/+*^$',
'delete_confirm_multiple' => 'Voulez-vous vraiment supprimer les pages sélectionnées ?',
'delete_confirm_single' => 'Voulez-vous vraiment supprimer cette page ?',
'no_layout' => '-- aucun layout --'

View File

@ -27,6 +27,7 @@ return [
'find_more_themes' => 'További témák az OctoberCMS témák piacterén.',
'activate_button' => 'Aktiválás',
'active_button' => 'Aktiválás',
'customize_button' => 'Testreszabás',
],
'maintenance' => [
'settings_menu' => 'Karbantartás mód',
@ -47,7 +48,7 @@ return [
'unsaved_label' => 'Nem mentett lap(ok)',
'no_list_records' => 'Nem találhatók lapok',
'new' => 'Új lap',
'invalid_url' => 'Érvénytelen URL-címformátum. Az URL-címnek perjellel kell kezdődnie, és számokat, latin betűket, valamint a következő számokat tartalmazhatja: ._-[]:?|/+*',
'invalid_url' => 'Érvénytelen URL-címformátum. Az URL-címnek perjellel kell kezdődnie, és számokat, latin betűket, valamint a következő számokat tartalmazhatja: ._-[]:?|/+*^$',
'delete_confirm_multiple' => 'Valóban törölni akarja a kijelölt lapokat?',
'delete_confirm_single' => 'Valóban törölni akarja ezt a lapot?',
'no_layout' => '-- nincs elrendezés --'

View File

@ -40,7 +40,7 @@ return [
'menu_label' => 'Pagine',
'no_list_records' => 'Pagine non trovate',
'new' => 'Nuova pagina',
'invalid_url' => 'Formato URL non valido. L\'URL deve iniziare con una barra e può contenere numeri, lettere e i seguenti simboli: ._-[]:?|/+*',
'invalid_url' => 'Formato URL non valido. L\'URL deve iniziare con una barra e può contenere numeri, lettere e i seguenti simboli: ._-[]:?|/+*^$',
'delete_confirm_multiple' => 'Sei sicuro di voler eliminare le pagine selezionate?',
'delete_confirm_single' => 'Sei sicuro di voler eliminare questa pagina?',
'no_layout' => '-- nessun layout --'

View File

@ -34,7 +34,7 @@ return [
'menu_label' => 'ページ',
'no_list_records' => 'ページが見つかりません',
'new' => '新ページ',
'invalid_url' => '正しくないURL形式。URLはスラッシュ(/)で始まり、数字、ラテン文字、._-[]:?|/+*で構成します。',
'invalid_url' => '正しくないURL形式。URLはスラッシュ(/)で始まり、数字、ラテン文字、._-[]:?|/+*^$で構成します。',
'delete_confirm_multiple' => '指定した全ページを本当に削除しますか?',
'delete_confirm_single' => '本当にこのページを削除しますか?',
'no_layout' => '-- レイアウト無し --'

View File

@ -40,7 +40,7 @@ return [
'menu_label' => 'Pagina\'s',
'no_list_records' => 'Geen pagina\'s gevonden',
'new' => 'Nieuwe pagina',
'invalid_url' => 'Ongeldig URL formaat. De URL moet beginnen met een schuine streep en mag enkel bestaan uit letters, cijfers en de volgende tekens: ._-[]:?|/+*',
'invalid_url' => 'Ongeldig URL formaat. De URL moet beginnen met een schuine streep en mag enkel bestaan uit letters, cijfers en de volgende tekens: ._-[]:?|/+*^$',
'delete_confirm_multiple' => 'Weet je zeker dat je de geselecteerde pagina\'s wilt verwijderen?',
'delete_confirm_single' => 'Weet je zeker dat je deze pagina wilt verwijderen?',
'no_layout' => '-- geen layout --'

View File

@ -40,7 +40,7 @@ return [
'menu_label' => 'Páginas',
'no_list_records' => 'Nenhuma página foi encontradas',
'new' => 'Nova página',
'invalid_url' => 'Formato de URL inválido. O URL deve começar com o símbolo de barra e pode conter dígitos, letras latinas e os seguintes símbolos: _-[]:?|/+*',
'invalid_url' => 'Formato de URL inválido. O URL deve começar com o símbolo de barra e pode conter dígitos, letras latinas e os seguintes símbolos: _-[]:?|/+*^$',
'delete_confirm_multiple' => 'Você realmente quer excluir as páginas selecionadas?',
'delete_confirm_single' => 'Você realmente quer excluir esta página?',
'no_layout' => '-- sem layout --'

View File

@ -40,7 +40,7 @@ return [
'menu_label' => 'Pagini',
'no_list_records' => 'Nu au fost gasite pagini',
'new' => 'Pagina noua',
'invalid_url' => 'Format URL invalid. URL-ul ar trebui sa inceapa cu un slash ( / ) si poate sa contina cifre, caractere latine si urmatoarele simboluri: ._-[]:?|/+*',
'invalid_url' => 'Format URL invalid. URL-ul ar trebui sa inceapa cu un slash ( / ) si poate sa contina cifre, caractere latine si urmatoarele simboluri: ._-[]:?|/+*^$',
'delete_confirm_multiple' => 'Vreti sa stergeti paginile selectate?',
'delete_confirm_single' => 'Vreti sa stergeti aceasta pagina?',
'no_layout' => '-- fara macheta --'

Some files were not shown because too many files have changed in this diff Show More