diff --git a/css/admin.css b/css/admin.css new file mode 100644 index 000000000..43d74946e --- /dev/null +++ b/css/admin.css @@ -0,0 +1,1697 @@ +@font-face { + font-family: 'siteorigin-panels'; + src: url('icons/siteorigin-panels.eot?-yv2c11'); + src: url('icons/siteorigin-panels.eot?#iefix-yv2c11') format('embedded-opentype'), url('icons/siteorigin-panels.woff?-yv2c11') format('woff'), url('icons/siteorigin-panels.ttf?-yv2c11') format('truetype'), url('icons/siteorigin-panels.svg?-yv2c11#siteorigin-panels') format('svg'); + font-weight: normal; + font-style: normal; +} +/* This is for the metabox */ +#so-panels-panels.attached-to-editor { + margin-top: 20px; +} +#so-panels-panels.attached-to-editor h3.hndle { + display: none; +} +#so-panels-panels.attached-to-editor .inside { + margin: 0 !important; + padding: 0 !important; +} +#so-panels-panels.attached-to-editor .so-toolbar .so-switch-to-standard { + display: block; +} +/* Everything for the main builder interface */ +.siteorigin-panels-builder { + position: relative; + /* These are generic iconic buttons used in the page builder interface */ + /* Page Builder icons */ +} +.siteorigin-panels-builder .so-builder-container { + /* Top padding for the toolbar */ + padding-top: 38px; + position: relative; +} +.siteorigin-panels-builder .so-tool-button { + padding: 6px 7px; + font-size: 11px; + text-decoration: none; + line-height: 0.9em; + float: left; + margin-right: 2px; + display: block; + visibility: visible; + position: relative; + border: 1px solid #C0C0C0; + background: #f0f0f0; + background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #f0f0f0), color-stop(1, #f9f9f9)); + background: -ms-linear-gradient(bottom, #f0f0f0, #f9f9f9); + background: -moz-linear-gradient(center bottom, #f0f0f0 0%, #f9f9f9 100%); + background: -o-linear-gradient(#f9f9f9, #f0f0f0); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f9f9f9', endColorstr='#f0f0f0', GradientType=0); + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.04); + -moz-box-shadow: 0 1px 1px rgba(0,0,0,0.04); + box-shadow: 0 1px 1px rgba(0,0,0,0.04); + outline: none; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} +.siteorigin-panels-builder .so-tool-button span { + display: inline-block; + color: #666666; + text-shadow: 0 1px 0 #FFFFFF; + min-width: 10px; + text-align: center; +} +.siteorigin-panels-builder .so-tool-button:hover { + background: #FFFFFF; +} +.siteorigin-panels-builder .so-tool-button:hover span { + color: #444444; +} +.siteorigin-panels-builder .so-builder-toolbar { + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + border-bottom: 1px solid #D0D0D0; + background: #F5F5F5; + line-height: 1em; + position: absolute; + z-index: 101; + white-space: nowrap; + overflow-x: hidden; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); + top: 0; + left: 0; + width: 100%; + padding: 6px 9px; + margin-top: 0 !important; + zoom: 1; + /* The toolbar buttons */ +} +.siteorigin-panels-builder .so-builder-toolbar:before { + content: ''; + display: block; +} +.siteorigin-panels-builder .so-builder-toolbar:after { + content: ''; + display: table; + clear: both; +} +.siteorigin-panels-builder .so-builder-toolbar > .so-tool-button { + display: inline-block; + color: #666666; + padding-right: 8px; +} +.siteorigin-panels-builder .so-builder-toolbar > .so-tool-button .so-panels-icon { + float: left; + margin: 0 5px 0 0; +} +.siteorigin-panels-builder .so-builder-toolbar > .so-tool-button:hover { + color: #444444; +} +.siteorigin-panels-builder .so-builder-toolbar .so-switch-to-standard { + position: absolute; + top: 5px; + right: 10px; + display: none; + text-decoration: none; + color: #666666; + padding: 5px 6px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + border: 1px solid transparent; + margin-top: 2px; + font-size: 11px; +} +.siteorigin-panels-builder .so-builder-toolbar .so-switch-to-standard:hover { + background: #fafafa; + border: 1px solid #999999; + color: #444444; +} +@media screen and (max-width: 600px) { + .siteorigin-panels-builder .so-builder-toolbar { + padding: 10px; + } + .siteorigin-panels-builder .so-builder-toolbar > .so-tool-button { + padding-right: 2px; + } + .siteorigin-panels-builder .so-builder-toolbar > .so-tool-button .so-panels-icon { + font-size: 20px; + } + .siteorigin-panels-builder .so-builder-toolbar > .so-tool-button span.so-button-text { + display: none; + } +} +.siteorigin-panels-builder .so-rows-container { + padding: 20px 15px 0 15px; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar { + zoom: 1; + margin-bottom: 4px; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar:before { + content: ''; + display: block; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar:after { + content: ''; + display: table; + clear: both; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar .so-tool-button { + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + height: 22px; + padding: 5px; + font-size: 10px; + float: right; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar .so-tool-button.so-row-move { + cursor: move; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar .so-dropdown-wrapper { + position: relative; + float: right; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar .so-dropdown-wrapper .so-dropdown-links-wrapper { + display: none; + z-index: 11; + position: absolute; + right: -10px; + padding: 6px 0 0 0; + top: 22px; + width: 125px; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar .so-dropdown-wrapper .so-dropdown-links-wrapper ul { + margin: 0; + border: 1px solid #C0C0C0; + background: #F9F9F9; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + padding: 4px 0; + -webkit-box-shadow: 0 2px 2px rgba(0,0,0,0.1); + -moz-box-shadow: 0 2px 2px rgba(0,0,0,0.1); + box-shadow: 0 2px 2px rgba(0,0,0,0.1); +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar .so-dropdown-wrapper .so-dropdown-links-wrapper ul li { + margin: 0; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar .so-dropdown-wrapper .so-dropdown-links-wrapper ul li:first-child { + border-top-width: 1px; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar .so-dropdown-wrapper .so-dropdown-links-wrapper ul li a { + display: block; + padding: 2px 8px; + text-decoration: none; + color: #666; + font-size: 11px; + font-weight: bold; + outline: 0 !important; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + /* Specific drop down hovers */ +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar .so-dropdown-wrapper .so-dropdown-links-wrapper ul li a:hover { + background: #F0F0F0; + color: #444; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar .so-dropdown-wrapper .so-dropdown-links-wrapper ul li a .dashicons { + font-size: 16px; + margin: 0; + float: right; + line-height: 16px; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar .so-dropdown-wrapper .so-dropdown-links-wrapper ul li a.so-row-delete { + color: #a00; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar .so-dropdown-wrapper .so-dropdown-links-wrapper ul li a.so-row-delete:hover { + color: #FFF; + background: #a00; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar .so-dropdown-wrapper .so-dropdown-links-wrapper ul .so-pointer { + width: 12px; + height: 6px; + position: absolute; + z-index: 12; + background: url("images/dropdown-pointer.png"); + background-size: 12px 6px; + top: 1px; + right: 18px; +} +.siteorigin-panels-builder .so-rows-container .so-row-toolbar .so-dropdown-wrapper:hover .so-dropdown-links-wrapper { + display: block; +} +.siteorigin-panels-builder .so-rows-container .ui-sortable-placeholder { + visibility: visible !important; + background: #F7F7F7; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.siteorigin-panels-builder .so-rows-container .so-row-container { + margin-bottom: 20px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells { + zoom: 1; + margin: 0 -5px; + position: relative; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells:before { + content: ''; + display: block; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells:after { + content: ''; + display: table; + clear: both; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .ui-resizable-handle.ui-resizable-w { + width: 10px; + left: -11px; + cursor: col-resize; + background: rgba(0, 150, 211, 0); + background: rgba(0, 150, 211, 0.25); + -webkit-transition: background 0.25s ease-in-out; + -moz-transition: background 0.25s ease-in-out; + -o-transition: background 0.25s ease-in-out; + transition: background 0.25s ease-in-out; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .ui-resizable-handle.ui-resizable-w:hover { + background: rgba(0, 150, 211, 0.1); +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell { + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + float: left; + position: relative; + padding: 0 5px; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell.so-first { + margin-left: 0; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell.so-last { + margin-right: 0; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .cell-wrapper { + background: #e4eff4; + border: 1px solid #bcccd2; + padding: 10px 10px 4px 10px; + /* 6px bottom to remove bottom margin from panels */ + height: 100%; + min-height: 70px; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell.cell-selected .cell-wrapper { + background: #cae7f4 url("images/cell-selected.png") repeat; + border-color: #9abcc7; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.2); +} +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + .siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell.cell-selected .cell-wrapper { + background-size: 3px 3px; + } +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell, +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .cell-wrapper { + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget { + cursor: move; + margin-bottom: 5px; + background: #f9f9fb; + border: 1px solid #9bafb5; + max-height: 49px; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.075); + -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.075); + box-shadow: 0 1px 2px rgba(0,0,0,0.075); +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget:hover { + border: 1px solid #93a7ad; + background: #feffff; + -webkit-box-shadow: 0 2px 2px rgba(0,0,0,0.075); + -moz-box-shadow: 0 2px 2px rgba(0,0,0,0.075); + box-shadow: 0 2px 2px rgba(0,0,0,0.075); +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget .so-widget-wrapper { + padding: 7px 9px; + overflow: hidden; + position: relative; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget h4 { + display: block; + cursor: pointer; + margin: 0 15px 3px 0; + font-weight: 600; + line-height: 1.25em; + color: #474747; + text-shadow: 0 1px 0 #FFF; + white-space: nowrap; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget h4 span { + font-weight: normal; + display: inline-block; + color: #999; + text-shadow: 0 1px 0 #FFF; + margin-left: 12px; + margin-right: 5px; + font-style: italic; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget .title .actions { + font-size: 12px; + position: absolute; + top: 5px; + right: 7px; + cursor: pointer; + padding: 2px 2px 2px 15px; + z-index: 10; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget .title .actions:hover { + background: #feffff; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget .title .actions:hover a { + opacity: 1; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget .title .actions a { + display: none; + margin-right: 3px; + text-decoration: none; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget .title .actions a.widget-delete { + color: #FF0000; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget .title .actions a.widget-delete:hover { + color: white; + background: #FF0000; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget:hover .title a { + display: inline-block; + opacity: 0.5; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget.panel-being-dragged .title .actions { + display: none; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget small { + display: block; + height: 16px; + overflow: hidden; + color: #777; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget .form { + display: none; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget.widget-being-dragged { + opacity: 0.9; + pointer-events: none; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .widgets-container .so-widget-sortable-highlight { + border: 1px solid; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + height: 49px; + background: #ddebef; + border-color: #bcccd2; + margin-bottom: 5px; + position: relative; + -webkit-box-shadow: inset 2px 2px 2px rgba(0,0,0,0.01); + -moz-box-shadow: inset 2px 2px 2px rgba(0,0,0,0.01); + box-shadow: inset 2px 2px 2px rgba(0,0,0,0.01); +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .resize-handle { + z-index: 100; + position: absolute; + top: 0; + width: 10px; + left: -5px; + cursor: col-resize; + background: #e5f4fa; + height: 100%; + -webkit-transition: background 0.25s ease-in-out; + -moz-transition: background 0.25s ease-in-out; + -o-transition: background 0.25s ease-in-out; + transition: background 0.25s ease-in-out; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell .resize-handle:hover { + background: #bfe4f3; +} +.siteorigin-panels-builder .so-rows-container .so-row-container .so-cells .cell:first-child .resize-handle { + display: none; +} +.siteorigin-panels-builder .so-panels-icon { + font-family: 'siteorigin-panels'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.siteorigin-panels-builder .so-panels-icon.so-panels-icon-plus:before { + content: "\f067"; +} +.siteorigin-panels-builder .so-panels-icon.so-panels-icon-eye:before { + content: "\f06e"; +} +.siteorigin-panels-builder .so-panels-icon.so-panels-icon-arrows-v:before { + content: "\f07d"; +} +.siteorigin-panels-builder .so-panels-icon.so-panels-icon-wrench:before { + content: "\f0ad"; +} +.siteorigin-panels-builder .so-panels-icon.so-panels-icon-columns:before { + content: "\f0db"; +} +.siteorigin-panels-builder .so-panels-icon.so-panels-icon-rotate-left:before { + content: "\f0e2"; +} +.siteorigin-panels-builder .so-panels-icon.so-panels-icon-puzzle-piece:before { + content: "\f12e"; +} +.siteorigin-panels-builder .so-panels-icon.so-panels-icon-cubes:before { + content: "\f1b3"; +} +.siteorigin-panels-builder .so-panels-welcome-message { + text-align: center; + padding: 0px 15px 20px 15px; + color: #555; + line-height: 1.8em; +} +.siteorigin-panels-builder .so-panels-welcome-message .so-message-wrapper { + padding: 15px 10px; + background: #F8F8F8; + border: 1px solid #E0E0E0; +} +.siteorigin-panels-builder .so-panels-welcome-message .so-tool-button { + font-size: inherit; + display: inline-block; + float: none; + color: #666; + padding: 5px 10px; + margin: 0 3px; +} +.siteorigin-panels-builder .so-panels-welcome-message .so-tool-button .so-panels-icon { + color: #777; + font-size: 0.8em; +} +/* This is to display a draggable widget */ +.so-widget.ui-sortable-helper.widget-being-dragged { + cursor: move; + margin-bottom: 5px; + background: #f9f9fb; + border: 1px solid #9bafb5; + max-height: 49px; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.075); + -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.075); + box-shadow: 0 1px 2px rgba(0,0,0,0.075); +} +.so-widget.ui-sortable-helper.widget-being-dragged:hover { + border: 1px solid #93a7ad; + background: #feffff; + -webkit-box-shadow: 0 2px 2px rgba(0,0,0,0.075); + -moz-box-shadow: 0 2px 2px rgba(0,0,0,0.075); + box-shadow: 0 2px 2px rgba(0,0,0,0.075); +} +.so-widget.ui-sortable-helper.widget-being-dragged .so-widget-wrapper { + padding: 7px 9px; + overflow: hidden; + position: relative; +} +.so-widget.ui-sortable-helper.widget-being-dragged h4 { + display: block; + cursor: pointer; + margin: 0 15px 3px 0; + font-weight: 600; + line-height: 1.25em; + color: #474747; + text-shadow: 0 1px 0 #FFF; + white-space: nowrap; +} +.so-widget.ui-sortable-helper.widget-being-dragged h4 span { + font-weight: normal; + display: inline-block; + color: #999; + text-shadow: 0 1px 0 #FFF; + margin-left: 12px; + margin-right: 5px; + font-style: italic; +} +.so-widget.ui-sortable-helper.widget-being-dragged .title .actions { + font-size: 12px; + position: absolute; + top: 5px; + right: 7px; + cursor: pointer; + padding: 2px 2px 2px 15px; + z-index: 10; +} +.so-widget.ui-sortable-helper.widget-being-dragged .title .actions:hover { + background: #feffff; +} +.so-widget.ui-sortable-helper.widget-being-dragged .title .actions:hover a { + opacity: 1; +} +.so-widget.ui-sortable-helper.widget-being-dragged .title .actions a { + display: none; + margin-right: 3px; + text-decoration: none; +} +.so-widget.ui-sortable-helper.widget-being-dragged .title .actions a.widget-delete { + color: #FF0000; +} +.so-widget.ui-sortable-helper.widget-being-dragged .title .actions a.widget-delete:hover { + color: white; + background: #FF0000; +} +.so-widget.ui-sortable-helper.widget-being-dragged:hover .title a { + display: inline-block; + opacity: 0.5; +} +.so-widget.ui-sortable-helper.widget-being-dragged.panel-being-dragged .title .actions { + display: none; +} +.so-widget.ui-sortable-helper.widget-being-dragged small { + display: block; + height: 16px; + overflow: hidden; + color: #777; +} +.so-widget.ui-sortable-helper.widget-being-dragged .form { + display: none; +} +.so-widget.ui-sortable-helper.widget-being-dragged.widget-being-dragged { + opacity: 0.9; + pointer-events: none; +} +/* Handles displaying a builder in the WordPress widget interface */ +.widgets-holder-wrap .widget-inside .siteorigin-panels-builder .so-builder-container { + padding-top: 0; +} +.widgets-holder-wrap .widget-inside .siteorigin-panels-builder .so-rows-container { + padding: 10px 0 0 0; +} +.widgets-holder-wrap .widget-inside .siteorigin-panels-builder .so-builder-toolbar { + padding-left: 15px; + padding-right: 15px; + margin: 0 -15px; +} +.so-panels-dialog { + /* The add widget dialog */ + /* The row edit dialog */ + /* For prebuilt layouts */ + /* Everything we need for the style fields */ + /* Special case of the builder interface being inside a dialog */ +} +.so-panels-dialog .so-overlay, +.so-panels-dialog .so-content, +.so-panels-dialog .so-title-bar, +.so-panels-dialog .so-toolbar, +.so-panels-dialog .so-left-sidebar, +.so-panels-dialog .so-right-sidebar { + z-index: 100000; + position: fixed; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding: 15px; +} +.so-panels-dialog .so-content, +.so-panels-dialog .so-left-sidebar, +.so-panels-dialog .so-right-sidebar { + overflow-y: auto; +} +.so-panels-dialog .so-overlay { + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); +} +.so-panels-dialog .so-content { + top: 80px; + left: 30px; + right: 30px; + bottom: 88px; + background-color: #fdfdfd; + overflow-x: hidden; + -webkit-box-shadow: inset 0 2px 2px rgba(0,0,0,0.03); + -moz-box-shadow: inset 0 2px 2px rgba(0,0,0,0.03); + box-shadow: inset 0 2px 2px rgba(0,0,0,0.03); +} +.so-panels-dialog .so-content > *:first-child { + margin-top: 0; +} +.so-panels-dialog .so-content > *:last-child { + margin-bottom: 0; +} +.so-panels-dialog .so-content .so-content-tabs > * { + display: none; +} +.so-panels-dialog .so-title-bar { + left: 30px; + right: 30px; + top: 30px; + height: 50px; + background-color: #fafafa; + border-bottom: 1px solid #d8d8d8; + /* These are the action buttons in the title bar */ +} +.so-panels-dialog .so-title-bar h3.so-title { + margin: 0 !important; + padding: 0 !important; +} +.so-panels-dialog .so-title-bar h3.so-parent-link { + cursor: pointer; + position: relative; + float: left; + margin: 0 15px 0 0 !important; + padding: 0 27px 0 0 !important; +} +.so-panels-dialog .so-title-bar h3.so-parent-link .so-separator { + position: absolute; + top: -15px; + right: 0; + width: 12px; + height: 50px; + display: block; + background: url(images/dialog-separator.png) no-repeat; +} +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + .so-panels-dialog .so-title-bar h3.so-parent-link .so-separator { + background: url(images/dialog-separator@2x.png) no-repeat; + background-size: cover; + } +} +.so-panels-dialog .so-title-bar a { + position: absolute; + box-sizing: border-box; + width: 50px; + height: 50px; + display: block; + top: 0; + right: 0; + -webkit-transition: all 0.2s ease; + -moz-transition: all 0.2s ease; + -o-transition: all 0.2s ease; + transition: all 0.2s ease; + background: #fafafa; + border-left: 1px solid #d8d8d8; + border-bottom: 1px solid #d8d8d8; + /* Disabled nav */ +} +.so-panels-dialog .so-title-bar a:hover { + background: #e9e9e9; +} +.so-panels-dialog .so-title-bar a:hover .so-dialog-icon { + color: #333333; +} +.so-panels-dialog .so-title-bar a .so-dialog-icon { + position: absolute; + top: 50%; + left: 50%; + text-decoration: none; + width: 20px; + height: 20px; + margin-left: -10px; + margin-top: -10px; + color: #666666; + text-align: center; +} +.so-panels-dialog .so-title-bar a .so-dialog-icon:before { + font: 400 20px/1em dashicons; + top: 7px; + left: 13px; +} +.so-panels-dialog .so-title-bar a.so-close { + right: 0; +} +.so-panels-dialog .so-title-bar a.so-close .so-dialog-icon:before { + content: "\f335"; +} +.so-panels-dialog .so-title-bar a.so-next { + right: 50px; +} +.so-panels-dialog .so-title-bar a.so-next .so-dialog-icon:before { + content: '\f345'; +} +.so-panels-dialog .so-title-bar a.so-previous { + right: 100px; +} +.so-panels-dialog .so-title-bar a.so-previous .so-dialog-icon:before { + content: '\f341'; +} +.so-panels-dialog .so-title-bar a.so-nav.so-disabled { + cursor: default; + pointer-events: none; +} +.so-panels-dialog .so-title-bar a.so-nav.so-disabled .so-dialog-icon { + color: #dddddd; +} +.so-panels-dialog .so-toolbar { + left: 30px; + right: 30px; + bottom: 30px; + height: 58px; + background-color: #fafafa; + border-top: 1px solid #d8d8d8; +} +.so-panels-dialog .so-toolbar .so-status { + float: left; + padding-top: 6px; + padding-bottom: 6px; + font-style: italic; + color: #999999; + line-height: 1em; +} +.so-panels-dialog .so-toolbar .so-status.so-panels-loading { + padding-left: 26px; + background-position: left center; +} +.so-panels-dialog .so-toolbar .so-buttons { + float: right; +} +.so-panels-dialog .so-toolbar .so-buttons .action-buttons { + position: absolute; + left: 15px; + top: 50%; + margin-top: -0.65em; +} +.so-panels-dialog .so-toolbar .so-buttons .action-buttons a { + display: inline; + padding: 0.2em 0.5em; + line-height: 1em; + margin-right: 0.5em; + text-decoration: none; +} +.so-panels-dialog .so-toolbar .so-buttons .action-buttons .so-delete { + color: #a00; +} +.so-panels-dialog .so-toolbar .so-buttons .action-buttons .so-delete:hover { + background: #a00; + color: #FFFFFF; +} +.so-panels-dialog .so-toolbar .so-buttons .action-buttons .so-duplicate:hover { + text-decoration: underline; +} +.so-panels-dialog .so-left-sidebar, +.so-panels-dialog .so-right-sidebar { + background-color: #f3f3f3; +} +.so-panels-dialog .so-left-sidebar { + display: none; + top: 30px; + left: 30px; + bottom: 30px; + width: 290px; + border-right: 1px solid #d8d8d8; +} +.so-panels-dialog .so-left-sidebar h4 { + margin: 0 0 20px 0; + font-size: 18px; +} +.so-panels-dialog .so-left-sidebar .so-sidebar-search { + width: 100%; + padding: 6px; + margin-bottom: 20px; +} +.so-panels-dialog .so-left-sidebar .so-sidebar-tabs { + list-style: none; + margin: 0 -15px; +} +.so-panels-dialog .so-left-sidebar .so-sidebar-tabs li { + margin-bottom: 0; +} +.so-panels-dialog .so-left-sidebar .so-sidebar-tabs li a { + padding: 7px 16px; + display: block; + font-size: 14px; + text-decoration: none; + box-shadow: none !important; +} +.so-panels-dialog .so-left-sidebar .so-sidebar-tabs li a:hover { + background: #FFFFFF; +} +.so-panels-dialog .so-left-sidebar .so-sidebar-tabs li.tab-active a { + color: #555; + font-weight: bold; + background: #FFFFFF; +} +.so-panels-dialog .so-left-sidebar .so-sidebar-tabs li.tab-active a:hover { + background: #FFFFFF; +} +.so-panels-dialog .so-right-sidebar { + display: none; + top: 80px; + right: 30px; + bottom: 88px; + width: 290px; + border-left: 1px solid #d8d8d8; +} +.so-panels-dialog .so-right-sidebar h3 { + color: #333; +} +.so-panels-dialog .so-right-sidebar h3:first-child { + margin-top: 0; +} +.so-panels-dialog .so-sidebar .form-field { + margin-bottom: 20px; +} +.so-panels-dialog .so-sidebar .form-field label { + font-weight: 500; + font-size: 15px; + display: block; + margin-bottom: 10px; +} +.so-panels-dialog.so-panels-dialog-has-left-sidebar .so-content, +.so-panels-dialog.so-panels-dialog-has-left-sidebar .so-toolbar, +.so-panels-dialog.so-panels-dialog-has-left-sidebar .so-title-bar { + left: 320px; +} +.so-panels-dialog.so-panels-dialog-has-left-sidebar .so-content { + -webkit-box-shadow: inset 2px 2px 2px rgba(0,0,0,0.03); + -moz-box-shadow: inset 2px 2px 2px rgba(0,0,0,0.03); + box-shadow: inset 2px 2px 2px rgba(0,0,0,0.03); +} +.so-panels-dialog.so-panels-dialog-has-left-sidebar .so-left-sidebar { + display: block; +} +.so-panels-dialog.so-panels-dialog-has-right-sidebar .so-content { + right: 320px; +} +.so-panels-dialog.so-panels-dialog-has-right-sidebar .so-right-sidebar { + display: block; +} +.so-panels-dialog.so-panels-dialog-edit-widget .so-left-sidebar .so-widgets .so-widget { + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + border: 1px solid #cccccc; + cursor: pointer; + padding: 10px; + background: #f9f9f9; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.075), inset 0 1px 0 #FFFFFF; + -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.075), inset 0 1px 0 #FFFFFF; + box-shadow: 0 1px 2px rgba(0,0,0,0.075), inset 0 1px 0 #FFFFFF; + margin-bottom: 15px; +} +.so-panels-dialog.so-panels-dialog-edit-widget .so-left-sidebar .so-widgets .so-widget:hover { + border: 1px solid #BBBBBB; + background: #FFFFFF; +} +.so-panels-dialog.so-panels-dialog-edit-widget .so-left-sidebar .so-widgets .so-widget.so-current { + border-color: #0074a2; + background: #2ea2cc; + cursor: auto; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.2); + -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.2); + box-shadow: 0 1px 2px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.2); +} +.so-panels-dialog.so-panels-dialog-edit-widget .so-left-sidebar .so-widgets .so-widget.so-current h3 { + color: #FFFFFF; +} +.so-panels-dialog.so-panels-dialog-edit-widget .so-left-sidebar .so-widgets .so-widget.so-current small { + color: #eeeeee; +} +.so-panels-dialog.so-panels-dialog-edit-widget .so-left-sidebar .so-widgets .so-widget.so-current:hover { + border-color: #0074a2; + background: #2ea2cc; +} +.so-panels-dialog.so-panels-dialog-edit-widget .so-left-sidebar .so-widgets .so-widget:last-child { + margin-bottom: 0; +} +.so-panels-dialog.so-panels-dialog-edit-widget .so-left-sidebar .so-widgets .so-widget h3 { + margin: 0 0 7px 0; + padding: 0; + height: 1.2em; + color: #222222; + font-size: 14px; +} +.so-panels-dialog.so-panels-dialog-edit-widget .so-left-sidebar .so-widgets .so-widget small { + font-size: 11px; + line-height: 1.25em; + display: block; + overflow: hidden; + color: #888888; +} +.so-panels-dialog.so-panels-dialog-add-widget .widget-type-list { + zoom: 1; + margin: 0 -5px -10px -5px; + min-height: 10px; +} +.so-panels-dialog.so-panels-dialog-add-widget .widget-type-list:before { + content: ''; + display: block; +} +.so-panels-dialog.so-panels-dialog-add-widget .widget-type-list:after { + content: ''; + display: table; + clear: both; +} +.so-panels-dialog.so-panels-dialog-add-widget .widget-type-list .widget-type { + -ms-user-select: none; + /* IE 10+ */ + -moz-user-select: -moz-none; + -khtml-user-select: none; + -webkit-user-select: none; + user-select: none; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + width: 25%; + padding: 0 5px; + margin-bottom: 10px; + float: left; +} +@media (max-width: 1280px) { + .so-panels-dialog.so-panels-dialog-add-widget .widget-type-list .widget-type { + width: 33.333%; + } +} +@media (max-width: 960px) { + .so-panels-dialog.so-panels-dialog-add-widget .widget-type-list .widget-type { + width: 50%; + } +} +.so-panels-dialog.so-panels-dialog-add-widget .widget-type-list .widget-type h3 { + margin: 0 0 7px 0; + padding: 0; + height: 1.2em; + color: #222222; + font-size: 14px; +} +.so-panels-dialog.so-panels-dialog-add-widget .widget-type-list .widget-type small { + font-size: 11px; + height: 2.5em; + line-height: 1.25em; + display: block; + overflow: hidden; + color: #888888; +} +.so-panels-dialog.so-panels-dialog-add-widget .widget-type-list .widget-type .widget-icon { + font-size: 20px; + width: 20px; + height: 20px; + color: #666; + float: left; + margin: -1px 0.5em 0 0; +} +.so-panels-dialog.so-panels-dialog-add-widget .widget-type-list .widget-type-wrapper { + border: 1px solid #cccccc; + cursor: pointer; + padding: 10px; + background: #F8F8F8; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.075); + -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.075); + box-shadow: 0 1px 2px rgba(0,0,0,0.075); +} +.so-panels-dialog.so-panels-dialog-add-widget .widget-type-list .widget-type-wrapper:hover { + border: 1px solid #BBBBBB; + background: #FFFFFF; + -webkit-box-shadow: 0 2px 2px rgba(0,0,0,0.075); + -moz-box-shadow: 0 2px 2px rgba(0,0,0,0.075); + box-shadow: 0 2px 2px rgba(0,0,0,0.075); +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-set-form { + zoom: 1; + padding: 8px; + border: 1px solid #ccc; + margin-bottom: 20px; + background: #F3F3F3; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-set-form:before { + content: ''; + display: block; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-set-form:after { + content: ''; + display: table; + clear: both; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-set-form input, +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-set-form select, +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-set-form button, +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-set-form strong, +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-set-form span { + display: block; + float: left; + margin-right: 5px; + outline: none; + box-shadow: none; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-set-form strong, +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-set-form span { + margin-top: 5px; + margin-right: 0.75em; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-set-form span { + margin-left: 0.75em; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-set-form label { + display: inline; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview { + margin: 0 -6px; + height: 360px; + position: relative; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell, +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell-in, +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell-weight { + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell { + float: left; + position: relative; + padding: 0 6px; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell .preview-cell-in { + border: 1px solid #bcccd2; + min-height: 360px; + background: #e4eff4; + position: relative; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell .preview-cell-in .preview-cell-weight, +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell .preview-cell-in .preview-cell-weight-input { + position: absolute; + font-size: 17px; + font-weight: bold; + top: 50%; + left: 50%; + width: 80px; + text-align: center; + color: #5e6d72; + margin: -0.95em 0 0 -40px; + padding: 10px 0; + border: 1px solid transparent; + line-height: 1.4em !important; + overflow: hidden; + cursor: pointer; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell .preview-cell-in .preview-cell-weight:after, +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell .preview-cell-in .preview-cell-weight-input:after { + content: '%'; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell .preview-cell-in .preview-cell-weight:hover, +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell .preview-cell-in .preview-cell-weight-input:hover { + background: #F6F6F6; + border: 1px solid #D0D0D0; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell .preview-cell-in .preview-cell-weight-input { + background: #F6F6F6; + border: 1px solid #D0D0D0; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell .resize-handle { + z-index: 100; + position: absolute; + top: 0; + width: 12px; + left: -6px; + cursor: col-resize; + background: #e5f4fb; + height: 360px; + -webkit-transition: background 0.15s ease-in-out; + -moz-transition: background 0.15s ease-in-out; + -o-transition: background 0.15s ease-in-out; + transition: background 0.15s ease-in-out; +} +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell .resize-handle:hover, +.so-panels-dialog.so-panels-dialog-row-edit .so-content .row-preview .preview-cell .resize-handle.ui-draggable-dragging { + background: #b7e0f1; +} +.so-panels-dialog.so-panels-dialog-history .so-left-sidebar { + padding: 0; +} +.so-panels-dialog.so-panels-dialog-history .history-entries .history-entry { + padding: 10px; + background: #F8F8F8; + cursor: pointer; + border-bottom: 1px solid #ccc; +} +.so-panels-dialog.so-panels-dialog-history .history-entries .history-entry h3 { + margin: 0 0 0.6em 0; + font-size: 12px; + font-weight: bold; + color: #444444; + line-height: 1em; +} +.so-panels-dialog.so-panels-dialog-history .history-entries .history-entry .timesince { + color: #999999; + font-size: 11px; + line-height: 1em; +} +.so-panels-dialog.so-panels-dialog-history .history-entries .history-entry:hover { + background: #F0F0F0; +} +.so-panels-dialog.so-panels-dialog-history .history-entries .history-entry.so-selected { + background: #EEEEEE; +} +.so-panels-dialog.so-panels-dialog-history .history-entries .history-entry .count { + color: #999999; +} +.so-panels-dialog.so-panels-dialog-history .so-content { + padding: 0; + overflow-y: hidden; +} +.so-panels-dialog.so-panels-dialog-history .so-content form.history-form { + display: none; +} +.so-panels-dialog.so-panels-dialog-history .so-content iframe.siteorigin-panels-history-iframe { + width: 100%; + height: 100%; +} +.so-panels-dialog.so-panels-dialog-prebuilt-layouts .so-content { + padding-left: 10px; + padding-right: 10px; +} +.so-panels-dialog.so-panels-dialog-prebuilt-layouts .so-content .layout { + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + float: left; + width: 33.333%; + padding: 0 5px 10px 5px; +} +.so-panels-dialog.so-panels-dialog-prebuilt-layouts .so-content .layout .layout-inside { + padding: 20px; + cursor: pointer; + border: 1px solid #cccccc; + background: #F8F8F8; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.075); + -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.075); + box-shadow: 0 1px 2px rgba(0,0,0,0.075); +} +.so-panels-dialog.so-panels-dialog-prebuilt-layouts .so-content .layout .layout-inside:hover { + border: 1px solid #BBBBBB; + background: #FFFFFF; + -webkit-box-shadow: 0 2px 2px rgba(0,0,0,0.075); + -moz-box-shadow: 0 2px 2px rgba(0,0,0,0.075); + box-shadow: 0 2px 2px rgba(0,0,0,0.075); +} +.so-panels-dialog.so-panels-dialog-prebuilt-layouts .so-content .layout .layout-inside h4 { + font-size: 15px; + margin: 0; + line-height: 1.2em; + height: 1.2em; + overflow: hidden; +} +.so-panels-dialog.so-panels-dialog-prebuilt-layouts .so-content .layout .layout-inside .description { + line-height: 1.2em; + height: 1.2em; + margin-top: 0.7em; + font-size: 12px; + color: #888; + overflow: hidden; +} +.so-panels-dialog.so-panels-dialog-prebuilt-layouts .so-content .layout .layout-inside .dashicons { + display: none; + float: left; + margin-top: 10px; +} +.so-panels-dialog.so-panels-dialog-prebuilt-layouts .so-content .layout.so-selected .layout-inside { + border: 1px solid #aaaaaa; + background: #F2F2F2; +} +.so-panels-dialog.so-panels-dialog-prebuilt-layouts .so-content .layout.so-selected .layout-inside h4, +.so-panels-dialog.so-panels-dialog-prebuilt-layouts .so-content .layout.so-selected .layout-inside .description { + margin-left: 35px; +} +.so-panels-dialog.so-panels-dialog-prebuilt-layouts .so-content .layout.so-selected .layout-inside .dashicons { + display: inline-block; +} +.so-panels-dialog.so-panels-dialog-prebuilt-layouts .so-content .so-error-message { + font-size: 14px; + border: 1px solid #cccccc; + background: #F8F8F8; + padding: 15px 20px; +} +.so-panels-dialog .so-visual-styles { + margin: -15px; + /* All the field types */ +} +.so-panels-dialog .so-visual-styles h3 { + line-height: 1em; + margin: 0; + padding: 20px 15px; + border-bottom: 1px solid #ddd; +} +.so-panels-dialog .so-visual-styles .style-section-head { + background: white; + padding: 15px 10px; + border-bottom: 1px solid #ddd; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.so-panels-dialog .so-visual-styles .style-section-head h4 { + margin: 0; +} +.so-panels-dialog .so-visual-styles .style-section-fields { + padding: 15px; + border-bottom: 1px solid #ddd; + background: #F7F7F7; +} +.so-panels-dialog .so-visual-styles .style-section-fields .style-field-wrapper { + margin-bottom: 20px; +} +.so-panels-dialog .so-visual-styles .style-section-fields .style-field-wrapper:last-child { + margin-bottom: 0; +} +.so-panels-dialog .so-visual-styles .style-section-fields .style-field-wrapper > label { + font-weight: bold; + display: block; + margin-bottom: 3px; +} +.so-panels-dialog .so-visual-styles .style-section-fields .style-field-wrapper .style-field { + zoom: 1; +} +.so-panels-dialog .so-visual-styles .style-section-fields .style-field-wrapper .style-field:before { + content: ''; + display: block; +} +.so-panels-dialog .so-visual-styles .style-section-fields .style-field-wrapper .style-field:after { + content: ''; + display: table; + clear: both; +} +.so-panels-dialog .so-visual-styles .style-section-fields .style-field-wrapper .style-field input { + font-size: 12px; +} +.so-panels-dialog .so-visual-styles .style-input-wrapper { + zoom: 1; +} +.so-panels-dialog .so-visual-styles .style-input-wrapper:before { + content: ''; + display: block; +} +.so-panels-dialog .so-visual-styles .style-input-wrapper:after { + content: ''; + display: table; + clear: both; +} +.so-panels-dialog .so-visual-styles .style-input-wrapper input { + max-width: 100%; +} +.so-panels-dialog .so-visual-styles .style-field-measurement input[type="text"] { + width: 60px; + float: left; +} +.so-panels-dialog .so-visual-styles .style-field-measurement select { + float: left; +} +.so-panels-dialog .so-visual-styles .style-field-image .so-image-selector { + display: inline-block; + background-color: #f7f7f7; + border: 1px solid #ccc; + height: 28px; + float: left; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + cursor: pointer; + -webkit-box-shadow: inset 0 1px #FFFFFF; + -moz-box-shadow: inset 0 1px #FFFFFF; + box-shadow: inset 0 1px #FFFFFF; +} +.so-panels-dialog .so-visual-styles .style-field-image .so-image-selector .current-image { + height: 28px; + width: 28px; + float: left; + background: #ffffff; + border-right: 1px solid #ccc; + background-size: cover; + -webkit-border-top-right-radius: 0; + -webkit-border-bottom-right-radius: 0; + -webkit-border-bottom-left-radius: 3px; + -webkit-border-top-left-radius: 3px; + -moz-border-radius-topright: 0; + -moz-border-radius-bottomright: 0; + -moz-border-radius-bottomleft: 3px; + -moz-border-radius-topleft: 3px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; + -moz-background-clip: padding-box; + -webkit-background-clip: padding-box; + background-clip: padding-box; +} +.so-panels-dialog .so-visual-styles .style-field-image .so-image-selector .select-image { + font-size: 12px; + line-height: 28px; + float: left; + padding: 0 8px; + color: #555; +} +.so-panels-dialog .so-visual-styles .style-field-image .remove-image { + font-size: 12px; + margin-top: 4px; + margin-left: 15px; + display: inline-block; + float: left; + color: #666; + text-decoration: none; +} +.so-panels-dialog .so-visual-styles .style-field-image .remove-image .remove-image { + color: #333; +} +.so-panels-dialog .so-visual-styles .so-field-code { + font-size: 12px; + font-family: "Courier 10 Pitch", Courier, monospace; +} +.so-panels-dialog .so-visual-styles .so-description { + color: #999; + font-size: 12px; + margin-top: 5px; + margin-bottom: 0; + font-style: italic; + clear: both; +} +.so-panels-dialog .so-content .siteorigin-panels-builder .so-builder-toolbar { + border: 1px solid #dedede; +} +.so-panels-dialog .so-content .siteorigin-panels-builder .so-rows-container { + padding: 20px 0 0 0; +} +.so-panels-live-editor > div { + position: fixed; + z-index: 99999; +} +.so-panels-live-editor .live-editor-form { + display: none; +} +.so-panels-live-editor .so-overlay { + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(0, 0, 0, 0.75); +} +.so-panels-live-editor .so-sidebar { + top: 0; + left: 0; + bottom: 0; + width: 260px; + overflow-y: auto; + background: #F2F2F2; + border-right: 1px solid #D0D0D0; + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.so-panels-live-editor .so-sidebar .so-sidebar-tools { + background: #eee; + border-bottom: 1px solid #ddd; +} +.so-panels-live-editor .so-sidebar .so-sidebar-tools .live-editor-close { + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + display: block; + width: 45px; + height: 45px; + background: #eee; + border-right: 1px solid #ddd; + color: #444; + cursor: pointer; + text-decoration: none; + position: relative; + text-align: center; + padding-top: 12px; +} +.so-panels-live-editor .so-sidebar .so-sidebar-tools .live-editor-close:hover { + background: #FFFFFF; +} +.so-panels-live-editor .so-sidebar .so-sidebar-tools .live-editor-close:before { + font: 400 22px/1 dashicons; + content: "\f341"; + top: 7px; + left: 13px; +} +.so-panels-live-editor .so-sidebar .page-widgets .page-widgets-section .section-header { + cursor: pointer; + background: white; + padding: 15px 10px; + border: solid #ddd; + border-width: 1px 0; +} +.so-panels-live-editor .so-sidebar .page-widgets .page-widgets-section .section-header h4 { + margin: 0; + font-size: 16px; +} +.so-panels-live-editor .so-sidebar .page-widgets .page-widgets-section .section-widgets { + padding: 10px; +} +.so-panels-live-editor .so-sidebar .page-widgets .page-widgets-section:first-child .section-header { + border-top: 0; +} +.so-panels-live-editor .so-sidebar .page-widgets .so-widget { + border: 1px solid #cccccc; + cursor: pointer; + padding: 10px; + background: #F8F8F8; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.075), inset 0 1px 0 #FFFFFF; + -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.075), inset 0 1px 0 #FFFFFF; + box-shadow: 0 1px 2px rgba(0,0,0,0.075), inset 0 1px 0 #FFFFFF; + margin-bottom: 6px; +} +.so-panels-live-editor .so-sidebar .page-widgets .so-widget:hover, +.so-panels-live-editor .so-sidebar .page-widgets .so-widget.so-hovered { + -webkit-box-shadow: 0 2px 2px rgba(0,0,0,0.0125), inset 0 1px 0 #FFFFFF; + -moz-box-shadow: 0 2px 2px rgba(0,0,0,0.0125), inset 0 1px 0 #FFFFFF; + box-shadow: 0 2px 2px rgba(0,0,0,0.0125), inset 0 1px 0 #FFFFFF; + border: 1px solid #9bafb5; + background: #f4f9fd; +} +.so-panels-live-editor .so-sidebar .page-widgets .so-widget.so-current { + border-color: #0074a2; + background: #2ea2cc; + cursor: auto; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.2); + -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.2); + box-shadow: 0 1px 2px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.2); +} +.so-panels-live-editor .so-sidebar .page-widgets .so-widget.so-current h4 { + color: #FFFFFF; +} +.so-panels-live-editor .so-sidebar .page-widgets .so-widget.so-current small { + color: #eeeeee; +} +.so-panels-live-editor .so-sidebar .page-widgets .so-widget.so-current:hover { + border-color: #0074a2; + background: #2ea2cc; +} +.so-panels-live-editor .so-sidebar .page-widgets .so-widget:last-child { + margin-bottom: 0; +} +.so-panels-live-editor .so-sidebar .page-widgets .so-widget h4 { + margin: 0 0 7px 0; + padding: 0; + height: 1.2em; + color: #222222; + font-size: 14px; +} +.so-panels-live-editor .so-sidebar .page-widgets .so-widget .actions { + display: none; +} +.so-panels-live-editor .so-sidebar .page-widgets .so-widget small { + font-size: 11px; + line-height: 1.2em; + height: 1.2em; + display: block; + overflow: hidden; + color: #888888; +} +.so-panels-live-editor .so-preview { + top: 0; + right: 0; + bottom: 0; + left: 260px; + background: #F4F4F4; +} +.so-panels-live-editor .so-preview iframe { + width: 100%; + height: 100%; +} +.so-panels-loading { + background-image: url("images/wpspin_light.gif"); + background-position: center center; + background-repeat: no-repeat; +} +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + .so-panels-loading { + background-image: url(images/wpspin_light-2x.gif); + background-size: 16px 16px; + } +} +/* For the custom home page interface */ +#panels-home-page { + /* The Switch - © 2013 Thibaut Courouble - MIT License */ +} +#panels-home-page .siteorigin-panels-builder { + border: 1px solid #D0D0D0; + background-color: #ffffff; + margin: 10px 0; +} +#panels-home-page .siteorigin-panels-builder.so-panels-loading { + min-height: 150px; +} +#panels-home-page .switch { + margin: 0 10px 0 0; + float: left; + position: relative; + display: inline-block; + vertical-align: top; + width: 68px; + height: 24px; + padding: 3px; + background-color: white; + border-radius: 24px; + box-shadow: inset 0 -1px #ffffff, inset 0 1px 1px rgba(0, 0, 0, 0.05); + cursor: pointer; + background-image: -webkit-linear-gradient(top, #eeeeee, #ffffff 25px); + background-image: -moz-linear-gradient(top, #eeeeee, #ffffff 25px); + background-image: -o-linear-gradient(top, #eeeeee, #ffffff 25px); + background-image: linear-gradient(to bottom, #eeeeee, #ffffff 25px); +} +#panels-home-page .switch .switch-input { + position: absolute; + top: 0; + left: 0; + opacity: 0; +} +#panels-home-page .switch .switch-label { + position: relative; + display: block; + height: inherit; + font-size: 12px; + text-transform: uppercase; + background: #eceeef; + border-radius: inherit; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.12), inset 0 0 2px rgba(0, 0, 0, 0.15); + -webkit-transition: 0.15s ease-out; + -moz-transition: 0.15s ease-out; + -o-transition: 0.15s ease-out; + transition: 0.15s ease-out; + -webkit-transition-property: opacity background; + -moz-transition-property: opacity background; + -o-transition-property: opacity background; + transition-property: opacity background; +} +#panels-home-page .switch .switch-label:before, +#panels-home-page .switch .switch-label:after { + position: absolute; + top: 50%; + margin-top: -0.5em; + line-height: 1; + -webkit-transition: inherit; + -moz-transition: inherit; + -o-transition: inherit; + transition: inherit; +} +#panels-home-page .switch .switch-label:before { + content: attr(data-off); + right: 11px; + color: #aaa; + text-shadow: 0 1px rgba(255, 255, 255, 0.5); +} +#panels-home-page .switch .switch-label:after { + content: attr(data-on); + left: 13px; + color: white; + text-shadow: 0 1px rgba(0, 0, 0, 0.2); + opacity: 0; +} +#panels-home-page .switch .switch-input:checked ~ .switch-label { + background: #47a8d8; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15), inset 0 0 3px rgba(0, 0, 0, 0.2); +} +#panels-home-page .switch .switch-input:checked ~ .switch-label:before { + opacity: 0; +} +#panels-home-page .switch .switch-input:checked ~ .switch-label:after { + opacity: 1; +} +#panels-home-page .switch .switch-handle { + position: absolute; + top: 4px; + left: 4px; + width: 22px; + height: 22px; + background: white; + border-radius: 12px; + box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2); + background-image: -webkit-linear-gradient(top, #ffffff 40%, #f0f0f0); + background-image: -moz-linear-gradient(top, #ffffff 40%, #f0f0f0); + background-image: -o-linear-gradient(top, #ffffff 40%, #f0f0f0); + background-image: linear-gradient(to bottom, #ffffff 40%, #f0f0f0); + -webkit-transition: left 0.15s ease-out; + -moz-transition: left 0.15s ease-out; + -o-transition: left 0.15s ease-out; + transition: left 0.15s ease-out; +} +#panels-home-page .switch .switch-handle:before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + margin: -7px 0 0 -7px; + width: 14px; + height: 14px; + background: #f9f9f9; + border-radius: 7px; + box-shadow: inset 0 1px rgba(0, 0, 0, 0.02); + background-image: -webkit-linear-gradient(top, #eeeeee, #ffffff); + background-image: -moz-linear-gradient(top, #eeeeee, #ffffff); + background-image: -o-linear-gradient(top, #eeeeee, #ffffff); + background-image: linear-gradient(to bottom, #eeeeee, #ffffff); +} +#panels-home-page .switch .switch-input:checked ~ .switch-handle { + left: 48px; + box-shadow: -1px 1px 5px rgba(0, 0, 0, 0.2); +} +#panels-home-page .switch .switch-green > .switch-input:checked ~ .switch-label { + background: #4fb845; +} +#panels-home-page #panels-view-as-page { + display: inline-block; + margin-left: 50px; +} diff --git a/css/admin.less b/css/admin.less new file mode 100644 index 000000000..2ee93afc6 --- /dev/null +++ b/css/admin.less @@ -0,0 +1,1850 @@ +@import "mixins"; + +@font-face { + font-family: 'siteorigin-panels'; + src:url('icons/siteorigin-panels.eot?-yv2c11'); + src:url('icons/siteorigin-panels.eot?#iefix-yv2c11') format('embedded-opentype'), + url('icons/siteorigin-panels.woff?-yv2c11') format('woff'), + url('icons/siteorigin-panels.ttf?-yv2c11') format('truetype'), + url('icons/siteorigin-panels.svg?-yv2c11#siteorigin-panels') format('svg'); + font-weight: normal; + font-style: normal; +} + +#panels{ +} + +/* This is for the metabox */ + +#so-panels-panels { + &.attached-to-editor{ + + margin-top: 20px; + + h3.hndle { + display: none; + } + + .inside { + margin: 0 !important; + padding: 0 !important; + } + + .so-toolbar .so-switch-to-standard{ + display: block; + } + } +} + +/* Everything for the main builder interface */ + +.siteorigin-panels-builder { + + position: relative; + + .so-builder-container { + /* Top padding for the toolbar */ + padding-top: 38px; + position: relative; + } + + /* These are generic iconic buttons used in the page builder interface */ + .so-tool-button { + padding: 6px 7px; + font-size: 11px; + text-decoration: none; + line-height: 0.9em; + + float: left; + margin-right: 2px; + display: block; + visibility: visible; + position: relative; + + border: 1px solid #C0C0C0; + .gradient(#F0F0F0, #F0F0F0, #F9F9F9); + .box-shadow(~"0 1px 1px rgba(0,0,0,0.04)"); + outline: none; + .rounded(2px); + + span { + display: inline-block; + color: #666666; + text-shadow: 0 1px 0 #FFFFFF; + min-width: 10px; + text-align: center; + } + + &:hover { + background: #FFFFFF; + + span { + color: #444444; + } + } + } + + .so-builder-toolbar { + .box-sizing(border-box); + border-bottom: 1px solid #D0D0D0; + background: #F5F5F5; + line-height: 1em; + position: absolute; + z-index: 101; + white-space: nowrap; + overflow-x: hidden; + + box-shadow: 0 1px 1px rgba(0,0,0,0.04); + + top: 0; + left: 0; + width: 100%; + + padding: 6px 9px; + margin-top: 0 !important; + .clearfix(); + + /* The toolbar buttons */ + > .so-tool-button { + display: inline-block; + color: #666666; + padding-right: 8px; + + .so-panels-icon { + float: left; + margin: 0 5px 0 0; + } + + &:hover { + color: #444444; + } + } + + .so-switch-to-standard { + position: absolute; + top: 5px; + right: 10px; + + display: none; + text-decoration: none; + color: #666666; + padding: 5px 6px; + .rounded(2px); + border: 1px solid transparent; + margin-top: 2px; + font-size: 11px; + + &:hover { + background: #fafafa; + border: 1px solid #999999; + color: #444444; + } + } + } + + @media screen and (max-width: 600px) { + .so-builder-toolbar { + + padding: 10px; + + > .so-tool-button { + padding-right: 2px; + + .so-panels-icon { + font-size: 20px; + } + + span.so-button-text { + display: none; + } + } + + } + + } + + .so-rows-container{ + padding: 20px 15px 0 15px; + + .so-row-toolbar { + .clearfix(); + + .so-tool-button { + .box-sizing(border-box); + height: 22px; + padding: 5px; + font-size: 10px; + float: right; + + &.so-row-move { + cursor: move; + } + } + + margin-bottom: 4px; + + .so-dropdown-wrapper { + position: relative; + float: right; + + .so-dropdown-links-wrapper { + display: none; + z-index: 11; + position:absolute; + right: -10px; + padding: 6px 0 0 0; + top: 22px; + width: 125px; + + ul { + margin: 0; + border: 1px solid #C0C0C0; + background: #F9F9F9; + .rounded(2px); + padding: 4px 0; + .box-shadow(~"0 2px 2px rgba(0,0,0,0.1)"); + + li { + margin: 0; + + &:first-child { + border-top-width: 1px; + } + + a { + display: block; + padding: 2px 8px; + text-decoration: none; + color: #666; + font-size: 11px; + font-weight: bold; + + outline: 0 !important; + .box-shadow(none); + + &:hover { + background: #F0F0F0; + color: #444; + } + + .dashicons { + font-size: 16px; + margin: 0; + float: right; + line-height: 16px; + } + + /* Specific drop down hovers */ + + &.so-row-delete { + color: #a00; + + &:hover { + color: #FFF; + background: #a00; + } + } + } + } + + .so-pointer { + width: 12px; + height: 6px; + position: absolute; + z-index: 12; + background: url("./images/dropdown-pointer.png"); + background-size: 12px 6px; + top: 1px; + right: 18px; + } + } + + } + + &:hover { + .so-dropdown-links-wrapper { + display: block; + } + } + + } + + } + + .ui-sortable-placeholder { + visibility: visible !important; + background: #F7F7F7; + .box-sizing(border-box); + } + + .so-row-container { + margin-bottom: 20px; + .user-select(none); + + .so-cells { + .clearfix(); + margin: 0 -5px; + position: relative; + + .ui-resizable-handle.ui-resizable-w{ + width: 10px; + left: -11px; + cursor: col-resize; + background: rgba(0,150,211, 0); + background: rgba(0,150,211, 0.25); + + -webkit-transition: background 0.25s ease-in-out; + -moz-transition: background 0.25s ease-in-out; + -o-transition: background 0.25s ease-in-out; + transition: background 0.25s ease-in-out; + + &:hover{ + background: rgba(0,150,211, 0.1); + } + } + + .cell { + + .box-sizing(border-box); + float: left; + position: relative; + padding: 0 5px; + + &.so-first{ + margin-left: 0; + } + + &.so-last{ + margin-right: 0; + } + + .cell-wrapper{ + background: #e4eff4; + border: 1px solid #bcccd2; + padding: 10px 10px 4px 10px; /* 6px bottom to remove bottom margin from panels */ + height: 100%; + min-height: 70px; + } + + &.cell-selected .cell-wrapper{ + background: #cae7f4 url("images/cell-selected.png") repeat; + border-color: #9abcc7; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.2); + } + + @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + &.cell-selected .cell-wrapper{ + background-size: 3px 3px; + } + } + + &, .cell-wrapper { + .box-sizing(border-box); + } + + .widgets-container { + + .so-widget { + cursor: move; + margin-bottom: 5px; + + background: #f9f9fb; + + border: 1px solid #9bafb5; + max-height: 49px; + .box-sizing(border-box); + .box-shadow(~"0 1px 2px rgba(0,0,0,0.075)"); + + + &:hover { + border: 1px solid #93a7ad; + background: #feffff; + .box-shadow(~"0 2px 2px rgba(0,0,0,0.075)"); + } + + .so-widget-wrapper{ + padding: 7px 9px; + overflow: hidden; + position: relative; + } + + h4{ + display: block; + cursor: pointer; + margin: 0 15px 3px 0; + font-weight: 600; + line-height: 1.25em; + color: #474747; + text-shadow: 0 1px 0 #FFF; + white-space: nowrap; + + span { + font-weight: normal; + display: inline-block; + color: #999; + text-shadow: 0 1px 0 #FFF; + margin-left: 12px; + margin-right: 5px; + font-style: italic; + } + } + + .title { + .actions { + font-size: 12px; + position: absolute; + top: 5px; + right: 7px; + cursor: pointer; + padding: 2px 2px 2px 15px; + z-index: 10; + + &:hover { + background: #feffff; + + a{ + opacity: 1; + } + } + + a{ + display: none; + margin-right: 3px; + text-decoration: none; + } + + a.widget-delete{ + color: #FF0000; + + &:hover { + color: white; + background: #FF0000; + } + } + + } + } + + &:hover { + .title a{ + display: inline-block; + opacity: 0.5; + } + } + + &.panel-being-dragged .title .actions { + display: none; + } + + small{ + display: block; + height: 16px; + overflow: hidden; + color: #777; + } + + .form{ + display: none; + } + + &.widget-being-dragged { + opacity: 0.9; + pointer-events: none; + } + + } + + .so-widget-sortable-highlight{ + border: 1px solid; + .box-sizing(border-box); + + height: 49px; + + background: #ddebef; + border-color: #bcccd2; + margin-bottom: 5px; + + position: relative; + + .box-shadow(~"inset 2px 2px 2px rgba(0,0,0,0.01)"); + } + + } + + .resize-handle{ + z-index: 100; + position: absolute; + top: 0; + width: 10px; + left: -5px; + cursor: col-resize; + background: #e5f4fa; + height: 100%; + + .transition(0.25s, background, ease-in-out); + + &:hover{ + background: #bfe4f3; + } + } + + &:first-child { + .resize-handle { + display: none; + } + } + } + } + + } + } + + /* Page Builder icons */ + + .so-panels-icon { + font-family: 'siteorigin-panels'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + &.so-panels-icon-plus:before { + content: "\f067"; + } + &.so-panels-icon-eye:before { + content: "\f06e"; + } + &.so-panels-icon-arrows-v:before { + content: "\f07d"; + } + &.so-panels-icon-wrench:before { + content: "\f0ad"; + } + &.so-panels-icon-columns:before { + content: "\f0db"; + } + &.so-panels-icon-rotate-left:before { + content: "\f0e2"; + } + &.so-panels-icon-puzzle-piece:before { + content: "\f12e"; + } + &.so-panels-icon-cubes:before { + content: "\f1b3"; + } + } + + .so-panels-welcome-message { + text-align: center; + padding: 0px 15px 20px 15px; + color: #555; + line-height: 1.8em; + + .so-message-wrapper { + padding: 15px 10px; + background: #F8F8F8; + border: 1px solid #E0E0E0; + } + + .so-tool-button { + font-size: inherit; + display: inline-block; + float: none; + color: #666; + padding: 5px 10px; + margin: 0 3px; + + .so-panels-icon { + color: #777; + font-size: 0.8em; + } + } + } + +} + +/* This is to display a draggable widget */ +.so-widget.ui-sortable-helper.widget-being-dragged { + .siteorigin-panels-builder.so-rows-container.so-row-container.so-cells.cell.widgets-container.so-widget; +} + +/* Handles displaying a builder in the WordPress widget interface */ +.widgets-holder-wrap .widget-inside { + + .siteorigin-panels-builder { + + .so-builder-container { + padding-top: 0; + } + + .so-rows-container { + padding: 10px 0 0 0; + } + + .so-builder-toolbar { + padding-left: 15px; + padding-right: 15px; + margin: 0 -15px; + } + } + +} + +.so-panels-dialog { + + @edge_spacing: 30px; + + @title_bar_height: 50px; + @toolbar_height: 58px; + @sidebar_width: 290px; + + @pane_padding: 15px; + @border_color: #D8D8D8; + + .so-overlay, .so-content, .so-title-bar, .so-toolbar, .so-left-sidebar, .so-right-sidebar { + z-index: 100000; + position: fixed; + .box-sizing(border-box); + padding: @pane_padding; + + } + + .so-content, .so-left-sidebar, .so-right-sidebar { + overflow-y: auto; + } + + .so-overlay { + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.5); + } + + .so-content { + top: @edge_spacing + @title_bar_height; + left: @edge_spacing; + right: @edge_spacing; + bottom: @edge_spacing + @toolbar_height; + background-color: #fdfdfd; + overflow-x: hidden; + + .box-shadow(~"inset 0 2px 2px rgba(0,0,0,0.03)"); + + > *:first-child { + margin-top: 0; + } + + > *:last-child { + margin-bottom: 0; + } + + .so-content-tabs > * { + display: none; + } + + } + + .so-title-bar { + left: @edge_spacing; + right: @edge_spacing; + top: @edge_spacing; + height: @title_bar_height; + background-color: #fafafa; + border-bottom: 1px solid @border_color; + + h3.so-title { + margin: 0 !important; + padding: 0 !important; + } + + h3.so-parent-link { + cursor: pointer; + position: relative; + float: left; + margin: 0 @pane_padding 0 0 !important; + padding: 0 @pane_padding+12px 0 0 !important; + + .so-separator { + position: absolute; + top: -@pane_padding; + right: 0; + width: 12px; + height: @title_bar_height; + display: block; + background: url(./images/dialog-separator.png) no-repeat; + } + + + @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + .so-separator { + background: url(./images/dialog-separator@2x.png) no-repeat; + background-size: cover; + } + } + + + } + + + + /* These are the action buttons in the title bar */ + a { + position: absolute; + box-sizing: border-box; + width: 50px; + height: 50px; + display: block; + + top: 0; + right: 0; + + .transition(0.2s); + background: #fafafa; + border-left: 1px solid #d8d8d8; + border-bottom: 1px solid #d8d8d8; + + &:hover { + background: #e9e9e9; + .so-dialog-icon { + color: #333333; + } + } + + .so-dialog-icon { + position: absolute; + top: 50%; + left: 50%; + text-decoration: none; + width: 20px; + height: 20px; + margin-left: -10px; + margin-top: -10px; + color: #666666; + text-align: center; + + &:before { + font: 400 20px/1em dashicons; + + top: 7px; + left: 13px; + } + } + + &.so-close { + right: 0; + + .so-dialog-icon:before { + content: "\f335"; + } + } + + &.so-next { + right: 50px; + + .so-dialog-icon:before { + content: '\f345'; + } + } + + &.so-previous { + right: 100px; + + .so-dialog-icon:before { + content: '\f341'; + } + } + + /* Disabled nav */ + &.so-nav.so-disabled { + cursor: default; + pointer-events:none; + + .so-dialog-icon { + color: #dddddd; + } + } + + } + } + + .so-toolbar { + left: @edge_spacing; + right: @edge_spacing; + bottom: @edge_spacing; + height: @toolbar_height; + background-color: #fafafa; + border-top: 1px solid @border_color; + + .so-status { + float: left; + padding-top: 6px; + padding-bottom: 6px; + font-style: italic; + color: #999999; + line-height: 1em; + + &.so-panels-loading { + padding-left: 26px; + background-position: left center; + } + } + + .so-buttons { + + .action-buttons { + + position: absolute; + left: 15px; + top: 50%; + margin-top: -0.65em; + + a { + display: inline; + padding: 0.2em 0.5em; + line-height: 1em; + margin-right: 0.5em; + text-decoration: none; + } + + .so-delete { + color: #a00; + + &:hover { + background: #a00; + color: #FFFFFF; + } + } + + .so-duplicate:hover { + text-decoration: underline; + } + + } + + float: right; + } + } + + .so-left-sidebar, .so-right-sidebar { + background-color: #f3f3f3; + } + + .so-left-sidebar { + display: none; + + top: @edge_spacing; + left: @edge_spacing; + bottom: @edge_spacing; + width: @sidebar_width; + + + border-right: 1px solid @border_color; + + h4 { + margin: 0 0 20px 0; + font-size: 18px; + } + + .so-sidebar-search { + width: 100%; + padding: 6px; + margin-bottom: 20px; + } + + .so-sidebar-tabs { + list-style: none; + margin: 0 -15px; + + li { + margin-bottom: 0; + a { + padding: 7px 16px; + display: block; + font-size: 14px; + text-decoration: none; + + &:hover { + background: #FFFFFF; + } + + box-shadow: none !important; + } + + &.tab-active { + a { + color: #555; + font-weight: bold; + background: #FFFFFF; + &:hover { + background: #FFFFFF; + } + } + } + } + } + + } + + .so-right-sidebar { + display: none; + + top: @edge_spacing + @title_bar_height; + right: @edge_spacing; + bottom: @edge_spacing + @toolbar_height; + width: @sidebar_width; + + border-left: 1px solid @border_color; + + h3 { + color: #333; + + &:first-child { + margin-top: 0; + } + } + } + + .so-sidebar { + .form-field { + margin-bottom: 20px; + + label{ + font-weight: 500; + font-size: 15px; + display: block; + margin-bottom: 10px; + } + + input[type=text] { + + } + } + } + + &.so-panels-dialog-has-left-sidebar { + .so-content, .so-toolbar, .so-title-bar { + left: @edge_spacing + @sidebar_width; + } + + .so-content { + .box-shadow(~"inset 2px 2px 2px rgba(0,0,0,0.03)"); + } + + .so-left-sidebar { + display: block; + } + } + + &.so-panels-dialog-has-right-sidebar { + .so-content { + right: @edge_spacing + @sidebar_width; + } + + .so-right-sidebar { + display: block; + } + } + + &.so-panels-dialog-edit-widget { + + .so-left-sidebar { + + .so-widgets { + + .so-widget { + .rounded(2px); + + border: 1px solid #cccccc; + cursor: pointer; + padding: 10px; + background: #f9f9f9; + .box-shadow(~"0 1px 2px rgba(0,0,0,0.075), inset 0 1px 0 #FFFFFF"); + margin-bottom: 15px; + + &:hover { + border: 1px solid #BBBBBB; + background: #FFFFFF; + } + + &.so-current { + border-color: #0074a2; + background: #2ea2cc; + cursor: auto; + .box-shadow(~"0 1px 2px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.2)"); + + h3 { + color: #FFFFFF; + } + + small { + color: #eeeeee; + } + + &:hover { + border-color: #0074a2; + background: #2ea2cc; + } + } + + &:last-child { + margin-bottom: 0; + } + + h3 { + margin: 0 0 7px 0; + padding: 0; + height: 1.2em; + color: #222222; + font-size: 14px; + } + + small{ + font-size: 11px; + line-height: 1.25em; + display: block; + overflow: hidden; + color: #888888; + } + + } + + } + + } + + } + + /* The add widget dialog */ + + &.so-panels-dialog-add-widget { + + .widget-type-list { + + .clearfix(); + + margin: 0 -5px -10px -5px; + min-height: 10px; + + .widget-type { + -ms-user-select: none; /* IE 10+ */ + -moz-user-select: -moz-none; + -khtml-user-select: none; + -webkit-user-select: none; + user-select: none; + + .box-sizing(border-box); + + width: 25%; + padding: 0 5px; + margin-bottom: 10px; + float: left; + + @media (max-width: 1280px) { + & { + width: 33.333%; + } + } + + @media (max-width: 960px) { + & { + width: 50%; + } + } + + h3 { + margin: 0 0 7px 0; + padding: 0; + height: 1.2em; + color: #222222; + font-size: 14px; + } + + small{ + font-size: 11px; + height: 2.5em; + line-height: 1.25em; + display: block; + overflow: hidden; + color: #888888; + } + + .widget-icon { + font-size: 20px; + width: 20px; + height: 20px; + color: #666; + float: left; + margin: -1px 0.5em 0 0; + } + } + + .widget-type-wrapper { + border: 1px solid #cccccc; + cursor: pointer; + padding: 10px; + background: #F8F8F8; + .box-shadow(~"0 1px 2px rgba(0,0,0,0.075)"); + + &:hover { + border: 1px solid #BBBBBB; + background: #FFFFFF; + .box-shadow(~"0 2px 2px rgba(0,0,0,0.075)"); + } + } + + } + + } + + /* The row edit dialog */ + + &.so-panels-dialog-row-edit { + + .so-content { + + @preview_height: 360px; + + .row-set-form { + .clearfix(); + padding: 8px; + border: 1px solid #ccc; + margin-bottom: 20px; + background: #F3F3F3; + + input, select, button, strong, span { + display: block; + float: left; + margin-right: 5px; + + outline: none; + box-shadow: none; + } + + strong, span { + margin-top: 5px; + margin-right: 0.75em; + } + + span { + margin-left: 0.75em; + } + + label{ + display: inline; + } + } + + .row-preview { + + margin: 0 -6px; + height: @preview_height; + position: relative; + .preview-cell, .preview-cell-in, .preview-cell-weight { + .box-sizing(border-box); + } + + .preview-cell { + float: left; + position: relative; + padding: 0 6px; + + .preview-cell-in { + border: 1px solid #bcccd2; + min-height: @preview_height; + background: #e4eff4; + position: relative; + + .preview-cell-weight, + .preview-cell-weight-input { + position: absolute; + font-size: 17px; + font-weight: bold; + top: 50%; + left: 50%; + width: 80px; + text-align: center; + color: #5e6d72; + margin: -0.95em 0 0 -40px; + padding: 10px 0; + border: 1px solid transparent; + line-height: 1.4em !important; + + &:after{ + content: '%'; + } + overflow: hidden; + cursor: pointer; + + &:hover { + background: #F6F6F6; + border: 1px solid #D0D0D0; + } + } + + .preview-cell-weight-input { + background: #F6F6F6; + border: 1px solid #D0D0D0; + .box-shadow(none); + } + } + + .resize-handle{ + z-index: 100; + position: absolute; + top: 0; + width: 12px; + left: -6px; + cursor: col-resize; + background: #e5f4fb; + height: @preview_height; + + .transition(0.15s, background, ease-in-out); + + &:hover, &.ui-draggable-dragging { + background: #b7e0f1; + } + } + } + } + + } + + + } + + &.so-panels-dialog-history { + + .so-left-sidebar { + padding: 0; + } + + .history-entries { + + + .history-entry { + padding: 10px; + background: #F8F8F8; + cursor: pointer; + + h3 { + margin: 0 0 0.6em 0; + font-size: 12px; + font-weight: bold; + color: #444444; + line-height: 1em; + } + + .timesince { + color: #999999; + font-size: 11px; + line-height: 1em; + } + + border-bottom: 1px solid #ccc; + + &:hover { + background: #F0F0F0; + } + + &.so-selected { + background: #EEEEEE; + } + + .count { + color: #999999; + } + } + } + + .so-content { + padding: 0; + overflow-y: hidden; + + form.history-form { + display: none; + } + + iframe.siteorigin-panels-history-iframe { + width: 100%; + height: 100%; + } + } + } + + /* For prebuilt layouts */ + + &.so-panels-dialog-prebuilt-layouts { + .so-content { + + padding-left: 10px; + padding-right: 10px; + + .layout { + .box-sizing(border-box); + float: left; + width: 33.333%; + padding: 0 5px 10px 5px; + + .layout-inside { + padding: 20px; + cursor: pointer; + border: 1px solid #cccccc; + background: #F8F8F8; + .box-shadow(~"0 1px 2px rgba(0,0,0,0.075)"); + + &:hover { + border: 1px solid #BBBBBB; + background: #FFFFFF; + .box-shadow(~"0 2px 2px rgba(0,0,0,0.075)"); + } + + h4 { + font-size: 15px; + margin: 0; + line-height: 1.2em; + height: 1.2em; + overflow: hidden; + } + + .description { + line-height: 1.2em; + height: 1.2em; + margin-top: 0.7em; + font-size: 12px; + color: #888; + overflow: hidden; + } + + .dashicons { + display: none; + float: left; + margin-top: 10px; + } + + } + + &.so-selected { + + .layout-inside { + border: 1px solid #aaaaaa; + background: #F2F2F2; + + h4, .description { + margin-left: 35px; + } + + .dashicons { + display: inline-block; + } + } + + } + + } + + .so-error-message { + font-size: 14px; + border: 1px solid #cccccc; + background: #F8F8F8; + padding: 15px 20px; + } + } + + } + + /* Everything we need for the style fields */ + + .so-visual-styles { + + margin: -15px; + + h3 { + line-height: 1em; + margin: 0; + padding: 20px 15px; + border-bottom: 1px solid #ddd; + } + + .style-section-head { + background: white; + padding: 15px 10px; + border-bottom: 1px solid #ddd; + cursor: pointer; + + .user-select(none); + + h4 { + margin: 0; + } + } + + .style-section-fields { + padding: 15px; + border-bottom: 1px solid #ddd; + background: #F7F7F7; + + .style-field-wrapper { + margin-bottom: 20px; + + &:last-child { + margin-bottom: 0; + } + + > label { + font-weight: bold; + display: block; + margin-bottom: 3px; + } + + .style-field { + .clearfix(); + + input { + font-size: 12px; + } + } + } + } + + .style-input-wrapper { + .clearfix(); + + input { + max-width: 100%; + } + } + + /* All the field types */ + + .style-field-measurement { + input[type="text"] { + width: 60px; + float: left; + } + + select { + float: left; + } + } + + .style-field-image { + + @image_field_height: 28px; + + .so-image-selector { + display: inline-block; + background-color: #f7f7f7; + border: 1px solid #ccc; + height: @image_field_height; + float: left; + .rounded(3px); + cursor: pointer; + + .box-shadow(~"inset 0 1px #FFFFFF"); + + .current-image { + height: @image_field_height; + width: @image_field_height; + float: left; + background: #ffffff; + border-right: 1px solid #ccc; + background-size: cover; + + .border-radius(0, 0, 3px, 3px); + } + + .select-image { + font-size: 12px; + line-height: @image_field_height; + float: left; + padding: 0 8px; + color: #555; + } + } + + .remove-image { + font-size: 12px; + margin-top: 4px; + margin-left: 15px; + + display: inline-block; + float: left; + color: #666; + text-decoration: none; + + .remove-image { + color: #333; + } + } + } + + .so-field-code { + font-size: 12px; + font-family: "Courier 10 Pitch", Courier, monospace; + } + + .so-description { + color: #999; + font-size: 12px; + margin-top: 5px; + margin-bottom: 0; + font-style: italic; + clear:both; + } + + } + + /* Special case of the builder interface being inside a dialog */ + + .so-content { + .siteorigin-panels-builder { + .so-builder-toolbar { + border: 1px solid #dedede; + } + + .so-rows-container { + padding: 20px 0 0 0; + } + } + } +} + +.so-panels-live-editor { + > div { + position: fixed; + z-index: 99999; + } + + .live-editor-form { + display: none; + } + + .so-overlay { + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(0,0,0,0.75); + } + + .so-sidebar { + top: 0; + left: 0; + bottom: 0; + width: 260px; + overflow-y: auto; + + background: #F2F2F2; + + border-right: 1px solid #D0D0D0; + .box-sizing(border-box); + + .so-sidebar-tools { + background: #eee; + border-bottom: 1px solid #ddd; + + .live-editor-close { + .box-sizing(border-box); + display: block; + width: 45px; + height: 45px; + background: #eee; + border-right: 1px solid #ddd; + color: #444; + cursor: pointer; + text-decoration: none; + position: relative; + text-align: center; + padding-top: 12px; + + &:hover { + background: #FFFFFF; + } + + &:before { + font: 400 22px/1 dashicons; + content: "\f341"; + top: 7px; + left: 13px; + } + } + + } + + .page-widgets { + + .page-widgets-section { + + .section-header { + cursor: pointer; + background: white; + padding: 15px 10px; + border: solid #ddd; + border-width: 1px 0; + + h4 { + margin: 0; + font-size: 16px; + } + } + + .section-widgets { + padding: 10px; + } + + + &:first-child { + .section-header { + border-top: 0; + } + } + } + + .so-widget { + border: 1px solid #cccccc; + cursor: pointer; + padding: 10px; + background: #F8F8F8; + .box-shadow(~"0 1px 2px rgba(0,0,0,0.075), inset 0 1px 0 #FFFFFF"); + margin-bottom: 6px; + + &:hover, &.so-hovered { + .box-shadow(~"0 2px 2px rgba(0,0,0,0.0125), inset 0 1px 0 #FFFFFF"); + border: 1px solid #9bafb5; + background: #f4f9fd; + } + + &.so-current { + border-color: #0074a2; + background: #2ea2cc; + cursor: auto; + .box-shadow(~"0 1px 2px rgba(0,0,0,0.15), inset 0 1px 0 rgba(255,255,255,0.2)"); + + h4 { + color: #FFFFFF; + } + + small { + color: #eeeeee; + } + + &:hover { + border-color: #0074a2; + background: #2ea2cc; + } + } + + &:last-child { + margin-bottom: 0; + } + + h4 { + margin: 0 0 7px 0; + padding: 0; + height: 1.2em; + color: #222222; + font-size: 14px; + } + + .actions { + display: none; + } + + small{ + font-size: 11px; + line-height: 1.2em; + height: 1.2em; + display: block; + overflow: hidden; + color: #888888; + } + + } + + } + } + + .so-preview { + top: 0; + right: 0; + bottom: 0; + left: 260px; + + background: #F4F4F4; + + iframe{ + width: 100%; + height: 100%; + } + } + +} + +.so-panels-loading { + background-image: url("images/wpspin_light.gif"); + background-position: center center; + background-repeat: no-repeat; + + @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + & { + background-image: url(images/wpspin_light-2x.gif); + background-size: 16px 16px; + } + } +} + + +/* For the custom home page interface */ + +#panels-home-page { + + .siteorigin-panels-builder { + border: 1px solid #D0D0D0; + background-color: #ffffff; + margin: 10px 0; + + &.so-panels-loading { + min-height: 150px; + } + } + + /* The Switch - © 2013 Thibaut Courouble - MIT License */ + + .switch { + @switch_height: 24px; + @switch_width: 68px; + @switch_padding: 3px; + + margin: 0 10px 0 0; + float: left; + position: relative; + display: inline-block; + vertical-align: top; + width: @switch_width; + height: @switch_height; + padding: @switch_padding; + background-color: white; + border-radius: @switch_height; + box-shadow: inset 0 -1px white, inset 0 1px 1px rgba(0, 0, 0, 0.05); + cursor: pointer; + background-image: -webkit-linear-gradient(top, #eeeeee, white 25px); + background-image: -moz-linear-gradient(top, #eeeeee, white 25px); + background-image: -o-linear-gradient(top, #eeeeee, white 25px); + background-image: linear-gradient(to bottom, #eeeeee, white 25px); + + .switch-input { + position: absolute; + top: 0; + left: 0; + opacity: 0; + } + + .switch-label { + position: relative; + display: block; + height: inherit; + font-size: @switch_height/2; + text-transform: uppercase; + background: #eceeef; + border-radius: inherit; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.12), inset 0 0 2px rgba(0, 0, 0, 0.15); + -webkit-transition: 0.15s ease-out; + -moz-transition: 0.15s ease-out; + -o-transition: 0.15s ease-out; + transition: 0.15s ease-out; + -webkit-transition-property: opacity background; + -moz-transition-property: opacity background; + -o-transition-property: opacity background; + transition-property: opacity background; + } + .switch-label:before, + .switch-label:after { + position: absolute; + top: 50%; + margin-top: -.5em; + line-height: 1; + -webkit-transition: inherit; + -moz-transition: inherit; + -o-transition: inherit; + transition: inherit; + } + .switch-label:before { + content: attr(data-off); + right: 11px; + color: #aaa; + text-shadow: 0 1px rgba(255, 255, 255, 0.5); + } + .switch-label:after { + content: attr(data-on); + left: @switch_height/2 + 1px; + color: white; + text-shadow: 0 1px rgba(0, 0, 0, 0.2); + opacity: 0; + } + .switch-input:checked ~ .switch-label { + background: #47a8d8; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15), inset 0 0 3px rgba(0, 0, 0, 0.2); + } + .switch-input:checked ~ .switch-label:before { + opacity: 0; + } + .switch-input:checked ~ .switch-label:after { + opacity: 1; + } + + .switch-handle { + position: absolute; + top: 4px; + left: @switch_padding + 1px; + width: @switch_height - 2px; + height: @switch_height - 2px; + background: white; + border-radius: @switch_height/2; + box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2); + background-image: -webkit-linear-gradient(top, white 40%, #f0f0f0); + background-image: -moz-linear-gradient(top, white 40%, #f0f0f0); + background-image: -o-linear-gradient(top, white 40%, #f0f0f0); + background-image: linear-gradient(to bottom, white 40%, #f0f0f0); + -webkit-transition: left 0.15s ease-out; + -moz-transition: left 0.15s ease-out; + -o-transition: left 0.15s ease-out; + transition: left 0.15s ease-out; + } + .switch-handle:before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + margin: -7px 0 0 -7px; + width: @switch_height/2 + 2px; + height: @switch_height/2 + 2px; + background: #f9f9f9; + border-radius: (@switch_height/2 + 2px) / 2; + box-shadow: inset 0 1px rgba(0, 0, 0, 0.02); + background-image: -webkit-linear-gradient(top, #eeeeee, white); + background-image: -moz-linear-gradient(top, #eeeeee, white); + background-image: -o-linear-gradient(top, #eeeeee, white); + background-image: linear-gradient(to bottom, #eeeeee, white); + } + .switch-input:checked ~ .switch-handle { + left: @switch_width - @switch_height + 2px + 2px; + box-shadow: -1px 1px 5px rgba(0, 0, 0, 0.2); + } + + .switch-green > .switch-input:checked ~ .switch-label { + background: #4fb845; + } + } + + #panels-view-as-page { + display: inline-block; + margin-left: 50px; + } +} \ No newline at end of file diff --git a/css/front.css b/css/front.css new file mode 100644 index 000000000..a575e8969 --- /dev/null +++ b/css/front.css @@ -0,0 +1,49 @@ +.panel-grid { + zoom: 1; +} +.panel-grid:before { + content: ''; + display: block; +} +.panel-grid:after { + content: ''; + display: table; + clear: both; +} +.panel-grid-cell { + -ms-box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + min-height: 1em; +} +.panel-grid-cell .panel { + zoom: 1; +} +.panel-grid-cell .panel:before { + content: ''; + display: block; +} +.panel-grid-cell .panel:after { + content: ''; + display: table; + clear: both; +} +.panel-grid-cell .panel.panel-last-child { + margin-bottom: 0; +} +.panel-grid-cell .widget-title { + margin-top: 0; +} +.panel-row-style { + zoom: 1; +} +.panel-row-style:before { + content: ''; + display: block; +} +.panel-row-style:after { + content: ''; + display: table; + clear: both; +} diff --git a/css/front.less b/css/front.less new file mode 100644 index 000000000..4dd71c7e9 --- /dev/null +++ b/css/front.less @@ -0,0 +1,26 @@ +@import "mixins"; + +.panel-grid { + zoom: 1; + .clearfix(); +} +.panel-grid-cell { + .box-sizing(border-box); + min-height: 1em; + + .panel { + .clearfix(); + } + + .panel.panel-last-child { + margin-bottom: 0; + } + + .widget-title { + margin-top: 0; + } +} + +.panel-row-style { + .clearfix(); +} \ No newline at end of file diff --git a/css/icons/readme.txt b/css/icons/readme.txt new file mode 100644 index 000000000..6b7f4b460 --- /dev/null +++ b/css/icons/readme.txt @@ -0,0 +1,5 @@ +Icons are a subset of FontAwesome +Font Awesome by Dave Gandy - http://fontawesome.io + +License: SIL OFL 1.1 +URL: http://scripts.sil.org/OFL \ No newline at end of file diff --git a/css/icons/siteorigin-panels.eot b/css/icons/siteorigin-panels.eot new file mode 100755 index 000000000..abf66f8d1 Binary files /dev/null and b/css/icons/siteorigin-panels.eot differ diff --git a/css/icons/siteorigin-panels.svg b/css/icons/siteorigin-panels.svg new file mode 100755 index 000000000..62435a06f --- /dev/null +++ b/css/icons/siteorigin-panels.svg @@ -0,0 +1,18 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + \ No newline at end of file diff --git a/css/icons/siteorigin-panels.ttf b/css/icons/siteorigin-panels.ttf new file mode 100755 index 000000000..8838c0c38 Binary files /dev/null and b/css/icons/siteorigin-panels.ttf differ diff --git a/css/icons/siteorigin-panels.woff b/css/icons/siteorigin-panels.woff new file mode 100755 index 000000000..3e9a22557 Binary files /dev/null and b/css/icons/siteorigin-panels.woff differ diff --git a/css/images/cell-selected.png b/css/images/cell-selected.png new file mode 100644 index 000000000..3ee49cfbc Binary files /dev/null and b/css/images/cell-selected.png differ diff --git a/css/images/cell-width.png b/css/images/cell-width.png new file mode 100644 index 000000000..b39da1013 Binary files /dev/null and b/css/images/cell-width.png differ diff --git a/css/images/dialog-separator.png b/css/images/dialog-separator.png new file mode 100644 index 000000000..7d4d7611e Binary files /dev/null and b/css/images/dialog-separator.png differ diff --git a/css/images/dialog-separator@2x.png b/css/images/dialog-separator@2x.png new file mode 100644 index 000000000..2251b9f7c Binary files /dev/null and b/css/images/dialog-separator@2x.png differ diff --git a/css/images/dropdown-pointer.png b/css/images/dropdown-pointer.png new file mode 100644 index 000000000..62694aa39 Binary files /dev/null and b/css/images/dropdown-pointer.png differ diff --git a/css/images/tooltip-pointer.png b/css/images/tooltip-pointer.png new file mode 100644 index 000000000..ee3c31746 Binary files /dev/null and b/css/images/tooltip-pointer.png differ diff --git a/css/images/wpspin_light-2x.gif b/css/images/wpspin_light-2x.gif new file mode 100644 index 000000000..fe2d5c0fc Binary files /dev/null and b/css/images/wpspin_light-2x.gif differ diff --git a/css/images/wpspin_light.gif b/css/images/wpspin_light.gif new file mode 100644 index 000000000..7f7172727 Binary files /dev/null and b/css/images/wpspin_light.gif differ diff --git a/css/mixins.less b/css/mixins.less new file mode 100644 index 000000000..5bd6dd754 --- /dev/null +++ b/css/mixins.less @@ -0,0 +1,173 @@ +.gradient(@color: #F5F5F5, @start: #EEE, @stop: #FFF) { + background: @color; + background: -webkit-gradient(linear, left bottom, left top, color-stop(0, @start), color-stop(1, @stop)); + background: -ms-linear-gradient(bottom,@start,@stop); + background: -moz-linear-gradient(center bottom,@start 0%,@stop 100%); + background: -o-linear-gradient(@stop,@start); + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)", @stop, @start)); +} + +.bw-gradient(@color: #F5F5F5, @start: 0, @stop: 255) { + background: @color; + background: -webkit-gradient(linear, left bottom, left top, color-stop(0, rgb(@start,@start,@start)), color-stop(1, rgb(@stop,@stop,@stop))); + background: -ms-linear-gradient(bottom, rgb(@start,@start,@start) 0%, rgb(@stop,@stop,@stop) 100%); + background: -moz-linear-gradient(center bottom, rgb(@start,@start,@start) 0%, rgb(@stop,@stop,@stop) 100%); + background: -o-linear-gradient(rgb(@stop,@stop,@stop), rgb(@start,@start,@start)); + background: linear-gradient(rgb(@stop,@stop,@stop), rgb(@start,@start,@start)); + + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",rgb(@stop,@stop,@stop), rgb(@start,@start,@start))); +} + +.linear-gradient(@color, @gradient) { + background: @color; + background: -moz-linear-gradient(@gradient); + background: -webkit-linear-gradient(@gradient); + background: -o-linear-gradient(@gradient); + background: -ms-linear-gradient(@gradient); + background: linear-gradient(@gradient); +} + +.bordered(@top-color: #EEE, @right-color: #EEE, @bottom-color: #EEE, @left-color: #EEE) { + border-top: solid 1px @top-color; + border-left: solid 1px @left-color; + border-right: solid 1px @right-color; + border-bottom: solid 1px @bottom-color; +} + +.drop-shadow(@x-axis: 0, @y-axis: 1px, @blur: 2px, @alpha: 0.1) { + -webkit-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); + -moz-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); + box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); +} + +.box-shadow(@shadow) { + -webkit-box-shadow: @shadow; + -moz-box-shadow: @shadow; + box-shadow: @shadow; +} + +.rounded(@radius: 2px) { + -webkit-border-radius: @radius; + -moz-border-radius: @radius; + border-radius: @radius; +} + +.border-radius(@topright: 0, @bottomright: 0, @bottomleft: 0, @topleft: 0) { + -webkit-border-top-right-radius: @topright; + -webkit-border-bottom-right-radius: @bottomright; + -webkit-border-bottom-left-radius: @bottomleft; + -webkit-border-top-left-radius: @topleft; + -moz-border-radius-topright: @topright; + -moz-border-radius-bottomright: @bottomright; + -moz-border-radius-bottomleft: @bottomleft; + -moz-border-radius-topleft: @topleft; + border-top-right-radius: @topright; + border-bottom-right-radius: @bottomright; + border-bottom-left-radius: @bottomleft; + border-top-left-radius: @topleft; + .background-clip(padding-box); +} + +.opacity(@opacity: 0.5) { + -moz-opacity: @opacity; + -khtml-opacity: @opacity; + -webkit-opacity: @opacity; + opacity: @opacity; + @opperc: @opacity * 100; + -ms-filter: ~"progid:DXImageTransform.Microsoft.Alpha(opacity=@{opperc})"; + filter: ~"alpha(opacity=@{opperc})"; +} + +.transition-duration(@duration: 0.2s) { + -moz-transition-duration: @duration; + -webkit-transition-duration: @duration; + -o-transition-duration: @duration; + transition-duration: @duration; +} + +.transform(...) { + -webkit-transform: @arguments; + -moz-transform: @arguments; + -o-transform: @arguments; + -ms-transform: @arguments; + transform: @arguments; +} + +.rotation(@deg:5deg) { + .transform(rotate(@deg)); +} + +.scale(@ratio:1.5) { + .transform(scale(@ratio)); +} + +.transition(@duration:0.2s, @on: all, @ease:ease) { + -webkit-transition: @on @duration @ease; + -moz-transition: @on @duration @ease; + -o-transition: @on @duration @ease; + transition: @on @duration @ease; +} + +.inner-shadow(@horizontal:0, @vertical:1px, @blur:2px, @alpha: 0.4) { + -webkit-box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha); + -moz-box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha); + box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha); +} + +.box-sizing(@sizing: border-box) { + -ms-box-sizing: @sizing; + -moz-box-sizing: @sizing; + -webkit-box-sizing: @sizing; + box-sizing: @sizing; +} + +.user-select(@argument: none) { + -webkit-user-select: @argument; + -moz-user-select: @argument; + -ms-user-select: @argument; + user-select: @argument; +} + +.columns(@colwidth: 250px, @colcount: 0, @colgap: 50px, @columnRuleColor: #EEE, @columnRuleStyle: solid, @columnRuleWidth: 1px) { + -moz-column-width: @colwidth; + -moz-column-count: @colcount; + -moz-column-gap: @colgap; + -moz-column-rule-color: @columnRuleColor; + -moz-column-rule-style: @columnRuleStyle; + -moz-column-rule-width: @columnRuleWidth; + -webkit-column-width: @colwidth; + -webkit-column-count: @colcount; + -webkit-column-gap: @colgap; + -webkit-column-rule-color: @columnRuleColor; + -webkit-column-rule-style: @columnRuleStyle; + -webkit-column-rule-width: @columnRuleWidth; + column-width: @colwidth; + column-count: @colcount; + column-gap: @colgap; + column-rule-color: @columnRuleColor; + column-rule-style: @columnRuleStyle; + column-rule-width: @columnRuleWidth; +} + +.translate(@x:0, @y:0) { + .transform(translate(@x, @y)); +} + +.background-clip(@argument: padding-box) { + -moz-background-clip: @argument; + -webkit-background-clip: @argument; + background-clip: @argument; +} + +.clearfix() { + zoom: 1; + &:before { + content: ''; + display: block; + } + &:after { + content: ''; + display: table; + clear: both; + } +} \ No newline at end of file diff --git a/inc/admin-actions.php b/inc/admin-actions.php new file mode 100644 index 000000000..c042fb88d --- /dev/null +++ b/inc/admin-actions.php @@ -0,0 +1,183 @@ + $vals) { + $return[$id] = array( + 'name' => $vals['name'], + 'description' => isset($vals['description']) ? $vals['description'] : __('No description', 'siteorigin-panels') + ); + } + + if( !empty($return) ) { + echo json_encode( $return ); + } + else { + $message = ''; + $message .= __("Your theme doesn't have any prebuilt layouts.", 'siteorigin-panels') . ' '; + $message .= __("You can still clone existing pages though.", 'siteorigin-panels') . ' '; + echo json_encode( array( + 'error_message' => $message, + ) ); + } + + + } + elseif( strpos($_REQUEST['type'], 'clone_') === 0 ) { + // Check that the user can view the given page types + $post_type = str_replace('clone_', '', $_REQUEST['type']); + global $wpdb; + + // Select only the posts with the given post type that also have panels_data + $results = $wpdb->get_results( $wpdb->prepare(" + SELECT ID, post_title, meta.meta_value + FROM {$wpdb->posts} AS posts + JOIN {$wpdb->postmeta} AS meta ON posts.ID = meta.post_id + WHERE + posts.post_type = %s + AND meta.meta_key = 'panels_data' + AND ( posts.post_status = 'publish' OR posts.post_status = 'draft' ) + ORDER BY post_title + LIMIT 200 + ", $post_type) ); + + foreach( $results as $result ) { + $meta_value = unserialize( $result->meta_value ); + if( empty($meta_value['widgets']) ) continue; + + // Create the return array + $return[$result->ID] = array( + 'name' => $result->post_title, + 'description' => __('Clone', 'siteorigin-panels') + ); + } + + if( !empty($return) ) { + echo json_encode( $return ); + } + else { + $type_object = get_post_type_object( $post_type ); + if( empty($type_object->labels->name) ) { + $type_name = ucfirst( $post_type ); + } + else { + $type_name = $type_object->labels->name; + } + + $message = ''; + $message .= sprintf( __("There are no %s with Page Builder content to clone.", 'siteorigin-panels') , $type_name ); + echo json_encode( array( + 'error_message' => $message, + ) ); + } + + } + else { + // Send back an error + } + + exit(); +} +add_action('wp_ajax_so_panels_prebuilt_layouts', 'siteorigin_panels_ajax_prebuilt_layouts'); + +/** + * Ajax handler to get an individual prebuilt layout + */ +function siteorigin_panels_ajax_get_prebuilt_layout(){ + if( empty( $_REQUEST['type'] ) ) exit(); + if( empty( $_REQUEST['lid'] ) ) exit(); + + header('content-type: application/json'); + + if( $_REQUEST['type'] == 'prebuilt' ) { + $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() ); + if( empty( $layouts[$_REQUEST['lid']] ) ) { + // Display an error message + exit(); + } + + $layout = $layouts[$_REQUEST['lid']]; + if( isset($layout['name']) ) unset($layout['name']); + + $layout = apply_filters('siteorigin_panels_prebuilt_layout', $layout); + + echo json_encode( $layout ); + exit(); + } + elseif( current_user_can('edit_post', $_REQUEST['lid']) ) { + $panels_data = get_post_meta( $_REQUEST['lid'], 'panels_data', true ); + $panels_data = apply_filters('siteorigin_panels_data', $panels_data); + echo json_encode( $panels_data ); + exit(); + } +} +add_action('wp_ajax_so_panels_get_prebuilt_layout', 'siteorigin_panels_ajax_get_prebuilt_layout'); + +/** + * Admin ajax handler for loading a single prebuilt layout. + * + * @TODO check if this is still being used + */ +function siteorigin_panels_ajax_action_prebuilt(){ + // Get any layouts that the current user could edit. + $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() ); + + if( empty($_GET['layout']) ) exit(); + if( empty($layouts[$_GET['layout']]) ) exit(); + + header('content-type: application/json'); + + $layout = !empty( $layouts[$_GET['layout']] ) ? $layouts[$_GET['layout']] : array(); + $layout = apply_filters('siteorigin_panels_prebuilt_layout', $layout); + + echo json_encode( $layout ); + exit(); +} +add_action('wp_ajax_so_panels_prebuilt_layout', 'siteorigin_panels_ajax_action_prebuilt'); \ No newline at end of file diff --git a/inc/css.php b/inc/css.php new file mode 100644 index 000000000..fda60f37d --- /dev/null +++ b/inc/css.php @@ -0,0 +1,129 @@ +css = array(); + } + + /** + * Add some general CSS. + * + * @param string $selector + * @param array $attributes + * @param int $resolution The pixel resolution that this applies to + */ + function add_css($selector, $attributes, $resolution = 1920) { + $attribute_string = ''; + foreach( $attributes as $k => $v ) { + $attribute_string[] = $k.':'.$v; + } + $attribute_string = implode(';', $attribute_string); + + // Add everything we need to the CSS selector + if( empty( $this->css[$resolution] ) ) $this->css[$resolution] = array(); + if( empty( $this->css[$resolution][$attribute_string] ) ) $this->css[$resolution][$attribute_string] = array(); + $this->css[$resolution][$attribute_string][] = $selector; + } + + /** + * Add CSS that applies to a row or group of rows. + * + * @param int $li The layout ID. If false, then the CSS applies to all layouts. + * @param int|bool $ri The row index. If false, then the CSS applies to all rows. + * @param string $sub_selector A sub selector if we need one. + * @param array $attributes An array of attributes. + * @param int $resolution The pixel resolution that this applies to + * @param bool $specify_layout Sometimes for CSS specificity, we need to include the layout ID. + */ + function add_row_css($li, $ri = false, $sub_selector = '', $attributes = array(), $resolution = 1920, $specify_layout = false) { + $selector = array(); + + if( $ri === false ) { + // This applies to all rows + $selector[] = '#pl-'.$li; + $selector[] = '.panel-grid'; + } + else { + // This applies to a specific row + if( $specify_layout ) $selector[] = '#pl-'.$li; + $selector[] = '#pg-'.$li.'-'.$ri; + } + + // Add in the sub selector + if( !empty($sub_selector) ) $selector[] = $sub_selector; + + // Add this to the CSS array + $this->add_css( implode(' ', $selector), $attributes, $resolution ); + } + + /** + * @param int $li The layout ID. If false, then the CSS applies to all layouts. + * @param int|bool $ri The row index. If false, then the CSS applies to all rows. + * @param int|bool $ci The cell index. If false, then the CSS applies to all rows. + * @param string $sub_selector A sub selector if we need one. + * @param array $attributes An array of attributes. + * @param int $resolution The pixel resolution that this applies to + * @param bool $specify_layout Sometimes for CSS specificity, we need to include the layout ID. + */ + function add_cell_css( $li, $ri = false, $ci = false, $sub_selector = '', $attributes = array(), $resolution = 1920, $specify_layout = false) { + $selector = array(); + + if( $ri === false && $ci === false ) { + // This applies to all cells in the layout + $selector[] = '#pl-'.$li; + $selector[] = '.panel-grid-cell'; + } + elseif( $ri !== false && $ci === false ) { + // This applies to all cells in a row + if( $specify_layout ) $selector[] = '#pl-'.$li; + $selector[] = '#pg-'.$li.'-'.$ri; + $selector[] = '.panel-grid-cell'; + } + elseif( $ri !== false && $ci !== false ) { + // This applies to a specific cell + if( $specify_layout ) $selector[] = '#pl-'.$li; + $selector[] = '#pgc-'.$li.'-'.$ri.'-'.$ci; + } + + // Add in the sub selector + if( !empty($sub_selector) ) $selector[] = $sub_selector; + + // Add this to the CSS array + $this->add_css( implode(' ', $selector), $attributes, $resolution ); + } + + /** + * Gets the CSS for this particular layout. + */ + function get_css(){ + // Build actual CSS from the array + $css_text = ''; + krsort( $this->css ); + foreach ( $this->css as $res => $def ) { + if ( empty( $def ) ) continue; + + if ( $res < 1920 ) { + $css_text .= '@media (max-width:' . $res . 'px)'; + $css_text .= '{ '; + } + + foreach ( $def as $property => $selector ) { + $selector = array_unique( $selector ); + $css_text .= implode( ' , ', $selector ) . ' { ' . $property . ' } '; + } + + if ( $res < 1920 ) $css_text .= ' } '; + } + + return $css_text; + } +} \ No newline at end of file diff --git a/inc/debug.php b/inc/debug.php new file mode 100644 index 000000000..1119fddeb --- /dev/null +++ b/inc/debug.php @@ -0,0 +1,19 @@ +ID, 'panels_data', true)); + } + echo "\n\n-->"; +} +add_action('siteorigin_panels_metabox_end', 'siteorigin_panels_dump'); \ No newline at end of file diff --git a/inc/default-styles.php b/inc/default-styles.php new file mode 100644 index 000000000..f23b07314 --- /dev/null +++ b/inc/default-styles.php @@ -0,0 +1,359 @@ + __('Row Class', 'siteorigin-panels'), + 'type' => 'text', + 'group' => 'attributes', + 'description' => __('A CSS class', 'siteorigin-panels'), + 'priority' => 5, + ); + + $fields['cell_class'] = array( + 'name' => __('Cell Class', 'siteorigin-panels'), + 'type' => 'text', + 'group' => 'attributes', + 'description' => __('Class added to all cells in this row.', 'siteorigin-panels'), + 'priority' => 6, + ); + + $fields['row_css'] = array( + 'name' => __('CSS Styles', 'siteorigin-panels'), + 'type' => 'code', + 'group' => 'attributes', + 'description' => __('CSS Styles, given as one per row.', 'siteorigin-panels'), + 'priority' => 10, + ); + + // Add the layout fields + + $fields['bottom_margin'] = array( + 'name' => __('Bottom Margin', 'siteorigin-panels'), + 'type' => 'measurement', + 'group' => 'layout', + 'description' => __('Space below the row.', 'siteorigin-panels'), + 'priority' => 5, + ); + + $fields['gutter'] = array( + 'name' => __('Gutter', 'siteorigin-panels'), + 'type' => 'measurement', + 'group' => 'layout', + 'description' => __('Amount of space between columns.', 'siteorigin-panels'), + 'priority' => 6, + ); + + $fields['padding'] = array( + 'name' => __('Padding', 'siteorigin-panels'), + 'type' => 'measurement', + 'group' => 'layout', + 'description' => __('Padding around the entire row.', 'siteorigin-panels'), + 'priority' => 7, + ); + + $fields['row_stretch'] = array( + 'name' => __('Row Layout', 'siteorigin-panels'), + 'type' => 'select', + 'group' => 'layout', + 'options' => array( + '' => __('Standard', 'siteorigin-panels'), + 'full' => __('Full Width', 'siteorigin-panels'), + 'full-stretched' => __('Full Width Stretched', 'siteorigin-panels'), + ), + 'priority' => 10, + ); + + // How lets add the design fields + + $fields['background'] = array( + 'name' => __('Background Color', 'siteorigin-panels'), + 'type' => 'color', + 'group' => 'design', + 'description' => __('Background color of the row.', 'siteorigin-panels'), + 'priority' => 5, + ); + + $fields['background_image_attachment'] = array( + 'name' => __('Background Image', 'siteorigin-panels'), + 'type' => 'image', + 'group' => 'design', + 'description' => __('Background image of the row.', 'siteorigin-panels'), + 'priority' => 6, + ); + + $fields['background_display'] = array( + 'name' => __('Background Image Display', 'siteorigin-panels'), + 'type' => 'select', + 'group' => 'design', + 'options' => array( + 'tile' => __('Tiled Image', 'siteorigin-panels'), + 'cover' => __('Cover', 'siteorigin-panels'), + 'center' => __('Centered, with original size', 'siteorigin-panels'), + ), + 'description' => __('How the background image is displayed.', 'siteorigin-panels'), + 'priority' => 7, + ); + + $fields['border_color'] = array( + 'name' => __('Border Color', 'siteorigin-panels'), + 'type' => 'color', + 'group' => 'design', + 'description' => __('Border color of the row.', 'siteorigin-panels'), + 'priority' => 10, + ); + + return $fields; + } + + static function widget_style_fields($fields) { + $fields['class'] = array( + 'name' => __('Widget Class', 'siteorigin-panels'), + 'type' => 'text', + 'group' => 'attributes', + 'description' => __('A CSS class', 'siteorigin-panels'), + 'priority' => 5, + ); + + $fields['widget_css'] = array( + 'name' => __('CSS Styles', 'siteorigin-panels'), + 'type' => 'code', + 'group' => 'attributes', + 'description' => __('CSS Styles, given as one per row.', 'siteorigin-panels'), + 'priority' => 10, + ); + + $fields['padding'] = array( + 'name' => __('Padding', 'siteorigin-panels'), + 'type' => 'measurement', + 'group' => 'layout', + 'description' => __('Padding around the entire widget.', 'siteorigin-panels'), + 'priority' => 7, + ); + + // How lets add the design fields + + $fields['background'] = array( + 'name' => __('Background Color', 'siteorigin-panels'), + 'type' => 'color', + 'group' => 'design', + 'description' => __('Background color of the widget.', 'siteorigin-panels'), + 'priority' => 5, + ); + + $fields['background_image_attachment'] = array( + 'name' => __('Background Image', 'siteorigin-panels'), + 'type' => 'image', + 'group' => 'design', + 'description' => __('Background image of the widget.', 'siteorigin-panels'), + 'priority' => 6, + ); + + $fields['background_display'] = array( + 'name' => __('Background Image Display', 'siteorigin-panels'), + 'type' => 'select', + 'group' => 'design', + 'options' => array( + 'tile' => __('Tiled Image', 'siteorigin-panels'), + 'cover' => __('Cover', 'siteorigin-panels'), + 'center' => __('Centered, with original size', 'siteorigin-panels'), + ), + 'description' => __('How the background image is displayed.', 'siteorigin-panels'), + 'priority' => 7, + ); + + $fields['border_color'] = array( + 'name' => __('Border Color', 'siteorigin-panels'), + 'type' => 'color', + 'group' => 'design', + 'description' => __('Border color of the widget.', 'siteorigin-panels'), + 'priority' => 10, + ); + + $fields['font_color'] = array( + 'name' => __('Font Color', 'siteorigin-panels'), + 'type' => 'color', + 'group' => 'design', + 'description' => __('Color of text inside this widget.', 'siteorigin-panels'), + 'priority' => 15, + ); + + return $fields; + } + + static function row_style_attributes( $attributes, $args ) { + if( !empty( $args['row_stretch'] ) ) { + $attributes['class'][] = 'siteorigin-panels-stretch'; + $attributes['data-stretch-type'] = $args['row_stretch']; + wp_enqueue_script('siteorigin-panels-front-styles'); + } + + if( !empty( $args['class'] ) ) { + $attributes['class'] = array_merge( $attributes['class'], explode(' ', $args['class']) ); + } + + if( !empty($args['row_css']) ){ + preg_match_all('/(.+?):(.+?);?$/', $args['row_css'], $matches); + + if(!empty($matches[0])){ + for($i = 0; $i < count($matches[0]); $i++) { + $attributes['style'] .= $matches[1][$i] . ':' . $matches[2][$i] . ';'; + } + } + } + + if( !empty( $args['padding'] ) ) { + $attributes['style'] .= 'padding: ' . esc_attr($args['padding']) . ';'; + } + + if( !empty( $args['background'] ) ) { + $attributes['style'] .= 'background-color:' . $args['background']. ';'; + } + + if( !empty( $args['background_image_attachment'] ) ) { + $url = wp_get_attachment_image_src( $args['background_image_attachment'], 'full' ); + + if( !empty($url) ) { + $attributes['style'] .= 'background-image: url(' . $url[0] . ');'; + } + + switch( $args['background_display'] ) { + case 'tile': + $attributes['style'] .= 'background-repeat: repeat;'; + break; + case 'cover': + $attributes['style'] .= 'background-size: cover;'; + break; + case 'center': + $attributes['style'] .= 'background-position: center center; background-repeat: no-repeat;'; + break; + } + } + + if( !empty( $args['border_color'] ) ) { + $attributes['style'] .= 'border: 1px solid ' . $args['border_color']. ';'; + } + + return $attributes; + } + + static function cell_style_attributes( $attributes, $row_args ) { + if( !empty( $row_args['cell_class'] ) ) { + if( empty($attributes['class']) ) $attributes['class'] = array(); + $attributes['class'] = array_merge( $attributes['class'], explode(' ', $row_args['cell_class']) ); + } + + return $attributes; + } + + static function widget_style_attributes( $attributes, $args ) { + if( !empty( $args['class'] ) ) { + if( empty($attributes['class']) ) $attributes['class'] = array(); + $attributes['class'] = array_merge( $attributes['class'], explode(' ', $args['class']) ); + } + + if( !empty($args['widget_css']) ){ + preg_match_all('/(.+?):(.+?);?$/', $args['widget_css'], $matches); + + if(!empty($matches[0])){ + for($i = 0; $i < count($matches[0]); $i++) { + $attributes['style'] .= $matches[1][$i] . ':' . $matches[2][$i] . ';'; + } + } + } + + if( !empty( $args['padding'] ) ) { + $attributes['style'] .= 'padding: ' . esc_attr($args['padding']) . ';'; + } + + if( !empty( $args['background'] ) ) { + $attributes['style'] .= 'background-color:' . $args['background']. ';'; + } + + if( !empty( $args['background_image_attachment'] ) ) { + $url = wp_get_attachment_image_src( $args['background_image_attachment'], 'full' ); + + if( !empty($url) ) { + $attributes['style'] .= 'background-image: url(' . $url[0] . ');'; + } + + switch( $args['background_display'] ) { + case 'tile': + $attributes['style'] .= 'background-repeat: repeat;'; + break; + case 'cover': + $attributes['style'] .= 'background-size: cover;'; + break; + case 'center': + $attributes['style'] .= 'background-position: center center; background-repeat: no-repeat;'; + break; + } + } + + if( !empty( $args['border_color'] ) ) { + $attributes['style'] .= 'border: 1px solid ' . $args['border_color']. ';'; + } + + if( !empty( $args['font_color'] ) ) { + $attributes['style'] .= 'color: ' . $args['font_color']. ';'; + } + + return $attributes; + } + + static function filter_css_object( $css, $panels_data, $post_id ) { + return $css; + } + + static function filter_row_bottom_margin( $margin, $grid ){ + if( !empty($grid['style']['bottom_margin']) ) { + $margin = $grid['style']['bottom_margin']; + } + return $margin; + } + + static function filter_row_gutter( $gutter, $grid ) { + if( !empty($grid['style']['gutter']) ) { + $gutter = $grid['style']['gutter']; + } + + return $gutter; + } + +} + +SiteOrigin_Panels_Default_Styling::init(); \ No newline at end of file diff --git a/inc/live-editor.php b/inc/live-editor.php new file mode 100644 index 000000000..91bbfb901 --- /dev/null +++ b/inc/live-editor.php @@ -0,0 +1,23 @@ + false, + 'home-page-default' => false, + 'home-template' => 'home-panels.php', + 'post-types' => array('page', 'post'), + + 'bundled-widgets' => get_option( 'siteorigin_panels_is_using_bundled', false ), + 'responsive' => true, + 'mobile-width' => 780, + + 'margin-bottom' => 30, + 'margin-sides' => 30, + 'affiliate-id' => apply_filters( 'siteorigin_panels_affiliate_id', false ), + 'copy-content' => true, + 'animations' => true, + 'inline-css' => true, + ) ) ); + $settings = wp_parse_args( $current_settings, $settings); + + // Filter these settings + $settings = apply_filters('siteorigin_panels_settings', $settings); + } + + if( !empty( $key ) ) return isset( $settings[$key] ) ? $settings[$key] : null; + return $settings; +} + +/** + * Add the options page + */ +function siteorigin_panels_options_admin_menu() { + add_options_page( __('SiteOrigin Page Builder', 'siteorigin-panels'), __('SiteOrigin Page Builder', 'siteorigin-panels'), 'manage_options', 'siteorigin_panels', 'siteorigin_panels_options_page' ); +} +add_action( 'admin_menu', 'siteorigin_panels_options_admin_menu' ); + +/** + * Display the admin page. + */ +function siteorigin_panels_options_page(){ + include plugin_dir_path(SITEORIGIN_PANELS_BASE_FILE) . '/tpl/options.php'; +} + +/** + * Display the field for selecting the post types + */ +function siteorigin_panels_options_field_post_types( $panels_post_types ){ + $all_post_types = array_values( array_merge( array( 'page' => 'page', 'post' => 'post' ), get_post_types( array( '_builtin' => false ) ) ) ); + + // These are post types we know we don't want to show + $all_post_types = array_diff($all_post_types, array( + // Meta Slider + 'ml-slider' + ) ); + + foreach($all_post_types as $type){ + $info = get_post_type_object($type); + if(empty($info->labels->name)) continue; + $checked = in_array( + $type, + $panels_post_types + ); + + ?> +
+

+ + + + +

+ + + $v){ + switch($f){ + case 'inline-css' : + case 'responsive' : + case 'copy-content' : + case 'animations' : + case 'bundled-widgets' : + $settings[$f] = !empty($settings[$f]); + break; + case 'margin-bottom' : + case 'margin-sides' : + case 'mobile-width' : + $settings[$f] = intval($settings[$f]); + break; + } + } + + // Checkbox settings + $settings['responsive'] = !empty($settings['responsive']); + $settings['copy-content'] = !empty($settings['copy-content']); + $settings['animations'] = !empty($settings['animations']); + $settings['inline-css'] = !empty($settings['inline-css']); + $settings['bundled-widgets'] = !empty($settings['bundled-widgets']); + + // Post type settings + $settings['post-types'] = $post_types; + + update_option('siteorigin_panels_settings', $settings); + + global $siteorigin_panels_settings; + $siteorigin_panels_settings = false; +} +add_action('load-settings_page_siteorigin_panels', 'siteorigin_panels_save_options'); \ No newline at end of file diff --git a/inc/plugin-activation.php b/inc/plugin-activation.php new file mode 100644 index 000000000..d5add76dc --- /dev/null +++ b/inc/plugin-activation.php @@ -0,0 +1,131 @@ + +
+ +
+ 'siteorigin_panels_plugin_activation', + 'plugin' => $plugin['slug'], + 'plugin_name' => $plugin['name'], + 'plugin_source' => $plugin['source'], + 'siteorigin-pa-install' => 'install-plugin', + ), + admin_url( 'themes.php' ) + ), + 'siteorigin-pa-install' + ); + $method = ''; // Leave blank so WP_Filesystem can populate it as necessary + $fields = array( sanitize_key( 'siteorigin-pa-install' ) ); // Extra fields to pass to WP_Filesystem + + if ( false === ( $creds = request_filesystem_credentials( $url, $method, false, false, $fields ) ) ) + return true; + + if ( ! WP_Filesystem( $creds ) ) { + request_filesystem_credentials( $url, $method, true, false, $fields ); // Setup WP_Filesystem + return true; + } + + require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; // Need for plugins_api + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; // Need for upgrade classes + + /** Prep variables for Plugin_Installer_Skin class */ + $title = sprintf( __('Installing %s', 'siteorigin-panels'), $plugin['name'] ); + $url = add_query_arg( array( 'action' => 'install-plugin', 'plugin' => $plugin['slug'] ), 'update.php' ); + if ( isset( $_GET['from'] ) ) + $url .= add_query_arg( 'from', urlencode( stripslashes( $_GET['from'] ) ), $url ); + + $nonce = 'install-plugin_' . $plugin['slug']; + + // Find the source of the plugin + $source = !empty( $plugin['source'] ) ? $plugin['source'] : 'http://downloads.wordpress.org/plugin/'.urlencode($plugin['slug']).'.zip'; + + /** Create a new instance of Plugin_Upgrader */ + $upgrader = new Plugin_Upgrader( $skin = new Plugin_Installer_Skin( compact( 'type', 'title', 'url', 'nonce', 'plugin', 'api' ) ) ); + + /** Perform the action and install the plugin from the $source urldecode() */ + $upgrader->install( $source ); + + /** Flush plugins cache so we can make sure that the installed plugins list is always up to date */ + wp_cache_flush(); + } +} + +function siteorigin_panels_plugin_activation_install_url($plugin, $plugin_name, $source = false){ + // This is to prevent the issue where this URL is called from outside the admin + if( !is_admin() || !function_exists('get_plugins') ) return false; + + $plugins = get_plugins(); + $plugins = array_keys($plugins); + + $installed = false; + foreach($plugins as $plugin_path){ + if(strpos($plugin_path, $plugin.'/') === 0) { + $installed = true; + break; + } + } + + if($installed && !is_plugin_active($plugin)){ + return wp_nonce_url( self_admin_url('plugins.php?action=activate&plugin='.$plugin_path), 'activate-plugin_'.$plugin_path); + } + elseif($installed && is_plugin_active($plugin)){ + return '#'; + } + else{ + return wp_nonce_url( + add_query_arg( + array( + 'page' => 'siteorigin_panels_plugin_activation', + 'plugin' => $plugin, + 'plugin_name' => $plugin_name, + 'plugin_source' => !empty($source) ? urlencode($source) : false, + 'siteorigin-pa-install' => 'install-plugin', + ), + admin_url( 'plugins.php' ) + ), + 'siteorigin-pa-install' + ); + } +} \ No newline at end of file diff --git a/inc/revisions.php b/inc/revisions.php new file mode 100644 index 000000000..83d82499c --- /dev/null +++ b/inc/revisions.php @@ -0,0 +1,72 @@ +base == 'post') return $fields; + + $fields['panels_data_field'] = __('Page Builder Content', 'siteorigin-panels'); + return $fields; + +} +add_filter( '_wp_post_revision_fields', 'siteorigin_panels_revisions_fields' ); + +/** + * Display the Page Builder content for the revision. + * + * @param $value + * @param $field + * @param $revision + * @return string + */ +function siteorigin_panels_revisions_field( $value, $field, $revision ) { + $parent_id = wp_is_post_revision( $revision->ID ); + $panels_data = get_metadata('post', $revision->ID, 'panels_data', true); + + if(empty($panels_data)) return ''; + return siteorigin_panels_render($parent_id, false, $panels_data); +} +add_filter( '_wp_post_revision_field_panels_data_field', 'siteorigin_panels_revisions_field', 10, 3 ); + diff --git a/inc/styles.php b/inc/styles.php new file mode 100644 index 000000000..ee681a4fc --- /dev/null +++ b/inc/styles.php @@ -0,0 +1,339 @@ +' . __('Row Styles', 'siteorigin-panels') . '', '', $current); + break; + + case 'widget': + siteorigin_panels_render_styles_fields('widget', '

' . __('Widget Styles', 'siteorigin-panels') . '

', '', $current); + } + + exit(); +} +add_action('wp_ajax_so_panels_style_form', 'siteorigin_panels_ajax_action_style_form'); + +/** + * Render all the style fields + * + * @param $section + * @param string $before + * @param string $after + * @param array $current + */ +function siteorigin_panels_render_styles_fields( $section, $before = '', $after = '', $current = array() ){ + $fields = apply_filters('siteorigin_panels_' . $section . '_style_fields', array() ); + if( empty($fields) ) return false; + + $groups = array( + 'attributes' => array( + 'name' => __('Attributes', 'siteorigin-panels'), + 'priority' => 5 + ), + 'layout' => array( + 'name' => __('Layout', 'siteorigin-panels'), + 'priority' => 10 + ), + 'design' => array( + 'name' => __('Design', 'siteorigin-panels'), + 'priority' => 15 + ), + ); + + // Check if we need a default group + foreach($fields as $field_id => $field) { + if( empty($field['group']) ) { + if( empty($groups['theme']) ) { + $groups['theme'] = array( + 'name' => __('Theme', 'siteorigin-panels'), + 'priority' => 10 + ); + } + $fields[$field_id]['group'] = 'theme'; + } + } + $groups = apply_filters('siteorigin_panels_' . $section . '_style_groups', $groups ); + + // Sort the style fields and groups by priority + uasort( $fields, 'siteorigin_panels_styles_sort_fields' ); + uasort( $groups, 'siteorigin_panels_styles_sort_fields' ); + + echo $before; + + $group_counts = array(); + foreach( $fields as $field_id => $field ) { + if(empty($group_counts[$field['group']])) $group_counts[$field['group']] = 0; + $group_counts[$field['group']]++; + } + + foreach( $groups as $group_id => $group ) { + + if( empty( $group_counts[$group_id] ) ) continue; + + ?> +
+
+

+
+ +
+ '; + switch($field['type']) { + case 'measurement' : + ?> + + + + + + +
+
> +
+ +
+ +
+ +
+ + + + + + '; + + if( !empty($field['description']) ) { + ?>

( isset( $b['priority'] ) ? $b['priority'] : 10 ) ) ? 1 : -1; +} + +/** + * Sanitize the style fields in panels_data + * + * @param $panels_data + * + * @return mixed + */ +function siteorigin_panels_styles_sanitize_all($panels_data){ + + if( !empty($panels_data['widgets']) ) { + // Sanitize the widgets + for ( $i = 0; $i < count( $panels_data['widgets'] ); $i ++ ) { + if ( empty( $panels_data['widgets'][ $i ]['panels_info']['style'] ) ) { + continue; + } + $panels_data['widgets'][ $i ]['panels_info']['style'] = siteorigin_panels_sanitize_style_fields( 'widget', $panels_data['widgets'][ $i ]['panels_info']['style'] ); + } + } + + if( !empty($panels_data['grids']) ) { + // The rows + for ( $i = 0; $i < count( $panels_data['grids'] ); $i ++ ) { + if ( empty( $panels_data['grids'][ $i ]['style'] ) ) { + continue; + } + $panels_data['grids'][ $i ]['style'] = siteorigin_panels_sanitize_style_fields( 'row', $panels_data['grids'][ $i ]['style'] ); + } + } + + if( !empty($panels_data['grid_cells']) ) { + // And finally, the cells + for ( $i = 0; $i < count( $panels_data['grid_cells'] ); $i ++ ) { + if ( empty( $panels_data['grid_cells'][ $i ]['style'] ) ) { + continue; + } + $panels_data['grid_cells'][ $i ]['style'] = siteorigin_panels_sanitize_style_fields( 'cell', $panels_data['grid_cells'][ $i ]['style'] ); + } + } + + return $panels_data; +} + +/** + * Sanitize style fields. + * + * @param $section + * @param $styles + * + * @return Sanitized styles + */ +function siteorigin_panels_sanitize_style_fields($section, $styles){ + static $fields_cache = array(); + + // Use the filter to get the fields for this section. + if( empty($fields_cache[$section]) ) { + $fields_cache[$section] = apply_filters('siteorigin_panels_' . $section . '_style_fields', array() ); + } + $fields = $fields_cache[$section]; + + $return = array(); + foreach($fields as $k => $field) { + + // Ignore this if we don't even have a value for the style + if( !isset($styles[$k]) || $styles[$k] == '' ) continue; + + switch($field['type']) { + case 'color' : + $color = $styles[$k]; + if ( preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) $return[$k] = $color; + else $return[$k] = ''; + break; + case 'image' : + $return[$k] = !empty( $styles[$k] ) ? intval( $styles[$k] ) : false; + break; + case 'url' : + $return[$k] = esc_url_raw( $styles[$k] ); + break; + case 'checkbox' : + $return[$k] = !empty( $styles[$k] ); + break; + case 'measurement' : + preg_match('/([0-9\.,]+)(.*)/', $styles[$k], $match); + if( !empty($match[0]) && $match[0] != '' && !empty($match[2]) ) $return[$k] = $styles[$k]; + else $return[$k] = ''; + break; + case 'select' : + if( !empty( $styles[$k] ) && in_array( $styles[$k], array_keys( $field['options'] ) ) ) { + $return[$k] = $styles[$k]; + } + break; + default: + // Just pass the value through. + $return[$k] = $styles[$k]; + break; + + } + } + + return $return; +} + +/** + * Convert the single string attribute of the grid style into an array. + * + * @param $panels_data + * @return mixed + */ +function siteorigin_panels_style_update_data($panels_data){ + if(empty($panels_data['grids'])) return $panels_data; + + for($i = 0; $i < count($panels_data['grids']); $i++) { + + if( isset($panels_data['grids'][$i]['style']) && is_string($panels_data['grids'][$i]['style']) ){ + $panels_data['grids'][$i]['style'] = array('class' => $panels_data['grids'][$i]['style']); + } + + } + return $panels_data; +} +add_filter('siteorigin_panels_data', 'siteorigin_panels_style_update_data'); +add_filter('siteorigin_panels_prebuilt_layout', 'siteorigin_panels_style_update_data'); \ No newline at end of file diff --git a/inc/widgets-bundle.php b/inc/widgets-bundle.php new file mode 100644 index 000000000..9ebe807f5 --- /dev/null +++ b/inc/widgets-bundle.php @@ -0,0 +1,64 @@ + array( + 'class' => 'SiteOrigin_Widget_Button_Widget', + 'title' => __('SiteOrigin Button', 'siteorigin-panels'), + 'description' => __('A simple button', 'siteorigin-panels'), + 'installed' => false, + 'plugin' => array( + 'name' => __('SiteOrigin Widgets Bundle', 'siteorigin-panels'), + 'slug' => 'so-widgets-bundle' + ), + 'groups' => array('so-widgets-bundle'), + ), + + 'SiteOrigin_Widget_Image_Widget' => array( + 'class' => 'SiteOrigin_Widget_Image_Widget', + 'title' => __('SiteOrigin Image', 'siteorigin-panels'), + 'description' => __('Choose images from your media library.', 'siteorigin-panels'), + 'installed' => false, + 'plugin' => array( + 'name' => __('SiteOrigin Widgets Bundle', 'siteorigin-panels'), + 'slug' => 'so-widgets-bundle' + ), + 'groups' => array('so-widgets-bundle'), + ), + + 'SiteOrigin_Widget_Slider_Widget' => array( + 'class' => 'SiteOrigin_Widget_Slider_Widget', + 'title' => __('SiteOrigin Slider', 'siteorigin-panels'), + 'description' => __('A basic slider widget.', 'siteorigin-panels'), + 'installed' => false, + 'plugin' => array( + 'name' => __('SiteOrigin Widgets Bundle', 'siteorigin-panels'), + 'slug' => 'so-widgets-bundle' + ), + 'groups' => array('so-widgets-bundle'), + ), + + 'SiteOrigin_Widget_Features_Widget' => array( + 'class' => 'SiteOrigin_Widget_Features_Widget', + 'title' => __('SiteOrigin Features', 'siteorigin-panels'), + 'description' => __('Display site features as a collection of icons.', 'siteorigin-panels'), + 'installed' => false, + 'plugin' => array( + 'name' => __('SiteOrigin Widgets Bundle', 'siteorigin-panels'), + 'slug' => 'so-widgets-bundle' + ), + 'groups' => array('so-widgets-bundle'), + ), + + 'SiteOrigin_Widget_PostCarousel_Widget' => array( + 'class' => 'SiteOrigin_Widget_PostCarousel_Widget', + 'title' => __('SiteOrigin Post Carousel', 'siteorigin-panels'), + 'description' => __('Display your posts as a carousel.', 'siteorigin-panels'), + 'installed' => false, + 'plugin' => array( + 'name' => __('SiteOrigin Widgets Bundle', 'siteorigin-panels'), + 'slug' => 'so-widgets-bundle' + ), + 'groups' => array('so-widgets-bundle'), + ), +); \ No newline at end of file diff --git a/inc/widgets.php b/inc/widgets.php new file mode 100644 index 000000000..a60adb003 --- /dev/null +++ b/inc/widgets.php @@ -0,0 +1,152 @@ + 'WP_Widget_Black_Studio_TinyMCE', + 'title' => __('Visual Editor', 'siteorigin-panels'), + 'description' => __('Arbitrary text or HTML with visual editor', 'siteorigin-panels'), + 'installed' => false, + 'plugin' => array( + 'name' => __('Black Studio TinyMCE', 'siteorigin-panels'), + 'slug' => 'black-studio-tinymce-widget' + ), + 'groups' => array('recommended'), + 'icon' => 'dashicons dashicons-edit', + ); + } + else { + $widgets['WP_Widget_Black_Studio_TinyMCE']['groups'] = array('recommended'); + $widgets['WP_Widget_Black_Studio_TinyMCE']['icon'] = 'dashicons dashicons-edit'; + } + + // Add in all the widgets bundle widgets + $widgets = wp_parse_args( + $widgets, + include plugin_dir_path(__FILE__).'/widgets-bundle.php' + ); + + foreach($widgets as $class => $data) { + if( strpos( $class, 'SiteOrigin_Panels_Widgets_' ) === 0 || strpos( $class, 'SiteOrigin_Panels_Widget_' ) === 0 ) { + $widgets[$class]['groups'] = array('panels'); + } + } + + $widgets['SiteOrigin_Panels_Widgets_Layout']['icon'] = 'dashicons dashicons-analytics'; + + $wordpress_widgets = array( + 'WP_Widget_Pages', + 'WP_Widget_Links', + 'WP_Widget_Search', + 'WP_Widget_Archives', + 'WP_Widget_Meta', + 'WP_Widget_Calendar', + 'WP_Widget_Text', + 'WP_Widget_Categories', + 'WP_Widget_Recent_Posts', + 'WP_Widget_Recent_Comments', + 'WP_Widget_RSS', + 'WP_Widget_Tag_Cloud', + 'WP_Nav_Menu_Widget', + ); + + foreach($wordpress_widgets as $wordpress_widget) { + if( isset( $widgets[$wordpress_widget] ) ) { + $widgets[$wordpress_widget]['groups'] = array('wordpress'); + $widgets[$wordpress_widget]['icon'] = 'dashicons dashicons-wordpress'; + } + } + + return $widgets; + +} +add_filter('siteorigin_panels_widgets', 'siteorigin_panels_add_recommended_widgets'); + +/** + * Add tabs to the widget dialog + * + * @param $tabs + * + * @return array + */ +function siteorigin_panels_add_widgets_dialog_tabs($tabs){ + + $tabs[] = array( + 'title' => __('Widgets Bundle', 'siteorigin-panels'), + 'filter' => array( + 'groups' => array('so-widgets-bundle') + ) + ); + + $tabs[] = array( + 'title' => __('Page Builder Widgets', 'siteorigin-panels'), + 'filter' => array( + 'groups' => array('panels') + ) + ); + + $tabs[] = array( + 'title' => __('WordPress Widgets', 'siteorigin-panels'), + 'filter' => array( + 'groups' => array('wordpress') + ) + ); + + $tabs[] = array( + 'title' => __('Recommended Widgets', 'siteorigin-panels'), + 'filter' => array( + 'groups' => array('recommended') + ) + ); + + return $tabs; +} +add_filter('siteorigin_panels_widget_dialog_tabs', 'siteorigin_panels_add_widgets_dialog_tabs', 20); + +/** + * This will try restore bundled widgets. + * + * @param $object + * @param $widget + * + * @return \WP_Widget_Text + */ +function siteorigin_panels_restore_bundled_widget($object, $widget){ + + // We can skip this if there's already an object + if( !empty($object) ) return $object; + + if( strpos($widget, 'SiteOrigin_Panels_Widget_') === 0 || strpos($widget, 'SiteOrigin_Panels_Widgets_') === 0 ) { + + if( !class_exists('SiteOrigin_Panels_Widget') ) { + // Initialize the bundled widgets + include plugin_dir_path( SITEORIGIN_PANELS_BASE_FILE ) . '/widgets/widgets.php'; + + // Initialize all the widgets + origin_widgets_init(); + siteorigin_panels_widgets_init(); + + // Set this to change the default behaviour to using the bundled widgets, wont override user settings though + add_option('siteorigin_panels_is_using_bundled', true); + } + + if( class_exists($widget) ) { + $object = new $widget(); + } + } + elseif(!is_admin() && $widget == 'WP_Widget_Black_Studio_TinyMCE') { + // If the visual editor is missing, we can replace it with the text widget for now + $object = new WP_Widget_Text(); + } + + return $object; +} +add_filter('siteorigin_panels_widget_object', 'siteorigin_panels_restore_bundled_widget', 10, 2); \ No newline at end of file diff --git a/js/siteorigin-panels-history.js b/js/siteorigin-panels-history.js new file mode 100644 index 000000000..c13d56f44 --- /dev/null +++ b/js/siteorigin-panels-history.js @@ -0,0 +1,301 @@ +/** + * History browser for Page Builder. + * + * @copyright Greg Priday 2014 - + * @license GPL 3.0 http://www.gnu.org/licenses/gpl.html + */ + +( function( $, _, panelsOptions ){ + + var panels = window.siteoriginPanels; + + /** + * + */ + panels.model.historyEntry = Backbone.Model.extend( { + defaults: { + text : '', + data : '', + time: null, + count: 1 + } + } ); + + /** + * + */ + panels.collection.historyEntries = Backbone.Collection.extend( { + model: panels.model.historyEntry, + + /** + * The builder model + */ + builder: null, + + /** + * The maximum number of items in the history + */ + maxSize: 12, + + initialize: function(){ + this.on( 'add', this.onAddEntry, this ); + }, + + /** + * Add an entry to the collection. + * + * @param text The text that defines the action taken to get to this + * @param data + */ + addEntry: function(text, data) { + + if(typeof data == 'undefined' || data == null) { + data = this.builder.getPanelsData(); + } + + var entry = new panels.model.historyEntry( { + text: text, + data: JSON.stringify( data ), + time: parseInt( new Date().getTime() / 1000 ), + collection: this + } ); + + this.add( entry ); + }, + + /** + * Resize the collection so it's not bigger than this.maxSize + */ + onAddEntry: function(entry){ + + if(this.models.length > 1) { + var lastEntry = this.at(this.models.length - 2); + + if( + ( entry.get('text') == lastEntry.get('text') && entry.get('time') - lastEntry.get('time') < 15 ) + || ( entry.get('data') == lastEntry.get('data') ) + ) { + // If both entries have the same text and are within 20 seconds of each other, or have the same data, then remove most recent + this.remove( entry ); + lastEntry.set( 'count', lastEntry.get('count') + 1 ); + } + } + + // Make sure that there are not to many entries in this collection + while( this.models.length > this.maxSize ) { + this.shift(); + } + } + } ); + + /** + * The history manager is + */ + panels.dialog.history = panels.view.dialog.extend( { + + historyEntryTemplate: _.template( $('#siteorigin-panels-dialog-history-entry').html() ), + + entries: {}, + currentEntry: null, + revertEntry: null, + selectedEntry: null, + + dialogClass: 'so-panels-dialog-history', + + events: { + 'click .so-close': 'closeDialog', + 'click .so-restore': 'restoreSelectedEntry' + }, + + initializeDialog: function(){ + this.entries = new panels.collection.historyEntries(); + + this.on('open_dialog', this.setCurrentEntry, this); + this.on('open_dialog', this.renderHistoryEntries, this); + }, + + render: function(){ + // Render the dialog and attach it to the builder interface + this.renderDialog( this.parseDialogContent( $('#siteorigin-panels-dialog-history').html(), {} ) ); + + this.$('iframe.siteorigin-panels-history-iframe').load(function(){ + $(this).show(); + }); + }, + + /** + * Set the origianl entry. This should be set when creating the dialog. + * + * @param {panels.model.builder} builder + */ + setRevertEntry: function(builder){ + this.revertEntry = new panels.model.historyEntry( { + data: JSON.stringify( builder.getPanelsData() ), + time: parseInt( new Date().getTime() / 1000 ) + } ); + }, + + /** + * This is triggered when the dialog is opened. + */ + setCurrentEntry: function(){ + this.currentEntry = new panels.model.historyEntry( { + data: JSON.stringify( this.builder.model.getPanelsData() ), + time: parseInt( new Date().getTime() / 1000 ) + } ); + + this.selectedEntry = this.currentEntry; + this.previewEntry( this.currentEntry ); + this.$('.so-buttons .so-restore').addClass('disabled'); + }, + + /** + * Render the history entries + */ + renderHistoryEntries: function(){ + var c = this.$('.history-entries'); + + // Set up an interval that will display the time since every 10 seconds + var thisView = this; + + c.empty(); + + if( this.currentEntry.get('data') != this.revertEntry.get('data') || this.entries.models.length > 0 ) { + $(this.historyEntryTemplate({title: panelsOptions.loc.history['revert'], count: 1})) + .data('historyEntry', this.revertEntry) + .prependTo(c); + } + + // Now load all the entries in this.entries + this.entries.each(function(entry){ + + var html = thisView.historyEntryTemplate( { + title: panelsOptions.loc.history[ entry.get('text') ], + count: entry.get('count') + } ); + + $( html ) + .data('historyEntry', entry) + .prependTo(c); + }); + + + $(this.historyEntryTemplate({title: panelsOptions.loc.history['current'], count: 1})) + .data('historyEntry', this.currentEntry) + .addClass('so-selected') + .prependTo(c); + + // Handle loading and selecting + c.find('.history-entry').click(function(){ + var $$ = $(this); + c.find('.history-entry').not($$).removeClass('so-selected'); + $$.addClass('so-selected'); + + var entry = $$.data('historyEntry'); + + thisView.selectedEntry = entry; + + if( thisView.selectedEntry.cid != thisView.currentEntry.cid ) { + thisView.$('.so-buttons .so-restore').removeClass('disabled'); + } + else { + thisView.$('.so-buttons .so-restore').addClass('disabled'); + } + + thisView.previewEntry( entry ); + }); + + this.updateEntryTimes(); + }, + + /** + * Preview an entry + * + * @param entry + */ + previewEntry: function(entry){ + this.$('iframe.siteorigin-panels-history-iframe').hide(); + this.$('form.history-form input[name="siteorigin_panels_data"]').val( entry.get('data') ); + this.$('form.history-form').submit(); + }, + + /** + * Restore the current entry + */ + restoreSelectedEntry: function(){ + + if( this.$('.so-buttons .so-restore').hasClass('disabled') ) return false; + + if( this.currentEntry.get('data') == this.selectedEntry.get('data') ) { + this.closeDialog(); + return; + } + + // Add an entry for this restore event + if( this.selectedEntry.get('text') != 'restore' ) { + this.entries.addEntry( 'restore', this.builder.model.getPanelsData() ); + } + + this.builder.model.loadPanelsData( JSON.parse( this.selectedEntry.get('data') ) ); + + this.closeDialog(); + + return false; + }, + + /** + * Update the entry times for the list of entries down the side + */ + updateEntryTimes: function(){ + var thisView = this; + + this.$('.history-entries .history-entry').each(function(){ + var $$ = $(this); + + var time = $$.find('.timesince'); + var entry = $$.data('historyEntry'); + + time.html( thisView.timeSince( entry.get('time') ) ); + }); + }, + + /** + * Gets the time since as a nice string. + * + * @param date + */ + timeSince: function(time){ + var diff = parseInt( new Date().getTime() / 1000 ) - time; + + var parts = []; + var interval; + + // There are 3600 seconds in an hour + if( diff > 3600 ) { + interval = Math.floor( diff / 3600 ); + if(interval == 1) parts.push(panelsOptions.loc.time.hour.replace('%d', interval )); + else parts.push(panelsOptions.loc.time.hours.replace('%d', interval )); + diff -= interval * 3600 + } + + // There are 60 seconds in a minute + if( diff > 60 ) { + interval = Math.floor( diff / 60 ); + if(interval == 1) parts.push(panelsOptions.loc.time.minute.replace('%d', interval )); + else parts.push(panelsOptions.loc.time.minutes.replace('%d', interval )); + diff -= interval * 60 + } + + if( diff > 0 ) { + if(diff == 1) parts.push(panelsOptions.loc.time.second.replace('%d', diff )); + else parts.push(panelsOptions.loc.time.seconds.replace('%d', diff )); + } + + // Return the amount of time ago + return parts.length == 0 ? panelsOptions.loc.time.now : panelsOptions.loc.time.ago.replace('%s', parts.slice(0,2).join(', ') ); + + } + + } ); + +} )( jQuery, _, soPanelsOptions ); \ No newline at end of file diff --git a/js/siteorigin-panels-live-editor.js b/js/siteorigin-panels-live-editor.js new file mode 100644 index 000000000..d509d34f5 --- /dev/null +++ b/js/siteorigin-panels-live-editor.js @@ -0,0 +1,308 @@ +/** + * Handles the live editor interface in Page Builder + * + * @copyright Greg Priday 2014 - + * @license GPL 3.0 http://www.gnu.org/licenses/gpl.html + */ + +( function( $, _, panelsOptions ){ + + var panels = window.siteoriginPanels; + + /** + * Live editor handles + */ + panels.view.liveEditor = Backbone.View.extend( { + template: _.template( $('#siteorigin-panels-live-editor').html() ), + + sectionTemplate: _.template( $('#siteorigin-panels-live-editor-sidebar-section').html() ), + + postId: false, + bodyScrollTop : null, + displayed: false, + + events: { + 'click .live-editor-close': 'close' + }, + frameScrollTop: 0, + + initialize: function(){ + }, + + /** + * Render the live editor + */ + render: function(){ + this.setElement( this.template() ); + this.$el.html( this.template() ); + + var thisView = this; + + // Prevent clicks inside the iframe + this.$('iframe#siteorigin-panels-live-editor-iframe') + .load(function(){ + $(this).show(); + + var ifc = $(this).contents(); + + // Lets find all the first level grids. This is to account for the Page Builder layout widget. + ifc.find('.panel-grid .panel-grid-cell .panel.widget') + .filter(function(){ + // Filter to only include non nested + return $(this).parents('.widget_siteorigin-panels-builder').length == 0; + }) + .each(function(i, el){ + var $$ = $(el); + var widgetEdit = thisView.$('.page-widgets .so-widget').eq(i); + var overlay; + + $$ + .css({ + 'cursor' : 'pointer' + }) + .mouseenter(function(){ + widgetEdit.addClass('so-hovered'); + overlay = thisView.createPreviewOverlay( $(this) ) + }) + .mouseleave( function(){ + widgetEdit.removeClass('so-hovered'); + overlay.fadeOut('fast', function(){ $(this).remove() }); + } ) + .click(function(e){ + e.preventDefault(); + // When we click a widget, send that click to the form + widgetEdit.click(); + }); + }); + + // Prevent default clicks + ifc.find( "a").css({'pointer-events' : 'none'}).click(function(e){ + return false; + }); + + }); + }, + + /** + * Attach the live editor to the document + */ + attach: function(){ + this.$el.appendTo('body'); + }, + + setPostId: function(postId){ + this.postId = postId; + }, + + /** + * Display the live editor + */ + open: function(){ + if( this.$el.html() == '' ) this.render(); + if( this.$el.closest('body').length == 0 ) this.attach(); + + // Refresh the preview display + this.refreshWidgets(); + this.$el.show(); + + // Refresh the preview after we show the editor + this.refreshPreview(); + + // Disable page scrolling + this.bodyScrollTop = $('body').scrollTop(); + $('body').css( {overflow:'hidden'} ); + + this.displayed = true; + }, + + close: function(){ + this.$el.hide(); + $('body').css( {overflow:'auto'} ); + $('body').scrollTop( this.bodyScrollTop ); + + this.displayed = false; + + return false; + }, + + /** + * Refresh the preview display + */ + refreshPreview: function(){ + if( !this.$el.is(':visible') ) return; + + this.$('iframe#siteorigin-panels-live-editor-iframe').hide(); + + this.frameScrollTop = this.$('iframe#siteorigin-panels-live-editor-iframe').contents().find('body').scrollTop(); + + this.$('form.live-editor-form input[name="siteorigin_panels_data"]').val( JSON.stringify( this.builder.model.getPanelsData() ) ); + this.$('form.live-editor-form').submit(); + }, + + /** + * + * @param over + * @return {*|Object} + */ + createPreviewOverlay: function(over) { + var previewFrame = this.$('iframe#siteorigin-panels-live-editor-iframe'); + + // Remove any old overlays + var body = previewFrame.contents().find('body').css('position', 'relative'); + + previewFrame.contents().find('.panels-live-editor-overlay').remove(); + + // Create the new overlay + var overlayContainer = $('
').addClass('panels-live-editor-overlay').css( { + 'pointer-events' : 'none' + } ); + + var overlay = $('
').css({ + 'position' : 'absolute', + 'background' : '#000000', + 'z-index' : 1000, + 'opacity' : 0.25 + }); + + var spacing = 15; + + overlayContainer + .append( + overlay.clone().css({ + 'top' : -body.offset().top, + 'left' : 0, + 'right' : 0, + 'height' : over.offset().top - spacing + }) + ) + .append( + overlay.clone().css({ + 'bottom' : 0, + 'left' : 0, + 'right' : 0, + 'height' : Math.round( body.height() - over.offset().top - over.outerHeight() - spacing + body.offset().top - 0.01 ) + }) + ) + .append( + overlay.clone().css({ + 'top' : over.offset().top - spacing - body.offset().top, + 'left' : 0, + 'width' : over.offset().left - spacing, + 'height' : Math.round(over.outerHeight() + spacing*2) + }) + ) + .append( + overlay.clone().css({ + 'top' : over.offset().top - spacing - body.offset().top, + 'right' : 0, + 'left' : over.offset().left + over.outerWidth() + spacing, + 'height' : Math.round(over.outerHeight() + spacing*2) + }) + ); + + // Create a new overlay + previewFrame.contents().find('body').append(overlayContainer); + return overlayContainer; + }, + + /** + * Refresh the widgets in the left sidebar. + */ + refreshWidgets: function(){ + // Empty all the current widgets + this.$('.so-sidebar .page-widgets').empty(); + var previewFrame = this.$('iframe#siteorigin-panels-live-editor-iframe'); + + // Now lets move all the widgets to the sidebar + var thisView = this; + var widgetIndex = 0; + + this.builder.$('.so-row-container').each(function(ri, el) { + var row = $(el); + var widgets = row.find('.so-cells .cell .so-widget'); + + var sectionWrapper = $( thisView.sectionTemplate({ title: 'Row ' + (ri+1) }) ) + .appendTo( thisView.$('.so-sidebar .page-widgets') ); + + sectionWrapper.find('.section-header').click(function(){ + row.data('view').editSettingsHandler(); + }); + + var widgetsWrapper = sectionWrapper.find('.section-widgets'); + + widgets.each(function(i, el){ + var widget = $(this); + var widgetClone = widget.clone().show().css({ + opacity : 1 + }); + + // Remove all the action buttons from the clone + widgetClone.find('.actions').remove(); + widgetClone.find('.widget-icon').remove(); + + var thisWidgetIndex = (widgetIndex++); + var getHoverWidget = function(){ + // TODO this should target the #pl-x selector + return previewFrame.contents() + .find('#pl-' + thisView.postId + ' .panel-grid .panel-grid-cell .panel') + .filter(function(){ + // Filter to only include non nested + return $(this).parents('.widget_siteorigin-panels-builder').length == 0; + }) + .not('panel-hover-widget').eq(thisWidgetIndex); + } + + var overlay = null, hoverWidget = null; + + widgetClone + .click(function(e){ + e.preventDefault(); + widget.data('view').editHandler(); + return false; + }) + .mouseenter(function(){ + var hoverWidget = getHoverWidget(); + + // Center the iframe on the over item + + previewFrame.contents() + .find('html,body') + .clearQueue() + .animate( { + scrollTop: hoverWidget.offset().top - Math.max(30, ( Math.min( previewFrame.contents().height(), previewFrame.height() ) - hoverWidget.outerHeight() ) /2 ) + }, 750); + + // Create the overlay + overlay = thisView.createPreviewOverlay( hoverWidget ); + + }) + .mouseleave(function(){ + // Stop any scroll animations that are currently happening + previewFrame.contents() + .find('html,body') + .clearQueue(); + + if(overlay != null) { + overlay.fadeOut('fast', function(){ $(this).remove() }); + overlay = null; + } + if(hoverWidget != null) { + hoverWidget.remove(); + hoverWidget = null; + } + }) + .appendTo( widgetsWrapper ); + }); + }); + }, + + /** + * Return true if the live editor has a valid preview URL. + * @return {boolean} + */ + hasPreviewUrl: function(){ + return this.$('form.live-editor-form').attr('action') != ''; + } + } ); + +} )( jQuery, _, soPanelsOptions ); \ No newline at end of file diff --git a/js/siteorigin-panels-styles.js b/js/siteorigin-panels-styles.js new file mode 100644 index 000000000..1903a2b72 --- /dev/null +++ b/js/siteorigin-panels-styles.js @@ -0,0 +1,169 @@ +/** + * @copyright Greg Priday 2014 - + * @license GPL 3.0 http://www.gnu.org/licenses/gpl.html + */ + +( function( $, _, panelsOptions ){ + + var panels = window.siteoriginPanels; + + + /** + * The styles view handlers all the cool rendering stuff + */ + panels.view.styles = Backbone.View.extend( { + + stylesLoaded: false, + + initialize: function(){ + + }, + + /** + * Render the visual styles object. + * + * @param type + */ + render: function( stylesType ){ + if( typeof stylesType == 'undefined' ) return false; + + this.$el.addClass('so-visual-styles'); + + // Load the form + var thisView = this; + $.post( + ajaxurl, + { + action: 'so_panels_style_form', + type: stylesType, + style: this.model.get('style') + }, + function( response ){ + thisView.$el.html( response ); + thisView.setupFields(); + thisView.stylesLoaded = true; + thisView.trigger('styles_loaded'); + } + ); + }, + + /** + * Attach the style view to the DOM. + * + * @param wrapper + */ + attach: function( wrapper ){ + wrapper.append( this.$el ); + }, + + /** + * Detach the styles view from the DOM + */ + detach: function(){ + this.$el.detach(); + }, + + /** + * Setup all the fields + */ + setupFields: function(){ + + // Set up the sections as collapsible + this.$('.style-section-wrapper').each(function(){ + var $s = $(this); + + $s.find('.style-section-head').click( function(e){ + e.preventDefault(); + $s.find('.style-section-fields').slideToggle('fast'); + } ); + }); + + // Set up the color fields + if(typeof $.fn.wpColorPicker != 'undefined') { + this.$('.so-wp-color-field').wpColorPicker(); + } + + // Set up the image select fields + this.$('.style-field-image').each( function(){ + var frame = null; + var $s = $(this); + + $s.find('.so-image-selector').click( function(){ + + if( frame == null ) { + // Create the media frame. + frame = wp.media({ + // Set the title of the modal. + title: 'choose', + + // Tell the modal to show only images. + library: { + type: 'image' + }, + + // Customize the submit button. + button: { + // Set the text of the button. + text: 'Done', + close: true + } + }); + + frame.on( 'select', function(){ + var attachment = frame.state().get('selection').first().attributes; + + try { + $s.find( '.current-image' ).css( 'background-image', 'url(' + attachment.sizes.thumbnail.url + ')' ); + } + catch(e) { + // We'll use the full image instead + $s.find( '.current-image' ).css( 'background-image', 'url(' + attachment.sizes.full.url + ')' ); + } + + // Store the ID + $s.find('input').val( attachment.id ) + } ); + } + + frame.open(); + + } ); + + // Handle clicking on remove + $s.find('.remove-image').click(function(){ + $s.find( '.current-image').css('background-image', 'none'); + $s.find('input').val( '' ) + }); + } ); + + // Set up all the measurement fields + this.$('.style-field-measurement').each(function(){ + var $$ = $(this); + + var text = $$.find('input[type="text"]'); + var unit = $$.find('select'); + var hidden = $$.find('input[type="hidden"]'); + + // Load the value from the hidden field + if( hidden.val() != '' ) { + var re = /([0-9\.,]+)(.*)/; + var match = re.exec( hidden.val() ); + if( match != null && typeof match[1] != 'undefined' && typeof match[2] != 'undefined' ) { + text.val( match[1] ); + unit.val( match[2] ); + } + } + + var setVal = function(){ + hidden.val( text.val() + unit.val() ); + }; + + // Set the value when ever anything changes + text.keyup(setVal).change(setVal); + unit.change(setVal); + } ); + } + + } ); + +} )( jQuery, _, soPanelsOptions ); \ No newline at end of file diff --git a/js/siteorigin-panels.js b/js/siteorigin-panels.js new file mode 100644 index 000000000..0dc1c2bed --- /dev/null +++ b/js/siteorigin-panels.js @@ -0,0 +1,3281 @@ +/** + * Everything we need for SiteOrigin Page Builder. + * + * @copyright Greg Priday 2013 - 2014 - + * @license GPL 3.0 http://www.gnu.org/licenses/gpl.html + */ + +( function( $, _, panelsOptions ){ + + var panels = { + model : { }, + collection : { }, + view : { }, + dialog : { }, + fn : {} + }; + + /** + * Model for an instance of a widget + */ + panels.model.widget = Backbone.Model.extend( { + + cell: null, + + defaults: { + // The PHP Class of the widget + class : null, + + // Is this class missing? + missing : false, + + // The values of the widget + values: {}, + + // Have the current values been passed through the widgets update function + raw: false, + + // Visual style fields + styles: {} + }, + + initialize: function(){ + }, + + /** + * @param field + * @returns {*} + */ + getWidgetField: function(field){ + if(typeof panelsOptions['widgets'][this.get('class')] == 'undefined') { + if(field == 'title' || field == 'description') return panelsOptions.loc.missing_widget[field]; + else return ''; + } + else return panelsOptions['widgets'][this.get('class')][field]; + }, + + /** + * Move this widget to a new cell + * + * @param panels.model.cell newCell + */ + moveToCell: function(newCell){ + if( this.cell.cid == newCell.cid ) return; + + this.cell = newCell; + this.collection.remove(this, {silent:true}); + newCell.widgets.add(this, {silent:true}); + }, + + /** + * Trigger an event on the model that indicates a user wants to edit it + */ + triggerEdit: function(){ + this.trigger('user_edit', this); + }, + + /** + * Trigger an event on the widget that indicates a user wants to duplicate it + */ + triggerDuplicate: function(){ + this.trigger('user_duplicate', this); + }, + + /** + * This is basically a wrapper for set that checks if we need to trigger a change + */ + setValues: function(values){ + var hasChanged = false; + if( JSON.stringify( values ) != JSON.stringify( this.get('values') ) ) { + hasChanged = true; + } + + this.set( 'values', values, {silent: true} ); + + if( hasChanged ) { + // We'll trigger our own change events + this.trigger('change'); + this.trigger('change:values') + } + }, + + /** + * Create a clone of this widget attached to the given cell. + * + * @param {panels.model.cell} cell + * @returns {panels.model.widget} + */ + clone: function( cell, options ){ + if( typeof cell == 'undefined' ) cell = this.cell; + + var clone = new this.constructor( this.attributes ); + + // Create a deep clone of the original values + var cloneValues = JSON.parse( JSON.stringify( this.get('values') ) ); + + if( this.get('class') == "SiteOrigin_Panels_Widgets_Layout" ) { + // Special case of this being a layout widget, it needs a new ID + cloneValues.builder_id = Math.random().toString(36).substr(2); + } + + clone.set( 'values', cloneValues, { silent: true } ); + clone.set( 'collection', cell.widgets, { silent: true } ); + clone.cell = cell; + clone.isDuplicate = true; + return clone; + }, + + /** + * Gets the value that makes most sense as the title. + */ + getTitle: function(){ + var widgetData = panelsOptions.widgets[this.get('class')]; + if( typeof widgetData.panels_title != 'undefined' ) { + // This means that the widget has told us which field it wants us to use as a title + if( widgetData.panels_title === false ) { + return panelsOptions.widgets[this.get('class')].description; + } + } + + var values = this.get('values'); + var thisModel = this; + + // Create a list of fields to check for a title + var titleFields = ['title', 'text']; + for (var k in values){ + titleFields.push( k ); + } + titleFields = _.uniq(titleFields); + + for( var i in titleFields ) { + if( + typeof values[titleFields[i]] != 'undefined' && + typeof values[titleFields[i]] == 'string' && + values[titleFields[i]] != '' && + !$.isNumeric( values[titleFields[i]] ) + ) { + var title = values[ titleFields[i] ]; + title = title.replace(/<\/?[^>]+(>|$)/g, ""); + var parts = title.split(" "); + parts = parts.slice(0, 20); + return parts.join(' '); + } + } + + // If we still have nothing, then just return the widget description + return this.getWidgetField('description'); + } + + } ); + + /** + * The view for a widget in the builder interface + */ + panels.view.widget = Backbone.View.extend({ + template: _.template( $('#siteorigin-panels-builder-widget').html() ), + + // The cell view that + cell: null, + + dialog: null, + + events: { + 'click .widget-edit' : 'editHandler', + 'click .title h4' : 'editHandler', + 'click .actions .widget-duplicate' : 'duplicateHandler', + 'click .actions .widget-delete' : 'deleteHandler' + }, + + /** + * Initialize the widget + */ + initialize: function(){ + // The 2 user actions on the model that this view will handle. + this.model.on('user_edit', this.editHandler, this); + this.model.on('user_duplicate', this.duplicateHandler, this); + this.model.on('destroy', this.onModelDestroy, this); + this.model.on('visual_destroy', this.visualDestroyModel, this); + + this.model.on('change:values', this.onModelChange, this); + }, + + /** + * Render the widget + */ + render: function(options){ + options = _.extend({'loadForm': false}, options); + + this.setElement( this.template( { + title : this.model.getWidgetField('title'), + description : this.model.getTitle() + } ) ); + + this.$el.data( 'view', this ); + + if( _.size( this.model.get('values') ) == 0 || options.loadForm) { + // If this widget doesn't have a value, create a form and save it + var dialog = this.getEditDialog(); + + // Save the widget as soon as the form is loaded + dialog.once('form_loaded', dialog.saveWidget, dialog); + + // Setup the dialog to load the form + dialog.setupDialog(); + } + }, + + /** + * Display an animation that implies creation using a visual animation + */ + visualCreate: function(){ + this.$el.hide().fadeIn( 'fast' ); + }, + + /** + * Get the dialog view of the form that edits this widget + * + * @returns {null} + */ + getEditDialog: function(){ + if(this.dialog == null){ + this.dialog = new panels.dialog.widget({ + model: this.model + }); + this.dialog.setBuilder(this.cell.row.builder); + + // Store the widget view + this.dialog.widgetView = this; + } + return this.dialog; + }, + + /** + * Handle clicking on edit widget. + * + * @returns {boolean} + */ + editHandler: function(){ + // Create a new dialog for editing this + this.getEditDialog().openDialog(); + return false; + }, + + /** + * Handle clicking on duplicate. + * + * @returns {boolean} + */ + duplicateHandler: function(){ + // Add the history entry + this.cell.row.builder.addHistoryEntry('widget_duplicated'); + + // Create the new widget and connect it to the widget collection for the current row + var newWidget = this.model.clone( this.model.cell ); + + this.cell.model.widgets.add(newWidget, { + // Add this after the existing model + at: this.model.collection.indexOf( this.model ) + 1 + }); + + return false; + }, + + /** + * Handle clicking on delete. + * + * @returns {boolean} + */ + deleteHandler: function(){ + this.model.trigger('visual_destroy'); + return false; + }, + + onModelChange: function(){ + // Update the description when ever the model changes + this.$('.description').html( this.model.getTitle() ); + }, + + /** + * When the model is destroyed, fade it out + */ + onModelDestroy: function(){ + this.remove(); + }, + + /** + * Visually destroy a model + */ + visualDestroyModel: function(){ + // Add the history entry + this.cell.row.builder.addHistoryEntry('widget_deleted'); + + var thisView = this; + this.$el.fadeOut('fast', function(){ + thisView.cell.row.resize(); + thisView.model.destroy(); + } ); + } + + }); + + /** + * A collection of widgets, most often used for cells + */ + panels.collection.widgets = Backbone.Collection.extend( { + model : panels.model.widget, + + initialize: function(){ + } + + } ); + + /** + * A cell is a collection of widget instances + */ + panels.model.cell = Backbone.Model.extend( { + /* A collection of widgets */ + widgets: {}, + + /* The row this model belongs to */ + row: null, + + defaults: { + weight : 0 + }, + + /** + * Set up the cell model + */ + initialize: function(){ + this.widgets = new panels.collection.widgets(); + this.on('destroy', this.onDestroy, this); + }, + + /** + * Triggered when we destroy a cell + */ + onDestroy: function(){ + _.invoke(this.widgets.toArray(), 'destroy'); + this.widgets.reset(); + }, + + /** + * Create a clone of the cell, along with all its widgets + */ + clone: function(row, cloneOptions){ + if( typeof row == 'undefined' ) row = this.row; + cloneOptions = _.extend({ cloneWidgets: true }, cloneOptions); + + var clone = new this.constructor( this.attributes ); + clone.set('collection', row.cells, {silent: true}); + clone.row = row; + + if( cloneOptions.cloneWidgets ) { + // Now we're going add all the widgets that belong to this, to the clone + this.widgets.each(function(widget){ + clone.widgets.add( widget.clone( clone, cloneOptions ), {silent: true} ); + }); + } + + return clone; + } + + } ); + + /** + * A cell collection is used to represent a row + */ + panels.collection.cells = Backbone.Collection.extend( { + model: panels.cell, + + initialize: function(){ + this.on('add', this.onAddCell, this); + }, + + /** + * Get the total weight for the cells in this collection. + * @returns {number} + */ + totalWeight: function(){ + var totalWeight = 0; + this.each(function(cell){ + totalWeight += cell.get('weight'); + }); + + return totalWeight; + } + } ); + + /** + * The view for a cell + */ + panels.view.cell = Backbone.View.extend( { + template: _.template( $('#siteorigin-panels-builder-cell').html() ), + events : { + 'click .cell-wrapper' : 'handleCellClick', + 'click .so-cell-actions a' : 'handleActionClick' + }, + + /* The row view that this cell is a part of */ + row: null, + widgetSortable: null, + + initialize: function(){ + this.model.widgets.on('add', this.onAddWidget, this); + }, + + /** + * Render the actual cell + */ + render: function(){ + var templateArgs = { + weight: this.model.get('weight'), + totalWeight: this.row.model.cells.totalWeight() + }; + + this.setElement( this.template(templateArgs) ); + this.$el.data('view', this); + + // Now lets render any widgets that are currently in the row + var thisView = this; + this.model.widgets.each(function(widget){ + var widgetView = new panels.view.widget( { model: widget } ); + widgetView.cell = thisView; + widgetView.render(); + + widgetView.$el.appendTo( thisView.$('.widgets-container') ); + }); + + this.initSortable(); + this.initResizable(); + }, + + /** + * Initialize the widget sortable + */ + initSortable: function(){ + var cellView = this; + + var builderID = cellView.row.builder.$el.attr('id'); + + var scrollTop; + + // Create a widget sortable that's connected with all other cells + this.widgetSortable = this.$el.find('.widgets-container').sortable( { + placeholder: "so-widget-sortable-highlight", + connectWith: '#' + builderID + ' .so-cells .cell .widgets-container', + tolerance:'pointer', + scroll: false, + over: function(e, ui){ + // This will make all the rows in the current builder resize + cellView.row.builder.trigger('widget_sortable_move'); + }, + stop: function(e, ui){ + cellView.row.builder.addHistoryEntry('widget_moved'); + + var widget = $(ui.item).data('view'); + var targetCell = $(ui.item).closest('.cell').data('view'); + + // Move the model and the view to the new cell + widget.model.moveToCell( targetCell.model ); + widget.cell = targetCell; + + cellView.row.builder.sortCollections(); + }, + helper: function(e, el){ + var helper = el.clone().css('width', el.outerWidth()).addClass('widget-being-dragged').appendTo( 'body' ); + + // Center the helper to the mouse cursor. + if( el.outerWidth() > 720 ) { + helper.animate({ + 'margin-left': e.pageX - el.offset().left - (480 / 2), + 'width': 480 + }, 'fast'); + } + + return helper; + } + } ); + }, + + /** + * Refresh the widget sortable when a new widget is added + */ + refreshSortable: function(){ + this.widgetSortable.sortable('refresh'); + }, + + /** + * This will make the cell resizble + */ + initResizable: function(){ + // var neighbor = this.$el.previous().data('view'); + var handle = this.$('.resize-handle').css('position', 'absolute'); + var container = this.row.$el; + var cellView = this; + + // The view of the cell to the left is stored when dragging starts. + var previousCell; + + handle.draggable({ + axis: 'x', + containment: container, + start: function(e, ui){ + // Set the containment to the cell parent + previousCell = cellView.$el.prev().data('view'); + if( typeof previousCell == 'undefined' ) return false; + + // Create the clone for the current cell + var newCellClone = cellView.$el.clone().appendTo(ui.helper).css({ + position : 'absolute', + top : '0', + width : cellView.$el.outerWidth(), + left : 5, + height: cellView.$el.outerHeight() + }); + newCellClone.find('.resize-handle').remove(); + + // Create the clone for the previous cell + var prevCellClone = previousCell.$el.clone().appendTo(ui.helper).css({ + position : 'absolute', + top : '0', + width : previousCell.$el.outerWidth(), + right : 5, + height: previousCell.$el.outerHeight() + }); + prevCellClone.find('.resize-handle').remove(); + + $(this).data({ + 'newCellClone' : newCellClone, + 'prevCellClone' : prevCellClone + }); + }, + drag: function(e, ui){ + // Calculate the new cell and previous cell widths as a percent + var containerWidth = cellView.row.$el.width() + 10; + var ncw = cellView.model.get('weight') - ( ( ui.position.left + handle.outerWidth()/2 ) / containerWidth ); + var pcw = previousCell.model.get('weight') + ( ( ui.position.left + handle.outerWidth()/2 ) / containerWidth ); + + $(this).data('newCellClone').css('width', containerWidth * ncw ) + .find('.preview-cell-weight').html( Math.round(ncw*1000)/10 ); + + $(this).data('prevCellClone').css('width', containerWidth * pcw ) + .find('.preview-cell-weight').html( Math.round(pcw*1000)/10 ); + }, + stop: function(e, ui){ + // Remove the clones + $(this).data('newCellClone').remove(); + $(this).data('prevCellClone').remove(); + + var containerWidth = cellView.row.$el.width() + 10; + var ncw = cellView.model.get('weight') - ( ( ui.position.left + handle.outerWidth()/2 ) / containerWidth ); + var pcw = previousCell.model.get('weight') + ( ( ui.position.left + handle.outerWidth()/2 ) / containerWidth ); + + if( ncw > 0.02 && pcw > 0.02 ) { + cellView.row.builder.addHistoryEntry('cell_resized'); + cellView.model.set('weight', ncw); + previousCell.model.set('weight', pcw); + cellView.row.resize(); + } + + ui.helper.css('left', -handle.outerWidth()/2); + } + }); + + }, + + /** + * This is triggered when ever a widget is added to the row collection. + * + * @param widget + */ + onAddWidget: function(widget, collection, options){ + options = _.extend({noAnimate : false}, options); + + // Create the view for the widget + var view = new panels.view.widget( { + model: widget + } ); + view.cell = this; + + if( typeof widget.isDuplicate == 'undefined' ) { + widget.isDuplicate = false; + } + + // Render and load the form if this is a duplicate + view.render({ + 'loadForm': widget.isDuplicate + }); + + if( typeof options.at == 'undefined' || collection.length <= 1 ) { + // Insert this at the end of the widgets container + view.$el.appendTo( this.$( '.widgets-container' ) ); + } + else { + // We need to insert this at a specific position + view.$el.insertAfter( + this.$('.widgets-container .so-widget').eq( options.at - 1 ) + ); + } + + if( options.noAnimate === false ) { + // We need an animation + view.visualCreate(); + } + + this.refreshSortable(); + this.row.resize(); + }, + + /** + * Handle an action click on this cell + * + * @param e + * @returns {boolean} + */ + handleActionClick : function(e){ + return false; + } + } ); + + /** + * Model for a row of cells + */ + panels.model.row = Backbone.Model.extend( { + /* A collection of the cells in this row */ + cells: {}, + + /* The builder model */ + builder: null, + + defaults :{ + style: {} + }, + + /** + * Initialize the row model + */ + initialize: function(){ + this.cells = new panels.collection.cells(); + this.on('destroy', this.onDestroy, this); + }, + + /** + * Add cells to the model row + * + * @param cells an array of cells, where each object in the array has a weight value + * @todo make this handle changing the number of cells in an existing row + */ + setCells: function(cells){ + var thisModel = this; + + if( this.cells.length == 0 ) { + // We're adding the initial cells + _.each(cells, function (cellWeight) { + // Add the new cell to the row + var cell = new panels.model.cell({ + weight: cellWeight, + collection: thisModel.cells + }); + cell.row = thisModel; + thisModel.cells.add(cell); + }); + } + else { + + if(cells.length > this.cells.length) { + // We need to add cells + for( var i = this.cells.length; i < cells.length; i++ ) { + var cell = new panels.model.cell({ + weight: cells[ cells.length + i ], + collection: thisModel.cells + }); + cell.row = this; + thisModel.cells.add(cell); + } + + } + else if(cells.length < this.cells.length) { + // We need to remove cells + _.each(this.cells.slice( cells.length, this.cells.length), function(cell){ + cell.destroy(); + }); + } + + // Now we need to change the weights of all the cells + this.cells.each(function(cell, i){ + cell.set('weight', cells[i]); + }); + } + + // Rescale the cells when we add or remove + this.reweightCells(); + }, + + /** + * Make sure that all the cell weights add up to 1 + */ + reweightCells: function() { + var totalWeight = 0; + this.cells.each( function(cell){ + totalWeight += cell.get('weight'); + } ); + + this.cells.each( function(cell){ + cell.set( 'weight', cell.get('weight') / totalWeight ); + } ); + + // This is for the row view to hook into and resize + this.trigger('reweight_cells'); + }, + + /** + * Triggered when the model is destroyed + */ + onDestroy: function(){ + // Also destroy all the cells + _.invoke(this.cells.toArray(), 'destroy'); + this.cells.reset(); + }, + + /** + * Create a clone of the row, along with all its cells + * + * @param {panels.model.builder} builder The builder model to attach this to. + * + * @return {panels.model.row} The cloned row. + */ + clone: function( builder, cloneOptions ){ + if(typeof builder == 'undefined') builder = this.builder; + cloneOptions = _.extend({ cloneCells: true }, cloneOptions); + + var clone = new this.constructor( this.attributes ); + clone.set('collection', builder.rows, {silent: true}); + clone.builder = builder; + + if( cloneOptions.cloneCells ) { + // Clone all the rows + this.cells.each(function(cell){ + clone.cells.add( cell.clone( clone, cloneOptions ), {silent: true}); + }); + } + + return clone; + } + } ); + + /** + * A collection of rows. This is used to represent the entire content of Page Builder. + */ + panels.collection.rows = Backbone.Collection.extend( { + model: panels.model.row, + + initialize: function(){ + }, + + /** + * Destroy all the rows in this collection + */ + empty: function(){ + var model; + while ( model = this.collection.first() ) { + model.destroy(); + } + } + } ); + + /** + * View for handling the row. + */ + panels.view.row = Backbone.View.extend( { + template: _.template( $('#siteorigin-panels-builder-row').html() ), + + events: { + 'click .so-row-settings' : 'editSettingsHandler', + 'click .so-row-duplicate' : 'duplicateHandler', + 'click .so-row-delete' : 'confirmedDeleteHandler' + }, + + builder: null, + dialog: null, + + /** + * Initialize the row view + */ + initialize: function(){ + + this.model.cells.on('add', this.handleCellAdd, this); + this.model.cells.on('remove', this.handleCellRemove, this); + this.model.on('reweight_cells', this.resize, this); + + this.model.on('destroy', this.onModelDestroy, this); + this.model.on('visual_destroy', this.visualDestroyModel, this); + + var thisView = this; + this.model.cells.each(function(cell){ + thisView.listenTo(cell.widgets, 'add', thisView.resize); + }); + + // When ever a new cell is added, listen to it for new widgets + this.model.cells.on('add', function(cell){ + thisView.listenTo(cell.widgets, 'add', thisView.resize); + }, this); + + }, + + /** + * Render the row. + * + * @returns {panels.view.row} + */ + render: function(){ + this.setElement( this.template() ); + this.$el.data('view', this); + + // Create views for the cells in this row + var thisView = this; + this.model.cells.each( function(cell){ + var cellView = new panels.view.cell({ + model: cell + }); + cellView.row = thisView; + cellView.render(); + cellView.$el.appendTo( thisView.$('.so-cells') ); + } ); + + // Resize the rows when ever the widget sortable moves + this.builder.on('widget_sortable_move', this.resize, this); + this.builder.on('builder_resize', this.resize, this); + + this.resize(); + + return this; + }, + + /** + * Give a visual indication of the creation of this row + */ + visualCreate: function(){ + this.$el.hide().fadeIn('fast'); + }, + + /** + * Visually resize the row so that all cell heights are the same and the widths so that they balance to 100% + * + * @param e + */ + resize: function(e){ + // Don't resize this + if( !this.$el.is(':visible') ) return; + + // Reset everything to have an automatic height + this.$el.find( '.so-cells .cell-wrapper' ).css( 'min-height', 0 ); + + // We'll tie the values to the row view, to prevent issue with values going to different rows + var height = 0; + this.$el.find('.so-cells .cell').each( function () { + height = Math.max( + height, + $(this ).height() + ); + + $( this ).css( 'width', ( $(this).data('view').model.get('weight') * 100 ) + "%" ); + } ); + + // Resize all the grids and cell wrappers + this.$el.find( '.so-cells .cell-wrapper' ).css( 'min-height', Math.max( height, 70 ) ); + }, + + /** + * Remove the view from the dom. + */ + onModelDestroy: function() { + this.remove(); + }, + + /** + * Fade out the view and destroy the model + */ + visualDestroyModel: function(){ + this.builder.addHistoryEntry('row_deleted'); + var thisView = this; + this.$el.fadeOut('normal', function(){ + thisView.model.destroy(); + thisView.builder.model.refreshPanelsData(); + + if(thisView.builder.liveEditor.displayed) { + thisView.builder.liveEditor.refreshWidgets(); + } + }); + }, + + /** + * Duplicate this row. + * + * @return {boolean} + */ + duplicateHandler: function(){ + this.builder.addHistoryEntry('row_duplicated'); + + var duplicateRow = this.model.clone( this.builder.model ); + + this.builder.model.rows.add( duplicateRow, { + at: this.builder.model.rows.indexOf( this.model ) + 1 + } ); + + return false; + }, + + /** + * Handles deleting the row with a confirmation. + */ + confirmedDeleteHandler: function(e){ + var $$ = $(e.target); + + // The user clicked on the dashicon + if( $$.hasClass('dashicons') ) $$ = $$.parent(); + + if( $$.hasClass('so-confirmed') ) { + this.visualDestroyModel(); + } + else { + var originalText = $$.html(); + + $$.addClass('so-confirmed').html( + '' + panelsOptions.loc.dropdown_confirm + ); + + setTimeout(function(){ + $$.removeClass('so-confirmed').html(originalText); + }, 2500); + } + + return false; + }, + + /** + * Handle displaying the settings dialog + */ + editSettingsHandler: function(){ + // Lets open up an instance of the settings dialog + var dialog = this.builder.dialogs.row; + + if( this.dialog == null ) { + // Create the dialog + this.dialog = new panels.dialog.row(); + this.dialog.setBuilder( this.builder).setRowModel( this.model); + } + + this.dialog.openDialog(); + + return false; + }, + + /** + * Handle deleting this entire row. + */ + deleteHandler: function(){ + this.model.destroy(); + return false; + }, + + /** + * Handle a new cell being added to this row view. For now we'll assume the new cell is always last + */ + handleCellAdd: function(cell){ + var cellView = new panels.view.cell({ + model: cell + }); + cellView.row = this; + cellView.render(); + cellView.$el.appendTo( this.$('.so-cells') ); + }, + + /** + * Handle a cell being removed from this row view + */ + handleCellRemove: function(cell){ + // Find the view that ties in to the cell we're removing + this.$el.find('.so-cells > .cell').each( function(){ + var view = $(this).data('view'); + if(typeof view == 'undefined') return; + + if( view.model.cid == cell.cid ) { + // Remove this view + view.remove(); + } + } ); + } + + } ); + + /** + * The builder model + */ + panels.model.builder = Backbone.Model.extend( { + rows: {}, + + defaults : { + 'data' : { + 'widgets' : [], + 'grids' : [], + 'grid_cells' : [] + } + }, + + initialize: function(){ + // These are the main rows in the interface + this.rows = new panels.collection.rows(); + }, + + /** + * Add a new row to this builder. + * + * @param weights + */ + addRow: function( weights, options ){ + options = _.extend({noAnimate : false}, options); + // Create the actual row + var row = new panels.model.row( { + collection: this.rows + } ); + row.setCells( weights ); + row.builder = this; + this.rows.add(row, options); + + return row; + }, + + /** + * Load the panels data into the builder + * + * @param data + */ + loadPanelsData: function(data){ + // Start by destroying any rows that currently exist. This will in turn destroy cells, widgets and all the associated views + this.emptyRows(); + + // This will empty out the current rows and reload the builder data. + this.set( 'data', data, {silent: true} ); + + var cit = 0; + var rows = []; + + if( typeof data.grid_cells == 'undefined' ) return; + + var gi; + for(var ci = 0; ci < data.grid_cells.length; ci++) { + gi = parseInt(data.grid_cells[ci].grid); + if(typeof rows[gi] == 'undefined') rows[gi] = []; + + rows[gi].push( parseFloat( data.grid_cells[ci].weight ) ); + } + + var builderModel = this; + _.each( rows, function(row, i){ + // This will create and add the row model and its cells + var row = builderModel.addRow( row, { noAnimate: true } ); + + if( typeof data.grids[i].style != 'undefined' ) { + row.set( 'style', data.grids[i].style ); + } + } ); + + + if( typeof data.widgets == 'undefined' ) return; + + // Add the widgets + _.each(data.widgets, function(widgetData){ + try { + var panels_info = null; + if (typeof widgetData.panels_info != 'undefined') { + panels_info = widgetData.panels_info; + delete widgetData.panels_info; + } + else { + panels_info = widgetData.info; + delete widgetData.info; + } + + var row = builderModel.rows.at( parseInt(panels_info.grid) ); + var cell = row.cells.at(parseInt(panels_info.cell)); + + var newWidget = new panels.model.widget({ + class: panels_info.class, + values: widgetData + }); + + if( typeof panels_info.style != 'undefined' ) { + newWidget.set('style', panels_info.style ); + } + + newWidget.cell = cell; + cell.widgets.add(newWidget, {noAnimate: true}); + } + catch (err) { + // TODO handle this error + } + } ); + }, + + /** + * Convert the content of the builder into a object that represents the page builder data + */ + getPanelsData: function(){ + + var data = { + 'widgets' : [], + 'grids' : [], + 'grid_cells' : [] + }; + var widgetId = 0; + + this.rows.each(function(row, ri){ + + row.cells.each(function(cell, ci){ + + cell.widgets.each(function(widget, wi){ + // Add the data for the widget, including the panels_info field. + var values = _.extend( _.clone( widget.get('values') ), { + panels_info : { + class: widget.get('class'), + raw: widget.get('raw'), + grid: ri, + cell: ci, + id: widgetId++, + style: widget.get('style') + } + } ); + data.widgets.push( values ); + }); + + // Add the cell info + data.grid_cells.push( { + grid: ri, + weight: cell.get('weight') + } ); + + }); + + data.grids.push( { + cells: row.cells.length, + style: row.get('style') + } ); + + } ); + + return data; + + }, + + /** + * This will check all the current entries and refresh the panels data + */ + refreshPanelsData: function(){ + var oldData = JSON.stringify( this.get('data') ); + var newData = this.getPanelsData(); + this.set( 'data', newData, { silent: true } ); + + if( JSON.stringify( newData ) != oldData ) { + // The default change event doesn't trigger on deep changes, so we'll trigger our own + this.trigger('change'); + this.trigger('change:data'); + } + }, + + /** + * Empty all the rows and the cells/widgets they contain. + */ + emptyRows: function(){ + _.invoke(this.rows.toArray(), 'destroy'); + this.rows.reset(); + + return this; + } + + } ); + + /** + * This is the main view for the Page Builder interface. + */ + panels.view.builder = Backbone.View.extend( { + template: _.template( $('#siteorigin-panels-builder').html() ), + dialogs: { }, + rowsSortable: null, + dataField : false, + currentData: '', + + attachedToEditor: false, + liveEditor: false, + + events: { + 'click .so-tool-button.so-widget-add': 'displayAddWidgetDialog', + 'click .so-tool-button.so-row-add': 'displayAddRowDialog', + 'click .so-tool-button.so-prebuilt-add': 'displayAddPrebuiltDialog', + 'click .so-tool-button.so-history': 'displayHistoryDialog', + 'click .so-tool-button.so-live-editor': 'displayLiveEditor', + + 'click .so-cells .cell .cell-wrapper' : 'cellClickHandler' + }, + + /* A row collection */ + rows: null, + + /** + * Initialize the builder + */ + initialize: function(){ + var builder = this; + + // Now lets create all the dialog boxes that the main builder interface uses + this.dialogs = { + widgets: new panels.dialog.widgets(), + row: new panels.dialog.row(), + prebuilt: new panels.dialog.prebuilt() + }; + + // Set the builder for each dialog and render it. + _.each(this.dialogs, function(p, i, d){ + d[i].setBuilder( builder ); + }) + + this.dialogs.row.setRowDialogType('create'); + + // This handles a new row being added to the collection - we'll display it in the interface + this.model.rows.on('add', this.onAddRow, this); + + // Reflow the entire builder when ever the + $(window).resize(function(e){ + if(e.target == window) { + builder.trigger('builder_resize'); + } + }); + + // When the data changes in the model, store it in the field + this.model.on('change:data', this.storeModelData, this); + + // Handle a content change + this.on('content_change', this.handleContentChange, this); + + this.on('display_builder', this.handleDisplayBuilder, this); + + this.model.on('change:data', this.toggleWelcomeDisplay, this); + }, + + /** + * Render the builder interface. + * + * @return {siteoriginPanels.view.builder} + */ + render: function(){ + this.$el.html( this.template() ); + this.$el + .attr( 'id', 'siteorigin-panels-builder-' + this.cid ) + .addClass('so-builder-container'); + return this; + }, + + /** + * Attach the builder to the given container + * + * @param container + * @returns {panels.view.builder} + */ + attach: function(options) { + + options = _.extend({ container: false, dialog: false }, options); + + if( options.dialog ) { + // We're going to add this to a dialog + this.dialog = new panels.dialog.builder(); + this.dialog.builder = this; + } + else { + // Attach this in the standard way + this.$el.appendTo( options.container ); + this.metabox = options.container.closest('.postbox'); + this.initSortable(); + } + + return this; + }, + + /** + * This will move the Page Builder Metabox into the editor + * + * @returns {panels.view.builder} + */ + attachToEditor: function(){ + if( typeof this.metabox == 'undefined' ) return this; + + this.attachedToEditor = true; + var metabox = this.metabox; + var thisView = this; + + // Handle switching between the page builder and other tabs + $( '#wp-content-wrap .wp-editor-tabs' ) + .find( '.wp-switch-editor' ) + .click(function (e) { + e.preventDefault(); + $( '#wp-content-editor-container, #post-status-info' ).show(); + metabox.hide(); + $( '#wp-content-wrap' ).removeClass('panels-active'); + $('#content-resize-handle' ).show(); + thisView.trigger('hide_builder'); + } ).end() + .prepend( + $( '' + metabox.find( 'h3.hndle span' ).html() + '' ) + .click( function (e) { + // Switch to the Page Builder interface + e.preventDefault(); + + var $$ = $( this ); + + // Hide the standard content editor + $( '#wp-content-wrap, #post-status-info' ).hide(); + + // Show page builder and the inside div + metabox.show().find('> .inside').show(); + + // Triggers full refresh + $( window ).resize(); + $( document).scroll(); + + thisView.trigger('display_builder'); + } ) + ); + + // Switch back to the standard editor + metabox.find('.so-switch-to-standard').click(function(e){ + e.preventDefault(); + + // Switch back to the standard editor + $( '#wp-content-wrap, #post-status-info' ).show(); + metabox.hide(); + // Resize to trigger reflow of WordPress editor stuff + $( window ).resize(); + }).show(); + + // Move the panels box into a tab of the content editor + metabox.insertAfter( '#wp-content-wrap').hide().addClass('attached-to-editor'); + + // Switch to the Page Builder interface as soon as we load the page if there are widgets + var data = this.model.get('data'); + if( typeof data.widgets != 'undefined' && _.size(data.widgets) != 0 ) { + $('#content-panels.switch-panels').click(); + } + + // We will also make this sticky if its attached to an editor. + var stickToolbar = function(){ + var toolbar = thisView.$('.so-builder-toolbar'); + var newTop = $(window).scrollTop() - thisView.$el.offset().top; + + if( $('#wpadminbar').css('position') == 'fixed' ) { + newTop += $('#wpadminbar').outerHeight(); + } + + // Make sure this falls in an acceptible range. + newTop = Math.max( newTop, 0 ); + newTop = Math.min( newTop, thisView.$el.outerHeight() - toolbar.outerHeight() + 20 ); // 20px extra to account for padding. + + // Position the toolbar + toolbar.css('top', newTop); + thisView.$el.css('padding-top', toolbar.outerHeight()); + } + $( window ).resize( stickToolbar ); + $( document ).scroll( stickToolbar ); + stickToolbar(); + + return this; + }, + + /** + * Initialize the row sortables + */ + initSortable: function(){ + // Create the sortable for the rows + var $el = this.$el; + var builderView = this; + + this.rowsSortable = this.$el.find('.so-rows-container').sortable( { + appendTo: '#wpwrap', + items: '.so-row-container', + handle: '.so-row-move', + tolerance: 'pointer', + scroll: false, + stop: function (e) { + builderView.addHistoryEntry('row_moved'); + + // Sort the rows collection after updating all the indexes. + builderView.sortCollections(); + } + } ); + }, + + /** + * Refresh the row sortable + */ + refreshSortable: function(){ + // Refresh the sortable to account for the new row + if(this.rowsSortable != null) { + this.rowsSortable.sortable('refresh'); + } + }, + + /** + * Set the field that's used to store the data + * @param field + */ + setDataField: function(field, options){ + options = _.extend({ + load: true + }, options); + + this.dataField = field; + this.dataField.data('builder', this); + + if( options.load && field.val() != '') { + var data; + try { + data = JSON.parse( this.dataField.val( ) ); + } + catch(err) { data = '' } + + this.model.loadPanelsData(data); + this.currentData = data; + this.toggleWelcomeDisplay(); + } + + return this; + }, + + /** + * Store the model data in the data field set in this.setDataField. + */ + storeModelData: function(){ + var data = JSON.stringify( this.model.get('data' ) ); + + if( $(this.dataField).val( ) != data ) { + // If the data is different, set it and trigger a content_change event + $(this.dataField).val( data ); + this.trigger('content_change'); + } + }, + + onAddRow: function(row, collection, options){ + options = _.extend( {noAnimate: false}, options ); + // Create a view for the row + var rowView = new panels.view.row( { model: row } ); + rowView.builder = this; + rowView.render(); + + // Attach the row elements to this builder + if( typeof options.at == 'undefined' || collection.length <= 1 ) { + // Insert this at the end of the widgets container + rowView.$el.appendTo( this.$( '.so-rows-container' ) ); + } + else { + // We need to insert this at a specific position + rowView.$el.insertAfter( + this.$('.so-rows-container .so-row-container').eq( options.at - 1 ) + ); + } + + if(options.noAnimate === false) { + rowView.visualCreate(); + } + + this.refreshSortable(); + rowView.resize(); + }, + + displayAddWidgetDialog: function(){ + this.dialogs.widgets.openDialog(); + return false; + }, + + displayAddRowDialog: function(){ + this.dialogs.row.openDialog(); + this.dialogs.row.setRowModel(); // Set this to an empty row model + return false; + }, + + displayAddPrebuiltDialog: function(){ + this.dialogs.prebuilt.openDialog(); + return false; + }, + + displayHistoryDialog: function(){ + this.dialogs.history.openDialog(); + return false; + }, + + cellClickHandler: function(e){ + var cells = this.$el.find('.so-cells .cell').removeClass('cell-selected'); + $(e.target).parent().addClass('cell-selected'); + }, + + /** + * Get the model for the currently active cell + */ + getActiveCell: function(){ + if( this.$('.so-cells .cell').length == 0 ) { + // Create a row with a single cell + this.model.addRow( [1], {noAnimate: true} ); + } + + var activeCell = this.$('.so-cells .cell.cell-selected'); + + if(!activeCell.length) { + activeCell = this.$('.so-cells .cell').first(); + } + + return activeCell.data('view').model; + }, + + /** + * Sort all widget and row collections based on their dom position + */ + sortCollections: function(){ + // Create an array that stores model indexes within the array + var indexes = {}; + + this.$('.so-rows-container .so-row-container').each(function(ri, el){ + var $r = $(el); + indexes[ $r.data('view').model.cid ] = ri; + + $r.find('.so-cells .cell').each(function(ci, el){ + var $c = $(el); + + $c.find('.so-widget').each(function(wi, el) { + var $w = $(el); + indexes[ $w.data('view').model.cid ] = wi; + }) + }); + }); + + // Sort everything + this.model.rows.models = this.model.rows.sortBy(function(model){ + return indexes[model.cid]; + }); + + this.model.rows.each(function(row){ + row.cells.each(function(cell){ + cell.widgets.models = cell.widgets.sortBy(function(widget){ + return indexes[widget.cid]; + }); + }) + }); + + // Update the builder model to reflect the newly ordered data. + this.model.refreshPanelsData(); + }, + + /** + * Add a live editor + * + * @returns {panels.view.builder} + */ + addLiveEditor: function(postId){ + if( typeof panels.view.liveEditor == 'undefined' ) return this; + + // Create the live editor and set the builder to this. + this.liveEditor = new panels.view.liveEditor(); + this.liveEditor.setPostId(postId); + + this.liveEditor.builder = this; + + // Display the live editor button in the toolbar + if( this.liveEditor.hasPreviewUrl() ) { + this.$('.so-builder-toolbar .so-live-editor').show(); + } + + return this; + }, + + /** + * Show the current live editor + */ + displayLiveEditor: function(){ + if(typeof this.liveEditor == 'undefined') return false; + + this.liveEditor.open(); + return false; + }, + + /** + * Add the history browser. + * + * @return {panels.view.builder} + */ + addHistoryBrowser: function(){ + if(typeof panels.dialog.history == 'undefined') return this; + + this.dialogs.history = new panels.dialog.history(); + this.dialogs.history.builder = this; + this.dialogs.history.entries.builder = this.model; + + // Set the revert entry + this.dialogs.history.setRevertEntry( this.model ); + + // Display the live editor button in the toolbar + this.$('.so-builder-toolbar .so-history').show(); + }, + + /** + * Add an entry. + * + * @param text + * @param data + */ + addHistoryEntry: function(text, data){ + if(typeof data == 'undefined') data = null; + + if( typeof this.dialogs.history != 'undefined' ) { + this.dialogs.history.entries.addEntry(text, data); + } + }, + + /** + * Handle a change of the content + */ + handleContentChange: function(){ + + if(this.attachedToEditor) { + // We're going to create a copy of page builder content into the post content + $.post( + ajaxurl, + { + action: 'so_panels_builder_content', + panels_data: JSON.stringify( this.model.getPanelsData() ), + post_id : $('#post_ID').val() + }, + function(content){ + + // Strip all the known layout divs + var t = $('
').html( content ); + t.find( 'div').each(function() { + var c = $(this).contents(); + $(this).replaceWith(c); + }); + content = t.html(); + + // Set the content of the editor + if( typeof tinyMCE == 'undefined' || tinyMCE.get("content") == null ) $('#content').val( content ); + else tinyMCE.get("content").setContent(content); + + // Trigger a focusout (mainly for Yoast SEO) + $('#content').focusout(); + } + ); + } + + if( this.liveEditor !== false ) { + // Refresh the content of the builder + this.liveEditor.refreshPreview(); + } + }, + + /** + * Handle displaying the builder + */ + handleDisplayBuilder: function(){ + var editorContent = ''; + if ( typeof tinyMCE != 'undefined' ) editor = tinyMCE.get( 'content' ); + if( editor != null && typeof( editor.getContent ) == "function" ) { + editorContent = editor.getContent(); + } + else { + editorContent = $('textarea#content').val(); + } + + if( this.model.get('data') == '' && editorContent != '') { + // Confirm with the user first + if( !confirm( panelsOptions.loc.confirm_use_builder ) ) return; + + var widgetClass = ''; + if( typeof panelsOptions.widgets["WP_Widget_Black_Studio_TinyMCE"] ) { + widgetClass = 'WP_Widget_Black_Studio_TinyMCE'; + } + // There is a small chance a theme will have removed this, so check + else if( typeof panelsOptions.widgets["WP_Widget_Text"] ) { + widgetClass = 'WP_Widget_Text'; + } + + if( widgetClass == '' ) return; + + // Create the existing page content in a single widget + this.model.loadPanelsData( { + grid_cells : [ { grid: 0, weight: 1 } ], + grids: [ { cells: 1 } ], + widgets: [{ + filter: "1", + text: editorContent, + title: "", + type: "visual", + panels_info: { + class: widgetClass, + raw: false, + grid: 0, + cell: 0 + } + }] + } ); + this.model.trigger('change'); + this.model.trigger('change:data'); + } + else if ( this.model.get('data') == '' ) { + // Set up a blank single row + //this.model.loadPanelsData( { + // grid_cells : [ { grid: 0, weight: 1 } ], + // grids: [ { cells: 1 } ], + // widgets: [] + //} ); + } + + }, + + /** + * Set the parent dialog for all the dialogs in this builder. + * + * @param text + * @param dialog + */ + setDialogParents: function(text, dialog){ + _.each(this.dialogs, function(p, i, d){ + d[i].setParent(text, dialog ); + }); + + // For any future dialogs + this.on('add_dialog', function(newDialog){ + newDialog.setParent(text, dialog); + }, this) + }, + + toggleWelcomeDisplay: function(){ + if( this.model.rows.length ) { + this.$('.so-panels-welcome-message').hide(); + } + else { + this.$('.so-panels-welcome-message').show(); + } + }, + + } ); + + /** + * The default dialog view. This should be extended by the other views. + */ + panels.view.dialog = Backbone.View.extend( { + dialogTemplate: _.template( $('#siteorigin-panels-dialog').html() ), + dialogTabTemplate: _.template( $('#siteorigin-panels-dialog-tab').html() ), + + tabbed: false, + rendered: false, + builder: false, + className: 'so-panels-dialog-wrapper', + dialogClass: '', + parentDialog: false, + + events : { + 'click .so-close': 'closeDialog', + 'click .so-nav.so-previous': 'navToPrevious', + 'click .so-nav.so-next': 'navToNext' + }, + + initialize: function(){ + // The first time this dialog is opened, render it + this.once('open_dialog', this.render); + this.once('open_dialog', this.attach); + this.once('open_dialog', this.setDialogClass); + + this.trigger('initialize_dialog', this); + + if(typeof this.initializeDialog != 'undefined') { + this.initializeDialog(); + } + }, + + /** + * Returns the next dialog in the sequence. Should be overwritten by a child dialog. + * @returns {null} + */ + getNextDialog: function(){ + return null; + }, + + /** + * Returns the previous dialog in this sequence. Should be overwritten by child dialog. + * @returns {null} + */ + getPrevDialog: function(){ + return null; + }, + + /** + * Adds a dialog class to uniquely identify this dialog type + */ + setDialogClass: function(){ + if(this.dialogClass != ''){ + this.$('.so-panels-dialog').addClass(this.dialogClass); + } + }, + + /** + * Set the builder that controls this dialog. + * @param {panels.view.builder} builder + */ + setBuilder: function(builder){ + this.builder = builder; + + // Trigger an add dialog event on the builder so it can modify the dialog in any way + builder.trigger('add_dialog', this, this.builder); + + return this; + }, + + /** + * Attach the dialog to the window + */ + attach: function(){ + this.$el.appendTo( 'body' ); + + return this; + }, + + /** + * Converts an HTML representation of the dialog into arguments for a dialog box + * @param html HTML for the dialog + * @param args Arguments passed to the template + * @returns {} + */ + parseDialogContent: function(html, args){ + // Add a CID + args = _.extend({cid: this.cid}, args); + + + var c = $( ( _.template(html) )( args ) ); + var r = { + title : c.find('.title').html(), + buttons : c.find('.buttons').html(), + content : c.find('.content').html() + }; + + if( c.has('.left-sidebar') ){ + r.left_sidebar = c.find('.left-sidebar').html(); + } + + if( c.has('.right-sidebar') ){ + r.right_sidebar = c.find('.right-sidebar').html(); + } + + return r; + + }, + + /** + * Render the dialog and initialize the tabs + * + * @param attributes + * @returns {panels.view.dialog} + */ + renderDialog: function(attributes){ + this.$el.html( this.dialogTemplate( attributes ) ).hide(); + this.$el.data('view', this); + this.$el.addClass('so-panels-dialog-wrapper'); + + if( this.parentDialog != false ) { + // Add a link to the parent dialog as a sort of crumbtrail. + var thisDialog = this; + var dialogParent = $('').html( this.parentDialog.text + '
' ); + dialogParent.click(function(e){ + e.preventDefault(); + thisDialog.closeDialog(); + thisDialog.parentDialog.openDialog(); + }); + this.$('.so-title-bar').prepend( dialogParent ); + } + + return this; + }, + + /** + * Initialize the sidebar tabs + */ + initTabs: function(){ + var tabs = this.$el.find('.so-sidebar-tabs li a'); + + if(tabs.length == 0) return; + + var thisDialog = this; + tabs.click(function(e){ + e.preventDefault(); + var $$ = $(this); + + thisDialog.$('.so-sidebar-tabs li').removeClass('tab-active'); + thisDialog.$('.so-content .so-content-tabs > *').hide(); + + $$.parent().addClass('tab-active'); + + var url = $$.attr('href'); + if(typeof url != 'undefined' && url.charAt(0) == '#') { + // Display the new tab + var tabName = url.split('#')[1]; + thisDialog.$('.so-content .so-content-tabs .tab-' + tabName).show(); + } + + // This lets other dialogs implement their own custom handlers + thisDialog.trigger('tab_click', $$); + + }); + + // Trigger a click on the first tab + this.$el.find('.so-sidebar-tabs li a').first().click(); + + }, + + /** + * Quickly setup the dialog by opening and closing it. + */ + setupDialog: function(){ + this.openDialog(); + this.closeDialog(); + }, + + /** + * Refresh the next and previous buttons. + */ + refreshDialogNav: function(){ + this.$('.so-title-bar .so-nav').show().removeClass('so-disabled'); + + // Lets also hide the next and previous if we don't have a next and previous dialog + var nextDialog = this.getNextDialog(); + var nextButton = this.$('.so-title-bar .so-next'); + + var prevDialog = this.getPrevDialog(); + var prevButton = this.$('.so-title-bar .so-previous'); + + if(nextDialog === null) nextButton.hide(); + else if(nextDialog === false) nextButton.addClass('so-disabled'); + + if(prevDialog === null) prevButton.hide(); + else if(prevDialog === false) prevButton.addClass('so-disabled'); + }, + + /** + * Open the dialog + */ + openDialog: function(){ + this.trigger('open_dialog'); + + this.refreshDialogNav(); + + // Stop scrolling for the main body + this.bodyScrollTop = $('body').scrollTop(); + $('body').css({'overflow':'hidden'}); + + this.$el.show(); + + // This triggers once everything is visible + this.trigger('open_dialog_complete'); + }, + + /** + * Close the dialog + * + * @param e + * @returns {boolean} + */ + closeDialog: function(e){ + this.trigger('close_dialog'); + + // In the builder, trigger an update + if(typeof this.builder != 'undefined') { + // Store the model data when a dialog is closed. + this.builder.model.refreshPanelsData(); + } + + this.$el.hide(); + + if( !$('.so-panels-dialog-wrapper').is(':visible') ){ + // Restore scrolling to the main body if there are no more dialogs + $('body').css({'overflow':'auto'}); + $('body').scrollTop( this.bodyScrollTop ); + } + + // This triggers once everything is hidden + this.trigger('close_dialog_complete'); + + return false; + }, + + /** + * Navigate to the previous dialog + */ + navToPrevious: function(){ + this.closeDialog(null); + + var prev = this.getPrevDialog(); + if(prev != null && prev != false){ + prev.openDialog(); + } + }, + + /** + * Navigate to the next dialog + */ + navToNext: function(){ + this.closeDialog(null); + + var next = this.getNextDialog(); + if(next != null && next != false){ + next.openDialog(); + } + }, + + /** + * Get the values from the form and convert them into a data array + */ + getFormValues: function(formSelector){ + if(typeof formSelector == 'undefined') formSelector = '.so-content'; + var $f = this.$(formSelector); + + var data = {}, parts; + + // Find all the named fields in the form + $f.find('[name]').each(function(){ + var $$ = $(this); + + var name = /([A-Za-z_]+)\[(.*)\]/.exec( $$.attr('name') ); + + // Create an array with the parts of the name + if(typeof name[2] == 'undefined') { + parts = $$.attr('name'); + } + else { + parts = name[2].split(']['); + parts.unshift( name[1] ); + } + + parts = parts.map(function(e){ + if( !isNaN(parseFloat(e)) && isFinite(e) ) return parseInt(e); + else return e; + }); + + var sub = data; + var fieldValue = null; + + // First we need to get the value from the field + if( $$.attr('type') == 'checkbox' ){ + if ( $$.is(':checked') ) { + fieldValue = $$.val() != '' ? $$.val() : true; + } + else { + fieldValue = false; + } + } + else if( $$.attr('type') == 'radio' ){ + if ( $$.is(':checked') ) { + fieldValue = $$.val(); + } + else { + //skip over unchecked radios + return; + } + } + else if( $$.prop('tagName') == 'TEXTAREA' && $$.hasClass('wp-editor-area') ){ + // This is a TinyMCE editor, so we'll use the tinyMCE object to get the content + var editor = null; + if ( typeof tinyMCE != 'undefined' ) editor = tinyMCE.get( $$.attr('id') ); + + if( editor != null && typeof( editor.getContent ) == "function" ) { + fieldValue = editor.getContent(); + } + } + else if ( $$.prop('tagName') == 'SELECT' ) { + fieldValue = $$.find('option:selected').val(); + } + if( fieldValue == null ) { + fieldValue = $$.val(); + } + + // Now, we need to filter this value if necessary + if( typeof $$.data('panels-filter') != 'undefined' ) { + switch( $$.data('panels-filter') ) { + case 'json_parse': + // Attempt to parse the JSON value of this field + try { + fieldValue = JSON.parse( fieldValue ); + } + catch(err) { + fieldValue = ''; + } + break; + } + } + + // Now convert this into an array + for(var i = 0; i < parts.length; i++) { + if(i == parts.length - 1) { + sub[parts[i]] = fieldValue; + } + else { + if(typeof sub[parts[i]] == 'undefined') { + sub[parts[i]] = {}; + } + sub = sub[parts[i]]; + } + } + + }); // End of each through input fields + + return data; + }, + + /** + * Set a status message for the dialog + */ + setStatusMessage: function(message, loading){ + this.$('.so-toolbar .so-status').html( message ); + if( typeof loading != 'undefined' && loading ) { + this.$('.so-toolbar .so-status').addClass('so-panels-loading'); + } + }, + + /** + * Set the parent after. + */ + setParent: function(text, dialog){ + this.parentDialog = { + text: text, + dialog: dialog + } + } + } ); + + /** + * This is the dialog that holds the builder. + */ + panels.dialog.builder = panels.view.dialog.extend( { + dialogClass : 'so-panels-dialog-add-builder', + + render: function(){ + // Render the dialog and attach it to the builder interface + this.renderDialog( this.parseDialogContent( $('#siteorigin-panels-dialog-builder').html(), {} ) ); + this.$('.so-content .siteorigin-panels-builder').append( this.builder.$el ); + }, + + initializeDialog: function(){ + var thisView = this; + this.once('open_dialog_complete', function(){ + thisView.builder.initSortable(); + }); + + this.on('open_dialog_complete', function(){ + thisView.builder.trigger('builder_resize'); + }); + } + } ); + + /** + * The dialog for selecting a widget to add to the page + */ + panels.dialog.widgets = panels.view.dialog.extend( { + + builder: null, + widgetTemplate: _.template( $('#siteorigin-panels-dialog-widgets-widget').html() ), + filter: {}, + + dialogClass : 'so-panels-dialog-add-widget', + + events: { + 'click .so-close': 'closeDialog', + 'click .widget-type' : 'widgetClickHandler', + 'keyup .so-sidebar-search' : 'searchHandler' + }, + + /** + * Initialize the widget adding dialog + */ + initializeDialog: function(){ + + this.on('open_dialog', function(){ + this.filter.search = ''; + this.filterWidgets(this.filter); + }, this); + + this.on('open_dialog_complete', function(){ + // Clear the search and re-filter the widgets when we open the dialog + this.$('.so-sidebar-search').val('').focus(); + }); + + // We'll implement a custom tab click handler + this.on('tab_click', this.tabClickHandler, this); + }, + + render: function(){ + // Render the dialog and attach it to the builder interface + this.renderDialog( this.parseDialogContent( $('#siteorigin-panels-dialog-widgets').html(), {} ) ); + + // Add all the widgets + _.each( panelsOptions.widgets, function( widget ){ + var $w = $( this.widgetTemplate( { + title : widget.title, + description : widget.description + } ) ) ; + + if(typeof widget.icon == 'undefined') widget.icon = 'dashicons dashicons-admin-generic'; + + if( typeof widget.icon != 'undefined' ){ + $('').addClass( widget.icon ).prependTo( $w.find('.widget-type-wrapper') ); + } + + $w.data('class', widget.class).appendTo( this.$el.find('.widget-type-list') ); + }, this ); + + // Add the sidebar tabs + var tabs = this.$el.find('.so-sidebar-tabs'); + _.each(panelsOptions.widget_dialog_tabs, function(tab){ + var $t = $( this.dialogTabTemplate( { 'title' : tab.title } )).data('filter', tab.filter).appendTo( tabs ); + }, this); + + // We'll be using tabs, so initialize them + this.initTabs(); + }, + + /** + * Handle a tab being clicked + */ + tabClickHandler: function($t){ + // Get the filter from the tab, and filter the widgets + this.filter = $t.parent().data('filter'); + if( this.$el.find('.so-sidebar-search').val() != '' ) { + this.filter.search = this.$el.find('.so-sidebar-search').val(); + } + this.filterWidgets(this.filter); + + return false; + }, + + /** + * Handle changes to the search value + */ + searchHandler: function(e){ + this.filter.search = $(e.target).val(); + this.filterWidgets(this.filter); + }, + + /** + * Filter the widgets that we're displaying + * @param filter + */ + filterWidgets: function(filter) { + if (typeof filter == 'undefined') filter = {}; + + if(typeof filter.groups == 'undefined') filter.groups = ''; + + this.$el.find('.widget-type-list .widget-type').each(function(){ + var $$ = $(this), showWidget; + var widgetClass = $$.data('class'); + + var widgetData = ( typeof panelsOptions.widgets[widgetClass] != 'undefined' ) ? panelsOptions.widgets[widgetClass] : false; + + if( filter.groups.length == 0 ) { + // This filter doesn't specify groups, so show all + showWidget = true; + } + else if( widgetData !== false && _.intersection(filter.groups, panelsOptions.widgets[widgetClass].groups).length ) { + // This widget is in the filter group + showWidget = true; + } + else { + // This widget is not in the filter group + showWidget = false; + } + + // This can probably be done with a more intelligent operator + if( showWidget ) { + + if( typeof filter.search != 'undefined' && filter.search != '' ) { + // Check if the widget title contains the search term + if( widgetData.title.toLowerCase().indexOf( filter.search.toLowerCase() ) == -1 ) { + showWidget = false; + } + } + + } + + if(showWidget) $$.show(); + else $$.hide(); + }); + }, + + /** + * Add the widget to the current builder + * + * @param e + */ + widgetClickHandler : function(e){ + // Add the history entry + this.builder.addHistoryEntry('widget_added'); + + var $w = $(e.currentTarget); + + var widget = new panels.model.widget( { + class: $w.data('class') + } ); + + // Add the widget to the cell model + widget.cell = this.builder.getActiveCell(); + widget.cell.widgets.add( widget ); + + this.closeDialog(); + } + } ); + + /** + * Dialog for displaying a single widget form + */ + panels.dialog.widget = panels.view.dialog.extend( { + + builder: null, + sidebarWidgetTemplate: _.template( $('#siteorigin-panels-dialog-widget-sidebar-widget').html() ), + dialogClass : 'so-panels-dialog-edit-widget', + widgetView : false, + + events: { + 'click .so-close': 'saveHistory', + 'click .so-nav.so-previous': 'navToPrevious', + 'click .so-nav.so-next': 'navToNext', + + // Action handlers + 'click .so-toolbar .so-delete': 'deleteHandler', + 'click .so-toolbar .so-duplicate': 'duplicateHandler' + }, + + initializeDialog: function(){ + this.model.on('destroy', this.remove, this); + }, + + /** + * Render the widget dialog. + */ + render: function() { + // Render the dialog and attach it to the builder interface + this.renderDialog( this.parseDialogContent( $('#siteorigin-panels-dialog-widget').html(), {} ) ); + this.loadForm(); + + if( typeof panelsOptions.widgets[ this.model.get('class') ] != 'undefined') { + this.$('.so-title .widget-name').html( panelsOptions.widgets[ this.model.get('class')].title ); + } + else { + this.$('.so-title .widget-name').html( panelsOptions.loc.missing_widget.title ); + } + + // Now we need to attach the style window + this.styles = new panels.view.styles(); + this.styles.model = this.model; + this.styles.render( 'widget' ); + this.styles.attach( this.$('.so-sidebar.so-right-sidebar') ); + + // Handle the loading class + this.styles.on('styles_loaded', function(){ + this.$('.so-sidebar.so-right-sidebar').removeClass('so-panels-loading'); + }, this); + this.$('.so-sidebar.so-right-sidebar').addClass('so-panels-loading'); + }, + + /** + * Get the previous widget editing dialog by looking at the dom. + * @returns {*} + */ + getPrevDialog: function(){ + var widgets = this.builder.$('.so-cells .cell .so-widget'); + if(widgets.length <= 1) return false; + var currentIndex = widgets.index( this.widgetView.$el ); + + if( currentIndex == 0 ) { + return false; + } + else { + var widgetView = widgets.eq(currentIndex - 1).data('view'); + if(typeof widgetView == 'undefined') return false; + + return widgetView.getEditDialog(); + } + }, + + /** + * Get the next widget editing dialog by looking at the dom. + * @returns {*} + */ + getNextDialog: function(){ + var widgets = this.builder.$('.so-cells .cell .so-widget'); + if(widgets.length <= 1) return false; + var currentIndex = widgets.index( this.widgetView.$el ); + + if( currentIndex == widgets.length - 1 ) { + return false; + } + else { + var widgetView = widgets.eq(currentIndex + 1).data('view'); + if(typeof widgetView == 'undefined') return false; + + return widgetView.getEditDialog(); + } + }, + + /** + * Load the widget form from the server + */ + loadForm: function(){ + var thisView = this; + this.$el.find('.so-content').addClass('so-panels-loading'); + + var data = { + 'action' : 'so_panels_widget_form', + 'widget' : this.model.get('class'), + 'instance' : JSON.stringify( this.model.get('values') ), + 'raw' : this.model.get('raw') + }; + + $.post( + ajaxurl, + data, + function(result){ + // Add in the CID of the widget model + var html = result.replace( /\{\$id\}/g, thisView.model.cid ); + + // Load this content into the form + thisView.$el.find('.so-content') + .removeClass('so-panels-loading') + .html(html); + + // Trigger all the necessary events + thisView.trigger('form_loaded', thisView); + + // For legacy compatibility, trigger a panelsopen event + thisView.$el.find('.panel-dialog').trigger('panelsopen'); + + // If the main dialog is closed from this point on, save the widget content + thisView.on('close_dialog', thisView.saveWidget, thisView); + }, + 'html' + ); + }, + + /** + * Save the widget from the form to the model + */ + saveWidget: function(){ + // Get the values from the form and assign the new values to the model + var values = this.getFormValues(); + if(typeof values.widgets == 'undefined') return; + values = values.widgets; + values = values[Object.keys(values)[0]]; + + this.model.setValues(values); + this.model.set('raw', true); // We've saved from the widget form, so this is now raw + + if( this.styles.stylesLoaded ) { + // If the styles view has loaded + var style = {}; + try { + var style = this.getFormValues('.so-sidebar .so-visual-styles').style; + } + catch (e) { + } + this.model.set('style', style); + } + }, + + saveHistory: function(){ + this.builder.addHistoryEntry('widget_edited'); + this.closeDialog(); + }, + + deleteHandler: function(){ + + if(this.builder.liveEditor.displayed) { + // We need to instantly destroy the widget + this.model.destroy(); + this.builder.liveEditor.refreshWidgets(); + } + else { + this.model.trigger('visual_destroy'); + } + + this.closeDialog(); + + return false; + }, + + duplicateHandler: function(){ + this.model.trigger('user_duplicate'); + + if(this.builder.liveEditor.displayed) { + this.builder.liveEditor.refreshWidgets(); + } + + this.closeDialog(); + + return false; + } + + } ); + + /** + * The dialog box for displaying prebuilt layouts. + */ + panels.dialog.prebuilt = panels.view.dialog.extend( { + + entryTemplate : _.template( $('#siteorigin-panels-dialog-prebuilt-entry').html() ), + builder: null, + dialogClass : 'so-panels-dialog-prebuilt-layouts', + + layoutCache : {}, + currentTab : false, + + events: { + 'click .so-close': 'closeDialog', + 'click .so-sidebar-tabs li a' : 'tabClickHandler', + 'click .so-content .layout' : 'layoutClickHandler', + 'keyup .so-sidebar-search' : 'searchHandler' + }, + + /** + * Initialize the prebuilt dialog. + */ + initializeDialog: function(){ + var thisView = this; + + this.on('open_dialog', function(){ + thisView.$('.so-sidebar-tabs li a[href="#prebuilt"]').click(); + }); + }, + + /** + * Render the prebuilt layouts dialog + */ + render: function(){ + this.renderDialog( this.parseDialogContent( $('#siteorigin-panels-dialog-prebuilt').html(), {} ) ); + }, + + /** + * + * @param e + * @return {boolean} + */ + tabClickHandler: function(e){ + this.$('.so-sidebar-tabs li').removeClass('tab-active'); + + var $$ = $(e.target); + var tab = $$.attr('href').split('#')[1]; + $$.parent().addClass( 'tab-active' ); + + var thisView = this; + + // Empty everything + this.$('.so-content').empty(); + + this.currentTab = tab; + + if( typeof this.layoutCache[tab] == 'undefined' ) { + // We need to load the tab items from the server + this.$('.so-content').addClass('so-panels-loading'); + + $.post( + ajaxurl, + { + action: 'so_panels_prebuilt_layouts', + type: tab + }, + function(layouts){ + thisView.layoutCache[ tab ] = layouts; + thisView.$( '.so-content' ).removeClass( 'so-panels-loading' ); + thisView.displayLayouts( tab, layouts ); + } + ); + } + else { + thisView.displayLayouts(tab, this.layoutCache[tab]); + } + + return false; + }, + + /** + * Display a list of layouts taking into account the search argument + */ + displayLayouts: function(type, layouts){ + var c = this.$('.so-content').empty(); + var query = this.$('.so-sidebar-search').val().toLowerCase(); + + if( typeof layouts.error_message != 'undefined' ) { + this.$('.so-content').append( + $('
').html( layouts.error_message ) + ); + return; + } + + for(var lid in layouts) { + // Exclude the current post if we have one + if( type != 'prebuilt' && lid == $('#post_ID').val() ) continue; + if(query != '' && layouts[lid].name.toLowerCase().indexOf( query ) === -1 ) continue; + + var $l = $( this.entryTemplate( { + name: layouts[lid].name, + description: layouts[lid].description + } ) ); + + // Create and append the + $l.appendTo(c).data({'type' : type, 'lid' : lid}); + } + }, + + /** + * Make the layout selected. + * @param e + */ + layoutClickHandler: function(e){ + var layout = $(e.target).closest('.layout'); + + this.loadLayout( + layout.data('type'), + layout.data('lid') + ); + + return false; + }, + + /** + * Load the layout into the main builder + */ + loadLayout: function(type, lid){ + var thisView = this; + + if( !confirm(panelsOptions.loc.prebuilt_confirm) ) return false; + this.setStatusMessage(panelsOptions.loc.prebuilt_loading, true); + + $.post( + ajaxurl, + { + action: 'so_panels_get_prebuilt_layout', + type: type, + lid: lid + }, + function(layout){ + // TODO check for an error message + thisView.setStatusMessage('', false); + thisView.builder.addHistoryEntry('prebuilt_loaded'); + + console.log(layout); + + thisView.builder.model.loadPanelsData(layout); + thisView.closeDialog(); + + } + ); + }, + + /** + * Handle an update to the search + */ + searchHandler: function(){ + if( this.currentTab == false || typeof this.layoutCache[ this.currentTab ] == 'undefined') return; + this.displayLayouts(this.currentTab, this.layoutCache[ this.currentTab ] ); + } + + } ); + + /** + * The dialog for adding and editing a row + */ + panels.dialog.row = panels.view.dialog.extend( { + + cellPreviewTemplate : _.template( $('#siteorigin-panels-dialog-row-cell-preview').html() ), + + events: { + 'click .so-close': 'closeDialog', + + // Toolbar buttons + 'click .so-toolbar .so-save': 'saveHandler', + 'click .so-toolbar .so-insert': 'insertHandler', + 'click .so-toolbar .so-delete': 'deleteHandler', + 'click .so-toolbar .so-duplicate': 'duplicateHandler', + + // Changing the row + 'change .row-set-form > *': 'setCellsFromForm', + 'click .row-set-form button.set-row': 'setCellsFromForm' + }, + + dialogClass : 'so-panels-dialog-row-edit', + styleType : 'row', + + /* This is used by */ + dialogType : 'edit', + + /** + * The current settings, not yet saved to the model + */ + row : { + // This is just the cell weights, cell content is not edited by this dialog + cells : [ ], + // The style settings of the row + style : { } + }, + + initializeDialog: function(){ + this.on('open_dialog', function(){ + if( typeof this.model != 'undefined' && this.model.cells.length != 0 ) { + this.setRowModel( this.model ); + } + else { + this.setRowModel( null ); + } + + this.regenerateRowPreview(); + }, this); + + // This is the default row layout + this.row = { + cells : [0.5, 0.5], + style : { } + } + }, + + /** + * + * @param dialogType Either "edit" or "create" + */ + setRowDialogType: function(dialogType){ + this.dialogType = dialogType; + }, + + /** + * Render the new row dialog + */ + render: function(dialogType){ + this.renderDialog( this.parseDialogContent( $('#siteorigin-panels-dialog-row').html(), { dialogType: this.dialogType } ) ); + + if( this.dialogType == 'edit' ) { + // Now we need to attach the style window + this.styles = new panels.view.styles(); + this.styles.model = this.model; + this.styles.render( 'row' ); + this.styles.attach( this.$('.so-sidebar.so-right-sidebar') ); + + // Handle the loading class + this.styles.on('styles_loaded', function(){ + this.$('.so-sidebar.so-right-sidebar').removeClass('so-panels-loading'); + }, this); + this.$('.so-sidebar.so-right-sidebar').addClass('so-panels-loading'); + } + + if( typeof this.model != 'undefined' ) { + // Set the initial value of the + this.$('input.so-row-field').val( this.model.cells.length ); + } + + var thisView = this; + this.$('input.so-row-field').keyup( function(){ + $(this).trigger('change'); + } ); + + return this; + }, + + /** + * Set the row model we'll be using for this dialog. + * + * @param model + */ + setRowModel: function(model){ + this.model = model; + if( this.model == null ) return; + + // Set the rows to be a copy of the model + this.row = { + cells: this.model.cells.map( function(cell){ + return cell.get('weight'); + } ), + style: { } + } + + // Set the initial value of the cell field. + this.$('input.so-row-field').val( this.model.cells.length ); + + return this; + }, + + /** + * Regenerate the row preview and resizing interface. + * + * @todo refactor this so we use the original row view. + */ + regenerateRowPreview: function(){ + var thisDialog = this; + var rowPreview = this.$('.row-preview'); + + rowPreview.empty(); + + var timeout; + + // Represent the cells + _.each(this.row.cells, function(cell, i){ + var newCell = $( this.cellPreviewTemplate( { weight: cell } ) ); + rowPreview.append( newCell ); + + var prevCell = newCell.prev(); + var handle; + + if( prevCell.length != 0 ) { + handle = $('
'); + handle + .appendTo( newCell ) + .dblclick(function(){ + var t = thisDialog.row.cells[i] + thisDialog.row.cells[i-1]; + thisDialog.row.cells[i] = thisDialog.row.cells[i-1] = t/2; + thisDialog.scaleRowWidths(); + }); + + handle.draggable({ + axis: 'x', + containment: rowPreview, + start: function(e, ui){ + + // Create the clone for the current cell + var newCellClone = newCell.clone().appendTo(ui.helper).css({ + position : 'absolute', + top : '0', + width : newCell.outerWidth(), + left : 6, + height: newCell.outerHeight() + }); + newCellClone.find('.resize-handle').remove(); + + // Create the clone for the previous cell + var prevCellClone = prevCell.clone().appendTo(ui.helper).css({ + position : 'absolute', + top : '0', + width : prevCell.outerWidth(), + right : 6, + height: prevCell.outerHeight() + }); + prevCellClone.find('.resize-handle').remove(); + + $(this).data({ + 'newCellClone' : newCellClone, + 'prevCellClone' : prevCellClone + }); + + // Hide the + newCell.find('> .preview-cell-in').css('visibility', 'hidden'); + prevCell.find('> .preview-cell-in').css('visibility', 'hidden'); + }, + drag: function(e, ui){ + // Calculate the new cell and previous cell widths as a percent + var ncw = thisDialog.row.cells[i] - ( ( ui.position.left + 6 ) / rowPreview.width() ); + var pcw = thisDialog.row.cells[i-1] + ( ( ui.position.left + 6 ) / rowPreview.width() ); + + var helperLeft = ui.helper.offset().left - rowPreview.offset().left - 6; + + $(this).data('newCellClone').css('width', rowPreview.width() * ncw ) + .find('.preview-cell-weight').html( Math.round(ncw*1000)/10 ); + + $(this).data('prevCellClone').css('width', rowPreview.width() * pcw ) + .find('.preview-cell-weight').html( Math.round(pcw*1000)/10 ); + }, + stop: function(e, ui){ + // Remove the clones + $(this).data('newCellClone').remove(); + $(this).data('prevCellClone').remove(); + + // Reshow the main cells + newCell.find('.preview-cell-in').css('visibility', 'visible'); + prevCell.find('.preview-cell-in').css('visibility', 'visible'); + + // Calculate the new cell weights + var offset = ui.position.left + 6; + var percent = offset / rowPreview.width(); + + // Ignore this if any of the cells are below 2% in width. + if( thisDialog.row.cells[i] - percent > 0.02 && thisDialog.row.cells[i-1] + percent > 0.02 ) { + thisDialog.row.cells[i] -= percent; + thisDialog.row.cells[i-1] += percent; + } + + thisDialog.scaleRowWidths(); + ui.helper.css('left', -6); + } + }); + } + + // Make this row weight click editable + newCell.find('.preview-cell-weight').click(function(ci){ + + // Disable the draggable while entering values + thisDialog.$('.resize-handle').css('pointer-event', 'none').draggable('disable'); + + rowPreview.find('.preview-cell-weight').each( function(){ + var $$ = $(this).hide(); + $('') + .val( parseFloat( $$.html() ) ).insertAfter( $$ ) + .focus( function(){ + clearTimeout( timeout ); + } ) + .keyup(function(e){ + // Enter is clicked + if(e.keyCode == 13){ + e.preventDefault(); + + $(this).removeClass('no-user-interacted'); + + // Select the next input + var inputs = rowPreview.find('.preview-cell-weight-input'); + var index = inputs.index( $(this) ); + + if(index == inputs.length - 1) index = 0; // Go to first input + else index = index + 1; // Go to next + + var next = rowPreview.find('.preview-cell-weight-input').eq( index ); + + // Either go to the next input or blur to set + if( !next.hasClass('no-user-interacted') ) $(this).blur(); + else next.select(); + } + }) + .blur( function(){ + timeout = setTimeout( function(){ + // If there are no weight inputs, then skip this + if( rowPreview.find( '.preview-cell-weight-input').length == 0 ) return; + + // Go through all the inputs + var rowWeights = []; + rowPreview.find( '.preview-cell-weight-input' ).each(function(i, el){ + var val = parseFloat( $(el).val() ); + if( val == NaN ) val = 1 / thisDialog.cells.length; + else val = val / 100; + + rowWeights.push( val ); + }); + + // Make sure the sum is 1 + var sum = 0; + for( var j = 0; j < rowWeights.length; j++ ) { + sum += rowWeights[j]; + } + for( var j = 0; j < rowWeights.length; j++ ) { + rowWeights[j] = rowWeights[j] / sum; + } + + // Set the new cell weights and regenerate the preview. + if( Math.min.apply(Math, rowWeights) > 0.01 ) { + thisDialog.row.cells = rowWeights; + } + + thisDialog.regenerateRowPreview(); + + }, 100 ); + } ) + .click( function(){ + rowPreview.find('.preview-cell-weight-input').addClass('no-user-interacted'); + $(this).select(); + } ); + } ); + + $(this).siblings('.preview-cell-weight-input').select(); + + }); + + }, this); + }, + + /** + * Visually scale the row widths based on the cell weights + */ + scaleRowWidths: function(){ + var thisDialog = this; + this.$('.row-preview .preview-cell').each(function(i, el){ + $(el) + .css('width', thisDialog.row.cells[i] * 100 + "%") + .find('.preview-cell-weight').html( Math.round( thisDialog.row.cells[i] * 1000 )/10 ) + }); + }, + + /** + * Get the weights from the + */ + setCellsFromForm: function(){ + var f = { + 'cells' : parseInt( this.$el.find('.row-set-form input[name="cells"]').val() ), + 'ratio' : parseFloat( this.$el.find('.row-set-form select[name="ratio"]').val() ), + 'direction' : this.$el.find('.row-set-form select[name="ratio_direction"]').val() + } + var cells = []; + + // Ignore this if the ratio or cell count is NaN + if( isNaN(f.cells) || isNaN(f.ratio) ) return; + + if( f.cells < 1 ) { + this.$el.find('.row-set-form input[name="cells"]').val(1); + f.cells = 1; + } + else if (f.cells > 20) { + this.$el.find('.row-set-form input[name="cells"]').val(20); + f.cells = 20; + } + + // Now, lets create some cells + var currentWeight = 1; + for( var i = 0; i < f.cells; i++ ) { + cells.push (currentWeight); + currentWeight *= f.ratio; + } + + // Now lets make sure that the row weights add up to 1 + + var totalRowWeight = _.reduce( cells, function(memo, weight){ return memo + weight }); + cells = _.map(cells, function(cell){ + return cell/totalRowWeight; + }); + + // Don't return cells that are too small + cells = _.filter(cells, function(cell){ return cell > 0.01 }); + + if(f.direction == 'left') { + cells = cells.reverse(); + } + + this.row.cells = cells; + this.regenerateRowPreview(); + + // Remove the button primary class + this.$el.find('.row-set-form .so-button-row-set').removeClass('button-primary'); + }, + + /** + * Handle a click on the dialog left bar tab + */ + tabClickHandler : function($t){ + if($t.attr('href') == '#row-layout') { + this.$('.so-panels-dialog').addClass('so-panels-dialog-has-right-sidebar'); + } + else { + this.$('.so-panels-dialog').removeClass('so-panels-dialog-has-right-sidebar'); + } + }, + + /** + * Update the current model with what we have in the dialog + */ + updateModel: function(){ + // Set the cells + this.model.setCells( this.row.cells ); + + // Update the styles if they've loaded + if ( typeof this.styles != 'undefined' && this.styles.stylesLoaded ) { + // This is an edit dialog, so there are styles + var style = {}; + try { + var style = this.getFormValues('.so-sidebar .so-visual-styles').style; + } + catch( e ) { } + + this.model.set('style', style); + } + }, + + /** + * Insert the new row + */ + insertHandler: function(){ + this.builder.addHistoryEntry('row_added'); + + this.model = new panels.model.row(); + this.updateModel(); + + // Set up the model and add it to the builder + this.model.collection = this.builder.model.rows; + this.builder.model.rows.add( this.model ); + + this.closeDialog(); + + return false; + }, + + /** + * We'll just save this model and close the dialog + */ + saveHandler: function(){ + this.builder.addHistoryEntry('row_edited'); + this.updateModel(); + this.closeDialog(); + + return false; + }, + + /** + * The user clicks delete, so trigger deletion on the row model + */ + deleteHandler: function(){ + // Trigger a destroy on the model that will happen with a visual indication to the user + this.model.trigger('visual_destroy'); + this.closeDialog(); + + return false; + }, + + /** + * Duplicate this row + */ + duplicateHandler: function(){ + this.builder.addHistoryEntry('row_duplicated'); + + var duplicateRow = this.model.clone( this.builder.model ); + + this.builder.model.rows.add( duplicateRow, { + at: this.builder.model.rows.indexOf( this.model ) + 1 + } ); + + this.closeDialog(); + + return false; + } + + } ); + + // Return the SiteOrigin Panels app + window.siteoriginPanels = panels; + +} )( jQuery, _, soPanelsOptions ); + +// Set up Page Builder if we're on the main interface +jQuery( function($){ + + var container = false, field = false, form = false, postId = false; + + if( $('#siteorigin-panels-metabox').length && $('form#post').length ) { + container = $( '#siteorigin-panels-metabox' ); + field = $( '#siteorigin-panels-metabox .siteorigin-panels-data-field' ); + form = $('form#post'); + postId = $('#post_ID').val(); + } + else if( $('div#panels-home-page.wrap').length ) { + // We're dealing with the custom home page interface + var $$ = $('div#panels-home-page.wrap'); + container = $$.find('.siteorigin-panels-builder'); + field = $$.find('input[name="panels_data"]'); + form = $$.find('form'); + postId = $('#panels-home-page').data('post-id'); + } + + if( container != false ) { + // If we have a container, then set up the main builder + var panels = window.siteoriginPanels; + + // Create the main builder model + var builderModel = new panels.model.builder(); + + // Now for the view to display the builder + var builderView = new panels.view.builder( { + model: builderModel + } ); + + // Set up the builder view + builderView + .render() + .attach( { container: container } ) + .setDataField( field ) + .attachToEditor() + .addLiveEditor( postId ) + .addHistoryBrowser(); + + // When the form is submitted, update the panels data + form.submit( function(e){ + // Refresh the data + builderModel.refreshPanelsData(); + } ); + + container.removeClass('so-panels-loading'); + } +} ); + +// A basic jQuery plugin for setting up a Page Builder widget. +(function ( $ ) { + + var panels = window.siteoriginPanels; + + $.fn.soPanelsSetupBuilderWidget = function () { + + return this.each(function(){ + var $$ = $(this); + var widgetId = $$.closest('form').find('.widget-id').val(); + + // Exit if this isn't a real widget + if( widgetId != null && widgetId.indexOf('__i__') > -1 ) { + return; + } + + // Create the main builder model + var builderModel = new panels.model.builder(); + + // Now for the view to display the builder + var builderView = new panels.view.builder( { + model: builderModel + } ); + + // Save panels data when we close the dialog, if we're in a dialog + var dialog = $$.closest('.so-panels-dialog-wrapper').data('view'); + if( dialog != null ) { + dialog.on('close_dialog', function(){ + builderModel.refreshPanelsData(); + } ); + + dialog.on('open_dialog_complete', function(){ + // Make sure the new layout widget is always properly setup + builderView.trigger('builder_resize'); + }); + + dialog.model.on('destroy', function(){ + // Destroy the builder + builderModel.emptyRows().destroy(); + } ); + + // Set the parent for all the sub dialogs + builderView.setDialogParents(soPanelsOptions.loc.layout_widget, dialog); + } + + // Basic setup for the builder + var isWidget = Boolean( $$.closest('.widget-content').length ); + builderView + .render() + .attach( { container: $$, dialog: isWidget } ) + .setDataField( $$.find('input.panels-data') ); + + if( isWidget ) { + // Set up the dialog opening + builderView.setDialogParents(soPanelsOptions.loc.layout_widget, builderView.dialog); + $$.find( '.siteorigin-panels-display-builder').click(function(){ + builderView.dialog.openDialog(); + }); + } + else { + // Remove the dialog opener button, this is already being displayed in a page builder dialog. + $$.find( '.siteorigin-panels-display-builder').parent().remove(); + } + + }); + }; + + // Setup new widgets when they're added in the standard widget interface + $(document).on('widget-added', function(e, widget) { + $(widget).find('.siteorigin-page-builder-widget').soPanelsSetupBuilderWidget(); + }); + + // Setup existing widgets on the page (for the widgets interface) + $(function(){ + $('.siteorigin-page-builder-widget').soPanelsSetupBuilderWidget(); + }); + +})(jQuery); \ No newline at end of file diff --git a/js/styling.js b/js/styling.js new file mode 100644 index 000000000..a06370b98 --- /dev/null +++ b/js/styling.js @@ -0,0 +1,45 @@ +jQuery(function($){ + + // This will handle stretching the cells. + $('.siteorigin-panels-stretch.panel-row-style').each(function(){ + var $$ = $(this); + + var onResize = function(){ + + $$.css({ + 'margin-left' : 0, + 'margin-right' : 0, + 'padding-left' : 0, + 'padding-right' : 0 + }); + + var leftSpace = $$.offset().left; + var rightSpace = $(window).outerWidth() - $$.offset().left - $$.parent().outerWidth(); + + $$.css({ + 'margin-left' : -leftSpace, + 'margin-right' : -rightSpace, + 'padding-left' : $$.data('stretch-type') == 'full' ? leftSpace : 0, + 'padding-right' : $$.data('stretch-type') == 'full' ? rightSpace : 0 + }); + + var cells = $$.find('> .panel-grid-cell'); + + if( $$.data('stretch-type') == 'full-stretched' && cells.length == 1 ) { + cells.css({ + 'padding-left' : 0, + 'padding-right' : 0 + }); + } + } + + $(window).resize( onResize ); + onResize(); + + $$.css({ + 'border-left' : 0, + 'border-right' : 0 + }); + }); + +}); \ No newline at end of file diff --git a/lang/siteorigin-panels.po b/lang/siteorigin-panels.po new file mode 100644 index 000000000..d6684e42a --- /dev/null +++ b/lang/siteorigin-panels.po @@ -0,0 +1,827 @@ +msgid "" +msgstr "" +"Project-Id-Version: Page Builder by SiteOrigin\n" +"POT-Creation-Date: 2014-09-15 22:17+0200\n" +"PO-Revision-Date: 2014-09-15 22:17+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.6.7\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-SourceCharset: UTF-8\n" +"X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;esc_attr__;" +"esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c;_n_noop:1,2;" +"_nx_noop:3c,1,2;__ngettext_noop:1,2\n" +"X-Poedit-SearchPath-0: .\n" + +#: inc/legacy.php:22 siteorigin-panels.php:104 +msgid "Home" +msgstr "" + +#: inc/notice.php:17 +#, php-format +msgid "" +"You've successfully installed Page Builder version %s. " +msgstr "" + +#: inc/notice.php:20 +#, php-format +msgid "" +"You've successfully updated Page Builder to version %s. " +msgstr "" + +#: inc/notice.php:24 +#, php-format +msgid "" +"Please post on our support forums if " +"you have any issues and sign up to our " +"newsletter to stay up to date." +msgstr "" + +#: inc/notice.php:31 inc/notice.php:102 +msgid "Support Forums" +msgstr "" + +#: inc/notice.php:32 siteorigin-panels.php:1170 +msgid "Newsletter" +msgstr "" + +#: inc/notice.php:34 inc/notice.php:103 +msgid "Dismiss" +msgstr "" + +#: inc/notice.php:84 +msgid "" +"One or more of your active plugins are known to be incompatible with Page " +"Builder." +msgstr "" + +#: inc/notice.php:93 +msgid "More" +msgstr "" + +#: inc/options.php:68 tpl/options.php:5 +msgid "SiteOrigin Page Builder" +msgstr "" + +#: inc/options.php:107 +msgid "Post types that will have the page builder available" +msgstr "" + +#: inc/options.php:122 +msgid "Enabled" +msgstr "" + +#: inc/options.php:127 +msgid "px" +msgstr "" + +#: inc/revisions.php:50 +msgid "Page Builder Content" +msgstr "" + +#: inc/styles.php:19 +msgid "Class" +msgstr "" + +#: inc/styles.php:22 widgets/basic.php:59 widgets/basic.php:482 +#: widgets/widgets.php:625 +msgid "Default" +msgstr "" + +#: inc/styles.php:36 +msgid "Your theme doesn't provide any visual style fields. " +msgstr "" + +#: siteorigin-panels.php:65 +msgid "Custom Home Page Builder" +msgstr "" + +#: siteorigin-panels.php:66 +msgid "Home Page" +msgstr "" + +#: siteorigin-panels.php:78 siteorigin-panels.php:87 siteorigin-panels.php:336 +msgid "Page Builder" +msgstr "" + +#: siteorigin-panels.php:229 +msgid "Insert" +msgstr "" + +#: siteorigin-panels.php:230 +msgid "cancel" +msgstr "" + +#: siteorigin-panels.php:231 +msgid "Delete" +msgstr "" + +#: siteorigin-panels.php:232 +msgid "Duplicate" +msgstr "" + +#: siteorigin-panels.php:233 +msgid "Edit" +msgstr "" + +#: siteorigin-panels.php:234 +msgid "Done" +msgstr "" + +#: siteorigin-panels.php:235 +msgid "Undo" +msgstr "" + +#: siteorigin-panels.php:236 +msgid "Add" +msgstr "" + +#: siteorigin-panels.php:239 +msgid "Columns deleted" +msgstr "" + +#: siteorigin-panels.php:240 +msgid "Widget deleted" +msgstr "" + +#: siteorigin-panels.php:241 +msgid "" +"Are you sure you want to load this layout? It will overwrite your current " +"page." +msgstr "" + +#: siteorigin-panels.php:242 +#, php-format +msgid "Edit %s Widget" +msgstr "" + +#: siteorigin-panels.php:262 +msgid "Install the missing widget" +msgstr "" + +#: siteorigin-panels.php:853 +msgid "Edit Home Page" +msgstr "" + +#: siteorigin-panels.php:965 +msgid "Untitled" +msgstr "" + +#: siteorigin-panels.php:966 +msgid "Unpublished" +msgstr "" + +#: siteorigin-panels.php:971 +#, php-format +msgid "Clone Page: %s" +msgstr "" + +#: siteorigin-panels.php:984 +msgid "Clone: Current Home Page" +msgstr "" + +#: siteorigin-panels.php:1003 +msgid "Recommended Plugins and Widgets" +msgstr "" + +#: siteorigin-panels.php:1004 +msgid "Free plugins that work well with Page Builder" +msgstr "" + +#: siteorigin-panels.php:1097 +msgid "This widget is not available, please install the missing plugin." +msgstr "" + +#: siteorigin-panels.php:1169 +msgid "Support Forum" +msgstr "" + +#: tpl/admin-home-page.php:7 +msgid "Custom Home Page" +msgstr "" + +#: tpl/admin-home-page.php:10 +msgid "ON" +msgstr "" + +#: tpl/admin-home-page.php:11 +msgid "OFF" +msgstr "" + +#: tpl/admin-home-page.php:24 +#, php-format +msgid "Home page updated. View page" +msgstr "" + +#: tpl/admin-home-page.php:31 +msgid "Preview Changes" +msgstr "" + +#: tpl/admin-home-page.php:36 +msgid "Save Home Page" +msgstr "" + +#: tpl/admin-home-page.php:44 +msgid "This interface requires Javascript" +msgstr "" + +#: tpl/help.php:2 +msgid "" +"You can use SiteOrigin Page Builder to create home and sub pages, filled " +"your own widgets." +msgstr "" + +#: tpl/help.php:3 +msgid "The page layouts are responsive and fully customizable." +msgstr "" + +#: tpl/help.php:8 +#, php-format +msgid "" +"Read the full documentation on SiteOrigin." +msgstr "" + +#: tpl/help.php:9 +#, php-format +msgid "" +"Ask a question on our support forum if you " +"need help and sign up to our newsletter to " +"stay up to date with future developments." +msgstr "" + +#: tpl/metabox-panels.php:14 +msgid "Add Widget" +msgstr "" + +#: tpl/metabox-panels.php:15 tpl/metabox-panels.php:59 +msgid "Add Row" +msgstr "" + +#: tpl/metabox-panels.php:17 +msgid "Prebuilt Layouts" +msgstr "" + +#: tpl/metabox-panels.php:20 +msgid "Switch to Editor" +msgstr "" + +#: tpl/metabox-panels.php:27 +msgid "Add New Widget" +msgstr "" + +#: tpl/metabox-panels.php:60 widgets/basic.php:76 +msgid "Columns" +msgstr "" + +#: tpl/metabox-panels.php:67 +msgid "Insert Prebuilt Page Layout" +msgstr "" + +#: tpl/metabox-panels.php:68 +msgid "Page Layout" +msgstr "" + +#: tpl/metabox-panels.php:70 +msgid "Select Layout" +msgstr "" + +#: tpl/metabox-panels.php:74 +msgid "Untitled Layout" +msgstr "" + +#: tpl/metabox-panels.php:83 +msgid "Row Visual Style" +msgstr "" + +#: tpl/options.php:11 +msgid "General" +msgstr "" + +#: tpl/options.php:15 +msgid "Post Types" +msgstr "" + +#: tpl/options.php:25 +msgid "Copy Content" +msgstr "" + +#: tpl/options.php:26 +msgid "Copy content from Page Builder into the standard content editor." +msgstr "" + +#: tpl/options.php:32 +msgid "Animations" +msgstr "" + +#: tpl/options.php:33 +msgid "Disable animations for improved performance." +msgstr "" + +#: tpl/options.php:39 +msgid "Bundled Widgets" +msgstr "" + +#: tpl/options.php:40 +msgid "Include the bundled widgets." +msgstr "" + +#: tpl/options.php:48 +msgid "Display" +msgstr "" + +#: tpl/options.php:57 +msgid "Responsive Layout" +msgstr "" + +#: tpl/options.php:58 +msgid "Should the layout collapse for mobile devices." +msgstr "" + +#: tpl/options.php:64 +msgid "Mobile Width" +msgstr "" + +#: tpl/options.php:70 +msgid "Row Bottom Margin" +msgstr "" + +#: tpl/options.php:76 +msgid "Cell Side Margins" +msgstr "" + +#: tpl/options.php:87 +msgid "Save Settings" +msgstr "" + +#: video/jplayer/skins/premium/gui.php:3 +#: video/jplayer/skins/siteorigin/gui.php:3 +msgid "play" +msgstr "" + +#: video/jplayer/skins/premium/gui.php:4 +#: video/jplayer/skins/siteorigin/gui.php:4 +msgid "pause" +msgstr "" + +#: video/jplayer/skins/premium/gui.php:5 +#: video/jplayer/skins/siteorigin/gui.php:5 +msgid "stop" +msgstr "" + +#: video/jplayer/skins/premium/gui.php:7 +#: video/jplayer/skins/siteorigin/gui.php:7 +msgid "full screen" +msgstr "" + +#: video/jplayer/skins/premium/gui.php:8 +#: video/jplayer/skins/siteorigin/gui.php:8 +msgid "restore screen" +msgstr "" + +#: video/jplayer/skins/premium/gui.php:10 +msgid "mute" +msgstr "" + +#: video/jplayer/skins/premium/gui.php:11 +msgid "unmute" +msgstr "" + +#: widgets/basic.php:7 +msgid "Gallery (PB)" +msgstr "" + +#: widgets/basic.php:9 +msgid "Displays a gallery." +msgstr "" + +#: widgets/basic.php:48 +msgid "Gallery Images" +msgstr "" + +#: widgets/basic.php:49 +msgid "edit gallery" +msgstr "" + +#: widgets/basic.php:53 +msgid "" +"Comma separated attachment IDs. Defaults to all current page's attachments." +msgstr "" + +#: widgets/basic.php:57 +msgid "Image Size" +msgstr "" + +#: widgets/basic.php:60 +msgid "Large" +msgstr "" + +#: widgets/basic.php:61 +msgid "Medium" +msgstr "" + +#: widgets/basic.php:62 +msgid "Thumbnail" +msgstr "" + +#: widgets/basic.php:63 +msgid "Full" +msgstr "" + +#: widgets/basic.php:71 +msgid "Gallery Type" +msgstr "" + +#: widgets/basic.php:81 +msgid "Link To" +msgstr "" + +#: widgets/basic.php:83 +msgid "Attachment Page" +msgstr "" + +#: widgets/basic.php:84 +msgid "File" +msgstr "" + +#: widgets/basic.php:85 widgets/basic.php:145 widgets/basic.php:455 +#: widgets/widgets.php:598 +msgid "None" +msgstr "" + +#: widgets/basic.php:97 +msgid "Post Content (PB)" +msgstr "" + +#: widgets/basic.php:99 +msgid "Displays some form of post content form the current post." +msgstr "" + +#: widgets/basic.php:146 widgets/basic.php:420 +#: widgets/widgets/call-to-action/call-to-action.php:15 +#: widgets/widgets/list/list.php:15 widgets/widgets/price-box/price-box.php:15 +msgid "Title" +msgstr "" + +#: widgets/basic.php:147 +msgid "Featured Image" +msgstr "" + +#: widgets/basic.php:152 +msgid "Display Content" +msgstr "" + +#: widgets/basic.php:167 +msgid "Image (PB)" +msgstr "" + +#: widgets/basic.php:169 +msgid "Displays a simple image." +msgstr "" + +#: widgets/basic.php:202 widgets/widgets/animated-image/animated-image.php:15 +msgid "Image URL" +msgstr "" + +#: widgets/basic.php:206 widgets/widgets/button/button.php:19 +msgid "Destination URL" +msgstr "" + +#: widgets/basic.php:222 +msgid "Post Loop (PB)" +msgstr "" + +#: widgets/basic.php:224 +msgid "Displays a post loop." +msgstr "" + +#: widgets/basic.php:409 +msgid "Your theme doesn't have any post loops." +msgstr "" + +#: widgets/basic.php:424 +msgid "Template" +msgstr "" + +#: widgets/basic.php:439 widgets/widgets.php:584 +msgid "Post Type" +msgstr "" + +#: widgets/basic.php:448 widgets/widgets.php:591 +msgid "Posts Per Page" +msgstr "" + +#: widgets/basic.php:453 widgets/widgets.php:596 +msgid "Order By" +msgstr "" + +#: widgets/basic.php:456 widgets/widgets.php:599 +msgid "Post ID" +msgstr "" + +#: widgets/basic.php:457 widgets/widgets.php:600 +msgid "Author" +msgstr "" + +#: widgets/basic.php:458 widgets/basic.php:459 widgets/widgets.php:601 +#: widgets/widgets.php:602 widgets/widgets/testimonial/testimonial.php:15 +msgid "Name" +msgstr "" + +#: widgets/basic.php:460 widgets/widgets.php:603 +msgid "Date" +msgstr "" + +#: widgets/basic.php:461 widgets/widgets.php:604 +msgid "Modified" +msgstr "" + +#: widgets/basic.php:462 widgets/widgets.php:605 +msgid "Parent" +msgstr "" + +#: widgets/basic.php:463 widgets/widgets.php:606 +msgid "Random" +msgstr "" + +#: widgets/basic.php:464 widgets/widgets.php:607 +msgid "Comment Count" +msgstr "" + +#: widgets/basic.php:465 widgets/basic.php:466 widgets/widgets.php:608 +msgid "Menu Order" +msgstr "" + +#: widgets/basic.php:467 +msgid "Post In Order" +msgstr "" + +#: widgets/basic.php:472 widgets/widgets.php:614 +msgid "Order" +msgstr "" + +#: widgets/basic.php:474 widgets/widgets.php:617 +msgid "Descending" +msgstr "" + +#: widgets/basic.php:475 widgets/widgets.php:616 +msgid "Ascending" +msgstr "" + +#: widgets/basic.php:480 widgets/widgets.php:623 +msgid "Sticky Posts" +msgstr "" + +#: widgets/basic.php:483 widgets/widgets.php:626 +msgid "Ignore Sticky" +msgstr "" + +#: widgets/basic.php:484 widgets/widgets.php:627 +msgid "Exclude Sticky" +msgstr "" + +#: widgets/basic.php:485 widgets/widgets.php:628 +msgid "Only Sticky" +msgstr "" + +#: widgets/basic.php:490 +msgid "More Link " +msgstr "" + +#: widgets/basic.php:492 +msgid "If the template supports it, cut posts and display the more link." +msgstr "" + +#: widgets/basic.php:496 +msgid "Additional " +msgstr "" + +#: widgets/basic.php:498 widgets/widgets.php:635 +#, php-format +msgid "" +"Additional query arguments. See query_posts." +msgstr "" + +#: widgets/basic.php:512 +msgid "Embedded Video (PB)" +msgstr "" + +#: widgets/basic.php:514 +msgid "Embeds a video." +msgstr "" + +#: widgets/basic.php:552 +msgid "Video" +msgstr "" + +#: widgets/basic.php:568 +msgid "Self Hosted Video (PB)" +msgstr "" + +#: widgets/basic.php:570 +msgid "A self hosted video player." +msgstr "" + +#: widgets/basic.php:645 +msgid "Video URL" +msgstr "" + +#: widgets/basic.php:649 +msgid "Poster URL" +msgstr "" + +#: widgets/basic.php:651 +msgid "An image that displays before the video starts playing." +msgstr "" + +#: widgets/basic.php:654 +msgid "Skin" +msgstr "" + +#: widgets/basic.php:656 +msgid "SiteOrigin" +msgstr "" + +#: widgets/basic.php:657 +msgid "Premium Pixels" +msgstr "" + +#: widgets/basic.php:661 +msgid "Aspect Ratio" +msgstr "" + +#: widgets/basic.php:663 +msgid "1.777 is HD standard." +msgstr "" + +#: widgets/basic.php:668 +msgid "Auto Play Video" +msgstr "" + +#: widgets/widgets.php:204 +msgid "Style" +msgstr "" + +#: widgets/widgets.php:228 +#, php-format +msgid "%s Style" +msgstr "" + +#: widgets/widgets.php:634 +msgid "Additional Arguments" +msgstr "" + +#: widgets/widgets/animated-image/animated-image.php:6 +msgid "Animated Image (PB)" +msgstr "" + +#: widgets/widgets/animated-image/animated-image.php:8 +msgid "An image that animates in when it enters the screen." +msgstr "" + +#: widgets/widgets/animated-image/animated-image.php:19 +msgid "Animation" +msgstr "" + +#: widgets/widgets/animated-image/animated-image.php:21 +msgid "Fade In" +msgstr "" + +#: widgets/widgets/animated-image/animated-image.php:22 +msgid "Slide Up" +msgstr "" + +#: widgets/widgets/animated-image/animated-image.php:23 +msgid "Slide Down" +msgstr "" + +#: widgets/widgets/animated-image/animated-image.php:24 +msgid "Slide Left" +msgstr "" + +#: widgets/widgets/animated-image/animated-image.php:25 +msgid "Slide Right" +msgstr "" + +#: widgets/widgets/button/button.php:6 +msgid "Button (PB)" +msgstr "" + +#: widgets/widgets/button/button.php:8 +msgid "A simple button" +msgstr "" + +#: widgets/widgets/button/button.php:15 widgets/widgets/list/list.php:19 +#: widgets/widgets/testimonial/testimonial.php:27 +msgid "Text" +msgstr "" + +#: widgets/widgets/button/button.php:23 +#: widgets/widgets/call-to-action/call-to-action.php:31 +#: widgets/widgets/price-box/price-box.php:44 +#: widgets/widgets/testimonial/testimonial.php:35 +msgid "Open In New Window" +msgstr "" + +#: widgets/widgets/button/button.php:27 +msgid "Button Alignment" +msgstr "" + +#: widgets/widgets/button/button.php:29 +msgid "Left" +msgstr "" + +#: widgets/widgets/button/button.php:30 +msgid "Right" +msgstr "" + +#: widgets/widgets/button/button.php:31 +msgid "Center" +msgstr "" + +#: widgets/widgets/button/button.php:32 +msgid "Justify" +msgstr "" + +#: widgets/widgets/call-to-action/call-to-action.php:6 +msgid "Call To Action (PB)" +msgstr "" + +#: widgets/widgets/call-to-action/call-to-action.php:8 +msgid "A Call to Action block" +msgstr "" + +#: widgets/widgets/call-to-action/call-to-action.php:19 +msgid "Sub Title" +msgstr "" + +#: widgets/widgets/call-to-action/call-to-action.php:23 +#: widgets/widgets/price-box/price-box.php:36 +msgid "Button Text" +msgstr "" + +#: widgets/widgets/call-to-action/call-to-action.php:27 +#: widgets/widgets/price-box/price-box.php:40 +msgid "Button URL" +msgstr "" + +#: widgets/widgets/call-to-action/call-to-action.php:37 +#: widgets/widgets/price-box/price-box.php:49 +msgid "Button" +msgstr "" + +#: widgets/widgets/list/list.php:6 +msgid "List (PB)" +msgstr "" + +#: widgets/widgets/list/list.php:8 widgets/widgets/price-box/price-box.php:8 +#: widgets/widgets/testimonial/testimonial.php:8 +msgid "Displays a bullet list of elements" +msgstr "" + +#: widgets/widgets/list/list.php:20 widgets/widgets/price-box/price-box.php:32 +msgid "Start each new point with an asterisk (*)" +msgstr "" + +#: widgets/widgets/price-box/price-box.php:6 +msgid "Price Box (PB)" +msgstr "" + +#: widgets/widgets/price-box/price-box.php:19 +msgid "Price" +msgstr "" + +#: widgets/widgets/price-box/price-box.php:23 +msgid "Per" +msgstr "" + +#: widgets/widgets/price-box/price-box.php:27 +msgid "Information Text" +msgstr "" + +#: widgets/widgets/price-box/price-box.php:31 +msgid "Features Text" +msgstr "" + +#: widgets/widgets/price-box/price-box.php:50 +msgid "Feature List" +msgstr "" + +#: widgets/widgets/testimonial/testimonial.php:6 +msgid "Testimonial (PB)" +msgstr "" + +#: widgets/widgets/testimonial/testimonial.php:19 +msgid "Location" +msgstr "" + +#: widgets/widgets/testimonial/testimonial.php:23 +msgid "Image" +msgstr "" + +#: widgets/widgets/testimonial/testimonial.php:31 +msgid "URL" +msgstr "" diff --git a/license.txt b/license.txt new file mode 100644 index 000000000..94a9ed024 --- /dev/null +++ b/license.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/readme.txt b/readme.txt new file mode 100644 index 000000000..d685c21af --- /dev/null +++ b/readme.txt @@ -0,0 +1,193 @@ +=== Page Builder by SiteOrigin === +Contributors: gpriday +Tags: page builder, responsive, widget, widgets, builder, page, admin, gallery, content, cms, pages, post, css, layout, grid +Requires at least: 3.7 +Tested up to: 4.0 +Stable tag: trunk +License: GPLv3 +License URI: http://www.gnu.org/licenses/gpl.html +Donate link: http://siteorigin.com/page-builder/#donate + +Build responsive page layouts using the widgets you know and love using this simple drag and drop page builder. + +== Description == + +[vimeo http://vimeo.com/59561067] + +WordPress has evolved into a fully functional CMS. Page Builder (previously called Panels) completes the transition by giving you a way to create responsive column layouts using the widgets you know and love. + += Use Your Widgets = + +You know widgets. They're the things you add to your sidebars. Page Builder makes all your widgets even more useful by turning them into the building blocks of your pages. + +We've included a few useful widgets, but it works with a lot of other widgets and plugins out there. + += Works with Most Themes = + +Page Builder works with most well made themes. The only requirement is that your theme supports pages. And if your theme is responsive, change a few settings and boom, your layouts will work with your theme and collapse into a single column on mobile devices. + +There are loads free and premium themes that work with the Page Builder, we have our own collection of [free themes](http://siteorigin.com/) if you'd like to use one of ours. + +Page Builder [Documentation](http://siteorigin.com/page-builder/documentation/) is available on SiteOrigin and we offer free support on our [support forum](http://siteorigin.com/threads/plugin-page-builder/). If you're having strange issues, try following [this guide](http://siteorigin.com/troubleshooting/identifying-plugin-conflicts/). + += Bundled Widgets = + +To get you started, we've include a few widgets: + +* Gallery widget for inserting image galleries. +* Image widget for inserting standard images. +* Self hosted video widget for embedding your own videos. +* Post Loop to display a list of posts. This requires that your theme supports it. + +As well as some essential page elements widgets: + +* Button +* Call to Action +* List +* Price Box +* Animated Image +* Testimonial + += 3rd Party Widgets = + +Most standard widgets work with Page Builder, but here are some of our favorites. + +* [SiteOrigin Widget Bundle](http://wordpress.org/plugins/so-widgets-bundle/) for growing collection of widgets like buttons, price tables and images. +* [Black Studio TinyMCE](http://wordpress.org/plugins/black-studio-tinymce-widget/) for a visual content editing widget. +* [Meta Slider](http://wordpress.org/plugins/ml-slider/) for a responsive slider widget. + +== Installation == + +1. Upload and install Page Builder in the same way you'd install any other plugin. +2. Read the [usage documentation](http://siteorigin.com/page-builder/documentation/) on SiteOrigin. + +== Screenshots == + +1. The page builder interface. +2. Adding a new widget. This includes a live search filter to help you keep control if you have a lot of widgets. +3. Editing a widget's settings. +4. Easily undo mistakes. + +== Documentation == + +[Documentation](http://siteorigin.com/page-builder/documentation/) is available on SiteOrigin. + +== Frequently Asked Questions == + += How do I move a site created with Page Builder from one server to another? = + +We recommend the [duplicator plugin](https://wordpress.org/plugins/duplicator/). We've tested it in several instances and it always works well with Page Builder data. + += Can I bundle Page Builder with my theme? = + +Yes, provided your theme is licensed under GPL or a compatible license. If you're publishing your theme on ThemeForest, you must select the GPL license instead of their regular license. + +Page Builder is actively developed and updated, so generally I'd recommend that you have your users install the actual plugin so they can receive updates. You can try [TGM Plugin Activation](http://tgmpluginactivation.com/). + += Will plugin X work with Page Builder? = + +I've tried to ensure that Page Builder is compatible with most plugin widgets. It's best to just download Page Builder and test for yourself. + +== Changelog == + += 2.0 = +* Complete rewrite of Page Builder Javascript using Backbone. + += 1.5.4 = +* Readded inline CSS setting. +* Improved handling of missing widgets in prebuilt layouts. + += 1.5.3 = +* Fixed post loop widget issue. +* Fixed settings issue. + += 1.5.2 = +* Changed to custom settings system to fix a few settings bugs. +* Added option to display more link in post loop widget. +* Fixed SSL in widget images. + += 1.5.1 = +* Compatibility with WordPress 4.0 - needed to change how tabs function. +* Compatibility with Black Studio TinyMCE Widget 2.0. +* Namespaced Tooltip to avoid conflicts. + += 1.5 = +* Increased size of widget dialog boxes. +* Updated incompatible plugins list. +* Updated to latest version of Chosen. +* Custom Home Page feature now uses standard pages. +* Improvements to preview handling. + += 1.4.12 = +* Improved how missing widgets are handled. +* General code clean up. +* Prebuilt layouts are no longer all filtered by siteorigin_panels_data. Filtered by siteorigin_panels_prebuilt_layout when fetched. +* Added more hooks and filters. +* Incompatible plugins now includes more link to give details about incompatibility. + += 1.4.11 = +* Fixed: Issue with setting up a home page, switching themes, then not being able to disable the home page. +* Updated to be compatible with latest Black Studio TinyMCE widget. +* Added a plugin incompatibility check with an admin notice. +* Improved bundled language files. + += 1.4.10 = +* Fixed: Fixed z-indexes so that TinyMCE dropdowns (like formatting) aren't hidden. + += 1.4.9 = +* Fixed: jQuery UI dialog wasn't being enqueued properly in WordPress 3.9. + += 1.4.8 = +* Updated Post Loop widget so it now accepts post__in in additional args field. +* Added update notification. +* Added filters for before and after the row content. +* Removed references to legacy widgets. + += 1.4.7 = +* Fixed size problem in gallery widget. +* Compatibility fixes with WordPress 3.9. + += 1.4.6 = +* Widgets are now only run through their update function when modified. +* Fixed gallery widget. + += 1.4.5 = +* Fixed an issue with copy content. +* Improved handling of styles in prebuilt layouts. +* Improved error handling in Javascript. +* Fixed issue with checkboxes. + += 1.4.4 = +* Generating Page Builder content in admin is now generated with a separate request to properly handle fatal errors from widgets. +* Fixed potential issue when loading home page interface. +* Added a way for themes to specify more advanced row styles. +* Dialogs and widget forms are now only loaded when needed in order to improve performance on large pages. +* Fixed several performance bottle necks. +* Page Builder data is now saved with auto save and revisions. + += 1.4.3 = +* Improved HTML5 validation be moving styles to header and footer. +* Basic improvements to memory efficiency. +* Black Studio TinyMCE height set to 350 pixels by default. +* Fixed: Black Studio TinyMCE update error. + += 1.4.2 = +* All existing widget forms are loaded with the initial interface, rather than through AJAX. Improves performance. +* Added safety check to ensure Page Builder data loaded before into the interface before saving into the database. Helps prevent content loss. +* Small usability improvements. +* Fixed: Embedded video widget. +* Fixed: Conflict with GPP Slideshow plugin. +* Fixed: Possible z-index conflicts with other plugins that have jQuery UI CSS. +* Fixed: Constant notification about autosave being more recent than current version. + += 1.4.1 = +* Fixed: Issue that was removing content for widgets with a lot of data. +* Fixed: Issue with duplicating widgets. + += 1.4.0 = +* Changed how widget forms are loaded to improve page load times. +* Several improvements to increase compatibility with various plugins and widgets. +* Properly handle widgets with form arrays. +* CSS fixes. +* Fixed compatibility issues with Black Studio TinyMCE. +* Added more development hooks and filters. \ No newline at end of file diff --git a/siteorigin-panels.php b/siteorigin-panels.php new file mode 100644 index 000000000..f7d416a2d --- /dev/null +++ b/siteorigin-panels.php @@ -0,0 +1,1259 @@ + __( 'Home', 'siteorigin-panels' ), + 'post_status' => $_POST['siteorigin_panels_home_enabled'] == 'true' ? 'publish' : 'draft', + 'post_type' => 'page', + 'comment_status' => 'closed', + ) ); + update_option( 'siteorigin_panels_home_page_id', $page_id ); + } + else { + $page_id = get_option( 'siteorigin_panels_home_page_id' ); + } + + // Save the updated page data + $panels_data = json_decode( wp_unslash( $_POST['panels_data'] ), true); + $panels_data['widgets'] = siteorigin_panels_process_raw_widgets($panels_data['widgets']); + $panels_data = siteorigin_panels_styles_sanitize_all( $panels_data ); + + update_post_meta( $page_id, 'panels_data', $panels_data ); + update_post_meta( $page_id, '_wp_page_template', siteorigin_panels_setting( 'home-template' ) ); + + if( !empty( $_POST['siteorigin_panels_home_enabled'] ) ) { + update_option('show_on_front', 'page'); + update_option('page_on_front', $page_id); + wp_publish_post($page_id); + } + else { + // We're disabling this home page + if( get_option('page_on_front') == $page_id ) { + // Disable the front page display + update_option('page_on_front', false); + + if( !get_option( 'page_for_posts' ) ) { + update_option( 'show_on_front', 'posts' ); + } + } + + // Change the post status to draft + $post = get_post($page_id); + if($post->post_status != 'draft') { + global $wpdb; + + $wpdb->update( $wpdb->posts, array( 'post_status' => 'draft' ), array( 'ID' => $post->ID ) ); + clean_post_cache( $post->ID ); + + $old_status = $post->post_status; + $post->post_status = 'draft'; + wp_transition_post_status( 'draft', $old_status, $post ); + + do_action( 'edit_post', $post->ID, $post ); + do_action( "save_post_{$post->post_type}", $post->ID, $post, true ); + do_action( 'save_post', $post->ID, $post, true ); + do_action( 'wp_insert_post', $post->ID, $post, true ); + } + + } +} +add_action('admin_init', 'siteorigin_panels_save_home_page'); + +/** + * After the theme is switched, change the template on the home page if the theme supports home page functionality. + */ +function siteorigin_panels_update_home_on_theme_change(){ + if( siteorigin_panels_setting( 'home-page' ) && siteorigin_panels_setting( 'home-template' ) && get_option( 'siteorigin_panels_home_page_id' ) ) { + // Lets update the home page to use the home template that this theme supports + update_post_meta( get_option( 'siteorigin_panels_home_page_id' ), '_wp_page_template', siteorigin_panels_setting( 'home-template' ) ); + } +} +add_action('after_switch_theme', 'siteorigin_panels_update_home_on_theme_change'); + +/** + * @return mixed|void Are we currently viewing the home page + */ +function siteorigin_panels_is_home(){ + $home = ( is_front_page() && is_page() && get_option('show_on_front') == 'page' && get_option('page_on_front') == get_the_ID() && get_post_meta( get_the_ID(), 'panels_data' ) ); + return apply_filters('siteorigin_panels_is_home', $home); +} + +/** + * Check if we're currently viewing a page builder page. + * + * @param bool $can_edit Also check if the user can edit this page + * @return bool + */ +function siteorigin_panels_is_panel($can_edit = false){ + // Check if this is a panel + $is_panel = ( siteorigin_panels_is_home() || ( is_singular() && get_post_meta(get_the_ID(), 'panels_data', false) != '' ) ); + return $is_panel && (!$can_edit || ( (is_singular() && current_user_can('edit_post', get_the_ID())) || ( siteorigin_panels_is_home() && current_user_can('edit_theme_options') ) )); +} + +/** + * Render a panel metabox. + * + * @param $post + */ +function siteorigin_panels_metabox_render( $post ) { + $panels_data = siteorigin_panels_get_current_admin_panels_data(); + include plugin_dir_path(__FILE__) . 'tpl/metabox-panels.php'; +} + +/** + * Enqueue the panels admin scripts + * + * @action admin_print_scripts-post-new.php + * @action admin_print_scripts-post.php + * @action admin_print_scripts-appearance_page_so_panels_home_page + */ +function siteorigin_panels_admin_enqueue_scripts($prefix) { + $screen = get_current_screen(); + + if ( ( $screen->base == 'post' && in_array( $screen->id, siteorigin_panels_setting('post-types') ) ) || $screen->base == 'appearance_page_so_panels_home_page' || $screen->base == 'widgets') { + + wp_enqueue_script( 'so-panels-admin', plugin_dir_url(__FILE__) . 'js/siteorigin-panels.js', array( 'jquery', 'jquery-ui-resizable', 'jquery-ui-sortable', 'jquery-ui-draggable', 'underscore', 'backbone' ), SITEORIGIN_PANELS_VERSION, true ); + wp_enqueue_script( 'so-panels-admin-styles', plugin_dir_url(__FILE__) . 'js/siteorigin-panels-styles.js', array( 'so-panels-admin', 'jquery', 'underscore', 'backbone', 'wp-color-picker' ), SITEORIGIN_PANELS_VERSION, true ); + + if( $screen->base != 'widgets' ) { + // We don't use the history browser and live editor in the widgets interface + wp_enqueue_script( 'so-panels-admin-history', plugin_dir_url(__FILE__) . 'js/siteorigin-panels-history.js', array( 'so-panels-admin', 'jquery', 'underscore', 'backbone' ), SITEORIGIN_PANELS_VERSION, true ); + wp_enqueue_script( 'so-panels-admin-live-editor', plugin_dir_url(__FILE__) . 'js/siteorigin-panels-live-editor.js', array( 'so-panels-admin', 'jquery', 'underscore', 'backbone' ), SITEORIGIN_PANELS_VERSION, true ); + } + + add_action('admin_footer', 'siteorigin_panels_js_templates'); + + $widgets = siteorigin_panels_get_widgets(); + + wp_localize_script( 'so-panels-admin', 'soPanelsOptions', array( + 'widgets' => $widgets, + 'widget_dialog_tabs' => apply_filters( 'siteorigin_panels_widget_dialog_tabs', array( + array( + 'title' => __('All Widgets', 'siteorigin-panels'), + 'filter' => array( 'installed' => true, 'groups' => '' ) + ) + ) ), + 'row_layouts' => apply_filters( 'siteorigin_panels_row_layouts', array() ), + // General localization messages + 'loc' => array( + 'missing_widget' => array( + 'title' => __('Missing Widget', 'siteorigin-panels'), + 'description' => __("Page Builder doesn't know about this widget", 'siteorigin-panels'), + ), + 'time' => array( + 'seconds' => __('%d seconds', 'siteorigin-panels'), + 'minutes' => __('%d minutes', 'siteorigin-panels'), + 'hours' => __('%d hours', 'siteorigin-panels'), + + 'second' => __('%d second', 'siteorigin-panels'), + 'minute' => __('%d minute', 'siteorigin-panels'), + 'hour' => __('%d hour', 'siteorigin-panels'), + + 'ago' => __('%s before', 'siteorigin-panels'), + 'now' => __('Now', 'siteorigin-panels'), + ), + 'history' => array( + // History messages + 'current' => __('Current', 'siteorigin-panels'), + 'revert' => __('Original', 'siteorigin-panels'), + 'restore' => __('Version restored', 'siteorigin-panels'), + + // Widgets + 'widget_deleted' => __('Widget deleted', 'siteorigin-panels'), + 'widget_added' => __('Widget added', 'siteorigin-panels'), + 'widget_edited' => __('Widget edited', 'siteorigin-panels'), + 'widget_duplicated' => __('Widget duplicated', 'siteorigin-panels'), + 'widget_moved' => __('Widget moved', 'siteorigin-panels'), + + // Rows + 'row_deleted' => __('Row deleted', 'siteorigin-panels'), + 'row_added' => __('Row added', 'siteorigin-panels'), + 'row_edited' => __('Row edited', 'siteorigin-panels'), + 'row_moved' => __('Row moved', 'siteorigin-panels'), + 'row_duplicated' => __('Row duplicated', 'siteorigin-panels'), + + // Cells + 'cell_resized' => __('Cell resized', 'siteorigin-panels'), + + // Prebuilt + 'prebuilt_loaded' => __('Prebuilt layout loaded', 'siteorigin-panels'), + ), + + // general localization + 'prebuilt_confirm' => __('Are you sure you want to overwrite your current content? This can be undone in the builder history.', 'siteorigin-panels'), + 'prebuilt_loading' => __('Loading prebuilt layout', 'siteorigin-panels'), + 'confirm_use_builder' => __("Would you like to copy this editor's existing content to Page Builder?", 'siteorigin-panels'), + 'layout_widget' => __('Layout Widget', 'siteorigin-panels'), + 'dropdown_confirm' => __('Are you sure?', 'siteorigin-panels'), + ), + )); + + // Let themes and plugins give names and descriptions to missing widgets. + global $wp_widget_factory; + $missing_widgets = array(); + if ( !empty( $panels_data['widgets'] ) ) { + foreach ( $panels_data['widgets'] as $i => $widget ) { + + // There's a chance the widget was activated by siteorigin_panels_widget_is_missing + if ( empty( $wp_widget_factory->widgets[ $widget['info']['class'] ] ) ) { + $missing_widgets[$widget['info']['class']] = apply_filters('siteorigin_panels_missing_widget_data', array( + 'title' => str_replace( '_', ' ', $widget['info']['class'] ), + 'description' => __('Install the missing widget', 'siteorigin-panels'), + ), $widget['info']['class']); + } + } + } + + if( !empty($missing_widgets) ) { + wp_localize_script( 'so-panels-admin', 'panelsMissingWidgets', $missing_widgets ); + } + + if( $screen->base != 'widgets' ) { + // Render all the widget forms. A lot of widgets use this as a chance to enqueue their scripts + $original_post = isset($GLOBALS['post']) ? $GLOBALS['post'] : null; // Make sure widgets don't change the global post. + foreach($GLOBALS['wp_widget_factory']->widgets as $class => $widget_obj){ + ob_start(); + $widget_obj->form( array() ); + ob_clean(); + } + $GLOBALS['post'] = $original_post; + } + + // This gives panels a chance to enqueue scripts too, without having to check the screen ID. + do_action( 'siteorigin_panel_enqueue_admin_scripts' ); + do_action( 'sidebar_admin_setup' ); + } +} +add_action( 'admin_print_scripts-post-new.php', 'siteorigin_panels_admin_enqueue_scripts' ); +add_action( 'admin_print_scripts-post.php', 'siteorigin_panels_admin_enqueue_scripts' ); +add_action( 'admin_print_scripts-appearance_page_so_panels_home_page', 'siteorigin_panels_admin_enqueue_scripts' ); +add_action( 'admin_print_scripts-widgets.php', 'siteorigin_panels_admin_enqueue_scripts' ); + +/** + * Get an array of all the available widgets. + * + * @return array + */ +function siteorigin_panels_get_widgets(){ + global $wp_widget_factory; + $widgets = array(); + foreach($wp_widget_factory->widgets as $class => $widget_obj) { + $widgets[$class] = array( + 'class' => $class, + 'title' => !empty($widget_obj->name) ? $widget_obj->name : __('Untitled Widget', 'siteorigin-panels'), + 'description' => !empty($widget_obj->widget_options['description']) ? $widget_obj->widget_options['description'] : '', + 'installed' => true, + 'groups' => array(), + ); + + // Get Page Builder specific widget options + if( isset($widget_obj->widget_options['panels_title']) ) { + $widgets[$class]['panels_title'] = $widget_obj->widget_options['panels_title']; + } + if( isset($widget_obj->widget_options['panels_groups']) ) { + $widgets[$class]['groups'] = $widget_obj->widget_options['panels_groups']; + } + if( isset($widget_obj->widget_options['panels_icon']) ) { + $widgets[$class]['icon'] = $widget_obj->widget_options['panels_icon']; + } + + } + + // Other plugins can manipulate the list of widgets. Possibly to add recommended widgets + $widgets = apply_filters('siteorigin_panels_widgets', $widgets); + + // Sort the widgets alphabetically + uasort($widgets, 'siteorigin_panels_widgets_sorter'); + + return $widgets; +} + +/** + * @param $a + * @param $b + */ +function siteorigin_panels_widgets_sorter($a, $b){ + return $a['title'] > $b['title'] ? 1 : -1; +} + +/** + * Display the templates for JS in the footer + */ +function siteorigin_panels_js_templates(){ + include plugin_dir_path(__FILE__).'tpl/js-templates.php'; +} + +/** + * Enqueue the admin panel styles + * + * @action admin_print_styles-post-new.php + * @action admin_print_styles-post.php + */ +function siteorigin_panels_admin_enqueue_styles() { + $screen = get_current_screen(); + if ( in_array( $screen->id, siteorigin_panels_setting('post-types') ) || $screen->base == 'appearance_page_so_panels_home_page' || $screen->base == 'widgets') { + wp_enqueue_style( 'so-panels-admin', plugin_dir_url(__FILE__) . 'css/admin.css', array( 'wp-color-picker' ), SITEORIGIN_PANELS_VERSION ); + do_action( 'siteorigin_panel_enqueue_admin_styles' ); + } +} +add_action( 'admin_print_styles-post-new.php', 'siteorigin_panels_admin_enqueue_styles' ); +add_action( 'admin_print_styles-post.php', 'siteorigin_panels_admin_enqueue_styles' ); +add_action( 'admin_print_styles-appearance_page_so_panels_home_page', 'siteorigin_panels_admin_enqueue_styles' ); +add_action( 'admin_print_styles-widgets.php', 'siteorigin_panels_admin_enqueue_styles' ); + +/** + * Add a help tab to pages with panels. + */ +function siteorigin_panels_add_help_tab($prefix) { + $screen = get_current_screen(); + if( + ( $screen->base == 'post' && ( in_array( $screen->id, siteorigin_panels_setting( 'post-types' ) ) || $screen->id == '') ) + || ($screen->id == 'appearance_page_so_panels_home_page') + ) { + $screen->add_help_tab( array( + 'id' => 'panels-help-tab', //unique id for the tab + 'title' => __( 'Page Builder', 'siteorigin-panels' ), //unique visible title for the tab + 'callback' => 'siteorigin_panels_add_help_tab_content' + ) ); + } +} +add_action('load-page.php', 'siteorigin_panels_add_help_tab', 12); +add_action('load-post-new.php', 'siteorigin_panels_add_help_tab', 12); +add_action('load-appearance_page_so_panels_home_page', 'siteorigin_panels_add_help_tab', 12); + +/** + * Display the content for the help tab. + */ +function siteorigin_panels_add_help_tab_content(){ + include plugin_dir_path(__FILE__) . 'tpl/help.php'; +} + +/** + * Save the panels data + * + * @param $post_id + * @param $post + * + * @action save_post + */ +function siteorigin_panels_save_post( $post_id, $post ) { + if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return; + if ( empty( $_POST['_sopanels_nonce'] ) || !wp_verify_nonce( $_POST['_sopanels_nonce'], 'save' ) ) return; + if ( !current_user_can( 'edit_post', $post_id ) ) return; + + if ( !wp_is_post_revision($post_id) ) { + $panels_data = json_decode( wp_unslash( $_POST['panels_data'] ), true); + $panels_data['widgets'] = siteorigin_panels_process_raw_widgets($panels_data['widgets']); + $panels_data = siteorigin_panels_styles_sanitize_all( $panels_data ); + + if( !empty( $panels_data['widgets'] ) ) { + update_post_meta( $post_id, 'panels_data', $panels_data ); + } + else { + // There are no widgets, so delete the panels data. + delete_post_meta( $post_id, 'panels_data' ); + } + } + else { + // When previewing, we don't need to wp_unslash the panels_data post variable. + $panels_data = json_decode( $_POST['panels_data'], true); + $panels_data['widgets'] = siteorigin_panels_process_raw_widgets($panels_data['widgets']); + $panels_data = siteorigin_panels_styles_sanitize_all( $panels_data ); + + // Because of issue #20299, we are going to save the preview into a different variable so we don't overwrite the actual data. + // https://core.trac.wordpress.org/panels_data/20299 + if( !empty( $panels_data['widgets'] ) ) { + update_post_meta( $post_id, '_panels_data_preview', $panels_data ); + } + } +} +add_action( 'save_post', 'siteorigin_panels_save_post', 10, 2 ); + +/** + * @param $value + * @param $post_id + * @param $meta_key + * + * @return mixed + */ +function siteorigin_panels_view_post_preview($value, $post_id, $meta_key){ + if( $meta_key == 'panels_data' && is_preview() && current_user_can( 'edit_post', $post_id ) ) { + $panels_preview = get_post_meta($post_id, '_panels_data_preview'); + return !empty($panels_preview) ? $panels_preview : $value; + } + + return $value; +} +add_filter('get_post_metadata', 'siteorigin_panels_view_post_preview', 10, 3); + +/** + * Process raw widgets that have come from the Page Builder front end. + * + * @param $widgets + */ +function siteorigin_panels_process_raw_widgets($widgets) { + for($i = 0; $i < count($widgets); $i++) { + + $info = isset($widgets[$i]['panels_info']) ? $widgets[$i]['panels_info'] : $widgets[$i]['info']; + unset($widgets[$i]['info']); + + if( !empty($info['raw']) ) { + if ( class_exists( $info['class'] ) && method_exists( $info['class'], 'update' ) ) { + $the_widget = new $info['class']; + $widgets[$i] = $the_widget->update( $widgets[$i], $widgets[$i] ); + unset($info['raw']); + } + } + + $widgets[$i]['panels_info'] = $info; + + } + + return $widgets; +} + +/** + * Get the home page panels layout data. + * + * @return mixed|void + */ +function siteorigin_panels_get_home_page_data(){ + $panels_data = get_option('siteorigin_panels_home_page', null); + if( is_null( $panels_data ) ){ + // Load the default layout + $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() ); + $panels_data = !empty($layouts['default_home']) ? $layouts['default_home'] : current($layouts); + } + + return $panels_data; +} + +/** + * Get the Page Builder data for the current admin page. + * + * @return array + */ +function siteorigin_panels_get_current_admin_panels_data(){ + $screen = get_current_screen(); + + // Localize the panels with the panels data + if($screen->base == 'appearance_page_so_panels_home_page'){ + $page_id = get_option( 'siteorigin_panels_home_page_id' ); + if( !empty($page_id) ) $panels_data = get_post_meta( $page_id, 'panels_data', true ); + else $panels_data = null; + + if( is_null( $panels_data ) ){ + // Load the default layout + $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() ); + + $home_name = siteorigin_panels_setting('home-page-default') ? siteorigin_panels_setting('home-page-default') : 'home'; + $panels_data = !empty($layouts[$home_name]) ? $layouts[$home_name] : current($layouts); + } + + $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, 'home'); + } + else{ + global $post; + $panels_data = get_post_meta( $post->ID, 'panels_data', true ); + $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, $post->ID ); + } + + if ( empty( $panels_data ) ) $panels_data = array(); + + return $panels_data; +} + +/** + * Generate the CSS for the page layout. + * + * @param $post_id + * @param $panels_data + * @return string + */ +function siteorigin_panels_generate_css($post_id, $panels_data){ + // Exit if we don't have panels data + if ( empty( $panels_data ) || empty( $panels_data['grids'] ) ) return; + + // Get some of the default settings + $settings = siteorigin_panels_setting(); + $panels_mobile_width = $settings['mobile-width']; + $panels_margin_bottom = $settings['margin-bottom']; + + $css = new SiteOrigin_Panels_Css_Builder(); + + $ci = 0; + foreach ( $panels_data['grids'] as $gi => $grid ) { + + $cell_count = intval( $grid['cells'] ); + + // Add the cell sizing + for ( $i = 0; $i < $cell_count; $i++ ) { + $cell = $panels_data['grid_cells'][$ci++]; + + if ( $cell_count > 1 ) { + $width = round( $cell['weight'] * 100, 3 ) . '%'; + $width = apply_filters('siteorigin_panels_css_cell_width', $width, $grid, $gi, $cell, $ci - 1, $panels_data, $post_id); + + // Add the width and ensure we have correct formatting for CSS. + $css->add_cell_css($post_id, $gi, $i, '', array( + 'width' => str_replace(',', '.', $width) + )); + } + } + + // Add the bottom margin to any grids that aren't the last + if($gi != count($panels_data['grids'])-1){ + // Filter the bottom margin for this row with the arguments + $css->add_row_css($post_id, $gi, '', array( + 'margin-bottom' => apply_filters('siteorigin_panels_css_row_margin_bottom', $panels_margin_bottom.'px', $grid, $gi, $panels_data, $post_id) + )); + } + + if ( $cell_count > 1 ) { + $css->add_cell_css($post_id, $gi, false, '', array( + // Float right for RTL + 'float' => !is_rtl() ? 'left' : 'right' + )); + } + + if ( $settings['responsive'] ) { + // Mobile Responsive + $css->add_cell_css($post_id, $gi, false, '', array( + 'float' => 'none', + 'width' => 'auto' + ), $panels_mobile_width); + + for ( $i = 0; $i < $cell_count; $i++ ) { + if ( $i != $cell_count - 1 ) { + $css->add_cell_css($post_id, $gi, $i, '', array( + 'margin-bottom' => $panels_margin_bottom . 'px', + ), $panels_mobile_width); + } + } + } + } + + if( $settings['responsive'] ) { + // Add CSS to prevent overflow on mobile resolution. + $css->add_row_css($post_id, false, '', array( + 'margin-left' => 0, + 'margin-right' => 0, + ), $panels_mobile_width); + + $css->add_cell_css($post_id, false, false, '', array( + 'padding' => 0, + ), $panels_mobile_width); + } + + // Add the bottom margins + $css->add_cell_css($post_id, false, false, '.panel', array( + 'margin-bottom' => $panels_margin_bottom.'px' + )); + $css->add_cell_css($post_id, false, false, '.panel:last-child', array( + 'margin-bottom' => 0 + )); + + // Let other plugins customize various aspects of the rows (grids) + foreach ( $panels_data['grids'] as $gi => $grid ) { + // Rows with only one cell don't need gutters + if($grid['cells'] <= 1) continue; + + // Let other themes and plugins change the gutter. + $gutter = apply_filters('siteorigin_panels_css_row_gutter', $settings['margin-sides'].'px', $grid, $gi, $panels_data); + + if( !empty($gutter) ) { + // We actually need to find half the gutter. + preg_match('/([0-9\.,]+)(.*)/', $gutter, $match); + if( !empty( $match[1] ) ) { + $margin_half = (floatval($match[1])/2) . $match[2]; + $css->add_row_css($post_id, $gi, '', array( + 'margin-left' => '-' . $margin_half, + 'margin-right' => '-' . $margin_half, + ) ); + $css->add_cell_css($post_id, $gi, false, '', array( + 'padding-left' => $margin_half, + 'padding-right' => $margin_half, + ) ); + + } + } + } + + // Let other plugins and components filter the CSS object. + $css = apply_filters('siteorigin_panels_css_object', $css, $panels_data, $post_id); + return $css->get_css(); +} + +/** + * Prepare the content of the page early on so widgets can enqueue their scripts and styles + */ +function siteorigin_panels_prepare_single_post_content(){ + if( is_singular() ) { + global $siteorigin_panels_cache; + if( empty($siteorigin_panels_cache[ get_the_ID() ] ) ) { + $siteorigin_panels_cache[ get_the_ID() ] = siteorigin_panels_render( get_the_ID() ); + } + } +} +add_action('wp_enqueue_scripts', 'siteorigin_panels_prepare_single_post_content'); + +/** + * Filter the content of the panel, adding all the widgets. + * + * @param $content + * @return string + * + * @filter the_content + */ +function siteorigin_panels_filter_content( $content ) { + global $post; + + if ( empty( $post ) ) return $content; + if ( !apply_filters( 'siteorigin_panels_filter_content_enabled', true ) ) return $content; + if ( in_array( $post->post_type, siteorigin_panels_setting('post-types') ) ) { + $panel_content = siteorigin_panels_render( $post->ID ); + + if ( !empty( $panel_content ) ) $content = $panel_content; + } + + return $content; +} +add_filter( 'the_content', 'siteorigin_panels_filter_content' ); + + +/** + * Render the panels + * + * @param int|string|bool $post_id The Post ID or 'home'. + * @param bool $enqueue_css Should we also enqueue the layout CSS. + * @param array|bool $panels_data Existing panels data. By default load from settings or post meta. + * @return string + */ +function siteorigin_panels_render( $post_id = false, $enqueue_css = true, $panels_data = false ) { + if( empty($post_id) ) $post_id = get_the_ID(); + + global $siteorigin_panels_current_post; + $old_current_post = $siteorigin_panels_current_post; + $siteorigin_panels_current_post = $post_id; + + // Try get the cached panel from in memory cache. + global $siteorigin_panels_cache; + if(!empty($siteorigin_panels_cache) && !empty($siteorigin_panels_cache[$post_id])) + return $siteorigin_panels_cache[$post_id]; + + if( empty($panels_data) ) { + if( strpos($post_id, 'prebuilt:') === 0) { + list($null, $prebuilt_id) = explode(':', $post_id, 2); + $layouts = apply_filters('siteorigin_panels_prebuilt_layouts', array()); + $panels_data = !empty($layouts[$prebuilt_id]) ? $layouts[$prebuilt_id] : array(); + } + else if($post_id == 'home'){ + $panels_data = get_post_meta( get_option('siteorigin_panels_home_page_id'), 'panels_data', true ); + + if( is_null($panels_data) ){ + // Load the default layout + $layouts = apply_filters('siteorigin_panels_prebuilt_layouts', array()); + $prebuilt_id = siteorigin_panels_setting('home-page-default') ? siteorigin_panels_setting('home-page-default') : 'home'; + + $panels_data = !empty($layouts[$prebuilt_id]) ? $layouts[$prebuilt_id] : current($layouts); + } + } + else{ + if ( post_password_required($post_id) ) return false; + $panels_data = get_post_meta( $post_id, 'panels_data', true ); + } + } + + $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, $post_id ); + if( empty( $panels_data ) || empty( $panels_data['grids'] ) ) return ''; + + if( is_rtl() ) $panels_data = siteorigin_panels_make_rtl( $panels_data ); + + // Create the skeleton of the grids + $grids = array(); + if( !empty( $panels_data['grids'] ) && !empty( $panels_data['grids'] ) ) { + foreach ( $panels_data['grids'] as $gi => $grid ) { + $gi = intval( $gi ); + $grids[$gi] = array(); + for ( $i = 0; $i < $grid['cells']; $i++ ) { + $grids[$gi][$i] = array(); + } + } + } + + // We need this to migrate from the old $panels_data that put widget meta into the "info" key instead of "panels_info" + if( !empty( $panels_data['widgets'] ) && is_array($panels_data['widgets']) ) { + foreach ( $panels_data['widgets'] as $i => $widget ) { + if( empty( $panels_data['widgets'][$i]['panels_info'] ) ) { + $panels_data['widgets'][$i]['panels_info'] = $panels_data['widgets'][$i]['info']; + unset($panels_data['widgets'][$i]['info']); + } + } + } + + if( !empty( $panels_data['widgets'] ) && is_array($panels_data['widgets']) ){ + foreach ( $panels_data['widgets'] as $widget ) { + // Put the widgets in the grids + $grids[ intval( $widget['panels_info']['grid']) ][ intval( $widget['panels_info']['cell'] ) ][] = $widget; + } + } + + ob_start(); + + // Add the panel layout wrapper + echo '
'; + + global $siteorigin_panels_inline_css; + if( empty($siteorigin_panels_inline_css) ) $siteorigin_panels_inline_css = ''; + + if($enqueue_css) { + wp_enqueue_style('siteorigin-panels-front'); + $siteorigin_panels_inline_css .= siteorigin_panels_generate_css($post_id, $panels_data); + } + + foreach ( $grids as $gi => $cells ) { + + $grid_classes = apply_filters( 'siteorigin_panels_row_classes', array('panel-grid'), $panels_data['grids'][$gi] ); + $grid_attributes = apply_filters( 'siteorigin_panels_row_attributes', array( + 'class' => implode( ' ', $grid_classes ), + 'id' => 'pg-' . $post_id . '-' . $gi + ), $panels_data['grids'][$gi] ); + + // This allows other themes and plugins to add html before the row + echo apply_filters( 'siteorigin_panels_before_row', '', $panels_data['grids'][$gi], $grid_attributes ); + + echo '
$value ) { + echo $name.'="'.esc_attr($value).'" '; + } + echo '>'; + + $style_attributes = array(); + if( !empty( $panels_data['grids'][$gi]['style']['class'] ) ) { + $style_attributes['class'] = array('panel-row-style-'.$panels_data['grids'][$gi]['style']['class']); + } + + // Themes can add their own attributes to the style wrapper + $row_style_wrapper = siteorigin_panels_start_style_wrapper( 'row', $style_attributes, !empty($panels_data['grids'][$gi]['style']) ? $panels_data['grids'][$gi]['style'] : array() ); + if( !empty($row_style_wrapper) ) echo $row_style_wrapper; + + foreach ( $cells as $ci => $widgets ) { + // Themes can add their own styles to cells + $cell_classes = apply_filters( 'siteorigin_panels_row_cell_classes', array('panel-grid-cell'), $panels_data ); + $cell_attributes = apply_filters( 'siteorigin_panels_row_cell_attributes', array( + 'class' => implode( ' ', $cell_classes ), + 'id' => 'pgc-' . $post_id . '-' . $gi . '-' . $ci + ), $panels_data ); + + echo '
$value ) { + echo $name.'="'.esc_attr($value).'" '; + } + echo '>'; + + $cell_style_wrapper = siteorigin_panels_start_style_wrapper( 'cell', array(), !empty($panels_data['grids'][$gi]['style']) ? $panels_data['grids'][$gi]['style'] : array() ); + if( !empty($cell_style_wrapper) ) echo $cell_style_wrapper; + + foreach ( $widgets as $pi => $widget_info ) { + $instance = $widget_info; + unset( $instance['panels_info'] ); + + // TODO this wrapper should go in the before/after widget arguments + $widget_style_wrapper = siteorigin_panels_start_style_wrapper( 'widget', array(), !empty( $widget_info['panels_info']['style'] ) ? $widget_info['panels_info']['style'] : array() ); + siteorigin_panels_the_widget( $widget_info['panels_info']['class'], $instance, $gi, $ci, $pi, $pi == 0, $pi == count( $widgets ) - 1, $post_id, $widget_style_wrapper ); + } + if ( empty( $widgets ) ) echo ' '; + + if( !empty($cell_style_wrapper) ) echo '
'; + echo '
'; + } + echo '
'; + + // Close the + if( !empty($row_style_wrapper) ) echo '
'; + + // This allows other themes and plugins to add html after the row + echo apply_filters( 'siteorigin_panels_after_row', '', $panels_data['grids'][$gi], $grid_attributes ); + } + + echo '
'; + + $html = ob_get_clean(); + + // Reset the current post + $siteorigin_panels_current_post = $old_current_post; + + return apply_filters( 'siteorigin_panels_render', $html, $post_id, !empty($post) ? $post : null ); +} + +/** + * Echo the style wrapper and return if there was a wrapper + * + * @param $name + * @param $style_attributes + * @param array $style_args + * + * @return bool Is there a style wrapper + */ +function siteorigin_panels_start_style_wrapper($name, $style_attributes, $style_args = array()){ + + $style_wrapper = ''; + + if( empty($style_attributes['class']) ) $style_attributes['class'] = array(); + if( empty($style_attributes['style']) ) $style_attributes['style'] = ''; + + $style_attributes = apply_filters('siteorigin_panels_' . $name . '_style_attributes', $style_attributes, $style_args ); + + if( empty($style_attributes['class']) ) unset($style_attributes['class']); + if( empty($style_attributes['style']) ) unset($style_attributes['style']); + + if( !empty($style_attributes) ) { + if(empty($style_attributes['class'])) $style_attributes['class'] = array(); + $style_attributes['class'][] = 'panel-' . $name . '-style'; + $style_attributes['class'] = array_unique( $style_attributes['class'] ); + + // Filter and sanitize the classes + $style_attributes['class'] = apply_filters('siteorigin_panels_' . $name . '_style_classes', $style_attributes['class'], $style_attributes, $style_args); + $style_attributes['class'] = array_map('sanitize_html_class', $style_attributes['class']); + + $style_wrapper = '
$value ) { + if( is_array($value) ) { + $style_wrapper .= $name.'="'.esc_attr( implode( " ", array_unique( $value ) ) ).'" '; + } + else { + $style_wrapper .= $name.'="'.esc_attr($value).'" '; + } + } + $style_wrapper .= '>'; + + return $style_wrapper; + } + + return $style_wrapper; +} + +/** + * Print inline CSS in the header and footer. + */ +function siteorigin_panels_print_inline_css(){ + global $siteorigin_panels_inline_css; + if(!empty($siteorigin_panels_inline_css)) { + ?>widgets[$widget]) ? $wp_widget_factory->widgets[$widget] : false; + $the_widget = apply_filters( 'siteorigin_panels_widget_object', $the_widget, $widget, $instance ); + + if( empty($post_id) ) $post_id = get_the_ID(); + + $classes = array( 'panel', 'widget' ); + if ( !empty( $the_widget ) && !empty( $the_widget->id_base ) ) $classes[] = 'widget_' . $the_widget->id_base; + if ( $is_first ) $classes[] = 'panel-first-child'; + if ( $is_last ) $classes[] = 'panel-last-child'; + $id = 'panel-' . $post_id . '-' . $grid . '-' . $cell . '-' . $panel; + + // Filter and sanitize the classes + $classes = apply_filters('siteorigin_panels_widget_classes', $classes, $widget, $instance); + $classes = array_map('sanitize_html_class', $classes); + + $args = array( + 'before_widget' => '
', + 'after_widget' => '
', + 'before_title' => '

', + 'after_title' => '

', + 'widget_id' => 'widget-' . $grid . '-' . $cell . '-' . $panel + ); + + // If there is a style wrapper, add it. + if( !empty($style_wrapper) ) { + $args['before_widget'] = $args['before_widget'] . $style_wrapper; + $args['after_widget'] = '
' . $args['after_widget']; + } + + if ( !empty($the_widget) && is_a($the_widget, 'WP_Widget') ) { + $the_widget->widget($args , $instance ); + } + else { + // This gives themes a chance to display some sort of placeholder for missing widgets + echo apply_filters('siteorigin_panels_missing_widget', '', $widget, $args , $instance); + } +} + +/** + * Add the Edit Home Page item to the admin bar. + * + * @param WP_Admin_Bar $admin_bar + * @return WP_Admin_Bar + */ +function siteorigin_panels_admin_bar_menu($admin_bar){ + // Ignore this unless the theme is using the home page feature. + if( !siteorigin_panels_setting('home-page') ) return $admin_bar; + if( !current_user_can('edit_theme_options') ) return $admin_bar; + + if( is_home() || is_front_page() ) { + if( ( is_page() && get_the_ID() == get_option('siteorigin_panels_home_page_id') ) || current_user_can('edit_theme_options') ) { + $admin_bar->add_node( array( + 'id' => 'edit-home-page', + 'title' => __('Edit Home Page', 'siteorigin-panels'), + 'href' => admin_url('themes.php?page=so_panels_home_page') + ) ); + } + + if( is_page() && get_the_ID() == get_option('siteorigin_panels_home_page_id') ) { + $admin_bar->remove_node('edit'); + } + } + + return $admin_bar; +} +add_action('admin_bar_menu', 'siteorigin_panels_admin_bar_menu', 100); + +/** + * Handles creating the preview. + */ +function siteorigin_panels_preview(){ + if(isset($_GET['siteorigin_panels_preview']) && isset($_GET['_wpnonce']) && wp_verify_nonce($_GET['_wpnonce'], 'siteorigin-panels-preview')){ + global $siteorigin_panels_is_preview; + $siteorigin_panels_is_preview = true; + // Set the panels home state to true + if(empty($_POST['post_id'])) $GLOBALS['siteorigin_panels_is_panels_home'] = true; + add_action('siteorigin_panels_data', 'siteorigin_panels_home_preview_load_data'); + locate_template( siteorigin_panels_setting('home-template'), true ); + exit(); + } +} +add_action('template_redirect', 'siteorigin_panels_preview'); + +/** + * Is this a preview. + * + * @return bool + */ +function siteorigin_panels_is_preview(){ + global $siteorigin_panels_is_preview; + return (bool) $siteorigin_panels_is_preview; +} + +/** + * Hide the admin bar for panels previews. + * + * @param $show + * @return bool + */ +function siteorigin_panels_preview_adminbar($show){ + if(!$show) return false; + return !(isset($_GET['siteorigin_panels_preview']) && wp_verify_nonce($_GET['_wpnonce'], 'siteorigin-panels-preview')); +} +add_filter('show_admin_bar', 'siteorigin_panels_preview_adminbar'); + +/** + * This is a way to show previews of panels, especially for the home page. + * + * @param $val + * @return array + */ +function siteorigin_panels_home_preview_load_data($val){ + if( isset($_GET['siteorigin_panels_preview']) ){ + $val = siteorigin_panels_get_panels_data_from_post( $_POST ); + } + + return $val; +} + +/** + * Add all the necessary body classes. + * + * @param $classes + * @return array + */ +function siteorigin_panels_body_class($classes){ + if( siteorigin_panels_is_panel() ) $classes[] = 'siteorigin-panels'; + if( siteorigin_panels_is_home() ) $classes[] = 'siteorigin-panels-home'; + + if(isset($_GET['siteorigin_panels_preview']) && isset($_GET['_wpnonce']) && wp_verify_nonce($_GET['_wpnonce'], 'siteorigin-panels-preview')) { + // This is a home page preview + $classes[] = 'siteorigin-panels'; + $classes[] = 'siteorigin-panels-home'; + } + + return $classes; +} +add_filter('body_class', 'siteorigin_panels_body_class'); + +/** + * Enqueue the required styles + */ +function siteorigin_panels_enqueue_styles(){ + wp_register_style('siteorigin-panels-front', plugin_dir_url(__FILE__) . 'css/front.css', array(), SITEORIGIN_PANELS_VERSION ); +} +add_action('wp_enqueue_scripts', 'siteorigin_panels_enqueue_styles', 1); + +/** + * Add a filter to import panels_data meta key. This fixes serialized PHP. + */ +function siteorigin_panels_wp_import_post_meta($post_meta){ + foreach($post_meta as $i => $meta) { + if($meta['key'] == 'panels_data') { + $value = $meta['value']; + $value = preg_replace("/[\r\n]/", "<<
>>", $value); + $value = preg_replace('!s:(\d+):"(.*?)";!e', "'s:'.strlen('$2').':\"$2\";'", $value); + $value = unserialize($value); + $value = array_map('siteorigin_panels_wp_import_post_meta_map', $value); + + $post_meta[$i]['value'] = $value; + } + } + + return $post_meta; +} +add_filter('wp_import_post_meta', 'siteorigin_panels_wp_import_post_meta'); + +/** + * A callback that replaces temporary break tag with actual line breaks. + * + * @param $val + * @return array|mixed + */ +function siteorigin_panels_wp_import_post_meta_map($val) { + if(is_string($val)) return str_replace('<<
>>', "\n", $val); + else return array_map('siteorigin_panels_wp_import_post_meta_map', $val); +} + +/** + * Render a widget form with all the Page Builder specific fields + * + * @param string $widget The class of the widget + * @param array $instance Widget values + * @param bool $raw + * @return mixed|string The form + */ +function siteorigin_panels_render_form($widget, $instance = array(), $raw = false){ + global $wp_widget_factory; + + // This is a chance for plugins to replace missing widgets + $the_widget = !empty($wp_widget_factory->widgets[$widget]) ? $wp_widget_factory->widgets[$widget] : false; + $the_widget = apply_filters( 'siteorigin_panels_widget_object', $the_widget, $widget ); + + if ( empty($the_widget) || !is_a( $the_widget, 'WP_Widget' ) ) { + $widgets = siteorigin_panels_get_widgets(); + + if( !empty($widgets[$widget]) && !empty( $widgets[$widget]['plugin'] ) ) { + // We know about this widget, show a form about installing it. + $install_url = siteorigin_panels_plugin_activation_install_url($widgets[$widget]['plugin']['slug'], $widgets[$widget]['plugin']['name']); + $form = + '
' . + '

' . sprintf( + __("You need to install %s to use the widget %s. It's a free plugin available off the official WordPress plugin directory.", 'siteorigin-panels'), + $install_url, + $widgets[$widget]['plugin']['name'], + $widget + ). '

' . + '

' . __("Save and reload this page to start using the widget after you've installed it.") . '

' . + '
'; + } + else { + // This widget is missing, so show a missing widgets form. + $form = + '

' . + sprintf( + __('The widget %s is not available. Please try locate and install the missing plugin. Post on the support forums if you need help.', 'siteorigin-panels'), + $widget, + 'http://siteorigin.com/thread/' + ). + '

'; + } + + // Allow other themes and plugins to change the missing widget form + return apply_filters('siteorigin_panels_missing_widget_form', $form, $widget, $instance); + } + + if( $raw ) $instance = $the_widget->update($instance, $instance); + + $the_widget->id = 'temp'; + $the_widget->number = '{$id}'; + + ob_start(); + $the_widget->form($instance); + $form = ob_get_clean(); + + // Convert the widget field naming into ones that Page Builder uses + $exp = preg_quote( $the_widget->get_field_name('____') ); + $exp = str_replace('____', '(.*?)', $exp); + $form = preg_replace( '/'.$exp.'/', 'widgets[{$id}][$1]', $form ); + + $form = apply_filters('siteorigin_panels_widget_form', $form, $widget, $instance); + + // Add all the information fields + return $form; +} + +/** + * This takes existing Page Builder data and makes it RTL by reversing the content + */ +function siteorigin_panels_make_rtl($panels_data){ + + // To start, we need a cell count for every row + foreach($panels_data['widgets'] as &$widget) { + // This reverses the cells of the widgets + $count = $panels_data['grids'][ $widget['panels_info']['grid'] ]['cells']; + $widget['panels_info']['cell'] = abs( $widget['panels_info']['cell'] - $count + 1 ); + } + + // Now we need to swap around the grid cells because we're going to use float right instead. + $grid_cells = array(); + foreach( $panels_data['grid_cells'] as $cell) { + if( empty( $grid_cells[ $cell['grid'] ] ) ) $grid_cells[ $cell['grid'] ] = array(); + array_unshift( $grid_cells[ $cell['grid'] ], $cell ); + } + $new_grid_cells = array(); + foreach( $grid_cells as $i => $cells ) { + foreach($cells as $cell) { + $new_grid_cells[] = $cell; + } + } + $panels_data['grid_cells'] = $new_grid_cells; + + return $panels_data; +} + +/** + * Add action links to the plugin list for Page Builder. + * + * @param $links + * @return array + */ +function siteorigin_panels_plugin_action_links($links) { + $links[] = '' . __('Support Forum', 'siteorigin-panels') . ''; + $links[] = '' . __('Newsletter', 'siteorigin-panels') . ''; + return $links; +} +add_action('plugin_action_links_' . plugin_basename(__FILE__), 'siteorigin_panels_plugin_action_links'); + +// Include the live editor file if we're in live editor mode. +if( !empty( $_GET['siteorigin_panels_live_editor'] ) ) require_once plugin_dir_path(__FILE__) . 'inc/live-editor.php'; \ No newline at end of file diff --git a/tpl/admin-home-page.php b/tpl/admin-home-page.php new file mode 100644 index 000000000..f4253c650 --- /dev/null +++ b/tpl/admin-home-page.php @@ -0,0 +1,39 @@ + + +
+
+

+

+ + + + + + + +

+ + +
+

View page', 'siteorigin-panels'), get_home_url() ) ?>

+
+ + +
+ +
+ + + +

+ + +
+ +
\ No newline at end of file diff --git a/tpl/help.php b/tpl/help.php new file mode 100644 index 000000000..3cdfe6b6a --- /dev/null +++ b/tpl/help.php @@ -0,0 +1,15 @@ +

+ + +

+

+ full documentation on SiteOrigin.", 'siteorigin-panels' ) . ' ' . + __( "Ask a question on our support forum if you need help and sign up to our newsletter to stay up to date with future developments.", 'siteorigin-panels' ), + 'http://siteorigin.com/page-builder/documentation/' , + 'http://siteorigin.com/threads/plugin-page-builder/', + 'http://siteorigin.com/#newsletter' + ); + ?> +

\ No newline at end of file diff --git a/tpl/js-templates.php b/tpl/js-templates.php new file mode 100644 index 000000000..4d879d1a7 --- /dev/null +++ b/tpl/js-templates.php @@ -0,0 +1,435 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tpl/metabox-panels.php b/tpl/metabox-panels.php new file mode 100644 index 000000000..9fe115e2e --- /dev/null +++ b/tpl/metabox-panels.php @@ -0,0 +1,8 @@ +
+ + + + + + +
\ No newline at end of file diff --git a/tpl/options.php b/tpl/options.php new file mode 100644 index 000000000..60edd0c8f --- /dev/null +++ b/tpl/options.php @@ -0,0 +1,97 @@ + + +
+

+

+ +
+ +
+ +

+ + + + + + + + + + +
+ +
+ +

+ + + + + + +
+ + + +

+ +

+ +
+
\ No newline at end of file diff --git a/video/jplayer/Jplayer.swf b/video/jplayer/Jplayer.swf new file mode 100755 index 000000000..2121715c8 Binary files /dev/null and b/video/jplayer/Jplayer.swf differ diff --git a/video/jplayer/jquery.jplayer.min.js b/video/jplayer/jquery.jplayer.min.js new file mode 100755 index 000000000..796e970e2 --- /dev/null +++ b/video/jplayer/jquery.jplayer.min.js @@ -0,0 +1,114 @@ +/* + * jPlayer Plugin for jQuery JavaScript Library + * http://www.jplayer.org + * + * Copyright (c) 2009 - 2013 Happyworm Ltd + * Licensed under the MIT license. + * http://opensource.org/licenses/MIT + * + * Author: Mark J Panaghiston + * Version: 2.5.0 + * Date: 7th November 2013 + */ + +(function(b,f){"function"===typeof define&&define.amd?define(["jquery"],f):b.jQuery?f(b.jQuery):f(b.Zepto)})(this,function(b,f){b.fn.jPlayer=function(a){var c="string"===typeof a,d=Array.prototype.slice.call(arguments,1),e=this;a=!c&&d.length?b.extend.apply(null,[!0,a].concat(d)):a;if(c&&"_"===a.charAt(0))return e;c?this.each(function(){var c=b(this).data("jPlayer"),h=c&&b.isFunction(c[a])?c[a].apply(c,d):c;if(h!==c&&h!==f)return e=h,!1}):this.each(function(){var c=b(this).data("jPlayer");c?c.option(a|| +{}):b(this).data("jPlayer",new b.jPlayer(a,this))});return e};b.jPlayer=function(a,c){if(arguments.length){this.element=b(c);this.options=b.extend(!0,{},this.options,a);var d=this;this.element.bind("remove.jPlayer",function(){d.destroy()});this._init()}};"function"!==typeof b.fn.stop&&(b.fn.stop=function(){});b.jPlayer.emulateMethods="load play pause";b.jPlayer.emulateStatus="src readyState networkState currentTime duration paused ended playbackRate";b.jPlayer.emulateOptions="muted volume";b.jPlayer.reservedEvent= +"ready flashreset resize repeat error warning";b.jPlayer.event={};b.each("ready flashreset resize repeat click error warning loadstart progress suspend abort emptied stalled play pause loadedmetadata loadeddata waiting playing canplay canplaythrough seeking seeked timeupdate ended ratechange durationchange volumechange".split(" "),function(){b.jPlayer.event[this]="jPlayer_"+this});b.jPlayer.htmlEvent="loadstart abort emptied stalled loadedmetadata loadeddata canplay canplaythrough".split(" ");b.jPlayer.pause= +function(){b.each(b.jPlayer.prototype.instances,function(a,c){c.data("jPlayer").status.srcSet&&c.jPlayer("pause")})};b.jPlayer.timeFormat={showHour:!1,showMin:!0,showSec:!0,padHour:!1,padMin:!0,padSec:!0,sepHour:":",sepMin:":",sepSec:""};var m=function(){this.init()};m.prototype={init:function(){this.options={timeFormat:b.jPlayer.timeFormat}},time:function(a){var c=new Date(1E3*(a&&"number"===typeof a?a:0)),b=c.getUTCHours();a=this.options.timeFormat.showHour?c.getUTCMinutes():c.getUTCMinutes()+60* +b;c=this.options.timeFormat.showMin?c.getUTCSeconds():c.getUTCSeconds()+60*a;b=this.options.timeFormat.padHour&&10>b?"0"+b:b;a=this.options.timeFormat.padMin&&10>a?"0"+a:a;c=this.options.timeFormat.padSec&&10>c?"0"+c:c;b=""+(this.options.timeFormat.showHour?b+this.options.timeFormat.sepHour:"");b+=this.options.timeFormat.showMin?a+this.options.timeFormat.sepMin:"";return b+=this.options.timeFormat.showSec?c+this.options.timeFormat.sepSec:""}};var n=new m;b.jPlayer.convertTime=function(a){return n.time(a)}; +b.jPlayer.uaBrowser=function(a){a=a.toLowerCase();var c=/(opera)(?:.*version)?[ \/]([\w.]+)/,b=/(msie) ([\w.]+)/,e=/(mozilla)(?:.*? rv:([\w.]+))?/;a=/(webkit)[ \/]([\w.]+)/.exec(a)||c.exec(a)||b.exec(a)||0>a.indexOf("compatible")&&e.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}};b.jPlayer.uaPlatform=function(a){var c=a.toLowerCase(),b=/(android)/,e=/(mobile)/;a=/(ipad|iphone|ipod|android|blackberry|playbook|windows ce|webos)/.exec(c)||[];c=/(ipad|playbook)/.exec(c)||!e.exec(c)&&b.exec(c)|| +[];a[1]&&(a[1]=a[1].replace(/\s/g,"_"));return{platform:a[1]||"",tablet:c[1]||""}};b.jPlayer.browser={};b.jPlayer.platform={};var k=b.jPlayer.uaBrowser(navigator.userAgent);k.browser&&(b.jPlayer.browser[k.browser]=!0,b.jPlayer.browser.version=k.version);k=b.jPlayer.uaPlatform(navigator.userAgent);k.platform&&(b.jPlayer.platform[k.platform]=!0,b.jPlayer.platform.mobile=!k.tablet,b.jPlayer.platform.tablet=!!k.tablet);b.jPlayer.getDocMode=function(){var a;b.jPlayer.browser.msie&&(document.documentMode? +a=document.documentMode:(a=5,document.compatMode&&"CSS1Compat"===document.compatMode&&(a=7)));return a};b.jPlayer.browser.documentMode=b.jPlayer.getDocMode();b.jPlayer.nativeFeatures={init:function(){var a=document,c=a.createElement("video"),b={w3c:"fullscreenEnabled fullscreenElement requestFullscreen exitFullscreen fullscreenchange fullscreenerror".split(" "),moz:"mozFullScreenEnabled mozFullScreenElement mozRequestFullScreen mozCancelFullScreen mozfullscreenchange mozfullscreenerror".split(" "), +webkit:" webkitCurrentFullScreenElement webkitRequestFullScreen webkitCancelFullScreen webkitfullscreenchange ".split(" "),webkitVideo:"webkitSupportsFullscreen webkitDisplayingFullscreen webkitEnterFullscreen webkitExitFullscreen ".split(" ")},e=["w3c","moz","webkit","webkitVideo"],g,h;this.fullscreen=c={support:{w3c:!!a[b.w3c[0]],moz:!!a[b.moz[0]],webkit:"function"===typeof a[b.webkit[3]],webkitVideo:"function"===typeof c[b.webkitVideo[2]]},used:{}};g=0;for(h=e.length;g','','','',''];c=document.createElement(''); +for(var e=0;e").join(">").split('"').join(""")},_qualifyURL:function(a){var c=document.createElement("div");c.innerHTML='x';return c.firstChild.href},_absoluteMediaUrls:function(a){var c=this;b.each(a,function(b,e){c.format[b]&& +(a[b]=c._qualifyURL(e))});return a},setMedia:function(a){var c=this,d=!1,e=this.status.media.poster!==a.poster;this._resetMedia();this._resetGate();this._resetActive();a=this._absoluteMediaUrls(a);b.each(this.formats,function(e,f){var k="video"===c.format[f].media;b.each(c.solutions,function(b,e){if(c[e].support[f]&&c._validString(a[f])){var g="html"===e;k?(g?(c.html.video.gate=!0,c._html_setVideo(a),c.html.active=!0):(c.flash.gate=!0,c._flash_setVideo(a),c.flash.active=!0),c.css.jq.videoPlay.length&& +c.css.jq.videoPlay.show(),c.status.video=!0):(g?(c.html.audio.gate=!0,c._html_setAudio(a),c.html.active=!0):(c.flash.gate=!0,c._flash_setAudio(a),c.flash.active=!0),c.css.jq.videoPlay.length&&c.css.jq.videoPlay.hide(),c.status.video=!1);d=!0;return!1}});if(d)return!1});d?(this.status.nativeVideoControls&&this.html.video.gate||!this._validString(a.poster)||(e?this.htmlElement.poster.src=a.poster:this.internal.poster.jq.show()),this.status.srcSet=!0,this.status.media=b.extend({},a),this._updateButtons(!1), +this._updateInterface()):this._error({type:b.jPlayer.error.NO_SUPPORT,context:"{supplied:'"+this.options.supplied+"'}",message:b.jPlayer.errorMsg.NO_SUPPORT,hint:b.jPlayer.errorHint.NO_SUPPORT})},_resetMedia:function(){this._resetStatus();this._updateButtons(!1);this._updateInterface();this._seeked();this.internal.poster.jq.hide();clearTimeout(this.internal.htmlDlyCmdId);this.html.active?this._html_resetMedia():this.flash.active&&this._flash_resetMedia()},clearMedia:function(){this._resetMedia(); +this.html.active?this._html_clearMedia():this.flash.active&&this._flash_clearMedia();this._resetGate();this._resetActive()},load:function(){this.status.srcSet?this.html.active?this._html_load():this.flash.active&&this._flash_load():this._urlNotSetError("load")},focus:function(){this.options.keyEnabled&&(b.jPlayer.focus=this)},play:function(a){a="number"===typeof a?a:NaN;this.status.srcSet?(this.focus(),this.html.active?this._html_play(a):this.flash.active&&this._flash_play(a)):this._urlNotSetError("play")}, +videoPlay:function(){this.play()},pause:function(a){a="number"===typeof a?a:NaN;this.status.srcSet?this.html.active?this._html_pause(a):this.flash.active&&this._flash_pause(a):this._urlNotSetError("pause")},tellOthers:function(a,c){var d=this,e="function"===typeof c,g=Array.prototype.slice.call(arguments);"string"===typeof a&&(e&&g.splice(1,1),b.each(this.instances,function(){d.element!==this&&(e&&!c.call(this.data("jPlayer"),d)||this.jPlayer.apply(this,g))}))},pauseOthers:function(a){this.tellOthers("pause", +function(){return this.status.srcSet},a)},stop:function(){this.status.srcSet?this.html.active?this._html_pause(0):this.flash.active&&this._flash_pause(0):this._urlNotSetError("stop")},playHead:function(a){a=this._limitValue(a,0,100);this.status.srcSet?this.html.active?this._html_playHead(a):this.flash.active&&this._flash_playHead(a):this._urlNotSetError("playHead")},_muted:function(a){this.mutedWorker(a);this.options.globalVolume&&this.tellOthers("mutedWorker",function(){return this.options.globalVolume}, +a)},mutedWorker:function(a){this.options.muted=a;this.html.used&&this._html_setProperty("muted",a);this.flash.used&&this._flash_mute(a);this.html.video.gate||this.html.audio.gate||(this._updateMute(a),this._updateVolume(this.options.volume),this._trigger(b.jPlayer.event.volumechange))},mute:function(a){a=a===f?!0:!!a;this._muted(a)},unmute:function(a){a=a===f?!0:!!a;this._muted(!a)},_updateMute:function(a){a===f&&(a=this.options.muted);this.css.jq.mute.length&&this.css.jq.unmute.length&&(this.status.noVolume? +(this.css.jq.mute.hide(),this.css.jq.unmute.hide()):a?(this.css.jq.mute.hide(),this.css.jq.unmute.show()):(this.css.jq.mute.show(),this.css.jq.unmute.hide()))},volume:function(a){this.volumeWorker(a);this.options.globalVolume&&this.tellOthers("volumeWorker",function(){return this.options.globalVolume},a)},volumeWorker:function(a){a=this._limitValue(a,0,1);this.options.volume=a;this.html.used&&this._html_setProperty("volume",a);this.flash.used&&this._flash_volume(a);this.html.video.gate||this.html.audio.gate|| +(this._updateVolume(a),this._trigger(b.jPlayer.event.volumechange))},volumeBar:function(a){if(this.css.jq.volumeBar.length){var c=b(a.currentTarget),d=c.offset(),e=a.pageX-d.left,g=c.width();a=c.height()-a.pageY+d.top;c=c.height();this.options.verticalVolume?this.volume(a/c):this.volume(e/g)}this.options.muted&&this._muted(!1)},volumeBarValue:function(){},_updateVolume:function(a){a===f&&(a=this.options.volume);a=this.options.muted?0:a;this.status.noVolume?(this.css.jq.volumeBar.length&&this.css.jq.volumeBar.hide(), +this.css.jq.volumeBarValue.length&&this.css.jq.volumeBarValue.hide(),this.css.jq.volumeMax.length&&this.css.jq.volumeMax.hide()):(this.css.jq.volumeBar.length&&this.css.jq.volumeBar.show(),this.css.jq.volumeBarValue.length&&(this.css.jq.volumeBarValue.show(),this.css.jq.volumeBarValue[this.options.verticalVolume?"height":"width"](100*a+"%")),this.css.jq.volumeMax.length&&this.css.jq.volumeMax.show())},volumeMax:function(){this.volume(1);this.options.muted&&this._muted(!1)},_cssSelectorAncestor:function(a){var c= +this;this.options.cssSelectorAncestor=a;this._removeUiClass();this.ancestorJq=a?b(a):[];a&&1!==this.ancestorJq.length&&this._warning({type:b.jPlayer.warning.CSS_SELECTOR_COUNT,context:a,message:b.jPlayer.warningMsg.CSS_SELECTOR_COUNT+this.ancestorJq.length+" found for cssSelectorAncestor.",hint:b.jPlayer.warningHint.CSS_SELECTOR_COUNT});this._addUiClass();b.each(this.options.cssSelector,function(a,b){c._cssSelector(a,b)});this._updateInterface();this._updateButtons();this._updateAutohide();this._updateVolume(); +this._updateMute()},_cssSelector:function(a,c){var d=this;"string"===typeof c?b.jPlayer.prototype.options.cssSelector[a]?(this.css.jq[a]&&this.css.jq[a].length&&this.css.jq[a].unbind(".jPlayer"),this.options.cssSelector[a]=c,this.css.cs[a]=this.options.cssSelectorAncestor+" "+c,this.css.jq[a]=c?b(this.css.cs[a]):[],this.css.jq[a].length&&this.css.jq[a].bind("click.jPlayer",function(c){c.preventDefault();d[a](c);b(this).blur()}),c&&1!==this.css.jq[a].length&&this._warning({type:b.jPlayer.warning.CSS_SELECTOR_COUNT, +context:this.css.cs[a],message:b.jPlayer.warningMsg.CSS_SELECTOR_COUNT+this.css.jq[a].length+" found for "+a+" method.",hint:b.jPlayer.warningHint.CSS_SELECTOR_COUNT})):this._warning({type:b.jPlayer.warning.CSS_SELECTOR_METHOD,context:a,message:b.jPlayer.warningMsg.CSS_SELECTOR_METHOD,hint:b.jPlayer.warningHint.CSS_SELECTOR_METHOD}):this._warning({type:b.jPlayer.warning.CSS_SELECTOR_STRING,context:c,message:b.jPlayer.warningMsg.CSS_SELECTOR_STRING,hint:b.jPlayer.warningHint.CSS_SELECTOR_STRING})}, +seekBar:function(a){if(this.css.jq.seekBar.length){var c=b(a.currentTarget),d=c.offset();a=a.pageX-d.left;c=c.width();this.playHead(100*a/c)}},playBar:function(){},playbackRate:function(a){this._setOption("playbackRate",a)},playbackRateBar:function(a){if(this.css.jq.playbackRateBar.length){var c=b(a.currentTarget),d=c.offset(),e=a.pageX-d.left,g=c.width();a=c.height()-a.pageY+d.top;c=c.height();this.playbackRate((this.options.verticalPlaybackRate?a/c:e/g)*(this.options.maxPlaybackRate-this.options.minPlaybackRate)+ +this.options.minPlaybackRate)}},playbackRateBarValue:function(){},_updatePlaybackRate:function(){var a=(this.options.playbackRate-this.options.minPlaybackRate)/(this.options.maxPlaybackRate-this.options.minPlaybackRate);this.status.playbackRateEnabled?(this.css.jq.playbackRateBar.length&&this.css.jq.playbackRateBar.show(),this.css.jq.playbackRateBarValue.length&&(this.css.jq.playbackRateBarValue.show(),this.css.jq.playbackRateBarValue[this.options.verticalPlaybackRate?"height":"width"](100*a+"%"))): +(this.css.jq.playbackRateBar.length&&this.css.jq.playbackRateBar.hide(),this.css.jq.playbackRateBarValue.length&&this.css.jq.playbackRateBarValue.hide())},repeat:function(){this._loop(!0)},repeatOff:function(){this._loop(!1)},_loop:function(a){this.options.loop!==a&&(this.options.loop=a,this._updateButtons(),this._trigger(b.jPlayer.event.repeat))},currentTime:function(){},duration:function(){},gui:function(){},noSolution:function(){},option:function(a,c){var d=a;if(0===arguments.length)return b.extend(!0, +{},this.options);if("string"===typeof a){var e=a.split(".");if(c===f){for(var d=b.extend(!0,{},this.options),g=0;g= +a&&(b=!0);return b},_validString:function(a){return a&&"string"===typeof a},_limitValue:function(a,b,d){return ad?d:a},_urlNotSetError:function(a){this._error({type:b.jPlayer.error.URL_NOT_SET,context:a,message:b.jPlayer.errorMsg.URL_NOT_SET,hint:b.jPlayer.errorHint.URL_NOT_SET})},_flashError:function(a){var c;c=this.internal.ready?"FLASH_DISABLED":"FLASH";this._error({type:b.jPlayer.error[c],context:this.internal.flash.swf,message:b.jPlayer.errorMsg[c]+a.message,hint:b.jPlayer.errorHint[c]}); +this.internal.flash.jq.css({width:"1px",height:"1px"})},_error:function(a){this._trigger(b.jPlayer.event.error,a);this.options.errorAlerts&&this._alert("Error!"+(a.message?"\n"+a.message:"")+(a.hint?"\n"+a.hint:"")+"\nContext: "+a.context)},_warning:function(a){this._trigger(b.jPlayer.event.warning,f,a);this.options.warningAlerts&&this._alert("Warning!"+(a.message?"\n"+a.message:"")+(a.hint?"\n"+a.hint:"")+"\nContext: "+a.context)},_alert:function(a){a="jPlayer "+this.version.script+" : id='"+this.internal.self.id+ +"' : "+a;this.options.consoleAlerts?console&&console.log&&console.log(a):alert(a)},_emulateHtmlBridge:function(){var a=this;b.each(b.jPlayer.emulateMethods.split(/\s+/g),function(b,d){a.internal.domNode[d]=function(b){a[d](b)}});b.each(b.jPlayer.event,function(c,d){var e=!0;b.each(b.jPlayer.reservedEvent.split(/\s+/g),function(a,b){if(b===c)return e=!1});e&&a.element.bind(d+".jPlayer.jPlayerHtml",function(){a._emulateHtmlUpdate();var b=document.createEvent("Event");b.initEvent(c,!1,!0);a.internal.domNode.dispatchEvent(b)})})}, +_emulateHtmlUpdate:function(){var a=this;b.each(b.jPlayer.emulateStatus.split(/\s+/g),function(b,d){a.internal.domNode[d]=a.status[d]});b.each(b.jPlayer.emulateOptions.split(/\s+/g),function(b,d){a.internal.domNode[d]=a.options[d]})},_destroyHtmlBridge:function(){var a=this;this.element.unbind(".jPlayerHtml");b.each((b.jPlayer.emulateMethods+" "+b.jPlayer.emulateStatus+" "+b.jPlayer.emulateOptions).split(/\s+/g),function(b,d){delete a.internal.domNode[d]})}};b.jPlayer.error={FLASH:"e_flash",FLASH_DISABLED:"e_flash_disabled", +NO_SOLUTION:"e_no_solution",NO_SUPPORT:"e_no_support",URL:"e_url",URL_NOT_SET:"e_url_not_set",VERSION:"e_version"};b.jPlayer.errorMsg={FLASH:"jPlayer's Flash fallback is not configured correctly, or a command was issued before the jPlayer Ready event. Details: ",FLASH_DISABLED:"jPlayer's Flash fallback has been disabled by the browser due to the CSS rules you have used. Details: ",NO_SOLUTION:"No solution can be found by jPlayer in this browser. Neither HTML nor Flash can be used.",NO_SUPPORT:"It is not possible to play any media format provided in setMedia() on this browser using your current options.", +URL:"Media URL could not be loaded.",URL_NOT_SET:"Attempt to issue media playback commands, while no media url is set.",VERSION:"jPlayer "+b.jPlayer.prototype.version.script+" needs Jplayer.swf version "+b.jPlayer.prototype.version.needFlash+" but found "};b.jPlayer.errorHint={FLASH:"Check your swfPath option and that Jplayer.swf is there.",FLASH_DISABLED:"Check that you have not display:none; the jPlayer entity or any ancestor.",NO_SOLUTION:"Review the jPlayer options: support and supplied.",NO_SUPPORT:"Video or audio formats defined in the supplied option are missing.", +URL:"Check media URL is valid.",URL_NOT_SET:"Use setMedia() to set the media URL.",VERSION:"Update jPlayer files."};b.jPlayer.warning={CSS_SELECTOR_COUNT:"e_css_selector_count",CSS_SELECTOR_METHOD:"e_css_selector_method",CSS_SELECTOR_STRING:"e_css_selector_string",OPTION_KEY:"e_option_key"};b.jPlayer.warningMsg={CSS_SELECTOR_COUNT:"The number of css selectors found did not equal one: ",CSS_SELECTOR_METHOD:"The methodName given in jPlayer('cssSelector') is not a valid jPlayer method.",CSS_SELECTOR_STRING:"The methodCssSelector given in jPlayer('cssSelector') is not a String or is empty.", +OPTION_KEY:"The option requested in jPlayer('option') is undefined."};b.jPlayer.warningHint={CSS_SELECTOR_COUNT:"Check your css selector and the ancestor.",CSS_SELECTOR_METHOD:"Check your method name.",CSS_SELECTOR_STRING:"Check your css selector is a string.",OPTION_KEY:"Check your option name."}}); \ No newline at end of file diff --git a/video/jplayer/skins/premium/gui.php b/video/jplayer/skins/premium/gui.php new file mode 100644 index 000000000..6aa8037db --- /dev/null +++ b/video/jplayer/skins/premium/gui.php @@ -0,0 +1,32 @@ + \ No newline at end of file diff --git a/video/jplayer/skins/premium/jplayer.premium.css b/video/jplayer/skins/premium/jplayer.premium.css new file mode 100755 index 000000000..a4aba28f7 --- /dev/null +++ b/video/jplayer/skins/premium/jplayer.premium.css @@ -0,0 +1,188 @@ +/* @override + http://wpdev.dynalias.com/focus/wp-content/themes/focus/jplayer/skins/siteorigin/jplayer.siteorigin.css?ver=3.5 */ + +.jp-audio, +.jp-audio-stream, +.jp-video { + background-color:#1c1c1c; + position: relative; +} + +.jp-video .jp-type-single{ +} + +/* @group GUI */ + +.jp-video .jp-gui{ + height: 35px; + overflow: hidden; + margin-top: -35px; + + background-image: -webkit-gradient(linear, left top, left bottom, from(#F1F1F1), to(#D1D1D1)); + + box-shadow: 0 0 1px rgba(255,255,255,1); + + position: relative; +} + +.jp-video .jp-gui .jp-controls { + list-style: none; + display: block; + margin: 0; +} + +.jp-video .jp-gui .jp-controls li a{ + position: absolute; + display: block; + text-indent: -9999px; + overflow: hidden; +} + +.jp-video .jp-gui .jp-controls .jp-play, +.jp-video .jp-gui .jp-controls .jp-pause{ + width: 15px; + height: 15px; + overflow: hidden; + + top: 11px; + left: 15px; + + background: url('sprites/play.png'); +} + +.jp-video .jp-gui .jp-controls .jp-pause{ + background: url('sprites/pause.png'); + width: 13px; +} + +/* @end */ + +/* @group Progress Bar */ + +.jp-video .jp-progress{ + display: block; + height: 10px; + width: auto; + margin: 0 125px 0 51px; + background: #706d6d; + margin-top: 13px; + + border-radius: 10px; + box-shadow: inset 0 1px 4px rgba(0,0,0,0.4), 0 1px 0 #FFF; +} + +.jp-video .jp-progress .jp-seek-bar{ + height: 10px; + background: rgba(34,34,34,0.34); + border-radius: 10px; + + cursor: pointer; +} + +.jp-video .jp-progress .jp-play-bar{ + position: relative; + height: 10px; + background: #bb2a1e; + border-radius: 10px; + box-shadow: inset 0 1px 4px rgba(0,0,0,0.4); +} + +.jp-video .jp-progress .jp-play-bar-marker{ + position: absolute; + width: 16px; + height: 17px; + background: url('sprites/handle.png') no-repeat; + right: -8px; + top: -3px; +} + +/* @end */ + +.jp-video .jp-time-info{ + + position: absolute; + right: 34px; + top: 50%; + margin-top: -0.475em; + width: 80px; + color: #444; + text-shadow: 0 1px 0 #FFF; + font: 11px/1 Arial, sans-serif; +} + +.jp-video .jp-time-info > div{ + display: inline; +} + +.jp-video .jp-full-screen, +.jp-video .jp-restore-screen{ + display: block; + top: 11px; + right: 15px; + + width: 14px; + height: 15px; + background: url('sprites/full-screen.png') no-repeat; +} + +/* @group Full Screen */ + +.jp-video-full .jp-jplayer{ + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 100000; /* One higher than the WordPress admin bar */ + background: #000000; +} + +.jp-video-full .jp-gui{ + position: fixed; + left: 0; + bottom: 0; + width: 100%; + z-index: 100001; /* One higher than the video player */ +} + +/* @end */ + +.jp-video .sep{ + position: absolute; + display: block; + width: 2px; + height: 21px; + background: url('sprites/sep.png') no-repeat; + top: 7px; +} + +.jp-video .jp-controls-sep{ + left: 40px; +} + +.jp-video .jp-time-sep{ + right: 235px; +} + +.jp-video .jp-full-sep{ + right: 39px; +} + +.jp-video .jp-play{ + position: absolute; + top: 50%; + left: 50%; + cursor: pointer; +} + +.jp-video .jp-play-default{ + margin-top: -49px; + margin-left: -49px; + + width: 97px; + height: 97px; +} + +.jp-video .jp-gui{ + display: none; +} \ No newline at end of file diff --git a/video/jplayer/skins/premium/sprites/full-screen.png b/video/jplayer/skins/premium/sprites/full-screen.png new file mode 100644 index 000000000..2dcdfe75b Binary files /dev/null and b/video/jplayer/skins/premium/sprites/full-screen.png differ diff --git a/video/jplayer/skins/premium/sprites/handle.png b/video/jplayer/skins/premium/sprites/handle.png new file mode 100644 index 000000000..c49647f2a Binary files /dev/null and b/video/jplayer/skins/premium/sprites/handle.png differ diff --git a/video/jplayer/skins/premium/sprites/large-play.png b/video/jplayer/skins/premium/sprites/large-play.png new file mode 100644 index 000000000..81ecaeb47 Binary files /dev/null and b/video/jplayer/skins/premium/sprites/large-play.png differ diff --git a/video/jplayer/skins/premium/sprites/pause.png b/video/jplayer/skins/premium/sprites/pause.png new file mode 100644 index 000000000..6cab26321 Binary files /dev/null and b/video/jplayer/skins/premium/sprites/pause.png differ diff --git a/video/jplayer/skins/premium/sprites/play.png b/video/jplayer/skins/premium/sprites/play.png new file mode 100644 index 000000000..fd8eed9aa Binary files /dev/null and b/video/jplayer/skins/premium/sprites/play.png differ diff --git a/video/jplayer/skins/premium/sprites/sep.png b/video/jplayer/skins/premium/sprites/sep.png new file mode 100644 index 000000000..82c34e243 Binary files /dev/null and b/video/jplayer/skins/premium/sprites/sep.png differ diff --git a/video/jplayer/skins/siteorigin/gui.php b/video/jplayer/skins/siteorigin/gui.php new file mode 100644 index 000000000..acca2582a --- /dev/null +++ b/video/jplayer/skins/siteorigin/gui.php @@ -0,0 +1,33 @@ + \ No newline at end of file diff --git a/video/jplayer/skins/siteorigin/jplayer.siteorigin.css b/video/jplayer/skins/siteorigin/jplayer.siteorigin.css new file mode 100755 index 000000000..e92527dc6 --- /dev/null +++ b/video/jplayer/skins/siteorigin/jplayer.siteorigin.css @@ -0,0 +1,183 @@ +/* @override + http://wpdev.dynalias.com/focus/wp-content/themes/focus/jplayer/skins/siteorigin/jplayer.siteorigin.css?ver=3.5 */ + +.jp-audio, +.jp-audio-stream, +.jp-video { + position: relative; +} + +.jp-video .jp-type-single{ +} + +/* @group GUI */ + +.jp-video .jp-gui{ + height: 60px; + margin-top: -60px; + + position: relative; + overflow: hidden; + + opacity: 0.85; +} + +.jp-video .jp-gui .jp-controls { + list-style: none; + display: block; + margin: 0; +} + +.jp-video .jp-gui .jp-controls a{ + position: absolute; + display: block; + text-indent: -9999px; + overflow: hidden; +} + +.jp-video .jp-gui .jp-controls .jp-play, +.jp-video .jp-gui .jp-controls .jp-pause{ + width: 55px; + height: 35px; + overflow: hidden; + display:block; + + top: 10px; + left: 15px; + margin: 0 !important; + + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} + +.jp-video .jp-gui .jp-controls .jp-play{ + background: rgba(0,0,0,0.8) url('sprites/play.png') center center no-repeat; +} + +.jp-video .jp-gui .jp-controls .jp-pause{ + background: rgba(0,0,0,0.8) url('sprites/pause.png') center center no-repeat; +} + +/* @end */ + +/* @group Progress Bar */ + +.jp-video .jp-progress-wrapper{ + display: block; + width: auto; + margin: 0px 65px 0 90px; + padding-top: 10px; +} + +.jp-video .jp-progress{ + + background-color: rgba(0,0,0,0.8); + margin-top: 13px; + + border-radius: 3px; +} + +.jp-video .jp-progress .jp-seek-bar{ + height: 10px; + background: rgba(34,34,34,0.34); + border-radius: 3px; + + cursor: pointer; +} + +.jp-video .jp-progress .jp-play-bar{ + position: relative; + height: 10px; + background: #3fabe9; + border-radius: 3px; +} + +/* @end */ + +.jp-video .jp-time-info{ + + width: 40px; + padding: 3px 2px; + + position: absolute; + right: -21px; + bottom: 15px; + + background-color: rgba(0,0,0,0.8); + + color: #777777; + font: 11px/1 Arial, sans-serif; + text-align:center; + + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; +} + +.jp-video .jp-time-info .jp-time-info-pointer{ + position: absolute; + width: 7px; + height: 4px; + left: 50%; + margin-left: -3.5px; + bottom: -4px; + background: url("sprites/time-pointer.png") no-repeat; +} + +.jp-video .jp-time-info > div{ + display: inline; +} + +.jp-video .jp-full-screen, +.jp-video .jp-restore-screen{ + display: block; + top: 19px; + right: 25px; + + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + + width: 18px; + height: 18px; + background: url('sprites/expand.png') center center no-repeat; + background-color: #222222; + background-color: rgba(0,0,0,0.8); +} + +.jp-video .jp-restore-screen{ + background: url('sprites/restore.png') no-repeat center center no-repeat; + background-color: #222222; + background-color: rgba(0,0,0,0.8); +} + +/* @group Full Screen */ + +.jp-video-full .jp-jplayer{ + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 100000; /* One higher than the WordPress admin bar */ + background: #000000; +} + +.jp-video-full .jp-gui{ + position: fixed; + left: 0; + bottom: 0; + width: 100%; + z-index: 100001; /* One higher than the video player */ +} + +/* @end */ + +.jp-video .jp-play-initial{ + display:none !important; +} \ No newline at end of file diff --git a/video/jplayer/skins/siteorigin/sprites/expand.png b/video/jplayer/skins/siteorigin/sprites/expand.png new file mode 100644 index 000000000..7d48743f5 Binary files /dev/null and b/video/jplayer/skins/siteorigin/sprites/expand.png differ diff --git a/video/jplayer/skins/siteorigin/sprites/large-play.png b/video/jplayer/skins/siteorigin/sprites/large-play.png new file mode 100644 index 000000000..81ecaeb47 Binary files /dev/null and b/video/jplayer/skins/siteorigin/sprites/large-play.png differ diff --git a/video/jplayer/skins/siteorigin/sprites/pause.png b/video/jplayer/skins/siteorigin/sprites/pause.png new file mode 100644 index 000000000..4e5b74f19 Binary files /dev/null and b/video/jplayer/skins/siteorigin/sprites/pause.png differ diff --git a/video/jplayer/skins/siteorigin/sprites/play.png b/video/jplayer/skins/siteorigin/sprites/play.png new file mode 100644 index 000000000..e2112462c Binary files /dev/null and b/video/jplayer/skins/siteorigin/sprites/play.png differ diff --git a/video/jplayer/skins/siteorigin/sprites/restore.png b/video/jplayer/skins/siteorigin/sprites/restore.png new file mode 100644 index 000000000..9257c9d98 Binary files /dev/null and b/video/jplayer/skins/siteorigin/sprites/restore.png differ diff --git a/video/jplayer/skins/siteorigin/sprites/time-pointer.png b/video/jplayer/skins/siteorigin/sprites/time-pointer.png new file mode 100644 index 000000000..3a54d35b7 Binary files /dev/null and b/video/jplayer/skins/siteorigin/sprites/time-pointer.png differ diff --git a/video/panels.video.jquery.js b/video/panels.video.jquery.js new file mode 100644 index 000000000..f1e139b0c --- /dev/null +++ b/video/panels.video.jquery.js @@ -0,0 +1,64 @@ +jQuery(function($){ + + $(window ).resize(function(){ + $('.jp-jplayer' ).each(function(){ + var $$ = $(this); + + if($$.data('player-ready') != undefined){ + // Change the height of the player + var ratio = Number($$.attr('data-ratio')); + $$.jPlayer( { size: {height: Math.floor($$.closest('.widget' ).outerWidth() / 1.777)} } ); + } + }); + }) + + + $('.jp-jplayer' ).each(function(){ + var $$ = $(this); + + var ratio = Number($$.attr('data-ratio')); + + $$.jPlayer({ + ready: function(){ + $$.data('player-ready', true); + + $(this).jPlayer("setMedia", { + m4v : $(this).attr('data-video'), + poster: $(this).attr('data-poster') + } ); + + if($(this ).attr('data-mobile') == 'true'){ + $(this).find('.jp-gui' ).hide(); + } + else{ + $(this ).find('.jp-gui' ).show(); + } + + // Check if we're using autoplay + if(Number($(this).attr('data-autoplay')) == 1){ $$.jPlayer("play"); } + + $(this).jPlayer( { size: {height: Math.floor($(this).closest('.widget' ).outerWidth() / ratio)} } ); + }, + solution: "flash, html", + supplied : "m4v", + swfPath : $(this).attr('data-swfpath'), + autohide : { + restored: false, + full: false + }, + play: function(){ + $(this).jPlayer("pauseOthers"); + $(this ).jPlayer('option', 'autohide', { + restored: true, + full: true, + hold: 2000 + }); + }, + size: { + width: "100%", + height: Math.floor($$.closest('.widget' ).outerWidth() / ratio) + }, + cssSelectorAncestor: "#" + $$.closest('.jp-video' ).attr('id') + }); + }); +}); \ No newline at end of file diff --git a/video/poster.jpg b/video/poster.jpg new file mode 100644 index 000000000..a50df523d Binary files /dev/null and b/video/poster.jpg differ diff --git a/widgets/basic.php b/widgets/basic.php new file mode 100644 index 000000000..d76ffafc1 --- /dev/null +++ b/widgets/basic.php @@ -0,0 +1,439 @@ + __( 'A full SiteOrigin Page Builder layout as a widget.', 'siteorigin-panels' ), + 'panels_title' => false, + ), + array( + ) + ); + } + + function widget($args, $instance) { + if( empty($instance['panels_data']) ) return; + + if( is_string( $instance['panels_data'] ) ) + $instance['panels_data'] = json_decode( $instance['panels_data'], true ); + if(empty($instance['panels_data']['widgets'])) return; + + if( empty( $instance['builder_id'] ) ) $instance['builder_id'] = uniqid(); + + echo $args['before_widget']; + echo siteorigin_panels_render( 'w'.$instance['builder_id'], true, $instance['panels_data'] ); + echo $args['after_widget']; + } + + function update($new, $old) { + $new['builder_id'] = uniqid(); + return $new; + } + + function form($instance){ + $instance = wp_parse_args($instance, array( + 'panels_data' => '', + 'builder_id' => uniqid(), + ) ); + + if( !is_string( $instance['panels_data'] ) ) $instance['panels_data'] = json_encode( $instance['panels_data'] ); + + ?> +
+

+ + + +
+ + __( 'Displays some form of post content form the current post.', 'siteorigin-panels' ), + ) + ); + } + + function widget( $args, $instance ) { + if( is_admin() ) return; + + echo $args['before_widget']; + $content = apply_filters('siteorigin_panels_widget_post_content', $this->default_content($instance['type'])); + echo $content; + echo $args['after_widget']; + } + + /** + * The default content for post types + * @param $type + * @return string + */ + function default_content($type){ + global $post; + if(empty($post)) return; + + switch($type) { + case 'title' : + return '

' . $post->post_title . '

'; + case 'content' : + return '
' . wpautop($post->post_content) . '
'; + case 'featured' : + if(!has_post_thumbnail()) return ''; + return ''; + default : + return ''; + } + } + + function update($new, $old){ + return $new; + } + + function form( $instance ) { + $instance = wp_parse_args($instance, array( + 'type' => 'content', + )); + + $types = apply_filters('siteorigin_panels_widget_post_content_types', array( + '' => __('None', 'siteorigin-panels'), + 'title' => __('Title', 'siteorigin-panels'), + 'featured' => __('Featured Image', 'siteorigin-panels'), + )); + + ?> +

+ + +

+ __( 'Displays a post loop.', 'siteorigin-panels' ), + ) + ); + } + + /** + * @param array $args + * @param array $instance + */ + function widget( $args, $instance ) { + if( empty( $instance['template'] ) ) return; + if( is_admin() ) return; + + $template = $instance['template']; + $query_args = $instance; + unset($query_args['template']); + unset($query_args['additional']); + unset($query_args['sticky']); + unset($query_args['title']); + + $query_args = wp_parse_args($instance['additional'], $query_args); + + global $wp_rewrite; + + if( $wp_rewrite->using_permalinks() ) { + + if( get_query_var('paged') ) { + // When the widget appears on a sub page. + $query_args['paged'] = get_query_var('paged'); + } + elseif( strpos( $_SERVER['REQUEST_URI'], '/page/' ) !== false ) { + // When the widget appears on the home page. + preg_match('/\/page\/([0-9]+)\//', $_SERVER['REQUEST_URI'], $matches); + if(!empty($matches[1])) $query_args['paged'] = intval($matches[1]); + else $query_args['paged'] = 1; + } + else $query_args['paged'] = 1; + } + else { + // Get current page number when we're not using permalinks + $query_args['paged'] = isset($_GET['paged']) ? intval($_GET['paged']) : 1; + } + + switch($instance['sticky']){ + case 'ignore' : + $query_args['ignore_sticky_posts'] = 1; + break; + case 'only' : + $query_args['post__in'] = get_option( 'sticky_posts' ); + break; + case 'exclude' : + $query_args['post__not_in'] = get_option( 'sticky_posts' ); + break; + } + + // Exclude the current post to prevent possible infinite loop + + global $siteorigin_panels_current_post; + + if( !empty($siteorigin_panels_current_post) ){ + if(!empty($query_args['post__not_in'])){ + $query_args['post__not_in'][] = $siteorigin_panels_current_post; + } + else { + $query_args['post__not_in'] = array( $siteorigin_panels_current_post ); + } + } + + if( !empty($query_args['post__in']) && !is_array($query_args['post__in']) ) { + $query_args['post__in'] = explode(',', $query_args['post__in']); + $query_args['post__in'] = array_map('intval', $query_args['post__in']); + } + + // Create the query + query_posts($query_args); + echo $args['before_widget']; + + // Filter the title + $instance['title'] = apply_filters('widget_title', $instance['title'], $instance, $this->id_base); + if ( !empty( $instance['title'] ) ) { + echo $args['before_title'] . $instance['title'] . $args['after_title']; + } + + add_filter( 'siteorigin_panels_filter_content_enabled', array( 'SiteOrigin_Panels_Widgets_PostLoop', 'remove_content_filter' ) ); + + global $more; $old_more = $more; $more = empty($instance['more']); + + if(strpos('/'.$instance['template'], '/content') !== false) { + while( have_posts() ) { + the_post(); + locate_template($instance['template'], true, false); + } + } + else { + locate_template($instance['template'], true, false); + } + + $more = $old_more; + remove_filter( 'siteorigin_panels_filter_content_enabled', array( 'SiteOrigin_Panels_Widgets_PostLoop', 'remove_content_filter' ) ); + + echo $args['after_widget']; + + // Reset everything + wp_reset_query(); + } + + /** + * @return bool + */ + static function remove_content_filter(){ + return false; + } + + /** + * Update the widget + * + * @param array $new + * @param array $old + * @return array + */ + function update($new, $old){ + $new['more'] = !empty( $new['more'] ); + return $new; + } + + /** + * Get all the existing files + * + * @return array + */ + function get_loop_templates(){ + $templates = array(); + + $template_files = array( + 'loop*.php', + '*/loop*.php', + 'content*.php', + '*/content*.php', + ); + + $template_dirs = array(get_template_directory(), get_stylesheet_directory()); + $template_dirs = array_unique($template_dirs); + foreach($template_dirs as $dir ){ + foreach($template_files as $template_file) { + foreach((array) glob($dir.'/'.$template_file) as $file) { + if( file_exists( $file ) ) $templates[] = str_replace($dir.'/', '', $file); + } + } + } + + $templates = array_unique($templates); + $templates = apply_filters('siteorigin_panels_postloop_templates', $templates); + sort($templates); + + return $templates; + } + + /** + * Display the form for the post loop. + * + * @param array $instance + * @return string|void + */ + function form( $instance ) { + $instance = wp_parse_args($instance, array( + 'title' => '', + 'template' => 'loop.php', + + // Query args + 'post_type' => 'post', + 'posts_per_page' => '', + + 'order' => 'DESC', + 'orderby' => 'date', + + 'sticky' => '', + + 'additional' => '', + 'more' => false, + )); + + $templates = $this->get_loop_templates(); + if( empty($templates) ) { + ?>

true)); + $post_types = array_values($post_types); + $post_types = array_diff($post_types, array('attachment', 'revision', 'nav_menu_item')); + + ?> +

+ + +

+

+ + +

+

+ + +

+ +

+ + +

+ +

+ + +

+ +

+ + +

+ +

+ + +

+ +

+ + />
+ +

+ +

+ + + query_posts.', 'siteorigin-panels'), 'http://codex.wordpress.org/Function_Reference/query_posts') ?> +

+ + \ + .fluid-width-video-wrapper { \ + width: 100%; \ + position: relative; \ + padding: 0; \ + } \ + \ + .fluid-width-video-wrapper iframe, \ + .fluid-width-video-wrapper object, \ + .fluid-width-video-wrapper embed { \ + position: absolute; \ + top: 0; \ + left: 0; \ + width: 100%; \ + height: 100%; \ + } \ + '; + + ref.parentNode.insertBefore(div,ref); + + } + + if ( options ) { + $.extend( settings, options ); + } + + return this.each(function(){ + var selectors = [ + "iframe[src*='player.vimeo.com']", + "iframe[src*='youtube.com']", + "iframe[src*='youtube-nocookie.com']", + "iframe[src*='kickstarter.com'][src*='video.html']", + "object", + "embed" + ]; + + if (settings.customSelector) { + selectors.push(settings.customSelector); + } + + var $allVideos = $(this).find(selectors.join(',')); + $allVideos = $allVideos.not("object object"); // SwfObj conflict patch + + $allVideos.each(function(){ + var $this = $(this); + if (this.tagName.toLowerCase() === 'embed' && $this.parent('object').length || $this.parent('.fluid-width-video-wrapper').length) { return; } + var height = ( this.tagName.toLowerCase() === 'object' || ($this.attr('height') && !isNaN(parseInt($this.attr('height'), 10))) ) ? parseInt($this.attr('height'), 10) : $this.height(), + width = !isNaN(parseInt($this.attr('width'), 10)) ? parseInt($this.attr('width'), 10) : $this.width(), + aspectRatio = height / width; + if(!$this.attr('id')){ + var videoID = 'fitvid' + Math.floor(Math.random()*999999); + $this.attr('id', videoID); + } + $this.wrap('
').parent('.fluid-width-video-wrapper').css('padding-top', (aspectRatio * 100)+"%"); + $this.removeAttr('height').removeAttr('width'); + }); + }); + }; +})( jQuery ); diff --git a/widgets/less/functions.php b/widgets/less/functions.php new file mode 100644 index 000000000..73bd7bf59 --- /dev/null +++ b/widgets/less/functions.php @@ -0,0 +1,93 @@ +lum += $a2_value/100; + else $color->lum -= $a2_value/100; + + return array('raw_color', $color->hex); +} + +function origin_widgets_less_lumlighten($args){ + return origin_widgets_less_lum_change($args, 'lighten'); +} + +function origin_widgets_less_lumdarken($args){ + return origin_widgets_less_lum_change($args, 'darken'); +} + +/** + * Less handler function for texture function + * + * @param $texture + * @return string + */ +function origin_widgets_less_texture($texture){ + if($texture[0] != 'list') return ''; + + $return = ''; + foreach($texture[2] as $arg) { + if($arg[0] == 'keyword') { + $t = $arg[1]; + if($t == 'none') continue; + foreach(SiteOrigin_Panels_Widget::get_image_folders() as $folder => $folder_url) { + if(file_exists($folder.'/textures/'.$t.'.png')) { + $return .= 'url('.esc_url($folder_url.'/textures/'.$t.'.png').') repeat '; + break; + } + } + } + elseif($arg[0] == 'raw_color') { + $return .= $arg[1].' '; + } + } + return trim($return); +} + +/** + * Less handler function for widgetimage function + * + * @param $url + * @return string + */ +function origin_widgets_less_widgetimage($url){ + $the_url = ''; + foreach($url[2] as $p){ + if(is_string($p)){ + $the_url .= $p; + } + elseif(is_array($p)){ + $the_url .= $p[1]; + } + } + + // Search for the appropriate image + $return_url = ''; + foreach(SiteOrigin_Panels_Widget::get_image_folders() as $folder => $folder_url) { + if(file_exists($folder.'/'.$the_url)) { + $return_url = $folder_url.'/'.$the_url; + } + } + + if ( is_ssl() ) { + $return_url = str_replace('http://', 'https://', $return_url); + } + + return $return_url; +} \ No newline at end of file diff --git a/widgets/less/mixins.less b/widgets/less/mixins.less new file mode 100755 index 000000000..311b74a1b --- /dev/null +++ b/widgets/less/mixins.less @@ -0,0 +1,183 @@ +.gradient(@color: #F5F5F5, @start: #EEE, @stop: #FFF) { + background: @color; + background: -webkit-gradient(linear, left bottom, left top, color-stop(0, @start), color-stop(1, @stop)); + background: -ms-linear-gradient(bottom,@start,@stop); + background: -moz-linear-gradient(center bottom,@start 0%,@stop 100%); + background: -o-linear-gradient(@stop,@start); +} + +.bw-gradient(@color: #F5F5F5, @start: 0, @stop: 255) { + background: @color; + background: -webkit-gradient(linear, left bottom, left top, color-stop(0, rgb(@start,@start,@start)), color-stop(1, rgb(@stop,@stop,@stop))); + background: -ms-linear-gradient(bottom, rgb(@start,@start,@start) 0%, rgb(@stop,@stop,@stop) 100%); + background: -moz-linear-gradient(center bottom, rgb(@start,@start,@start) 0%, rgb(@stop,@stop,@stop) 100%); + background: -o-linear-gradient(rgb(@stop,@stop,@stop), rgb(@start,@start,@start)); + background: linear-gradient(rgb(@stop,@stop,@stop), rgb(@start,@start,@start)); +} + +.linear-gradient(@color, @gradient) { + background: @color; + background: -moz-linear-gradient(@gradient); + background: -webkit-linear-gradient(@gradient); + background: -o-linear-gradient(@gradient); + background: -ms-linear-gradient(@gradient); + background: linear-gradient(@gradient); +} + +.bordered(@top-color: #EEE, @right-color: #EEE, @bottom-color: #EEE, @left-color: #EEE) { + border-top: solid 1px @top-color; + border-left: solid 1px @left-color; + border-right: solid 1px @right-color; + border-bottom: solid 1px @bottom-color; +} + +.drop-shadow(@x-axis: 0, @y-axis: 1px, @blur: 2px, @alpha: 0.1) { + -webkit-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); + -moz-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); + box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); +} + +.box-shadow(@shadow) { + -webkit-box-shadow: @shadow; + -moz-box-shadow: @shadow; + box-shadow: @shadow; +} + +.rounded(@radius: 2px) { + -webkit-border-radius: @radius; + -moz-border-radius: @radius; + border-radius: @radius; +} + +.border-radius(@topright: 0, @bottomright: 0, @bottomleft: 0, @topleft: 0) { + -webkit-border-top-right-radius: @topright; + -webkit-border-bottom-right-radius: @bottomright; + -webkit-border-bottom-left-radius: @bottomleft; + -webkit-border-top-left-radius: @topleft; + -moz-border-radius-topright: @topright; + -moz-border-radius-bottomright: @bottomright; + -moz-border-radius-bottomleft: @bottomleft; + -moz-border-radius-topleft: @topleft; + border-top-right-radius: @topright; + border-bottom-right-radius: @bottomright; + border-bottom-left-radius: @bottomleft; + border-top-left-radius: @topleft; + .background-clip(padding-box); +} + +.opacity(@opacity: 0.5) { + -moz-opacity: @opacity; + -khtml-opacity: @opacity; + -webkit-opacity: @opacity; + opacity: @opacity; + @opperc: @opacity * 100; + -ms-filter: ~"progid:DXImageTransform.Microsoft.Alpha(opacity=@{opperc})"; + filter: ~"alpha(opacity=@{opperc})"; +} + +.transition-duration(@duration: 0.2s) { + -moz-transition-duration: @duration; + -webkit-transition-duration: @duration; + -o-transition-duration: @duration; + transition-duration: @duration; +} + +.transform(...) { + -webkit-transform: @arguments; + -moz-transform: @arguments; + -o-transform: @arguments; + -ms-transform: @arguments; + transform: @arguments; +} + +.rotation(@deg:5deg) { + .transform(rotate(@deg)); +} + +.scale(@ratio:1.5) { + .transform(scale(@ratio)); +} + +.transition(@duration:0.2s, @on: all, @ease:ease) { + -webkit-transition: @on @duration @ease; + -moz-transition: @on @duration @ease; + -o-transition: @on @duration @ease; + transition: @on @duration @ease; +} + +.inner-shadow(@horizontal:0, @vertical:1px, @blur:2px, @alpha: 0.4) { + -webkit-box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha); + -moz-box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha); + box-shadow: inset @horizontal @vertical @blur rgba(0, 0, 0, @alpha); +} + +.box-sizing(@sizing: border-box) { + -ms-box-sizing: @sizing; + -moz-box-sizing: @sizing; + -webkit-box-sizing: @sizing; + box-sizing: @sizing; +} + +.user-select(@argument: none) { + -webkit-user-select: @argument; + -moz-user-select: @argument; + -ms-user-select: @argument; + user-select: @argument; +} + +.columns(@colwidth: 250px, @colcount: 0, @colgap: 50px, @columnRuleColor: #EEE, @columnRuleStyle: solid, @columnRuleWidth: 1px) { + -moz-column-width: @colwidth; + -moz-column-count: @colcount; + -moz-column-gap: @colgap; + -moz-column-rule-color: @columnRuleColor; + -moz-column-rule-style: @columnRuleStyle; + -moz-column-rule-width: @columnRuleWidth; + -webkit-column-width: @colwidth; + -webkit-column-count: @colcount; + -webkit-column-gap: @colgap; + -webkit-column-rule-color: @columnRuleColor; + -webkit-column-rule-style: @columnRuleStyle; + -webkit-column-rule-width: @columnRuleWidth; + column-width: @colwidth; + column-count: @colcount; + column-gap: @colgap; + column-rule-color: @columnRuleColor; + column-rule-style: @columnRuleStyle; + column-rule-width: @columnRuleWidth; +} + +.translate(@x:0, @y:0) { + .transform(translate(@x, @y)); +} + +.background-clip(@argument: padding-box) { + -moz-background-clip: @argument; + -webkit-background-clip: @argument; + background-clip: @argument; +} + +.clearfix() { + zoom: 1; + &:before { + content: ''; + display: block; + } + &:after { + content: ''; + display: table; + clear: both; + } +} + +.button-style(@base_color, @bg_var: 3.5, @border_darken: 18, @border_var: 4) { + @gradient_start: lumdarken(@base_color, @bg_var); + @gradient_end: lumlighten(@base_color, @bg_var); + + @border_color: lumdarken(@base_color, @border_darken); + + @border_top: lumlighten(@border_color, @border_var ); + @border_bottom: lumdarken(@border_color, @border_var ); + + .gradient(@base_color, @gradient_start, @gradient_end); + .bordered(@border_top, @border_color, @border_bottom, @border_color); +} \ No newline at end of file diff --git a/widgets/lib/color.php b/widgets/lib/color.php new file mode 100644 index 000000000..45c179b50 --- /dev/null +++ b/widgets/lib/color.php @@ -0,0 +1,474 @@ +> 0x10); + $rgb[1] = 0xFF & ($color_val >> 0x8); + $rgb[2] = 0xFF & $color_val; + } + elseif (strlen($hex) == 3) { //if shorthand notation, need some string manipulations + $rgb[0] = hexdec(str_repeat(substr($hex, 0, 1), 2)); + $rgb[1] = hexdec(str_repeat(substr($hex, 1, 1), 2)); + $rgb[2] = hexdec(str_repeat(substr($hex, 2, 1), 2)); + } + else { + throw new Exception('Invalid hex color'); + } + + foreach($rgb as $i => $p) $rgb[$i] = self::maxmin(round($p),0,255); + return $rgb; + } + + /** + * Convert RGB to HEX + */ + public static function rgb2hex($rgb){ + $hex = '#'; + foreach($rgb as $p){ + $p = base_convert($p,10,16); + $p = str_pad($p,2,'0',STR_PAD_LEFT); + $hex .= $p; + } + return strtoupper($hex); + } + + /** + * Convert a HSV color to an RGB color. + * + * @param array $hsv HSV array with values 0-1 + */ + public static function hsv2rgb ($hsv) + { + // The return RGB value + $rgb = array(); + + if($hsv[1] == 0){ + $rgb = array_fill(0,3,$hsv[2] * 255); + } + else{ + // Break hue into 6 possible segments + $hue = $hsv[0] * 6; + $hue_range = floor( $hue ); + + $v = array( + $hsv[2] * ( 1 - $hsv[1] ), + $hsv[2] * ( 1 - $hsv[1] * ( $hue - $hue_range ) ), + $hsv[2] * (1 - $hsv[1] * (1 - ($hue-$hue_range))) + ); + + switch($hue_range){ + case 0: + $rgb[0] = $hsv[2]; $rgb[1] = $v[2]; $rgb[2] = $v[0]; + break; + case 1: + $rgb[0] = $v[1]; $rgb[1] = $hsv[2]; $rgb[2] = $v[0]; + break; + case 2: + $rgb[0] = $v[0]; $rgb[1] = $hsv[2]; $rgb[2] = $v[2]; + break; + case 3: + $rgb[0] = $v[0]; $rgb[1] = $v[1]; $rgb[2] = $hsv[2]; + break; + case 4: + $rgb[0] = $v[2]; $rgb[1] = $v[0]; $rgb[2] = $hsv[2]; + break; + default : + $rgb[0] = $hsv[2]; $rgb[1] = $v[0]; $rgb[2] = $v[1]; + break; + } + + $rgb[0] = round($rgb[0] * 255); + $rgb[1] = round($rgb[1] * 255); + $rgb[2] = round($rgb[2] * 255); + } + + // Make sure the parts are in the proper range + foreach($rgb as $i => $p) $rgb[$i] = self::maxmin(round($p),0,255); + return $rgb; + } + + /** + * Converts an RGB color to an XYZ color. + * + * @param array $color The input color. Values from 0-255. + */ + public static function rgb2xyz(array $rgb) + { + foreach($rgb as $i => $c) $rgb[$i] /= 255; + + foreach($rgb as $i => $c){ + if ($c > 0.04045){ $rgb[$i] = pow(($c + 0.055) / 1.055, 2.4); } + else { $rgb[$i] = $c / 12.92; } + + $rgb[$i] = $rgb[$i] * 100; + } + + //Observer. = 2¡, Illuminant = D65 + $xyz = array(0,0,0); + $xyz[0] = $rgb[0] * 0.4124 + $rgb[1] * 0.3576 + $rgb[2] * 0.1805; + $xyz[1] = $rgb[0] * 0.2126 + $rgb[1] * 0.7152 + $rgb[2] * 0.0722; + $xyz[2] = $rgb[0] * 0.0193 + $rgb[1] * 0.1192 + $rgb[2] * 0.9505; + + return $xyz; + } + + /** + * Convert a RGB color to a HSV color + * + * @param array $rgb RGB array with values 0-255 + */ + public static function rgb2hsv ($rgb) + { + $rgb = self::rgb($rgb); + + $rgb[0] = ($rgb[0] / 255); + $rgb[1] = ($rgb[1] / 255); + $rgb[2] = ($rgb[2] / 255); + + $min = min($rgb[0], $rgb[1], $rgb[2]); + $max = max($rgb[0], $rgb[1], $rgb[2]); + $del_max = $max - $min; + + $hsv = array(0,0,$max); + + if ($del_max != 0){ + $hsv[1] = $del_max / $max; + + $del_r = ( ( ( $del_max - $rgb[0] ) / 6 ) + ( $del_max / 2 ) ) / $del_max; + $del_g = ( ( ( $del_max - $rgb[1] ) / 6 ) + ( $del_max / 2 ) ) / $del_max; + $del_b = ( ( ( $del_max - $rgb[2] ) / 6 ) + ( $del_max / 2 ) ) / $del_max; + + if ($rgb[0] == $max) $hsv[0] = $del_b - $del_g; + else if ($rgb[1] == $max) $hsv[0] = ( 1 / 3 ) + $del_r - $del_b; + else if ($rgb[2] == $max) $hsv[0] = ( 2 / 3 ) + $del_g - $del_r; + + if ($hsv[0] < 0) $hsv[0]++; + if ($hsv[0] > 1) $hsv[0]--; + } + + return $hsv; + } + + /** + * Converts a LAB color into RGB + */ + public static function lab2xyz(array $lab) + { + foreach($lab as $i => $c) $lab[$i] *= 100; + + // Observer= 2¡, Illuminant= D65 + $REF_X = 95.047; + $REF_Y = 100.000; + $REF_Z = 108.883; + + $xyz = array(); + + $xyz[1] = ($lab[0] + 16) / 116; + $xyz[0] = $lab[1] / 500 + $xyz[1]; + $xyz[2] = $xyz[1] - $lab[2] / 200; + + foreach($xyz as $i => $c){ + if ( pow( $c , 3 ) > 0.008856 ) { $xyz[$i] = pow( $c , 3 ); } + else { $xyz[$i] = ( $c - 16 / 116 ) / 7.787; } + } + + $xyz[0] *= $REF_X; + $xyz[1] *= $REF_Y; + $xyz[2] *= $REF_Z; + + return $xyz; + } + + + /** + * Convert XYZ color to a LAB color + */ + public static function xyz2lab(array $xyz) + { + // Observer= 2¡, Illuminant= D65 + $REF_X = 95.047; + $REF_Y = 100.000; + $REF_Z = 108.883; + + $xyz[0] = $xyz[0] / $REF_X; + $xyz[1] = $xyz[1] / $REF_Y; + $xyz[2] = $xyz[2] / $REF_Z; + + foreach($xyz as $i => $c){ + if ($c > 0.008856 ) { $xyz[$i] = pow( $c , 1/3 ); } + else { $xyz[$i] = ( 7.787 * $c ) + ( 16/116 ); } + } + + $lab = array(); + $lab[0] = ( 116 * $xyz[1] ) - 16; + $lab[1] = 500 * ( $xyz[0] - $xyz[1] ); + $lab[2] = 200 * ( $xyz[1] - $xyz[2] ); + + foreach($lab as $i => $c) $lab[$i] /= 100; + + return $lab; + } + + /** + * Convert an XYZ color to an RGB color + */ + public static function xyz2rgb($xyz) + { + // (Observer = 2¡, Illuminant = D65) + $xyz[0] /= 100; //X from 0 to 95.047 + $xyz[1] /= 100; //Y from 0 to 100.000 + $xyz[2] /= 100; //Z from 0 to 108.883 + + $rgb = array(); + + $rgb[0] = $xyz[0] * 3.2406 + $xyz[1] * -1.5372 + $xyz[2] * -0.4986; + $rgb[1] = $xyz[0] * -0.9689 + $xyz[1] * 1.8758 + $xyz[2] * 0.0415; + $rgb[2] = $xyz[0] * 0.0557 + $xyz[1] * -0.2040 + $xyz[2] * 1.0570; + + foreach($rgb as $i => $c){ + if ( $c > 0.0031308 ) { $rgb[$i] = 1.055 * pow( $c , ( 1 / 2.4 ) ) - 0.055; } + else { $rgb[$i] = 12.92 * $c; } + } + + $rgb[0] = round(min(max($rgb[0],0),1) * 255); + $rgb[1] = round(min(max($rgb[1],0),1) * 255); + $rgb[2] = round(min(max($rgb[2],0),1) * 255); + + return $rgb; + } + + // Combine the primary functions to create all 6 conversion functions + + /** + * Convert an RGB color to a LAB color. + */ + public static function rgb2lab($rgb) + { + $xyx = self::rgb2xyz(self::rgb($rgb)); + return self::xyz2lab($xyx); + } + + /** + * Convert a LAB color to a + */ + public static function lab2rgb($lab) + { + $xyx = self::lab2xyz($lab); + return self::xyz2rgb($xyx); + } + + /** + * Convert a LAB color to HSV + */ + public static function lab2hsv($lab) + { + $rgb = self::lab2rgb($lab); + return self::rgb2hsv($rgb); + } + + /** + * Convert an HSV color to LAB + */ + public static function hsv2lab($hsv) + { + $rgb = self::hsv2rgb($hsv); + return self::rgb2lab($rgb); + } + + /** + * Makes sure that the given value falls inside a range. + */ + public static function maxmin($i, $min, $max){ + return min(max($i,$min),$max); + } + + public static function float2hex($float){ + $hsv = array( + 0, + 0, + $float + ); + + return self::rgb2hex(self::hsv2rgb($hsv)); + } +} + +/** + * A color conversions class. Of course, you really spell it colour. Color conversion based on algorithms form EasyRGB . + * + * @author Greg Priday + * @copyright Copyright (c) 2011, Greg Priday + * @license GPL + */ +class SiteOrigin_Color_Object extends SiteOrigin_Color{ + private $changed; + + /** + * The hex value of this color before it was varied. + */ + private $color; + private $type; + + const COLOR_HSV = 'hsv'; + const COLOR_RGB = 'rgb'; + const COLOR_LAB = 'lab'; + + const COLOR_GREY = 'grey'; + const COLOR_HEX = 'hex'; + + const COLOR_RGB_R = 'red'; + const COLOR_RGB_G = 'green'; + const COLOR_RGB_B = 'blue'; + + const COLOR_LAB_L = 'lum'; + const COLOR_LAB_A = 'a'; + const COLOR_LAB_B = 'b'; + + const COLOR_HSV_H = 'hue'; + const COLOR_HSV_S = 'sat'; + const COLOR_HSV_V = 'val'; + + function __construct($color, $type = self::COLOR_HEX){ + if($type == self::COLOR_HEX){ + $this->type = self::COLOR_RGB; + $this->color = self::hex2rgb($color); + } + elseif(is_numeric($color) && $type == self::COLOR_GREY){ + // We're going to assume this is a greyscale color + $this->type = self::COLOR_HSV; + $this->color = array(1,0,min(max($color,0),1)); + } + elseif($type == self::COLOR_GREY){ + if(!is_int($color)) throw Exception('Invalid color'); + $this->type = self::COLOR_RGB; + $this->color = array($color,$color,$color); + } + else{ + $this->color = $color; + $this->type = $type; + } + + $this->changed = array(); + } + + /** + * Get a color or color part + */ + public function __get($name) + { + $colors = array( + self::COLOR_HSV => array(self::COLOR_HSV_H, self::COLOR_HSV_S, self::COLOR_HSV_V), + self::COLOR_RGB => array(self::COLOR_RGB_R, self::COLOR_RGB_G, self::COLOR_RGB_B), + self::COLOR_LAB => array(self::COLOR_LAB_L, self::COLOR_LAB_A, self::COLOR_LAB_B) + ); + + if($name == 'hex') { + return self::rgb2hex($this->rgb); + } + elseif(in_array($name, array_keys($colors))){ + // We need a color array + if($name == $this->type) return $this->color; + else{ + $func = $this->type.'2'.$name; + return call_user_func(array($this,$func), $this->color); + } + } + else{ + // We need an individual color element + foreach($colors as $type => $parts){ + if(in_array($name, $parts)){ + $color = $this->{$type}; + $i = array_search($name, $parts); + return $color[$i]; + } + } + } + + } + + /** + * Set a color or color part. + */ + public function __set($name, $value) + { + $this->changed[] = $name; + + $colors = array( + self::COLOR_HSV => array(self::COLOR_HSV_H, self::COLOR_HSV_S, self::COLOR_HSV_V), + self::COLOR_RGB => array(self::COLOR_RGB_R, self::COLOR_RGB_G, self::COLOR_RGB_B), + self::COLOR_LAB => array(self::COLOR_LAB_L, self::COLOR_LAB_A, self::COLOR_LAB_B) + ); + + if($name == 'hex'){ + $this->type = 'rgb'; + $this->color = self::hex2rgb($value); + } + elseif(in_array($name, array_keys($colors))){ + $this->type = $name; + $this->color = $value; + } + else{ + foreach($colors as $type => $parts){ + if(in_array($name, $parts)){ + $color = $this->{$type}; + $i = array_search($name, $parts); + $color[$i] = $value; + + $this->type = $type; + $this->color = $color; + } + } + } + } + + /** + * @return array + */ + public function get_changed(){ + return $this->changed; + } + + public function __toString() { + return $this->hex; + } + + /** + * Calculates the percieved difference between 2 colors. + */ + public static function distance(SiteOrigin_Color_Object $c1, SiteOrigin_Color_Object $c2){ + return sqrt( + pow($c1->lab[0]-$c2->lab[0],2) + + pow($c1->lab[1]-$c2->lab[1],2) + + pow($c1->lab[2]-$c2->lab[2],2) + ); + } +} \ No newline at end of file diff --git a/widgets/lib/lessc.inc.php b/widgets/lib/lessc.inc.php new file mode 100644 index 000000000..411ca2fab --- /dev/null +++ b/widgets/lib/lessc.inc.php @@ -0,0 +1,3473 @@ + + * Licensed under MIT or GPLv3, see LICENSE + */ + + +/** + * The less compiler and parser. + * + * Converting LESS to CSS is a three stage process. The incoming file is parsed + * by `lessc_parser` into a syntax tree, then it is compiled into another tree + * representing the CSS structure by `lessc`. The CSS tree is fed into a + * formatter, like `lessc_formatter` which then outputs CSS as a string. + * + * During the first compile, all values are *reduced*, which means that their + * types are brought to the lowest form before being dump as strings. This + * handles math equations, variable dereferences, and the like. + * + * The `parse` function of `lessc` is the entry point. + * + * In summary: + * + * The `lessc` class creates an intstance of the parser, feeds it LESS code, + * then transforms the resulting tree to a CSS tree. This class also holds the + * evaluation context, such as all available mixins and variables at any given + * time. + * + * The `lessc_parser` class is only concerned with parsing its input. + * + * The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string, + * handling things like indentation. + */ +class lessc { + static public $VERSION = "v0.3.9"; + static protected $TRUE = array("keyword", "true"); + static protected $FALSE = array("keyword", "false"); + + protected $libFunctions = array(); + protected $registeredVars = array(); + protected $preserveComments = false; + + public $vPrefix = '@'; // prefix of abstract properties + public $mPrefix = '$'; // prefix of abstract blocks + public $parentSelector = '&'; + + public $importDisabled = false; + public $importDir = ''; + + protected $numberPrecision = null; + + // set to the parser that generated the current line when compiling + // so we know how to create error messages + protected $sourceParser = null; + protected $sourceLoc = null; + + static public $defaultValue = array("keyword", ""); + + static protected $nextImportId = 0; // uniquely identify imports + + // attempts to find the path of an import url, returns null for css files + protected function findImport($url) { + foreach ((array)$this->importDir as $dir) { + $full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url; + if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) { + return $file; + } + } + + return null; + } + + protected function fileExists($name) { + return is_file($name); + } + + static public function compressList($items, $delim) { + if (!isset($items[1]) && isset($items[0])) return $items[0]; + else return array('list', $delim, $items); + } + + static public function preg_quote($what) { + return preg_quote($what, '/'); + } + + protected function tryImport($importPath, $parentBlock, $out) { + if ($importPath[0] == "function" && $importPath[1] == "url") { + $importPath = $this->flattenList($importPath[2]); + } + + $str = $this->coerceString($importPath); + if ($str === null) return false; + + $url = $this->compileValue($this->lib_e($str)); + + // don't import if it ends in css + if (substr_compare($url, '.css', -4, 4) === 0) return false; + + $realPath = $this->findImport($url); + if ($realPath === null) return false; + + if ($this->importDisabled) { + return array(false, "/* import disabled */"); + } + + $this->addParsedFile($realPath); + $parser = $this->makeParser($realPath); + $root = $parser->parse(file_get_contents($realPath)); + + // set the parents of all the block props + foreach ($root->props as $prop) { + if ($prop[0] == "block") { + $prop[1]->parent = $parentBlock; + } + } + + // copy mixins into scope, set their parents + // bring blocks from import into current block + // TODO: need to mark the source parser these came from this file + foreach ($root->children as $childName => $child) { + if (isset($parentBlock->children[$childName])) { + $parentBlock->children[$childName] = array_merge( + $parentBlock->children[$childName], + $child); + } else { + $parentBlock->children[$childName] = $child; + } + } + + $pi = pathinfo($realPath); + $dir = $pi["dirname"]; + + list($top, $bottom) = $this->sortProps($root->props, true); + $this->compileImportedProps($top, $parentBlock, $out, $parser, $dir); + + return array(true, $bottom, $parser, $dir); + } + + protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) { + $oldSourceParser = $this->sourceParser; + + $oldImport = $this->importDir; + + // TODO: this is because the importDir api is stupid + $this->importDir = (array)$this->importDir; + array_unshift($this->importDir, $importDir); + + foreach ($props as $prop) { + $this->compileProp($prop, $block, $out); + } + + $this->importDir = $oldImport; + $this->sourceParser = $oldSourceParser; + } + + /** + * Recursively compiles a block. + * + * A block is analogous to a CSS block in most cases. A single LESS document + * is encapsulated in a block when parsed, but it does not have parent tags + * so all of it's children appear on the root level when compiled. + * + * Blocks are made up of props and children. + * + * Props are property instructions, array tuples which describe an action + * to be taken, eg. write a property, set a variable, mixin a block. + * + * The children of a block are just all the blocks that are defined within. + * This is used to look up mixins when performing a mixin. + * + * Compiling the block involves pushing a fresh environment on the stack, + * and iterating through the props, compiling each one. + * + * See lessc::compileProp() + * + */ + protected function compileBlock($block) { + switch ($block->type) { + case "root": + $this->compileRoot($block); + break; + case null: + $this->compileCSSBlock($block); + break; + case "media": + $this->compileMedia($block); + break; + case "directive": + $name = "@" . $block->name; + if (!empty($block->value)) { + $name .= " " . $this->compileValue($this->reduce($block->value)); + } + + $this->compileNestedBlock($block, array($name)); + break; + default: + $this->throwError("unknown block type: $block->type\n"); + } + } + + protected function compileCSSBlock($block) { + $env = $this->pushEnv(); + + $selectors = $this->compileSelectors($block->tags); + $env->selectors = $this->multiplySelectors($selectors); + $out = $this->makeOutputBlock(null, $env->selectors); + + $this->scope->children[] = $out; + $this->compileProps($block, $out); + + $block->scope = $env; // mixins carry scope with them! + $this->popEnv(); + } + + protected function compileMedia($media) { + $env = $this->pushEnv($media); + $parentScope = $this->mediaParent($this->scope); + + $query = $this->compileMediaQuery($this->multiplyMedia($env)); + + $this->scope = $this->makeOutputBlock($media->type, array($query)); + $parentScope->children[] = $this->scope; + + $this->compileProps($media, $this->scope); + + if (count($this->scope->lines) > 0) { + $orphanSelelectors = $this->findClosestSelectors(); + if (!is_null($orphanSelelectors)) { + $orphan = $this->makeOutputBlock(null, $orphanSelelectors); + $orphan->lines = $this->scope->lines; + array_unshift($this->scope->children, $orphan); + $this->scope->lines = array(); + } + } + + $this->scope = $this->scope->parent; + $this->popEnv(); + } + + protected function mediaParent($scope) { + while (!empty($scope->parent)) { + if (!empty($scope->type) && $scope->type != "media") { + break; + } + $scope = $scope->parent; + } + + return $scope; + } + + protected function compileNestedBlock($block, $selectors) { + $this->pushEnv($block); + $this->scope = $this->makeOutputBlock($block->type, $selectors); + $this->scope->parent->children[] = $this->scope; + + $this->compileProps($block, $this->scope); + + $this->scope = $this->scope->parent; + $this->popEnv(); + } + + protected function compileRoot($root) { + $this->pushEnv(); + $this->scope = $this->makeOutputBlock($root->type); + $this->compileProps($root, $this->scope); + $this->popEnv(); + } + + protected function compileProps($block, $out) { + foreach ($this->sortProps($block->props) as $prop) { + $this->compileProp($prop, $block, $out); + } + } + + protected function sortProps($props, $split = false) { + $vars = array(); + $imports = array(); + $other = array(); + + foreach ($props as $prop) { + switch ($prop[0]) { + case "assign": + if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) { + $vars[] = $prop; + } else { + $other[] = $prop; + } + break; + case "import": + $id = self::$nextImportId++; + $prop[] = $id; + $imports[] = $prop; + $other[] = array("import_mixin", $id); + break; + default: + $other[] = $prop; + } + } + + if ($split) { + return array(array_merge($vars, $imports), $other); + } else { + return array_merge($vars, $imports, $other); + } + } + + protected function compileMediaQuery($queries) { + $compiledQueries = array(); + foreach ($queries as $query) { + $parts = array(); + foreach ($query as $q) { + switch ($q[0]) { + case "mediaType": + $parts[] = implode(" ", array_slice($q, 1)); + break; + case "mediaExp": + if (isset($q[2])) { + $parts[] = "($q[1]: " . + $this->compileValue($this->reduce($q[2])) . ")"; + } else { + $parts[] = "($q[1])"; + } + break; + case "variable": + $parts[] = $this->compileValue($this->reduce($q)); + break; + } + } + + if (count($parts) > 0) { + $compiledQueries[] = implode(" and ", $parts); + } + } + + $out = "@media"; + if (!empty($parts)) { + $out .= " " . + implode($this->formatter->selectorSeparator, $compiledQueries); + } + return $out; + } + + protected function multiplyMedia($env, $childQueries = null) { + if (is_null($env) || + !empty($env->block->type) && $env->block->type != "media") + { + return $childQueries; + } + + // plain old block, skip + if (empty($env->block->type)) { + return $this->multiplyMedia($env->parent, $childQueries); + } + + $out = array(); + $queries = $env->block->queries; + if (is_null($childQueries)) { + $out = $queries; + } else { + foreach ($queries as $parent) { + foreach ($childQueries as $child) { + $out[] = array_merge($parent, $child); + } + } + } + + return $this->multiplyMedia($env->parent, $out); + } + + protected function expandParentSelectors(&$tag, $replace) { + $parts = explode("$&$", $tag); + $count = 0; + foreach ($parts as &$part) { + $part = str_replace($this->parentSelector, $replace, $part, $c); + $count += $c; + } + $tag = implode($this->parentSelector, $parts); + return $count; + } + + protected function findClosestSelectors() { + $env = $this->env; + $selectors = null; + while ($env !== null) { + if (isset($env->selectors)) { + $selectors = $env->selectors; + break; + } + $env = $env->parent; + } + + return $selectors; + } + + + // multiply $selectors against the nearest selectors in env + protected function multiplySelectors($selectors) { + // find parent selectors + + $parentSelectors = $this->findClosestSelectors(); + if (is_null($parentSelectors)) { + // kill parent reference in top level selector + foreach ($selectors as &$s) { + $this->expandParentSelectors($s, ""); + } + + return $selectors; + } + + $out = array(); + foreach ($parentSelectors as $parent) { + foreach ($selectors as $child) { + $count = $this->expandParentSelectors($child, $parent); + + // don't prepend the parent tag if & was used + if ($count > 0) { + $out[] = trim($child); + } else { + $out[] = trim($parent . ' ' . $child); + } + } + } + + return $out; + } + + // reduces selector expressions + protected function compileSelectors($selectors) { + $out = array(); + + foreach ($selectors as $s) { + if (is_array($s)) { + list(, $value) = $s; + $out[] = trim($this->compileValue($this->reduce($value))); + } else { + $out[] = $s; + } + } + + return $out; + } + + protected function eq($left, $right) { + return $left == $right; + } + + protected function patternMatch($block, $callingArgs) { + // match the guards if it has them + // any one of the groups must have all its guards pass for a match + if (!empty($block->guards)) { + $groupPassed = false; + foreach ($block->guards as $guardGroup) { + foreach ($guardGroup as $guard) { + $this->pushEnv(); + $this->zipSetArgs($block->args, $callingArgs); + + $negate = false; + if ($guard[0] == "negate") { + $guard = $guard[1]; + $negate = true; + } + + $passed = $this->reduce($guard) == self::$TRUE; + if ($negate) $passed = !$passed; + + $this->popEnv(); + + if ($passed) { + $groupPassed = true; + } else { + $groupPassed = false; + break; + } + } + + if ($groupPassed) break; + } + + if (!$groupPassed) { + return false; + } + } + + $numCalling = count($callingArgs); + + if (empty($block->args)) { + return $block->isVararg || $numCalling == 0; + } + + $i = -1; // no args + // try to match by arity or by argument literal + foreach ($block->args as $i => $arg) { + switch ($arg[0]) { + case "lit": + if (empty($callingArgs[$i]) || !$this->eq($arg[1], $callingArgs[$i])) { + return false; + } + break; + case "arg": + // no arg and no default value + if (!isset($callingArgs[$i]) && !isset($arg[2])) { + return false; + } + break; + case "rest": + $i--; // rest can be empty + break 2; + } + } + + if ($block->isVararg) { + return true; // not having enough is handled above + } else { + $numMatched = $i + 1; + // greater than becuase default values always match + return $numMatched >= $numCalling; + } + } + + protected function patternMatchAll($blocks, $callingArgs) { + $matches = null; + foreach ($blocks as $block) { + if ($this->patternMatch($block, $callingArgs)) { + $matches[] = $block; + } + } + + return $matches; + } + + // attempt to find blocks matched by path and args + protected function findBlocks($searchIn, $path, $args, $seen=array()) { + if ($searchIn == null) return null; + if (isset($seen[$searchIn->id])) return null; + $seen[$searchIn->id] = true; + + $name = $path[0]; + + if (isset($searchIn->children[$name])) { + $blocks = $searchIn->children[$name]; + if (count($path) == 1) { + $matches = $this->patternMatchAll($blocks, $args); + if (!empty($matches)) { + // This will return all blocks that match in the closest + // scope that has any matching block, like lessjs + return $matches; + } + } else { + $matches = array(); + foreach ($blocks as $subBlock) { + $subMatches = $this->findBlocks($subBlock, + array_slice($path, 1), $args, $seen); + + if (!is_null($subMatches)) { + foreach ($subMatches as $sm) { + $matches[] = $sm; + } + } + } + + return count($matches) > 0 ? $matches : null; + } + } + + if ($searchIn->parent === $searchIn) return null; + return $this->findBlocks($searchIn->parent, $path, $args, $seen); + } + + // sets all argument names in $args to either the default value + // or the one passed in through $values + protected function zipSetArgs($args, $values) { + $i = 0; + $assignedValues = array(); + foreach ($args as $a) { + if ($a[0] == "arg") { + if ($i < count($values) && !is_null($values[$i])) { + $value = $values[$i]; + } elseif (isset($a[2])) { + $value = $a[2]; + } else $value = null; + + $value = $this->reduce($value); + $this->set($a[1], $value); + $assignedValues[] = $value; + } + $i++; + } + + // check for a rest + $last = end($args); + if ($last[0] == "rest") { + $rest = array_slice($values, count($args) - 1); + $this->set($last[1], $this->reduce(array("list", " ", $rest))); + } + + $this->env->arguments = $assignedValues; + } + + // compile a prop and update $lines or $blocks appropriately + protected function compileProp($prop, $block, $out) { + // set error position context + $this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1; + + switch ($prop[0]) { + case 'assign': + list(, $name, $value) = $prop; + if ($name[0] == $this->vPrefix) { + $this->set($name, $value); + } else { + $out->lines[] = $this->formatter->property($name, + $this->compileValue($this->reduce($value))); + } + break; + case 'block': + list(, $child) = $prop; + $this->compileBlock($child); + break; + case 'mixin': + list(, $path, $args, $suffix) = $prop; + + $args = array_map(array($this, "reduce"), (array)$args); + $mixins = $this->findBlocks($block, $path, $args); + + if ($mixins === null) { + // fwrite(STDERR,"failed to find block: ".implode(" > ", $path)."\n"); + break; // throw error here?? + } + + foreach ($mixins as $mixin) { + $haveScope = false; + if (isset($mixin->parent->scope)) { + $haveScope = true; + $mixinParentEnv = $this->pushEnv(); + $mixinParentEnv->storeParent = $mixin->parent->scope; + } + + $haveArgs = false; + if (isset($mixin->args)) { + $haveArgs = true; + $this->pushEnv(); + $this->zipSetArgs($mixin->args, $args); + } + + $oldParent = $mixin->parent; + if ($mixin != $block) $mixin->parent = $block; + + foreach ($this->sortProps($mixin->props) as $subProp) { + if ($suffix !== null && + $subProp[0] == "assign" && + is_string($subProp[1]) && + $subProp[1]{0} != $this->vPrefix) + { + $subProp[2] = array( + 'list', ' ', + array($subProp[2], array('keyword', $suffix)) + ); + } + + $this->compileProp($subProp, $mixin, $out); + } + + $mixin->parent = $oldParent; + + if ($haveArgs) $this->popEnv(); + if ($haveScope) $this->popEnv(); + } + + break; + case 'raw': + $out->lines[] = $prop[1]; + break; + case "directive": + list(, $name, $value) = $prop; + $out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';'; + break; + case "comment": + $out->lines[] = $prop[1]; + break; + case "import"; + list(, $importPath, $importId) = $prop; + $importPath = $this->reduce($importPath); + + if (!isset($this->env->imports)) { + $this->env->imports = array(); + } + + $result = $this->tryImport($importPath, $block, $out); + + $this->env->imports[$importId] = $result === false ? + array(false, "@import " . $this->compileValue($importPath).";") : + $result; + + break; + case "import_mixin": + list(,$importId) = $prop; + $import = $this->env->imports[$importId]; + if ($import[0] === false) { + $out->lines[] = $import[1]; + } else { + list(, $bottom, $parser, $importDir) = $import; + $this->compileImportedProps($bottom, $block, $out, $parser, $importDir); + } + + break; + default: + $this->throwError("unknown op: {$prop[0]}\n"); + } + } + + + /** + * Compiles a primitive value into a CSS property value. + * + * Values in lessphp are typed by being wrapped in arrays, their format is + * typically: + * + * array(type, contents [, additional_contents]*) + * + * The input is expected to be reduced. This function will not work on + * things like expressions and variables. + */ + protected function compileValue($value) { + switch ($value[0]) { + case 'list': + // [1] - delimiter + // [2] - array of values + return implode($value[1], array_map(array($this, 'compileValue'), $value[2])); + case 'raw_color': + if (!empty($this->formatter->compressColors)) { + return $this->compileValue($this->coerceColor($value)); + } + return $value[1]; + case 'keyword': + // [1] - the keyword + return $value[1]; + case 'number': + list(, $num, $unit) = $value; + // [1] - the number + // [2] - the unit + if ($this->numberPrecision !== null) { + $num = round($num, $this->numberPrecision); + } + return $num . $unit; + case 'string': + // [1] - contents of string (includes quotes) + list(, $delim, $content) = $value; + foreach ($content as &$part) { + if (is_array($part)) { + $part = $this->compileValue($part); + } + } + return $delim . implode($content) . $delim; + case 'color': + // [1] - red component (either number or a %) + // [2] - green component + // [3] - blue component + // [4] - optional alpha component + list(, $r, $g, $b) = $value; + $r = round($r); + $g = round($g); + $b = round($b); + + if (count($value) == 5 && $value[4] != 1) { // rgba + return 'rgba('.$r.','.$g.','.$b.','.$value[4].')'; + } + + $h = sprintf("#%02x%02x%02x", $r, $g, $b); + + if (!empty($this->formatter->compressColors)) { + // Converting hex color to short notation (e.g. #003399 to #039) + if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) { + $h = '#' . $h[1] . $h[3] . $h[5]; + } + } + + return $h; + + case 'function': + list(, $name, $args) = $value; + return $name.'('.$this->compileValue($args).')'; + default: // assumed to be unit + $this->throwError("unknown value type: $value[0]"); + } + } + + protected function lib_isnumber($value) { + return $this->toBool($value[0] == "number"); + } + + protected function lib_isstring($value) { + return $this->toBool($value[0] == "string"); + } + + protected function lib_iscolor($value) { + return $this->toBool($this->coerceColor($value)); + } + + protected function lib_iskeyword($value) { + return $this->toBool($value[0] == "keyword"); + } + + protected function lib_ispixel($value) { + return $this->toBool($value[0] == "number" && $value[2] == "px"); + } + + protected function lib_ispercentage($value) { + return $this->toBool($value[0] == "number" && $value[2] == "%"); + } + + protected function lib_isem($value) { + return $this->toBool($value[0] == "number" && $value[2] == "em"); + } + + protected function lib_isrem($value) { + return $this->toBool($value[0] == "number" && $value[2] == "rem"); + } + + protected function lib_rgbahex($color) { + $color = $this->coerceColor($color); + if (is_null($color)) + $this->throwError("color expected for rgbahex"); + + return sprintf("#%02x%02x%02x%02x", + isset($color[4]) ? $color[4]*255 : 255, + $color[1],$color[2], $color[3]); + } + + protected function lib_argb($color){ + return $this->lib_rgbahex($color); + } + + // utility func to unquote a string + protected function lib_e($arg) { + switch ($arg[0]) { + case "list": + $items = $arg[2]; + if (isset($items[0])) { + return $this->lib_e($items[0]); + } + return self::$defaultValue; + case "string": + $arg[1] = ""; + return $arg; + case "keyword": + return $arg; + default: + return array("keyword", $this->compileValue($arg)); + } + } + + protected function lib__sprintf($args) { + if ($args[0] != "list") return $args; + $values = $args[2]; + $string = array_shift($values); + $template = $this->compileValue($this->lib_e($string)); + + $i = 0; + if (preg_match_all('/%[dsa]/', $template, $m)) { + foreach ($m[0] as $match) { + $val = isset($values[$i]) ? + $this->reduce($values[$i]) : array('keyword', ''); + + // lessjs compat, renders fully expanded color, not raw color + if ($color = $this->coerceColor($val)) { + $val = $color; + } + + $i++; + $rep = $this->compileValue($this->lib_e($val)); + $template = preg_replace('/'.self::preg_quote($match).'/', + $rep, $template, 1); + } + } + + $d = $string[0] == "string" ? $string[1] : '"'; + return array("string", $d, array($template)); + } + + protected function lib_floor($arg) { + $value = $this->assertNumber($arg); + return array("number", floor($value), $arg[2]); + } + + protected function lib_ceil($arg) { + $value = $this->assertNumber($arg); + return array("number", ceil($value), $arg[2]); + } + + protected function lib_round($arg) { + $value = $this->assertNumber($arg); + return array("number", round($value), $arg[2]); + } + + protected function lib_unit($arg) { + if ($arg[0] == "list") { + list($number, $newUnit) = $arg[2]; + return array("number", $this->assertNumber($number), + $this->compileValue($this->lib_e($newUnit))); + } else { + return array("number", $this->assertNumber($arg), ""); + } + } + + /** + * Helper function to get arguments for color manipulation functions. + * takes a list that contains a color like thing and a percentage + */ + protected function colorArgs($args) { + if ($args[0] != 'list' || count($args[2]) < 2) { + return array(array('color', 0, 0, 0), 0); + } + list($color, $delta) = $args[2]; + $color = $this->assertColor($color); + $delta = floatval($delta[1]); + + return array($color, $delta); + } + + protected function lib_darken($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[3] = $this->clamp($hsl[3] - $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_lighten($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[3] = $this->clamp($hsl[3] + $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_saturate($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[2] = $this->clamp($hsl[2] + $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_desaturate($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[2] = $this->clamp($hsl[2] - $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_spin($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + + $hsl[1] = $hsl[1] + $delta % 360; + if ($hsl[1] < 0) $hsl[1] += 360; + + return $this->toRGB($hsl); + } + + protected function lib_fadeout($args) { + list($color, $delta) = $this->colorArgs($args); + $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100); + return $color; + } + + protected function lib_fadein($args) { + list($color, $delta) = $this->colorArgs($args); + $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100); + return $color; + } + + protected function lib_hue($color) { + $hsl = $this->toHSL($this->assertColor($color)); + return round($hsl[1]); + } + + protected function lib_saturation($color) { + $hsl = $this->toHSL($this->assertColor($color)); + return round($hsl[2]); + } + + protected function lib_lightness($color) { + $hsl = $this->toHSL($this->assertColor($color)); + return round($hsl[3]); + } + + // get the alpha of a color + // defaults to 1 for non-colors or colors without an alpha + protected function lib_alpha($value) { + if (!is_null($color = $this->coerceColor($value))) { + return isset($color[4]) ? $color[4] : 1; + } + } + + // set the alpha of the color + protected function lib_fade($args) { + list($color, $alpha) = $this->colorArgs($args); + $color[4] = $this->clamp($alpha / 100.0); + return $color; + } + + protected function lib_percentage($arg) { + $num = $this->assertNumber($arg); + return array("number", $num*100, "%"); + } + + // mixes two colors by weight + // mix(@color1, @color2, @weight); + // http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method + protected function lib_mix($args) { + if ($args[0] != "list" || count($args[2]) < 3) + $this->throwError("mix expects (color1, color2, weight)"); + + list($first, $second, $weight) = $args[2]; + $first = $this->assertColor($first); + $second = $this->assertColor($second); + + $first_a = $this->lib_alpha($first); + $second_a = $this->lib_alpha($second); + $weight = $weight[1] / 100.0; + + $w = $weight * 2 - 1; + $a = $first_a - $second_a; + + $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0; + $w2 = 1.0 - $w1; + + $new = array('color', + $w1 * $first[1] + $w2 * $second[1], + $w1 * $first[2] + $w2 * $second[2], + $w1 * $first[3] + $w2 * $second[3], + ); + + if ($first_a != 1.0 || $second_a != 1.0) { + $new[] = $first_a * $weight + $second_a * ($weight - 1); + } + + return $this->fixColor($new); + } + + protected function lib_contrast($args) { + if ($args[0] != 'list' || count($args[2]) < 3) { + return array(array('color', 0, 0, 0), 0); + } + + list($inputColor, $darkColor, $lightColor) = $args[2]; + + $inputColor = $this->assertColor($inputColor); + $darkColor = $this->assertColor($darkColor); + $lightColor = $this->assertColor($lightColor); + $hsl = $this->toHSL($inputColor); + + if ($hsl[3] > 50) { + return $darkColor; + } + + return $lightColor; + } + + protected function assertColor($value, $error = "expected color value") { + $color = $this->coerceColor($value); + if (is_null($color)) $this->throwError($error); + return $color; + } + + protected function assertNumber($value, $error = "expecting number") { + if ($value[0] == "number") return $value[1]; + $this->throwError($error); + } + + protected function toHSL($color) { + if ($color[0] == 'hsl') return $color; + + $r = $color[1] / 255; + $g = $color[2] / 255; + $b = $color[3] / 255; + + $min = min($r, $g, $b); + $max = max($r, $g, $b); + + $L = ($min + $max) / 2; + if ($min == $max) { + $S = $H = 0; + } else { + if ($L < 0.5) + $S = ($max - $min)/($max + $min); + else + $S = ($max - $min)/(2.0 - $max - $min); + + if ($r == $max) $H = ($g - $b)/($max - $min); + elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min); + elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min); + + } + + $out = array('hsl', + ($H < 0 ? $H + 6 : $H)*60, + $S*100, + $L*100, + ); + + if (count($color) > 4) $out[] = $color[4]; // copy alpha + return $out; + } + + protected function toRGB_helper($comp, $temp1, $temp2) { + if ($comp < 0) $comp += 1.0; + elseif ($comp > 1) $comp -= 1.0; + + if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp; + if (2 * $comp < 1) return $temp2; + if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6; + + return $temp1; + } + + /** + * Converts a hsl array into a color value in rgb. + * Expects H to be in range of 0 to 360, S and L in 0 to 100 + */ + protected function toRGB($color) { + if ($color[0] == 'color') return $color; + + $H = $color[1] / 360; + $S = $color[2] / 100; + $L = $color[3] / 100; + + if ($S == 0) { + $r = $g = $b = $L; + } else { + $temp2 = $L < 0.5 ? + $L*(1.0 + $S) : + $L + $S - $L * $S; + + $temp1 = 2.0 * $L - $temp2; + + $r = $this->toRGB_helper($H + 1/3, $temp1, $temp2); + $g = $this->toRGB_helper($H, $temp1, $temp2); + $b = $this->toRGB_helper($H - 1/3, $temp1, $temp2); + } + + // $out = array('color', round($r*255), round($g*255), round($b*255)); + $out = array('color', $r*255, $g*255, $b*255); + if (count($color) > 4) $out[] = $color[4]; // copy alpha + return $out; + } + + protected function clamp($v, $max = 1, $min = 0) { + return min($max, max($min, $v)); + } + + /** + * Convert the rgb, rgba, hsl color literals of function type + * as returned by the parser into values of color type. + */ + protected function funcToColor($func) { + $fname = $func[1]; + if ($func[2][0] != 'list') return false; // need a list of arguments + $rawComponents = $func[2][2]; + + if ($fname == 'hsl' || $fname == 'hsla') { + $hsl = array('hsl'); + $i = 0; + foreach ($rawComponents as $c) { + $val = $this->reduce($c); + $val = isset($val[1]) ? floatval($val[1]) : 0; + + if ($i == 0) $clamp = 360; + elseif ($i < 3) $clamp = 100; + else $clamp = 1; + + $hsl[] = $this->clamp($val, $clamp); + $i++; + } + + while (count($hsl) < 4) $hsl[] = 0; + return $this->toRGB($hsl); + + } elseif ($fname == 'rgb' || $fname == 'rgba') { + $components = array(); + $i = 1; + foreach ($rawComponents as $c) { + $c = $this->reduce($c); + if ($i < 4) { + if ($c[0] == "number" && $c[2] == "%") { + $components[] = 255 * ($c[1] / 100); + } else { + $components[] = floatval($c[1]); + } + } elseif ($i == 4) { + if ($c[0] == "number" && $c[2] == "%") { + $components[] = 1.0 * ($c[1] / 100); + } else { + $components[] = floatval($c[1]); + } + } else break; + + $i++; + } + while (count($components) < 3) $components[] = 0; + array_unshift($components, 'color'); + return $this->fixColor($components); + } + + return false; + } + + protected function reduce($value, $forExpression = false) { + switch ($value[0]) { + case "interpolate": + $reduced = $this->reduce($value[1]); + $var = $this->compileValue($reduced); + $res = $this->reduce(array("variable", $this->vPrefix . $var)); + + if (empty($value[2])) $res = $this->lib_e($res); + + return $res; + case "variable": + $key = $value[1]; + if (is_array($key)) { + $key = $this->reduce($key); + $key = $this->vPrefix . $this->compileValue($this->lib_e($key)); + } + + $seen =& $this->env->seenNames; + + if (!empty($seen[$key])) { + $this->throwError("infinite loop detected: $key"); + } + + $seen[$key] = true; + $out = $this->reduce($this->get($key, self::$defaultValue)); + $seen[$key] = false; + return $out; + case "list": + foreach ($value[2] as &$item) { + $item = $this->reduce($item, $forExpression); + } + return $value; + case "expression": + return $this->evaluate($value); + case "string": + foreach ($value[2] as &$part) { + if (is_array($part)) { + $strip = $part[0] == "variable"; + $part = $this->reduce($part); + if ($strip) $part = $this->lib_e($part); + } + } + return $value; + case "escape": + list(,$inner) = $value; + return $this->lib_e($this->reduce($inner)); + case "function": + $color = $this->funcToColor($value); + if ($color) return $color; + + list(, $name, $args) = $value; + if ($name == "%") $name = "_sprintf"; + $f = isset($this->libFunctions[$name]) ? + $this->libFunctions[$name] : array($this, 'lib_'.$name); + + if (is_callable($f)) { + if ($args[0] == 'list') + $args = self::compressList($args[2], $args[1]); + + $ret = call_user_func($f, $this->reduce($args, true), $this); + + if (is_null($ret)) { + return array("string", "", array( + $name, "(", $args, ")" + )); + } + + // convert to a typed value if the result is a php primitive + if (is_numeric($ret)) $ret = array('number', $ret, ""); + elseif (!is_array($ret)) $ret = array('keyword', $ret); + + return $ret; + } + + // plain function, reduce args + $value[2] = $this->reduce($value[2]); + return $value; + case "unary": + list(, $op, $exp) = $value; + $exp = $this->reduce($exp); + + if ($exp[0] == "number") { + switch ($op) { + case "+": + return $exp; + case "-": + $exp[1] *= -1; + return $exp; + } + } + return array("string", "", array($op, $exp)); + } + + if ($forExpression) { + switch ($value[0]) { + case "keyword": + if ($color = $this->coerceColor($value)) { + return $color; + } + break; + case "raw_color": + return $this->coerceColor($value); + } + } + + return $value; + } + + + // coerce a value for use in color operation + protected function coerceColor($value) { + switch($value[0]) { + case 'color': return $value; + case 'raw_color': + $c = array("color", 0, 0, 0); + $colorStr = substr($value[1], 1); + $num = hexdec($colorStr); + $width = strlen($colorStr) == 3 ? 16 : 256; + + for ($i = 3; $i > 0; $i--) { // 3 2 1 + $t = $num % $width; + $num /= $width; + + $c[$i] = $t * (256/$width) + $t * floor(16/$width); + } + + return $c; + case 'keyword': + $name = $value[1]; + if (isset(self::$cssColors[$name])) { + $rgba = explode(',', self::$cssColors[$name]); + + if(isset($rgba[3])) + return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]); + + return array('color', $rgba[0], $rgba[1], $rgba[2]); + } + return null; + } + } + + // make something string like into a string + protected function coerceString($value) { + switch ($value[0]) { + case "string": + return $value; + case "keyword": + return array("string", "", array($value[1])); + } + return null; + } + + // turn list of length 1 into value type + protected function flattenList($value) { + if ($value[0] == "list" && count($value[2]) == 1) { + return $this->flattenList($value[2][0]); + } + return $value; + } + + protected function toBool($a) { + if ($a) return self::$TRUE; + else return self::$FALSE; + } + + // evaluate an expression + protected function evaluate($exp) { + list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp; + + $left = $this->reduce($left, true); + $right = $this->reduce($right, true); + + if ($leftColor = $this->coerceColor($left)) { + $left = $leftColor; + } + + if ($rightColor = $this->coerceColor($right)) { + $right = $rightColor; + } + + $ltype = $left[0]; + $rtype = $right[0]; + + // operators that work on all types + if ($op == "and") { + return $this->toBool($left == self::$TRUE && $right == self::$TRUE); + } + + if ($op == "=") { + return $this->toBool($this->eq($left, $right) ); + } + + if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) { + return $str; + } + + // type based operators + $fname = "op_${ltype}_${rtype}"; + if (is_callable(array($this, $fname))) { + $out = $this->$fname($op, $left, $right); + if (!is_null($out)) return $out; + } + + // make the expression look it did before being parsed + $paddedOp = $op; + if ($whiteBefore) $paddedOp = " " . $paddedOp; + if ($whiteAfter) $paddedOp .= " "; + + return array("string", "", array($left, $paddedOp, $right)); + } + + protected function stringConcatenate($left, $right) { + if ($strLeft = $this->coerceString($left)) { + if ($right[0] == "string") { + $right[1] = ""; + } + $strLeft[2][] = $right; + return $strLeft; + } + + if ($strRight = $this->coerceString($right)) { + array_unshift($strRight[2], $left); + return $strRight; + } + } + + + // make sure a color's components don't go out of bounds + protected function fixColor($c) { + foreach (range(1, 3) as $i) { + if ($c[$i] < 0) $c[$i] = 0; + if ($c[$i] > 255) $c[$i] = 255; + } + + return $c; + } + + protected function op_number_color($op, $lft, $rgt) { + if ($op == '+' || $op == '*') { + return $this->op_color_number($op, $rgt, $lft); + } + } + + protected function op_color_number($op, $lft, $rgt) { + if ($rgt[0] == '%') $rgt[1] /= 100; + + return $this->op_color_color($op, $lft, + array_fill(1, count($lft) - 1, $rgt[1])); + } + + protected function op_color_color($op, $left, $right) { + $out = array('color'); + $max = count($left) > count($right) ? count($left) : count($right); + foreach (range(1, $max - 1) as $i) { + $lval = isset($left[$i]) ? $left[$i] : 0; + $rval = isset($right[$i]) ? $right[$i] : 0; + switch ($op) { + case '+': + $out[] = $lval + $rval; + break; + case '-': + $out[] = $lval - $rval; + break; + case '*': + $out[] = $lval * $rval; + break; + case '%': + $out[] = $lval % $rval; + break; + case '/': + if ($rval == 0) $this->throwError("evaluate error: can't divide by zero"); + $out[] = $lval / $rval; + break; + default: + $this->throwError('evaluate error: color op number failed on op '.$op); + } + } + return $this->fixColor($out); + } + + function lib_red($color){ + $color = $this->coerceColor($color); + if (is_null($color)) { + $this->throwError('color expected for red()'); + } + + return $color[1]; + } + + function lib_green($color){ + $color = $this->coerceColor($color); + if (is_null($color)) { + $this->throwError('color expected for green()'); + } + + return $color[2]; + } + + function lib_blue($color){ + $color = $this->coerceColor($color); + if (is_null($color)) { + $this->throwError('color expected for blue()'); + } + + return $color[3]; + } + + + // operator on two numbers + protected function op_number_number($op, $left, $right) { + $unit = empty($left[2]) ? $right[2] : $left[2]; + + $value = 0; + switch ($op) { + case '+': + $value = $left[1] + $right[1]; + break; + case '*': + $value = $left[1] * $right[1]; + break; + case '-': + $value = $left[1] - $right[1]; + break; + case '%': + $value = $left[1] % $right[1]; + break; + case '/': + if ($right[1] == 0) $this->throwError('parse error: divide by zero'); + $value = $left[1] / $right[1]; + break; + case '<': + return $this->toBool($left[1] < $right[1]); + case '>': + return $this->toBool($left[1] > $right[1]); + case '>=': + return $this->toBool($left[1] >= $right[1]); + case '=<': + return $this->toBool($left[1] <= $right[1]); + default: + $this->throwError('parse error: unknown number operator: '.$op); + } + + return array("number", $value, $unit); + } + + + /* environment functions */ + + protected function makeOutputBlock($type, $selectors = null) { + $b = new stdclass; + $b->lines = array(); + $b->children = array(); + $b->selectors = $selectors; + $b->type = $type; + $b->parent = $this->scope; + return $b; + } + + // the state of execution + protected function pushEnv($block = null) { + $e = new stdclass; + $e->parent = $this->env; + $e->store = array(); + $e->block = $block; + + $this->env = $e; + return $e; + } + + // pop something off the stack + protected function popEnv() { + $old = $this->env; + $this->env = $this->env->parent; + return $old; + } + + // set something in the current env + protected function set($name, $value) { + $this->env->store[$name] = $value; + } + + + // get the highest occurrence entry for a name + protected function get($name, $default=null) { + $current = $this->env; + + $isArguments = $name == $this->vPrefix . 'arguments'; + while ($current) { + if ($isArguments && isset($current->arguments)) { + return array('list', ' ', $current->arguments); + } + + if (isset($current->store[$name])) + return $current->store[$name]; + else { + $current = isset($current->storeParent) ? + $current->storeParent : $current->parent; + } + } + + return $default; + } + + // inject array of unparsed strings into environment as variables + protected function injectVariables($args) { + $this->pushEnv(); + $parser = new lessc_parser($this, __METHOD__); + foreach ($args as $name => $strValue) { + if ($name{0} != '@') $name = '@'.$name; + $parser->count = 0; + $parser->buffer = (string)$strValue; + if (!$parser->propertyValue($value)) { + throw new Exception("failed to parse passed in variable $name: $strValue"); + } + + $this->set($name, $value); + } + } + + /** + * Initialize any static state, can initialize parser for a file + * $opts isn't used yet + */ + public function __construct($fname = null) { + if ($fname !== null) { + // used for deprecated parse method + $this->_parseFile = $fname; + } + } + + public function compile($string, $name = null) { + $locale = setlocale(LC_NUMERIC, 0); + setlocale(LC_NUMERIC, "C"); + + $this->parser = $this->makeParser($name); + $root = $this->parser->parse($string); + + $this->env = null; + $this->scope = null; + + $this->formatter = $this->newFormatter(); + + if (!empty($this->registeredVars)) { + $this->injectVariables($this->registeredVars); + } + + $this->sourceParser = $this->parser; // used for error messages + $this->compileBlock($root); + + ob_start(); + $this->formatter->block($this->scope); + $out = ob_get_clean(); + setlocale(LC_NUMERIC, $locale); + return $out; + } + + public function compileFile($fname, $outFname = null) { + if (!is_readable($fname)) { + throw new Exception('load error: failed to find '.$fname); + } + + $pi = pathinfo($fname); + + $oldImport = $this->importDir; + + $this->importDir = (array)$this->importDir; + $this->importDir[] = $pi['dirname'].'/'; + + $this->allParsedFiles = array(); + $this->addParsedFile($fname); + + $out = $this->compile(file_get_contents($fname), $fname); + + $this->importDir = $oldImport; + + if ($outFname !== null) { + return file_put_contents($outFname, $out); + } + + return $out; + } + + // compile only if changed input has changed or output doesn't exist + public function checkedCompile($in, $out) { + if (!is_file($out) || filemtime($in) > filemtime($out)) { + $this->compileFile($in, $out); + return true; + } + return false; + } + + /** + * Execute lessphp on a .less file or a lessphp cache structure + * + * The lessphp cache structure contains information about a specific + * less file having been parsed. It can be used as a hint for future + * calls to determine whether or not a rebuild is required. + * + * The cache structure contains two important keys that may be used + * externally: + * + * compiled: The final compiled CSS + * updated: The time (in seconds) the CSS was last compiled + * + * The cache structure is a plain-ol' PHP associative array and can + * be serialized and unserialized without a hitch. + * + * @param mixed $in Input + * @param bool $force Force rebuild? + * @return array lessphp cache structure + */ + public function cachedCompile($in, $force = false) { + // assume no root + $root = null; + + if (is_string($in)) { + $root = $in; + } elseif (is_array($in) and isset($in['root'])) { + if ($force or ! isset($in['files'])) { + // If we are forcing a recompile or if for some reason the + // structure does not contain any file information we should + // specify the root to trigger a rebuild. + $root = $in['root']; + } elseif (isset($in['files']) and is_array($in['files'])) { + foreach ($in['files'] as $fname => $ftime ) { + if (!file_exists($fname) or filemtime($fname) > $ftime) { + // One of the files we knew about previously has changed + // so we should look at our incoming root again. + $root = $in['root']; + break; + } + } + } + } else { + // TODO: Throw an exception? We got neither a string nor something + // that looks like a compatible lessphp cache structure. + return null; + } + + if ($root !== null) { + // If we have a root value which means we should rebuild. + $out = array(); + $out['root'] = $root; + $out['compiled'] = $this->compileFile($root); + $out['files'] = $this->allParsedFiles(); + $out['updated'] = time(); + return $out; + } else { + // No changes, pass back the structure + // we were given initially. + return $in; + } + + } + + // parse and compile buffer + // This is deprecated + public function parse($str = null, $initialVariables = null) { + if (is_array($str)) { + $initialVariables = $str; + $str = null; + } + + $oldVars = $this->registeredVars; + if ($initialVariables !== null) { + $this->setVariables($initialVariables); + } + + if ($str == null) { + if (empty($this->_parseFile)) { + throw new exception("nothing to parse"); + } + + $out = $this->compileFile($this->_parseFile); + } else { + $out = $this->compile($str); + } + + $this->registeredVars = $oldVars; + return $out; + } + + protected function makeParser($name) { + $parser = new lessc_parser($this, $name); + $parser->writeComments = $this->preserveComments; + + return $parser; + } + + public function setFormatter($name) { + $this->formatterName = $name; + } + + protected function newFormatter() { + $className = "lessc_formatter_lessjs"; + if (!empty($this->formatterName)) { + if (!is_string($this->formatterName)) + return $this->formatterName; + $className = "lessc_formatter_$this->formatterName"; + } + + return new $className; + } + + public function setPreserveComments($preserve) { + $this->preserveComments = $preserve; + } + + public function registerFunction($name, $func) { + $this->libFunctions[$name] = $func; + } + + public function unregisterFunction($name) { + unset($this->libFunctions[$name]); + } + + public function setVariables($variables) { + $this->registeredVars = array_merge($this->registeredVars, $variables); + } + + public function unsetVariable($name) { + unset($this->registeredVars[$name]); + } + + public function setImportDir($dirs) { + $this->importDir = (array)$dirs; + } + + public function addImportDir($dir) { + $this->importDir = (array)$this->importDir; + $this->importDir[] = $dir; + } + + public function allParsedFiles() { + return $this->allParsedFiles; + } + + protected function addParsedFile($file) { + $this->allParsedFiles[realpath($file)] = filemtime($file); + } + + /** + * Uses the current value of $this->count to show line and line number + */ + protected function throwError($msg = null) { + if ($this->sourceLoc >= 0) { + $this->sourceParser->throwError($msg, $this->sourceLoc); + } + throw new exception($msg); + } + + // compile file $in to file $out if $in is newer than $out + // returns true when it compiles, false otherwise + public static function ccompile($in, $out, $less = null) { + if ($less === null) { + $less = new self; + } + return $less->checkedCompile($in, $out); + } + + public static function cexecute($in, $force = false, $less = null) { + if ($less === null) { + $less = new self; + } + return $less->cachedCompile($in, $force); + } + + static protected $cssColors = array( + 'aliceblue' => '240,248,255', + 'antiquewhite' => '250,235,215', + 'aqua' => '0,255,255', + 'aquamarine' => '127,255,212', + 'azure' => '240,255,255', + 'beige' => '245,245,220', + 'bisque' => '255,228,196', + 'black' => '0,0,0', + 'blanchedalmond' => '255,235,205', + 'blue' => '0,0,255', + 'blueviolet' => '138,43,226', + 'brown' => '165,42,42', + 'burlywood' => '222,184,135', + 'cadetblue' => '95,158,160', + 'chartreuse' => '127,255,0', + 'chocolate' => '210,105,30', + 'coral' => '255,127,80', + 'cornflowerblue' => '100,149,237', + 'cornsilk' => '255,248,220', + 'crimson' => '220,20,60', + 'cyan' => '0,255,255', + 'darkblue' => '0,0,139', + 'darkcyan' => '0,139,139', + 'darkgoldenrod' => '184,134,11', + 'darkgray' => '169,169,169', + 'darkgreen' => '0,100,0', + 'darkgrey' => '169,169,169', + 'darkkhaki' => '189,183,107', + 'darkmagenta' => '139,0,139', + 'darkolivegreen' => '85,107,47', + 'darkorange' => '255,140,0', + 'darkorchid' => '153,50,204', + 'darkred' => '139,0,0', + 'darksalmon' => '233,150,122', + 'darkseagreen' => '143,188,143', + 'darkslateblue' => '72,61,139', + 'darkslategray' => '47,79,79', + 'darkslategrey' => '47,79,79', + 'darkturquoise' => '0,206,209', + 'darkviolet' => '148,0,211', + 'deeppink' => '255,20,147', + 'deepskyblue' => '0,191,255', + 'dimgray' => '105,105,105', + 'dimgrey' => '105,105,105', + 'dodgerblue' => '30,144,255', + 'firebrick' => '178,34,34', + 'floralwhite' => '255,250,240', + 'forestgreen' => '34,139,34', + 'fuchsia' => '255,0,255', + 'gainsboro' => '220,220,220', + 'ghostwhite' => '248,248,255', + 'gold' => '255,215,0', + 'goldenrod' => '218,165,32', + 'gray' => '128,128,128', + 'green' => '0,128,0', + 'greenyellow' => '173,255,47', + 'grey' => '128,128,128', + 'honeydew' => '240,255,240', + 'hotpink' => '255,105,180', + 'indianred' => '205,92,92', + 'indigo' => '75,0,130', + 'ivory' => '255,255,240', + 'khaki' => '240,230,140', + 'lavender' => '230,230,250', + 'lavenderblush' => '255,240,245', + 'lawngreen' => '124,252,0', + 'lemonchiffon' => '255,250,205', + 'lightblue' => '173,216,230', + 'lightcoral' => '240,128,128', + 'lightcyan' => '224,255,255', + 'lightgoldenrodyellow' => '250,250,210', + 'lightgray' => '211,211,211', + 'lightgreen' => '144,238,144', + 'lightgrey' => '211,211,211', + 'lightpink' => '255,182,193', + 'lightsalmon' => '255,160,122', + 'lightseagreen' => '32,178,170', + 'lightskyblue' => '135,206,250', + 'lightslategray' => '119,136,153', + 'lightslategrey' => '119,136,153', + 'lightsteelblue' => '176,196,222', + 'lightyellow' => '255,255,224', + 'lime' => '0,255,0', + 'limegreen' => '50,205,50', + 'linen' => '250,240,230', + 'magenta' => '255,0,255', + 'maroon' => '128,0,0', + 'mediumaquamarine' => '102,205,170', + 'mediumblue' => '0,0,205', + 'mediumorchid' => '186,85,211', + 'mediumpurple' => '147,112,219', + 'mediumseagreen' => '60,179,113', + 'mediumslateblue' => '123,104,238', + 'mediumspringgreen' => '0,250,154', + 'mediumturquoise' => '72,209,204', + 'mediumvioletred' => '199,21,133', + 'midnightblue' => '25,25,112', + 'mintcream' => '245,255,250', + 'mistyrose' => '255,228,225', + 'moccasin' => '255,228,181', + 'navajowhite' => '255,222,173', + 'navy' => '0,0,128', + 'oldlace' => '253,245,230', + 'olive' => '128,128,0', + 'olivedrab' => '107,142,35', + 'orange' => '255,165,0', + 'orangered' => '255,69,0', + 'orchid' => '218,112,214', + 'palegoldenrod' => '238,232,170', + 'palegreen' => '152,251,152', + 'paleturquoise' => '175,238,238', + 'palevioletred' => '219,112,147', + 'papayawhip' => '255,239,213', + 'peachpuff' => '255,218,185', + 'peru' => '205,133,63', + 'pink' => '255,192,203', + 'plum' => '221,160,221', + 'powderblue' => '176,224,230', + 'purple' => '128,0,128', + 'red' => '255,0,0', + 'rosybrown' => '188,143,143', + 'royalblue' => '65,105,225', + 'saddlebrown' => '139,69,19', + 'salmon' => '250,128,114', + 'sandybrown' => '244,164,96', + 'seagreen' => '46,139,87', + 'seashell' => '255,245,238', + 'sienna' => '160,82,45', + 'silver' => '192,192,192', + 'skyblue' => '135,206,235', + 'slateblue' => '106,90,205', + 'slategray' => '112,128,144', + 'slategrey' => '112,128,144', + 'snow' => '255,250,250', + 'springgreen' => '0,255,127', + 'steelblue' => '70,130,180', + 'tan' => '210,180,140', + 'teal' => '0,128,128', + 'thistle' => '216,191,216', + 'tomato' => '255,99,71', + 'transparent' => '0,0,0,0', + 'turquoise' => '64,224,208', + 'violet' => '238,130,238', + 'wheat' => '245,222,179', + 'white' => '255,255,255', + 'whitesmoke' => '245,245,245', + 'yellow' => '255,255,0', + 'yellowgreen' => '154,205,50' + ); +} + +// responsible for taking a string of LESS code and converting it into a +// syntax tree +class lessc_parser { + static protected $nextBlockId = 0; // used to uniquely identify blocks + + static protected $precedence = array( + '=<' => 0, + '>=' => 0, + '=' => 0, + '<' => 0, + '>' => 0, + + '+' => 1, + '-' => 1, + '*' => 2, + '/' => 2, + '%' => 2, + ); + + static protected $whitePattern; + static protected $commentMulti; + + static protected $commentSingle = "//"; + static protected $commentMultiLeft = "/*"; + static protected $commentMultiRight = "*/"; + + // regex string to match any of the operators + static protected $operatorString; + + // these properties will supress division unless it's inside parenthases + static protected $supressDivisionProps = + array('/border-radius$/i', '/^font$/i'); + + protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document"); + protected $lineDirectives = array("charset"); + + /** + * if we are in parens we can be more liberal with whitespace around + * operators because it must evaluate to a single value and thus is less + * ambiguous. + * + * Consider: + * property1: 10 -5; // is two numbers, 10 and -5 + * property2: (10 -5); // should evaluate to 5 + */ + protected $inParens = false; + + // caches preg escaped literals + static protected $literalCache = array(); + + public function __construct($lessc, $sourceName = null) { + $this->eatWhiteDefault = true; + // reference to less needed for vPrefix, mPrefix, and parentSelector + $this->lessc = $lessc; + + $this->sourceName = $sourceName; // name used for error messages + + $this->writeComments = false; + + if (!self::$operatorString) { + self::$operatorString = + '('.implode('|', array_map(array('lessc', 'preg_quote'), + array_keys(self::$precedence))).')'; + + $commentSingle = lessc::preg_quote(self::$commentSingle); + $commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft); + $commentMultiRight = lessc::preg_quote(self::$commentMultiRight); + + self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight; + self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais'; + } + } + + public function parse($buffer) { + $this->count = 0; + $this->line = 1; + + $this->env = null; // block stack + $this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer); + $this->pushSpecialBlock("root"); + $this->eatWhiteDefault = true; + $this->seenComments = array(); + + // trim whitespace on head + // if (preg_match('/^\s+/', $this->buffer, $m)) { + // $this->line += substr_count($m[0], "\n"); + // $this->buffer = ltrim($this->buffer); + // } + $this->whitespace(); + + // parse the entire file + $lastCount = $this->count; + while (false !== $this->parseChunk()); + + if ($this->count != strlen($this->buffer)) + $this->throwError(); + + // TODO report where the block was opened + if (!is_null($this->env->parent)) + throw new exception('parse error: unclosed block'); + + return $this->env; + } + + /** + * Parse a single chunk off the head of the buffer and append it to the + * current parse environment. + * Returns false when the buffer is empty, or when there is an error. + * + * This function is called repeatedly until the entire document is + * parsed. + * + * This parser is most similar to a recursive descent parser. Single + * functions represent discrete grammatical rules for the language, and + * they are able to capture the text that represents those rules. + * + * Consider the function lessc::keyword(). (all parse functions are + * structured the same) + * + * The function takes a single reference argument. When calling the + * function it will attempt to match a keyword on the head of the buffer. + * If it is successful, it will place the keyword in the referenced + * argument, advance the position in the buffer, and return true. If it + * fails then it won't advance the buffer and it will return false. + * + * All of these parse functions are powered by lessc::match(), which behaves + * the same way, but takes a literal regular expression. Sometimes it is + * more convenient to use match instead of creating a new function. + * + * Because of the format of the functions, to parse an entire string of + * grammatical rules, you can chain them together using &&. + * + * But, if some of the rules in the chain succeed before one fails, then + * the buffer position will be left at an invalid state. In order to + * avoid this, lessc::seek() is used to remember and set buffer positions. + * + * Before parsing a chain, use $s = $this->seek() to remember the current + * position into $s. Then if a chain fails, use $this->seek($s) to + * go back where we started. + */ + protected function parseChunk() { + if (empty($this->buffer)) return false; + $s = $this->seek(); + + // setting a property + if ($this->keyword($key) && $this->assign() && + $this->propertyValue($value, $key) && $this->end()) + { + $this->append(array('assign', $key, $value), $s); + return true; + } else { + $this->seek($s); + } + + + // look for special css blocks + if ($this->literal('@', false)) { + $this->count--; + + // media + if ($this->literal('@media')) { + if (($this->mediaQueryList($mediaQueries) || true) + && $this->literal('{')) + { + $media = $this->pushSpecialBlock("media"); + $media->queries = is_null($mediaQueries) ? array() : $mediaQueries; + return true; + } else { + $this->seek($s); + return false; + } + } + + if ($this->literal("@", false) && $this->keyword($dirName)) { + if ($this->isDirective($dirName, $this->blockDirectives)) { + if (($this->openString("{", $dirValue, null, array(";")) || true) && + $this->literal("{")) + { + $dir = $this->pushSpecialBlock("directive"); + $dir->name = $dirName; + if (isset($dirValue)) $dir->value = $dirValue; + return true; + } + } elseif ($this->isDirective($dirName, $this->lineDirectives)) { + if ($this->propertyValue($dirValue) && $this->end()) { + $this->append(array("directive", $dirName, $dirValue)); + return true; + } + } + } + + $this->seek($s); + } + + // setting a variable + if ($this->variable($var) && $this->assign() && + $this->propertyValue($value) && $this->end()) + { + $this->append(array('assign', $var, $value), $s); + return true; + } else { + $this->seek($s); + } + + if ($this->import($importValue)) { + $this->append($importValue, $s); + return true; + } + + // opening parametric mixin + if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) && + ($this->guards($guards) || true) && + $this->literal('{')) + { + $block = $this->pushBlock($this->fixTags(array($tag))); + $block->args = $args; + $block->isVararg = $isVararg; + if (!empty($guards)) $block->guards = $guards; + return true; + } else { + $this->seek($s); + } + + // opening a simple block + if ($this->tags($tags) && $this->literal('{')) { + $tags = $this->fixTags($tags); + $this->pushBlock($tags); + return true; + } else { + $this->seek($s); + } + + // closing a block + if ($this->literal('}', false)) { + try { + $block = $this->pop(); + } catch (exception $e) { + $this->seek($s); + $this->throwError($e->getMessage()); + } + + $hidden = false; + if (is_null($block->type)) { + $hidden = true; + if (!isset($block->args)) { + foreach ($block->tags as $tag) { + if (!is_string($tag) || $tag{0} != $this->lessc->mPrefix) { + $hidden = false; + break; + } + } + } + + foreach ($block->tags as $tag) { + if (is_string($tag)) { + $this->env->children[$tag][] = $block; + } + } + } + + if (!$hidden) { + $this->append(array('block', $block), $s); + } + + // this is done here so comments aren't bundled into he block that + // was just closed + $this->whitespace(); + return true; + } + + // mixin + if ($this->mixinTags($tags) && + ($this->argumentValues($argv) || true) && + ($this->keyword($suffix) || true) && $this->end()) + { + $tags = $this->fixTags($tags); + $this->append(array('mixin', $tags, $argv, $suffix), $s); + return true; + } else { + $this->seek($s); + } + + // spare ; + if ($this->literal(';')) return true; + + return false; // got nothing, throw error + } + + protected function isDirective($dirname, $directives) { + // TODO: cache pattern in parser + $pattern = implode("|", + array_map(array("lessc", "preg_quote"), $directives)); + $pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i'; + + return preg_match($pattern, $dirname); + } + + protected function fixTags($tags) { + // move @ tags out of variable namespace + foreach ($tags as &$tag) { + if ($tag{0} == $this->lessc->vPrefix) + $tag[0] = $this->lessc->mPrefix; + } + return $tags; + } + + // a list of expressions + protected function expressionList(&$exps) { + $values = array(); + + while ($this->expression($exp)) { + $values[] = $exp; + } + + if (count($values) == 0) return false; + + $exps = lessc::compressList($values, ' '); + return true; + } + + /** + * Attempt to consume an expression. + * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code + */ + protected function expression(&$out) { + if ($this->value($lhs)) { + $out = $this->expHelper($lhs, 0); + + // look for / shorthand + if (!empty($this->env->supressedDivision)) { + unset($this->env->supressedDivision); + $s = $this->seek(); + if ($this->literal("/") && $this->value($rhs)) { + $out = array("list", "", + array($out, array("keyword", "/"), $rhs)); + } else { + $this->seek($s); + } + } + + return true; + } + return false; + } + + /** + * recursively parse infix equation with $lhs at precedence $minP + */ + protected function expHelper($lhs, $minP) { + $this->inExp = true; + $ss = $this->seek(); + + while (true) { + $whiteBefore = isset($this->buffer[$this->count - 1]) && + ctype_space($this->buffer[$this->count - 1]); + + // If there is whitespace before the operator, then we require + // whitespace after the operator for it to be an expression + $needWhite = $whiteBefore && !$this->inParens; + + if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) { + if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) { + foreach (self::$supressDivisionProps as $pattern) { + if (preg_match($pattern, $this->env->currentProperty)) { + $this->env->supressedDivision = true; + break 2; + } + } + } + + + $whiteAfter = isset($this->buffer[$this->count - 1]) && + ctype_space($this->buffer[$this->count - 1]); + + if (!$this->value($rhs)) break; + + // peek for next operator to see what to do with rhs + if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) { + $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]); + } + + $lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter); + $ss = $this->seek(); + + continue; + } + + break; + } + + $this->seek($ss); + + return $lhs; + } + + // consume a list of values for a property + public function propertyValue(&$value, $keyName = null) { + $values = array(); + + if ($keyName !== null) $this->env->currentProperty = $keyName; + + $s = null; + while ($this->expressionList($v)) { + $values[] = $v; + $s = $this->seek(); + if (!$this->literal(',')) break; + } + + if ($s) $this->seek($s); + + if ($keyName !== null) unset($this->env->currentProperty); + + if (count($values) == 0) return false; + + $value = lessc::compressList($values, ', '); + return true; + } + + protected function parenValue(&$out) { + $s = $this->seek(); + + // speed shortcut + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") { + return false; + } + + $inParens = $this->inParens; + if ($this->literal("(") && + ($this->inParens = true) && $this->expression($exp) && + $this->literal(")")) + { + $out = $exp; + $this->inParens = $inParens; + return true; + } else { + $this->inParens = $inParens; + $this->seek($s); + } + + return false; + } + + // a single value + protected function value(&$value) { + $s = $this->seek(); + + // speed shortcut + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") { + // negation + if ($this->literal("-", false) && + (($this->variable($inner) && $inner = array("variable", $inner)) || + $this->unit($inner) || + $this->parenValue($inner))) + { + $value = array("unary", "-", $inner); + return true; + } else { + $this->seek($s); + } + } + + if ($this->parenValue($value)) return true; + if ($this->unit($value)) return true; + if ($this->color($value)) return true; + if ($this->func($value)) return true; + if ($this->string($value)) return true; + + if ($this->keyword($word)) { + $value = array('keyword', $word); + return true; + } + + // try a variable + if ($this->variable($var)) { + $value = array('variable', $var); + return true; + } + + // unquote string (should this work on any type? + if ($this->literal("~") && $this->string($str)) { + $value = array("escape", $str); + return true; + } else { + $this->seek($s); + } + + // css hack: \0 + if ($this->literal('\\') && $this->match('([0-9]+)', $m)) { + $value = array('keyword', '\\'.$m[1]); + return true; + } else { + $this->seek($s); + } + + return false; + } + + // an import statement + protected function import(&$out) { + $s = $this->seek(); + if (!$this->literal('@import')) return false; + + // @import "something.css" media; + // @import url("something.css") media; + // @import url(something.css) media; + + if ($this->propertyValue($value)) { + $out = array("import", $value); + return true; + } + } + + protected function mediaQueryList(&$out) { + if ($this->genericList($list, "mediaQuery", ",", false)) { + $out = $list[2]; + return true; + } + return false; + } + + protected function mediaQuery(&$out) { + $s = $this->seek(); + + $expressions = null; + $parts = array(); + + if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) { + $prop = array("mediaType"); + if (isset($only)) $prop[] = "only"; + if (isset($not)) $prop[] = "not"; + $prop[] = $mediaType; + $parts[] = $prop; + } else { + $this->seek($s); + } + + + if (!empty($mediaType) && !$this->literal("and")) { + // ~ + } else { + $this->genericList($expressions, "mediaExpression", "and", false); + if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]); + } + + if (count($parts) == 0) { + $this->seek($s); + return false; + } + + $out = $parts; + return true; + } + + protected function mediaExpression(&$out) { + $s = $this->seek(); + $value = null; + if ($this->literal("(") && + $this->keyword($feature) && + ($this->literal(":") && $this->expression($value) || true) && + $this->literal(")")) + { + $out = array("mediaExp", $feature); + if ($value) $out[] = $value; + return true; + } elseif ($this->variable($variable)) { + $out = array('variable', $variable); + return true; + } + + $this->seek($s); + return false; + } + + // an unbounded string stopped by $end + protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) { + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + $stop = array("'", '"', "@{", $end); + $stop = array_map(array("lessc", "preg_quote"), $stop); + // $stop[] = self::$commentMulti; + + if (!is_null($rejectStrs)) { + $stop = array_merge($stop, $rejectStrs); + } + + $patt = '(.*?)('.implode("|", $stop).')'; + + $nestingLevel = 0; + + $content = array(); + while ($this->match($patt, $m, false)) { + if (!empty($m[1])) { + $content[] = $m[1]; + if ($nestingOpen) { + $nestingLevel += substr_count($m[1], $nestingOpen); + } + } + + $tok = $m[2]; + + $this->count-= strlen($tok); + if ($tok == $end) { + if ($nestingLevel == 0) { + break; + } else { + $nestingLevel--; + } + } + + if (($tok == "'" || $tok == '"') && $this->string($str)) { + $content[] = $str; + continue; + } + + if ($tok == "@{" && $this->interpolation($inter)) { + $content[] = $inter; + continue; + } + + if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) { + $ount = null; + break; + } + + $content[] = $tok; + $this->count+= strlen($tok); + } + + $this->eatWhiteDefault = $oldWhite; + + if (count($content) == 0) return false; + + // trim the end + if (is_string(end($content))) { + $content[count($content) - 1] = rtrim(end($content)); + } + + $out = array("string", "", $content); + return true; + } + + protected function string(&$out) { + $s = $this->seek(); + if ($this->literal('"', false)) { + $delim = '"'; + } elseif ($this->literal("'", false)) { + $delim = "'"; + } else { + return false; + } + + $content = array(); + + // look for either ending delim , escape, or string interpolation + $patt = '([^\n]*?)(@\{|\\\\|' . + lessc::preg_quote($delim).')'; + + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + while ($this->match($patt, $m, false)) { + $content[] = $m[1]; + if ($m[2] == "@{") { + $this->count -= strlen($m[2]); + if ($this->interpolation($inter, false)) { + $content[] = $inter; + } else { + $this->count += strlen($m[2]); + $content[] = "@{"; // ignore it + } + } elseif ($m[2] == '\\') { + $content[] = $m[2]; + if ($this->literal($delim, false)) { + $content[] = $delim; + } + } else { + $this->count -= strlen($delim); + break; // delim + } + } + + $this->eatWhiteDefault = $oldWhite; + + if ($this->literal($delim)) { + $out = array("string", $delim, $content); + return true; + } + + $this->seek($s); + return false; + } + + protected function interpolation(&$out) { + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = true; + + $s = $this->seek(); + if ($this->literal("@{") && + $this->openString("}", $interp, null, array("'", '"', ";")) && + $this->literal("}", false)) + { + $out = array("interpolate", $interp); + $this->eatWhiteDefault = $oldWhite; + if ($this->eatWhiteDefault) $this->whitespace(); + return true; + } + + $this->eatWhiteDefault = $oldWhite; + $this->seek($s); + return false; + } + + protected function unit(&$unit) { + // speed shortcut + if (isset($this->buffer[$this->count])) { + $char = $this->buffer[$this->count]; + if (!ctype_digit($char) && $char != ".") return false; + } + + if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) { + $unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]); + return true; + } + return false; + } + + // a # color + protected function color(&$out) { + if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) { + if (strlen($m[1]) > 7) { + $out = array("string", "", array($m[1])); + } else { + $out = array("raw_color", $m[1]); + } + return true; + } + + return false; + } + + // consume a list of property values delimited by ; and wrapped in () + protected function argumentValues(&$args, $delim = ',') { + $s = $this->seek(); + if (!$this->literal('(')) return false; + + $values = array(); + while (true) { + if ($this->expressionList($value)) $values[] = $value; + if (!$this->literal($delim)) break; + else { + if ($value == null) $values[] = null; + $value = null; + } + } + + if (!$this->literal(')')) { + $this->seek($s); + return false; + } + + $args = $values; + return true; + } + + // consume an argument definition list surrounded by () + // each argument is a variable name with optional value + // or at the end a ... or a variable named followed by ... + protected function argumentDef(&$args, &$isVararg, $delim = ',') { + $s = $this->seek(); + if (!$this->literal('(')) return false; + + $values = array(); + + $isVararg = false; + while (true) { + if ($this->literal("...")) { + $isVararg = true; + break; + } + + if ($this->variable($vname)) { + $arg = array("arg", $vname); + $ss = $this->seek(); + if ($this->assign() && $this->expressionList($value)) { + $arg[] = $value; + } else { + $this->seek($ss); + if ($this->literal("...")) { + $arg[0] = "rest"; + $isVararg = true; + } + } + $values[] = $arg; + if ($isVararg) break; + continue; + } + + if ($this->value($literal)) { + $values[] = array("lit", $literal); + } + + if (!$this->literal($delim)) break; + } + + if (!$this->literal(')')) { + $this->seek($s); + return false; + } + + $args = $values; + + return true; + } + + // consume a list of tags + // this accepts a hanging delimiter + protected function tags(&$tags, $simple = false, $delim = ',') { + $tags = array(); + while ($this->tag($tt, $simple)) { + $tags[] = $tt; + if (!$this->literal($delim)) break; + } + if (count($tags) == 0) return false; + + return true; + } + + // list of tags of specifying mixin path + // optionally separated by > (lazy, accepts extra >) + protected function mixinTags(&$tags) { + $s = $this->seek(); + $tags = array(); + while ($this->tag($tt, true)) { + $tags[] = $tt; + $this->literal(">"); + } + + if (count($tags) == 0) return false; + + return true; + } + + // a bracketed value (contained within in a tag definition) + protected function tagBracket(&$value) { + // speed shortcut + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") { + return false; + } + + $s = $this->seek(); + if ($this->literal('[') && $this->to(']', $c, true) && $this->literal(']', false)) { + $value = '['.$c.']'; + // whitespace? + if ($this->whitespace()) $value .= " "; + + // escape parent selector, (yuck) + $value = str_replace($this->lessc->parentSelector, "$&$", $value); + return true; + } + + $this->seek($s); + return false; + } + + protected function tagExpression(&$value) { + $s = $this->seek(); + if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) { + $value = array('exp', $exp); + return true; + } + + $this->seek($s); + return false; + } + + // a space separated list of selectors + protected function tag(&$tag, $simple = false) { + if ($simple) + $chars = '^@,:;{}\][>\(\) "\''; + else + $chars = '^@,;{}["\''; + + $s = $this->seek(); + + if (!$simple && $this->tagExpression($tag)) { + return true; + } + + $hasExpression = false; + $parts = array(); + while ($this->tagBracket($first)) $parts[] = $first; + + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + while (true) { + if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) { + $parts[] = $m[1]; + if ($simple) break; + + while ($this->tagBracket($brack)) { + $parts[] = $brack; + } + continue; + } + + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") { + if ($this->interpolation($interp)) { + $hasExpression = true; + $interp[2] = true; // don't unescape + $parts[] = $interp; + continue; + } + + if ($this->literal("@")) { + $parts[] = "@"; + continue; + } + } + + if ($this->unit($unit)) { // for keyframes + $parts[] = $unit[1]; + $parts[] = $unit[2]; + continue; + } + + break; + } + + $this->eatWhiteDefault = $oldWhite; + if (!$parts) { + $this->seek($s); + return false; + } + + if ($hasExpression) { + $tag = array("exp", array("string", "", $parts)); + } else { + $tag = trim(implode($parts)); + } + + $this->whitespace(); + return true; + } + + // a css function + protected function func(&$func) { + $s = $this->seek(); + + if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) { + $fname = $m[1]; + + $sPreArgs = $this->seek(); + + $args = array(); + while (true) { + $ss = $this->seek(); + // this ugly nonsense is for ie filter properties + if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) { + $args[] = array("string", "", array($name, "=", $value)); + } else { + $this->seek($ss); + if ($this->expressionList($value)) { + $args[] = $value; + } + } + + if (!$this->literal(',')) break; + } + $args = array('list', ',', $args); + + if ($this->literal(')')) { + $func = array('function', $fname, $args); + return true; + } elseif ($fname == 'url') { + // couldn't parse and in url? treat as string + $this->seek($sPreArgs); + if ($this->openString(")", $string) && $this->literal(")")) { + $func = array('function', $fname, $string); + return true; + } + } + } + + $this->seek($s); + return false; + } + + // consume a less variable + protected function variable(&$name) { + $s = $this->seek(); + if ($this->literal($this->lessc->vPrefix, false) && + ($this->variable($sub) || $this->keyword($name))) + { + if (!empty($sub)) { + $name = array('variable', $sub); + } else { + $name = $this->lessc->vPrefix.$name; + } + return true; + } + + $name = null; + $this->seek($s); + return false; + } + + /** + * Consume an assignment operator + * Can optionally take a name that will be set to the current property name + */ + protected function assign($name = null) { + if ($name) $this->currentProperty = $name; + return $this->literal(':') || $this->literal('='); + } + + // consume a keyword + protected function keyword(&$word) { + if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) { + $word = $m[1]; + return true; + } + return false; + } + + // consume an end of statement delimiter + protected function end() { + if ($this->literal(';')) { + return true; + } elseif ($this->count == strlen($this->buffer) || $this->buffer{$this->count} == '}') { + // if there is end of file or a closing block next then we don't need a ; + return true; + } + return false; + } + + protected function guards(&$guards) { + $s = $this->seek(); + + if (!$this->literal("when")) { + $this->seek($s); + return false; + } + + $guards = array(); + + while ($this->guardGroup($g)) { + $guards[] = $g; + if (!$this->literal(",")) break; + } + + if (count($guards) == 0) { + $guards = null; + $this->seek($s); + return false; + } + + return true; + } + + // a bunch of guards that are and'd together + // TODO rename to guardGroup + protected function guardGroup(&$guardGroup) { + $s = $this->seek(); + $guardGroup = array(); + while ($this->guard($guard)) { + $guardGroup[] = $guard; + if (!$this->literal("and")) break; + } + + if (count($guardGroup) == 0) { + $guardGroup = null; + $this->seek($s); + return false; + } + + return true; + } + + protected function guard(&$guard) { + $s = $this->seek(); + $negate = $this->literal("not"); + + if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) { + $guard = $exp; + if ($negate) $guard = array("negate", $guard); + return true; + } + + $this->seek($s); + return false; + } + + /* raw parsing functions */ + + protected function literal($what, $eatWhitespace = null) { + if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault; + + // shortcut on single letter + if (!isset($what[1]) && isset($this->buffer[$this->count])) { + if ($this->buffer[$this->count] == $what) { + if (!$eatWhitespace) { + $this->count++; + return true; + } + // goes below... + } else { + return false; + } + } + + if (!isset(self::$literalCache[$what])) { + self::$literalCache[$what] = lessc::preg_quote($what); + } + + return $this->match(self::$literalCache[$what], $m, $eatWhitespace); + } + + protected function genericList(&$out, $parseItem, $delim="", $flatten=true) { + $s = $this->seek(); + $items = array(); + while ($this->$parseItem($value)) { + $items[] = $value; + if ($delim) { + if (!$this->literal($delim)) break; + } + } + + if (count($items) == 0) { + $this->seek($s); + return false; + } + + if ($flatten && count($items) == 1) { + $out = $items[0]; + } else { + $out = array("list", $delim, $items); + } + + return true; + } + + + // advance counter to next occurrence of $what + // $until - don't include $what in advance + // $allowNewline, if string, will be used as valid char set + protected function to($what, &$out, $until = false, $allowNewline = false) { + if (is_string($allowNewline)) { + $validChars = $allowNewline; + } else { + $validChars = $allowNewline ? "." : "[^\n]"; + } + if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false; + if ($until) $this->count -= strlen($what); // give back $what + $out = $m[1]; + return true; + } + + // try to match something on head of buffer + protected function match($regex, &$out, $eatWhitespace = null) { + if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault; + + $r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais'; + if (preg_match($r, $this->buffer, $out, null, $this->count)) { + $this->count += strlen($out[0]); + if ($eatWhitespace && $this->writeComments) $this->whitespace(); + return true; + } + return false; + } + + // match some whitespace + protected function whitespace() { + if ($this->writeComments) { + $gotWhite = false; + while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) { + if (isset($m[1]) && empty($this->commentsSeen[$this->count])) { + $this->append(array("comment", $m[1])); + $this->commentsSeen[$this->count] = true; + } + $this->count += strlen($m[0]); + $gotWhite = true; + } + return $gotWhite; + } else { + $this->match("", $m); + return strlen($m[0]) > 0; + } + } + + // match something without consuming it + protected function peek($regex, &$out = null, $from=null) { + if (is_null($from)) $from = $this->count; + $r = '/'.$regex.'/Ais'; + $result = preg_match($r, $this->buffer, $out, null, $from); + + return $result; + } + + // seek to a spot in the buffer or return where we are on no argument + protected function seek($where = null) { + if ($where === null) return $this->count; + else $this->count = $where; + return true; + } + + /* misc functions */ + + public function throwError($msg = "parse error", $count = null) { + $count = is_null($count) ? $this->count : $count; + + $line = $this->line + + substr_count(substr($this->buffer, 0, $count), "\n"); + + if (!empty($this->sourceName)) { + $loc = "$this->sourceName on line $line"; + } else { + $loc = "line: $line"; + } + + // TODO this depends on $this->count + if ($this->peek("(.*?)(\n|$)", $m, $count)) { + throw new exception("$msg: failed at `$m[1]` $loc"); + } else { + throw new exception("$msg: $loc"); + } + } + + protected function pushBlock($selectors=null, $type=null) { + $b = new stdclass; + $b->parent = $this->env; + + $b->type = $type; + $b->id = self::$nextBlockId++; + + $b->isVararg = false; // TODO: kill me from here + $b->tags = $selectors; + + $b->props = array(); + $b->children = array(); + + $this->env = $b; + return $b; + } + + // push a block that doesn't multiply tags + protected function pushSpecialBlock($type) { + return $this->pushBlock(null, $type); + } + + // append a property to the current block + protected function append($prop, $pos = null) { + if ($pos !== null) $prop[-1] = $pos; + $this->env->props[] = $prop; + } + + // pop something off the stack + protected function pop() { + $old = $this->env; + $this->env = $this->env->parent; + return $old; + } + + // remove comments from $text + // todo: make it work for all functions, not just url + protected function removeComments($text) { + $look = array( + 'url(', '//', '/*', '"', "'" + ); + + $out = ''; + $min = null; + while (true) { + // find the next item + foreach ($look as $token) { + $pos = strpos($text, $token); + if ($pos !== false) { + if (!isset($min) || $pos < $min[1]) $min = array($token, $pos); + } + } + + if (is_null($min)) break; + + $count = $min[1]; + $skip = 0; + $newlines = 0; + switch ($min[0]) { + case 'url(': + if (preg_match('/url\(.*?\)/', $text, $m, 0, $count)) + $count += strlen($m[0]) - strlen($min[0]); + break; + case '"': + case "'": + if (preg_match('/'.$min[0].'.*?'.$min[0].'/', $text, $m, 0, $count)) + $count += strlen($m[0]) - 1; + break; + case '//': + $skip = strpos($text, "\n", $count); + if ($skip === false) $skip = strlen($text) - $count; + else $skip -= $count; + break; + case '/*': + if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) { + $skip = strlen($m[0]); + $newlines = substr_count($m[0], "\n"); + } + break; + } + + if ($skip == 0) $count += strlen($min[0]); + + $out .= substr($text, 0, $count).str_repeat("\n", $newlines); + $text = substr($text, $count + $skip); + + $min = null; + } + + return $out.$text; + } + +} + +class lessc_formatter_classic { + public $indentChar = " "; + + public $break = "\n"; + public $open = " {"; + public $close = "}"; + public $selectorSeparator = ", "; + public $assignSeparator = ":"; + + public $openSingle = " { "; + public $closeSingle = " }"; + + public $disableSingle = false; + public $breakSelectors = false; + + public $compressColors = false; + + public function __construct() { + $this->indentLevel = 0; + } + + public function indentStr($n = 0) { + return str_repeat($this->indentChar, max($this->indentLevel + $n, 0)); + } + + public function property($name, $value) { + return $name . $this->assignSeparator . $value . ";"; + } + + protected function isEmpty($block) { + if (empty($block->lines)) { + foreach ($block->children as $child) { + if (!$this->isEmpty($child)) return false; + } + + return true; + } + return false; + } + + public function block($block) { + if ($this->isEmpty($block)) return; + + $inner = $pre = $this->indentStr(); + + $isSingle = !$this->disableSingle && + is_null($block->type) && count($block->lines) == 1; + + if (!empty($block->selectors)) { + $this->indentLevel++; + + if ($this->breakSelectors) { + $selectorSeparator = $this->selectorSeparator . $this->break . $pre; + } else { + $selectorSeparator = $this->selectorSeparator; + } + + echo $pre . + implode($selectorSeparator, $block->selectors); + if ($isSingle) { + echo $this->openSingle; + $inner = ""; + } else { + echo $this->open . $this->break; + $inner = $this->indentStr(); + } + + } + + if (!empty($block->lines)) { + $glue = $this->break.$inner; + echo $inner . implode($glue, $block->lines); + if (!$isSingle && !empty($block->children)) { + echo $this->break; + } + } + + foreach ($block->children as $child) { + $this->block($child); + } + + if (!empty($block->selectors)) { + if (!$isSingle && empty($block->children)) echo $this->break; + + if ($isSingle) { + echo $this->closeSingle . $this->break; + } else { + echo $pre . $this->close . $this->break; + } + + $this->indentLevel--; + } + } +} + +class lessc_formatter_compressed extends lessc_formatter_classic { + public $disableSingle = true; + public $open = "{"; + public $selectorSeparator = ","; + public $assignSeparator = ":"; + public $break = ""; + public $compressColors = true; + + public function indentStr($n = 0) { + return ""; + } +} + +class lessc_formatter_lessjs extends lessc_formatter_classic { + public $disableSingle = true; + public $breakSelectors = true; + public $assignSeparator = ": "; + public $selectorSeparator = ","; +} + + diff --git a/widgets/widgets.php b/widgets/widgets.php new file mode 100644 index 000000000..04704afd6 --- /dev/null +++ b/widgets/widgets.php @@ -0,0 +1,1042 @@ +create_css($style, $preset); + $css = preg_replace('#/\*.*?\*/#s', '', $css); + $css = preg_replace('/\s*([{}|:;,])\s+/', '$1', $css); + $css = preg_replace('/\s\s+(.*)/', '$1', $css); + $css = str_replace(';}', '}', $css); + + set_site_transient('origin_wcss:'.$key, $css, 86400); + } + + return $css; +} + +/** + * Class SiteOrigin_Panels_Widget + */ +abstract class SiteOrigin_Panels_Widget extends WP_Widget{ + public $form_args; + protected $demo; + protected $origin_id; + public $sub_widgets; + + private $styles; + + /** + * Create the widget + * + * @param string $name Name for the widget displayed on the configuration page. + * @param array $widget_options Optional Passed to wp_register_sidebar_widget() + * - description: shown on the configuration page + * - classname + * @param array $control_options Optional Passed to wp_register_widget_control() + * - width: required if more than 250px + * - height: currently not used but may be needed in the future + * @param array $form Form arguments. + * @param array $demo Values for the demo of the page builder widget. + * @internal param string $id_base + */ + function __construct($name, $widget_options = array(), $control_options = array(), $form = array(), $demo = array()){ + $id_base = str_replace('SiteOrigin_Panels_Widget_', '', get_class($this)); + $id_base = strtolower(str_replace('_', '-', $id_base)); + + parent::__construct('origin_'.$id_base, $name, $widget_options, $control_options); + $this->origin_id = $id_base; + + $this->form_args = $form; + $this->demo = $demo; + $this->styles = array(); + $this->sub_widgets = array(); + } + + /** + * Update the widget and save the new CSS. + * + * @param array $old + * @param array $new + * @return array + */ + function update($new, $old) { + + // We wont clear cache if this is a preview + if(!siteorigin_panels_is_preview()){ + // Remove the old CSS file + if(!empty($old['origin_style'])) { + list($style, $preset) = explode(':', $old['origin_style']); + $this->clear_css_cache($style, $preset); + } + + // Clear the cache for all sub widgets + if(!empty($this->sub_widgets)){ + global $wp_widget_factory; + foreach($this->sub_widgets as $id => $sub) { + if(empty($old['origin_style_'.$id])) continue; + $the_widget = $wp_widget_factory->widgets[$sub[1]]; + list($style, $preset) = explode(':', $old['origin_style_'.$id]); + + $the_widget->clear_css_cache($style, $preset); + } + } + + + + } + + foreach($this->form_args as $field_id => $field_args) { + if($field_args['type'] == 'checkbox') { + $new[$field_id] = !empty($new[$field_id]); + } + } + + return $new; + } + + /** + * Display the form for the widget. Auto generated from form array. + * + * @param array $instance + * @return string|void + */ + public function form($instance){ + + foreach($this->form_args as $field_id => $field_args) { + if(isset($field_args['default']) && !isset($instance[$field_id])) { + $instance[$field_id] = $field_args['default']; + } + if(!isset($instance[$field_id])) $instance[$field_id] = false; + + ?>

'; + + switch($field_args['type']) { + case 'text' : + ?>/> + + '.esc_html($field_args['description']).''; + + ?>

widget_options['default_style']) ? $this->widget_options['default_style'] : false; + } + + do_action('siteorigin_panels_widget_before_styles', $this, $instance); + + // Now, lets add the style options. + $styles = $this->get_styles(); + if( !empty( $styles ) ) { + ?> +

+ + +

+ sub_widgets as $id => $sub) { + global $wp_widget_factory; + $the_widget = $wp_widget_factory->widgets[$sub[1]]; + + if(!isset($instance['origin_style_'.$id])) $instance['origin_style_'.$id] = !empty($this->widget_options['default_style_'.$id]) ? $this->widget_options['default_style_'.$id] : false; + + ?> +

+ + +

+ form_args as $field_id => $field_args) { + if(isset($field_args['default']) && !isset($instance[$field_id])) { + $instance[$field_id] = $field_args['default']; + } + if(!isset($instance[$field_id])) $instance[$field_id] = false; + } + + // Filter the title + if(!empty($instance['title'])) { + $instance['title'] = apply_filters('widget_title', $instance['title'], $instance, $this->id_base); + } + + if(!empty($instance['origin_style'])) { + list($style, $preset) = explode(':', $instance['origin_style']); + $style = sanitize_file_name($style); + $preset = sanitize_file_name($preset); + + $data = $this->get_style_data($style); + $template = $data['Template']; + } + else { + $style = 'default'; + $preset = 'default'; + } + + if(empty($template)) $template = 'default'; + + $template_file = false; + $paths = $this->get_widget_paths(); + + foreach($paths as $path) { + if(file_exists($path.'/'.$this->origin_id.'/tpl/'.$template.'.php')) { + $template_file = $path.'/'.$this->origin_id.'/tpl/'.$template.'.php'; + break; + } + } + if(empty($template_file)) { + echo $args['before_widget']; + echo 'Template not found'; + echo $args['after_widget']; + return false; + } + + // Dynamically generate the CSS + if(!empty($instance['origin_style'])) { + $filename = $this->origin_id.'-'.$style.'-'.$preset; + if(siteorigin_panels_setting('inline-css')) { + static $inlined_css = array(); + if(empty($inlined_css[$filename])) { + $inlined_css[$filename] = true; + ?> get_class($this), + 'style' => $style, + 'preset' => $preset, + ), site_url('?action=origin_widgets_css') ), array(), SITEORIGIN_PANELS_VERSION ); + } + } + + if(method_exists($this, 'enqueue_scripts')) { + $this->enqueue_scripts(); + } + + $widget_classes = apply_filters('siteorigin_widgets_classes', array( + 'origin-widget', + 'origin-widget-'.$this->origin_id, + 'origin-widget-'.$this->origin_id.'-'. $style .'-' . $preset, + ), $instance); + + if(method_exists($this, 'widget_classes')) { + $widget_classes = $this->widget_classes(array( + 'origin-widget', + 'origin-widget-'.$this->origin_id, + 'origin-widget-'.$this->origin_id.'-'. $style .'-' . $preset, + ), $instance); + } + + echo $args['before_widget']; + echo '
'; + include $template_file; + echo '
'; + echo $args['after_widget']; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Extra functions specific to a SiteOrigin widget. + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * A sub widget is a widget that's style is required by this widget + * + * @param $id + * @param $instance + */ + function sub_widget($id, $instance){ + $sub = $this->sub_widgets[$id]; + global $wp_widget_factory; + $the_widget = $wp_widget_factory->widgets[$sub[1]]; + $the_widget->widget(array('before_widget' => '', 'after_widget' => ''), $instance); + } + + /** + * Get the CSS for the given style and preset + * + * @param $style + * @param $preset + * @return string + */ + function create_css($style, $preset) { + $paths = $this->get_widget_paths(); + $style_file = false; + + // Find the file - exit if it can't be found. + foreach($paths as $path) { + if(file_exists($path.'/'.$this->origin_id.'/styles/'.$style.'.less')) { + $style_file = $path.'/'.$this->origin_id.'/styles/'.$style.'.less'; + break; + } + } + if(empty($style_file)) return ''; + + if( !class_exists('lessc') ) include plugin_dir_path(__FILE__) . 'lib/lessc.inc.php'; + + foreach($this->get_widget_folders() as $folder => $folder_url) { + $filename = rtrim($folder, '/') . '/' . $this->origin_id.'/styles/'.$style.'.less'; + if(file_exists($filename)) { + $less = file_get_contents($filename); + break; + } + } + // Add in the mixins + $less = str_replace( + '@import "../../../less/mixins";', + "\n\n".file_get_contents(plugin_dir_path(__FILE__).'less/mixins.less'), + $less + ); + + // Apply the preset variables to the LESS file + $presets = $this->get_style_presets($style); + if(!empty($presets[$preset]) && is_array($presets[$preset])){ + foreach($presets[$preset] as $k => $v) { + $less = preg_replace('/@'.preg_quote($k).':(.*);/', '@'.$k.': '.$v.';', $less); + } + } + + // Scope the CSS with the wrapper we'll be adding + $less = '.origin-widget.origin-widget-'.$this->origin_id.'-'.$style.'-'.$preset.' {' . $less . '}'; + $lc = new lessc(); + $lc->setPreserveComments(false); + + $lc->registerFunction('lumlighten', 'origin_widgets_less_lumlighten'); + $lc->registerFunction('lumdarken', 'origin_widgets_less_lumdarken'); + $lc->registerFunction('texture', 'origin_widgets_less_texture'); + $lc->registerFunction('widgetimage', 'origin_widgets_less_widgetimage'); + + // Create the CSS + return $lc->compile($less); + } + + /** + * Removes a CSS file + * + * @param $style + * @param $preset + */ + function clear_css_cache($style, $preset){ + $filename = $this->origin_id.'-'.$style.'-'.$preset; + delete_site_transient('origin_widgets_css_cache:'.$filename); + } + + /** + * Get all the paths where we'll look for widgets. + * + * @return array + */ + function get_widget_paths(){ + static $paths = array(); + + if(empty($paths)) { + $paths = array_keys($this->get_widget_folders()); + } + + return $paths; + } + + /** + * Get all the folders where we'll look for widgets + * + * @return mixed|void + */ + static function get_widget_folders(){ + static $folders = array(); + + if(empty($folders)) { + $folders = array( + get_stylesheet_directory().'/widgets' => get_stylesheet_directory_uri().'/widgets/widgets', + get_template_directory().'/widgets' => get_template_directory_uri().'/widgets', + plugin_dir_path(SITEORIGIN_PANELS_BASE_FILE).'widgets/widgets' => plugin_dir_url(SITEORIGIN_PANELS_BASE_FILE).'widgets/widgets', + ); + $folders = apply_filters('siteorigin_widget_folders', $folders); + } + + return $folders; + } + + /** + * Get all the folders where we'll look for widget images + * + * @return mixed|void + */ + static function get_image_folders(){ + static $folders = array(); + if(empty($folders)) { + $folders = array( + get_stylesheet_directory().'/widgets/img' => get_stylesheet_directory_uri().'/widgets/img', + get_template_directory().'/widgets/img' => get_template_directory_uri().'/widgets/img', + plugin_dir_path(SITEORIGIN_PANELS_BASE_FILE).'widgets/img' => plugin_dir_url(SITEORIGIN_PANELS_BASE_FILE).'widgets/img', + ); + $folders = apply_filters('siteorigin_widget_image_folders', $folders); + } + + return $folders; + } + + /** + * Get all the styles for this widget. + * + * @return array + */ + public function get_styles(){ + if( empty( $this->styles ) ) { + // We can add extra paths here + foreach($this->get_widget_paths() as $path) { + if(!is_dir($path)) continue; + + $files = glob($path.'/'.$this->origin_id.'/styles/*.less'); + if(!empty($files)) { + foreach(glob($path.'/'.$this->origin_id.'/styles/*.less') as $file) { + $p = pathinfo($file); + $this->styles[$p['filename']] = $this->get_style_data($p['filename']); + } + } + } + } + + return $this->styles; + } + + /** + * Get the presets for a given style + * + * @param $style_id + * @return mixed|void + */ + public function get_style_presets($style_id) { + + $presets = array(); + + foreach($this->get_widget_folders() as $folder => $folder_uri) { + $filename = rtrim($folder, '/') . '/' . $this->origin_id.'/presets/'.sanitize_file_name($style_id).'.php'; + + if(file_exists($filename)) { + // This file should register a filter that adds the presets + $new_presets = include($filename); + $presets = array_merge($presets, $new_presets); + } + } + + + return apply_filters('origin_widget_presets_'.$this->origin_id.'_'.$style_id, $presets); + } + + /** + * Get data for the style. + * + * @param $name + * @return array + */ + public function get_style_data($name) { + $paths = $this->get_widget_paths(); + + foreach($paths as $path) { + $filename = $path.'/'.$this->origin_id.'/styles/'.sanitize_file_name($name).'.less'; + if(!file_exists($filename)) continue; + + $data = get_file_data($filename, array( + 'Name' => 'Name', + 'Template' => 'Template', + 'Author' => 'Author', + 'Author URI' => 'Author URI', + ), 'origin_widget'); + return $data; + } + return false; + } + + /** + * Render a demo of the widget. + * + * @param array $args + */ + function render_demo($args = array()){ + $this->widget($args, $this->demo); + } + + /** + * Register a widget that we'll be using inside this widget. + * + * @param $id + * @param $name + * @param $class + */ + function add_sub_widget($id, $name, $class){ + $this->sub_widgets[$id] = array($name, $class); + } + + /** + * Add the fields required to query the posts. + */ + function add_post_query_fields(){ + // Add the posts type field + $post_types = get_post_types(array('public' => true)); + $post_types = array_values($post_types); + $this->form_args['query_post_type'] = array( + 'type' => 'select', + 'options' => $post_types, + 'label' => __('Post Type', 'siteorigin-panels') + ); + + // Add the posts per page field + $this->form_args['query_posts_per_page'] = array( + 'type' => 'number', + 'default' => 10, + 'label' => __('Posts Per Page', 'siteorigin-panels'), + ); + + $this->form_args['query_orderby'] = array( + 'type' => 'select', + 'label' => __('Order By', 'siteorigin-panels'), + 'options' => array( + 'none' => __('None', 'siteorigin-panels'), + 'ID' => __('Post ID', 'siteorigin-panels'), + 'author' => __('Author', 'siteorigin-panels'), + 'name' => __('Name', 'siteorigin-panels'), + 'name' => __('Name', 'siteorigin-panels'), + 'date' => __('Date', 'siteorigin-panels'), + 'modified' => __('Modified', 'siteorigin-panels'), + 'parent' => __('Parent', 'siteorigin-panels'), + 'rand' => __('Random', 'siteorigin-panels'), + 'comment_count' => __('Comment Count', 'siteorigin-panels'), + 'menu_order' => __('Menu Order', 'siteorigin-panels'), + ) + ); + + $this->form_args['query_order'] = array( + 'type' => 'select', + 'label' => __('Order', 'siteorigin-panels'), + 'options' => array( + 'ASC' => __('Ascending', 'siteorigin-panels'), + 'DESC' => __('Descending', 'siteorigin-panels'), + ) + ); + + $this->form_args['query_sticky'] = array( + 'type' => 'select', + 'label' => __('Sticky Posts', 'siteorigin-panels'), + 'options' => array( + '' => __('Default', 'siteorigin-panels'), + 'ignore' => __('Ignore Sticky', 'siteorigin-panels'), + 'exclude' => __('Exclude Sticky', 'siteorigin-panels'), + 'only' => __('Only Sticky', 'siteorigin-panels'), + ) + ); + + $this->form_args['query_additional'] = array( + 'type' => 'text', + 'label' => __('Additional Arguments', 'siteorigin-panels'), + 'description' => sprintf(__('Additional query arguments. See query_posts.', 'siteorigin-panels'), 'http://codex.wordpress.org/Function_Reference/query_posts'), + ); + } + + /** + * Get all the posts for the current query + * + * @param $instance + * @return WP_Query + */ + static function get_query_posts($instance) { + $query_args = array(); + foreach($instance as $k => $v){ + if(strpos($k, 'query_') === 0) { + $query_args[preg_replace('/query_/', '', $k, 1)] = $v; + } + } + $query = $query_args; + unset($query['additional']); + unset($query['sticky']); + + // Add the additional arguments + $query = wp_parse_args($query_args['additional'], $query); + + // Add the sticky posts if required + switch($query_args['sticky']){ + case 'ignore' : + $query['ignore_sticky_posts'] = 1; + break; + case 'only' : + $query['post__in'] = get_option( 'sticky_posts' ); + break; + case 'exclude' : + $query['post__not_in'] = get_option( 'sticky_posts' ); + break; + } + + // Add the current page + global $wp_query; + $query['paged'] = $wp_query->get('paged'); + + return new WP_Query($query); + } +} + +// All the standard bundled widgets + +/** + * A gallery widget + * + * Class SiteOrigin_Panels_Widgets_Gallery + */ +class SiteOrigin_Panels_Widgets_Gallery extends WP_Widget { + function __construct() { + parent::__construct( + 'siteorigin-panels-gallery', + __( 'Gallery (PB)', 'siteorigin-panels' ), + array( + 'description' => __( 'Displays a gallery.', 'siteorigin-panels' ), + ) + ); + } + + function widget( $args, $instance ) { + echo $args['before_widget']; + + $shortcode_attr = array(); + foreach($instance as $k => $v){ + if(empty($v)) continue; + $shortcode_attr[] = $k.'="'.esc_attr($v).'"'; + } + + echo do_shortcode('[gallery '.implode(' ', $shortcode_attr).']'); + + echo $args['after_widget']; + } + + function update( $new, $old ) { + return $new; + } + + function form( $instance ) { + global $_wp_additional_image_sizes; + + $types = apply_filters('siteorigin_panels_gallery_types', array()); + + $instance = wp_parse_args($instance, array( + 'ids' => '', + 'size' => apply_filters('siteorigin_panels_gallery_default_size', ''), + 'type' => apply_filters('siteorigin_panels_gallery_default_type', ''), + 'columns' => 3, + 'link' => '', + + )); + + ?> +

+ + + +

+

+ +

+ +

+ + +

+ +

+ + +

+ +

+ + +

+ +

+ + +

+ + __( 'Displays a simple image.', 'siteorigin-panels' ), + ) + ); + } + + /** + * @param array $args + * @param array $instance + */ + function widget( $args, $instance ) { + echo $args['before_widget']; + if(!empty($instance['href'])) echo ''; + echo ''; + if(!empty($instance['href'])) echo ''; + echo $args['after_widget']; + } + + function update($new, $old){ + $new = wp_parse_args($new, array( + 'src' => '', + 'href' => '', + )); + return $new; + } + + function form( $instance ) { + $instance = wp_parse_args($instance, array( + 'src' => '', + 'href' => '', + )); + + ?> +

+ + +

+

+ + +

+ __( 'Embeds a video.', 'siteorigin-panels' ), + ) + ); + } + + /** + * Display the video using + * + * @param array $args + * @param array $instance + */ + function widget( $args, $instance ) { + $embed = new WP_Embed(); + + if(!wp_script_is('fitvids')) + wp_enqueue_script('fitvids', plugin_dir_url(SITEORIGIN_PANELS_BASE_FILE).'widgets/js/jquery.fitvids.js', array('jquery'), SITEORIGIN_PANELS_VERSION); + + if(!wp_script_is('siteorigin-panels-embedded-video')) + wp_enqueue_script('siteorigin-panels-embedded-video', plugin_dir_url(SITEORIGIN_PANELS_BASE_FILE).'widgets/js/embedded-video.js', array('jquery', 'fitvids'), SITEORIGIN_PANELS_VERSION); + + echo $args['before_widget']; + ?>
run_shortcode( '[embed]' . $instance['video'] . '[/embed]' ) ?>
'', + ) ) + + ?> +

+ + +

+ __( 'A self hosted video player.', 'siteorigin-panels' ), + ) + ); + } + + function widget( $args, $instance ) { + if (empty($instance['url'])) return; + static $video_widget_id = 1; + + $instance = wp_parse_args($instance, array( + 'url' => '', + 'poster' => '', + 'skin' => 'siteorigin', + 'ratio' => 1.777, + 'autoplay' => false, + )); + + // Enqueue jPlayer scripts and intializer + wp_enqueue_script( 'siteorigin-panels-video-jplayer', plugin_dir_url(SITEORIGIN_PANELS_BASE_FILE).'video/jplayer/jquery.jplayer.min.js', array('jquery'), SITEORIGIN_PANELS_VERSION, true); + wp_enqueue_script( 'siteorigin-panels-video', plugin_dir_url(SITEORIGIN_PANELS_BASE_FILE).'video/panels.video.jquery.js', array('jquery'), SITEORIGIN_PANELS_VERSION, true); + + // Enqueue the SiteOrigin jPlayer skin + $skin = sanitize_file_name($instance['skin']); + wp_enqueue_style('siteorigin-panels-video-jplayer-skin', plugin_dir_url(SITEORIGIN_PANELS_BASE_FILE).'video/jplayer/skins/'.$skin.'/jplayer.'.$skin.'.css', array(), SITEORIGIN_PANELS_VERSION); + + $file = $instance['url']; + $poster = !empty($instance['poster']) ? $instance['poster'] : plugin_dir_url(SITEORIGIN_PANELS_BASE_FILE).'video/poster.jpg'; + $instance['ratio'] = floatval($instance['ratio']); + if(empty($instance['ratio'])) $instance['ratio'] = 1.777; + + echo $args['before_widget']; + + ?> + + '', + 'poster' => '', + 'skin' => 'siteorigin', + 'ratio' => 1.777, + 'autoplay' => false, + )); + + ?> +

+ + +

+

+ + + +

+

+ + +

+

+ + + +

+

+ +

+ '', + 'src' => '', + 'poster' => plugin_dir_url(SITEORIGIN_PANELS_BASE_FILE).'video/poster.jpg', + 'skin' => 'siteorigin', + 'ratio' => 1.777, + 'autoplay' => 0, + ), $atts ); + + if(!empty($instance['src'])) $instance['url'] = $instance['src']; + if(empty($instance['url'])) return; + + ob_start(); + the_widget('SiteOrigin_Panels_Widgets_Video', $instance); + return ob_get_clean(); + +} +add_shortcode('self_video', 'siteorigin_panels_video_shortcode'); + +/** + * Register the widgets. + */ +function siteorigin_panels_widgets_init(){ + register_widget('SiteOrigin_Panels_Widgets_Gallery'); + register_widget('SiteOrigin_Panels_Widgets_Image'); + register_widget('SiteOrigin_Panels_Widgets_EmbeddedVideo'); + register_widget('SiteOrigin_Panels_Widgets_Video'); +} +add_action('widgets_init', 'siteorigin_panels_widgets_init'); \ No newline at end of file diff --git a/widgets/widgets/animated-image/animated-image.php b/widgets/widgets/animated-image/animated-image.php new file mode 100644 index 000000000..8fb142b5c --- /dev/null +++ b/widgets/widgets/animated-image/animated-image.php @@ -0,0 +1,41 @@ + __('An image that animates in when it enters the screen.', 'siteorigin-panels'), + 'default_style' => 'simple', + ), + array(), + array( + 'image' => array( + 'type' => 'text', + 'label' => __('Image URL', 'siteorigin-panels'), + ), + 'animation' => array( + 'type' => 'select', + 'label' => __('Animation', 'siteorigin-panels'), + 'options' => array( + 'fade' => __('Fade In', 'siteorigin-panels'), + 'slide-up' => __('Slide Up', 'siteorigin-panels'), + 'slide-down' => __('Slide Down', 'siteorigin-panels'), + 'slide-left' => __('Slide Left', 'siteorigin-panels'), + 'slide-right' => __('Slide Right', 'siteorigin-panels'), + ) + ), + ) + ); + } + + function enqueue_scripts(){ + static $enqueued = false; + if(!$enqueued) { + wp_enqueue_script('siteorigin-widgets-'.$this->origin_id.'-onscreen', plugin_dir_url(__FILE__).'js/onscreen.js', array('jquery'), SITEORIGIN_PANELS_VERSION); + wp_enqueue_script('siteorigin-widgets-'.$this->origin_id, plugin_dir_url(__FILE__).'js/main.js', array('jquery'), SITEORIGIN_PANELS_VERSION); + $enqueued = true; + } + + } +} \ No newline at end of file diff --git a/widgets/widgets/animated-image/js/main.js b/widgets/widgets/animated-image/js/main.js new file mode 100644 index 000000000..543a3acd8 --- /dev/null +++ b/widgets/widgets/animated-image/js/main.js @@ -0,0 +1,42 @@ +jQuery(function($){ + var theInterval = setInterval(function(){ + // Check if any images that weren't visible, now are + $('.origin-widget-animated-image img').not('.animated').filter(':onScreen').each(function(){ + var $$ = $(this); + // TODO wait for images + if(!$$.get(0).complete) return; + + $$.addClass('animated'); + + setTimeout(function(){ + var a = $$.data('animation'); + + if(a == 'fade') { + $$.css('visibility', 'visible'); + $$.hide().fadeIn(750); + } + else { + var offset; + if(a == 'slide-up') offset = {top : 25, left: 0}; + else if(a == 'slide-down') offset = {top : -25, left: 0}; + else if(a == 'slide-left') offset = {top : 0, left: 25}; + else if(a == 'slide-right') offset = {top : 0, left: -25}; + + var $a = $$.clone().insertAfter($$).css({ + 'visibility' : 'visible', + 'opacity' : 0, + 'position' : 'absolute', + 'top' : $$.position().top + offset.top, + 'left' : $$.position().left + offset.left, + 'width' : $$.width(), + 'height' : $$.height() + }).animate({top: $$.position().top, left: $$.position().left, opacity: 1}, 750, function(){$(this).remove(); $$.css('visibility', 'visible');}); + } + }, 750); + } ); + + if($('.origin-widget-animated-image img').not('.animated').length == 0) { + clearInterval(theInterval); + } + }, 500); +}); \ No newline at end of file diff --git a/widgets/widgets/animated-image/js/onscreen.js b/widgets/widgets/animated-image/js/onscreen.js new file mode 100644 index 000000000..2999e1cc4 --- /dev/null +++ b/widgets/widgets/animated-image/js/onscreen.js @@ -0,0 +1,24 @@ +// onScreen jQuery plugin v0.2.1 +// (c) 2011 Ben Pickles +// +// http://benpickles.github.com/onScreen +// +// Released under MIT license. +;(function($) { + $.expr[":"].onScreen = function(elem) { + // The viewport position + var $window = $(window); + var viewport_top = $window.scrollTop(); + var viewport_height = $window.height(); + var viewport_bottom = viewport_top + viewport_height; + + // Element position + var $elem = $(elem); + var top = $elem.offset().top; + var height = $elem.height(); + var bottom = top + height; + + return (top >= viewport_top && bottom + 30 < viewport_bottom); + + } +})(jQuery); \ No newline at end of file diff --git a/widgets/widgets/animated-image/tpl/default.php b/widgets/widgets/animated-image/tpl/default.php new file mode 100644 index 000000000..65a6734e9 --- /dev/null +++ b/widgets/widgets/animated-image/tpl/default.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/widgets/widgets/button/button.php b/widgets/widgets/button/button.php new file mode 100644 index 000000000..6462e0932 --- /dev/null +++ b/widgets/widgets/button/button.php @@ -0,0 +1,43 @@ + __('A simple button', 'siteorigin-panels'), + 'default_style' => 'simple', + ), + array(), + array( + 'text' => array( + 'type' => 'text', + 'label' => __('Text', 'siteorigin-panels'), + ), + 'url' => array( + 'type' => 'text', + 'label' => __('Destination URL', 'siteorigin-panels'), + ), + 'new_window' => array( + 'type' => 'checkbox', + 'label' => __('Open In New Window', 'siteorigin-panels'), + ), + 'align' => array( + 'type' => 'select', + 'label' => __('Button Alignment', 'siteorigin-panels'), + 'options' => array( + 'left' => __('Left', 'siteorigin-panels'), + 'right' => __('Right', 'siteorigin-panels'), + 'center' => __('Center', 'siteorigin-panels'), + 'justify' => __('Justify', 'siteorigin-panels'), + ) + ), + ) + ); + } + + function widget_classes($classes, $instance) { + $classes[] = 'align-'.(empty($instance['align']) ? 'none' : $instance['align']); + return $classes; + } +} \ No newline at end of file diff --git a/widgets/widgets/button/presets/simple.php b/widgets/widgets/button/presets/simple.php new file mode 100644 index 000000000..3681fd1a9 --- /dev/null +++ b/widgets/widgets/button/presets/simple.php @@ -0,0 +1,54 @@ + array( + 'background_color' => '#F5F5F5', + 'text_darken' => '45%', + 'text_shadow' => '30%', + ), + + 'charcoal' => array( + 'background_color' => '#999999', + ), + + 'pink' => array( + 'background_color' => '#f6778c', + ), + + 'orange' => array( + 'background_color' => '#fece62', + 'text_darken' => '37.5%', + ), + + 'green' => array( + 'background_color' => '#b7d870', + 'text_darken' => '37.5%', + ), + + 'blue' => array( + 'background_color' => '#91dbf6', + 'text_darken' => '37.5%', + ), + + 'purple' => array( + 'background_color' => '#e5b2dd', + ), + + 'turquoise' => array( + 'background_color' => '#a2ebf1', + ), + + 'slate' => array( + 'background_color' => '#a5b7c3', + 'text_darken' => '45%', + 'text_shadow' => '15%', + 'inset_highlight' => '10%', + ), + + 'black' => array( + 'background_color' => '#333333', + 'text_darken' => '-75%', + 'text_shadow' => '-30%', + 'inset_highlight' => '17%', + ), +); diff --git a/widgets/widgets/button/styles/simple.css b/widgets/widgets/button/styles/simple.css new file mode 100644 index 000000000..84a13a6a6 --- /dev/null +++ b/widgets/widgets/button/styles/simple.css @@ -0,0 +1,71 @@ +/* +Name: Simple +Template: simple +Author: Greg Priday +Author URI: http://siteorigin.com/ +*/ +a { + display: inline-block; + padding: 10px 45px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + color: lumdarken(#f5f5f5, 35%); + font-size: 0.875em; + font-family: inherit; + font-weight: 500; + text-decoration: none; + text-shadow: 0 1px 0 lumlighten(#f5f5f5, 20%); + text-align: center; + -webkit-box-shadow: inset 0 1px 0 lumlighten(#f5f5f5, 10%), 0 1px 2px rgba(0,0,0,0.1); + -moz-box-shadow: inset 0 1px 0 lumlighten(#f5f5f5, 10%), 0 1px 2px rgba(0,0,0,0.1); + box-shadow: inset 0 1px 0 lumlighten(#f5f5f5, 10%), 0 1px 2px rgba(0,0,0,0.1); + background: #f5f5f5; + background: -webkit-gradient(linear, left bottom, left top, color-stop(0, lumdarken(#f5f5f5, 3.5)), color-stop(1, lumlighten(#f5f5f5, 3.5))); + background: -ms-linear-gradient(bottom, lumdarken(#f5f5f5, 3.5), lumlighten(#f5f5f5, 3.5)); + background: -moz-linear-gradient(center bottom, lumdarken(#f5f5f5, 3.5) 0%, lumlighten(#f5f5f5, 3.5) 100%); + background: -o-linear-gradient(lumlighten(#f5f5f5, 3.5), lumdarken(#f5f5f5, 3.5)); + border-top: solid 1px lumlighten(lumdarken(#f5f5f5, 18), 4); + border-left: solid 1px lumdarken(#f5f5f5, 18); + border-right: solid 1px lumdarken(#f5f5f5, 18); + border-bottom: solid 1px lumdarken(lumdarken(#f5f5f5, 18), 4); +} +a:hover { + background: lumlighten(#f5f5f5, 1.75%); + background: -webkit-gradient(linear, left bottom, left top, color-stop(0, lumdarken(lumlighten(#f5f5f5, 1.75%), 3.5)), color-stop(1, lumlighten(lumlighten(#f5f5f5, 1.75%), 3.5))); + background: -ms-linear-gradient(bottom, lumdarken(lumlighten(#f5f5f5, 1.75%), 3.5), lumlighten(lumlighten(#f5f5f5, 1.75%), 3.5)); + background: -moz-linear-gradient(center bottom, lumdarken(lumlighten(#f5f5f5, 1.75%), 3.5) 0%, lumlighten(lumlighten(#f5f5f5, 1.75%), 3.5) 100%); + background: -o-linear-gradient(lumlighten(lumlighten(#f5f5f5, 1.75%), 3.5), lumdarken(lumlighten(#f5f5f5, 1.75%), 3.5)); + border-top: solid 1px lumlighten(lumdarken(lumlighten(#f5f5f5, 1.75%), 18), 4); + border-left: solid 1px lumdarken(lumlighten(#f5f5f5, 1.75%), 18); + border-right: solid 1px lumdarken(lumlighten(#f5f5f5, 1.75%), 18); + border-bottom: solid 1px lumdarken(lumdarken(lumlighten(#f5f5f5, 1.75%), 18), 4); +} +a:active { + background: lumdarken(#f5f5f5, 1.75); + background: -webkit-gradient(linear, left bottom, left top, color-stop(0, lumdarken(lumdarken(#f5f5f5, 1.75), -2)), color-stop(1, lumlighten(lumdarken(#f5f5f5, 1.75), -2))); + background: -ms-linear-gradient(bottom, lumdarken(lumdarken(#f5f5f5, 1.75), -2), lumlighten(lumdarken(#f5f5f5, 1.75), -2)); + background: -moz-linear-gradient(center bottom, lumdarken(lumdarken(#f5f5f5, 1.75), -2) 0%, lumlighten(lumdarken(#f5f5f5, 1.75), -2) 100%); + background: -o-linear-gradient(lumlighten(lumdarken(#f5f5f5, 1.75), -2), lumdarken(lumdarken(#f5f5f5, 1.75), -2)); + border-top: solid 1px lumlighten(lumdarken(lumdarken(#f5f5f5, 1.75), 18), 4); + border-left: solid 1px lumdarken(lumdarken(#f5f5f5, 1.75), 18); + border-right: solid 1px lumdarken(lumdarken(#f5f5f5, 1.75), 18); + border-bottom: solid 1px lumdarken(lumdarken(lumdarken(#f5f5f5, 1.75), 18), 4); + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + padding-top: 11px; + padding-bottom: 9px; +} +.align-left { + text-align: left; +} +.align-right { + text-align: right; +} +.align-center { + text-align: center; +} +.align-justify a { + display: block; +} diff --git a/widgets/widgets/button/styles/simple.less b/widgets/widgets/button/styles/simple.less new file mode 100644 index 000000000..9894bd409 --- /dev/null +++ b/widgets/widgets/button/styles/simple.less @@ -0,0 +1,78 @@ +/* +Name: Simple +Template: simple +Author: Greg Priday +Author URI: http://siteorigin.com/ +*/ + +@background_color: #F5F5F5; + +@text_darken: 35%; +@inset_highlight: 10%; +@drop_shadow: 0.1; +@text_shadow: 20%; + +@rounded: 4px; +@font_size: 0.875em; + +@padding_top: 10px; +@padding_sides: 45px; + +@font_family: inherit; +@font_weight: 500; + +@import "../../../less/mixins"; + +& { + + a { + display:inline-block; + padding: @padding_top @padding_sides; + + .rounded(@rounded); + + color: lumdarken(@background_color, @text_darken); + + font-size: @font_size; + font-family: @font_family; + font-weight: @font_weight; + text-decoration:none; + + text-shadow: 0 1px 0 lumlighten(@background_color, @text_shadow); + text-align: center; + + @inset_highlight_color: lumlighten(@background_color, @inset_highlight); + .box-shadow(~"inset 0 1px 0 @{inset_highlight_color}, 0 1px 2px rgba(0,0,0,@{drop_shadow})"); + .button-style(@background_color); + + &:hover { + .button-style( lumlighten(@background_color, 1.75%) ); + } + + &:active { + .button-style(lumdarken(@background_color, 1.75), -2); + .box-shadow(none); + padding-top: @padding_top + 1; + padding-bottom: @padding_top - 1; + } + } + + &.align-left { + text-align: left; + } + + &.align-right { + text-align: right; + } + + &.align-center { + text-align: center; + } + + &.align-justify { + a { + display: block; + } + } + +} \ No newline at end of file diff --git a/widgets/widgets/button/tpl/simple.php b/widgets/widgets/button/tpl/simple.php new file mode 100644 index 000000000..e30208566 --- /dev/null +++ b/widgets/widgets/button/tpl/simple.php @@ -0,0 +1,3 @@ +> + + \ No newline at end of file diff --git a/widgets/widgets/call-to-action/call-to-action.php b/widgets/widgets/call-to-action/call-to-action.php new file mode 100644 index 000000000..310406ae4 --- /dev/null +++ b/widgets/widgets/call-to-action/call-to-action.php @@ -0,0 +1,39 @@ + __('A Call to Action block', 'siteorigin-panels'), + 'default_style' => 'simple', + ), + array(), + array( + 'title' => array( + 'type' => 'text', + 'label' => __('Title', 'siteorigin-panels'), + ), + 'subtitle' => array( + 'type' => 'text', + 'label' => __('Sub Title', 'siteorigin-panels'), + ), + 'button_text' => array( + 'type' => 'text', + 'label' => __('Button Text', 'siteorigin-panels'), + ), + 'button_url' => array( + 'type' => 'text', + 'label' => __('Button URL', 'siteorigin-panels'), + ), + 'button_new_window' => array( + 'type' => 'checkbox', + 'label' => __('Open In New Window', 'siteorigin-panels'), + ), + ) + ); + + // We need the button style + $this->add_sub_widget('button', __('Button', 'siteorigin-panels'), 'SiteOrigin_Panels_Widget_Button'); + } +} \ No newline at end of file diff --git a/widgets/widgets/call-to-action/presets/simple.php b/widgets/widgets/call-to-action/presets/simple.php new file mode 100644 index 000000000..2a3b50e71 --- /dev/null +++ b/widgets/widgets/call-to-action/presets/simple.php @@ -0,0 +1,24 @@ + array( + 'texture' => 'light-dashed', + 'title_text_color' => '#333333', + 'subtitle_text_color' => '#555555', + ), + + 'dark_dashed' => array( + 'texture' => 'dark-dashed', + 'title_text_color' => '#FFFFFF', + 'subtitle_text_color' => '#CCCCCC', + ), + + 'clean' => array( + 'background_color' => '#FCFCFC', + 'texture' => 'none', + 'title_text_color' => '#333333', + 'subtitle_text_color' => '#555555', + 'rounding' => '0px', + 'borders' => '1px solid #D0D0D0', + ), +); diff --git a/widgets/widgets/call-to-action/styles/simple.less b/widgets/widgets/call-to-action/styles/simple.less new file mode 100644 index 000000000..88740167f --- /dev/null +++ b/widgets/widgets/call-to-action/styles/simple.less @@ -0,0 +1,70 @@ +/* +Name: Simple +Template: simple +Author: Greg Priday +Author URI: http://siteorigin.com/ +*/ + +@import "../../../less/mixins"; + +/* Everything for the background */ + +@background_color: #F6F6F6; +@padding: 2em; +@rounding: 4px; +@shadow: 0.1; +@borders: 1px solid #E0E0E0; + +/* Everything for the Title */ + +@title_text_color: #EFEFEF; +@title_font_weight: auto; +@subtitle_text_color: #CCCCCC; +@subtitle_font_weight: auto; +@texture: dark-dashed; + +& { + .clearfix(); + padding: @padding; + position: relative; + .rounded(@rounding); + + background: texture(@texture, @background_color); + .box-shadow(0 1px 2px rgba(0,0,0,@shadow)); + border: @borders; + + .title { + line-height: 1.6em; + margin: 0; + color: @title_text_color; + font-weight: @title_font_weight; + } + + .subtitle { + line-height: 1.25em; + margin: 0; + color: @subtitle_text_color; + font-weight: @subtitle_font_weight; + } + + .origin-widget-button { + position: absolute; + display: block; + top: 50%; + right: 2em; + margin-top: -22px; + } +} + +@media (max-width:680px) { + & { + .origin-widget-button { + position: static; + margin-top: 2em; + + a { + display: block; + } + } + } +} \ No newline at end of file diff --git a/widgets/widgets/call-to-action/tpl/simple.php b/widgets/widgets/call-to-action/tpl/simple.php new file mode 100644 index 000000000..d34737582 --- /dev/null +++ b/widgets/widgets/call-to-action/tpl/simple.php @@ -0,0 +1,3 @@ +

+
+sub_widget('button', array('text' => $instance['button_text'], 'url' => $instance['button_url'], 'new_window' => $instance['button_new_window'], 'origin_style' => $instance['origin_style_button'])) ?> \ No newline at end of file diff --git a/widgets/widgets/list/list.php b/widgets/widgets/list/list.php new file mode 100644 index 000000000..25691b512 --- /dev/null +++ b/widgets/widgets/list/list.php @@ -0,0 +1,35 @@ + __('Displays a bullet list of elements', 'siteorigin-panels'), + 'default_style' => 'simple', + ), + array(), + array( + 'title' => array( + 'type' => 'text', + 'label' => __('Title', 'siteorigin-panels'), + ), + 'text' => array( + 'type' => 'textarea', + 'label' => __('Text', 'siteorigin-panels'), + 'description' => __('Start each new point with an asterisk (*)', 'siteorigin-panels'), + ), + ) + ); + } + + static function create_list($text){ + // Add the list items + $text = preg_replace( "/\*+(.*)?/i", "
  • $1
", $text ); + $text = preg_replace( "/(\<\/ul\>\n(.*)\*)+/", "", $text ); + $text = wpautop( $text ); + + // Return sanitized version of the list + return wp_kses_post($text); + } +} \ No newline at end of file diff --git a/widgets/widgets/list/presets/simple.php b/widgets/widgets/list/presets/simple.php new file mode 100644 index 000000000..1ac61fa03 --- /dev/null +++ b/widgets/widgets/list/presets/simple.php @@ -0,0 +1,39 @@ + array( + 'image' => 'charcoal', + ), + + 'pink' => array( + 'image' => 'pink', + ), + + 'orange' => array( + 'image' => 'orange', + ), + + 'green' => array( + 'image' => 'green', + ), + + 'blue' => array( + 'image' => 'blue', + ), + + 'purple' => array( + 'image' => 'purple', + ), + + 'turquoise' => array( + 'image' => 'turquoise', + ), + + 'slate' => array( + 'image' => 'slate', + ), + + 'black' => array( + 'image' => 'black', + ), +); diff --git a/widgets/widgets/list/styles/simple.less b/widgets/widgets/list/styles/simple.less new file mode 100644 index 000000000..67f6f7b14 --- /dev/null +++ b/widgets/widgets/list/styles/simple.less @@ -0,0 +1,23 @@ +/* +Name: Simple +Template: simple +Author: Greg Priday +Author URI: http://siteorigin.com/ +*/ + +@import "../../../less/mixins"; + +@image: white; + +ul { + margin: 0; + padding: 0; + + li { + @image_url: 'checks/@{image}.png'; + list-style: url(widgetimage(@image_url)); + line-height: 2em; + margin-left: 24px; + padding-left: 6px; + } +} \ No newline at end of file diff --git a/widgets/widgets/list/tpl/simple.php b/widgets/widgets/list/tpl/simple.php new file mode 100644 index 000000000..e8d448622 --- /dev/null +++ b/widgets/widgets/list/tpl/simple.php @@ -0,0 +1,5 @@ +create_list($instance['text']); \ No newline at end of file diff --git a/widgets/widgets/price-box/presets/simple.php b/widgets/widgets/price-box/presets/simple.php new file mode 100644 index 000000000..8410669a8 --- /dev/null +++ b/widgets/widgets/price-box/presets/simple.php @@ -0,0 +1,8 @@ + array( + 'background_color' => '#FCFCFC', + 'borders' => '1px solid #D0D0D0', + ), +); diff --git a/widgets/widgets/price-box/price-box.php b/widgets/widgets/price-box/price-box.php new file mode 100644 index 000000000..20435d75b --- /dev/null +++ b/widgets/widgets/price-box/price-box.php @@ -0,0 +1,52 @@ + __('Displays a bullet list of elements', 'siteorigin-panels'), + 'default_style' => 'simple', + ), + array(), + array( + 'title' => array( + 'type' => 'text', + 'label' => __('Title', 'siteorigin-panels'), + ), + 'price' => array( + 'type' => 'text', + 'label' => __('Price', 'siteorigin-panels'), + ), + 'per' => array( + 'type' => 'text', + 'label' => __('Per', 'siteorigin-panels'), + ), + 'information' => array( + 'type' => 'text', + 'label' => __('Information Text', 'siteorigin-panels'), + ), + 'features' => array( + 'type' => 'textarea', + 'label' => __('Features Text', 'siteorigin-panels'), + 'description' => __('Start each new point with an asterisk (*)', 'siteorigin-panels'), + ), + 'button_text' => array( + 'type' => 'text', + 'label' => __('Button Text', 'siteorigin-panels'), + ), + 'button_url' => array( + 'type' => 'text', + 'label' => __('Button URL', 'siteorigin-panels'), + ), + 'button_new_window' => array( + 'type' => 'checkbox', + 'label' => __('Open In New Window', 'siteorigin-panels'), + ), + ) + ); + + $this->add_sub_widget('button', __('Button', 'siteorigin-panels'), 'SiteOrigin_Panels_Widget_Button'); + $this->add_sub_widget('list', __('Feature List', 'siteorigin-panels'), 'SiteOrigin_Panels_Widget_List'); + } +} \ No newline at end of file diff --git a/widgets/widgets/price-box/styles/simple.less b/widgets/widgets/price-box/styles/simple.less new file mode 100644 index 000000000..a88e0d1dc --- /dev/null +++ b/widgets/widgets/price-box/styles/simple.less @@ -0,0 +1,38 @@ +/* +Name: Simple +Template: simple +Author: Greg Priday +Author URI: http://siteorigin.com/ +*/ + +@import "../../../less/mixins"; +@borders: 1px solid #E0E0E0; +@background_color: #FCFCFC; +@box_shadow: 0 1px 2px rgba(0,0,0,0.1); + +& { + padding: 25px 20px; + border: @borders; + background: @background_color; + .box-shadow(@box_shadow); + + h2 { + line-height: 1; + margin: 0 0 0.25em 0; + text-align: center; + } + + h4 { + line-height: 1; + margin: 0 0 1em 0; + text-align: center; + + span { + font-size: 0.6em; + } + } + + .origin-widget-button { + margin-top: 30px; + } +} \ No newline at end of file diff --git a/widgets/widgets/price-box/tpl/simple.php b/widgets/widgets/price-box/tpl/simple.php new file mode 100644 index 000000000..cf10bdc12 --- /dev/null +++ b/widgets/widgets/price-box/tpl/simple.php @@ -0,0 +1,16 @@ +

+

/

+

+ +sub_widget('list', array('title' => '', 'text' => $instance['features'], 'origin_style' => $instance['origin_style_list'])); +$this->sub_widget('button', array( + 'text' => $instance['button_text'], + 'url' => $instance['button_url'], + 'align' => 'center', + 'origin_style' => $instance['origin_style_button'], + 'new_window' => !empty($instance['button_new_window']) +)); + +?> \ No newline at end of file diff --git a/widgets/widgets/testimonial/presets/simple.php b/widgets/widgets/testimonial/presets/simple.php new file mode 100644 index 000000000..7de577889 --- /dev/null +++ b/widgets/widgets/testimonial/presets/simple.php @@ -0,0 +1,6 @@ + array( + ), +); diff --git a/widgets/widgets/testimonial/styles/simple.less b/widgets/widgets/testimonial/styles/simple.less new file mode 100644 index 000000000..923742383 --- /dev/null +++ b/widgets/widgets/testimonial/styles/simple.less @@ -0,0 +1,61 @@ +/* +Name: Simple +Template: simple +Author: Greg Priday +Author URI: http://siteorigin.com/ +*/ + +@import "../../../less/mixins"; + +@borders: 1px solid #D0D0D0; +@background_color: #FCFCFC; + +& { + background: @background_color; + border: @borders; + padding: 20px; + .clearfix(); + .box-shadow(0 1px 2px rgba(0,0,0,0.1)); + + h5.testimonial-name { + margin: 1em 0 5px 0; + line-height: 1; + color: #333; + clear: none; + + a { + color: inherit; + } + } + + small.testimonial-location { + font-size: 0.9em; + display: block; + margin: 0; + line-height: 1; + color: #999; + clear: none; + } + + .text { + margin-left: 80px; + font-size: 0.95em; + + p:first-child { + margin-top: 0; + } + } + + .testimonial-image-wrapper { + .rounded(4px); + float: left; + width: 60px; + height: 60px; + overflow: hidden; + background: #333; + img { + width: 100%; + height: auto; + } + } +} \ No newline at end of file diff --git a/widgets/widgets/testimonial/testimonial.php b/widgets/widgets/testimonial/testimonial.php new file mode 100644 index 000000000..8f72f0b91 --- /dev/null +++ b/widgets/widgets/testimonial/testimonial.php @@ -0,0 +1,40 @@ + __('Displays a bullet list of elements', 'siteorigin-panels'), + 'default_style' => 'simple', + ), + array(), + array( + 'name' => array( + 'type' => 'text', + 'label' => __('Name', 'siteorigin-panels'), + ), + 'location' => array( + 'type' => 'text', + 'label' => __('Location', 'siteorigin-panels'), + ), + 'image' => array( + 'type' => 'text', + 'label' => __('Image', 'siteorigin-panels'), + ), + 'text' => array( + 'type' => 'textarea', + 'label' => __('Text', 'siteorigin-panels'), + ), + 'url' => array( + 'type' => 'text', + 'label' => __('URL', 'siteorigin-panels'), + ), + 'new_window' => array( + 'type' => 'checkbox', + 'label' => __('Open In New Window', 'siteorigin-panels'), + ), + ) + ); + } +} \ No newline at end of file diff --git a/widgets/widgets/testimonial/tpl/simple.php b/widgets/widgets/testimonial/tpl/simple.php new file mode 100644 index 000000000..31a9203b5 --- /dev/null +++ b/widgets/widgets/testimonial/tpl/simple.php @@ -0,0 +1,13 @@ +
+ +
+ +
+ +
+ + + +
+ +
\ No newline at end of file