diff options
Diffstat (limited to 'plugins/Customizer')
90 files changed, 16903 insertions, 0 deletions
diff --git a/plugins/Customizer/Assets/css/README.md b/plugins/Customizer/Assets/css/README.md new file mode 100644 index 00000000..7b7416b3 --- /dev/null +++ b/plugins/Customizer/Assets/css/README.md @@ -0,0 +1,39 @@ + +Preset Themes that come with Customizer +-------- + +<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/Clemson_Tigers_logo.svg/2000px-Clemson_Tigers_logo.svg.png" height="20"> **Clemson Theme (Go Tigers!):** + +![image](https://user-images.githubusercontent.com/26339368/48094361-fccc3c80-e1df-11e8-9695-6b9c510aa522.png) + +:octocat: Github: + +![image](https://user-images.githubusercontent.com/26339368/47761386-8636b880-dc8e-11e8-9b6e-c46e7b5dcc44.png) + +:milky_way: Galaxy: + +![image](https://user-images.githubusercontent.com/26339368/47761350-68695380-dc8e-11e8-9e87-a9471e5e1adf.png) + + +:partly_sunny: Breathe: + +![image](https://user-images.githubusercontent.com/26339368/47761312-47086780-dc8e-11e8-9460-5b1ce4b54d5e.png) + +:blue_book: Blueboard + +![image](https://user-images.githubusercontent.com/26339368/49310748-29a50400-f4ad-11e8-9734-eca2e5a558fc.png) + +:sparkles: Material + +![image](https://user-images.githubusercontent.com/26339368/49310723-1003bc80-f4ad-11e8-8c03-8390ecc78d20.png) + +:8ball: KindaDark + +<img src="https://i.imgur.com/Rg9RYwf.png" /> +<br/> + + + +## How to add your own css + +To add your own custom css files to Customizer, simply place your files in the `DATA_DIR . '/files/customizer/themes` folder. If that folder doesn't exist, create it, and then place your files in there. This will retain your custom themes through upgrades of both Kanboard or Customizer. diff --git a/plugins/Customizer/Assets/css/customizer.css b/plugins/Customizer/Assets/css/customizer.css new file mode 100644 index 00000000..9423a4d5 --- /dev/null +++ b/plugins/Customizer/Assets/css/customizer.css @@ -0,0 +1,316 @@ +/* KANBOARD PLUGIN - CUSTOMIZER CSS FILE */ + +/*------ PAGE HEADER ------*/ +.logo > a > img {vertical-align:middle;} /* This aligns the logo certically to the text */ + +.logo a { + opacity:1.0; +} + +/*------ LOGIN PAGE-SPECIFIC STYLES MOVED TO logintop.php SO THEY WON'T AFFECT OTHER PARTS OF KANBOARD ------*/ + + +/*------ PLUGIN SETTINGS PAGE ------*/ +.form-help {margin-top: 5px;} /* This gives line spacing above the help text */ + +code { + margin: 0; + /*padding: 2px 0.4em;*/ + background-color: rgba(27, 31, 35, 0.32); + border-radius: 3px; + color: #FFF; + font-family: Tahoma; + font-size: 95%!important; + letter-spacing: 1px; +} /* This styles the url link examples */ + +.login-link-block > label { + font-weight: bold; + margin-bottom: 5px; +} /* This is to highlight the title fields */ + +.panel-heading { + margin: -2px -25px; + float: right; + font-size: 1.2em; +} /* This adds a heading area to each section */ + +.panel-title { + margin-top: 0; + font-weight: bold; +} /* This is the title text for the heading area */ + +.links-title { + margin: -2px 25px; +} /* This is the title text for the links heading area, styled uniquely as the type is fieldset */ + +.upload-link { + float: left; + list-style: outside none none; + width: auto; + display: block; + margin: 0px 10px; +} /* This places the upload link on the same line as the remove link */ + +.remove-link { + float: left; + list-style: outside none none; + width: auto; + display: block; + margin: 0px 10px; +} /* This places the upload link on the same line as the remove link */ + +.upload-link > a > i { + color: green; +} /* This colours the upload icon to green */ + +.remove-link > a > i { + color: red; +} /* This colours the upload icon to red */ + +/* Style the buttons that are used to open and close the accordion panel */ +.login-accordion { + font-weight: bold; + background-color: rgba(136,136,136,0.7); + cursor: pointer; + padding: 18px; + width: 100%; + text-align: left; + border: none; + outline: none; + transition: 0.4s; + color: #f5f5f5; + border-radius: 4px; + margin-bottom: 5px; + box-shadow: 0 1px 3px 0px rgba(70, 70, 70, 0.10); +} + +/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */ +.login-accordion:hover { + background-color: #f5f5f5; + color: #777777; + transition: 0.4s; +} + +.current { + background-color: #f5f5f5; + color: #777777; + transition: 0.4s; +} + +/* Style the accordion panel. Note: hidden by default */ +.login-accordian-panel { + max-height: 0; + overflow: hidden; + transition: 0.4s ease-in-out; + opacity: 0; +} + +div.login-accordian-panel.show { + opacity: 1; + max-height: 5000px; +} + +.title-creator { + border-left: 5px solid #333; + padding-left: 8px; +} + +/* +* Short classes +* m - for classes that set margin +* p - for classes that set padding +* t - for classes that set margin-top or padding-top +* b - for classes that set margin-bottom or padding-bottom +* l - for classes that set margin-left or padding-left +* r - for classes that set margin-right or padding-right +* number(5) - for classes that set the margin or padding +*/ + +.mt-5 { + margin-top: 5px; +} + +.mt-10 { + margin-top: 10px; +} + +.mt-15 { + margin-top: 15px; +} + +.mt-20 { + margin-top: 20px; +} + +.mb-5 { + margin-bottom: 5px; +} + +.mb-10 { + margin-bottom: 10px; +} + +.mb-15 { + margin-bottom: 15px; +} + +.mb-20 { + margin-bottom: 20px; +} + +.ml-5 { + margin-left: 5px; +} + +.ml-10 { + margin-left: 10px; +} + +.ml-15 { + margin-left: 15px; +} + +.ml-20 { + margin-left: 20px; +} + +.mr-5 { + margin-right: 5px; +} + +.mr-10 { + margin-right: 10px; +} + +.mr-15 { + margin-right: 15px; +} + +.mr-20 { + margin-right: 20px; +} + +.pt-5 { + padding-top: 5px; +} + +.pt-10 { + padding-top: 10px; +} + +.pt-15 { + padding-top: 15px; +} + +.pt-20 { + padding-top: 20px; +} + +.pb-5 { + padding-bottom: 5px; +} + +.pb-10 { + padding-bottom: 10px; +} + +.pb-15 { + padding-bottom: 15px; +} + +.pb-20 { + padding-bottom: 20px; +} + +.pl-5 { + padding-left: 5px; +} + +.pl-10 { + padding-left: 10px; +} + +.pl-15 { + padding-left: 15px; +} + +.pl-20 { + padding-left: 20px; +} + +.pr-5 { + padding-right: 5px; +} + +.pr-10 { + padding-right: 10px; +} + +.pr-15 { + padding-right: 15px; +} + +.pr-20 { + padding-right: 20px; +} + +.switch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 26px; + width: 26px; + left: 4px; + bottom: 4px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked + .slider { + background-color: #2196F3; +} + +input:focus + .slider { + box-shadow: 0 0 1px #2196F3; +} + +input:checked + .slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); +} + +/* Rounded sliders */ +.slider.round { + border-radius: 34px; +} + +.slider.round:before { + border-radius: 50%; +} diff --git a/plugins/Customizer/Assets/css/theme.css b/plugins/Customizer/Assets/css/theme.css new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/plugins/Customizer/Assets/css/theme.css @@ -0,0 +1 @@ + diff --git a/plugins/Customizer/Assets/css/themes/Blueboard.css b/plugins/Customizer/Assets/css/themes/Blueboard.css new file mode 100644 index 00000000..1fa8097b --- /dev/null +++ b/plugins/Customizer/Assets/css/themes/Blueboard.css @@ -0,0 +1,265 @@ +/*! + * Blueboard by bgibout - Theme for Kanboard + * Licensed under the MIT license - blueboard/LICENSE + * https://github.com/bgibout/blueboard + * Copyright (c) 2017 bgibout + */ + +body { + color: #868ba1; + background-color: #e9ecef; + font-family: "Roboto", "Helvetica Neue", Arial, sans-s +} + +.page { + margin: 0; +} + +header { + margin: 0 !important; + border: 0 !important; + background-color : #3B6998 !important; +} + +.action-menu { + padding: 10px; +} + +.views { + line-height: 30px; +} + +.filter-box-component { + padding-top: 5px; +} + +.project-header .dropdown-component { + margin-top: 4px; + margin-right: 5px; + float: left; + line-height: 30px; +} + +header h1 { + color: #ffffff; + font-weight: 600; + font-size: 1.1rem; + line-height: 2rem +} + +.tooltip i.fa { + color: #fff; +} + +.btn { + text-decoration: none; + border-radius: 0; + min-height: 30px; + line-height: 30px; + color: #fff; + background-color: #3B6998; + border-color: #386491; + font-size: 12px; + text-align: center; + padding-left: 15px; + padding-right: 15px +} + +label { + padding: 0px 40px 0 0px; + line-height: 25px; + vertical-align: middle; + font-size: .9rem; +} + +/* + form { + margin-top: 40px; +} +*/ + +fieldset { + background: #ffffff; +} + +select, span.select2 { + width: 95% !important; +} + +input[type="number"], input[type="date"], input[type="email"], input[type="password"], input[type="text"]:not(.input-addon-field) { + padding: 0.65rem 0.75rem; + font-size: 0.875rem; + line-height: 1.25; + color: #495057; + background-color: #fff; + background-image: none; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 2px; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + height: auto +} + +.select-dropdown-input { + border: none !important; +} + +.select-dropdown-input-container .select-dropdown-chevron { + top: 10px; +} + +.js-submit-buttons-rendered { + margin-top: 40px; +} + +.page .page-header { + padding: 10px; + background: #343a40; + font-size: 0.9rem; + margin: 0; +} + +.page-header a { + color: #ffffff; + text-decoration: none; +} + +#config-section .page-header a { + color: #000; + font-size: 0.8rem; +} + +.page-header a .fa { + color: #868e96; +} + +.menus-container a .fa { + color: #ffffff; +} + +.margin-bottom { + margin-bottom: 20px; + padding: 10px; +} + +.accordion-section { + margin: 20px; + background: #fff; + padding: 50px 30px; +} + +.project-overview-column { + background: #fff; +} + +.table-list { + font-size: 0.85em; + margin-bottom: 20px; + padding: 10px; + background: #ffffff; +} + +.table-list-header { + background: #fbfbfb; + border: 1px solid #e5e5e5; + border-radius: 5px 5px 0 0; + line-height: 30px; + padding-left: 10px; + padding-right: 3px; + height: 30px; +} + +.table-list-row { + padding: 10px; +} + +.sidebar-content { + background: #f8f9fa; + margin-top: 10px; + padding: 10px; +} + +.sidebar-container { + height: 100%; + width: 100%; +} + +.sidebar-container .page-header { + background: none; +} + +.sidebar { + margin-top: 20px; +} + +.sidebar .js-select-dropdown-autocomplete-rendered { + padding: 10px; +} + + +.sidebar ul { + margin-top: 10px; +} + +.sidebar ul:before { + content: "Navigation"; + text-transform: uppercase; + font-size: 11px; + padding: 7px 15px; + display: block; + margin-bottom: 0; + font-weight: 500; + letter-spacing: 0.5px; +} + +.sidebar ul>li { + padding: 5px 0 0 15px; + border-top: 1px solid #ced4da; +} + +.sidebar ul>li:last-child { + border-bottom: 1px solid #ced4da; +} + +.sidebar ul>li a { + color: #868e96; + font-size: 0.9rem; +} + +.sidebar>ul li.active { + background-color: #f8f9fa +} + +.sidebar ul>li.active a { + color: #343a40; +} + +#modal-box { + padding: 20px 20px 30px 20px; +} + +table { + color: #000; +} + +table.table-fixed th { + background: #3B6998; +} +table.table-fixed th, table.table-fixed th a { + color: #ffffff ; +} + +table.table-fixed th, table.table-fixed td { + padding-left: 10px; +} + +.board-add-icon i { + color: #3B6998; +} + + +@media (max-width: 768px) { + .sidebar { + max-width: 100%; + } +} diff --git a/plugins/Customizer/Assets/css/themes/Breathe.css b/plugins/Customizer/Assets/css/themes/Breathe.css new file mode 100644 index 00000000..b262244c --- /dev/null +++ b/plugins/Customizer/Assets/css/themes/Breathe.css @@ -0,0 +1,327 @@ +/*! + * Modified version of Oxygen - Theme for Kanboard + * Licensed under the MIT license - Oxygen/LICENSE + * https://github.com/kenlog/Oxygen + * Copyright (c) 2018 Valentino Pesce - https://iltuobrand.it + */ + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} +h1{ + color:#007BA8; +} +a{ + color:#007BA8; +} +.js-modal-medium{ + color:#007BA8; +} +.sidebar>ul a:hover{ + color:#007BA8; +} +div.task-board-recent { + box-shadow: none; + border-bottom: 6px solid rgba(0, 0, 0, 0.3); +} +div.ganttview-vtheader-series-name { + padding: 0 6px; +} +th,td { + padding: 10px; +} +header { + border-bottom: none; + box-shadow: 0px 1px 3px 0 rgba(46,61,73,.12); + padding: 15px 10px; + margin-bottom: 15px; + background-color: #e8fbff!important; +} +.header img { + float: left; +} +.header h2 { + position: relative; + color:#007BA8; + top: 13px; + left: 10px; + margin: 0; +} +label { + font-weight: bold; + margin-top: 18px; +} +.task-board { + margin-bottom: 8px; + padding: 12px; + border-radius: 6px; + box-shadow: 0px 5px 5px 0 rgba(46,61,73,.12); + border: none; +} +.task-board-title { + font-size: 1.25em; + font-weight: 600; +} +.task-show-details { + border-radius: 10px; + margin-bottom: 20px; +} +.task-show-details h2 { + background-color: rgba(0, 0, 0, 0.3); + padding: 20px; + border-radius: 8px 8px 0 0; + color: #fff; +} +.task-show-details ul { + padding: 20px; +} +.task-summary-container { + border: none; + border-radius: 7px; + border-bottom: 5px solid; + padding: 20px; +} +.table-small { + font-size: 1em; +} +table th { + text-align: left; + padding: 0.7em 3px; + border: 1px solid #eee; + background: #fbfbfb; +} +table td { + border: none; + padding: 0.5em 7px; + vertical-align: top; +} +.sidebar ul { + padding-bottom: 20px; + border-bottom: 1px solid #ddd; +} +.sidebar ul:last-of-type { + border-bottom: none; +} +.sidebar-collapse i,.sidebar-collapsed .sidebar { + background-color: #999; + border-radius: 24px; + padding: 5px 10px 5px 8px; +} +.sidebar-collapse i,.sidebar-collapsed .sidebar i { + color: #fff; +} +.sidebar-collapse i:hover,.sidebar-collapsed .sidebar:hover { + background-color: #ccc; +} +.page-header { + margin: 15px 0; + background-color: #eee; + padding: 6px 10px 10px 10px; + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; +} +.page-header li { + font-size: 1.1em; +} +.page-header h2 { + border-bottom: none; +} +.listing { + border: none; +} +.fa-play { + font-size: 1.2em; + background-color: #ddd; + border-radius: 100%; + width: 25px; + height: 28px; + padding: 8px 0 0 15px; + margin-right: 8px; +} +.fa-play:hover { + background-color: #999; + color: #fff; +} +.fc-event .fc-content { + padding: 4px 8px; +} +.board-add-icon { + float: left; + padding: 0 5px +} +.board-add-icon i { + text-decoration: none; + color: #289E7B; + font-size: 1.4em +} +.board-add-icon i:focus, +.board-add-icon i:hover { + text-decoration: none; + color: #23292d +} +.form-column select { + font-size: 1.2em; + margin: 10px 0 2px 0; +} +.form-column:nth-of-type(2) { + padding: 10px 25px; + background-color: #f7f7f7; +} +.form-actions { + font-size: 1.2em; +} +.page-header ul.dropdown-submenu-open { + margin: 10px 0 0 -10px; + padding: 0; +} +.select-dropdown-input-container { + max-width: 300px; +} +.dropdown-submenu-open li:nth-child(even) { + background-color: #f7f7f7; +} +.dropdown-submenu-open li { + padding: 6px 10px; +} +.dropdown-submenu-open li:hover { + background-color: #eee; +} +.filters { + border: none; +} +.filters ul.dropdown-submenu-open { + margin: 6px 0 0 8px; +} +.markdown pre { + background: #3333330d; + max-height: 600px; + overflow: auto; + margin-bottom: 1em; + padding: 5px; + color: #242729; + font-family: Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,sans-serif; +} +a i.web-notification-icon { + color: #D45353; +} +.fa-play { + font-size:1.2em; + background-color: #ddd; + border-radius: 100%; + width: 25px; + height: 25px!important; + padding: 9px 3px 0 9px!important; + margin-right: 8px; +} +#modal-box { + padding: 10px; +} +.table-list-category { + padding: 2px 2px 2px 5px; +} +.table-list-row:hover { + background: #f5f5f5; + border-bottom: 1px solid #f7f7f7; + border-right: 1px solid #f7f7f7; +} +.page-header { + margin: 15px 0; + background-color: #ffffff; + padding: 6px 10px 10px 10px; + border-bottom: 1px solid #f7f7f7; + border-top: 1px solid #f7f7f7; +} +.comments .comment-highlighted { + background-color: #fff; + border: 2px solid #F5E982; + border-radius: 5px; +} +.comments .comment:hover { + background: #f5f5f5; + border-radius: 5px; +} +.comments .comment:nth-child(even):not(.comment-highlighted):hover { + border-radius: 5px; + background: #f5f5f5; +} +.comments .comment:nth-child(even):not(.comment-highlighted) { + background: #fbfbfb; + border-radius: 5px; +} +.sidebar { + background-color: #f7f7f7; + padding: 7px; + border-radius: 7px; + border-left: 2px solid #e9e9e9; + box-shadow: 1px 0px 7px 0 rgba(46,61,73,.12); +} +input[type="number"]:focus, input[type="date"]:focus, input[type="email"]:focus, input[type="password"]:focus, input[type="text"]:focus { + color: #000; + border-color: #007BA8; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(222,222,222,0.25); +} +textarea:focus { + color: #000; + border-color: #007BA8; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(222,222,222,0.25); +} +input[type="number"], input[type="date"], input[type="email"], input[type="password"], input[type="text"]:not(.input-addon-field) { + padding:3px; + transition: box-shadow 1s; +} +.text-editor textarea{ + padding:3px; + transition: box-shadow 1s; +} +select{ + border: 1px solid #ccc; + background: #f9f9f9; + padding: 3px; +} +.btn-blue { + border-color: #007BA8; + background: #007BA8; + color: #fff; + background-image: linear-gradient(-180deg, #007BA8 0%, #007387 90%); +} +.btn-blue:hover, .btn-blue:focus { + border-color: #007BA8; + background: #ffffff; + color: #007BA8; +} +.select2-container--default.select2-container--focus .select2-selection--multiple { + border: solid #007BA8 1px; + outline: 0; +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: #007BA8; + color: white; +} +.board-add-icon i:focus, .board-add-icon i:hover { + text-decoration: none; + color: #91C259; +} +.board-add-icon i { + text-decoration: none; + color: #007BA8; + font-size: 1.4em; +} +.dropdown-submenu-open li:not(.no-hover):hover { + background: #007BA8; + color: #fff; +} +.sidebar>ul li.active a { + color: #007BA8; + font-weight: bold; +} +.table-list-row .table-list-title a:hover, .table-list-row .table-list-title a:focus { + text-decoration: underline; + color: #007BA8; +} +.image-slideshow-overlay img { + display: block; + margin: auto; + max-width: 100%; +} diff --git a/plugins/Customizer/Assets/css/themes/Clemson.css b/plugins/Customizer/Assets/css/themes/Clemson.css new file mode 100644 index 00000000..d7406315 --- /dev/null +++ b/plugins/Customizer/Assets/css/themes/Clemson.css @@ -0,0 +1,341 @@ +/*! + * Modified version of Oxygen - Theme for Kanboard + * Licensed under the MIT license - Oxygen/LICENSE + * https://github.com/kenlog/Oxygen + * Copyright (c) 2018 Valentino Pesce - https://iltuobrand.it + */ + +@import url("https://fonts.googleapis.com/css?family=Raleway:400,700"); + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} + +h1{ + color:#FF6200; +} +a{ + color:#FF6200; +} +.js-modal-medium{ + color:#FF6200; +} +.sidebar>ul a:hover{ + color:#FF6200; +} +div.ganttview-vtheader-series-name { + padding: 0 6px; +} +th,td { + padding: 10px; +} +header { + border-bottom: none; + box-shadow: 0px 1px 3px 0 rgba(46,61,73,.12); + padding: 15px 10px; + margin-bottom: 15px; + background-color: #ff6200; /* Old browsers */ + background: -moz-linear-gradient(-45deg, #ff6200 36%, #9d00ff 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(-45deg, #ff6200 36%,#9d00ff 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(135deg, #ff6200 36%,#9d00ff 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff6200', endColorstr='#9d00ff',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */ +} +.header img { + float: left; +} +.header h2 { + position: relative; + color:#FFFFFF;!important; + top: 13px; + left: 10px; + margin: 0; +} +header h1 { + font-size: 1.4em!important; + color: #FFFFFF!important; +} +label { + font-weight: bold; + margin-top: 18px; +} + +.task-board { + margin-bottom: 8px; + padding: 9px; + border-radius: 3px; + box-shadow: 0px 1px 4px 0.6px rgba(157,0,255,0.6); + border: 2px solid; + background-color: #ffffff!important; +} +.task-board-title { + font-size: 1.25em; + font-weight: 600; +} +.task-show-details { + border-radius: 10px; + margin-bottom: 20px; +} +.task-show-details h2 { + background-color: rgba(0, 0, 0, 0.3); + padding: 20px; + border-radius: 8px 8px 0 0; + color: #fff; +} +.task-show-details ul { + padding: 20px; +} +.task-summary-container { + border: none; + border-radius: 7px; + border-bottom: 5px solid; + padding: 20px; +} +.table-small { + font-size: 1em; +} +table th { + text-align: left; + padding: 0.7em 3px; + border: 1px solid #F2DCD3; + background: #FFEFEB; +} +table td { + border: none; + padding: 0.5em 7px; + vertical-align: top; +} +.sidebar ul { + padding-bottom: 20px; + border-bottom: 1px solid #ddd; +} +.sidebar ul:last-of-type { + border-bottom: none; +} +.sidebar-collapse i,.sidebar-collapsed .sidebar { + background-color: #999; + border-radius: 24px; + padding: 5px 10px 5px 8px; +} +.sidebar-collapse i,.sidebar-collapsed .sidebar i { + color: #fff; +} +.sidebar-collapse i:hover,.sidebar-collapsed .sidebar:hover { + background-color: #ccc; +} +.page-header { + margin: 15px 0; + background-color: #F2DCD3; + padding: 6px 10px 10px 10px; + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; +} +.page-header li { + font-size: 1.1em; +} +.page-header h2 { + border-bottom: none; +} +.listing { + border: none; +} +.fa-play { + font-size: 1.2em; + background-color: #ddd; + border-radius: 100%; + width: 25px; + height: 28px; + padding: 8px 0 0 15px; + margin-right: 8px; +} +.fa-play:hover { + background-color: #999; + color: #fff; +} +.fc-event .fc-content { + padding: 4px 8px; +} +.board-add-icon { + float: left; + padding: 0 5px +} +.board-add-icon i { + text-decoration: none; + color: #9D00FF; + font-size: 1.4em +} +.board-add-icon i:focus, +.board-add-icon i:hover { + text-decoration: none; + color: #23292d +} +.form-column select { + font-size: 1.2em; + margin: 10px 0 2px 0; +} +.form-column:nth-of-type(2) { + padding: 10px 25px; + background-color: #f7f7f7; +} +.form-actions { + font-size: 1.2em; +} +.page-header ul.dropdown-submenu-open { + margin: 10px 0 0 -10px; + padding: 0; +} +.select-dropdown-input-container { + max-width: 300px; +} +.dropdown-submenu-open li:nth-child(even) { + background-color: #f7f7f7; +} +.dropdown-submenu-open li { + padding: 6px 10px; +} +.dropdown-submenu-open li:hover { + background-color: #F2DCD3; +} +.filters { + border: none; +} +.filters ul.dropdown-submenu-open { + margin: 6px 0 0 8px; +} +.markdown pre { + background: #FFF; + max-height: 600px; + overflow: auto; + margin-bottom: 1em; + padding: 5px; + color: #242729; + font-family: Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,sans-serif; +} +code { + background-color: rgba(249, 161, 0, 0.12)!important; + color: #ab05ff!important; +} +a i.web-notification-icon { + color: #FF6200; +} +.fa-play { + font-size:1.2em; + background-color: #ddd; + border-radius: 100%; + width: 25px; + height: 25px!important; + padding: 9px 3px 0 9px!important; + margin-right: 8px; +} +#modal-box { + padding: 10px; +} +.table-list-category { + padding: 2px 2px 2px 5px; +} +.table-list-row:hover { + background: #f5f5f5; + border-bottom: 1px solid #f7f7f7; + border-right: 1px solid #f7f7f7; +} +.page-header { + margin: 15px 0; + background-color: #ffffff; + padding: 6px 10px 10px 10px; + border-bottom: 1px solid #f7f7f7; + border-top: 1px solid #f7f7f7; +} +.comments .comment-highlighted { + background-color: #fff; + border: 2px solid #AD3BFF; + border-radius: 5px; +} +.comments .comment:hover { + background: #f5f5f5; + border-radius: 5px; +} +.comments .comment:nth-child(even):not(.comment-highlighted):hover { + border-radius: 5px; + background: #f5f5f5; +} +.comments .comment:nth-child(even):not(.comment-highlighted) { + background: #FFEFEB; + border-radius: 5px; +} +.sidebar { + background-color: #f7f7f7; + padding: 7px; + border-radius: 7px; + border-left: 2px solid #e9e9e9; + box-shadow: 1px 0px 7px 0 rgba(46,61,73,.12); +} +input[type="number"]:focus, input[type="date"]:focus, input[type="email"]:focus, input[type="password"]:focus, input[type="text"]:focus { + color: #000; + border-color: #FF6200; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(222,222,222,0.25); +} +textarea:focus { + color: #000; + border-color: #FF6200; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(222,222,222,0.25); +} +input[type="number"], input[type="date"], input[type="email"], input[type="password"], input[type="text"]:not(.input-addon-field) { + padding:3px; + transition: box-shadow 1s; +} +.text-editor textarea{ + padding:3px; + transition: box-shadow 1s; +} +select{ + border: 1px solid #ccc; + background: #f9f9f9; + padding: 3px; +} +.btn-blue { + border-color: #FF6200; + background: #FF6200; + color: #fff; + background-image: linear-gradient(-180deg, #FF6200 0%, #4B00B5 90%); +} +.btn-blue:hover, .btn-blue:focus { + border-color: #FF6200; + background: #ffffff; + color: #FF6200; +} +.select2-container--default.select2-container--focus .select2-selection--multiple { + border: solid #FF6200 1px; + outline: 0; +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: #FF6200; + color: white; +} +.board-add-icon i:focus, .board-add-icon i:hover { + text-decoration: none; + color: #E68332; +} +.board-add-icon i { + text-decoration: none; + color: #FF6200; + font-size: 1.4em; +} +.dropdown-submenu-open li:not(.no-hover):hover { + background: #FF6200; + color: #fff; +} +.sidebar>ul li.active a { + color: #FF6200; + font-weight: bold; +} +.table-list-row .table-list-title a:hover, .table-list-row .table-list-title a:focus { + text-decoration: underline; + color: #FF6200; +} +.image-slideshow-overlay img { + display: block; + margin: auto; + max-width: 100%; +} + diff --git a/plugins/Customizer/Assets/css/themes/Galaxy.css b/plugins/Customizer/Assets/css/themes/Galaxy.css new file mode 100644 index 00000000..9517cecc --- /dev/null +++ b/plugins/Customizer/Assets/css/themes/Galaxy.css @@ -0,0 +1,2713 @@ +/*! + * Modified version of Nebula - Theme for Kanboard + * Licensed under the MIT license - Nebula/LICENSE + * https://github.com/kenlog/Nebula + * Copyright (c) 2018 Valentino Pesce - https://iltuobrand.it + */ + + h1,li,ul,ol,table,tr,td,th,p,blockquote,body { + margin:0; + padding:0; + font-size:100% +} +body { + padding-bottom:10px; + color:#ced4da; + background-color: #222; + font-family: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + text-rendering:optimizeLegibility +} +code { + background-color: #cccccc52!important; +} +small { + font-size:0.8em +} +::selection{background:rgba(113,113,113,0.5);color:#ffffff}::-moz-selection{background:rgba(113,113,113,0.5);color:#ffffff} +hr { + border:0; + height:0; + border-top:0px solid rgba(0,0,0,0.1); + border-bottom:0px solid rgba(255,255,255,0.3) +} +::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */ + color: #ffffff!important; + opacity: 1; /* Firefox */ +} + +:-ms-input-placeholder { /* Internet Explorer 10-11 */ + color: #ffffff!important; +} + +::-ms-input-placeholder { /* Microsoft Edge */ + color: #ffffff!important; +} +.select-dropdown-input-container { + background-color: #252525!important; + max-width: 300px; +} +.page { + margin-left:10px; + margin-right:10px +} +.margin-top { + margin-top:20px +} +.margin-bottom { + margin-bottom:20px +} +.pull-right { + text-align:right +} +ul.no-bullet li { + list-style-type:none; + margin-left:0 +} +#app-loading-icon { + position:fixed; + right:3px; + bottom:3px +} +.assign-me { + vertical-align:bottom +} +a { + color:#00bc8c; + border:none; + text-decoration:none; +} +a:focus { + outline:0; + color:#20c997; + text-decoration:none +} +a:hover { + color:#ced4da; + text-decoration:none +} +a .fa { + padding-right:3px; + text-decoration:none; + color:#ced4da +} +h1,h2,h3 { + font-weight:normal; + color:#ffffff +} +h1 { + font-size:1.5em +} +h2 { + font-size:1.4em; + margin-bottom:10px +} +h3 { + margin-top:10px; + font-size:1.2em +} +table { + width:100%; + border-collapse:collapse; + border-spacing:0; + margin-bottom:20px +} +table.table-fixed { + table-layout:fixed; + white-space:nowrap +} +table.table-fixed th { + overflow:hidden +} +table.table-fixed td { + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis +} +table.table-small { + font-size:0.8em +} +table.table-striped tr:nth-child(odd) { + background:#252525 +} +@media (max-width: 768px) { + table.table-scrolling { + overflow-x:auto; + display:inline-block; + vertical-align:top; + max-width:100%; + white-space:nowrap + } +} +table th { + text-align:left; + padding:0.5em 3px; + border:0px solid #eee; + background:#252525 +} +table th a { + text-decoration:none; + color:#ced4da +} +table th a:focus,table th a:hover { + text-decoration:underline +} +table td { + border:0px solid #eee; + padding:0.5em 3px; + vertical-align:top +} +table td li { + margin-left:20px +} +.column-1 { + width:1% +} +.column-2 { + width:2% +} +.column-3 { + width:3% +} +.column-4 { + width:4% +} +.column-5 { + width:5% +} +.column-6 { + width:6% +} +.column-7 { + width:7% +} +.column-8 { + width:8% +} +.column-9 { + width:9% +} +.column-10 { + width:10% +} +.column-11 { + width:11% +} +.column-12 { + width:12% +} +.column-13 { + width:13% +} +.column-14 { + width:14% +} +.column-15 { + width:15% +} +.column-16 { + width:16% +} +.column-17 { + width:17% +} +.column-18 { + width:18% +} +.column-19 { + width:19% +} +.column-20 { + width:20% +} +.column-21 { + width:21% +} +.column-22 { + width:22% +} +.column-23 { + width:23% +} +.column-24 { + width:24% +} +.column-25 { + width:25% +} +.column-26 { + width:26% +} +.column-27 { + width:27% +} +.column-28 { + width:28% +} +.column-29 { + width:29% +} +.column-30 { + width:30% +} +.column-31 { + width:31% +} +.column-32 { + width:32% +} +.column-33 { + width:33% +} +.column-34 { + width:34% +} +.column-35 { + width:35% +} +.column-36 { + width:36% +} +.column-37 { + width:37% +} +.column-38 { + width:38% +} +.column-39 { + width:39% +} +.column-40 { + width:40% +} +.column-41 { + width:41% +} +.column-42 { + width:42% +} +.column-43 { + width:43% +} +.column-44 { + width:44% +} +.column-45 { + width:45% +} +.column-46 { + width:46% +} +.column-47 { + width:47% +} +.column-48 { + width:48% +} +.column-49 { + width:49% +} +.column-50 { + width:50% +} +.column-51 { + width:51% +} +.column-52 { + width:52% +} +.column-53 { + width:53% +} +.column-54 { + width:54% +} +.column-55 { + width:55% +} +.column-56 { + width:56% +} +.column-57 { + width:57% +} +.column-58 { + width:58% +} +.column-59 { + width:59% +} +.column-60 { + width:60% +} +.column-61 { + width:61% +} +.column-62 { + width:62% +} +.column-63 { + width:63% +} +.column-64 { + width:64% +} +.column-65 { + width:65% +} +.column-66 { + width:66% +} +.column-67 { + width:67% +} +.column-68 { + width:68% +} +.column-69 { + width:69% +} +.column-70 { + width:70% +} +.column-71 { + width:71% +} +.column-72 { + width:72% +} +.column-73 { + width:73% +} +.column-74 { + width:74% +} +.column-75 { + width:75% +} +.column-76 { + width:76% +} +.column-77 { + width:77% +} +.column-78 { + width:78% +} +.column-79 { + width:79% +} +.column-80 { + width:80% +} +.column-81 { + width:81% +} +.column-82 { + width:82% +} +.column-83 { + width:83% +} +.column-84 { + width:84% +} +.column-85 { + width:85% +} +.column-86 { + width:86% +} +.column-87 { + width:87% +} +.column-88 { + width:88% +} +.column-89 { + width:89% +} +.column-90 { + width:90% +} +.column-91 { + width:91% +} +.column-92 { + width:92% +} +.column-93 { + width:93% +} +.column-94 { + width:94% +} +.column-95 { + width:95% +} +.column-96 { + width:96% +} +.column-97 { + width:97% +} +.column-98 { + width:98% +} +.column-99 { + width:99% +} +.column-100 { + width:100% +} +.draggable-row-handle { + cursor:move; + color: #ced4da; + padding-left: 10px; +} +.draggable-row-handle:hover { + color:#ced4da +} +.ui-widget-content { + color: #00bc8c; +} +tr.draggable-item-selected { + background:#fff; + border:2px solid #666; + box-shadow:4px 2px 10px -4px rgba(0,0,0,0.55) +} +tr.draggable-item-selected td { + border-top:none; + border-bottom:none +} +tr.draggable-item-selected td:first-child { + border-left:none +} +tr.draggable-item-selected td:last-child { + border-right:none +} +.table-stripped tr.draggable-item-hover,.table-stripped tr.draggable-item-hover { + background:#FEFFF2 +} +.table-list { + font-size:0.85em; + margin-bottom:20px +} +.table-list-header { + background:#222; + border:0px solid #e5e5e5; + border-radius:5px 5px 0 0; + line-height:28px; + padding-left:3px; + padding-right:3px +} +.table-list-header a { + color:#ced4da; + font-weight:500; + text-decoration:none; + margin-right:10px +} +.table-list-header a:hover,.table-list-header a:focus { + color:#767676 +} +.table-list-header .table-list-header-count { + color:#ced4d1; + display:inline-block; + float:left +} +.table-list-header .table-list-header-menu { + text-align:right +} +.table-list-row { + padding-left:3px; + padding-right:3px; + padding: 10px; + border-bottom:0px solid #e5e5e5; + border-right:0px solid #e5e5e5 +} +.table-list-row.table-border-left { + border-left:0px solid #e5e5e5 +} +.table-list-row:nth-child(odd) { + background:#252525 +} +.table-list-row:last-child { + border-radius:0 0 5px 5px +} +.table-list-row:hover { + background:#252525; + border-bottom:0px solid #ffeb8e; + border-right:0px solid #ffeb8e +} +.table-list-row .table-list-title { + font-weight:500; + line-height:23px +} +.table-list-row .table-list-title.status-closed { + text-decoration:line-through; + margin-right:10px +} +.table-list-row .table-list-title.status-closed a { + font-style:italic +} +.table-list-row .table-list-title a { + color:#ced4da; + text-decoration:none +} +.table-list-row .table-list-title a:hover,.table-list-row .table-list-title a:focus { + text-decoration:underline +} +.table-list-row .table-list-details { + color:#f7f7f7; + font-weight:300; + line-height:20px +} +.table-list-row .table-list-details span { + margin-left:5px +} +.table-list-row .table-list-details span:first-child { + margin-left:0 +} +.table-list-row .table-list-details li { + display:inline; + list-style-type:none +} +.table-list-row .table-list-details li:after { + content:', ' +} +.table-list-row .table-list-details li:last-child:after { + content:'' +} +.table-list-row .table-list-details strong { + font-weight:400; + color:#f7f7f7 +} +.table-list-row .table-list-details-with-icons { + float:left +} +@media (max-width: 768px) { + .table-list-row .table-list-details-with-icons { + float:none + } +} +.table-list-row .table-list-icons { + font-size:0.8em; + text-align:right; + line-height:30px +} +@media (max-width: 768px) { + .table-list-row .table-list-icons { + text-align:left; + line-height:20px + } +} +.table-list-row .table-list-icons span { + margin-left:5px +} +.table-list-row .table-list-icons a { + text-decoration:none +} +.table-list-row .table-list-icons a:hover { + color:#ced4da +} +.table-list-row .table-list-icons a:hover i { + color:#ced4da +} +.table-list-category { + font-size:0.9em; + font-weight:500; + color:#000; + padding:1px 2px 1px 2px; + border-radius:3px; + background:#fcfcfc; + border:0px solid #ccc +} +.table-list-category a { + text-decoration:none; + color:#000 +} +.table-list-category a:hover { + color:#00bc8c +} +fieldset { + border:0px solid #ddd; + margin-top:10px +} +legend { + font-weight:500; + font-size:1.2em +} +label { + cursor:pointer; + display:block; + margin-top:10px; + font-weight:400 +} +input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]:not(.input-addon-field) { + width:300px; + max-width:95%; + font-size:1em; + height:25px; + padding-bottom:0; + padding-left:4px; + + line-height: 1.5; + color: #fff; + background-color: #252525; + border: 2px solid #4a5368; + border-radius: .25rem; + -webkit-transition: border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out; + transition: border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out; + transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; + transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out; + + font-family:sans-serif; + -webkit-appearance:none; + -moz-appearance:none + +} +input[type="number"]::-webkit-input-placeholder,input[type="date"]::-webkit-input-placeholder,input[type="email"]::-webkit-input-placeholder,input[type="password"]::-webkit-input-placeholder,input[type="text"]:not(.input-addon-field)::-webkit-input-placeholder { + color:#222 +} +input[type="number"]::-moz-placeholder,input[type="date"]::-moz-placeholder,input[type="email"]::-moz-placeholder,input[type="password"]::-moz-placeholder,input[type="text"]:not(.input-addon-field)::-moz-placeholder { + color:#222 +} +input[type="number"]:-ms-input-placeholder,input[type="date"]:-ms-input-placeholder,input[type="email"]:-ms-input-placeholder,input[type="password"]:-ms-input-placeholder,input[type="text"]:not(.input-addon-field):-ms-input-placeholder { + color:#222 +} +input[type="number"]:focus,input[type="date"]:focus,input[type="email"]:focus,input[type="password"]:focus,input[type="text"]:focus { + color:#ced4da; + border-color:rgba(82,168,236,0.8); + outline:0; + box-shadow:0 0 8px rgb(45, 60, 93) +} +input[type="number"] { + width:70px +} +input[type="text"]:not(.input-addon-field).form-numeric { + width:70px +} +input[type="text"]:not(.input-addon-field).form-datetime,input[type="text"]:not(.input-addon-field).form-date { + width:150px +} +input[type="text"]:not(.input-addon-field).form-input-large { + width:400px +} +input[type="text"]:not(.input-addon-field).form-input-small { + width:150px +} +textarea:focus { + color:#fff; + border-color:rgba(82,168,236,0.8); + outline:0; + box-shadow:0 0 8px rgba(82,168,236,0.6) +} +textarea { + padding:10px; + border:0px solid #ccc; + width:400px; + max-width:99%; + height:200px; + font-family:sans-serif; + font-size:1em; + line-height: 1.5; + color: #fff; + background-color: #252525; + border: 2px solid #4a5368; + border-radius: .25rem; + -webkit-transition: border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out; + transition: border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out; + transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; + transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out; +} +textarea::-webkit-input-placeholder { + color:#222 +} +textarea::-moz-placeholder { + color:#222 +} +textarea:-ms-input-placeholder { + color:#222 +} +select{ + max-width:95%; + border: 3px solid #4a5368; + background: #252525; + padding: 3px; + color:#eee; +} +select:focus { + outline:0 +} +select[multiple] { + width:300px +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #3b3e47; + border: 1px solid #3b3e47; + border-radius: 4px; + cursor: default; + float: left; + margin-right: 5px; + margin-top: 5px; + padding: 0 5px; +} +.tag-autocomplete { + width:400px +} +span.select2-container { + margin-top:2px +} +.form-actions { + padding-top:20px; + clear:both; +} +.form-required { + color:red; + padding-left:5px; + font-weight:bold +} +@media (max-width: 480px) { + .form-required { + display:none + } +} +input[type="text"].form-max-width { + width:100% +} +input.form-error,textarea.form-error { + border:2px solid #b94a48 +} +input.form-error:focus,textarea.form-error:focus { + box-shadow:none; + border:2px solid #b94a48 +} +.form-errors { + color:#b94a48; + list-style-type:none +} +ul.form-errors li { + margin-left:0 +} +.form-help { + font-size: 0.9em; + color: #00c9ff; + margin-bottom:15px +} +.form-inline { + padding:0; + margin:0; + border:none +} +.form-inline label { + display:inline; + padding-right:3px +} +.form-inline input,.form-inline select { + margin:0 15px 0 0 +} +.form-inline .form-required { + display:none +} +.form-inline .form-actions { + display:inline-block +} +.form-inline .js-submit-buttons-rendered { + display:inline-block +} +.form-inline-group { + display:inline +} +.form-columns { + display:-webkit-flex; + display:flex; + -webkit-flex-direction:row; + flex-direction:row; + -webkit-flex-wrap:wrap; + flex-wrap:wrap; + -webkit-justify-content:flex-start; + justify-content:flex-start +} +.form-columns .form-column { + margin-right:25px; + flex-grow:1 +} +.form-columns fieldset { + margin-top:0 +} +.form-login { + max-width:350px; + margin:5% auto 0 +} +@media (max-width: 480px) { + .form-login { + margin-left:5px + } +} +.form-login li { + margin-left:25px; + line-height:25px +} +.form-login h2 { + margin-bottom:30px; + font-weight:bold +} +.reset-password { + margin-top:20px; + margin-bottom:20px +} +.reset-password a { + color:#999 +} +.input-addon { + display:flex +} +.input-addon-field { + flex:1; + font-size:1em; + color:#ced4da; + background: #3e424d; + margin:0; + -webkit-appearance:none; + -moz-appearance:none +} +.input-addon-field:first-child { + border-radius:5px 0 0 5px +} +.input-addon-field:last-child { + border-radius:0 5px 5px 0 +} +.input-addon-item { + background-color:rgba(147,128,108,0.1); + color:#666; + font:inherit; + font-weight:normal +} +.input-addon-item:first-child { + border-radius:5px 0 0 5px +} +.input-addon-item:last-child { + border-radius:0 5px 5px 0 +} +@media (max-width: 480px) { + .input-addon-item .dropdown .fa-caret-down { + display:none + } +} +.input-addon-field,.input-addon-item { + border:0px solid rgba(147,128,108,0.25); + padding:4px 0.75em +} +.input-addon-field:not(:first-child),.input-addon-item:not(:first-child) { + border-left:0 +} +.icon-success { + color:#468847 +} +.icon-error { + color:#b94a48 +} +.icon-fade-out { + opacity:1; + animation:icon-fadeout 5s linear forwards +} +@keyframes icon-fadeout { + 0% { + opacity:1 + } + 100% { + opacity:0 + } +} +.alert { + padding:8px 35px 8px 14px; + margin-top:5px; + margin-bottom:5px; + color:#fff; + background-color:#20c997; + border:0px solid #20c997; + border-radius:4px +} +.alert-success { + color: #ffffff; + background-color: #20c997; + border-color: #20c997; +} +.alert-error { + color:#b94a48; + background-color:#f2dede; + border-color:#eed3d7 +} +.alert-info { + color:#3a87ad; + background-color:#d9edf7; + border-color:#bce8f1 +} +.alert-normal { + color:#ced4da; + background-color:#f0f0f0; + border-color:#ddd +} +.alert ul { + margin-top:10px; + margin-bottom:10px +} +.alert li { + margin-left:25px +} +.alert-fade-out { + text-align:center; + position:fixed; + bottom:0; + left:20%; + width:60%; + padding-top:5px; + padding-bottom:5px; + margin-bottom:0; + border-width:1px 0 0; + border-radius:4px 4px 0 0; + z-index:9999; + opacity:1; + animation:fadeout 5s linear forwards +} +@keyframes fadeout { + 0% { + opacity:1 + } + 100% { + opacity:0 + } +} +a.btn { + text-decoration:none +} +.btn { + -webkit-appearance: none; + -moz-appearance: none; + font-size: 1.2em; + font-weight: normal; + cursor: pointer; + display: inline-block; + border-radius: 5px; + padding: 3px 10px; + margin: 0; + border: 0px solid #ddd; + background: #00bc8c; + color: #fbfbfb; +} +.btn:hover,.btn:focus { + border-color:#bbb; + background:#fafafa; + color:#000 +} +.btn-red { + border-color:#b0281a; + background:#d14836; + color:#fff +} +.btn-red:hover,.btn-red:focus { + border-color:#b0281a; + background:#c53727; + color:#fff +} +.btn-blue { + border-color:#188ae2; + background:#188ae2; + color:#fff +} +.btn-blue:hover,.btn-blue:focus { + border-color:#1475bf; + background:#1475bf; + color:#fff +} +.btn:disabled { + color:#ccc; + border-color:#ccc; + background:#f7f7f7 +} +.buttons-header { + font-size:0.8em; + margin-top:5px; + margin-bottom:15px +} +.tooltip i.fa { + cursor:pointer +} +.tooltip .fa-info-circle { + color:#999 +} +#tooltip-container { + padding: 5px; + background: #3a404c; + border: 2px solid #ddd; + border-radius: 4px; + box-shadow: -1px -1px 7px #aaa; + position: absolute; + min-width: 350px; +} +#tooltip-container .markdown p { + margin-bottom:0 +} +#tooltip-container .tooltip-large { + width:600px +} +h2 .dropdown ul { + display:none +} +.dropdown { + display:inline; + position:relative +} +.dropdown ul { + display:none +} +.dropdown-smaller { + font-size:0.85em +} +ul.dropdown-submenu-open { + display:block; + position:absolute; + z-index:1000; + min-width:285px; + list-style:none; + margin:3px 0 0 1px; + padding:6px 0; + background-color:#fff; + border:0px solid #b2b2b2; + border-radius:3px; + box-shadow:0 1px 3px rgba(0,0,0,0.15) +} +.dropdown-submenu-open li { + display:block; + margin:0; + padding:8px 10px; + font-size:0.9em; + border-bottom:0px solid #f8f8f8; + cursor:pointer +} +.dropdown-submenu-open li.no-hover { + cursor:default +} +.dropdown-submenu-open li:last-child { + border:none +} +.dropdown-submenu-open li:not(.no-hover):hover { + background:#222; + transition-duration: .05s; + color:#fff +} +.dropdown-submenu-open li:hover a { + color:#fff; + transition-duration: .05s; +} +.dropdown-submenu-open a { + text-decoration:none; + color:#3b3e47; + transition-duration: .05s; +} +.dropdown-submenu-open a:focus { + text-decoration:underline +} +.dropdown-menu-link-text,.dropdown-menu-link-icon { + color:#ced4da; + text-decoration:none +} +.dropdown-menu-link-text:hover { + text-decoration:underline +} +td a.dropdown-menu strong { + color:#ced4da +} +td a.dropdown-menu strong i { + color:#ced4da +} +td a.dropdown-menu i { + color:#ced4da +} +td a.dropdown-menu:hover strong { + color:#555 +} +td a.dropdown-menu:hover strong i { + color:#555 +} +td a.dropdown-menu:hover i { + color:#ced4da +} +.accordion-title { + background: #222; +} +.accordion-title h3 { + display:inline; + padding-right:5px; + background:#222; +} +.accordion-content { + margin-top:15px; + margin-bottom:25px +} +.accordion-toggle { + color:#ced4da; + text-decoration:none +} +.accordion-toggle:focus { + color:#ced4da +} +.accordion-toggle:hover { + color:#999 +} +.accordion-toggle:before { + content:"\f0d7" +} +.accordion-collapsed { + margin-bottom:25px +} +.accordion-collapsed .accordion-toggle:before { + content:"\f0da" +} +.accordion-collapsed .accordion-content { + display:none +} +.select2-container--default .select2-selection--single { + background-color: #3b4658; + border: 1px solid #aaa; + border-radius: 4px; +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: #fff; + line-height: 28px; +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: #3b4658; + color: white; +} +.select2-container--default .select2-results>.select2-results__options { + max-height: 200px; + overflow-y: auto; + background-color: #3b4658; +} +.select2-container--default .select2-results__option[aria-selected="true"] { + background-color: #4e5663!important; + color: #fff!important; +} +#select-dropdown-menu { + position:absolute; + display:block; + z-index:1000; + min-width:160px; + padding:5px 0; + background:#3b4658; + list-style:none; + border:0px solid #ccc; + border-radius:3px; + box-shadow:0 6px 12px rgba(0,0,0,0.175); + overflow-x: hidden; +} +.select-dropdown-menu-item { + white-space:nowrap; + overflow:hidden; + padding:3px 10px; + color:#ced4da; + cursor:pointer; + border-bottom:0px solid #f8f8f8; + line-height:1.5em; + font-weight:400 +} +.select-dropdown-menu-item.active { + color:#fff; + background:#428bca +} +.select-dropdown-menu-item:last-child { + border:none +} +.select-dropdown-input-container { + position:relative; + border:0px solid #ccc; + border-radius:5px; + background-color:#fff +} +.select-dropdown-input-container input.select-dropdown-input { + margin:0 0 0 5px; + border:none; + height:23px +} +.select-dropdown-input-container input.select-dropdown-input:focus { + border:none; + box-shadow:none +} +.select-dropdown-input-container .select-dropdown-chevron { + color:#555; + position:absolute; + top:4px; + right:5px; + cursor:pointer +} +.select-dropdown-input-container .select-loading-icon { + color:#555; + position:absolute; + top:4px; + right:5px; +} +#suggest-menu { + position:absolute; + display:block; + z-index:1000; + min-width:160px; + padding:5px 0; + background:#fff; + list-style:none; + border:0px solid #ccc; + border-radius:3px; + box-shadow:0 6px 12px rgba(0,0,0,0.175) +} +.suggest-menu-item { + white-space:nowrap; + padding:3px 10px; + color:#ced4da; + font-weight:bold; + cursor:pointer +} +.suggest-menu-item.active { + color:#fff; + background:#428bca +} +.suggest-menu-item.active small { + color:#fff +} +.suggest-menu-item small { + color:#999; + font-weight:normal +} +#modal-overlay { + position:fixed; + top:0; + left:0; + width:100%; + height:100%; + background:rgba(0,0,0,0.9); + overflow:auto; + z-index:100 +} +#modal-box { + position:fixed; + max-height:calc(100% - 30px); + top:0%; + padding: 20px; + left:50%; + transform:translateX(-50%); + background:#252525; + overflow:auto; + border-radius:5px +} +#modal-content { + padding:0 5px 5px +} +#modal-header { + text-align:right; + padding-right:5px +} +#modal-close-button { + color:#ced4da +} +#modal-close-button:hover { + color:#b94a48 +} +.pagination { + text-align:center; + font-size:0.9em +} +.pagination-showing { + margin-right:5px; + padding-right:5px; + border-right:0px solid #999 +} +.pagination-next { + margin-left:5px +} +.pagination-previous { + margin-right:5px +} +header { + border-bottom: none; + box-shadow: 0px 1px 3px 0 rgba(46,61,73,.12); + padding: 15px 10px; + margin-bottom: 15px; + background: #303030; +} +.header img { + float: left; +} +.header h2 { + position: relative; + color:#ced4da; + top: 16px; + left: 10px; + margin: 0; +} +header .title-container { + flex:1; + min-width:300px +} +@media (max-width: 480px) { + header .title-container { + order:3 + } +} +header .board-selector-container { + min-width:320px; + display:flex; + align-items:center +} +@media (max-width: 480px) { + header .board-selector-container { + order:2; + min-width:300px + } + header .board-selector-container input[type=text] { + max-width:280px + } +} +header .menus-container { + min-width:120px; + display:flex; + align-items:center; + justify-content:flex-end +} +@media (max-width: 480px) { + header .menus-container { + order:1; + margin-bottom:5px; + margin-left:auto + } +} +header h1 { + font-size:1.33em!important; +} +header h1 .tooltip { + opacity:0.3; + font-size:0.7em +} +a i.web-notification-icon { + color:#00bc8c +} +a i.web-notification-icon:focus,a i.web-notification-icon:hover { + color:#000 +} +.logo a { + opacity:0.5; + color:#d40000; + text-decoration:none +} +.logo span { + color:#ced4da +} +.logo a:hover { + opacity:0.8; + color:#ced4da +} +.logo a:focus span,.logo a:hover span { + color:#d40000 +} +.page-header { + margin-bottom:20px +} +.page-header .dropdown { + padding-right:10px +} +.page-header h2 { + margin:0; + padding:0; + font-weight:bold; + border-bottom:0px dotted #ccc +} +.page-header h2 a { + color:#ced4da; + text-decoration:none +} +.page-header h2 a:focus,.page-header h2 a:hover { + color:#999 +} +.page-header ul { + text-align:left; + margin-top:5px; + display:inline-block +} +.page-header li { + display:inline; + padding-right:15px +} +@media (max-width: 480px) { + .page-header li { + display:block; + line-height:1.5em + } +} +.page-header li.active a { + color:#ced4da; + text-decoration:none; + font-weight:bold +} +.page-header li.active a:hover,.page-header li.active a:focus { + text-decoration:underline +} +.menu-inline { + margin-bottom:5px +} +.menu-inline li { + display:inline; + padding-right:15px +} +.menu-inline li .active a { + font-weight:bold; + color:#000; + text-decoration:none +} +.sidebar-container { + height:100%; + display:flex; + flex-flow:row +} +@media (max-width: 768px) { + .sidebar-container { + flex-flow:wrap + } +} +.sidebar-content { + padding-left:10px; + flex:1 100%; + max-width:85%; + overflow-wrap:break-word +} +@media (max-width: 768px) { + .sidebar-content { + padding-left:0; + order:1; + max-width:100% + } +} +@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) and (-webkit-min-device-pixel-ratio: 1) { + .sidebar-content { + max-width:75% + } +} +.sidebar { + max-width:25%; + min-width:230px; + background-color: #252525; + padding: 20px; +} +@media (max-width: 768px) { + .sidebar { + flex:1 auto; + order:2 + } +} +.sidebar h2 { + margin-top:0 +} +.sidebar>ul a { + text-decoration:none; + color:#ced4da; + font-weight:300 +} +.sidebar>ul a:hover { + color:#ced4da +} +.sidebar>ul li { + list-style-type:none; + line-height:35px; + border-bottom:0px dotted #efefef; + padding-left:13px +} +.sidebar>ul li:hover { + border-left:5px solid #00bc8c; + padding-left:8px +} +.sidebar>ul li.active { + border-left:5px solid #00bc8c; + padding-left:8px +} +.sidebar>ul li.active a { + color:#ced4da; + font-weight:bold +} +.sidebar-icons>ul li { + padding-left:10px +} +.sidebar-icons>ul li:hover,.sidebar-icons>ul li.active { + padding-left: 10px; + border-left: none; + background-color: #3b404b; +} +.sidebar>ul li.active a:focus,.sidebar>ul li.active a:hover { + color:#ced4da +} +.sidebar>ul li:last-child { + margin-bottom:15px +} +.avatar img { + vertical-align:bottom +} +.avatar-left { + float:left; + margin-right:10px +} +.avatar-inline { + display:inline-block; + margin-right:3px +} +.avatar-48 img,.avatar-48 div { + border-radius:30px +} +.avatar-48 .avatar-letter { + line-height:48px; + width:48px; + font-size:25px +} +.avatar-20 img,.avatar-20 div { + border-radius:10px +} +.avatar-20 .avatar-letter { + line-height:20px; + width:20px; + font-size:11px +} +.avatar-letter { + color:#fff; + text-align:center +} +#file-dropzone,#screenshot-zone { + position:relative; + border:2px dashed #ccc; + width:99%; + height:250px; + overflow:auto +} +#file-dropzone-inner,#screenshot-inner { + position:absolute; + left:0; + bottom:48%; + width:100%; + text-align:center; + color:#aaa +} +#screenshot-zone.screenshot-pasted { + border:2px solid #ced4da +} +#file-list { + margin:20px +} +#file-list li { + list-style-type:none; + padding-top:8px; + padding-bottom:8px; + border-bottom:0px dotted #ddd; + width:95% +} +#file-list li .file-error { + font-weight:bold; + color:#b94a48 +} +.file-thumbnails { + display:-webkit-flex; + display:flex; + -webkit-flex-direction:row; + flex-direction:row; + -webkit-flex-wrap:wrap; + flex-wrap:wrap; + -webkit-justify-content:flex-start; + justify-content:flex-start +} +.file-thumbnail { + width:250px; + border:0px solid #efefef; + border-radius:5px; + margin-bottom:20px; + box-shadow:4px 2px 10px -6px rgba(0,0,0,0.55); + margin-right:15px +} +.file-thumbnail img { + cursor:pointer; + border-top-left-radius:5px; + border-top-right-radius:5px +} +.file-thumbnail img:hover { + opacity:0.5 +} +.file-thumbnail-content { + padding-left:8px; + padding-right:8px +} +.file-thumbnail-title { + font-weight:700; + font-size:0.9em; + color:#555; + overflow:hidden; + text-overflow:ellipsis +} +.file-thumbnail-description { + font-size:0.8em; + color:#999; + margin-top:8px; + margin-bottom:5px +} +.file-viewer { + position:relative +} +.file-viewer img { + max-width:95%; + max-height:85%; + margin-top:10px +} +.color-picker { + width:180px +} +.color-picker-option { + height:25px +} +.color-picker-square { + display:inline-block; + width:18px; + height:18px; + margin-right:5px; + border:0px solid #000 +} +.color-picker-label { + display:inline-block; + vertical-align:bottom; + padding-bottom:3px +} +.filter-box { + max-width:1024px +} +.action-menu { + color:#ced4da; + text-decoration:none +} +.action-menu:hover,.action-menu:focus { + text-decoration:underline +} +.js-project-creation-options { + max-width:500px; + border-left:1px solid #6e727b; + margin-top:20px; + padding-left:15px; + padding-bottom:5px; + padding-top:5px +} +.project-overview-columns { + display:-webkit-flex; + display:flex; + -webkit-flex-direction:row; + flex-direction:row; + -webkit-flex-wrap:wrap; + flex-wrap:wrap; + -webkit-align-items:center; + align-items:center; + -webkit-justify-content:center; + justify-content:center; + margin-bottom:20px; + font-size:1.4em +} +@media (max-width: 480px) { + .project-overview-columns { + display:block + } +} +.project-overview-column { + text-align:center; + margin-right:3%; + margin-top:5px; + padding:3px 15px 3px 15px; + border:1px dashed #999 +} +@media (max-width: 480px) { + .project-overview-column { + text-align:left + } +} +.project-overview-column small { + color:#e5e5e5 +} +.project-overview-column strong { + color:#eee; + display:block +} +@media (max-width: 480px) { + .project-overview-column strong { + display:inline + } +} +.project-header { + margin-bottom:8px +} +.project-header .dropdown-component { + margin-top:4px; + margin-right:5px; + float:left +} +@media (max-width: 768px) { + .project-header .dropdown-component { + float:none + } +} +.project-header .views-switcher-component { + margin-top:4px; + float:left +} +@media (max-width: 768px) { + .project-header .views-switcher-component { + float:none; + margin-bottom:10px + } +} +.project-header .filter-box-component form { + margin:0 +} +.views { + margin-right:10px; + margin-top:1px; + font-size:0.9em +} +@media (max-width: 560px) { + .views { + width:100% + } +} +@media (max-width: 768px) { + .views { + margin-top:10px; + font-size:1em + } +} +@media (max-width: 480px) { + .views { + margin-top:5px + } +} +.views li { + white-space:nowrap; + background:rgba(147,128,108,0.1); + border:0px solid #ddd; + border-right:none; + padding:4px 8px; + display:inline +} +@media (max-width: 560px) { + .views li { + display:block; + margin-top:5px; + border-radius:5px; + border:0px solid #ddd + } +} +.views li.active a { + font-weight:bold; + color:#00bc8c; + text-decoration:none +} +.views li:first-child { + border-top-left-radius:5px; + border-bottom-left-radius:5px +} +.views li:last-child { + border-right:0px solid #ddd; + border-top-right-radius:5px; + border-bottom-right-radius:5px +} +.views a { + color:#ced4da; + text-decoration:none +} +.views a:hover { + color:#fff; + text-decoration:none; +} +.dashboard-project-stats small { + margin-right:10px; + color:#999 +} +.dashboard-table-link { + font-weight:bold; + color:#000; + text-decoration:none +} +.dashboard-table-link:focus,.dashboard-table-link:hover { + color:#999 +} +.public-board { + margin-top:5px +} +.public-task { + max-width:800px; + margin:5px auto 0 +} +#board-container { + overflow-x:auto +} +#board { + table-layout:fixed; + margin-bottom:0 +} +#board th.board-column-header { + width:240px +} +#board td { + vertical-align:top +} +.board-container-compact { + overflow-x:initial +} +@media all and (-ms-high-contrast: active), (-ms-high-contrast: none) { + .board-container-compact #board { + table-layout:auto + } +} +#board th.board-column-header.board-column-compact { + width:initial +} +.board-column-collapsed { + display:none +} +td.board-column-task-collapsed { + font-weight:bold; + background-color:#222 +} +#board th.board-column-header-collapsed { + width:28px; + min-width:28px; + text-align:center; + overflow:hidden +} +.board-rotation-wrapper { + position:relative; + padding:8px 4px; + min-height:150px; + overflow:hidden +} +.board-rotation { + white-space:nowrap; + -webkit-backface-visibility:hidden; + -webkit-transform:rotate(90deg); + -moz-transform:rotate(90deg); + -ms-transform:rotate(90deg); + transform:rotate(90deg); + -webkit-transform-origin:0 100%; + -moz-transform-origin:0 100%; + -ms-transform-origin:0 100%; + transform-origin:0 100% +} +.board-column-title .dropdown-menu { + text-decoration:none +} +.board-add-icon { + float:left; + padding:0 5px +} +.board-add-icon i { + text-decoration:none; + color:#00bc8c; + font-size:1.4em +} +.board-add-icon i:focus,.board-add-icon i:hover { + text-decoration:none; + color:#20c997 +} +.board-column-header-task-count { + color:#999; + font-weight:normal +} +a.board-swimlane-toggle { + text-decoration:none +} +a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus { + color:#000; + text-decoration:none; + border:none +} +.board-task-list { + min-height:60px +} +.board-task-list .task-board:last-child { + margin-bottom:0 +} +.board-task-list-limit { + background-color:#DF5353 +} +.draggable-item { + cursor:pointer; + user-select:none; + -webkit-user-select:none; + -moz-user-select:none +} +.draggable-placeholder { + border:2px dashed #000; + background:#fafafa; + height:70px; + margin-bottom:10px +} +div.draggable-item-selected { + border:0px solid #000 +} +.task-board-sort-handle { + float:left; + padding-right:5px +} +.task-board { + position:relative; + margin-bottom:4px; + border:2px solid; + padding:10px; + word-wrap:break-word; + font-size:0.9em; + border-radius:6px; + background-color:#252525!important; +} +div.task-board-recent { + border-width:2px +} +div.task-board-status-closed { + user-select:none; + border:0px dotted #555 +} +.task-board a { + color:#00bc8c; + text-decoration:none +} +.task-board-collapsed { + overflow:hidden; + white-space:nowrap; + text-overflow:ellipsis +} +.task-board-title { + margin-top:5px; + margin-bottom:8px +} +.task-board-title a:hover { + text-decoration:underline +} +.task-board-saving-state { + opacity:0.3 +} +.task-board-saving-icon { + position:absolute; + margin:auto; + width:100%; + text-align:center; + color:#000 +} +.task-board-avatars { + text-align:right; + float:right +} +.task-board-change-assignee { + cursor:pointer +} +.task-board-change-assignee:hover { + opacity:0.6 +} +.task-list-avatars { + display:inline-block; + float:left +} +.c3-chart-arc path { + stroke: #fff0; +} +@media (max-width: 768px) { + .task-list-avatars { + float:none; + display:block + } +} +.task-list-avatars .task-avatar-assignee { + font-weight:300; + color:#999 +} +.task-list-avatars:hover .task-avatar-assignee { + font-weight:400; + color:#000 +} +.task-board-icons,.task-list-icons { + font-size:0.8em; + text-align:right +} +.task-board-icons a,.task-list-icons a { + text-decoration:none +} +.task-board-icons a:hover,.task-list-icons a:hover { + color:#ced4da +} +.task-board-icons a:hover i,.task-list-icons a:hover i { + color:#ced4da +} +.task-board-icons .task-score,.task-list-icons .task-score { + font-weight:bold +} +.task-board-icons .flag-milestone,.task-list-icons .flag-milestone { + color:green +} +.task-board-icons { + margin-top:7px +} +.task-board-icons a { + opacity:0.5; +} +.task-board-icons span { + opacity:0.5; + margin-left:4px; + color:#97d2ff; +} +.task-board-icons a:hover { + opacity:1.0; + font-weight:bold +} +.task-board-icons .task-board-icons-row { + line-height:22px +} +.task-list-icons { + line-height:22px +} +.task-list-icons a,.task-list-icons span,.task-list-icons i { + color:#999; + opacity:1.0 +} +.task-list-icons span { + margin-left:5px +} +@media (max-width: 768px) { + .task-list-icons { + text-align:left + } +} +.task-icon-age { + display:inline-block +} +span.task-icon-age-total { + border:0px solid #e5e5e5; + padding:1px 3px 1px 3px; + border-top-left-radius:3px; + border-bottom-left-radius:3px +} +span.task-icon-age-column { + border:0px solid #e5e5e5; + border-left:none; + margin-left:-5px; + padding:1px 3px 1px 3px; + border-top-right-radius:3px; + border-bottom-right-radius:3px +} +.task-board span.task-icon-age-total,.task-board span.task-icon-age-column { + border-color:#666 +} +.task-board-category-container { + text-align:right; + margin-top:8px; + margin-bottom:8px +} +.task-board-category { + border:0px solid #555; + font-size:0.9em; + font-weight:500; + color:#000; + padding:1px 3px 1px 2px; + border-radius:3px +} +.task-board-category a:hover { + text-decoration:underline +} +.task-date { + font-weight:500; + color:#000 +} +span.task-date-today { + opacity:1.0; + color:#00bc8c +} +span.task-date-overdue { + opacity:1.0; + color:#b94a48 +} +.task-tags li { + display:inline-block; + margin:3px 3px 0 0; + padding:1px 3px 1px 3px; + border:0px solid #ced4da; + border-radius:4px; + background:#20c997; +} +.select2-container--default .select2-results__option[aria-selected="true"] { + background-color: #e5e5e5; + color: #333; +} +.task-summary-container .task-tags { + margin-top:10px +} +.task-list-tag { + background:#20c997; + border-color:#20c997; + padding-left: 3px; +} +#task-summary { + margin-bottom:15px +} +#task-summary h2 { + color:#f7f7f7; + font-size:1.6em; + margin-top:0; + padding-top:0 +} +.task-summary-container { + border: 2px solid; + border-radius: 8px; + padding: 10px; + background-color:#29303e!important; +} +.task-summary-columns { + display:flex; + flex-flow:row; + justify-content:space-between +} +@media (max-width: 768px) { + .task-summary-columns { + flex-flow:column + } +} +.task-summary-column { + color:#ced4da +} +.task-summary-column span { + color:#ced4cb +} +.task-summary-column li { + line-height:23px +} +#external-task-view { + padding:10px; + margin-top:10px; + margin-bottom:10px; + border:0px dotted #ccc +} +.task-form-container { + box-sizing:border-box; + display:flex; + flex-wrap:wrap +} +.task-form-container>* { + box-sizing:border-box +} +.task-form-container>* { + width:1% +} +.task-form-main-column { + width:60% +} +@media (max-width: 1000px) { + .task-form-main-column { + width:100% + } +} +.task-form-main-column input[type="text"] { + width:700px; + max-width:99% +} +.task-form-secondary-column { + max-width:250px; + min-width:200px; + max-height:600px; + padding-left:10px; + overflow:auto; + width:20% +} +@media (max-width: 1000px) { + .task-form-secondary-column { + width:100%; + max-width:99%; + max-height:none + } +} +@media (max-width: 768px) { + .task-form-secondary-column { + padding-left:0 + } +} +.task-form-secondary-column label:first-child { + margin-top:0 +} +@media (max-width: 1000px) { + .task-form-secondary-column label:first-child { + margin-top:10px + } +} +.task-form-bottom { + width:100% +} +.comment-sorting { + text-align:right +} +.comment-sorting a { + color:#f7f7f7; + font-weight:normal; + text-decoration:none +} +.comment-sorting a:hover { + color:#999 +} +.comment { + padding:5px; + margin-bottom:15px +} +.comment-title { + border-bottom:0px dotted #eee; + margin-left:55px +} +.comment-date { + color:#999; + font-weight:200 +} +.comment-actions { + text-align:right +} +.comment-content { + margin-left:55px +} +.comments .text-editor textarea { + height:90px +} +.comments .text-editor .text-editor-preview-area { + height:90px +} +.comments .comment-highlighted { + background-color:#252525; + border:2px solid #ffeb8e +} +.comments .comment-highlighted:hover { + background-color:#252525 +} +.comments .comment:hover { + background:#252525 +} +.comments .comment:nth-child(even):not(.comment-highlighted) { + background:#222 +} +.comments .comment:nth-child(even):not(.comment-highlighted):hover { + background:#252525 +} +.subtask-cell { + padding:4px 10px; + border-top:0px dotted #222; + border-left:0px dotted #222; + display:table-cell; + vertical-align:middle; +} +.subtask-cell a { + color:#ced4da; + text-decoration:none +} +.subtask-cell a:hover,.subtask-cell a:focus { + color:#00bc8c; +} +.subtask-cell:first-child { + border-left:none +} +@media (max-width: 768px) { + .subtask-cell { + width:90%; + display:block; + border-left:none + } +} +.task-list-subtasks { + display:table; + width:100% +} +@media (max-width: 768px) { + .task-list-subtasks { + display:block + } +} +.task-list-subtask { + display:table-row +} +@media (max-width: 768px) { + .task-list-subtask { + display:block + } +} +@media (max-width: 768px) { + .subtask-assignee,.subtask-time-tracking-cell { + display:none + } +} +.task-links-table td { + vertical-align:middle +} +.task-links-task-count { + color:#999; + font-weight:normal +} +.task-link-closed { + text-decoration:line-through +} +.text-editor { + margin-top:10px +} +.text-editor-toolbar { + margin-bottom: 10px; +} +.text-editor a { + font-size:1em; + color:#999; + text-decoration:none; + margin-right:10px +} +.text-editor a:hover { + color:#00bc8c +} +.text-editor .text-editor-preview-area { + border:0px solid #222; + width:700px; + max-width:99%; + height:250px; + overflow:auto; + padding:2px +} +.text-editor textarea { + width:700px; + max-width:98%; + height:250px +} +.markdown { + line-height:1.4em +} +.markdown h1 { + margin-top:5px; + margin-bottom:10px; + font-weight:bold +} +.markdown h2 { + font-weight:bold +} +.markdown p { + margin-bottom:10px +} +.markdown ol,.markdown ul { + margin-left:25px; + margin-top:10px; + margin-bottom:10px +} +.markdown pre { + background:#242729; + padding:10px; + border-radius:5px; + border:0px solid #ddd; + overflow:auto; + overflow-wrap:initial; + color:#555 +} +.markdown blockquote { + font-style:italic; + border-left:3px solid #ddd; + padding-left:10px; + margin-bottom:10px; + margin-left:20px +} +.markdown img { + display:block; + max-width:80%; + margin-top:10px +} +.documentation { + margin:0 auto; + padding:20px; + max-width:850px; + background:#fefefe; + border:0px solid #ccc; + border-radius:5px; + color:#555 +} +.documentation img { + border:0px solid #ced4da +} +.documentation h1 { + text-decoration:none; + margin-bottom:30px +} +.documentation h2 { + text-decoration:none; + border-bottom:0px solid #ccc; + margin-bottom:25px +} +.documentation li { + line-height:30px +} +.panel { + border-radius:4px; + padding:8px 35px 8px 10px; + margin-top:10px; + margin-bottom:15px; + border:0px solid #ddd; + color:#ced4da; + background-color:#252525; + overflow:auto +} +.panel li { + list-style-type:square; + margin-left:20px; + line-height:1.35em +} +.activity-event { + margin-bottom:15px; + padding:10px +} +.activity-event:nth-child(even) { + background:#252525 +} +.activity-event:hover { + background:#252525 +} +.activity-date { + margin-left:10px; + font-weight:normal; + color:#999 +} +.activity-content { + margin-left:55px +} +.activity-title { + font-weight:bold; + color:#ced4da; + border-bottom:0px dotted #efefef +} +.activity-description { + color:#f7f7f7; + margin-top:10px +} +@media (max-width: 480px) { + .activity-description { + overflow:auto + } +} +.activity-description li { + list-style-type:circle +} +.activity-description ul { + margin-top:10px; + margin-left:20px +} +.user-mention-link { + font-weight:bold; + color:#5897fb; + text-decoration:none +} +.user-mention-link:hover { + color:#fff +} +.image-slideshow-overlay { + position:fixed; + top:0; + left:0; + width:100%; + height:100%; + background:rgba(0,0,0,0.95); + overflow:auto; + z-index:100 +} +.image-slideshow-overlay img { + display:block; + margin:auto; + max-width: 100%; +} +.image-slideshow-overlay figcaption { + color:#fff; + opacity:0.7; + position:absolute; + bottom:5px; + right:15px +} +.slideshow-icon { + color:#fff; + position:absolute; + font-size:2.5em; + opacity:0.6 +} +.slideshow-icon:hover { + opacity:0.9; + cursor:pointer +} +.slideshow-previous-icon { + left:10px; + top:45% +} +.slideshow-next-icon { + right:10px; + top:45% +} +.slideshow-close-icon { + right:10px; + top:10px; + font-size:1.4em +} +.slideshow-download-icon { + left:10px; + bottom:10px; + font-size:1.3em +} +.list-item-links,.list-item-actions { + display:inline-block; + float:left; + margin-left:10px +} +.list-item-links a { + margin:0 +} +.list-item-action-hidden { + display:none +} +.bulk-change-checkbox { + float:left +} +.bulk-change-inputs { + float:left; + padding-left:10px +} +.bulk-change-inputs label { + margin-top:0; + margin-bottom:3px +} +/* style plugin-gantt */ +div.ganttview-hzheader-month, div.ganttview-hzheader-day, div.ganttview-vtheader, div.ganttview-vtheader-item-name, div.ganttview-vtheader-series, div.ganttview-grid, div.ganttview-grid-row-cell { + float: left; +} + +div.ganttview-hzheader-month, div.ganttview-hzheader-day { + text-align: center; +} + +div.ganttview-grid-row-cell.last, div.ganttview-hzheader-day.last, div.ganttview-hzheader-month.last { + border-right: none; +} + +div.ganttview { + border: 1px solid #999; +} + +div.ganttview-hzheader-month { + width: 60px; + height: 20px; + border-right: 1px solid #d0d0d0; + line-height: 20px; + overflow: hidden; +} + +div.ganttview-hzheader-day { + width: 20px; + height: 20px; + border-right: 1px solid #f0f0f0; + border-top: 1px solid #d0d0d0; + line-height: 20px; + color: #f5f5f5!important; +} + +div.ganttview-vtheader { + margin-top: 41px; + width: 400px; + overflow: hidden; + background-color: #fff; +} + +div.ganttview-vtheader-item { + color: #555; +} + +div.ganttview-vtheader-series-name { + width: 400px; + height: 31px; + line-height: 31px; + padding-left: 3px; + color: #eee!important; + background: #2f3948!important; + border-top: 1px solid #d0d0d0; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +div.ganttview-vtheader-series-name a { + color: #f5f5f5!important; + text-decoration: none; +} + +div.ganttview-vtheader-series-name a:hover { + color: #333; + text-decoration: underline; +} + +div.ganttview-vtheader-series-name a i { + color: #f5f5f5!important; +} + +div.ganttview-vtheader-series-name a:hover i { + color: #eee!important; +} + +div.ganttview-slide-container { + overflow: auto; + border-left: 1px solid #999; +} + +div.ganttview-grid-row-cell { + width: 20px; + height: 31px; + border-right: 1px solid #f0f0f0; + border-top: 1px solid #f0f0f0; +} + +div.ganttview-grid-row-cell.ganttview-weekend { + background-color: #2f3948!important; +} + +div.ganttview-grid-row-cell.ganttview-today { + background-color: #0979b6!important; +} + +div.ganttview-blocks { + margin-top: 40px; +} + +div.ganttview-block-container { + height: 28px; + padding-top: 4px; +} + +div.ganttview-block { + position: relative; + height: 25px; + background-color: #E5ECF9; + border: 1px solid #c0c0c0; + border-radius: 3px; +} + +.ganttview-block-movable { + cursor: move; +} + +div.ganttview-block-text { + position: absolute; + height: 12px; + font-size: 0.7em; + color: #666; + padding: 2px 3px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +div.ganttview-block div.ui-resizable-handle.ui-resizable-s { + bottom: 0; +} + +div.ganttview-progress-bar { + z-index: 0; + position: absolute; + top: 0; + bottom: 0; + opacity: 0.4; +} diff --git a/plugins/Customizer/Assets/css/themes/Github.css b/plugins/Customizer/Assets/css/themes/Github.css new file mode 100644 index 00000000..5fe5a3d3 --- /dev/null +++ b/plugins/Customizer/Assets/css/themes/Github.css @@ -0,0 +1,3260 @@ +/*! + * Derived from Moon - Theme for Kanboard by kenlog + * Licensed under the MIT license - Moon/LICENSE + * https://github.com/kenlog/Moon + * Copyright (c) 2018 Valentino Pesce - https://iltuobrand.it + */ + +h1, +li, +ul, +ol, +table, +tr, +td, +th, +p, +blockquote, +body { + margin: 0; + padding: 0; + font-size: 100% +} + +body { + padding-bottom: 10px; + color: #8d9498; + background-color: #fefefe; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + text-rendering: optimizeLegibility +} + +::selection{background:rgba(113,113,113,0.5);color:#ffffff}::-moz-selection{background:rgba(113,113,113,0.5);color:#ffffff} + +small { + font-size: 0.8em +} + +hr { + border: 0; + height: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid rgba(255, 255, 255, 0.3) +} + +.page { + margin-left: 10px; + margin-right: 10px +} + +.margin-top { + margin-top: 20px +} + +.margin-bottom { + margin-bottom: 20px +} + +.pull-right { + text-align: right +} + +ul.no-bullet li { + list-style-type: none; + margin-left: 0 +} + +#app-loading-icon { + position: fixed; + right: 3px; + bottom: 3px +} + +.assign-me { + vertical-align: bottom +} + +a { + color: #0366d6; + border: none; + text-decoration: none; +} + +a:focus { + outline: 0; + color: #0366d6; + text-decoration: none +} + +a:hover { + color: #333; + text-decoration: none +} + +a .fa { + padding-right: 3px; + text-decoration: none; + color: #acafb1 +} + +h1, +h2, +h3 { + font-weight: normal; + color: #333 +} + +h1 { + font-size: 1.5em +} + +h2 { + font-size: 1.4em; + margin-bottom: 10px +} + +h3 { + margin-top: 10px; + font-size: 1.2em +} + +table { + width: 100%; + border-collapse: collapse; + border-spacing: 0; + margin-bottom: 20px +} + +table.table-fixed { + table-layout: fixed; + white-space: nowrap +} + +table.table-fixed th { + overflow: hidden +} + +table.table-fixed td { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis +} + +table.table-small { + font-size: 0.8em +} + +table.table-striped tr:nth-child(odd) { + background: #fefefe +} + +@media (max-width: 768px) { + table.table-scrolling { + overflow-x: auto; + display: inline-block; + vertical-align: top; + max-width: 100%; + white-space: nowrap + } +} + +table th { + text-align: left; + padding: 0.5em 5px; + border: 1px solid #eee; + background: #fafbfc; +} + +table th a { + text-decoration: none; + color: #333 +} + +table th a:focus, +table th a:hover { + text-decoration: underline +} + +table td { + border: 1px solid #f5f5f5; + padding: 0.5em 7px; + vertical-align: top +} + +table td li { + margin-left: 20px +} + +.column-1 { + width: 1% +} + +.column-2 { + width: 2% +} + +.column-3 { + width: 3% +} + +.column-4 { + width: 4% +} + +.column-5 { + width: 5% +} + +.column-6 { + width: 6% +} + +.column-7 { + width: 7% +} + +.column-8 { + width: 8% +} + +.column-9 { + width: 9% +} + +.column-10 { + width: 10% +} + +.column-11 { + width: 11% +} + +.column-12 { + width: 12% +} + +.column-13 { + width: 13% +} + +.column-14 { + width: 14% +} + +.column-15 { + width: 15% +} + +.column-16 { + width: 16% +} + +.column-17 { + width: 17% +} + +.column-18 { + width: 18% +} + +.column-19 { + width: 19% +} + +.column-20 { + width: 20% +} + +.column-21 { + width: 21% +} + +.column-22 { + width: 22% +} + +.column-23 { + width: 23% +} + +.column-24 { + width: 24% +} + +.column-25 { + width: 25% +} + +.column-26 { + width: 26% +} + +.column-27 { + width: 27% +} + +.column-28 { + width: 28% +} + +.column-29 { + width: 29% +} + +.column-30 { + width: 30% +} + +.column-31 { + width: 31% +} + +.column-32 { + width: 32% +} + +.column-33 { + width: 33% +} + +.column-34 { + width: 34% +} + +.column-35 { + width: 35% +} + +.column-36 { + width: 36% +} + +.column-37 { + width: 37% +} + +.column-38 { + width: 38% +} + +.column-39 { + width: 39% +} + +.column-40 { + width: 40% +} + +.column-41 { + width: 41% +} + +.column-42 { + width: 42% +} + +.column-43 { + width: 43% +} + +.column-44 { + width: 44% +} + +.column-45 { + width: 45% +} + +.column-46 { + width: 46% +} + +.column-47 { + width: 47% +} + +.column-48 { + width: 48% +} + +.column-49 { + width: 49% +} + +.column-50 { + width: 50% +} + +.column-51 { + width: 51% +} + +.column-52 { + width: 52% +} + +.column-53 { + width: 53% +} + +.column-54 { + width: 54% +} + +.column-55 { + width: 55% +} + +.column-56 { + width: 56% +} + +.column-57 { + width: 57% +} + +.column-58 { + width: 58% +} + +.column-59 { + width: 59% +} + +.column-60 { + width: 60% +} + +.column-61 { + width: 61% +} + +.column-62 { + width: 62% +} + +.column-63 { + width: 63% +} + +.column-64 { + width: 64% +} + +.column-65 { + width: 65% +} + +.column-66 { + width: 66% +} + +.column-67 { + width: 67% +} + +.column-68 { + width: 68% +} + +.column-69 { + width: 69% +} + +.column-70 { + width: 70% +} + +.column-71 { + width: 71% +} + +.column-72 { + width: 72% +} + +.column-73 { + width: 73% +} + +.column-74 { + width: 74% +} + +.column-75 { + width: 75% +} + +.column-76 { + width: 76% +} + +.column-77 { + width: 77% +} + +.column-78 { + width: 78% +} + +.column-79 { + width: 79% +} + +.column-80 { + width: 80% +} + +.column-81 { + width: 81% +} + +.column-82 { + width: 82% +} + +.column-83 { + width: 83% +} + +.column-84 { + width: 84% +} + +.column-85 { + width: 85% +} + +.column-86 { + width: 86% +} + +.column-87 { + width: 87% +} + +.column-88 { + width: 88% +} + +.column-89 { + width: 89% +} + +.column-90 { + width: 90% +} + +.column-91 { + width: 91% +} + +.column-92 { + width: 92% +} + +.column-93 { + width: 93% +} + +.column-94 { + width: 94% +} + +.column-95 { + width: 95% +} + +.column-96 { + width: 96% +} + +.column-97 { + width: 97% +} + +.column-98 { + width: 98% +} + +.column-99 { + width: 99% +} + +.column-100 { + width: 100% +} + +.draggable-row-handle { + cursor: move; + color: #dedede +} + +.draggable-row-handle:hover { + color: #333 +} + +tr.draggable-item-selected { + background: #fff; + border: 2px solid #666; + box-shadow: 4px 2px 10px -4px rgba(0, 0, 0, 0.55) +} + +tr.draggable-item-selected td { + border-top: none; + border-bottom: none +} + +tr.draggable-item-selected td:first-child { + border-left: none +} + +tr.draggable-item-selected td:last-child { + border-right: none +} + +.table-stripped tr.draggable-item-hover, +.table-stripped tr.draggable-item-hover { + background: #FEFFF2 +} + +.table-list { + font-size: 0.85em; + margin-bottom: 20px +} + +.table-list-header { + background: #f6f8fa; + border: 1px solid #d1d5da; + border-radius: 3px 3px 0px 0px; + line-height: 28px; + padding-left: 7px; + padding-right: 7px; +} + +.table-list-header a { + color: #333; + font-weight: 500; + text-decoration: none; + margin-right: 10px +} + +.table-list-header a:hover, +.table-list-header a:focus { + color: #767676 +} + +.table-list-header .table-list-header-count { + color: #767676; + display: inline-block; + float: left +} + +.table-list-header .table-list-header-menu { + text-align: right +} + +.table-list-row { + padding-left: 7px; + padding-right: 7px; + border-bottom: 1px solid #e5e5e5; + border-right: 1px solid #e5e5e5 +} + +.table-list-row.table-border-left { + border-left: 1px solid #e5e5e5 +} + +.table-list-row:nth-child(odd) { + background: #fefefe +} + +.table-list-row:last-child { + border-radius: 0 0 5px 5px +} + +.table-list-row:hover { + background: #f6f8fa; + border-bottom: 1px solid #f6f8fa; + border-right: 1px solid #f6f8fa +} + +.table-list-row .table-list-title { + font-weight: 500; + line-height: 23px +} + +.table-list-row .table-list-title.status-closed { + text-decoration: line-through; + margin-right: 10px +} + +.table-list-row .table-list-title.status-closed a { + font-style: italic +} + +.table-list-row .table-list-title a { + color: #333; + text-decoration: none +} + +.table-list-row .table-list-title a:hover, +.table-list-row .table-list-title a:focus { + text-decoration: underline +} + +.table-list-row .table-list-details { + color: #999; + font-weight: 300; + line-height: 20px +} + +.table-list-row .table-list-details span { + margin-left: 5px +} + +.table-list-row .table-list-details span:first-child { + margin-left: 0 +} + +.table-list-row .table-list-details li { + display: inline; + list-style-type: none +} + +.table-list-row .table-list-details li:after { + content: ', ' +} + +.table-list-row .table-list-details li:last-child:after { + content: '' +} + +.table-list-row .table-list-details strong { + font-weight: 400; + color: #555 +} + +.table-list-row .table-list-details-with-icons { + float: left +} + +@media (max-width: 768px) { + .table-list-row .table-list-details-with-icons { + float: none + } +} + +.table-list-row .table-list-icons { + font-size: 0.8em; + text-align: right; + line-height: 30px +} + +@media (max-width: 768px) { + .table-list-row .table-list-icons { + text-align: left; + line-height: 20px + } +} + +.table-list-row .table-list-icons span { + margin-left: 5px +} + +.table-list-row .table-list-icons a { + text-decoration: none +} + +.table-list-row .table-list-icons a:hover { + color: #333 +} + +.table-list-row .table-list-icons a:hover i { + color: #333 +} + +.table-list-category { + font-size: 0.9em; + font-weight: 500; + color: #000; + padding: 1px 2px 1px 2px; + border-radius: 3px; + background: #fcfcfc; + border: 1px solid #ccc +} + +.table-list-category a { + text-decoration: none; + color: #000 +} + +.table-list-category a:hover { + color: #0366d6 +} + +fieldset { + border: 1px solid #ddd; + margin-top: 10px +} + +legend { + font-weight: 500; + font-size: 1.2em +} + +label { + cursor: pointer; + display: block; + margin-top: 10px; + font-weight: 400 +} + +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #f9f9f9; + border: 1px solid #ccc; + border-radius: 4px; + cursor: default; + float: left; + margin-right: 5px; + margin-top: 5px; + padding: 0 5px; +} + +input[type="number"], +input[type="date"], +input[type="email"], +input[type="password"], +input[type="text"]:not(.input-addon-field) { + color: #999; + border: 1px solid #ccc; + width: 300px; + max-width: 95%; + font-size: 1em; + height: 25px; + border-radius: 3px; + padding-bottom: 0; + padding-left: 4px; + font-family: sans-serif; + -webkit-appearance: none; + -moz-appearance: none +} + +input[type="number"]::-webkit-input-placeholder, +input[type="date"]::-webkit-input-placeholder, +input[type="email"]::-webkit-input-placeholder, +input[type="password"]::-webkit-input-placeholder, +input[type="text"]:not(.input-addon-field)::-webkit-input-placeholder { + color: #dedede +} + +input[type="number"]::-moz-placeholder, +input[type="date"]::-moz-placeholder, +input[type="email"]::-moz-placeholder, +input[type="password"]::-moz-placeholder, +input[type="text"]:not(.input-addon-field)::-moz-placeholder { + color: #dedede +} + +input[type="number"]:-ms-input-placeholder, +input[type="date"]:-ms-input-placeholder, +input[type="email"]:-ms-input-placeholder, +input[type="password"]:-ms-input-placeholder, +input[type="text"]:not(.input-addon-field):-ms-input-placeholder { + color: #dedede +} + +input[type="number"]:focus, +input[type="date"]:focus, +input[type="email"]:focus, +input[type="password"]:focus, +input[type="text"]:focus { + color: #000; + border-color: rgba(82, 168, 236, 0.8); + outline: 0; + box-shadow: 0 0 0 2px rgba(222,222,222,0.25) +} + +input[type="number"] { + width: 70px +} + +input[type="text"]:not(.input-addon-field).form-numeric { + width: 70px +} + +input[type="text"]:not(.input-addon-field).form-datetime, +input[type="text"]:not(.input-addon-field).form-date { + width: 150px +} + +input[type="text"]:not(.input-addon-field).form-input-large { + width: 400px +} + +input[type="text"]:not(.input-addon-field).form-input-small { + width: 150px +} + +textarea:focus { + color: #000; + border-color: rgba(82, 168, 236, 0.8); + outline: 0; + box-shadow: 0 0 0 2px rgba(222,222,222,0.25) +} + +.text-editor-toolbar { + margin-bottom: 10px!important; +} + +textarea { + padding: 4px; + border: 1px solid #ccc; + width: 400px; + max-width: 99%; + height: 200px; + border-radius: 3px; + font-family: sans-serif; + font-size: 1em +} + +textarea::-webkit-input-placeholder { + color: #dedede +} + +textarea::-moz-placeholder { + color: #dedede +} + +textarea:-ms-input-placeholder { + color: #dedede +} + +select { + font-size: 1.0em; + max-width: 95%; + border: 1px solid #AAA; + color: #555; + font-size: inherit; + overflow: hidden; + border-radius: 3px; + padding: 5px 10px; + background-color: #fff; +} + +select:focus { + outline: 0 +} + +select[multiple] { + width: 300px +} + +.tag-autocomplete { + width: 400px +} + +span.select2-container { + margin-top: 2px +} + +.form-actions { + padding-top: 20px; + clear: both +} + +.form-required { + color: red; + padding-left: 5px; + font-weight: bold +} + +@media (max-width: 480px) { + .form-required { + display: none + } +} + +input[type="text"].form-max-width { + width: 100% +} + +input.form-error, +textarea.form-error { + border: 2px solid #b94a48 +} + +input.form-error:focus, +textarea.form-error:focus { + box-shadow: none; + border: 2px solid #b94a48 +} + +.form-errors { + color: #b94a48; + list-style-type: none +} + +ul.form-errors li { + margin-left: 0 +} + +.form-help { + font-size: 0.8em; + color: brown; + margin-bottom: 15px +} + +.form-inline { + padding: 0; + margin: 0; + border: none +} + +.form-inline label { + display: inline; + padding-right: 3px +} + +.form-inline input, +.form-inline select { + margin: 0 15px 0 0 +} + +.form-inline .form-required { + display: none +} + +.form-inline .form-actions { + display: inline-block +} + +.form-inline .js-submit-buttons-rendered { + display: inline-block +} + +.form-inline-group { + display: inline +} + +.form-columns { + display: -webkit-flex; + display: flex; + -webkit-flex-direction: row; + flex-direction: row; + -webkit-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-justify-content: flex-start; + justify-content: flex-start +} + +.form-columns .form-column { + margin-right: 25px; + flex-grow: 1 +} + +.form-columns fieldset { + margin-top: 0 +} + +.form-login { + max-width: 350px; + margin: 5% auto 0 +} + +@media (max-width: 480px) { + .form-login { + margin-left: 5px + } +} + +.form-login li { + margin-left: 25px; + line-height: 25px +} + +.form-login h2 { + margin-bottom: 30px; + font-weight: bold +} + +.reset-password { + margin-top: 20px; + margin-bottom: 20px +} + +.reset-password a { + color: #999 +} + +.input-addon { + display: flex +} + +.input-addon-field { + flex: 1; + font-size: 1em; + color: #999; + margin: 0; + -webkit-appearance: none; + -moz-appearance: none +} + +.input-addon-field:first-child { + border-radius: 5px 0 0 5px +} + +.input-addon-field:last-child { + border-radius: 0 5px 5px 0 +} + +.input-addon-item { + background-image: linear-gradient(-180deg, #fafbfc 0%, #eff3f6 90%); + color: #666; + font: inherit; + font-weight: normal; +} + +.input-addon-item:first-child { + border-radius: 5px 0 0 5px +} + +.input-addon-item:last-child { + border-radius: 0 5px 5px 0 +} + +@media (max-width: 480px) { + .input-addon-item .dropdown .fa-caret-down { + display: none + } +} + +.input-addon-field, +.input-addon-item { + border: 1px solid rgba(147, 128, 108, 0.25); + padding: 4px 0.75em +} + +.input-addon-field:not(:first-child), +.input-addon-item:not(:first-child) { + border-left: 0 +} + +.icon-success { + color: #468847 +} + +.icon-error { + color: #b94a48 +} + +.icon-fade-out { + opacity: 1; + animation: icon-fadeout 5s linear forwards +} + +@keyframes icon-fadeout { + 0% { + opacity: 1 + } + 100% { + opacity: 0 + } +} + +.alert { + padding: 8px 35px 8px 14px; + margin-top: 5px; + margin-bottom: 5px; + color: #464646; + background-color: #FFEB3B; + border: 1px solid #f9df00; + border-radius: 4px +} + +.alert-success { + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6 +} + +.alert-error { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7 +} + +.alert-info { + color: #464646; + background-color: #92ddff; + border-color: #83d8ff; +} + +.alert-normal { + color: #333; + background-color: #f0f0f0; + border-color: #ddd +} + +.alert ul { + margin-top: 10px; + margin-bottom: 10px +} + +.alert li { + margin-left: 25px +} + +.alert-fade-out { + text-align: center; + position: fixed; + bottom: 0; + left: 20%; + width: 60%; + padding-top: 5px; + padding-bottom: 5px; + margin-bottom: 0; + border-width: 1px 0 0; + border-radius: 4px 4px 0 0; + z-index: 9999; + opacity: 1; + animation: fadeout 5s linear forwards +} + +@keyframes fadeout { + 0% { + opacity: 1 + } + 100% { + opacity: 0 + } +} + +a.btn { + text-decoration: none +} + +.btn { + -webkit-appearance: none; + -moz-appearance: none; + font-size: 1.2em; + font-weight: normal; + cursor: pointer; + display: inline-block; + border-radius: 2px; + padding: 3px 10px; + margin: 0; + border: 1px solid #ddd; + background: #f5f5f5; + color: #333 +} + +.btn:hover, +.btn:focus { + background-color: #eee; + color: #000; + border-color: #eee; + border-color: #e5e5e5; +} + +.btn-red { + border-color: #b0281a; + background: #d14836; + color: #fff +} + +.btn-red:hover, +.btn-red:focus { + background-color: #28a745; + background-image: linear-gradient(-180deg, #34d058 0%, #28a745 90%); + color: #fff; + border-color: #28a745; +} + +.btn-blue { + border-color: #3079ed; + background-image: linear-gradient(-180deg, #4d90fe 0%, #335996 90%); + color: #fff +} + +.btn-blue:hover, +.btn-blue:focus { + background-color: #28a745; + background-image: linear-gradient(-180deg, #34d058 0%, #28a745 90%); + color: #fff; + border-color: #28a745; +} + +.btn:disabled { + color: #ccc; + border-color: #ccc; + background: #f7f7f7 +} + +.buttons-header { + font-size: 0.8em; + margin-top: 5px; + margin-bottom: 15px +} + +.tooltip i.fa { + cursor: pointer +} + +.tooltip .fa-info-circle { + color: #999 +} + +#tooltip-container { + padding: 5px; + background: #fff; + border: 1px solid #ddd; + border-radius: 4px; + box-shadow: 0 6px 12px #aaa; + position: absolute; + min-width: 350px +} + +#tooltip-container .markdown p { + margin-bottom: 0 +} + +#tooltip-container .tooltip-large { + width: 600px +} + +h2 .dropdown ul { + display: none +} + +.dropdown { + display: inline; + position: relative +} + +.dropdown ul { + display: none +} + +.dropdown-smaller { + font-size: 0.85em +} + +ul.dropdown-submenu-open { + display: block; + position: absolute; + z-index: 1000; + min-width: 285px; + list-style: none; + margin: 3px 0 0 1px; + padding: 6px 0; + background-color: #fff; + border: 1px solid #b2b2b2; + border-radius: 3px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15) +} + +.dropdown-submenu-open li { + display: block; + margin: 0; + padding: 9px 20px; + font-size: 0.9em; + border-bottom: none; + cursor: pointer; +} + +.dropdown-submenu-open li.no-hover { + cursor: default +} + +.dropdown-submenu-open li:last-child { + border: none +} + +.dropdown-submenu-open li:not(.no-hover):hover { + background: #f6f8fa; + color: #8d9498 +} + +.dropdown-submenu-open li:hover a { + color: #262626 +} + +.dropdown-submenu-open a { + text-decoration: none; + color: #333 +} + +.dropdown-submenu-open a:focus { + text-decoration: underline +} + +.dropdown-menu-link-text, +.dropdown-menu-link-icon { + color: #333; + text-decoration: none +} + +.dropdown-menu-link-text:hover { + text-decoration: underline +} + +td a.dropdown-menu strong { + color: #cb2431 +} + +td a.dropdown-menu strong i { + color: #cb2431 +} + +td a.dropdown-menu i { + color: #dedede +} + +td a.dropdown-menu:hover strong { + color: #555 +} + +td a.dropdown-menu:hover strong i { + color: #555 +} + +td a.dropdown-menu:hover i { + color: #333 +} + +.accordion-title { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAADCAYAAABS3WWCAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NEQ5RDgxQzc2RjQ5MTFFMjhEMUNENzFGRUMwRjhBRTciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NEQ5RDgxQzg2RjQ5MTFFMjhEMUNENzFGRUMwRjhBRTciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0RDlEODFDNTZGNDkxMUUyOEQxQ0Q3MUZFQzBGOEFFNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0RDlEODFDNjZGNDkxMUUyOEQxQ0Q3MUZFQzBGOEFFNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvXFWFAAAAAYSURBVHjaYvj//z8D0/Pnz/8zgFgAAQYAS5UJscReGMIAAAAASUVORK5CYII=) repeat-x scroll 0 10px +} + +.accordion-title h3 { + display: inline; + padding-right: 5px; + background: #fff +} + +.accordion-content { + margin-top: 15px; + margin-bottom: 25px +} + +.accordion-toggle { + color: #333; + text-decoration: none +} + +.accordion-toggle:focus { + color: #333 +} + +.accordion-toggle:hover { + color: #999 +} + +.accordion-toggle:before { + content: "\f0d7" +} + +.accordion-collapsed { + margin-bottom: 25px +} + +.accordion-collapsed .accordion-toggle:before { + content: "\f0da" +} + +.accordion-collapsed .accordion-content { + display: none +} + +#select-dropdown-menu { + position: absolute; + display: block; + z-index: 1000; + min-width: 160px; + padding: 5px 0; + background: #fff; + list-style: none; + border: 1px solid #ccc; + border-radius: 3px; + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + overflow-x: hidden; +} + +.select-dropdown-menu-item { + white-space: nowrap; + overflow: hidden; + padding: 3px 10px; + color: #555; + cursor: pointer; + border-bottom: 1px solid #f8f8f8; + line-height: 1.5em; + font-weight: 400 +} + +.select-dropdown-menu-item.active { + color: #fff; + background: #0366d6 +} + +.select-dropdown-menu-item:last-child { + border: none +} + +.select-dropdown-input-container { + position: relative; + border: 1px solid #ccc; + border-radius: 3px; + background-color: #fff; + max-width: 300px; +} + +.select-dropdown-input-container input.select-dropdown-input { + margin: 0 0 0 5px; + border: none; + height: 23px +} + +.select-dropdown-input-container input.select-dropdown-input:focus { + border: none; + box-shadow: none +} + +.select-dropdown-input-container .select-dropdown-chevron { + color: #555; + position: absolute; + top: 4px; + right: 5px; + cursor: pointer +} + +.select-dropdown-input-container .select-loading-icon { + color: #555; + position: absolute; + top: 4px; + right: 5px +} + +#suggest-menu { + position: absolute; + display: block; + z-index: 1000; + min-width: 160px; + padding: 5px 0; + background: #fff; + list-style: none; + border: 1px solid #ccc; + border-radius: 3px; + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175) +} + +.suggest-menu-item { + white-space: nowrap; + padding: 3px 10px; + color: #333; + font-weight: bold; + cursor: pointer +} + +.suggest-menu-item.active { + color: #fff; + background: #0366d6 +} + +.suggest-menu-item.active small { + color: #fff +} + +.suggest-menu-item small { + color: #999; + font-weight: normal +} + +#modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.9); + overflow: auto; + z-index: 100 +} + +#modal-box { + position: fixed; + max-height: calc(100% - 30px); + top: 0; + padding: 20px; + left: 50%; + transform: translateX(-50%); + background: #fafbfc; + overflow: auto; + border-radius: 0px 0px 3px 3px; +} + +#modal-content { + padding: 0 5px 5px +} + +#modal-header { + text-align: right; + padding-right: 5px +} + +#modal-close-button { + color: #333 +} + +#modal-close-button:hover { + color: #b94a48 +} + +.pagination { + text-align: center; + font-size: 0.9em +} + +.pagination-showing { + margin-right: 5px; + padding-right: 5px; + border-right: 1px solid #999 +} + +.pagination-next { + margin-left: 5px +} + +.pagination-previous { + margin-right: 5px +} + +.header img { + float: left; +} + +.header h2 { + position: relative; + color:#fff; + top: 16px; + left: 10px; + margin: 0; +} + +header { + display: flex; + flex-wrap: wrap; + padding: 5px 10px; + margin-bottom: 5px; + border-bottom: 0px solid #dedede; + padding: 15px 10px; + margin-bottom: 15px; + background: #24292e; +} + +header .title-container { + flex: 1; + min-width: 300px +} + +@media (max-width: 480px) { + header .title-container { + order: 3 + } +} + +header .board-selector-container { + min-width: 320px; + display: flex; + align-items: center +} + +@media (max-width: 480px) { + header .board-selector-container { + order: 2; + min-width: 300px + } + header .board-selector-container input[type=text] { + max-width: 280px + } +} + +header .menus-container { + min-width: 120px; + display: flex; + align-items: center; + justify-content: flex-end +} + +@media (max-width: 480px) { + header .menus-container { + order: 1; + margin-bottom: 5px; + margin-left: auto + } +} + +header h1 { + font-size: 1.4em!important; + color: #dddddd!important +} + +header h1 .tooltip { + opacity: 0.3; + font-size: 0.7em +} + +a i.web-notification-icon { + color: #0366d6 +} + +a i.web-notification-icon:focus, +a i.web-notification-icon:hover { + color: #000 +} + +.logo a { + opacity: 0.5; + color: #d40000; + text-decoration: none +} + +.logo span { + color: #333 +} + +.logo a:hover { + opacity: 0.8; + color: #333 +} + +.logo a:focus span, +.logo a:hover span { + color: #d40000 +} + +.page-header { + margin-bottom: 20px; + padding: 10px; + background-color: #fafbfc; + border-radius: 3px; +} + +.page-header .dropdown { + padding-right: 10px +} + +.page-header h2 { + margin: 0; + padding: 0; + font-weight: bold; + border-bottom: none; +} + +.page-header h2 a { + color: #333; + text-decoration: none +} + +.page-header h2 a:focus, +.page-header h2 a:hover { + color: #999 +} + +.page-header ul { + text-align: left; + margin-top: 5px; + display: inline-block +} + +.page-header li { + display: inline; + padding-right: 15px +} + +@media (max-width: 480px) { + .page-header li { + display: block; + line-height: 1.5em + } +} + +.page-header li.active a { + color: #333; + text-decoration: none; + font-weight: bold +} + +.page-header li.active a:hover, +.page-header li.active a:focus { + text-decoration: underline +} + +.menu-inline { + margin-bottom: 5px +} + +.menu-inline li { + display: inline; + padding-right: 15px +} + +.menu-inline li .active a { + font-weight: bold; + color: #000; + text-decoration: none +} + +.sidebar-container { + height: 100%; + display: flex; + flex-flow: row +} + +@media (max-width: 768px) { + .sidebar-container { + flex-flow: wrap + } +} + +.sidebar-content { + padding-left: 10px; + flex: 1 100%; + max-width: 85%; + overflow-wrap: break-word +} + +@media (max-width: 768px) { + .sidebar-content { + padding-left: 0; + order: 1; + max-width: 100% + } +} + +@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) and (-webkit-min-device-pixel-ratio: 1) { + .sidebar-content { + max-width: 75% + } +} + +.sidebar { + max-width: 25%; + border: 1px solid #d1d5da; + min-width: 230px; + padding: 8px 10px; + border-radius: 3px; + background: #fafbfc; +} + +@media (max-width: 768px) { + .sidebar { + flex: 1 auto; + order: 2 + } +} + +.sidebar h2 { + margin-top: 0 +} + +.sidebar>ul a { + text-decoration: none; + color: #586069; + font-weight: 500 +} + +.sidebar>ul a:hover { + color: #23292d +} + +.sidebar>ul li { + list-style-type: none; + line-height: 35px; + border-bottom: 1px dotted #e5e5e5; + padding-left: 13px +} + +.sidebar>ul li:hover { + border-left: 5px solid #555; + padding-left: 8px +} + +.sidebar>ul li.active { + border-left: 5px solid #23292d; + padding-left: 8px +} + +.sidebar>ul li.active a { + color: #23292d; + font-weight: bold +} + +.sidebar-icons>ul li { + padding-left: 0 +} + +.sidebar-icons>ul li:hover, +.sidebar-icons>ul li.active { + padding-left: 0; + border-left: none +} + +.sidebar>ul li.active a:focus, +.sidebar>ul li.active a:hover { + color: #555 +} + +.sidebar>ul li:last-child { + margin-bottom: 15px +} + +.avatar img { + vertical-align: bottom +} + +.avatar-left { + float: left; + margin-right: 10px +} + +.avatar-inline { + display: inline-block; + margin-right: 3px +} + +.avatar-48 img, +.avatar-48 div { + border-radius: 30px +} + +.avatar-48 .avatar-letter { + line-height: 48px; + width: 48px; + font-size: 25px +} + +.avatar-20 img, +.avatar-20 div { + border-radius: 10px +} + +.avatar-20 .avatar-letter { + line-height: 20px; + width: 20px; + font-size: 11px +} + +.avatar-letter { + color: #fff; + text-align: center +} + +#file-dropzone, +#screenshot-zone { + position: relative; + border: 2px dashed #ccc; + width: 99%; + height: 250px; + overflow: auto +} + +#file-dropzone-inner, +#screenshot-inner { + position: absolute; + left: 0; + bottom: 48%; + width: 100%; + text-align: center; + color: #aaa +} + +#screenshot-zone.screenshot-pasted { + border: 2px solid #333 +} + +#file-list { + margin: 20px +} + +#file-list li { + list-style-type: none; + padding-top: 8px; + padding-bottom: 8px; + border-bottom: 1px dotted #ddd; + width: 95% +} + +#file-list li .file-error { + font-weight: bold; + color: #b94a48 +} + +.file-thumbnails { + display: -webkit-flex; + display: flex; + -webkit-flex-direction: row; + flex-direction: row; + -webkit-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-justify-content: flex-start; + justify-content: flex-start +} + +.file-thumbnail { + width: 250px; + border: 1px solid #efefef; + border-radius: 5px; + margin-bottom: 20px; + box-shadow: 4px 2px 10px -6px rgba(0, 0, 0, 0.55); + margin-right: 15px +} + +.file-thumbnail img { + cursor: pointer; + border-top-left-radius: 5px; + border-top-right-radius: 5px +} + +.file-thumbnail img:hover { + opacity: 0.5 +} + +.file-thumbnail-content { + padding-left: 8px; + padding-right: 8px +} + +.file-thumbnail-title { + font-weight: 700; + font-size: 0.9em; + color: #555; + overflow: hidden; + text-overflow: ellipsis +} + +.file-thumbnail-description { + font-size: 0.8em; + color: #999; + margin-top: 8px; + margin-bottom: 5px +} + +.file-viewer { + position: relative +} + +.file-viewer img { + max-width: 95%; + max-height: 85%; + margin-top: 10px +} + +.color-picker { + width: 180px +} + +.color-picker-option { + height: 25px +} + +.color-picker-square { + display: inline-block; + width: 18px; + height: 18px; + margin-right: 5px; + border: 1px solid #000 +} + +.color-picker-label { + display: inline-block; + vertical-align: bottom; + padding-bottom: 3px +} + +.filter-box { + max-width: 1024px +} + +.action-menu { + color: #333; + text-decoration: none +} + +.action-menu:hover, +.action-menu:focus { + text-decoration: underline +} + +.js-project-creation-options { + max-width: 500px; + border-left: 3px solid #efefef; + margin-top: 20px; + padding-left: 15px; + padding-bottom: 5px; + padding-top: 5px +} + +.project-overview-columns { + display: -webkit-flex; + display: flex; + -webkit-flex-direction: row; + flex-direction: row; + -webkit-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-align-items: center; + align-items: center; + -webkit-justify-content: center; + justify-content: center; + margin-bottom: 20px; + font-size: 1.4em +} + +@media (max-width: 480px) { + .project-overview-columns { + display: block + } +} + +.project-overview-column { + text-align: center; + margin-right: 3%; + margin-top: 5px; + padding: 3px 15px 3px 15px; + border: 1px dashed #ddd +} + +@media (max-width: 480px) { + .project-overview-column { + text-align: left + } +} + +.project-overview-column small { + color: #999 +} + +.project-overview-column strong { + color: #555; + display: block +} + +@media (max-width: 480px) { + .project-overview-column strong { + display: inline + } +} + +.project-header { + margin-bottom: 8px +} + +.project-header .dropdown-component { + margin-top: 4px; + margin-right: 5px; + float: left +} + +@media (max-width: 768px) { + .project-header .dropdown-component { + float: none + } +} + +.project-header .views-switcher-component { + margin-top: 4px; + float: left +} + +@media (max-width: 768px) { + .project-header .views-switcher-component { + float: none; + margin-bottom: 10px + } +} + +.project-header .filter-box-component form { + margin: 0 +} + +.views { + margin-right: 10px; + margin-top: 1px; + font-size: 0.9em +} + +@media (max-width: 560px) { + .views { + width: 100% + } +} + +@media (max-width: 768px) { + .views { + margin-top: 10px; + font-size: 1em + } +} + +@media (max-width: 480px) { + .views { + margin-top: 5px + } +} + +.views li { + white-space: nowrap; + background-color: #e6ebf1; + background-image: linear-gradient(-180deg, #f0f3f6 0%, #e6ebf1 90%); + background-position: -.5em; + border-color: rgba(27,31,35,0.35); + border: 1px solid #ddd; + border-right: none; + padding: 4px 8px; + display: inline; +} + +.views li:hover { + white-space: nowrap; + background-image: linear-gradient(-180deg, #fafbfc 0%, #eff3f6 90%); + border: 1px solid #ddd; + border-right: none; + padding: 4px 8px; + display: inline; +} + +@media (max-width: 560px) { + .views li { + display: block; + margin-top: 5px; + border-radius: 5px; + border: 1px solid #ddd + } +} + +.views li.active a { + font-weight: bold; + color: #000; + text-decoration: none +} + +.views li:first-child { + border-top-left-radius: 5px; + border-bottom-left-radius: 5px +} + +.views li:last-child { + border-right: 1px solid #ddd; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px +} + +.views a { + color: #555; + text-decoration: none +} + +.views a:hover { + color: #333; + text-decoration: none; +} + +.dashboard-project-stats small { + margin-right: 10px; + color: #999 +} + +.dashboard-table-link { + font-weight: bold; + color: #000; + text-decoration: none +} + +.dashboard-table-link:focus, +.dashboard-table-link:hover { + color: #999 +} + +.public-board { + margin-top: 5px +} + +.public-task { + max-width: 800px; + margin: 5px auto 0 +} + +#board-container { + overflow-x: auto +} + +#board { + table-layout: fixed; + margin-bottom: 0 +} + +#board th.board-column-header { + width: 240px +} + +#board td { + vertical-align: top +} + +.board-container-compact { + overflow-x: initial +} + +@media all and (-ms-high-contrast: active), +(-ms-high-contrast: none) { + .board-container-compact #board { + table-layout: auto + } +} + +#board th.board-column-header.board-column-compact { + width: initial +} + +.board-column-collapsed { + display: none +} + +td.board-column-task-collapsed { + font-weight: bold; + background-color: #f6f8fa +} + +#board th.board-column-header-collapsed { + width: 28px; + min-width: 28px; + text-align: center; + overflow: hidden +} + +.board-rotation-wrapper { + position: relative; + padding: 8px 4px; + min-height: 150px; + overflow: hidden +} + +.board-rotation { + white-space: nowrap; + -webkit-backface-visibility: hidden; + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); + -webkit-transform-origin: 0 100%; + -moz-transform-origin: 0 100%; + -ms-transform-origin: 0 100%; + transform-origin: 0 100% +} + +.board-column-title .dropdown-menu { + text-decoration: none +} + +.board-add-icon { + float: left; + padding: 0 5px +} + +.board-add-icon i { + text-decoration: none; + color: #279f43; + font-size: 1.4em +} + +.board-add-icon i:focus, +.board-add-icon i:hover { + text-decoration: none; + color: #23292d +} + +.board-column-header-task-count { + color: #999; + font-weight: normal +} + +a.board-swimlane-toggle { + text-decoration: none +} + +a.board-swimlane-toggle:hover, +a.board-swimlane-toggle:focus { + color: #000; + text-decoration: none; + border: none +} + +.board-task-list { + min-height: 60px +} + +.board-task-list .task-board:last-child { + margin-bottom: 0 +} + +.board-task-list-limit { + background-color: #DF5353 +} + +.draggable-item { + cursor: pointer; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none +} + +.draggable-placeholder { + border: 2px dashed #000; + background: #fafafa; + height: 70px; + margin-bottom: 10px +} + +div.draggable-item-selected { + border: 1px solid #000 +} + +.task-board-sort-handle { + float: left; + padding-right: 5px +} + +.task-board { + margin-bottom: 8px; + padding: 12px; + border-radius: 3px; + box-shadow: 0px 1px 1px 0 rgba(46, 61, 73, 0.30); + border-bottom: none; + border-left: none; + border-right: 2px solid; + border-top: none; + background-color: #f9f9f9!important; +} + +div.task-board-recent { + border-width: 2px +} + +div.task-board-status-closed { + user-select: none; + border: 1px dotted #555 +} + +.task-board a { + color: #0366d6; + font-weight: 600; + text-decoration: none +} + +.task-board-collapsed { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis +} + +.task-board-title { + margin-top: 5px; + margin-bottom: 8px +} + +.task-board-title a:hover { + text-decoration: underline +} + +.task-board-saving-state { + opacity: 0.3 +} + +.task-board-saving-icon { + position: absolute; + margin: auto; + width: 100%; + text-align: center; + color: #000 +} + +.task-board-avatars { + text-align: right; + float: right +} + +.task-board-change-assignee { + cursor: pointer +} + +.task-board-change-assignee:hover { + opacity: 0.6 +} + +.task-list-avatars { + display: inline-block; + float: left +} + +@media (max-width: 768px) { + .task-list-avatars { + float: none; + display: block + } +} + +.task-list-avatars .task-avatar-assignee { + font-weight: 300; + color: #999 +} + +.task-list-avatars:hover .task-avatar-assignee { + font-weight: 400; + color: #000 +} + +.task-board-icons, +.task-list-icons { + font-size: 0.8em; + text-align: right +} + +.task-board-icons a, +.task-list-icons a { + text-decoration: none +} + +.task-board-icons a:hover, +.task-list-icons a:hover { + color: #333 +} + +.task-board-icons a:hover i, +.task-list-icons a:hover i { + color: #333 +} + +.task-board-icons .task-score, +.task-list-icons .task-score { + font-weight: bold +} + +.task-board-icons .flag-milestone, +.task-list-icons .flag-milestone { + color: green +} + +.task-board-icons { + margin-top: 7px +} + +.task-board-icons a { + opacity: 0.5 +} + +.task-board-icons span { + opacity: 0.5; + color: #1e252a; + margin-left: 4px +} + +.task-board-icons a:hover { + opacity: 1.0; + font-weight: bold +} + +.task-board-icons .task-board-icons-row { + line-height: 22px +} + +.task-list-icons { + line-height: 22px +} + +.task-list-icons a, +.task-list-icons span, +.task-list-icons i { + color: #999; + opacity: 1.0 +} + +.task-list-icons span { + margin-left: 5px +} + +@media (max-width: 768px) { + .task-list-icons { + text-align: left + } +} + +.task-icon-age { + display: inline-block +} + +span.task-icon-age-total { + border: 1px solid #e5e5e5; + padding: 1px 3px 1px 3px; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px +} + +span.task-icon-age-column { + border: 1px solid #e5e5e5; + border-left: none; + margin-left: -5px; + padding: 1px 3px 1px 3px; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px +} + +.task-board span.task-icon-age-total, +.task-board span.task-icon-age-column { + border-color: #666 +} + +.task-board-category-container { + text-align: right; + margin-top: 8px; + margin-bottom: 8px +} + +.task-board-category { + border: 1px solid #555; + font-size: 0.9em; + font-weight: 500; + color: #000; + padding: 1px 3px 1px 2px; + border-radius: 3px +} + +.task-board-category a:hover { + text-decoration: underline +} + +.task-date { + font-weight: 500; + color: #000 +} + +span.task-date-today { + opacity: 1.0; + color: #0366d6 +} + +span.task-date-overdue { + opacity: 1.0; + color: #b94a48 +} + +.task-tags li { + display: inline-block; + margin: 3px 3px 0 0; + padding: 1px 3px 1px 3px; + color: #333; + border: 1px solid #333; + border-radius: 4px +} + +.task-summary-container .task-tags { + margin-top: 10px +} + +.task-list-tag { + padding: 0.1em 0.9em; + margin: 0 0.5em 0.5em 0; + white-space: nowrap; + background-color: #f9f9f9; + border-radius: 3px; +} + +#task-summary { + margin-bottom: 15px +} + +#task-summary h2 { + color: #555; + font-size: 1.6em; + margin-top: 0; + padding-top: 0 +} + +.task-summary-container { + border: none; + border-radius: 3px; + border-bottom: 5px solid; + padding: 20px; +} + +.task-summary-columns { + display: flex; + flex-flow: row; + justify-content: space-between +} + +@media (max-width: 768px) { + .task-summary-columns { + flex-flow: column + } +} + +.task-summary-column { + color: #333 +} + +.task-summary-column span { + color: #555 +} + +.task-summary-column li { + line-height: 23px +} + +#external-task-view { + padding: 10px; + margin-top: 10px; + margin-bottom: 10px; + border: 1px dotted #ccc +} + +.task-form-container { + box-sizing: border-box; + display: flex; + flex-wrap: wrap +} + +.task-form-container>* { + box-sizing: border-box +} + +.task-form-container>* { + width: 1% +} + +.task-form-main-column { + width: 60% +} + +@media (max-width: 1000px) { + .task-form-main-column { + width: 100% + } +} + +.task-form-main-column input[type="text"] { + width: 700px; + max-width: 99% +} + +.task-form-secondary-column { + max-width: 250px; + min-width: 200px; + max-height: 600px; + padding-left: 10px; + overflow: auto; + width: 20% +} + +@media (max-width: 1000px) { + .task-form-secondary-column { + width: 100%; + max-width: 99%; + max-height: none + } +} + +@media (max-width: 768px) { + .task-form-secondary-column { + padding-left: 0 + } +} + +.task-form-secondary-column label:first-child { + margin-top: 0 +} + +@media (max-width: 1000px) { + .task-form-secondary-column label:first-child { + margin-top: 10px + } +} + +.task-form-bottom { + width: 100% +} + +.comment-sorting { + text-align: right +} + +.comment-sorting a { + color: #555; + font-weight: normal; + text-decoration: none +} + +.comment-sorting a:hover { + color: #999 +} + +.comment { + padding: 5px; + margin-bottom: 15px +} + +.comment-title { + border-bottom: 1px dotted #eee; + margin-left: 55px +} + +.comment-date { + color: #999; + font-weight: 200 +} + +.comment-actions { + text-align: right +} + +.comment-content { + margin-left: 55px +} + +.comments .text-editor textarea { + height: 90px +} + +.comments .text-editor .text-editor-preview-area { + height: 90px +} + +.comments .comment-highlighted { + background-color: #f6f8fa; + border: 2px solid #ffeb8e +} + +.comments .comment-highlighted:hover { + background-color: #f6f8fa +} + +.comments .comment:hover { + background: #f6f8fa +} + +.comments .comment:nth-child(even):not(.comment-highlighted) { + background: #f6f8fa +} + +.comments .comment:nth-child(even):not(.comment-highlighted):hover { + background: #f6f8fa +} + +.subtask-cell { + padding: 4px 10px; + border-top: 1px dotted #dedede; + border-left: 1px dotted #dedede; + display: table-cell; + vertical-align: middle +} + +.subtask-cell a { + color: #333; + text-decoration: none +} + +.subtask-cell a:hover, +.subtask-cell a:focus { + color: #0366d6 +} + +.subtask-cell:first-child { + border-left: none +} + +@media (max-width: 768px) { + .subtask-cell { + width: 90%; + display: block; + border-left: none + } +} + +.task-list-subtasks { + display: table; + width: 100% +} + +@media (max-width: 768px) { + .task-list-subtasks { + display: block + } +} + +.task-list-subtask { + display: table-row +} + +@media (max-width: 768px) { + .task-list-subtask { + display: block + } +} + +@media (max-width: 768px) { + .subtask-assignee, + .subtask-time-tracking-cell { + display: none + } +} + +.task-links-table td { + vertical-align: middle +} + +.task-links-task-count { + color: #999; + font-weight: normal +} + +.task-link-closed { + text-decoration: line-through +} + +.text-editor { + margin-top: 10px +} + +.text-editor a { + font-size: 1em; + color: #999; + text-decoration: none; + margin-right: 10px +} + +.text-editor a:hover { + color: #0366d6 +} + +.text-editor .text-editor-preview-area { + border: none; + width: 700px; + max-width: 99%; + height: 250px; + overflow: auto; + padding: 2px +} + +.text-editor textarea { + width: 700px; + max-width: 98%; + height: 250px; + padding: 10px; +} + +.markdown { + line-height: 1.4em +} + +.markdown h1 { + margin-top: 5px; + margin-bottom: 10px; + font-weight: bold +} + +.markdown h2 { + font-weight: bold +} + +.markdown p { + margin-bottom: 10px +} + +.markdown ol, +.markdown ul { + margin-left: 25px; + margin-top: 10px; + margin-bottom: 10px +} + +.markdown pre { + background: #3333330d; + padding: 10px; + border-radius: 5px; + border: 1px solid #ddd; + overflow: auto; + overflow-wrap: initial; + color: #555 +} + +.markdown blockquote { + font-style: italic; + border-left: 3px solid #ddd; + padding-left: 10px; + margin-bottom: 10px; + margin-left: 20px +} + +.markdown img { + display: block; + max-width: 80%; + margin-top: 10px +} + +.documentation { + margin: 0 auto; + padding: 20px; + max-width: 850px; + background: #fefefe; + border: 1px solid #ccc; + border-radius: 5px; + color: #555 +} + +.documentation img { + border: 1px solid #333 +} + +.documentation h1 { + text-decoration: none; + margin-bottom: 30px +} + +.documentation h2 { + text-decoration: none; + border-bottom: 1px solid #ccc; + margin-bottom: 25px +} + +.documentation li { + line-height: 30px +} + +.panel { + border-radius: 4px; + padding: 8px 35px 8px 10px; + margin-top: 10px; + margin-bottom: 15px; + border: 1px solid #ddd; + color: #333; + background-color: #fcfcfc; + overflow: auto +} + +.panel li { + list-style-type: square; + margin-left: 20px; + line-height: 1.35em +} + +.activity-event { + margin-bottom: 15px; + padding: 10px +} + +.activity-event:nth-child(even) { + background: #fafafa +} + +.activity-event:hover { + background: #f6f8fa +} + +.activity-date { + margin-left: 10px; + font-weight: normal; + color: #999 +} + +.activity-content { + margin-left: 55px +} + +.activity-title { + font-weight: bold; + color: #000; + border-bottom: 1px dotted #efefef +} + +.activity-description { + color: #555; + margin-top: 10px +} + +@media (max-width: 480px) { + .activity-description { + overflow: auto + } +} + +.activity-description li { + list-style-type: circle +} + +.activity-description ul { + margin-top: 10px; + margin-left: 20px +} + +.user-mention-link { + font-weight: bold; + color: #000; + text-decoration: none +} + +.user-mention-link:hover { + color: #555 +} + +.image-slideshow-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.95); + overflow: auto; + z-index: 100 +} + +.image-slideshow-overlay img { + display: block; + margin: auto +} + +.image-slideshow-overlay figcaption { + color: #fff; + opacity: 0.7; + position: absolute; + bottom: 5px; + right: 15px +} + +.slideshow-icon { + color: #fff; + position: absolute; + font-size: 2.5em; + opacity: 0.6 +} + +.slideshow-icon:hover { + opacity: 0.9; + cursor: pointer +} + +.slideshow-previous-icon { + left: 10px; + top: 45% +} + +.slideshow-next-icon { + right: 10px; + top: 45% +} + +.slideshow-close-icon { + right: 10px; + top: 10px; + font-size: 1.4em +} + +.slideshow-download-icon { + left: 10px; + bottom: 10px; + font-size: 1.3em +} + +.list-item-links, +.list-item-actions { + display: inline-block; + float: left; + margin-left: 10px +} + +.list-item-links a { + margin: 0 +} + +.list-item-action-hidden { + display: none +} + +.bulk-change-checkbox { + float: left +} + +.bulk-change-inputs { + float: left; + padding-left: 10px +} + +.bulk-change-inputs label { + margin-top: 0; + margin-bottom: 3px +} + +#to-top { + margin: 0 0px 30px 0px; +} + +#backToTop { + position: fixed; + left: 50%; + bottom: -25px; + display: block; + width: 50px; + height: 50px; + z-index: 10000; + transition: all 0.4s; + -webkit-transition: all 0.4s; + -moz-transition: all 0.4s; + -o-transition: all 0.4s; + -ms-transition: all 0.4s; +} + +#backToTop.topshow { + bottom: 0; +} + +#backToTop span { + position: absolute; + left: -25px; + bottom: -25px; + display: block; + width: 50px; + height: 50px; + background: rgba(50, 50, 50, 0.15); + text-decoration: none; + -webkit-border-radius: 35px; + -moz-border-radius: 35px; + border-radius: 35px; + transition: all 0.4s; + -webkit-transition: all 0.4s; + -moz-transition: all 0.4s; + -o-transition: all 0.4s; + -ms-transition: all 0.4s; +} + +#backToTop span:hover { + bottom: 5px; + background: rgba(70, 70, 70, 0.9); +} + +#backToTop i:before { + position: relative; + left: 12px; + top: 0; + display: block; + margin: 0; + width: 50px; + color: #fff; + font-size: 27px; + -webkit-transition: all 0.6s ease; + -moz-transition: all 0.6s ease; + -ms-transition: all 0.6s ease; + -o-transition: all 0.6s ease; + transition: all 0.6s ease; +} + +#backToTop:hover i:before { + top: 9px; +} diff --git a/plugins/Customizer/Assets/css/themes/KindaDark.css b/plugins/Customizer/Assets/css/themes/KindaDark.css new file mode 100644 index 00000000..542c69f8 --- /dev/null +++ b/plugins/Customizer/Assets/css/themes/KindaDark.css @@ -0,0 +1,685 @@ +/* + * Modified version of "Breathe" Theme for Kanboard + * Licensed under the MIT license - Oxygen/LICENSE + * - https://github.com/kenlog/Oxygen + * Copyright (c) 2019 Samm Du [https://sammdu.com] + */ + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} + +input, +textarea { + background-color: #fff; + color: #000; +} + +::-webkit-input-placeholder { + /* Chrome/Opera/Safari */ + color: #3c3c3c; +} + +::-moz-placeholder { + /* Firefox 19+ */ + color: #3c3c3c; +} + +:-ms-input-placeholder { + /* IE 10+ */ + color: #3c3c3c; +} + +:-moz-placeholder { + /* Firefox 18- */ + color: #3c3c3c; +} + +::placeholder { + color: #3c3c3c; +} + +h1 { + color: #fff; +} + +a { + color: #2c52e3; +} + +.js-modal-medium { + color: #2c52e3; +} + +.js-modal-large { +} + +.page-header ul li .fa { + color: #2c52e3; +} + +.page-header ul li a { + text-decoration: none; +} + +#modal-overlay .fa { + color: #222427; +} + +a .fa { + color: #fff; +} + +.project-header * { + border-radius: 0 !important; +} + +.project-header li .fa { + color: #222427; +} + +.table-list-row .fa { + color: #222427; +} + +.dropdown-submenu-open .fa { + color: #222427; +} + +.project-header .action-menu .fa { + color: #222427; +} + +.text-editor .fa { + color: #222427; +} + +.sidebar .fa { + color: #222427; +} + +.board-column-title .fa { + color: #222427; +} + +.task-board a { + color: #222427; + text-decoration: none; +} + +.sidebar>ul a:hover { + color: #2c52e3; +} + +.logo>a>img { + vertical-align: middle; + padding: 0.5em 1em 0.5em 0.5em; +} + +div.ganttview-vtheader-series-name { + padding: 0 6px; +} + +th, +td { + padding: 10px; +} + +header { + border-bottom: none; + box-shadow: 0px 1px 3px 0 rgba(46, 61, 73, .12); + padding: 1em 0.6em; + margin-bottom: 1em; + background-color: #222427 !important; +} + +.header img { + float: left; +} + +.header h2 { + position: relative; + color: #2c52e3; + top: 13px; + left: 10px; + margin: 0; +} + +header .title-container { + display: flex; + flex-direction: row; + align-items: center; +} + +label { + font-weight: bold; + margin-top: 18px; +} + +.table-list-header { + background: #222427; + border-radius: 0; + border-color: #222427; + line-height: 28px; + padding: 0.2em; +} + +.table-list-header .table-list-header-count { + color: #fff; + display: inline-block; + float: left; + margin-left: 0.4em; +} + +.table-list-header a { + color: #fff; + font-weight: 500; +} + +.table-list-header a:hover { + color: #fff; + font-weight: 500; +} + +.table-list-row:last-child { + border-radius: 0; +} + +.task-board { + margin-bottom: 8px; + padding: 12px; + border-radius: 0; + border-top: none; + border-right: none; + border-left: none; + border-bottom: 0.4em solid rgba(0, 0, 0, 0.3); +} + +div.task-board-recent { + border-width: 0.4em; +} + +.task-board-title { + font-size: 1.25em; + font-weight: 600; +} + +.task-board-header i { + color: #222427 !important; +} + +.task-show-details { + border-radius: 0; + margin-bottom: 20px; +} + +.task-show-details h2 { + background-color: rgba(0, 0, 0, 0.3); + padding: 20px; + border-radius: 0; + color: #fff; +} + +.task-show-details ul { + padding: 20px; +} + +.task-summary-container { + border: none; + border-radius: 0; + border-bottom: 5px solid; + padding: 20px; +} + +.table-small { + font-size: 1em; +} + +table th { + text-align: left; + padding: 0.8em 3px 0.6em 3px; + border: 1px solid #eee; + background: #fbfbfb; +} + +table td { + border: none; + padding: 0.5em 7px; + vertical-align: top; +} + +.sidebar ul { + padding-bottom: 20px; + border-bottom: 1px solid #ddd; + border-radius: 0; +} + +.sidebar ul:last-of-type { + border-bottom: none; +} + +.sidebar-collapse i, +.sidebar-collapsed .sidebar { + background-color: #999; + border-radius: 0; + padding: 5px 10px 5px 8px; +} + +.sidebar-collapse i, +.sidebar-collapsed .sidebar i { + color: #fff; +} + +.sidebar-collapse i:hover, +.sidebar-collapsed .sidebar:hover { + background-color: #ccc; +} + +.page-header { + margin: 15px 0; + background-color: #eee; + padding: 6px 10px 10px 10px; + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; +} + +.page-header li { + font-size: 1.1em; +} + +.page-header h2 { + border-bottom: none; +} + +header .avatar img { + width: 48px; + border-radius: 0; + margin-left: 0.5em; + margin-right: 0.4em; +} + +.menus-container .avatar+i { + display: none; +} + +.listing { + border: none; +} + +.fa-play { + font-size: 1.2em; + background-color: #ddd; + border-radius: 100%; + width: 25px; + height: 28px; + padding: 8px 0 0 15px; + margin-right: 8px; +} + +.fa-play:hover { + background-color: #999; + color: #fff; +} + +.fc-event .fc-content { + padding: 4px 8px; +} + +.board-add-icon { + float: left; + padding: 0 5px; +} + +.board-add-icon i { + text-decoration: none; + color: #289E7B; + font-size: 1.4em; +} + +.board-add-icon i:focus, +.board-add-icon i:hover { + text-decoration: none; + color: #23292d; +} + +.form-column select { + font-size: 1.2em; + margin: 10px 0 2px 0; +} + +.form-column:nth-of-type(2) { + padding: 10px 25px; + background-color: #f7f7f7; +} + +.form-actions { + font-size: 1.2em; +} + +.page-header ul.dropdown-submenu-open { + margin: 10px 0 0 -10px; + padding: 0; +} + +.select-dropdown-input-container { + position: relative; + border: 2px solid #3c3c3c; + width: 20em; + max-width: 20em; + border-radius: 0; + background-color: #222427; + margin-right: 1em; +} + +header input { + color: #fff !important; +} + +.board-selector-container input, +.board-selector-container textarea { + background-color: inherit; + color: #fff; +} + +.select-dropdown-input-container .select-dropdown-chevron { + position: relative; + color: #fff; + top: auto; + right: -5%; +} + +ul.dropdown-submenu-open { + margin: 10px 0 0 -10px; + padding: 0; + background-color: #fff; + border: 1px solid #222427; + border-radius: 0; + box-shadow: 0 0.1em 0.5em rgba(0, 0, 0, 0.5); +} + +.dropdown-submenu-open .no-hover { + cursor: default; + padding: 1em; + font-size: 1em; + background-color: #222427; + color: #fff; +} + +.dropdown-submenu-open li { + padding: 6px 10px; +} + +#select-dropdown-menu { + background-color: #222427; + border-color: #222427; +} + +.select-dropdown-menu-item.active { + color: #fff; + background: #2c52e3; +} + +.views li { + padding: 0.4em; +} + +.filters { + border: none; +} + +.filters ul.dropdown-submenu-open { + margin: 6px 0 0 8px; +} + +.markdown pre { + background: #3333330d; + max-height: 600px; + overflow: auto; + margin-bottom: 1em; + padding: 5px; + color: #242729; + font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif; +} + +a i.web-notification-icon { + color: #D45353; +} + +.fa-play { + font-size: 1.2em; + background-color: #ddd; + border-radius: 100%; + width: 25px; + height: 25px !important; + padding: 9px 3px 0 9px !important; + margin-right: 8px; +} + +#modal-box { + padding: 10px; +} + +.table-list-category { + padding: 2px 2px 2px 5px; +} + +.table-list-row { + transition: 0.15s; +} + +.table-list-row:hover { + background: #f5f5f5; + border: none; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); +} + +.page-header { + margin: 15px 0; + background-color: #ffffff; + padding: 6px 10px 10px 10px; + border-bottom: 1px solid #f7f7f7; + border-top: 1px solid #f7f7f7; +} + +.comments .comment-highlighted { + background-color: #fff; + border: 2px solid #F5E982; + border-radius: 0; +} + +.comments .comment:hover { + background: #f5f5f5; + border-radius: 0; +} + +.comments .comment:nth-child(even):not(.comment-highlighted):hover { + border-radius: 0; + background: #f5f5f5; +} + +.comments .comment:nth-child(even):not(.comment-highlighted) { + background: #fbfbfb; + border-radius: 0; +} + +.sidebar { + background-color: #f7f7f7; + padding: 7px; + border-radius: 0; + border-left: 2px solid #e9e9e9; + box-shadow: 1px 0px 7px 0 rgba(46, 61, 73, .12); +} + +.input-addon * { + border-radius: 0 !important; +} + +input[type="number"]:focus, +input[type="date"]:focus, +input[type="email"]:focus, +input[type="password"]:focus, +input[type="text"]:focus { + color: #000; + border-color: #2c52e3; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(222, 222, 222, 0.25); +} + +textarea:focus { + color: #000; + border-color: #2c52e3; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(222, 222, 222, 0.25); +} + +input[type="number"], +input[type="date"], +input[type="email"], +input[type="password"], +input[type="text"]:not(.input-addon-field) { + padding: 0.4em; + transition: box-shadow 1s; +} + +.select-dropdown-input-container input.select-dropdown-input { + width: 80%; +} + +.text-editor textarea { + padding: 3px; + transition: box-shadow 1s; +} + +.table-list-row { + padding: 0.4em; +} + +select { + border: 1px solid #ccc; + background: #f9f9f9; + padding: 3px; +} + +.btn-blue { + border-color: #2c52e3; + background-color: #2c52e3; + color: #fff; +} + +.btn-blue:hover, +.btn-blue:focus { + border-color: #2c52e3; + background: #ffffff; + color: #2c52e3; +} + +.select2-container--default.select2-container--focus .select2-selection--multiple { + border: solid #2c52e3 1px; + outline: 0; +} + +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: #2c52e3; + color: #fff; +} + +.board-column-header-task-count { + display: none; +} + +.board-add-icon i:focus, +.board-add-icon i:hover { + text-decoration: none; + color: #91C259; +} + +.board-column-title *:hover { + color: #aaa; +} +/* +.board-column-title *:hover { + color: #aaa; +} */ + +.board-add-icon i { + text-decoration: none; + color: #2c52e3; + font-size: 1.4em; +} + +.dropdown-submenu-open li:not(.no-hover):hover { + background: #2c52e3; + color: #fff; +} + +.sidebar>ul li.active a { + color: #2c52e3; + font-weight: bold; +} + +.table-list-row .table-list-title a:hover, +.table-list-row .table-list-title a:focus { + text-decoration: underline; + color: #2c52e3; +} + +.image-slideshow-overlay img { + display: block; + margin: auto; + max-width: 100%; +} + +.input-addon-item { + background-color: #222427; + color: #fff; +} + +span.task-date-overdue { + opacity: 1.0; + color: #e90529; +} + +.board-add-icon i:focus, +.board-add-icon i:hover { + text-decoration: none; + color: #2BBF5F; +} + +span.task-date-today { + opacity: 1.0; + color: #2c52e3; +} + +#task-summary h2 { + color: #222427; +} + +.input-addon { + position: relative; + top: -0.07em; +} + +.input-addon-field, .input-addon-item { + padding: 0.25em 0.75em; +} + +#modal-box { + border-radius: 0; +} + +.selection * { + border-radius: 0 !important; +} + +.task-date { + font-size: 1.5em; + color: #000; +} + +.task-board-icons-row { + font-size: 1.2em; +} + +.sidebar > ul a { + color: #222427; +} diff --git a/plugins/Customizer/Assets/css/themes/Material.css b/plugins/Customizer/Assets/css/themes/Material.css new file mode 100644 index 00000000..706afaf0 --- /dev/null +++ b/plugins/Customizer/Assets/css/themes/Material.css @@ -0,0 +1,1427 @@ +/*! + * Material Like - Theme for Kanboard + * Licensed under the MIT license - kanboard-theme-material-like/LICENSE + * https://github.com/erichk4/kanboard-theme-material-like + * Copyright (c) 2017 Erich Munz + */ + +@import "https://fonts.googleapis.com/css?family=Open+Sans"; + +/* CSSTidy 1.5.2: Mon, 25 Sep 2017 09:04:23 -0700 */ +body +{ + font-family:'Open Sans',sans-serif; + font-size:14px; + background-color:#f1f1f1!important +} + +div#modal-box { + width: max-content!important; +} + +.logo a { + opacity: 1; + color: inherit; + text-decoration: none; +} + +.title-container .logo { + display: block; + margin-bottom: 4px; +} + +.title-container .title:before { + font-family: FontAwesome; + content: "\f054"; + display: inline-block; + padding-right: 2px; + vertical-align: middle; + font-size: 0.6em; +} + +#task-summary h2:before { + font-family: FontAwesome; + content: "\f017"; + display: inline-block; + padding-right: 8px; + vertical-align: middle; + font-size: 0.6em; +} + +@media only screen and (max-width: 1024px) { + body + { + font-size:12px + } +} + +.ui-widget-shadow { + -webkit-box-shadow: 0 0 5px #ccc; + box-shadow: 0 0 5px #ccc; +} + +.ui-widget.ui-widget-content { + border: 1px solid #ccc; +} + +.sidebar-content .task-list-avatars .avatar +{ + border:none; + background:none; + padding:0; + overflow:initial; + margin:0; + margin-right:3px; + margin-top:5px +} + +tr td:last-child { + border-right:1px solid #e5e5e5; +} + +tr:last-child td { + border-bottom:1px solid #e5e5e5; +} + +.sidebar-content .accordion-section +{ + box-shadow:0 1px 1px rgba(0,0,0,.04); + background-color:#fff; + border:1px solid #e5e5e5; + padding:12px +} + +.accordion-section.accordion-collapsed .accordion-title +{ + border-bottom:none +} + +.project-overview-columns +{ + box-shadow:0 1px 1px rgba(0,0,0,.04); + background-color:#fff; + border:1px solid #e5e5e5; + padding:12px +} + +.accordion-section +{ + box-shadow:0 1px 1px rgba(0,0,0,.04); + background-color:#fff; + border:1px solid #e5e5e5; + padding:12px; + margin-bottom:25px +} + +.sidebar-content .accordion-title h3 +{ + margin-top:12px; + background-color:none; + border:none; + box-shadow:none; + position:relative; + padding:12px; + padding-left:0; + margin-bottom:-11px +} + +.sidebar-content .filter-box +{ + float:none; + border:1px solid #e5e5e5; + box-shadow:0 1px 1px rgba(0,0,0,.04); + max-width:inherit; + margin-top:12px +} + +.sidebar-content .filter-box form +{ + margin-top:0 +} + +.filter-box form +{ + border:none; + box-shadow:none +} + +.js-chart-task-time-column-rendered +{ + background:#fff; + margin-top:10px; + padding:20px 0; + margin-bottom:14px; + border:1px solid #e5e5e5; + box-shadow:0 1px 1px rgba(0,0,0,.04) +} + +.panel li +{ + list-style-type:square; + margin-left:20px; + line-height:1.8em +} + +.sidebar-content .activity-event .avatar +{ + border-radius:0; + padding:8px 35px 8px 10px; + margin-top:10px; + margin-bottom:15px; + border:none; + color:#333; + background-color:transparent; + overflow:auto; + box-shadow:none +} + +.project-header +{ + background:#fff; + padding:0; + margin-top:10px; + margin-bottom:10px +} + +.task-board-title +{ + margin-top:5px; + margin-bottom:20px +} + +#modal-overlay +{ + position:fixed; + top:0; + left:0; + width:100%; + height:100%; + background:rgba(0,0,0,0.36); + overflow:auto; + z-index:100 +} + +#modal-box .page-header +{ + box-shadow:none; + background:#fff; + padding:10px 20px 10px 0; + border:none; + border-bottom:1px solid #e5e5e5; + margin-top:0; + margin-bottom:10px +} + +.task-form-secondary-column +{ + max-width:250px; + min-width:200px; + max-height:600px; + overflow:auto; + width:20%; + background:#f2f2f2; + padding:20px +} + +#modal-box form +{ + margin-top:10px; + background-color:#fff; + border:none; + box-shadow:none; + position:relative; + padding-left:0 +} + +.table-list-row:hover +{ + background: rgba(51, 146, 204, 0.1); + border-bottom:none; + border-right:none +} + +.table-list-row.table-border-left +{ + border-left:none +} + +.filter-box +{ + float:right +} + +.project-header .views-switcher-component +{ + margin-top:16px; + float:left +} + +.project-header .dropdown-component +{ + margin-top:16px; + margin-right:5px; + float:left +} + +.sidebar-title +{ + padding:8px +} + +input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]:not(.input-addon-field) +{ + color:#999; + border:2px solid #ccc; + max-width:88.5%; + font-size:1em; + height:25px; + padding:8px; + font-family:inherit +} + +.select-dropdown-input-container input[type="text"] +{ + border:none +} + +.select-dropdown-input-container +{ + position:relative; + border:2px solid #ccc; + border-radius:0 +} + +.select-dropdown-input-container .select-dropdown-chevron +{ + color:#555; + position:absolute; + top:12px; + right:8px; + cursor:pointer +} + +textarea +{ + max-width:99%; + height:200px; + font-size:1em; + color:#999; + border:2px solid #ccc; + width:300px; + padding:8px; + font-family:inherit +} + +select +{ + padding:8px; + max-width:95%; + width:200px; + border:#ccc 2px solid +} + +.sidebar>ul li.active a +{ + color:#3392cc; + font-weight:700 +} + +.sidebar>ul li.active +{ + border-left:5px solid #3392cc; + padding-left:8px +} + +.page-header +{ + margin-top:14px; + margin-bottom:0 +} + +.btn +{ + padding:10px 16px +} + +.page-header,header +{ + border:1px solid #e5e5e5; + box-shadow:0 1px 1px rgba(0,0,0,.04); + background:#fff; + padding:20px +} + +header { + box-sizing: border-box; + display: flex; + flex-wrap: wrap; + margin-top: 10px; + margin-bottom: 10px; + border-bottom: 1px solid #dedede; +} + +header .menus-container +{ + width:10%; + margin-top:12px +} + +header .title-container +{ + width:65%; + margin-top:10px +} + +.sidebar-content fieldset +{ + margin-top:10px; + margin-bottom:10px; + position:relative +} + +.sidebar-content .panel,.sidebar-content fieldset +{ + border:none; + border-bottom:1px solid #efefef +} + +.sidebar-content .avatar,.sidebar-content .panel +{ + border-radius:0; + padding:8px 35px 8px 10px; + margin-top:10px; + margin-bottom:15px; + border:1px solid #e5e5e5; + color:#333; + background-color:#fff; + overflow:auto; + box-shadow:0 1px 1px rgba(0,0,0,.04) +} + +.table-list +{ + box-shadow:0 1px 1px rgba(0,0,0,.04); + background-color:#fff; + border:1px solid #e5e5e5 +} + +.table-list-header,.table-list-row +{ + border:none +} + +.sidebar +{ + max-width:240px; + min-width:190px; + width:18%; + border:1px solid #e5e5e5; + box-shadow:0 1px 1px rgba(0,0,0,.04); + background-color:#fff; + margin-top:12px +} + +label +{ + cursor:pointer; + display:block; + margin-top:10px; + margin-bottom:4px; + font-weight:700 +} + +.sidebar-content legend +{ + top:14px; + left:0; + right:0; + padding-bottom:7px; + font-size:1.2em +} + +.sidebar-icons>ul li +{ + padding-left:12px +} + +.sidebar-icons>ul li:hover,.sidebar-icons>ul li.active +{ + padding-left:12px; + border-left:none +} + +.sidebar-content h3,form h3 +{ + margin-top:12px; + background-color:#fff; + border:1px solid #e5e5e5; + box-shadow:0 1px 1px rgba(0,0,0,.04); + position:relative; + padding:12px; + margin-bottom:-11px +} + +#task-summary h2 +{ + margin-top:12px; + background-color:#fff; + border:1px solid #e5e5e5; + box-shadow:0 1px 1px rgba(0,0,0,.04); + position:relative; + padding:12px +} + +.activity-date,.activity-description,.btn,.buttons-header,.documentation,.dropdown-submenu-open li,.fc-event,.form-help,.menu-inline li,.page-header li,.project-overview-column span,.project-overview-columns,.sidebar,.subtasks-table,.table-list,.table-list-row .table-list-icons,.table-small,.task-board,.task-board-category,.task-board-title,.task-summary-buttons,.task-summary-column,.textarea-dropdown li,.tooltip .fa-info-circle,.views,div.ganttview-vtheader-series-name,header ul,legend,table +{ + font-size:1em +} + +.ui-widget +{ + font-family:'Open Sans',sans-serif; + font-size:1em +} + +#ui-datepicker-div,.ui-datepicker table,div.ui-tooltip +{ + font-size:1em +} + +.assign-me,.comment-actions,.comment-date,.dashboard-project-stats span +{ + font-size:.85em +} + +input[type=date],input[type=email],input[type=number],input[type=password],input[type=text] +{ + font-family:'Open Sans',sans-serif; + font-size:14px +} + +input[type=file] +{ + font-family:'Open Sans',sans-serif; + font-size:14px +} + +select +{ + font-family:'Open Sans',sans-serif; + font-size:14px +} + +.filter-box input[type=text] +{ + font-size:14px +} + +.chosen-container +{ + font-size:14px +} + +.CodeMirror,.editor-toolbar,.filter-box div.dropdown:last-child,.filter-box input[type=text],.input-addon-item:last-child,.panel,.table-list-header,.table-list-row:last-child,.views li:first-child,.views li:last-child +{ + border-bottom-left-radius:0; + border-top-left-radius:0; + border-bottom-right-radius:0; + border-top-right-radius:0 +} + +#modal-box,.alert,.btn,.documentation,.listing,.markdown pre,.task-board,.task-show-title,.task-summary-container,ul.dropdown-submenu-open +{ + border-radius:0 +} + +.ui-corner-all,.ui-corner-bottom,.ui-corner-br,.ui-corner-right +{ + border-bottom-right-radius:0; + border-bottom-left-radius:0; + border-top-right-radius:0; + border-top-left-radius:0 +} + +.select2-container--default .select2-selection--multiple,.select2-container--default .select2-selection--single +{ + border-radius:0 +} + +#select-dropdown-menu,.select-dropdown-input-container +{ + border-radius:0 +} + +#select-dropdown-menu +{ + border-top:none +} + +.chosen-container .chosen-results li.highlighted +{ + background-color:#36c; + background-image:none +} + +.chosen-container-single .chosen-single +{ + -webkit-box-shadow:none; + box-shadow:none; + background:#fff +} + +.chosen-container-active.chosen-with-drop .chosen-single +{ + background:#fff +} + +.color-picker-square.color-yellow,.task-board.color-yellow,.task-summary-container.color-yellow +{ + background-color:#ffeb8e!important; + border-color:#e4c600!important +} + +.alert +{ + background-color:#f7e400; + border-color:#e8d700; + color:#776e00 +} + +a +{ + color:#3392cc +} + +a:hover +{ + color:#3392cc +} + +a:focus +{ + color:#3392cc +} + +h2 +{ + font-size:1.3em; + font-weight:700 +} + +h3 +{ + font-size:1.3em; + font-weight:700 +} + +legend +{ + font-weight:700 +} + +input[type=date],input[type=email],input[type=number],input[type=password],input[type=text] +{ + padding-left:5px; + border-color:#ccc; + color:#333 +} + +input[type=date]:focus,input[type=email]:focus,input[type=number]:focus,input[type=password]:focus,input[type=text]:focus +{ + -webkit-box-shadow:none; + box-shadow:none +} + +input[type=text]:not(.input-addon-field) +{ + color:#333 +} + +textarea +{ + padding:5px +} + +textarea:focus +{ + -webkit-box-shadow:none; + box-shadow:none +} + +.select2-container--default,.select2-selection--single +{ + min-width:200px; + border-color:#ccc +} + +.select2-container--default .select2-selection__rendered,.select2-selection--single .select2-selection__rendered +{ + color:#333 +} + +.select2-container--default .select2-selection--multiple,.select2-selection--single .select2-selection--multiple +{ + border-color:#ccc; + border-width:2px +} + +.select2-container--default .select2-selection--single +{ + background-color:#fff; + border:2px solid #ccc; + border-radius:0 +} + +.color-picker-option +{ + height:20px +} + +#select-dropdown-menu +{ + overflow:auto +} + +table select +{ + margin-top:0 +} + +.filter-box input[type=text] +{ + padding-left:5px +} + +.form-actions +{ + margin-bottom:4px +} + +.form-column ul +{ + margin-top:5px +} + +.form-help +{ + margin-top:5px; + font-size:.85em; + color:gray; + font-style:italic +} + +.form-required +{ + color:#900 +} + +.buttons-header +{ + font-size:.8em; + margin-top:10px; + margin-bottom:15px +} + +.btn +{ + font-size:1.2em; + font-weight:400; + cursor:pointer; + display:inline-block; + border-radius:2px; + padding:8px 18px; + margin:0; + border:1px solid #ccc; + background:#fff; + color:#000 +} + +.btn-blue +{ + background:#3392cc; + border:none; + color:#fff +} + +.btn-blue:focus,.btn-blue:hover +{ + background:#036; + border:none +} + +.btn-red +{ + background:#900; + border:none; + color: #fff; +} + +.btn-red:focus,.btn-red:hover +{ + background:#600; + border:none +} + +table +{ + border-collapse:separate; + margin-bottom:30px; + padding:20px; + background-color:#fff; + border:1px solid #e5e5e5; + box-shadow:0 1px 1px rgba(0,0,0,.04) +} + +.table-small { + border-collapse:separate; + margin-bottom:0; + padding:4px; + background-color:#fff; + border:none; + box-shadow:none +} + +table th +{ + background:#eee +} + +table td,table th +{ + border-top:1px solid #e5e5e5; + /*border-right:1px solid #e5e5e5;*/ + border-bottom:none; + border-left:1px solid #e5e5e5; + padding:10px; + border-collapse:collapse +} + +.table-list-header +{ + background:#fff; + border-color:#ccc; + border-bottom:1px solid #efefef +} + +.table-list-header .table-list-header-count +{ + margin-left:15px; + font-weight:700 +} + +.table-list-row +{ + padding:10px 15px; + border-color:#e5e5e5 +} + +.table-list-row.color-yellow +{ + border-left:solid 5px #e4c600!important +} + +.table-list-row:hover +{ + border-right-color:#e5e5e5; + border-bottom-color:#e5e5e5; + border-top-color:#e5e5e5 +} + +.table-list-row .table-list-details +{ + line-height:22px; + margin-bottom:5px +} + +.sidebar-content +{ + width:calc(100% - 240px) +} + +@media (max-width: 480px) { + .sidebar-content + { + width:100% + } +} + +.page-header +{ + background:#fff; + padding:10px 20px 10px 15px; + border:1px solid #e5e5e5; + box-shadow:0 1px 1px rgba(0,0,0,.04); + margin-top:15px; + margin-bottom:5px +} + +.page-header:first-child +{ + border-top:none +} + +.page-header h2 +{ + font-size:1.3em; + border:none +} + +.page-header ul +{ + margin-top:5px; + margin-bottom:5px +} + +.page-header ul .fa +{ + color:#036 +} + +.page-header ul a +{ + text-decoration:none +} + +.project-header +{ + background:#fff; + padding:12px; + margin-top:10px; + margin-bottom:10px; + overflow:auto; + border:1px solid #e5e5e5; + box-shadow:0 1px 1px rgba(0,0,0,.04) +} + +.project-header .input-addon-item +{ + background:#fff; + border-color:#ccc +} + +.sidebar-content .page-header ul +{ + margin-top:7px; + margin-bottom:3px +} + +#board-container +{ + overflow-x:inherit +} + +.board-add-icon +{ + padding-top:2px; + line-height:10px +} + +.board-add-icon a i +{ + font-size:1em; + color:#3392cc +} + +.board-add-icon a:hover i +{ + color:#036 +} + +.board-column-header-task-count +{ + color:#999; + font-size:.85em +} + +.task-board +{ + padding:10px; + margin:2px 2px 6px +} + +div.task-board-recent +{ + border-width:0 +} + +.task-board-icons,.task-list-icons +{ + font-size:.8em; + padding:4px; + background:rgba(255,255,255,0.41) +} + +.task-board-category,.task-tags li +{ + padding:2px 6px +} + +.task-tags li +{ + border-color:rgba(102,102,102,0.5); + color:#666; + background-color:rgba(255,255,255,0.5); + font-size:.9em +} + +.project-overview-column strong +{ + font-size:2em +} + +.project-overview-column +{ + border-radius:0; + min-width:100px; + margin-right:20px +} + +.activity-event +{ + border-bottom:1px solid #e5e5e5; + padding:12px; + overflow:auto; + background:#fff +} + +.activity-event:nth-child(even) { + background: #fff; +} + +.activity-event:nth-child(even):hover +{ + background:rgba(51, 146, 204, 0.1) +} + +.activity-event:hover { + background: rgba(51, 146, 204, 0.1); +} + +.activity-title +{ + border:none +} + +.editor-toolbar +{ + border-top-color:#ccc; + border-left-color:#ccc; + border-right-color:#ccc +} + +.CodeMirror +{ + border-color:#ccc +} + +#task-summary h2 +{ + font-size:1.6em; + color:#333 +} + +.task-show-title h2 +{ + font-size:1.6em; + color:#333 +} + +.comment-sorting +{ + font-size:11px +} + +.task-summary-column +{ + color:#333 +} + +.task-list-tag +{ + background:#eee; + border-color:#ccc +} + +.table-list-category +{ + border-color:#ccc +} + +.subtask-cell +{ + border-color:#ccc +} + +.subtask-cell:first-child +{ + padding-left:0 +} + +.task-list-subtasks +{ + margin-top:5px +} + +.task-list-subtask:last-child .subtask-cell +{ + border-bottom:1px dotted #e5e5e5 +} + +.text-editor .text-editor-toolbar +{ + width:687px; + max-width:98%; + margin-top:20px; + margin-bottom:5px; + padding:12px; + background:#f2f2f2 +} + +.text-editor textarea +{ + width:696px; + max-width:98% +} + +.alert +{ + margin-bottom:30px +} + +.dropdown-submenu-open li:hover:not(.no-hover),.textarea-dropdown .active,.textarea-dropdown li:hover +{ + background:#3392cc +} + +.accordion-title +{ + background:#fff; + padding:4px 0; + border-bottom:1px solid #ccc +} + +.accordion-title h3 +{ + background:0 0; + padding-left:0 +} + +.views li +{ + white-space:nowrap; + background:#fafafa; + border:1px solid #ddd; + border-right:none; + padding:8px; + display:inline +} + +.pagination +{ + margin-bottom:30px +} + +.activity-content +{ + margin-left:65px +} + +#board_selector_chosen +{ + width:350px!important +} + +#login-top +{ + margin-bottom: 20px; + text-align: center; +} + +#login-bottom +{ + margin-top:40px +} + +#login-bottom ul +{ + list-style-type:none +} + +#login-bottom ul li +{ + margin-left:0 +} + +#modal-header +{ + margin-top:5px; + margin-bottom:5px; + font-size:1.5em; + float:right +} + +#modal-content +{ + padding:0 20px +} + +.documentation img +{ + border-color:#ccc; + margin-top:40px; + margin-bottom:20px +} + +.documentation li +{ + line-height:1.6em +} + +.documentation h1 +{ + padding-bottom:5px; + border-bottom:1px solid #e5e5e5; + margin-top:40px; + margin-bottom:30px +} + +.documentation h1:first-child +{ + margin-top:5px +} + +.documentation h2 +{ + padding-bottom:5px; + border-color:#e5e5e5; + margin-top:25px; + margin-bottom:10px +} + +.documentation h3 +{ + margin-top:25px; + margin-bottom:10px +} + +.markdown h1 +{ + margin-top:20px; + margin-bottom:10px; + padding-bottom:5px; + border-bottom:1px solid #e5e5e5 +} + +.markdown h2 +{ + margin-top:20px; + margin-bottom:10px; + padding-bottom:5px; + border-bottom:1px solid #e5e5e5 +} + +.markdown h3 +{ + margin-top:20px; + margin-bottom:10px +} + +.markdown p +{ + margin-bottom:5px +} + +@media print { + a.btn + { + display:none + } + + header + { + display:block + } + + header nav ul + { + display:none + } + + .page-header + { + display:block + } + + .page-header ul + { + display:none + } + + .project-header + { + display:none + } + + .filter-box + { + display:none + } + + .dropdown ul + { + display:none + } + + #board-container .board-add-icon + { + display:none + } + + #board-container a.dropdown-menu i + { + display:none + } + + #task-view .sidebar + { + display:block + } + + #task-view .sidebar h2 + { + display:none + } + + #task-view .sidebar ul + { + display:none + } + + #task-view .sidebar h2:first-child + { + display:block + } + + #task-summary h2,.task-show-title h2,.task-summary-column span + { + color:#000 + } + + .task-summary-column .smaller + { + display:none + } + + .accordion-collapsed + { + display:none + } + + #comments .comment-sorting + { + display:none + } + + #comments .comment-actions + { + display:none + } + + a,th a + { + color:#000; + text-decoration:none + } + + .table-fixed + { + white-space:normal + } + + .table-fixed td + { + -o-text-overflow:clip; + text-overflow:clip; + white-space:normal + } + + header nav h1 .logo + { + display:block + } + + .page-header + { + background:0 0; + padding:0 0 10px + } + + .page-header h2 + { + margin:0 + } + + header nav h1 .tooltip i + { + display:none + } + + #board-container + { + overflow-x:visible + } + + #board-container .board-column-header + { + padding-left:7px + } + + #task-view .sidebar h2:first-child + { + margin-top:50px; + margin-bottom:5px; + font-size:1.6em; + font-weight:400 + } + + #task-summary .task-summary-container + { + padding-right:80px + } + + .accordion-title + { + background:0 0; + padding:0; + margin-top:40px + } + + .accordion-title h3 + { + margin:0 + } + + #comments .avatar + { + float:left; + width:48px + } + + #comments .avatar .avatar-letter + { + color:#fff; + text-align:center + } + + #comments .avatar-48 .avatar-letter + { + font-size:25px; + line-height:48px; + width:48px + } + + #comments .avatar-48 div,#comments .avatar-48 img + { + border-radius:30px + } +} diff --git a/plugins/Customizer/Assets/css/userthemes/niebieski.css b/plugins/Customizer/Assets/css/userthemes/niebieski.css new file mode 100644 index 00000000..2d68776c --- /dev/null +++ b/plugins/Customizer/Assets/css/userthemes/niebieski.css @@ -0,0 +1 @@ +header{background-color:#006;background-image:linear-gradient(-180deg,transparent 0%,#FF3333 90%)}header h1,header a .fa{color:#36F}a i.web-notification-icon{color:}body{background:;color:}ul.dropdown-submenu-open,.accordion-title h3{background-color:}.dropdown-submenu-open a{color:}a:hover{color:}a .fa{color:}h1,h2,h3,.accordion-toggle{color:}.table-list-header a{color:}.table-list-header .table-list-header-count{color:}.table-list-row .table-list-title a{color:}.table-list-row .table-list-details strong{color:}.dropdown-menu-link-icon{color:}.page-header h2 a{color:}.sidebar>ul a:hover{color:}.sidebar>ul li.active a{color:}.task-list-icons a:hover{color:}.task-list-icons a:hover i{color:}.subtask-cell a{color:}a{color:}.table-list-category a:hover{color:}.subtask-cell a:hover,.subtask-cell a:focus{color:}a:focus,a:hover{color:}.table-list-header a:hover,.table-list-header a:focus,.task-board a{color:}.table-list-row .table-list-details{color:}.input-addon-field{color:}.page-header h2 a:focus,.page-header h2 a:hover,code{color:}.sidebar>ul a{color:}.task-list-avatars .task-avatar-assignee{color:}.task-list-icons a,.task-list-icons span,.task-list-icons i,.task-date{color:}span.task-date-overdue{color:}.sidebar>ul li.active{border-left:5px solid}.sidebar>ul li.active a:focus,.sidebar>ul li.active a:hover{color:}.sidebar>ul li:hover{border-left:5px solid}.panel{color:}td a.dropdown-menu strong,td a.dropdown-menu strong i{color:}#task-summary h2{color:unset}.comments .comment:hover{background:#efefef40}.comments .comment:nth-child(even):not(.comment-highlighted):hover{background:#efefef40}.comments .comment:nth-child(even):not(.comment-highlighted){background:#efefef40}table.table-striped tr:nth-child(odd){background:#efefef40}header{box-shadow:0 -1px 5px 1px;border-bottom:none}.project-header{margin-bottom:8px;margin-top:8px}.panel{background-color:#efefef40;border:1px solid #efefef40}.task-board{border-width:2px;background:#efefef22!important}div.task-board-recent{border-width:2px}table td{border:none}table th:first-child{border-top-left-radius:8px}table th:last-child{border-top-right-radius:8px}.table-list-header{background:#efefef40;border:1px solid #efefef40;border-radius:5px 5px 0 0;line-height:28px;padding-left:3px;padding-right:3px}.table-list-row{padding-left:3px;padding-right:3px;border-bottom:1px solid #efefef40;border-right:1px solid #efefef40}.table-list-row.table-border-left{border-left:1px solid #efefef40}.table-list-row:nth-child(odd){background:#efefef30}.table-list-row:hover{background:#efefef32;border-bottom:1px solid #efefef32;border-right:1px solid #efefef32}.dropdown-menu-link-icon{text-decoration:none}.dropdown-submenu-open li{border-bottom:1px solid #efefef40}.page-header h2{margin:0;padding:0;font-weight:700;border-bottom:1px dotted #efefef40}.sidebar>ul li{list-style-type:none;line-height:35px;border-bottom:1px dotted #efefef40;padding-left:13px}span.task-icon-age-total{border:1px solid #efefef40;padding:1px 3px 1px 3px;border-top-left-radius:3px;border-bottom-left-radius:3px}span.task-icon-age-column{border:1px solid #efefef40;border-left:none;margin-left:-5px;padding:1px 3px 1px 3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.subtask-cell{padding:4px 10px;border-top:1px dotted #efefef42;border-left:1px dotted #efefef40;display:table-cell;vertical-align:middle}table th{text-align:left;padding:.5em 3px;border:none;background:#efefef40}.views li{white-space:nowrap;background:#efefef40;border:none;border-right:none;padding:4px 8px;display:inline}
\ No newline at end of file diff --git a/plugins/Customizer/Assets/img/logo-gen.png b/plugins/Customizer/Assets/img/logo-gen.png Binary files differnew file mode 100644 index 00000000..1df1986f --- /dev/null +++ b/plugins/Customizer/Assets/img/logo-gen.png diff --git a/plugins/Customizer/Assets/js/customizer.js b/plugins/Customizer/Assets/js/customizer.js new file mode 100644 index 00000000..ae7c4f13 --- /dev/null +++ b/plugins/Customizer/Assets/js/customizer.js @@ -0,0 +1,248 @@ +//Functionality for a slider value feedback + +var header_logo_output = $('header_logo_output')[0]; + +$(document).on('input', 'input[name="headerlogo_size"]', function(e) { + header_logo_output.innerHTML = e.currentTarget.value; + document.getElementById("hl1").style.height = e.currentTarget.value + "px"; +}); + +var login_logo_output = $('login_logo_output')[0]; + +$(document).on('input', 'input[name="loginlogo_size"]', function(e) { + login_logo_output.innerHTML = e.currentTarget.value; + document.getElementById("ll1").style.height = e.currentTarget.value + "px"; +}); + +var av_icon_output = $('av_icon_output')[0]; + +$(document).on('input', 'input[name="av_size"]', function(e) { + var siz = e.currentTarget.value; + av_icon_output.innerHTML = siz; + if (document.querySelector(".avatar-preview .avatar-letter") !== null) { + document.querySelector(".avatar-preview .avatar-letter").style.lineHeight = siz + "px"; + document.querySelector(".avatar-preview .avatar-letter").style.width = siz + "px"; + document.querySelector(".avatar-preview .avatar-letter").style.fontSize = (siz / 2) + "px"; + } else { + var link = document.querySelector(".avatar-preview img").src; + var changedLink = link.substring(0, link.length-2); + document.querySelector(".avatar-preview img").src = changedLink + siz; + } +}); + +var av_radius_output = $('av_radius_output')[0]; + +$(document).on('input', 'input[name="av_radius"]', function(e) { + var rad = e.currentTarget.value; + av_radius_output.innerHTML = rad; + if (document.querySelector(".avatar-preview .avatar-letter") !== null) { + document.querySelector(".avatar-preview .avatar-letter").style.borderRadius = rad + "%"; + } else { + document.querySelector(".avatar-preview img").style.borderRadius = rad + "%"; + } +}); + +var b_av_icon_output = $('b_av_icon_output')[0]; + +$(document).on('input', 'input[name="b_av_size"]', function(e) { + var siz = e.currentTarget.value; + b_av_icon_output.innerHTML = siz; + if (document.querySelector(".b-avatar-preview .avatar-letter") !== null) { + document.querySelector(".b-avatar-preview .avatar-letter").style.lineHeight = siz + "px"; + document.querySelector(".b-avatar-preview .avatar-letter").style.width = siz + "px"; + document.querySelector(".b-avatar-preview .avatar-letter").style.fontSize = (siz / 2) + "px"; + } else { + var link = document.querySelector(".b-avatar-preview img").src; + var changedLink = link.substring(0, link.length-2); + document.querySelector(".b-avatar-preview img").src = changedLink + siz; + } +}); + +var b_av_radius_output = $('b_av_radius_output')[0]; + +$(document).on('input', 'input[name="b_av_radius"]', function(e) { + var rad = e.currentTarget.value; + b_av_radius_output.innerHTML = rad; + if (document.querySelector(".b-avatar-preview .avatar-letter") !== null) { + document.querySelector(".b-avatar-preview .avatar-letter").style.borderRadius = rad + "%"; + } else { + document.querySelector(".b-avatar-preview img").style.borderRadius = rad + "%"; + } +}); + + +//Accordion for settings page + +document.addEventListener("DOMContentLoaded", function(event) { + + + var acc = document.getElementsByClassName("login-accordion"); + var panel = document.getElementsByClassName('login-accordian-panel'); + + for (var i = 0; i < acc.length; i++) { + acc[i].onclick = function() { + var setClasses = !this.classList.contains('current'); + setClass(acc, 'current', 'remove'); + setClass(panel, 'show', 'remove'); + + if (setClasses) { + this.classList.toggle("current"); + this.nextElementSibling.classList.toggle("show"); + } + } + } + + function setClass(els, className, fnName) { + for (var i = 0; i < els.length; i++) { + els[i].classList[fnName](className); + } + } +}); + +// Valid for the form id="settings" checkboxes, when the "change" event occurs, the module is sent. + +$(document).ready(function(){ + $("#settings").on("change", "input:checkbox", function(){ + $("#settings").submit(); + }); +}); + +// auto submit on change for user theme +if (document.getElementById('userthemeSelection')) { + document.getElementById('userthemeSelection').onchange = function() { + document.getElementById('ts').submit(); + } +} + +//Live Preview + +if (document.getElementById('loginpanel_color')) { + document.getElementById('loginpanel_color').oninput = function() { + document.getElementById('preview-form-login').style.backgroundColor = document.getElementById('loginpanel_color').value; + document.getElementById('preview-form-note').style.backgroundColor = document.getElementById('loginpanel_color').value; + } + + document.getElementById('loginbackground_color').oninput = function() { + document.getElementById('preview').style.backgroundColor = document.getElementById('loginbackground_color').value; + } + + document.getElementById('form-login_note').oninput = function() { + document.getElementById('preview-form-note').innerHTML = document.getElementById('form-login_note').value; + } + + document.getElementById('login_shadow_color').oninput = function() { + var slider = document.getElementById("login_shadow").value; + var color = document.getElementById('login_shadow_color').value; + document.getElementById('preview-form-login').style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + color; + document.getElementById('preview-form-note').style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + color; + } + + document.getElementById('login_border_color').oninput = function() { + document.getElementById('preview-form-login').style.borderColor = document.getElementById('login_border_color').value; + document.getElementById('preview-form-note').style.borderColor = document.getElementById('login_border_color').value; + } + + document.getElementById('login_btn_color').oninput = function() { + document.getElementById('preview-login-btn').style.backgroundColor = document.getElementById('login_btn_color').value; + } + + document.getElementById('login_btn_shadow_color').oninput = function() { + var slider = document.getElementById("login_btn_shadow").value; + var color = document.getElementById('login_btn_shadow_color').value; + document.getElementById('preview-login-btn').style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + color; + } + + document.getElementById('login_btn_font_color').oninput = function() { + document.getElementById('preview-login-btn').style.color = document.getElementById('login_btn_font_color').value; + } + + document.getElementById('login_btn_shade_color').oninput = function() { + var color = document.getElementById('login_btn_shade_color').value; + document.getElementById('preview-login-btn').style.backgroundImage = 'linear-gradient(-180deg, transparent 0%, ' + color + ' 90%'; + } + + document.getElementById('login_btn_shadow').oninput = function() { + var slider = document.getElementById("login_btn_shadow").value; + var color = document.getElementById('login_btn_shadow_color').value; + document.getElementById('preview-login-btn').style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + color; + } + + document.getElementById('login_btn_border').oninput = function() { + var slider = document.getElementById("login_btn_border").value; + var color = document.getElementById('login_btn_border_color').value; + document.getElementById('preview-login-btn').style.border = slider + 'px solid ' + color; + } + + document.getElementById('login_border').oninput = function() { + var slider = document.getElementById("login_border").value; + var color = document.getElementById('login_border_color').value; + document.getElementById('preview-form-login').style.border = slider + 'px solid ' + color; + document.getElementById('preview-form-note').style.border = slider + 'px solid ' + color; + } + + document.getElementById('login_shadow').oninput = function() { + var slider = document.getElementById("login_shadow").value; + var color = document.getElementById('login_shadow_color').value; + document.getElementById('preview-form-login').style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + color; + document.getElementById('preview-form-note').style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + color; + } + + document.getElementById('login_btn_width').oninput = function() { + var slider = document.getElementById("login_btn_width").value; + document.getElementById('preview-login-btn').style.width = slider + 'px'; + } + + document.getElementById('preview-login-btn').onmouseover = function() { + document.getElementById('preview-login-btn').style.color = document.getElementById('login_btn_color').value; + document.getElementById('preview-login-btn').style.backgroundColor = document.getElementById('login_btn_font_color').value; + } + + document.getElementById('preview-login-btn').onmouseout = function() { + document.getElementById('preview-login-btn').style.backgroundColor = document.getElementById('login_btn_color').value; + document.getElementById('preview-login-btn').style.color = document.getElementById('login_btn_font_color').value; + } + + document.getElementById('form-background_url').oninput = function() { + var val = document.getElementById("form-background_url").value; + document.getElementById('preview').style.background = 'url("' + val +'") no-repeat center center'; + document.getElementById('preview').style.backgroundSize = 'cover'; + } +} + +function OnColorChanged(selectedColor, inputId) { + if (inputId == "login_shadow_color") { + var slider = document.getElementById("login_shadow").value; + document.getElementById("preview-form-login").style.color = selectedColor; + document.getElementById("preview-form-note").style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + selectedColor; + } + if (inputId == "loginbackground_color") { + var rgbaBox = document.getElementById("preview"); + rgbaBox.style.backgroundColor = selectedColor; + } + if (inputId == "loginpanel_color") { + document.getElementById("preview-form-login").style.backgroundColor = selectedColor; + document.getElementById("preview-form-note").style.backgroundColor = selectedColor; + } + if (inputId == "login_border_color") { + document.getElementById("preview-form-login").style.borderColor = selectedColor; + document.getElementById("preview-form-note").style.borderColor = selectedColor; + } + if (inputId == "login_btn_color") { + var rgbaBox = document.getElementById("preview-login-btn"); + rgbaBox.style.backgroundColor = selectedColor; + } + if (inputId == "login_btn_shadow_color") { + var rgbaBox = document.getElementById("preview-login-btn"); + var slider = document.getElementById("login_btn_shadow").value; + rgbaBox.style.boxShadow = '0px 0px ' + slider + 'px ' + slider * 0.1 + 'px ' + selectedColor; + } + if (inputId == "login_btn_font_color") { + var rgbaBox = document.getElementById("preview-login-btn"); + rgbaBox.style.color = selectedColor; + } + if (inputId == "login_btn_shade_color") { + var rgbaBox = document.getElementById("preview-login-btn"); + rgbaBox.style.backgroundImage = 'linear-gradient(-180deg, transparent 0%, ' + selectedColor + ' 90%'; + } +} + diff --git a/plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.css b/plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.css new file mode 100644 index 00000000..6db3d7b3 --- /dev/null +++ b/plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.css @@ -0,0 +1,167 @@ +/*! Menucool rgba Color Picker v2018.9.12. www.menucool.com/rgba-color-picker */
+
+input.color{
+ width:110px;
+ height: 18px;
+ font-size:11px;
+ border:1px solid #dadada;
+ box-sizing:content-box;
+ padding-left:4px;
+}
+
+#colorpicker
+{
+ padding: 20px;
+ position: absolute;
+ top: 22px;
+ left:auto; right:0;/*If need align to the right, set: left:0;right:auto; */
+ background-color: #FFF;
+ border: 1px solid #BBB;
+ display: none;
+ z-index: 200;
+ box-sizing:content-box;
+ font: normal 10px verdana;
+ color: #666;
+ border-radius:4px;
+ box-shadow:0 1px 8px rgba(0,0,0,0.5);
+}
+
+input.color.up + .colorChooser > #colorpicker {
+ top: -306px;
+}
+
+#colorpicker div
+{
+ float:left;
+ padding:0;
+ box-sizing:content-box;
+}
+#colorpicker div.clear, #colorpicker div.separator
+{
+ float: none;
+ clear: both;
+ border: 0;
+ overflow:hidden;
+ height:0;
+ font-size:0;
+}
+#colorpicker div.separator
+{
+ margin-bottom:8px;
+}
+
+#colorpicker #colorContainer
+{
+ border:0;
+ border-right: 1px solid black;
+ border-bottom: 1px solid black;
+ cursor: pointer;
+ font-size:0;
+ width:234px;
+}
+#colorContainer div
+{
+ border:0;
+ border-top: solid 1px black;
+ border-left: solid 1px black;
+ width:12px;
+ height:12px;
+ overflow:hidden;
+}
+#colorpicker .w1, #colorpicker .w2 {
+ width:80px;
+ padding-left:4px;
+ border: 1px solid #999;
+ border-radius:3px;
+ position:relative;
+}
+div.w1, div.w2 {
+ height: 22px;
+ line-height: 22px;
+}
+input.w1, input.w2 {
+ font: normal 10px verdana;
+ height: 18px;
+ line-height: 18px;
+}
+#colorpicker .w2 {
+ width:120px;
+ float:right;
+
+}
+#colorpicker div.w2::before {
+ content: " ";
+ width:100%;
+ height:22px;
+ position:absolute;
+ left:0;top:0;
+ z-index:-1;
+}
+.opacitySpan {display:inline-block;padding-top:4px;}
+
+input.rgbaRange {
+ width:118px;
+ margin-left:20px;
+ vertical-align:middle;
+}
+/*brennaobrien.com/blog/2014/05/style-input-type-range-in-every-browser.html*/
+input[type=range]{
+ -webkit-appearance: none;
+ border:none;
+}
+
+input[type=range]::-webkit-slider-runnable-track {
+ width: 118px;
+ height: 5px;
+ background: #ddd;
+ border: none;
+ border-radius: 2px;
+}
+
+input[type=range]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ border: none;
+ height: 15px;
+ width: 15px;
+ border-radius: 50%;
+ background: #888;
+ margin-top: -5px;
+}
+
+input[type=range]:focus {
+ outline: none;
+}
+
+input[type=range]:focus::-webkit-slider-runnable-track {
+ background: #ccc;
+}
+
+#colorpicker .btnOK {
+ width:40px;
+ float:right;
+ height:22px;
+ font-size: 12px;
+ vertical-align:middle;
+ cursor:pointer;
+}
+
+.transChooser, #colorpicker div.w2::before {
+ background: white url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAAB90RVh0U29mdHdhcmUATWFjcm9tZWRpYSBGaXJld29ya3MgOLVo0ngAAAAWdEVYdENyZWF0aW9uIFRpbWUAMDkvMDcvMTgBUiN5AAAAKklEQVQokWNkYGDwZcAC/v//j02YgQmrKB4wHDSw4AoNRkZG6tgwHDQAAMuIBmTkVeR2AAAAAElFTkSuQmCC') repeat;
+}
+
+/*Click span.colorChooser will popup the div#colorpicker */
+.colorChooser {
+ width: 46px;
+ height: 20px;
+ border: 1px solid rgba(0,0,0,0.3);
+ display: inline-block;
+ overflow: visible;
+ vertical-align: middle;
+ position: relative;
+ box-sizing: content-box;
+ border-radius: 0 3px 3px 0;
+ user-select: none;
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAICAYAAAAftBSpAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAAB90RVh0U29mdHdhcmUATWFjcm9tZWRpYSBGaXJld29ya3MgOLVo0ngAAAAWdEVYdENyZWF0aW9uIFRpbWUAMDkvMjIvMThMTIArAAAAvklEQVQokZXMrw6CUBiG8dc/xUICIpliOHMjH4vZeQU2LV6BmWD2DrwYNgMJyqGQSNC4gMfEpk5xftvbnu83ASTpLmmu/y+QtBYgYO+cI4oiJP2c53lkWQZwAxYDIuBUliW+748CQRCQ5znAFfABPSMCjm3bEsfxRyAMQ5qmATg//70jU2BXFAVJkrwAxhiccwAHYDaGDNv0fY+1FklYa6nrGmD7qf+GCDBVVZGmKV3XAay+tWOIAANcgOVY9wCltDLGu2gdigAAAABJRU5ErkJggg==');
+ background-position:center center;
+ background-repeat:no-repeat;
+}
diff --git a/plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.js b/plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.js new file mode 100644 index 00000000..6e7de940 --- /dev/null +++ b/plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.js @@ -0,0 +1,492 @@ +/*r
+ColorPickerCallback:cpC
+element:r
+hexInput:a
+bgDiv1:b
+bgDiv2:c
+initIt:d
+dColorContainer:e
+appendSeparator:f
+createColorDivs:g
+stopPropagation:h
+addColorElements:j
+createElm:k
+
+clrContainerEvtHdler_getSelectedColorAndThenUpdateInnerValues:m
+
+createDropdownSpans:o
+
+
+initColorPickerValues:s
+setColorPickerInsideValues:t
+
+rgbaInput:v
+iRgbaRange:w
+getParsedColors:x
+r*/
+
+/*
+Knowledge: 1. backgound-color:none; color:none; are not a valid color.
+ 2. Convert rgba to similar hex color: https://stackoverflow.com/questions/15898740/how-to-convert-rgba-to-a-transparency-adjusted-hex
+*/
+
+/*! Menucool rgba Color Picker v2018.9.23. menucool.com/rgba-color-picker */
+var MenuCoolRgbaColorPickerOptions = {
+ initOnPageLoad: true
+};
+
+var rgbaColorPicker = (function (myOptions) {
+ 'use strict';
+ // Private members
+ var addEvent = function (elem, evtType, func) {
+ if (elem.addEventListener) {
+ elem.addEventListener(evtType, func, false);
+ } else if (elem.attachEvent) {
+ elem.attachEvent("on" + evtType, func);
+ }
+ else {
+ // for IE/Mac, NN4, and older
+ elem["on" + evtType] = func;
+ }
+ };
+ var classIsColor = function (myClass) {
+ if (!myClass) return 0;
+ var pattern = /\bcolor\b/;
+ return pattern.test(myClass);
+ };
+ var len = "length";
+ var documentCreateElement = function (tagName) {
+ return document.createElement(tagName);
+ };
+ var hexToRgb = function (hex) {
+ var retVal = 0;
+ if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
+ var c = hex.substring(1).split('');
+ if (c[len] == 3) {
+ c = [c[0], c[0], c[1], c[1], c[2], c[2]];
+ }
+ c = '0x' + c.join('');
+ retVal = [(c >> 16) & 255, (c >> 8) & 255, c & 255];
+ }
+ return retVal;
+ };
+ var hexAlphaToRgba = function (hex, alpha) {
+ var retVal = hexToRgb(hex);
+ return retVal ? 'rgba(' + retVal.join(',') + ',' + alpha + ')' : _invalid;
+ };
+
+ var rgbaToSimilarHex = function (rgba, bgHexColor) {
+ var hex = '';
+ var match = rgba.match(/rgba\((\d+),(\d+),(\d+),([.\d]+)/i);
+ if (match) {
+ var rgbBg = hexToRgb(bgHexColor);
+ var rgbConverted = [];
+ var alpha = +match[4];
+ //now convert hex+alpha to similar hex. //rgba to similar rbg: Color = Color * alpha + Bkg * (1 - alpha);
+ for (var i = 0; i < 3; i++) {
+ rgbConverted.push(Math.floor(+match[i+1] * alpha + (+rgbBg[i]) * (1 - alpha)));
+ }
+ hex = rgbToHex(rgbConverted.join(','));
+ //console.log(hex, rgba);
+ }
+ return hex;
+ };
+
+ var componentToHex = function (c) {
+ var hex = c.toString(16).toUpperCase();
+ return hex[len] == 1 ? "0" + hex : hex;
+ };
+ var rgbToHex = function (rgbStr) {
+ var rgb = rgbStr.split(',');
+ return "#" + componentToHex(+rgb[0]) + componentToHex(+rgb[1]) + componentToHex(+rgb[2]);
+ };
+ /* //jsfiddle.net/salman/f9Re3/
+ var invertColor = function (color) {
+ color = parseInt(color.substring(1), 16); // convert to integer
+ color = 0xFFFFFF ^ color; // invert three bytes
+ color = color.toString(16); // convert to hex
+ color = "#" + ("000000" + color).slice(-6); // pad with leading zeros, also prepend #
+ return color;
+ };*/
+ var validColorNumbers = function (nums) {
+ var numArr = nums.split(',');
+ for (var i = 0; i < numArr[len]; i++) {
+ if (+numArr[i] < 0 || +numArr[i] > 255) return 0;
+ }
+ return 1;
+ };
+
+ var isiOS = (navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false);
+
+ var clickOrTouch = isiOS ? "touchstart" : "click";
+
+ var clsNm = "className", sty = "style", bgClr = "backgroundColor", dspl = "display", val = "value", _invalid = "invalid", appendCld = "appendChild", trans = "transparent", undef = "undefined";
+
+ var getStyle = function (elm) {
+ if (window.getComputedStyle) //modern browsers
+ var computedStyle = window.getComputedStyle(elm, null); //null can be replaced with pseudo such as 'hover'
+ else if (elm.currentStyle) //IE
+ computedStyle = elm.currentStyle;
+ else
+ computedStyle = elm[sty];
+
+ return computedStyle;
+ };
+
+ var picker, //the picker instance
+ HexInSession;//unconfirmed chosen hex color
+
+ var btnOKClickHandler = function () {
+ if (picker) {
+ if (+picker.iRgbaRange[val] == 1)
+ var color = picker.hexInput[val];
+ else
+ color = picker.rgbaInput[val];
+ picker.R[picker.i][val] = color;
+ picker.R[picker.i].onchange();
+ picker.element[sty][dspl] = "none";
+ if (typeof OnColorChanged !== "undefined") OnColorChanged(color, picker.R[picker.i].id);
+ }
+ };
+
+ //Picker class constructor
+ var Picker = function () {
+ var that = this;
+ that.hexInput = //div elelment for displaying selected color as its text
+ that.bgDiv1 = //div elelment for displaying selected color as its background
+ that.bgDiv2 =
+ that.dColorContainer = null; //div element containing colors
+ that.i = -1; //current target index
+ that.R = []; //target inputs that class contains "color"
+ that.S = []; //span elements after the R target inputs: the chooser
+
+ that.initIt();
+ };
+
+ Picker.prototype = {
+ appendSeparator: function (div) {
+ var sep = documentCreateElement("div");
+ if (!div) {
+ div = this.element;
+ sep[clsNm] = "separator";
+ }
+ else
+ sep[clsNm] = "clear";
+ div[appendCld](sep);
+ },
+
+ createColorDivs: function (r, b, g) {
+ var colorCell = documentCreateElement("div");
+ if (r == "TT") {
+ colorCell[clsNm] = "transChooser";
+ colorCell.setAttribute("rgb", trans);
+ }
+ else {
+ colorCell[sty][bgClr] = "#" + r + g + b;
+ colorCell.setAttribute("rgb", "#" + r + g + b);
+ }
+ return colorCell;
+ },
+
+ stopPropagation: function (e) {
+ e = e ? e : window.event;
+ e.cancelBubble = true;
+ if (e.stopPropagation) e.stopPropagation();
+ },
+
+ addColorElements: function () {
+ var that = this;
+ var colorCell;
+ var div = that.dColorContainer;
+
+ //add grayscales to div
+ var grays = ["00", "11", "22", "33", "44", "55", "66", "77", "88", "99", "AA", "BB", "CC", "DD", "EE", "F6", "FF", "TT"];
+ for (var a = 0; a < 18; a++) {
+ colorCell = that.createColorDivs(grays[a], grays[a], grays[a]);
+ div[appendCld](colorCell);
+ }
+ that.appendSeparator(div);
+
+ //add colors to div (first group)
+ var c = ["00", "33", "66", "99", "CC", "FF"];
+ for (var b = 0; b < 6; b++) {
+ for (var r = 0; r < 3; r++) {
+ for (var g = 0; g < 6; g++) {
+ colorCell = that.createColorDivs(c[r], c[g], c[b]);
+ div[appendCld](colorCell);
+ }
+ }
+ that.appendSeparator(div);
+ }
+ that.appendSeparator(div);
+
+ //add colors to div (second group)
+ for (var b = 0; b < 6; b++) {
+ for (var r = 3; r < 6; r++) {
+ for (var g = 0; g < 6; g++) {
+ colorCell = that.createColorDivs(c[r], c[g], c[b]);
+ div[appendCld](colorCell);
+ }
+ }
+ that.appendSeparator(div);
+ }
+ },
+ //type: null-div, 1-span, 2-input[type=text], 3-input[type=range], 4-button
+ createElm: function (id, type) {
+ var tagName;
+ switch (type) {
+ case 1:
+ tagName = "span";
+ break;
+ case 2:
+ case 3:
+ tagName = "input";
+ break;
+ case 4:
+ tagName = "button";
+ break;
+ default:
+ tagName = "div";
+ break;
+ }
+ var el = documentCreateElement(tagName);
+ if (id[0] == '#')
+ el.id = id.substring(1);
+ else
+ el[clsNm] = id;
+
+ if (type == 2) {
+ el.type = "text";
+ el.setAttribute("spellcheck", "false");
+ }
+ else if (type == 3) {
+ el.type = "range";
+ }
+ if (id != "#colorpicker" && id != "colorChooser")
+ this.element[appendCld](el);
+ return el;
+ },
+
+ initIt: function () {
+ var that = this;
+ // 1. create color picker
+ that.element = that.createElm("#colorpicker");
+ addEvent(that.element, clickOrTouch, that.stopPropagation);
+
+ that.bgDiv1 = that.createElm("w1");
+ that.bgDiv2 = that.createElm("w2");
+ that.appendSeparator();//----------
+ that.hexInput = that.createElm("w1", 2);
+ that.rgbaInput = that.createElm("w2", 2);
+ that.appendSeparator();//----------
+ var btnOK = that.createElm("btnOK", 4);//it will float to right
+ btnOK.setAttribute("type", "button");//avoid submitting form
+ btnOK.innerHTML = "OK";
+ var opacitySpan = that.createElm("opacitySpan", 1);
+ opacitySpan.innerHTML = "Opacity";
+ that.iRgbaRange = that.createElm("rgbaRange", 3);
+ that.appendSeparator();//----------
+
+ that.dColorContainer = that.createElm("#colorContainer");
+ addEvent(that.dColorContainer, "mouseover", function (e) { that.clrContainerEvtHdler_getSelectedColorAndThenUpdateInnerValues(e, 1); });
+ addEvent(that.dColorContainer, "mouseout", function (e) { that.clrContainerEvtHdler_getSelectedColorAndThenUpdateInnerValues(e, 2); });
+ //The following eventType ==3 will also update HexInSession
+ addEvent(that.dColorContainer, clickOrTouch, function (e) { that.clrContainerEvtHdler_getSelectedColorAndThenUpdateInnerValues(e, 3); });
+
+ //that.iRgbaRange.type = "range";
+ that.iRgbaRange[val] = 1;
+ that.iRgbaRange.min = 0;
+ that.iRgbaRange.max = 1;
+ that.iRgbaRange.step = 0.1;
+ addEvent(that.iRgbaRange, "input", function () { that.setColorPickerInsideValues(that.hexInput[val]); });
+
+
+ // 2. populate color picker cells
+ that.addColorElements();
+
+ // 3. create spans after all class="color" elements, set popup events for them
+ that.createDropdownSpans();
+
+ // 4. click on body will hide the #colorpicker
+ //I add this event to document.documentElement other than document.body as sometimes I clicked <html> instead of <body>
+ addEvent(document.documentElement, clickOrTouch, function () { that.element[sty][dspl] = "none"; });
+ addEvent(btnOK, clickOrTouch, btnOKClickHandler);
+
+ //if (typeof OnColorPickerLoaded != undef) OnColorPickerLoaded();
+
+ },
+
+ clrContainerEvtHdler_getSelectedColorAndThenUpdateInnerValues: function (e, eventType) {
+ if (eventType == 2) //mouseout of colorContainer
+ {
+ var selectedColor = HexInSession;
+ }
+ else{ //mouseover, or clickOrTouch
+ if (e.target) var target = e.target; //recognized by all except IE
+ else target = e.srcElement; //IE only knows srcElement. Chrome, Safari, Opera also knows it.
+ if (target.id != "colorContainer"){
+ selectedColor = target.getAttribute("rgb");
+ if (eventType == 3) HexInSession = selectedColor;
+ }
+ }
+ if(selectedColor)
+ picker.setColorPickerInsideValues(selectedColor);
+ //picker.stopPropagation(e);//don't need it anymore as we now have: addEvent(that.element, "click", that.stopPropagation);
+ },
+
+ // create choosers(span) after all class="color" elements, set popup events for them
+ createDropdownSpans: function () {
+ var colorInputs = document.getElementsByTagName("input");
+ var that = this;
+ for (var j = 0; j < colorInputs[len]; j++) {
+ if (classIsColor(colorInputs[j][clsNm])) { //search all input element who has "color" class
+ var i = that.R[len];
+ that.R[i] = colorInputs[j]; //that.R.push(colorInputs); will make the R[i].parentNode throw exception
+ that.R[i].i = i;
+ //create choosers
+ that.S[i] = that.createElm("colorChooser", 1);
+ that.S[i].i = i;
+ that.S[i].id = colorInputs[j]['name'];
+
+ //that.S[i].arrow = that.createElm("colorChooserArrow", 1);
+ //that.S[i][appendCld](that.S[i].arrow);
+
+ that.R[i].parentNode.insertBefore(that.S[i], that.R[i].nextSibling);
+ that.S[i][sty][bgClr] = that.R[i][val];
+ addEvent(that.S[i], clickOrTouch, function (e) {
+ if (that.element.parentNode == this && that.element[sty][dspl] == "block") that.element[sty][dspl] = "none";
+ else {
+ that.i = this.i;
+ this[appendCld](that.element)[sty][dspl] = "block";
+ that.initColorPickerValues();
+ }
+ that.stopPropagation(e);
+ });
+
+ //now we know what is S, so we can add the following event
+ that.R[i].onchange = function () {
+ that.S[this.i][sty][bgClr] = this[val];
+ };
+
+ if (typeof OnColorChanged !== "undefined") OnColorChanged(that.R[i][val], that.R[i].id);
+ }
+ }
+ },
+
+
+ //called by chooser click event handler: it will set values and BGs based on R[i][val]
+ initColorPickerValues: function () {
+ var that = this;
+ var parsedColor = that.getParsedColors(that.R[that.i][val]);
+ HexInSession = parsedColor[0];
+ that.iRgbaRange[val] = parsedColor[len] == 2 ? parsedColor[1] : 1;
+ if (HexInSession == _invalid) {
+ that.bgDiv1[sty][bgClr] = that.bgDiv2[sty][bgClr] = trans;
+ that.hexInput[val] = that.rgbaInput[val] = _invalid;
+ }
+ else
+ that.setColorPickerInsideValues(HexInSession);
+
+ },
+ ///possible return values: [''], ['transparent'], ['invalid'], ['#......'], ['#......', num <=1]
+ getParsedColors: function (color) { //,chooser
+ color = color.replace(/\s+/g, '').toLowerCase();
+ var retVal = [_invalid];
+ if (!color || color == trans) {
+ retVal = [color];
+ }
+ else if (color[0] == '#') {
+ if (/^#([a-f0-9]{3}){1,2}$/.test(color)) {
+ retVal = [color];
+ }
+ }
+ else if (/^rgba\(\d+,\d+,\d+,[\.\d]+\)$/.test(color)) {
+ var match = color.match(/^rgba\((\d+,\d+,\d+),([\.\d]+)\)$/);
+ if (match) {
+ if (validColorNumbers(match[1]) && +match[2] <= 1) {
+ retVal = [rgbToHex(match[1]), +match[2]];
+ }
+ }
+ }
+ else if (/^rgb\(\d+,\d+,\d+\)$/.test(color)) {
+ var match = color.match(/^rgb\((\d+,\d+,\d+)\)$/);
+ if (validColorNumbers(match[1])) {
+ retVal = [rgbToHex(match[1])];
+ }
+ }
+ else { //color name such as red, gray. Chrome will change bad name to 'rgba(0,0,0,0)', IE will be 'transparent'
+ var testBox = this.bgDiv1; //chooser ? chooser : this.bgDiv1;
+ testBox[sty][bgClr] = trans;//why having this line of code? It is a bug fix: If color is not a valid color, the next line won't change the previous color, and computedColor will still get the previous color. So we first change it to "transparent"
+ testBox[sty][bgClr] = color;
+ var computedColor = getStyle(testBox)[bgClr];
+
+ //console.log("computedColor", computedColor, color, chooser);
+ if (computedColor.indexOf('rgb(') != -1) {
+ retVal = [rgbToHex(computedColor.replace('rgb(', '').replace(')', ''))];
+ }
+ }
+ return retVal;
+ },
+
+ //called by many events. It will update two inputs and two bg divs
+ setColorPickerInsideValues: function (hex) {
+ var alpha = +picker.iRgbaRange[val];
+ picker.bgDiv1[sty][bgClr] = picker.hexInput[val] = (hex && hex[0] == '#' ? hex.toUpperCase() : hex);
+ picker.bgDiv2[sty][bgClr] = picker.rgbaInput[val] = (hex && hex[0] == '#' ? hexAlphaToRgba(hex, alpha) : hex);//the later hex will be (!hex || hex == 'transparent')
+ }
+ };
+
+ var buildPicker = function () {
+ if (!picker) picker = new Picker();
+ /*if (isiOS) {
+ //stackoverflow.com/questions/17567344/detect-left-right-swipe-on-touch-devices-but-allow-up-down-scrolling
+ //The following defines the touch event handlers for: fast click : 'fc', swipe horizontal: 'swh', swipe vertical: 'swv'
+ (function (d) {
+ var
+ ce = function (e, n) { var a = document.createEvent("CustomEvent"); a.initCustomEvent(n, true, true, e.target); e.target.dispatchEvent(a); a = null; return false },
+ nm = true, sp = { x: 0, y: 0 }, ep = { x: 0, y: 0 },
+ touch = {
+ touchstart: function (e) { sp = { x: e.touches[0].pageX, y: e.touches[0].pageY } },
+ touchmove: function (e) { nm = false; ep = { x: e.touches[0].pageX, y: e.touches[0].pageY } },
+ touchend: function (e) { if (nm) { ce(e, 'fc') } else { var x = ep.x - sp.x, xr = Math.abs(x), y = ep.y - sp.y, yr = Math.abs(y); if (Math.max(xr, yr) > 20) { ce(e, (xr > yr ? 'swh' : 'swv')) } }; nm = true },
+ touchcancel: function (e) { nm = false }
+ };
+ for (var a in touch) { d.addEventListener(a, touch[a], false); }
+ })(picker.element);
+ }*/
+ };
+
+ if (myOptions.initOnPageLoad)
+ addEvent(window, "load", buildPicker);
+
+ return {
+ //reload code below not work. Maybe the removed stuff is still there as it might be referenced somewhere else. So comment it out!
+ //reload: function () {
+ // if (picker.S && picker.S.length) {
+ // console.log(picker.S.length);
+ // for (var i = 0; i < picker.S.length; i++) {
+ // picker.S[i].parentNode.removeChild(picker.S[i]);
+ // picker.S[i] = null;
+ // }
+ // }
+ // picker.R=picker.S=[];
+ // picker.createDropdownSpans();
+ //},
+ hexAlphaToRgba: hexAlphaToRgba, //(hex, alpha) such as hexAlphaToRgba("#DD9999", 0.5)
+ rgbToHex: rgbToHex, //(rgbStr) such as rgbToHex("255,0,0")
+ rgbaToHex: rgbaToSimilarHex, //(rgba, bgHexColor) such as rgbaToSimilarHex("rgba(255,0,0,0.2)", "#DD9999")
+ init: buildPicker //when option is !MenuCool.cpInitOnLoad, you need to call init() manually when you are ready to populate color picker
+ };
+})(MenuCoolRgbaColorPickerOptions);
+
+/*
+ChangeSet #1 (2012-2-10): I use colorContainerEventDelegate to replace each color cell's onclick event. Greatly decreased the number of event listeners.
+ChangeSet #2 (2012-2-10): Use stopPropagation instead of timer to fix the event bubbling to body that will hide the color picker.
+ChangeSet #3 (2012-2-11): add if(target.id!="colorContainer") to fix the IE7 bug that the even responde to element "colorContainer".
+ChangeSet #4 (2012-6-27): add to support Ajax by calling reload. Requested by Birger on 6.26 (check email)
+ChangeSet #5 (2012-6-28): add transparent color to mcColorPicker.
+ChangeSet #6 (2012-8-30): Don't need the menucool link anymore.
+ChangeSet #7 (2014-9-29): DOMContentLoaded instead of window.onload; added DOMContentLoaded handler
+ChangeSet #8 (2014-10-27): If color fields are added quite late like my ddmenu skinBuilder, the buildPicker cannot run onDomReady or even window.onload. So I added option cpInitOnLoad and init() function.
+ChangeSet #9 (2014-11-??): Click color chooser will toggle the display.
+*/
diff --git a/plugins/Customizer/Controller/CustomizerConfigController.php b/plugins/Customizer/Controller/CustomizerConfigController.php new file mode 100644 index 00000000..d816384d --- /dev/null +++ b/plugins/Customizer/Controller/CustomizerConfigController.php @@ -0,0 +1,388 @@ +<?php + +namespace Kanboard\Plugin\Customizer\Controller; + +require_once __DIR__.'/../vendor/autoload.php'; + +use Kanboard\Model\ConfigModel; +use Kanboard\Model\LanguageModel; +use Kanboard\Controller\BaseController; +use luizbills\CSS_Generator\Generator as CSS_Generator; + +/** + * Config Controller + * + * @package Customizer/Controller + * @author creecros + */ +class CustomizerConfigController extends BaseController +{ + + /** + * Save settings + * + */ + public function save() + { + if (isset($_POST['remove'])) { + $values = $this->request->getValues(); + $this->remove($values['themeSelection']); + } else { + + $values = $this->request->getValues(); + + if (array_key_exists('use_custom_login', $values) === false) { $this->configModel->save(['use_custom_login' => '']); } + if (array_key_exists('enable_cache', $values) === false) { $this->configModel->save(['enable_cache' => '']); } + + if ($this->configModel->save($values)) { + $this->languageModel->loadCurrentLanguage(); + $this->flash->success(t('Settings saved successfully.')); + } else { + $this->flash->failure(t('Unable to save your settings.')); + } + + $this->response->redirect($this->helper->url->to('CustomizerFileController', 'show', array('plugin' => 'Customizer'))); + } + } + + /** + * Save user theme + * + */ + public function usertheme() + { + $user = $this->getUser(); + $values = $this->request->getValues(); + + if ($this->userMetadataModel->save($user['id'], $values)) { + $this->languageModel->loadCurrentLanguage(); + $this->flash->success(t('Settings saved successfully.')); + } else { + $this->flash->failure(t('Unable to save your settings.')); + } + + $this->response->redirect($this->helper->url->to('UserViewController', 'show', array('user_id' => $user['id'])), true); + + } + + /** + * Reset all User themes + * + */ + public function resetUserThemes() + { + $users = $this->userModel->getAll(); + foreach ($users as $user) { + $this->userMetadataModel->remove($user['id'], 'themeSelection'); + } + $this->response->redirect($this->helper->url->to('CustomizerFileController', 'show', array('plugin' => 'Customizer'))); + } + + /** + * Toggle User themes + * + */ + public function enableDisableThemes() + { + $status = $this->configModel->get('toggle_user_themes', 'disable'); + + if ($status == 'disable') { $this->configModel->save(['toggle_user_themes' => 'enable']); } else { $this->configModel->save(['toggle_user_themes' => 'disable']); } + $this->response->redirect($this->helper->url->to('CustomizerFileController', 'show', array('plugin' => 'Customizer'))); + } + + + /** + * Upload css theme + * + */ + public function uploadcss() + { + $target_dir = DATA_DIR . '/files/customizer/themes/'; + $target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]); + $uploadOk = 1; + $imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION)); + // Check if file already exists + if (file_exists($target_file)) { + $this->flash->failure(t('Sorry, file already exists.')); + $uploadOk = 0; + } + // Check file size + if ($_FILES["fileToUpload"]["size"] > 1000000) { + $this->flash->failure(t('Sorry, your file is too large.')); + $uploadOk = 0; + } + // Allow certain file formats + if($imageFileType != "css") { + $this->flash->failure(t('Sorry, only CSS files are allowed.')); + $uploadOk = 0; + } + // Check if $uploadOk is set to 0 by an error + if ($uploadOk == 0) { + $this->flash->failure(t('Sorry, your file was not uploaded.')); + // if everything is ok, try to upload file + } else { + if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) { + $this->flash->success(t('Theme file uploaded successfully.')); + } else { + $this->flash->failure(t('Sorry, there was an error uploading your file.')); + } + } + + $this->response->redirect($this->helper->url->to('CustomizerFileController', 'show', array('plugin' => 'Customizer'))); + } + + public function remove($file) + { + $filename = basename($file); + if (file_exists(DATA_DIR . '/files/customizer/themes/' . $filename)) { unlink(DATA_DIR . '/files/customizer/themes/' . $filename); } + unlink($file); + $this->response->redirect($this->helper->url->to('CustomizerFileController', 'show', array('plugin' => 'Customizer'))); + } + + public function create_theme() + { + $values = $this->request->getValues(); + + $options = [ + // default values + // 'indentation' => ' ', // 4 spaces + ]; + + $css = new CSS_Generator($options); + + // Header + $css->add_rule('header', + [ + 'background-color' => $values['header_background'], + 'background-image' => 'linear-gradient(-180deg, transparent 0%, '.$values['header_shade'].' 90%)' + ] + ); + $css->add_rule(['header h1', 'header a .fa'], + [ + 'color' => $values['header_title'] + ] + ); + $css->add_rule('a i.web-notification-icon', + [ + 'color' => $values['notification_icon'] + ] + ); + // Body + $css->add_rule('body', + [ + 'background' => $values['background_color'], + 'color' => $values['font_main'] + ] + ); + $css->add_rule(['ul.dropdown-submenu-open', '.accordion-title h3'], + [ + 'background-color' => $values['background_color'] + ] + ); + $css->add_rule('.dropdown-submenu-open a', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule('a:hover', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule('a .fa', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule(['h1', 'h2', 'h3', '.accordion-toggle'], + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule('.table-list-header a', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule('.table-list-header .table-list-header-count', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule('.table-list-row .table-list-title a', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule('.table-list-row .table-list-details strong', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule('.dropdown-menu-link-icon', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule('.page-header h2 a', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule('.sidebar>ul a:hover', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule('.sidebar>ul li.active a', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule('.task-list-icons a:hover', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule('.task-list-icons a:hover i', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule('.subtask-cell a', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule('a', + [ + 'color' => $values['font_link'] + ] + ); + $css->add_rule('.table-list-category a:hover', + [ + 'color' => $values['font_link'] + ] + ); + $css->add_rule(['.subtask-cell a:hover', '.subtask-cell a:focus'], + [ + 'color' => $values['font_link'] + ] + ); + $css->add_rule(['a:focus', 'a:hover'], + [ + 'color' => $values['font_link_focus'] + ] + ); + $css->add_rule(['.table-list-header a:hover', '.table-list-header a:focus', '.task-board a'], + [ + 'color' => $values['font_secondary'] + ] + ); + $css->add_rule('.table-list-row .table-list-details', + [ + 'color' => $values['font_secondary'] + ] + ); + $css->add_rule('.input-addon-field', + [ + 'color' => $values['font_secondary'] + ] + ); + $css->add_rule(['.page-header h2 a:focus', '.page-header h2 a:hover', 'code'], + [ + 'color' => $values['font_secondary'] + ] + ); + $css->add_rule('.sidebar>ul a', + [ + 'color' => $values['font_secondary'] + ] + ); + $css->add_rule('.task-list-avatars .task-avatar-assignee', + [ + 'color' => $values['font_secondary'] + ] + ); + $css->add_rule(['.task-list-icons a', '.task-list-icons span', '.task-list-icons i', '.task-date'], + [ + 'color' => $values['font_secondary'] + ] + ); + $css->add_rule('span.task-date-overdue', + [ + 'color' => $values['font_overdue'] + ] + ); + $css->add_rule('.sidebar>ul li.active', + [ + 'border-left' => '5px solid '.$values['font_main'] + ] + ); + $css->add_rule(['.sidebar>ul li.active a:focus', '.sidebar>ul li.active a:hover'], + [ + 'color' => $values['font_secondary'] + ] + ); + $css->add_rule('.sidebar>ul li:hover', + [ + 'border-left' => '5px solid '.$values['font_secondary'] + ] + ); + $css->add_rule('.panel', + [ + 'color' => $values['font_main'] + ] + ); + $css->add_rule(['td a.dropdown-menu strong', 'td a.dropdown-menu strong i'], + [ + 'color' => $values['font_main'] + ] + ); + + $css->add_raw(' + #task-summary h2 {color: unset;} + .comments .comment:hover {background: #efefef40;} + .comments .comment:nth-child(even):not(.comment-highlighted):hover {background: #efefef40;} + .comments .comment:nth-child(even):not(.comment-highlighted) {background: #efefef40;} + table.table-striped tr:nth-child(odd) {background: #efefef40;} + header {box-shadow: 0px -1px 5px 1px;border-bottom: none;} + .project-header {margin-bottom: 8px;margin-top: 8px;} + .panel{background-color: #efefef40;border: 1px solid #efefef40;} + .task-board{border-width: 2px;background: #efefef22!important;} + div.task-board-recent {border-width: 2px;} + table td {border:none;} + table th:first-child {border-top-left-radius:8px;} + table th:last-child {border-top-right-radius:8px;} + .table-list-header{background:#efefef40;border:1px solid #efefef40;border-radius:5px 5px 0 0;line-height:28px;padding-left:3px;padding-right:3px;} + .table-list-row{padding-left:3px;padding-right:3px;border-bottom:1px solid #efefef40;border-right:1px solid #efefef40;} + .table-list-row.table-border-left{border-left:1px solid #efefef40;} + .table-list-row:nth-child(odd){background:#efefef30;} + .table-list-row:hover{background:#efefef32;border-bottom:1px solid #efefef32;border-right:1px solid #efefef32;} + .dropdown-menu-link-icon{text-decoration:none;} + .dropdown-submenu-open li{border-bottom:1px solid #efefef40;} + .page-header h2{margin:0;padding:0;font-weight:bold;border-bottom:1px dotted #efefef40;} + .sidebar>ul li{list-style-type:none;line-height:35px;border-bottom:1px dotted #efefef40;padding-left:13px;} + span.task-icon-age-total{border:1px solid #efefef40;padding:1px 3px 1px 3px;border-top-left-radius:3px;border-bottom-left-radius:3px;} + span.task-icon-age-column{border:1px solid #efefef40;border-left:none;margin-left:-5px;padding:1px 3px 1px 3px;border-top-right-radius:3px;border-bottom-right-radius:3px;} + .subtask-cell{padding:4px 10px;border-top:1px dotted #efefef42;border-left:1px dotted #efefef40;display:table-cell;vertical-align:middle;} + table th {text-align: left;padding: 0.5em 3px;border:none;background: #efefef40;} + .views li {white-space: nowrap;background: #efefef40;border:none;border-right: none;padding: 4px 8px;display: inline;} + '); + + + $minify = true; + + $extension = '.css'; + $rename = str_replace('.', '', $values['theme_name']); + + if (file_exists(DATA_DIR . '/files/customizer/themes')) { + file_put_contents(DATA_DIR . '/files/customizer/themes/' . $rename . $extension, $css->get_output($minify)); + } else { + mkdir(DATA_DIR . '/files/customizer/themes', 0755); + file_put_contents(DATA_DIR . '/files/customizer/themes/' . $rename . $extension, $css->get_output($minify)); + } + + $this->response->redirect($this->helper->url->to('CustomizerFileController', 'show', array('plugin' => 'Customizer'))); + } +} diff --git a/plugins/Customizer/Controller/CustomizerFileController.php b/plugins/Customizer/Controller/CustomizerFileController.php new file mode 100644 index 00000000..75271320 --- /dev/null +++ b/plugins/Customizer/Controller/CustomizerFileController.php @@ -0,0 +1,313 @@ +<?php + +namespace Kanboard\Plugin\Customizer\Controller; + +use Kanboard\Plugin\Customizer\Model\CustomizerFileModel; +use Kanboard\Core\ObjectStorage\ObjectStorageException; +use Kanboard\Controller\BaseController; +use Kanboard\Model\ConfigModel; + +/** + * Customizer Controller + * + * @package Customizer\Controller + * @author creecros + */ +class CustomizerFileController extends BaseController +{ + /** + * Get file content from object storage + * + * @access protected + * @param array $file + * @return string + */ + protected function getFileContent(array $file) + { + $content = ''; + + try { + if ($file['is_image'] == 0) { + $content = $this->objectStorage->get($file['path']); + } + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + } + + return $content; + } + + /** + * Output file with cache + * + * @param array $file + * @param $mimetype + */ + protected function renderFileWithCache(array $file, $mimetype) + { + $etag = md5($file['path']); + + if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') { + $this->response->status(304); + } else { + try { + $this->response->withContentType($mimetype); + $this->response->withCache(5 * 86400, $etag); + $this->response->send(); + $this->objectStorage->output($file['path']); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + } + } + } + + /** + * Output file without cache + * + * @param array $file + * @param $mimetype + */ + protected function renderFileWithoutCache(array $file, $mimetype) + { + $etag = md5($file['path']); + + if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') { + $this->response->status(304); + } else { + try { + $this->response->withContentType($mimetype); + $this->response->withOutCache(); + $this->response->send(); + $this->objectStorage->output($file['path']); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + } + } + } + + public function show() + { + $logo = $this->customizerFileModel->getByType(1); + $flavicon = $this->customizerFileModel->getByType(2); + $loginlogo = $this->customizerFileModel->getByType(3); + $logopath = $logo['path']; + $flaviconpath = $flavicon['path']; + $this->response->html($this->helper->layout->config('customizer:file/show', array( + 'logo' => $logo, + 'title' => t('Settings').' > '.t('Customizer'), + 'flavicon' => $flavicon, + 'logopath' => $logopath, + 'flaviconpath' => $flaviconpath, + 'loginlogo' => $loginlogo + ))); + + } + + public function logo() + { + if ($this->logoexists()) { + $file = $this->customizerFileModel->getByType(1); + if ($this->configModel->get('enable_cache', '') == 'checked') { + $this->renderFileWithCache($file, $this->helper->file->getImageMimeType($file['name'])); + } else { + $this->renderFileWithoutCache($file, $this->helper->file->getImageMimeType($file['name'])); + } + } + } + + public function logo_setting() + { + if ($this->logoexists()) { + $file = $this->customizerFileModel->getByType(1); + $this->renderFileWithoutCache($file, $this->helper->file->getImageMimeType($file['name'])); + } + } + + public function loginlogo() + { + if ($this->loginlogoexists()) { + $file = $this->customizerFileModel->getByType(3); + if ($this->configModel->get('enable_cache', '') == 'checked') { + $this->renderFileWithCache($file, $this->helper->file->getImageMimeType($file['name'])); + } else { + $this->renderFileWithoutCache($file, $this->helper->file->getImageMimeType($file['name'])); + } + } + } + + public function loginlogo_setting() + { + if ($this->loginlogoexists()) { + $file = $this->customizerFileModel->getByType(3); + $this->renderFileWithoutCache($file, $this->helper->file->getImageMimeType($file['name'])); + } + } + + public function icon() + { + if ($this->iconexists()) { + $file = $this->customizerFileModel->getByType(2); + if ($this->configModel->get('enable_cache', '') == 'checked') { + $this->renderFileWithCache($file, $this->helper->file->getImageMimeType($file['name'])); + } else { + $this->renderFileWithoutCache($file, $this->helper->file->getImageMimeType($file['name'])); + } + } + } + + public function icon_setting() + { + if ($this->iconexists()) { + $file = $this->customizerFileModel->getByType(2); + $this->renderFileWithoutCache($file, $this->helper->file->getImageMimeType($file['name'])); + } + } + + public function link() + { + if ($this->logoexists() && $this->linkexists()) { + return $this->response->redirect($this->configModel->get('login_link', 'https://kanboard.org')); + } else { + return $this->response->redirect($this->configModel->get('application_url', '') . 'login'); + } + } + + public function logoexists() + { + if (null !== $this->customizerFileModel->getByType(1)) { return true; } else { return false; } + } + + public function loginlogoexists() + { + if (null !== $this->customizerFileModel->getByType(3)) { + $customizer['loginCheck'] = true; + return true; + } else { + $customizer['loginCheck'] = false; + return false; + } + } + + public function linkexists() + { + if ($this->configModel->exists('login_link')) { return true; } else { return false; } + } + + public function iconexists() + { + if (null !== $this->customizerFileModel->getByType(2)) { return true; } else { return false; } + } + + public function image() + { + $file = $this->customizerFileModel->getById($this->request->getIntegerParam('file_id')); + if ($this->configModel->get('enable_cache', '') == 'checked') { + $this->renderFileWithCache($file, $this->helper->file->getImageMimeType($file['name'])); + } else { + $this->renderFileWithoutCache($file, $this->helper->file->getImageMimeType($file['name'])); + } + } + + /** + * File upload form + * + * @access public + */ + public function create() + { + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + error_reporting(E_ALL); + $custom_id = $this->request->getIntegerParam('custom_id'); + if ($custom_id == 1) { + $this->response->html($this->template->render('customizer:file/upload_logo', array( + 'custom_id' => $custom_id, + 'multiple' => false, + ))); + } else if ($custom_id == 2) { + $this->response->html($this->template->render('customizer:file/upload_flavicon', array( + 'custom_id' => $custom_id, + 'multiple' => false, + ))); + } else if ($custom_id == 3) { + $this->response->html($this->template->render('customizer:file/upload_loginlogo', array( + 'custom_id' => $custom_id, + 'multiple' => false, + ))); + } + } + + /** + * File upload (save files) + * + * @access public + */ + public function save() + { + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + error_reporting(E_ALL); + $custom_id = $this->request->getIntegerParam('custom_id'); + if ($custom_id ==3) { $loginCheck = true; } + + $result = $this->customizerFileModel->uploadFiles($custom_id, $this->request->getFileInfo('files')); + if ($this->request->isAjax()) { + if (!$result) { + $this->response->json(array('message' => t('Unable to upload files, check the permissions of your data folder.')), 500); + } else { + $this->response->json(array('message' => 'OK')); + } + } else { + if (!$result) { + $this->flash->failure(t('Unable to upload files, check the permissions of your data folder.')); + } + } + } + /** + * Remove a file + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $custom_id = $this->request->getIntegerParam('custom_id'); + $file = $this->customizerFileModel->getById($this->request->getIntegerParam('file_id')); + if ($this->customizerFileModel->remove($file['id'])) { + $this->flash->success(t('File removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this file.')); + } + + } + + public function removeform() + { + $this->checkCSRFParam(); + $custom_id = $this->request->getIntegerParam('custom_id'); + if ($custom_id == 3) { $loginCheck = false; } + $file = $this->customizerFileModel->getById($this->request->getIntegerParam('file_id')); + if ($this->customizerFileModel->remove($file['id'])) { + $this->flash->success(t('File removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this file.')); + } + + return $this->response->redirect($this->configModel->get('application_url', '') . 'settings/customizer'); + + } + /** + * Confirmation dialog before removing a file + * + * @access public + */ + public function confirm() + { + $custom_id = $this->request->getIntegerParam('custom_id'); + $file = $this->customizerFileModel->getById($this->request->getIntegerParam('file_id')); + $this->response->html($this->template->render('customizer:file/remove', array( + 'custom_id' => $custom_id, + 'file' => $file, + ))); + } +} diff --git a/plugins/Customizer/Helper/DynamicAvatar.php b/plugins/Customizer/Helper/DynamicAvatar.php new file mode 100644 index 00000000..cf7ab83f --- /dev/null +++ b/plugins/Customizer/Helper/DynamicAvatar.php @@ -0,0 +1,59 @@ +<?php + +namespace Kanboard\Plugin\Customizer\Helper; + +use Kanboard\Helper\AvatarHelper; +use Kanboard\Core\Base; +/** + * Avatar Helper + * + * @package helper + * @author Craig Crosby + */ +class DynamicAvatar extends AvatarHelper +{ + + public function dynamicRender($user_id, $username, $name, $email, $avatar_path, $css = 'avatar-left', $size = 48) + { + if (empty($user_id) && empty($username)) { + $html = $this->avatarManager->renderDefault($size); + } else { + $html = $this->avatarManager->render($user_id, $username, $name, $email, $avatar_path, $size); + } + return '<div id="'.$css.'" class="avatar avatar-dyn '.$css.'">'.$html.'</div>'; + } + + public function dynamic($user_id, $username, $name, $email, $avatar_path, $css = '', $size) + { + return $this->dynamicRender($user_id, $username, $name, $email, $avatar_path, $css, $size); + } + + public function currentUserDynamic($css = '') + { + $user = $this->userSession->getAll(); + return $this->dynamic($user['id'], $user['username'], $user['name'], $user['email'], $user['avatar_path'], $css, $this->configModel->get('av_size', '20')); + } + + public function boardDynamicRender($user_id, $username, $name, $email, $avatar_path, $css = 'avatar-left', $size = 48) + { + if (empty($user_id) && empty($username)) { + $html = $this->avatarManager->renderDefault($size); + } else { + $html = $this->avatarManager->render($user_id, $username, $name, $email, $avatar_path, $size); + } + return '<div id="'.$css.'" class="avatar avatar-bdyn '.$css.'">'.$html.'</div>'; + } + + public function boardDynamic($user_id, $username, $name, $email, $avatar_path, $css = '', $size) + { + return $this->boardDynamicRender($user_id, $username, $name, $email, $avatar_path, $css, $size); + } + + public function boardCurrentUserDynamic($css = '') + { + $user = $this->userSession->getAll(); + return $this->boardDynamic($user['id'], $user['username'], $user['name'], $user['email'], $user['avatar_path'], $css, $this->configModel->get('b_av_size', '20')); + } + + + } diff --git a/plugins/Customizer/Helper/ThemeHelper.php b/plugins/Customizer/Helper/ThemeHelper.php new file mode 100644 index 00000000..c2f767d1 --- /dev/null +++ b/plugins/Customizer/Helper/ThemeHelper.php @@ -0,0 +1,64 @@ +<?php + +namespace Kanboard\Plugin\Customizer\Helper; + +use Kanboard\Core\Base; + +class ThemeHelper extends Base +{ + + public function reverseSelect($name, array $options, array $values = array(), array $errors = array(), array $attributes = array(), $class = '') + { + $html = '<select name="'.$name.'" id="form-'.$name.'" class="'.$class.'" '.implode(' ', $attributes).'>'; + foreach ($options as $id => $value) { + $html .= '<option value="'.$this->helper->text->e($value).'"'; + if (isset($values->$name) && $value == $values->$name) { + $html .= ' selected="selected"'; + } + if (isset($values[$name]) && $value == $values[$name]) { + $html .= ' selected="selected"'; + } + $html .= '>'.$this->helper->text->e($id).'</option>'; + } + $html .= '</select>'; + $html .= $this->errorList($errors, $name); + return $html; + } + + public function reverseSelectOnChange($name, array $options, array $values = array(), array $errors = array(), array $attributes = array(), $class = '') + { + $html = '<select name="'.$name.'" id="userthemeSelection" class="'.$class.'" '.implode(' ', $attributes).'>'; + foreach ($options as $id => $value) { + $html .= '<option value="'.$this->helper->text->e($value).'"'; + if (isset($values->$name) && $value == $values->$name) { + $html .= ' selected="selected"'; + } + if (isset($values[$name]) && $value == $values[$name]) { + $html .= ' selected="selected"'; + } + $html .= '>'.$this->helper->text->e($id).'</option>'; + } + $html .= '</select>'; + $html .= $this->errorList($errors, $name); + return $html; + } + + private function errorClass(array $errors, $name) + { + return ! isset($errors[$name]) ? '' : ' form-error'; + } + + private function errorList(array $errors, $name) + { + $html = ''; + if (isset($errors[$name])) { + $html .= '<ul class="form-errors">'; + foreach ($errors[$name] as $error) { + $html .= '<li>'.$this->helper->text->e($error).'</li>'; + } + $html .= '</ul>'; + } + return $html; + } + + } diff --git a/plugins/Customizer/LICENSE b/plugins/Customizer/LICENSE new file mode 100644 index 00000000..7c5e911a --- /dev/null +++ b/plugins/Customizer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Craig Crosby + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/Customizer/Locale/it_IT/translations.php b/plugins/Customizer/Locale/it_IT/translations.php new file mode 100644 index 00000000..aea56cfc --- /dev/null +++ b/plugins/Customizer/Locale/it_IT/translations.php @@ -0,0 +1,49 @@ +<?php + +return [ + 'Image Assets & Settings' => 'Risorse e impostazioni dell\'immagini', + 'Header Image' => 'Logo dell\'intestazione', + 'Login Image' => 'Logo di accesso', + 'Upload Header Logo' => 'Carica il logo dell\'intestazione', + 'Header Logo' => 'Logo intestazione', + 'Recommend 100 pixels in width, *.png, *.jpg, *.gif, max size 500kb.' => 'Dimensione consigliata in larghezza 100 pixel, formati: * .png, * .jpg, * .gif, dimensione massima 500kb.', + 'Drag and drop your file here' => 'Trascina e rilascia il tuo file qui', + 'or' => 'o', + 'choose file' => 'scegli il file', + 'Recommend 50x50 pixels, *.png only, max size 20kb.' => 'Dimensione consigliata 50x50 pixel, formato consentito * .png, dimensione massima 20kb.', + 'Remove Header Logo' => 'Rimuovi il logo dell\'intestazione', + 'Upload Login Logo' => 'Carica il logo di accesso', + 'Remove Login Logo' => 'Rimuovi il logo di accesso', + 'Favicon Image' => 'Immagine favicon', + 'Upload Favicon' => 'Carica favicon', + 'Remove Favicon' => 'Rimuovi favicon', + 'Links & Settings' => 'Collegamenti e impostazioni', + 'Login Link' => 'Link di accesso', + 'Example: <code>https://example.kanboard.org/</code> (used as logo link on login page)' => 'Esempio: <code> https://example.kanboard.org </code> (utilizzato come collegamento logo sulla pagina di accesso)', + 'Login Page Background Color' => 'Colore di sfondo della pagina di accesso', + 'Login Panel Shadow Color' => 'Colore dell\'ombra del pannello di accesso', + 'Login Panel Border Color' => 'Colore del bordo del pannello di accesso', + 'Login Panel Border Thickness' => 'Spessore del bordo del pannello di accesso', + 'Login Panel Color' => 'Colore del pannello di accesso', + 'Login Panel Shadow Intensity' => 'Intensità dell\'ombra del pannello di accesso', + 'Login Button Background Color' => 'Colore di sfondo del pulsante di accesso', + 'Login Button Shadow Color' => 'Colore dell\'ombra del pulsante di accesso', + 'Login Button Border Color' => 'Colore del bordo del pulsante di accesso', + 'Login Button Shade Color' => 'Colore sfumato del pulsante di accesso', + 'Login Button Font Color' => 'Colore del carattere del pulsante di accesso', + 'Login Button Shadow Intensity' => 'Intensità ombra del pulsante di accesso', + 'Login Button Border Thickness' => 'Spessore bordo del pulsante di accesso', + 'Login Button Width' => 'Larghezza del pulsante di accesso', + 'Preview' => 'Anteprima', + 'Login Background Image URL' => 'URL dell\'immagine di sfondo di accesso', + 'Header Logo Size' => 'Dimensioni del logo dell\'intestazione', + 'Example: <code>https://source.unsplash.com/random</code> (URL for a background image on the login page, centered, autoscale, no-repeat)' => 'Esempio: <codice> https://source.unsplash.com/random </ code> (URL per un\'immagine di sfondo nella pagina di accesso, centrata, automatica, senza ripetizione)', + 'Login Logo Size' => 'Dimensione logo di accesso', + 'Example: <code>30</code> (Default is 30px in height, intgers only, max 999)' => 'Esempio: <code> 30 </code> (l\'impostazione predefinita è 30px in altezza, solo numeri interi, max 999)', + 'Example: <code>50</code> (Default is 50px in height, intgers only, max 999)' => 'Esempio: <code> 50 </code> (l\'impostazione predefinita è 50px in altezza, solo numeri interi, max 999)', + 'Theme' => 'Tema', + 'You can see a preview only after saving' => 'Puoi vedere l\'anteprima solo dopo aver salvato', + 'Login Page Settings' => 'Impostazioni della pagina di accesso', + ' pixels high' => ' altezza pixel', + 'Use Custom Login Settings' => 'Usa le impostazioni di accesso personalizzate' +]; diff --git a/plugins/Customizer/Model/CustomizerFileModel.php b/plugins/Customizer/Model/CustomizerFileModel.php new file mode 100644 index 00000000..64dbc2c8 --- /dev/null +++ b/plugins/Customizer/Model/CustomizerFileModel.php @@ -0,0 +1,320 @@ +<?php + +namespace Kanboard\Plugin\Customizer\Model; + +use Exception; +use Kanboard\Core\Base; +use Kanboard\Core\Thumbnail; +use Kanboard\Core\ObjectStorage\ObjectStorageException; + +/** + * Customizer File Model + * + * @package Customizer\Model + * @author creecros + */ +class CustomizerFileModel extends Base +{ + /** + * Table name + * + * @var string + */ + const TABLE = 'customizer_files'; + + /** + * Get the table + * + * @abstract + * @access protected + * @return string + */ + public function getTable() + { + return self::TABLE; + } + + /** + * Define the path prefix + * + * @abstract + * @access protected + * @return string + */ + public function getPathPrefix() + { + return 'customizer'; + } + + /** + * Get a file by the Id + * + * @access public + * @param integer $file_id File id + * @return array + */ + public function getById($file_id) + { + return $this->db->table($this->getTable())->eq('id', $file_id)->findOne(); + } + + /** + * Get a file by the type + * + * @access public + * @param integer $custom_id 1=logo 2=flavicon 3=loginlogo + * @return array + */ + public function getByType($custom_id) + { + return $this->db->table($this->getTable())->eq('custom_id', $custom_id)->findOne(); + } + + /** + * Get a file id by the type + * + * @access public + * @param integer $custom_id 1=logo 2=flavicon 3=loginlogo + * @return array + */ + public function getIdByType($custom_id) + { + $file = $this->db->table($this->getTable())->eq('custom_id', $custom_id)->findOne(); + return $file['id']; + } + + /** + * Get all files by the type + * + * @access public + * @param integer $custom_id 1=logo 2=flavicon 3=loginlogo + * @return array + */ + public function getAllByType($custom_id) + { + return $this->db->table($this->getTable())->eq('custom_id', $custom_id)->findAll(); + } + + /** + * Create a file entry in the database + * + * @access public + * @param integer $custom_id 1=logo 2=flavicon + * @param string $name Filename + * @param string $path Path on the disk + * @param integer $size File size + * @return bool|integer + */ + public function create($custom_id, $name, $path, $size) + { + $values = array( + 'custom_id' => $custom_id, + 'name' => substr($name, 0, 255), + 'path' => $path, + 'is_image' => $this->isImage($name) ? 1 : 0, + 'size' => $size, + 'user_id' => $this->userSession->getId() ?: 0, + 'date' => time(), + ); + if (null !== $this->getByType($custom_id)) { + foreach ($this->getAllByType($custom_id) as $image) { $this->remove($image['id']); } + $result = $this->db->table($this->getTable())->insert($values); + } else { + $result = $this->db->table($this->getTable())->insert($values); + } + if ($result) { + $file_id = (int) $this->db->getLastId(); + return $file_id; + } + return false; + } + + /** + * Remove a file + * + * @access public + * @param integer $file_id File id + * @return bool + */ + public function remove($file_id) + { + try { + $file = $this->getById($file_id); + $this->objectStorage->remove($file['path']); + if ($file['is_image'] == 1) { + $this->objectStorage->remove($this->getThumbnailPath($file['path'])); + } + return $this->db->table($this->getTable())->eq('id', $file['id'])->remove(); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + return false; + } + } + + /** + * Check if a filename is an image (file types that can be shown as thumbnail) + * + * @access public + * @param string $filename Filename + * @return bool + */ + public function isImage($filename) + { + switch (get_file_extension($filename)) { + case 'jpeg': + case 'jpg': + case 'png': + case 'gif': + return true; + } + return false; + } + /** + * Generate the path for a thumbnails + * + * @access public + * @param string $key Storage key + * @return string + */ + public function getThumbnailPath($key) + { + return 'thumbnails'.DIRECTORY_SEPARATOR.$key; + } + + /** + * Generate the path for a new filename + * + * @access public + * @param integer $id Foreign key + * @param string $filename Filename + * @return string + */ + public function generatePath($id, $filename) + { + return $this->getPathPrefix().DIRECTORY_SEPARATOR.$id.DIRECTORY_SEPARATOR.hash('sha1', $filename.time()); + } + + /** + * Upload multiple files + * + * @access public + * @param integer $id + * @param array $files + * @return bool + */ + public function uploadFiles($id, array $files) + { + try { + if (empty($files)) { + return false; + } + foreach (array_keys($files['error']) as $key) { + $file = array( + 'name' => $files['name'][$key], + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key], + 'error' => $files['error'][$key], + ); + $this->uploadFile($id, $file); + } + return true; + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + return false; + } + } + + /** + * Upload a file + * + * @access public + * @param integer $id + * @param array $file + * @throws Exception + */ + public function uploadFile($id, array $file) + { + if ($file['error'] == UPLOAD_ERR_OK && $file['size'] > 0) { + $destination_filename = $this->generatePath($id, $file['name']); + if ($this->isImage($file['name'])) { + $this->generateThumbnailFromFile($file['tmp_name'], $destination_filename); + } + $this->objectStorage->moveUploadedFile($file['tmp_name'], $destination_filename); + $this->create($id, $file['name'], $destination_filename, $file['size']); + } else { + throw new Exception('File not uploaded: '.var_export($file['error'], true)); + } + } + + /** + * Handle file upload (base64 encoded content) + * + * @access public + * @param integer $id + * @param string $originalFilename + * @param string $data + * @param bool $isEncoded + * @return bool|int + */ + public function uploadContent($id, $originalFilename, $data, $isEncoded = true) + { + try { + if ($isEncoded) { + $data = base64_decode($data); + } + if (empty($data)) { + $this->logger->error(__METHOD__.': Content upload with no data'); + return false; + } + $destinationFilename = $this->generatePath($id, $originalFilename); + $this->objectStorage->put($destinationFilename, $data); + if ($this->isImage($originalFilename)) { + $this->generateThumbnailFromData($destinationFilename, $data); + } + return $this->create( + $id, + $originalFilename, + $destinationFilename, + strlen($data) + ); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + return false; + } + } + + /** + * Generate thumbnail from a blob + * + * @access public + * @param string $destination_filename + * @param string $data + */ + public function generateThumbnailFromData($destination_filename, &$data) + { + $blob = Thumbnail::createFromString($data) + ->resize() + ->toString(); + $this->objectStorage->put($this->getThumbnailPath($destination_filename), $blob); + } + /** + * Generate thumbnail from a local file + * + * @access public + * @param string $uploaded_filename + * @param string $destination_filename + */ + public function generateThumbnailFromFile($uploaded_filename, $destination_filename) + { + $blob = Thumbnail::createFromFile($uploaded_filename) + ->resize() + ->toString(); + $this->objectStorage->put($this->getThumbnailPath($destination_filename), $blob); + } + + public function getUserSessionId() + { + return $this->userSession->getId(); + } + +} diff --git a/plugins/Customizer/Plugin.php b/plugins/Customizer/Plugin.php new file mode 100644 index 00000000..4055d4a4 --- /dev/null +++ b/plugins/Customizer/Plugin.php @@ -0,0 +1,173 @@ +<?php + +namespace Kanboard\Plugin\Customizer; + +use Kanboard\Core\Plugin\Base; +use Kanboard\Core\Translator; +use Kanboard\Core\Security\Role; +use Kanboard\Event\AuthSuccessEvent; +use Kanboard\Core\Security\AuthenticationManager; +use Kanboard\Plugin\Customizer\Model\CustomizerFileModel; + +class Plugin extends Base +{ + + public function initialize() + { + global $customizer; + + // Themes + $customizer['themes'] = array( + 'Default' => 'plugins/Customizer/Assets/css/theme.css' + ); + + $scanned_temp_themes = array_diff(scandir('plugins/Customizer/Assets/css/userthemes'), array('..', '.')); + $scanned_preset_themes = array_diff(scandir('plugins/Customizer/Assets/css/themes'), array('..', '.')); + + foreach ($scanned_temp_themes as $theme) { + unlink('plugins/Customizer/Assets/css/userthemes/' . $theme); + } + + if (file_exists(DATA_DIR . '/files/customizer/themes')) { + $scanned_user_themes = array_diff(scandir(DATA_DIR . '/files/customizer/themes'), array('..', '.')); + foreach ($scanned_user_themes as $theme) { + copy(DATA_DIR . '/files/customizer/themes/' . $theme, 'plugins/Customizer/Assets/css/userthemes/' . $theme); + $customizer['themes'][rtrim($theme, '.css')] = 'plugins/Customizer/Assets/css/userthemes/' . $theme; + } + } else { mkdir(DATA_DIR . '/files/customizer/themes', 0755, true); } + + foreach ($scanned_preset_themes as $theme) { + $customizer['themes'][rtrim($theme, '.css')] = 'plugins/Customizer/Assets/css/themes/' . $theme; + } + + + + //Helper + $this->helper->register('themeHelper', '\Kanboard\Plugin\Customizer\Helper\ThemeHelper'); + $this->helper->register('dynamicAvatar', '\Kanboard\Plugin\Customizer\Helper\DynamicAvatar'); + + //Check if login logo is set + if (null !== $this->customizerFileModel->getByType(3)) { + $customizer['loginCheck'] = true; + } else { + $customizer['loginCheck'] = false; + } + + //Grabs login page settings from database + $customizer['backURL'] = $this->configModel->get('background_url', ''); + $customizer['backColor'] = $this->configModel->get('loginbackground_color', '#ffffff'); + $customizer['logoSize'] = $this->configModel->get('loginlogo_size', '50'); + $customizer['loginpanel_color'] = $this->configModel->get('loginpanel_color', '#ffffff'); + $customizer['login_shadow_color'] = $this->configModel->get('login_shadow_color', '#333'); + $customizer['login_shadow'] = $this->configModel->get('login_shadow', '0'); + $customizer['login_border_color'] = $this->configModel->get('login_border_color', '#ffffff'); + $customizer['login_border'] = $this->configModel->get('login_border', '0'); + $customizer['login_btn_color'] = $this->configModel->get('login_btn_color', '#3079ed'); + $customizer['login_btn_shadow_color'] = $this->configModel->get('login_btn_shadow_color', '#333'); + $customizer['login_btn_border_color'] = $this->configModel->get('login_btn_border_color', 'transparent'); + $customizer['login_btn_shade_color'] = $this->configModel->get('login_btn_shade_color', 'transparent'); + $customizer['login_btn_font_color'] = $this->configModel->get('login_btn_font_color', '#ffffff'); + $customizer['login_btn_shadow'] = $this->configModel->get('login_btn_shadow', '0'); + $customizer['login_btn_border'] = $this->configModel->get('login_btn_border', '0'); + $customizer['login_btn_width'] = $this->configModel->get('login_btn_width', '95'); + $customizer['login_note'] = $this->configModel->get('login_note', ''); + + //Templates and Assets + $this->template->hook->attach('template:config:sidebar', 'customizer:config/sidebar'); + $this->template->setTemplateOverride('header/title', 'customizer:header/title'); + $this->template->setTemplateOverride('header/user_dropdown', 'customizer:header/user_dropdown'); + $this->template->setTemplateOverride('board/task_avatar', 'customizer:board/task_avatar'); + $this->template->setTemplateOverride('layout', 'customizer:layout/layout'); + $this->template->setTemplateOverride('auth/index', 'customizer:layout/index'); + $this->hook->on('template:layout:css', array('template' => 'plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.css')); + $this->hook->on('template:layout:js', array('template' => 'plugins/Customizer/Assets/rgbaColorPicker/rgbaColorPicker.js')); + $this->hook->on('template:layout:css', array('template' => 'plugins/Customizer/Assets/css/customizer.css')); + $this->hook->on('template:layout:js', array('template' => 'plugins/Customizer/Assets/js/customizer.js')); + $this->template->hook->attach('customizer:config:themecreator', 'customizer:config/themecreator'); + + if ($customizer['login_note'] != '') { + $this->template->hook->attach('template:auth:login-form:newbox', 'customizer:layout/note'); + } + if ($this->configModel->get('toggle_user_themes', 'disable') == 'enable') { + $this->template->setTemplateOverride('user_modification/show', 'customizer:user_mod/show'); + } + + + if ($this->configModel->get('use_custom_login', '') == 'checked') { + $this->template->hook->attach('customizer:config:style', 'customizer:layout/preview_style'); + $this->template->hook->attach('template:auth:login-form:before', 'customizer:layout/login_with_custom'); + } else { + $this->template->hook->attach('template:auth:login-form:before', 'customizer:layout/login_no_custom'); + } + + //Routes + $this->route->addRoute('settings/customizer', 'CustomizerFileController', 'show', 'Customizer'); + + //Permissions for login page to access logos + $this->applicationAccessMap->add('CustomizerFileController', array('image', 'loginlogo', 'logo', 'link', 'logoexists', 'linkexists'), Role::APP_PUBLIC); + + //Get accurate version + $wasmaster = str_replace('v', '', APP_VERSION); + $wasmaster = preg_replace('/\s+/', '', $wasmaster); + + if (strpos(APP_VERSION, 'master') !== false && file_exists('ChangeLog')) { $wasmaster = trim(file_get_contents('ChangeLog', false, null, 8, 6), ' '); } + if (version_compare($wasmaster, '1.2.4') >= 0) { + $this->template->setTemplateOverride('header/title', 'customizer:header/title'); + } else { + $this->template->setTemplateOverride('header/title', 'customizer:header/title_older_kb'); + } + + } + + public function onStartup() + { + Translator::load($this->languageModel->getCurrentLanguage(), __DIR__.'/Locale'); + $user_id = $this->customizerFileModel->getUserSessionId(); + $user_theme = $this->userMetadataModel->get($user_id, 'themeSelection', $this->configModel->get('themeSelection', 'plugins/Customizer/Assets/css/theme.css' )); + $default_theme = $this->configModel->get('themeSelection', 'plugins/Customizer/Assets/css/theme.css'); + if ($this->configModel->get('toggle_user_themes', 'disable') == 'enable') { + $this->hook->on('template:layout:css', array('template' => $user_theme)); + } else { + $this->hook->on('template:layout:css', array('template' => $default_theme)); + } + } + + public function getClasses() { + return array( + 'Plugin\Customizer\Model' => array( + 'CustomizerFileModel', + ) + ); + } + + public function getPluginName() + { + return 'Customizer'; + } + + public function getPluginDescription() + { + return t('Completely customize your Kanboard experience with logos, favicons & themes.'); + } + + public function getPluginAuthor() + { + return 'Craig Crosby'; + } + + public function getPluginVersion() + { + return '1.13.1'; + } + + public function getPluginHomepage() + { + return 'https://github.com/creecros/Customizer'; + } + + public function getCompatibleVersion() + { + return '>=1.0.42'; + } +} + diff --git a/plugins/Customizer/README.md b/plugins/Customizer/README.md new file mode 100644 index 00000000..9ae17cdb --- /dev/null +++ b/plugins/Customizer/README.md @@ -0,0 +1,142 @@ +## Checkout our latest project +[![](https://raw.githubusercontent.com/docpht/docpht/master/public/assets/img/logo.png)](https://github.com/docpht/docpht) + +- With [DocPHT](https://github.com/docpht/docpht) you can take notes and quickly document anything and without the use of any database. +----------- +[![Latest release](https://img.shields.io/github/release/creecros/Customizer.svg)](https://github.com/creecros/Customizer/releases) +[![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://github.com/creecros/Customizer/blob/master/LICENSE) +[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/creecros/Customizer/graphs/contributors) +[![Open Source Love](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)]() +[![Downloads](https://img.shields.io/github/downloads/creecros/Customizer/total.svg)](https://github.com/creecros/Customizer/releases) + +Donate to help keep this project maintained. +<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SEGNEVQFXHXGW&source=url"> +<img src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif" border="0" name="submit" title="PayPal - The safer, easier way to pay online!" alt="Donate with PayPal button" /></a> + +**:star: If you use it, you should star it on Github!** +*It's the least you can do for all the work put into it!* + +Customizer - it's like magic! +---------- + +## Author: [creecros](https://github.com/creecros) +### Other [Contributors](https://github.com/creecros/Customizer/graphs/contributors) + +If you need a Logo and Favicon, try using this simple logo generator: https://creecros.github.io/simple_logo_gen/ + +:sparkles: **Adds a settings panel Settings>Customizer:** + +![image](https://user-images.githubusercontent.com/26339368/49309828-7fc47800-f4aa-11e8-809e-8cc9686a2a2d.png) + +**GUI to customize...** + +* [header logo](#header-logo-rainbow) + * set header logo size +* [upper right corner Avatar Icon](#header-avatar-icon-boy) + * set icon size + * set border radius +* [task board Avatar Icon](#task-board-avatar-icon-girl) + * set icon size + * set border radius +* [customizable login screen](#fully-customizable-login-screen-gem) + * [login logo](#login-logo-peach) + * [login page background image url](#login-screen-background-image-dart) + * login logo link + * set login logo size + * set login page background color + * set login panel color + * panel shadow adjustment (no shadow to heavy shadow with color adjustment) + * panel border adjustment (no border to thick border with color adjustment) + * add a [custom note panel](#login-screen-custom-note-book) on login screen +* [favicon](#favicon-beginner) +* theme management + * can upload css files to add to theme selection + * can remove themes from interface + * can enable/disable user themes (i.e. one global theme for everyone, or users get to select their own theme) + * admins can reset all users themes to default +* [theme selector](#includes-preset-themes-mega) with 7 preloaded themes + * Github :octocat: + * Galaxy :milky_way: + * Breathe :partly_sunny: + * Clemson <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/Clemson_Tigers_logo.svg/2000px-Clemson_Tigers_logo.svg.png" height="20"> + * Blueboard :blue_book: + * Material :sparkles: + * KindaDark :8ball: +* [theme creator](#theme-creator-notes) + + +**...to your site without any backend coding or config settings.** + +**Want more customization? Take a look at [BlueTek](https://github.com/BlueTeck)'s [BoardCustomizer](https://github.com/BlueTeck/kanboard_plugin_boardcustomizer) plugin** + +## Favicon :beginner: + +![image](https://user-images.githubusercontent.com/26339368/47174055-a43f0900-d2dd-11e8-9932-430e11b74fea.png) + +## Header Logo :rainbow: + +![image](https://user-images.githubusercontent.com/26339368/47369113-f9e62d80-d6b0-11e8-90e0-974c31b4b535.png) + +## Header Avatar Icon :boy: + +![image](https://user-images.githubusercontent.com/26339368/55774303-8bc8d380-5a62-11e9-8d59-6dc2a1c33387.png) + +## Task Board Avatar Icon :girl: + +![image](https://user-images.githubusercontent.com/26339368/59513842-ae73d000-8e89-11e9-94b5-ae27a0f8c651.png) + +## Login Logo :peach: + +![image](https://user-images.githubusercontent.com/26339368/48488290-622ab980-e7ee-11e8-8efd-58d7b834a02f.png) + +## Fully Customizable Login Screen :gem: + +![image](https://user-images.githubusercontent.com/26339368/48627714-44428d80-e983-11e8-8451-2e873572007a.png) + +![image](https://user-images.githubusercontent.com/26339368/48627526-ca120900-e982-11e8-9870-fd469c553124.png) + +## Login Screen Background Image :dart: + +![image](https://user-images.githubusercontent.com/26339368/47959793-573c8180-dfc3-11e8-84bc-ab654f8c50b5.png) + +## Login Screen Custom Note :book: + +![image](https://user-images.githubusercontent.com/26339368/64443449-e6ad3800-d09f-11e9-8d77-49e05db75e89.png) + +## Includes Preset Themes :mega: + +![image](https://user-images.githubusercontent.com/26339368/48488078-f2b4ca00-e7ed-11e8-8d4b-37d5b51f374b.png) +![image](https://user-images.githubusercontent.com/26339368/48488101-ffd1b900-e7ed-11e8-8438-9ec7b91c98d9.png) +![image](https://user-images.githubusercontent.com/26339368/49310809-64a73780-f4ad-11e8-81e6-82852275199a.png) + +:8ball: KindaDark + +<img src="https://i.imgur.com/Mw3DRBM.png" /> + + +## Theme Creator :notes: + +![image](https://user-images.githubusercontent.com/26339368/49310587-aedbe900-f4ac-11e8-935f-e499c14eb830.png) + +![image](https://user-images.githubusercontent.com/26339368/49310623-c024f580-f4ac-11e8-968e-a05762634b80.png) + +## Considerations :smirk: + +**Will probably not function fully with other CSS plugins that override templates related to layout or headers** + +## Theme Plugins that support Customizer + * https://github.com/kenlog/nebula :dash: + * https://github.com/kenlog/Moon :waning_gibbous_moon: + * https://github.com/aljawaid/KanboardCSS :computer: + * https://github.com/kenlog/Essential + +*Make pull request to add other theme support, must come from theme maintainer.* + +## Future Enhancements :lollipop: + +- [x] Custom Theme Creator with Color Pickers +- [x] Preloaded Theme Selector +- [x] Sizing options +- [x] Increasing Compatibility +- [x] Toggle switch to enable/disable cache for images related to Customizer sitewide + diff --git a/plugins/Customizer/Schema/Mysql.php b/plugins/Customizer/Schema/Mysql.php new file mode 100644 index 00000000..978ceb0a --- /dev/null +++ b/plugins/Customizer/Schema/Mysql.php @@ -0,0 +1,23 @@ +<?php + +namespace Kanboard\Plugin\Customizer\Schema; + +use PDO; + +const VERSION = 1; + +function version_1(PDO $pdo) +{ + $pdo->exec("CREATE TABLE customizer_files ( + id INT NOT NULL AUTO_INCREMENT, + custom_id INT NOT NULL, + name VARCHAR(255), + path VARCHAR(255), + is_image TINYINT(1) DEFAULT 0, + date INT NOT NULL DEFAULT 0, + user_id INT NOT NULL DEFAULT 0, + size INT NOT NULL DEFAULT 0, + PRIMARY KEY (id) + ) ENGINE=InnoDB CHARSET=utf8" + ); +} diff --git a/plugins/Customizer/Schema/Postgres.php b/plugins/Customizer/Schema/Postgres.php new file mode 100644 index 00000000..54568c69 --- /dev/null +++ b/plugins/Customizer/Schema/Postgres.php @@ -0,0 +1,25 @@ +<?php + +namespace Kanboard\Plugin\Customizer\Schema; + +use PDO; + +const VERSION = 1; + +function version_1(PDO $pdo) +{ + +$pdo->exec(" + CREATE TABLE customizer_files ( + id SERIAL PRIMARY KEY, + custom_id INTEGER NOT NULL, + name VARCHAR(255) NOT NULL, + path VARCHAR(255) NOT NULL, + is_image BOOLEAN DEFAULT '0', + size INTEGER DEFAULT 0 NOT NULL, + user_id INTEGER DEFAULT 0 NOT NULL, + date INTEGER DEFAULT 0 NOT NULL + )" + ); + +} diff --git a/plugins/Customizer/Schema/Sqlite.php b/plugins/Customizer/Schema/Sqlite.php new file mode 100644 index 00000000..e8da08c1 --- /dev/null +++ b/plugins/Customizer/Schema/Sqlite.php @@ -0,0 +1,25 @@ +<?php + +namespace Kanboard\Plugin\Customizer\Schema; + +use PDO; + +const VERSION = 1; + +function version_1(PDO $pdo) +{ + +$pdo->exec(" + CREATE TABLE customizer_files ( + id INTEGER PRIMARY KEY, + custom_id INTEGER NOT NULL, + name TEXT COLLATE NOCASE NOT NULL, + path TEXT NOT NULL, + is_image INTEGER DEFAULT 0, + size INTEGER DEFAULT 0 NOT NULL, + user_id INTEGER DEFAULT 0 NOT NULL, + date INTEGER DEFAULT 0 NOT NULL + )" + ); + +} diff --git a/plugins/Customizer/Template/board/task_avatar.php b/plugins/Customizer/Template/board/task_avatar.php new file mode 100644 index 00000000..3dd34a4c --- /dev/null +++ b/plugins/Customizer/Template/board/task_avatar.php @@ -0,0 +1,26 @@ +
+<?php if (! empty($task['owner_id'])): ?>
+<div class="task-board-avatars">
+ <span
+ <?php if ($this->user->hasProjectAccess('TaskModificationController', 'edit', $task['project_id'])): ?>
+ class="task-board-assignee task-board-change-assignee"
+ data-url="<?= $this->url->href('TaskModificationController', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>">
+ <?php else: ?>
+ class="task-board-assignee">
+ <?php endif ?>
+ <?= $this->helper->dynamicAvatar->boardDynamic(
+ $task['owner_id'],
+ $task['assignee_username'],
+ $task['assignee_name'],
+ $task['assignee_email'],
+ $task['assignee_avatar_path'],
+ 'avatar-inline',
+ $this->task->configModel->get('b_av_size', '20')
+ ) ?>
+ </span>
+</div>
+<?php endif ?>
+<style>
+.avatar-bdyn img, .avatar-bdyn div {border-radius: <?= $this->task->configModel->get('b_av_radius', '50') ?>%}
+.avatar-bdyn .avatar-letter {line-height:<?= $this->task->configModel->get('b_av_size', '20') ?>px;width:<?= $this->task->configModel->get('b_av_size', '20') ?>px;font-size:<?= $this->task->configModel->get('b_av_size', '20') / 2 ?>px;}
+</style>
\ No newline at end of file diff --git a/plugins/Customizer/Template/config/sidebar.php b/plugins/Customizer/Template/config/sidebar.php new file mode 100644 index 00000000..b9d11934 --- /dev/null +++ b/plugins/Customizer/Template/config/sidebar.php @@ -0,0 +1,3 @@ +<li <?= $this->app->checkMenuSelection('CustomizerFileController', 'show') ?>> + <?= $this->url->link(t('Customizer'), 'CustomizerFileController', 'show', ['plugin' => 'Customizer']) ?> +</li> diff --git a/plugins/Customizer/Template/config/themecreator.php b/plugins/Customizer/Template/config/themecreator.php new file mode 100644 index 00000000..218547da --- /dev/null +++ b/plugins/Customizer/Template/config/themecreator.php @@ -0,0 +1,96 @@ +<form name="themecreator" id="themecreator" class="url-links" method="post" action="<?= $this->url->href('CustomizerConfigController', 'create_theme', array('plugin' => 'customizer', 'redirect' => 'application')) ?>" autocomplete="off"> +<?= $this->form->csrf() ?> + <div class="column-100" style="min-height: 600px;"> + <?= t('Theme Name: ') ?><input type="text" name="theme_name" placeholder="<?= t('Theme Name') ?>" pattern="[a-zA-Z0-9]+" title="<?= t('it should only contain alphanumeric without spaces') ?>" required> + <br> + <br> + <table> + <tr> + <th colspan="2" class="title-creator"> + <?= t('Header') ?> + </th> + <tr> + <td> + <strong><?= t('Header Background') ?></strong> + </td> + <td> + <input class="color" name="header_background" value=""> + </td> + <td> + <strong><?= t('Header Shade') ?></strong> + </td> + <td> + <input class="color" name="header_shade" value=""> + </td> + </tr> + <tr> + <td> + <strong><?= t('Header Title') ?></strong> + </td> + <td> + <input class="color" name="header_title" value=""> + </td> + </tr> + <tr> + <td> + <strong><?= t('Notification Icon') ?></strong> + </td> + <td> + <input class="color" name="notification_icon" value=""> + </td> + </tr> + <tr> + <th colspan="2" class="title-creator"> + <?= t('Body') ?> + </th> + <tr> + <tr> + <td> + <strong><?= t('Background Color') ?></strong> + </td> + <td> + <input class="color" name="background_color" value=""> + </td> + </tr> + <tr> + <td> + <strong><?= t('Main Font and Icons') ?></strong> + </td> + <td> + <input class="color" name="font_main" value=""> + </td> + <td> + <strong><?= t('Secondary Fonts and Icons') ?></strong> + </td> + <td> + <input class="color" name="font_secondary" value=""> + </td> + </tr> + <tr> + <td> + <strong><?= t('Links') ?></strong> + </td> + <td> + <input class="color" name="font_link" value=""> + </td> + <td> + <strong><?= t('Link Hover & Focus') ?></strong> + </td> + <td> + <input class="color" name="font_link_focus" value=""> + </td> + </tr> + <tr> + <td> + <strong><?= t('Over due') ?></strong> + </td> + <td> + <input class="color" name="font_overdue" value=""> + </td> + </tr> + </table> + </div> + <div class="form-actions" style="margin-bottom: 50px"> + <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> + </div> +</form> diff --git a/plugins/Customizer/Template/file/remove.php b/plugins/Customizer/Template/file/remove.php new file mode 100644 index 00000000..4031f8a8 --- /dev/null +++ b/plugins/Customizer/Template/file/remove.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Remove a file') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this file: "%s"?', $this->text->e($file['name'])) ?> + </p> + + <?= $this->modal->confirmButtons( + 'CustomizerFileController', + 'removeform', + array('plugin' => 'customizer', 'custom_id' => $file['custom_id'], 'file_id' => $file['id']) + ) ?> +</div> diff --git a/plugins/Customizer/Template/file/show.php b/plugins/Customizer/Template/file/show.php new file mode 100644 index 00000000..c184656c --- /dev/null +++ b/plugins/Customizer/Template/file/show.php @@ -0,0 +1,435 @@ +<?php +global $customizer; +?> + +<?= $this->hook->render('customizer:config:style') ?> + +<div class="sidebar-content"> + <form name="settings" id="settings" class="url-links" method="post" action="<?= $this->url->href('CustomizerConfigController', 'save', array('plugin' => 'customizer', 'redirect' => 'application')) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <fieldset class="login-link-block panel"> + + <button type="button" class="login-accordion"><i class="fa fa-picture-o" aria-hidden="true"></i> <?= t('Image Assets & Settings') ?></button> + <div class="login-accordian-panel mt-10"> + <div class="panel header-logo-panel"> + <div class="panel-heading"> + <h3 class="panel-title"><?= t('Header Image') ?></h3> + </div> + <img id="hl1" src="<?= $this->url->href('CustomizerFileController', 'logo_setting', array('plugin' => 'customizer', 'file_id' => $logo['id'])) ?>" alt="<?= $this->text->e($logo['name']) ?>" height="<?= $this->task->configModel->get('headerlogo_size', '30') ?>"> + <br> + <br> + <ul class="upload-link"> + <?php + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + error_reporting(E_ALL); + ?> + + <?= $this->modal->medium('file', t('Upload Header Logo'), 'CustomizerFileController', 'create', array('plugin' => 'customizer', 'custom_id' => 1))?> + </ul class="remove-link"> + <?php if (null !== $this->task->customizerFileModel->getByType(1)) : ?> + <ul class="remove-link"> + <?php + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + error_reporting(E_ALL); + ?> + + <?= $this->modal->medium('remove', t('Remove Header Logo'), 'CustomizerFileController', 'confirm', array('plugin' => 'customizer', 'custom_id' => 1, 'file_id' => $logo['id']))?> + </ul> + <?php endif ?> + <br><br> + <table> + <tr> + <th width="25%"><strong><?= t('Header Logo Size') ?></strong></th> + <th><input type="range" name="headerlogo_size" min="20" max="250" value="<?= $this->task->configModel->get('headerlogo_size','30') ?>"> + <header_logo_output> <?= $this->task->configModel->get('headerlogo_size','30') ?></header_logo_output><?= t(' pixels high') ?> + </th> + </tr> + </table> + + </div> + + <div class="panel login-logo-panel"> + <div class="panel-heading"> + <h3 class="panel-title"><?= t('Login Image') ?></h3> + </div> + + <img id="ll1" src="<?= $this->url->href('CustomizerFileController', 'loginlogo_setting', array('plugin' => 'customizer', 'file_id' => $loginlogo['id'])) ?>" alt="<?= $this->text->e($loginlogo['name']) ?>" height="<?= $this->task->configModel->get('loginlogo_size', '50') ?>"> + <br> + <br> + <ul class="upload-link"> + <?php + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + error_reporting(E_ALL); + ?> + + <?= $this->modal->medium('file', t('Upload Login Logo'), 'CustomizerFileController', 'create', array('plugin' => 'customizer', 'custom_id' => 3))?> + </ul> + <?php if (null !== $this->task->customizerFileModel->getByType(3)) : ?> + <ul class="remove-link"> + <?php + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + error_reporting(E_ALL); + ?> + + <?= $this->modal->medium('remove', t('Remove Login Logo'), 'CustomizerFileController', 'confirm', array('plugin' => 'customizer', 'custom_id' => 3, 'file_id' => $loginlogo['id']))?> + </ul> + <?php endif ?> + <br><br> + <table> + <tr> + <th width="25%"><strong><?= t('Login Logo Size') ?></strong></th> + <th><input type="range" name="loginlogo_size" min="20" max="500" value="<?= $this->task->configModel->get('loginlogo_size','50') ?>"> + <login_logo_output><?= $this->task->configModel->get('loginlogo_size','50') ?></login_logo_output><?= t(' pixels high') ?> + </th> + </tr> + </table> + + </div> + + <div class="panel favicon-panel"> + <div class="panel-heading"> + <h3 class="panel-title"><?= t('Favicon Image') ?></h3> + </div> + + <img src="<?= $this->url->href('CustomizerFileController', 'icon_setting', array('plugin' => 'customizer', 'file_id' => $flavicon['id'])) ?>" alt="<?= $this->text->e($flavicon['name']) ?>" height="16"> + <br> + <br> + <ul class="upload-link"> + <?php + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + error_reporting(E_ALL); + ?> + <?= $this->modal->medium('file', t('Upload Favicon'), 'CustomizerFileController', 'create', array('plugin' => 'customizer', 'custom_id' => 2))?> + </ul> + </ul> + <?php if (null !== $this->task->customizerFileModel->getByType(2)) : ?> + <ul class="remove-link"> + <?php + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + error_reporting(E_ALL); + ?> + + <?= $this->modal->medium('remove', t('Remove Favicon'), 'CustomizerFileController', 'confirm', array('plugin' => 'customizer', 'custom_id' => 2, 'file_id' => $flavicon['id']))?> + </ul> + <?php endif ?> + </div> + + <div class="panel avatar-sizing-panel"> + <div class="panel-heading"> + <h3 class="panel-title"><?= t('Header Avatar Icon') ?></h3> + </div> + <?= $this->helper->dynamicAvatar->currentUserDynamic('avatar-preview') ?> + <br> + <br> + <table> + <tr> + <th width="25%"><strong><?= t('Header Avatar Icon Size') ?></strong></th> + <th><input type="range" name="av_size" id="av_size" min="20" max="50" value="<?= $this->task->configModel->get('av_size','20') ?>"> + <av_icon_output> <?= $this->task->configModel->get('av_size','20') ?></av_icon_output><?= t(' pixels') ?> + </th> + </tr> + <tr> + <th width="25%"><strong><?= t('Header Avatar Icon Radius') ?></strong></th> + <th><input type="range" name="av_radius" id="av_radius" min="0" max="50" value="<?= $this->task->configModel->get('av_radius','50') ?>"> + <av_radius_output> <?= $this->task->configModel->get('av_radius','50') ?></av_radius_output><?= t(' percent') ?> + </th> + </tr> + </table> + + </div> + + <div class="panel b-avatar-sizing-panel"> + <div class="panel-heading"> + <h3 class="panel-title"><?= t('Task Board Avatar Icon') ?></h3> + </div> + <?= $this->helper->dynamicAvatar->boardCurrentUserDynamic('b-avatar-preview') ?> + <br> + <br> + <table> + <tr> + <th width="25%"><strong><?= t('Avatar Icon Size') ?></strong></th> + <th><input type="range" name="b_av_size" id="b_av_size" min="20" max="50" value="<?= $this->task->configModel->get('b_av_size','20') ?>"> + <b_av_icon_output> <?= $this->task->configModel->get('b_av_size','20') ?></b_av_icon_output><?= t(' pixels') ?> + </th> + </tr> + <tr> + <th width="25%"><strong><?= t('Avatar Icon Radius') ?></strong></th> + <th><input type="range" name="b_av_radius" id="b_av_radius" min="0" max="50" value="<?= $this->task->configModel->get('b_av_radius','50') ?>"> + <b_av_radius_output> <?= $this->task->configModel->get('b_av_radius','50') ?></b_av_radius_output><?= t(' percent') ?> + </th> + </tr> + </table> + + </div> +<style> +.avatar-bdyn img, .avatar-bdyn div {border-radius: <?= $this->task->configModel->get('b_av_radius', '50') ?>%} +.avatar-bdyn .avatar-letter {line-height:<?= $this->task->configModel->get('b_av_size', '20') ?>px;width:<?= $this->task->configModel->get('b_av_size', '20') ?>px;font-size:<?= $this->task->configModel->get('b_av_size', '20') / 2 ?>px;} +</style> + + + <table> + <tr> + <th width="25%"><strong><?= t('Enable Cache') ?></strong> + <p class="form-help enable-cache-desc"><?= e('Once enabled, site assets will begin to be cached for 5 days, increasing speed of site. However, you will need to clear your cache to see any new images uploaded. The settings page, will be unaffected by this setting.') ?></p> + </th> + <th> + <label class="switch"> + <input id="toggle" name="enable_cache" type="checkbox" value="checked" <?= $this->task->configModel->get('enable_cache','') ?>> + <span class="slider round"></span> + </label> + </th> + </tr> + </table> + <table> + <tr> + <th width="25%"><strong><?= t('Logo Generator') ?></strong> + <p class="form-help enable-cache-desc"><?= e('Experimental Tool, to create simple logos for those in need.') ?></p> + </th> + <th> + <a href="https://creecros.github.io/simple_logo_gen/"> + <img border="0" alt="logo_gen" src="/plugins/Customizer/Assets/img/logo-gen.png"> + </a> + </th> + </table> + <div class="form-actions mb-20 ml-15"> + <button type="submit" name="save" value="save" class="btn btn-blue"><?= t('Save') ?></button> + </div> + </div> + + <button type="button" class="login-accordion"><i class="fa fa-sign-in" aria-hidden="true"></i> <?= t('Login Page Settings') ?></button> + <?php if ($this->task->configModel->get('use_custom_login', '') == 'checked') : ?> + <div class="login-accordian-panel mt-10"> + <?php else :?> + <div class="login-accordian-panel mt-10"> + <?php endif ?> + <table> + <tr> + <th width="25%"><strong><?= t('Use Custom Login Settings') ?></strong></th> + <th> + <label class="switch"> + <input id="toggle" name="use_custom_login" type="checkbox" value="checked" <?= $this->task->configModel->get('use_custom_login','') ?>> + <span class="slider round"></span> + </label> + </th> + </tr> + </table> + + <?php if ($this->task->configModel->get('use_custom_login', '') == 'checked') : ?> + <?= $this->form->label(t('Login Link'), 'login_link') ?> + <?= $this->form->text('login_link', $values, $errors, array('placeholder="https://example.kanboard.org/"')) ?> + <p class="form-help login-link-desc"><?= e('Example: <code>https://example.kanboard.org/</code> (used as logo link on login page)') ?></p> + <?= $this->form->label(t('Login Background Image URL'), 'background_url') ?> + <?= $this->form->text('background_url', $values, $errors, array('placeholder="https://source.unsplash.com/random"')) ?> + <p class="form-help background-img-link-desc"><?= e('Example: <code>https://source.unsplash.com/random</code> (URL for a background image on the login page, centered, autoscale, no-repeat)') ?></p> + <?= $this->form->label(t('Login page note'), 'login_note') ?> + <?= $this->form->textarea('login_note', $values, $errors, array('placeholder="Welcome to Kanboard!"')) ?> + <p class="form-help login-note-desc"><?= e('Hint: Use HTML formatting to customize your note further.') ?></p> + <div class="column-100"> + <table> + <tr> + <th> + <strong><?= t('Login Page Background Color') ?></strong> + </th> + <th> + <input id="loginbackground_color" class="color" name="loginbackground_color" value="<?= $this->task->configModel->get('loginbackground_color','#ffffff') ?>"> + </th> + </tr> + <tr> + <th> + <strong><?= t('Login Panel Shadow Color') ?></strong> + </th> + <th> + <input id="login_shadow_color" class="color" name="login_shadow_color" value="<?= $this->task->configModel->get('login_shadow_color','#333') ?>"> + </th> + </tr> + <tr> + <th> + <strong><?= t('Login Panel Border Color') ?></strong> + </th> + <th> + <input id="login_border_color" class="color" name="login_border_color" value="<?= $this->task->configModel->get('login_border_color','#ffffff') ?>"> + </th> + </tr> + <tr> + <th> + <strong><?= t('Login Panel Border Thickness') ?></strong> + </th> + <th> + <input id="login_border" type="range" name="login_border" min="0" max="10" value="<?= $this->task->configModel->get('login_border','0') ?>"> + </th> + </tr> + <tr> + <th> + <strong><?= t('Login Panel Color') ?></strong> + </th> + <th> + <input id="loginpanel_color" class="color" name="loginpanel_color" value="<?= $this->task->configModel->get('loginpanel_color','#ffffff') ?>"> + </th> + </tr> + <tr> + <th> + <strong><?= t('Login Panel Shadow Intensity') ?></strong> + </th> + <th> + <input id="login_shadow" type="range" name="login_shadow" min="0" max="20" value="<?= $this->task->configModel->get('login_shadow','0') ?>"> + </th> + </tr> + <tr> + <th> + <strong><?= t('Login Button Background Color') ?></strong> + </th> + <th> + <input id="login_btn_color" class="color" name="login_btn_color" value="<?= $this->task->configModel->get('login_btn_color','#3079ed') ?>"> + </th> + </tr> + <tr> + <th> + <strong><?= t('Login Button Shadow Color') ?></strong> + </th> + <th> + <input id="login_btn_shadow_color" class="color" name="login_btn_shadow_color" value="<?= $this->task->configModel->get('login_btn_shadow_color','#333') ?>"> + </th> + </tr> + <tr> + <th> + <strong><?= t('Login Button Border Color') ?></strong> + </th> + <th> + <input id="login_btn_border_color" class="color" name="login_btn_border_color" value="<?= $this->task->configModel->get('login_btn_border_color','transparent') ?>"> + </th> + </tr> + <tr> + <th> + <strong><?= t('Login Button Shade Color') ?></strong> + </th> + <th> + <input id="login_btn_shade_color" class="color" name="login_btn_shade_color" value="<?= $this->task->configModel->get('login_btn_shade_color','transparent') ?>"> + </th> + </tr> + <tr> + <th> + <strong><?= t('Login Button Font Color') ?></strong> + </th> + <th> + <input id="login_btn_font_color" class="color" name="login_btn_font_color" value="<?= $this->task->configModel->get('login_btn_font_color','#ffffff') ?>"> + </th> + </tr> + <tr> + <th> + <strong><?= t('Login Button Shadow Intensity') ?></strong> + </th> + <th> + <input id="login_btn_shadow" type="range" name="login_btn_shadow" min="0" max="20" value="<?= $this->task->configModel->get('login_btn_shadow','0') ?>"> + </th> + </tr> + <tr> + <th> + <strong><?= t('Login Button Border Thickness') ?></strong> + </th> + <th> + <input id="login_btn_border" type="range" name="login_btn_border" min="0" max="10" value="<?= $this->task->configModel->get('login_btn_border','0') ?>"> + </th> + </tr> + <tr> + <th> + <strong><?= t('Login Button Width') ?></strong> + </th> + <th> + <input id="login_btn_width" type="range" name="login_btn_width" min="95" max="300" value="<?= $this->task->configModel->get('login_btn_width','95') ?>"> + </th> + </tr> + </table> + <p class="alert" style="max-width: 1000px;"><?= t('Changes must be saved in order to take effect.') ?> <button type="submit" name="save" value="save" class="btn btn-blue" style="float: right;margin-top: -6px;"><?= t('Save') ?></button></p> + <div class="panel" id="preview" style="background: url('<?= $customizer['backURL'] ?>') no-repeat center center;background-size: cover;height: 700px;max-width: 1000px;background-color: <?= $customizer['backColor'] ?>;"> + <div> + <p style="color: #f5f5f5;"><?= t('Preview') ?></p> + </div> + <div id="preview-form-login" class="preview-form-login"> + <?php if ($customizer['loginCheck']): ?> + <?= $this->url->link('<img src="' . $this->url->href('CustomizerFileController', 'loginlogo_setting', array('plugin' => 'customizer')) . '" height="' . $customizer['logoSize'] . '">', 'CustomizerFileController', 'link', array('plugin' => 'customizer')) ?> + <?php else: ?> + <?= $this->url->link('K<span>B</span>', 'DashboardController', 'show', array(), false, '', t('Dashboard')) ?> + <?php endif ?> + + <label for="form-username"></label> + <input type="text" name="username" placeholder="<?= t('Enter your username') ?>" style=" + border-radius: 5px; + "> + <span class="preview-form-required"></span> + <label for="form-password"></label> + <input type="password" name="password" placeholder="<?= t('Enter your password') ?>" style=" + border-radius: 5px; + "> + <span class="preview-form-required"></span> + <label style="color:grey"><input type="checkbox" name="remember_me" value="1" checked="checked" disabled> <?= t('Remember Me') ?></label> + <div style="margin-bottom: 10px !important;"></div> + <div class="preview-form-actions"> + <button type="button" id="preview-login-btn" class="btn preview-login-btn"><?= t('Sign in') ?></button> + </div> + <?php if ($this->app->config('password_reset') == 1): ?> + <div class="reset-password"> + <?= $this->url->link(t('Forgot password?'), 'PasswordResetController', 'create') ?> + </div> + <?php endif ?> + </div> + <div id="preview-form-note" class="preview-form-note"> + <div class="login-note"> + <?= $customizer['login_note'] ?> + </div> + </div> + </div> + </div> + <?php endif ?> + </div> + <button type="button" class="login-accordion"><i class="fa fa-refresh" aria-hidden="true"></i> <?= t('Manage Themes') ?></button> + <div class="login-accordian-panel mt-10"> + <div class="panel header-logo-panel"> + <h3 class="panel-title"><?= t('Global Themes') ?></h3> + <?= $this->form->label(t('Theme'), 'themeSelection') ?> + <?= $this->helper->themeHelper->reverseSelect('themeSelection', $customizer['themes'], $values, $errors) ?> + <p class="form-help theme-select"><?= e('This will be the theme selection for all users who have not chosen their own theme.') ?></p> + <div class="form-actions" style="margin-bottom: 50px"> + <button type="submit" name="save" value="save" class="btn btn-blue"><?= t('Save') ?> </button><button type="submit" name="remove" value="remove" class="btn btn-red"><?= t('Remove') ?></button> + </div> + </div> + </form> + + <form method="post" enctype="multipart/form-data" action="<?= $this->url->href('CustomizerConfigController', 'uploadcss', array('plugin' => 'customizer')) ?>"> + <div class="panel header-logo-panel"> + <h3 class="panel-title"><?= t('Upload a theme') ?></h3> + <input type="file" name="fileToUpload" id="fileToUpload"> + <input type="submit" class="btn btn-blue" value="<?= t('Add Theme') ?>" name="submit"> + </div> + </form> + <form method="post" enctype="multipart/form-data" action="<?= $this->url->href('CustomizerConfigController', 'resetUserThemes', array('plugin' => 'customizer')) ?>"> + <div class="panel header-logo-panel"> + <h3 class="panel-title"><?= t('Users themes option') ?></h3> + <input type="submit" class="btn btn-red" value="<?= t('Reset All Users Themes') ?>" name="submit"> + </div> + </form> + <form method="post" enctype="multipart/form-data" action="<?= $this->url->href('CustomizerConfigController', 'enableDisableThemes', array('plugin' => 'customizer')) ?>"> + <div class="panel header-logo-panel"> + <h3 class="panel-title"><?= t('Toggle Users themes') ?></h3> + <?php if ($this->task->configModel->get('toggle_user_themes', 'disable') == 'disable') : ?> + <input type="submit" class="btn btn-blue" value="<?= t('Enable Users Themes') ?>" name="submit"> + <?php else :?> + <input type="submit" class="btn btn-red" value="<?= t('Disable Users Themes') ?>" name="submit"> + <?php endif ?> + </div> + </form> + + + </div> + + <button type="button" class="login-accordion"><i class="fa fa-magic" aria-hidden="true"></i> <?= t('Theme Creator') ?></button> + <div class="login-accordian-panel mt-10"> + <?= $this->hook->render('customizer:config:themecreator') ?> + </div> + </div> +</fieldset> diff --git a/plugins/Customizer/Template/file/upload_flavicon.php b/plugins/Customizer/Template/file/upload_flavicon.php new file mode 100644 index 00000000..b0e8988c --- /dev/null +++ b/plugins/Customizer/Template/file/upload_flavicon.php @@ -0,0 +1,22 @@ +<div class="page-header"> + <h2><?= t('Favicon') ?></h2> + <br> + <?= t('Recommend 50x50 pixels, *.png only, max size 20kb.') ?> +</div> + +<?= $this->app->component('file-upload', array( + 'maxSize' => 20000, + 'url' => $this->url->to('CustomizerFileController', 'save', array('plugin' => 'customizer', 'custom_id' => 2)), + 'labelDropzone' => t('Drag and drop your file here'), + 'labelOr' => t('or'), + 'labelChooseFiles' => t('choose file'), + 'labelOversize' => t('The maximum allowed file size is %sB.', $this->text->bytes(20000)), + 'labelSuccess' => t('File has been uploaded successfully.'), + 'labelCloseSuccess' => t('Close this window'), + 'labelUploadError' => t('Unable to upload this file.'), +)) ?> + +<?= $this->modal->submitButtons(array( + 'submitLabel' => t('Upload Favicon'), + 'disabled' => true, +)) ?> diff --git a/plugins/Customizer/Template/file/upload_loginlogo.php b/plugins/Customizer/Template/file/upload_loginlogo.php new file mode 100644 index 00000000..fa715f36 --- /dev/null +++ b/plugins/Customizer/Template/file/upload_loginlogo.php @@ -0,0 +1,22 @@ +<div class="page-header"> + <h2><?= t('Login Logo') ?></h2> + <br> + <?= t('Recommend 100 pixels in height, *.png, *.jpg, *.gif, max size 500kb.') ?> +</div> + +<?= $this->app->component('file-upload', array( + 'maxSize' => 500000, + 'url' => $this->url->to('CustomizerFileController', 'save', array('plugin' => 'customizer', 'custom_id' => 3)), + 'labelDropzone' => t('Drag and drop your file here'), + 'labelOr' => t('or'), + 'labelChooseFiles' => t('choose file'), + 'labelOversize' => t('The maximum allowed file size is %sB.', $this->text->bytes(500000)), + 'labelSuccess' => t('File has been uploaded successfully.'), + 'labelCloseSuccess' => t('Close this window'), + 'labelUploadError' => t('Unable to upload this file.'), +)) ?> + +<?= $this->modal->submitButtons(array( + 'submitLabel' => t('Upload Login Logo'), + 'disabled' => true, +)) ?> diff --git a/plugins/Customizer/Template/file/upload_logo.php b/plugins/Customizer/Template/file/upload_logo.php new file mode 100644 index 00000000..0dae640c --- /dev/null +++ b/plugins/Customizer/Template/file/upload_logo.php @@ -0,0 +1,22 @@ +<div class="page-header"> + <h2><?= t('Header Logo') ?></h2> + <br> + <?= t('Recommend 100 pixels in width, *.png, *.jpg, *.gif, max size 500kb.') ?> +</div> + +<?= $this->app->component('file-upload', array( + 'maxSize' => 500000, + 'url' => $this->url->to('CustomizerFileController', 'save', array('plugin' => 'customizer', 'custom_id' => 1)), + 'labelDropzone' => t('Drag and drop your file here'), + 'labelOr' => t('or'), + 'labelChooseFiles' => t('choose file'), + 'labelOversize' => t('The maximum allowed file size is %sB.', $this->text->bytes(500000)), + 'labelSuccess' => t('File has been uploaded successfully.'), + 'labelCloseSuccess' => t('Close this window'), + 'labelUploadError' => t('Unable to upload this file.'), +)) ?> + +<?= $this->modal->submitButtons(array( + 'submitLabel' => t('Upload Header Logo'), + 'disabled' => true, +)) ?> diff --git a/plugins/Customizer/Template/header/title.php b/plugins/Customizer/Template/header/title.php new file mode 100644 index 00000000..5080c9be --- /dev/null +++ b/plugins/Customizer/Template/header/title.php @@ -0,0 +1,24 @@ +<h1> + <?php if (null !== $this->task->customizerFileModel->getByType(1)) : ?> + <span class="logo"> + <a href="<?= $this->url->href('DashboardController', 'show', array(), false, '', t('Dashboard')) ?>"> + <img src="<?= $this->url->href('CustomizerFileController', 'image', array('plugin' => 'customizer', 'file_id' => $this->task->customizerFileModel->getIdByType(1))) ?>" height="<?= $this->task->configModel->get('headerlogo_size', '30') ?>"> + </a> + </span> + <?php else: ?> + <span class="logo"> + <?= $this->url->link('K<span>B</span>', 'DashboardController', 'show', array(), false, '', t('Dashboard')) ?> + </span> + <?php endif ?> + + <span class="title"> + <?php if (! empty($project) && ! empty($task)): ?> + <?= $this->url->link($this->text->e($project['name']), 'BoardViewController', 'show', array('project_id' => $project['id'])) ?> + <?php else: ?> + <?= $this->text->e($title) ?> + <?php endif ?> + </span> + <?php if (! empty($description)): ?> + <?= $this->app->tooltipHTML($description) ?> + <?php endif ?> +</h1> diff --git a/plugins/Customizer/Template/header/title_older_kb.php b/plugins/Customizer/Template/header/title_older_kb.php new file mode 100644 index 00000000..a38ff323 --- /dev/null +++ b/plugins/Customizer/Template/header/title_older_kb.php @@ -0,0 +1,26 @@ +<h1> + <?php if (null !== $this->task->customizerFileModel->getByType(1)) : ?> + <span class="logo"> + <a href="<?= $this->url->href('DashboardController', 'show', array(), false, '', t('Dashboard')) ?>"> + <img src="<?= $this->url->href('CustomizerFileController', 'image', array('plugin' => 'customizer', 'file_id' => $this->task->customizerFileModel->getIdByType(1))) ?>" height="<?= $this->task->configModel->get('headerlogo_size', '30') ?>"> + </a> + </span> + <?php else: ?> + <span class="logo"> + <?= $this->url->link('K<span>B</span>', 'DashboardController', 'show', array(), false, '', t('Dashboard')) ?> + </span> + <?php endif ?> + + <span class="title"> + <?php if (! empty($project) && ! empty($task)): ?> + <?= $this->url->link($this->text->e($project['name']), 'BoardViewController', 'show', array('project_id' => $project['id'])) ?> + <?php else: ?> + <?= $this->text->e($title) ?> + <?php endif ?> + </span> + <?php if (! empty($description)): ?> + <small class="tooltip" title="<?= $this->text->markdownAttribute($description) ?>"> + <i class="fa fa-info-circle"></i> + </small> + <?php endif ?> +</h1> diff --git a/plugins/Customizer/Template/header/user_dropdown.php b/plugins/Customizer/Template/header/user_dropdown.php new file mode 100644 index 00000000..d302924f --- /dev/null +++ b/plugins/Customizer/Template/header/user_dropdown.php @@ -0,0 +1,45 @@ +<div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon" style="display:flex;position:relative;align-items:center;flex-direction:row;"><?= $this->helper->dynamicAvatar->currentUserDynamic('avatar-inline') ?><i class="fa fa-caret-down"></i></a> + <ul> + <li class="no-hover"><strong><?= $this->text->e($this->user->getFullname()) ?></strong></li> + <li> + <?= $this->url->icon('tachometer', t('My dashboard'), 'DashboardController', 'show', array('user_id' => $this->user->getId())) ?> + </li> + <li> + <?= $this->url->icon('home', t('My profile'), 'UserViewController', 'show', array('user_id' => $this->user->getId())) ?> + </li> + <li> + <?= $this->url->icon('folder', t('Projects management'), 'ProjectListController', 'show') ?> + </li> + <?php if ($this->user->hasAccess('UserListController', 'show')): ?> + <li> + <?= $this->url->icon('user', t('Users management'), 'UserListController', 'show') ?> + </li> + <li> + <?= $this->url->icon('group', t('Groups management'), 'GroupListController', 'index') ?> + </li> + <li> + <?= $this->url->icon('cubes', t('Plugins'), 'PluginController', 'show') ?> + </li> + <li> + <?= $this->url->icon('cog', t('Settings'), 'ConfigController', 'index') ?> + </li> + <?php endif ?> + + <?= $this->hook->render('template:header:dropdown') ?> + + <li> + <i class="fa fa-fw fa-life-ring" aria-hidden="true"></i> + <?= $this->url->doc(t('Documentation'), 'index') ?> + </li> + <?php if (! DISABLE_LOGOUT): ?> + <li> + <?= $this->url->icon('sign-out', t('Logout'), 'AuthController', 'logout') ?> + </li> + <?php endif ?> + </ul> +</div> +<style> +.avatar-dyn img, .avatar-dyn div {border-radius: <?= $this->task->configModel->get('av_radius', '50') ?>%} +.avatar-dyn .avatar-letter {line-height:<?= $this->task->configModel->get('av_size', '20') ?>px;width:<?= $this->task->configModel->get('av_size', '20') ?>px;font-size:<?= $this->task->configModel->get('av_size', '20') / 2 ?>px;} +</style> diff --git a/plugins/Customizer/Template/layout/index.php b/plugins/Customizer/Template/layout/index.php new file mode 100644 index 00000000..587b3e04 --- /dev/null +++ b/plugins/Customizer/Template/layout/index.php @@ -0,0 +1,47 @@ +<div class="form-login"> + + <?= $this->hook->render('template:auth:login-form:before') ?> + + <?php if (isset($errors['login'])): ?> + <p class="alert alert-error"><?= $this->text->e($errors['login']) ?></p> + <?php endif ?> + + <?php if (! HIDE_LOGIN_FORM): ?> + <form method="post" action="<?= $this->url->href('AuthController', 'check') ?>"> + + <?= $this->form->csrf() ?> + + <?= $this->form->label(t('Username'), 'username') ?> + <?= $this->form->text('username', $values, $errors, array('autofocus', 'required', 'placeholder="Enter your username"')) ?> + + <?= $this->form->label(t('Password'), 'password') ?> + <?= $this->form->password('password', $values, $errors, array('required', 'placeholder="Enter your password"')) ?> + + <?php if (isset($captcha) && $captcha): ?> + <?= $this->form->label(t('Enter the text below'), 'captcha') ?> + <img src="<?= $this->url->href('CaptchaController', 'image') ?>" alt="Captcha"> + <?= $this->form->text('captcha', array(), $errors, array('required')) ?> + <?php endif ?> + + <?php if (REMEMBER_ME_AUTH == true): ?> + <?= $this->form->checkbox('remember_me', t('Remember Me'), 1, true) ?> + <div class="mb-10"></div> + <?php else: ?> + <div class="mb-15"></div> + <?php endif ?> + + <div class="form-actions"> + <button type="submit" class="btn login-btn"><?= t('Sign in') ?></button> + </div> + <?php if ($this->app->config('password_reset') == 1): ?> + <div class="reset-password"> + <?= $this->url->link(t('Forgot password?'), 'PasswordResetController', 'create') ?> + </div> + <?php endif ?> + </form> + <?php endif ?> + + <?= $this->hook->render('template:auth:login-form:after') ?> +</div> + +<?= $this->hook->render('template:auth:login-form:newbox') ?> diff --git a/plugins/Customizer/Template/layout/layout.php b/plugins/Customizer/Template/layout/layout.php new file mode 100644 index 00000000..e5b4b772 --- /dev/null +++ b/plugins/Customizer/Template/layout/layout.php @@ -0,0 +1,85 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width"> + <meta name="mobile-web-app-capable" content="yes"> + <meta name="robots" content="noindex,nofollow"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="referrer" content="no-referrer"> + + <?php if (isset($board_public_refresh_interval)): ?> + <meta http-equiv="refresh" content="<?= $board_public_refresh_interval ?>"> + <?php endif ?> + + <?= $this->asset->colorCss() ?> + <?= $this->asset->css('assets/css/vendor.min.css') ?> + <?= $this->asset->css('assets/css/app.min.css') ?> + <?php if (file_exists('assets/css/print.min.css')) :?> + <?= $this->asset->css('assets/css/print.min.css', true, 'print') ?> + <?php endif ?> + <?= $this->asset->customCss() ?> + + <?php if (! isset($not_editable)): ?> + <?= $this->asset->js('assets/js/vendor.min.js') ?> + <?= $this->asset->js('assets/js/app.min.js') ?> + <?php endif ?> + + <?= $this->hook->asset('css', 'template:layout:css') ?> + <?= $this->hook->asset('js', 'template:layout:js') ?> + <?php if (null !== $this->task->customizerFileModel->getByType(2)) : ?> + <link rel="icon" type="image/png" href="<?= $this->url->href('CustomizerFileController', 'image', array('plugin' => 'customizer', 'file_id' => $this->task->customizerFileModel->getIdByType(2))) ?>"> + <link rel="apple-touch-icon" href="<?= $this->url->href('CustomizerFileController', 'image', array('plugin' => 'customizer', 'file_id' => $this->task->customizerFileModel->getIdByType(2))) ?>"> + <link rel="apple-touch-icon" sizes="72x72" href="<?= $this->url->href('CustomizerFileController', 'image', array('plugin' => 'customizer', 'file_id' => $this->task->customizerFileModel->getIdByType(2))) ?>"> + <link rel="apple-touch-icon" sizes="114x114" href="<?= $this->url->href('CustomizerFileController', 'image', array('plugin' => 'customizer', 'file_id' => $this->task->customizerFileModel->getIdByType(2))) ?>"> + <link rel="apple-touch-icon" sizes="144x144" href="<?= $this->url->href('CustomizerFileController', 'image', array('plugin' => 'customizer', 'file_id' => $this->task->customizerFileModel->getIdByType(2))) ?>"> + <?php else: ?> + <link rel="icon" type="image/png" href="<?= $this->url->dir() ?>assets/img/favicon.png"> + <link rel="apple-touch-icon" href="<?= $this->url->dir() ?>assets/img/touch-icon-iphone.png"> + <link rel="apple-touch-icon" sizes="72x72" href="<?= $this->url->dir() ?>assets/img/touch-icon-ipad.png"> + <link rel="apple-touch-icon" sizes="114x114" href="<?= $this->url->dir() ?>assets/img/touch-icon-iphone-retina.png"> + <link rel="apple-touch-icon" sizes="144x144" href="<?= $this->url->dir() ?>assets/img/touch-icon-ipad-retina.png"> + <?php endif ?> + + + <title> + <?php if (isset($page_title)): ?> + <?= $this->text->e($page_title) ?> + <?php elseif (isset($title)): ?> + <?= $this->text->e($title) ?> + <?php else: ?> + Kanboard + <?php endif ?> + </title> + + <?= $this->hook->render('template:layout:head') ?> + </head> + <body data-status-url="<?= $this->url->href('UserAjaxController', 'status') ?>" + data-login-url="<?= $this->url->href('AuthController', 'login') ?>" + data-keyboard-shortcut-url="<?= $this->url->href('DocumentationController', 'shortcuts') ?>" + data-timezone="<?= $this->app->getTimezone() ?>" + data-js-lang="<?= $this->app->jsLang() ?>" + data-js-date-format="<?= $this->app->getJsDateFormat() ?>" + data-js-time-format="<?= $this->app->getJsTimeFormat() ?>" + data-js-modal-close-msg="<?= t('Close window?\\n\\nChanges that you made have not been saved.') ?>" + > + + <?php if (isset($no_layout) && $no_layout): ?> + <?= $this->app->flashMessage() ?> + <?= $content_for_layout ?> + <?php else: ?> + <?= $this->hook->render('template:layout:top') ?> + <?= $this->render('header', array( + 'title' => $title, + 'description' => isset($description) ? $description : '', + 'board_selector' => isset($board_selector) ? $board_selector : array(), + 'project' => isset($project) ? $project : array(), + )) ?> + <section class="page"> + <?= $this->app->flashMessage() ?> + <?= $content_for_layout ?> + </section> + <?= $this->hook->render('template:layout:bottom') ?> + <?php endif ?> + </body> +</html> diff --git a/plugins/Customizer/Template/layout/login_no_custom.php b/plugins/Customizer/Template/layout/login_no_custom.php new file mode 100644 index 00000000..7e7cbd54 --- /dev/null +++ b/plugins/Customizer/Template/layout/login_no_custom.php @@ -0,0 +1,21 @@ +<?php global $customizer; ?> +<?php if ($customizer['loginCheck']): ?> +<?= $this->url->link('<img src="' . $this->url->href('CustomizerFileController', 'loginlogo', array('plugin' => 'customizer')) . '" height="' . $customizer['logoSize'] . '">', 'CustomizerFileController', 'link', array('plugin' => 'customizer')) ?> +<?php endif ?> +<?php +if (function_exists('session_exists')) { +if (session_exists('redirectAfterLogin') && ! filter_var(session_get('redirectAfterLogin'), FILTER_VALIDATE_URL)) { + $redirect = session_get('redirectAfterLogin'); + if (strpos($redirect, 'Customizer') !== false) { + session_remove('redirectAfterLogin'); + } +} +} else { +if (isset($this->task->sessionStorage->redirectAfterLogin) && ! empty($this->task->sessionStorage->redirectAfterLogin) && ! filter_var($this->task->sessionStorage->redirectAfterLogin, FILTER_VALIDATE_URL)) { + $redirect = $this->task->sessionStorage->redirectAfterLogin; + if (strpos($redirect, 'Customizer') !== false) { + unset($this->task->sessionStorage->redirectAfterLogin); + } +} +} +?> diff --git a/plugins/Customizer/Template/layout/login_with_custom.php b/plugins/Customizer/Template/layout/login_with_custom.php new file mode 100644 index 00000000..1ca70d77 --- /dev/null +++ b/plugins/Customizer/Template/layout/login_with_custom.php @@ -0,0 +1,128 @@ +<?php global $customizer; ?> +<?php if ($customizer['loginCheck']): ?> +<?= $this->url->link('<img src="' . $this->url->href('CustomizerFileController', 'loginlogo', array('plugin' => 'customizer')) . '" height="' . $customizer['logoSize'] . '">', 'CustomizerFileController', 'link', array('plugin' => 'customizer')) ?> +<?php endif ?> +<style> +body { + background: url("<?= $customizer['backURL'] ?>") no-repeat center center fixed; + background-size: cover; + background-color: <?= $customizer['backColor'] ?>; +} +.mb-10 { + margin-bottom: 10px !important; +} +.mb-15 { + margin-bottom: 15px !important; +} + +.form-login > a > img { + display: block; + margin: auto; + padding-top: 10px +} /* This aligns the logo to the text. Adds padding to top of logo. */ + +.form-login { + background-color: <?= $customizer['loginpanel_color'] ?>; + -webkit-box-shadow: 0px 0px <?= $customizer['login_shadow'] ?>px <?= $customizer['login_shadow'] * .1 ?>px <?= $customizer['login_shadow_color'] ?>; + -moz-box-shadow: 0px 0px <?= $customizer['login_shadow'] ?>px <?= $customizer['login_shadow'] * .1 ?>px <?= $customizer['login_shadow_color'] ?>; + box-shadow: 0px 0px <?= $customizer['login_shadow'] ?>px <?= $customizer['login_shadow'] * .1 ?>px <?= $customizer['login_shadow_color'] ?>; + padding: 10px; + border: <?= $customizer['login_border'] ?>px solid <?= $customizer['login_border_color'] ?>; + border-radius: 5px; + max-width: max-content; + text-align: center; +} +.login-btn { + width: <?= $customizer['login_btn_width'] ?>px; + -webkit-box-shadow: 0px 0px <?= $customizer['login_btn_shadow'] ?>px <?= $customizer['login_btn_shadow'] * .1 ?>px <?= $customizer['login_btn_shadow_color'] ?>; + -moz-box-shadow: 0px 0px <?= $customizer['login_btn_shadow'] ?>px <?= $customizer['login_btn_shadow'] * .1 ?>px <?= $customizer['login_btn_shadow_color'] ?>; + box-shadow: 0px 0px <?= $customizer['login_btn_shadow'] ?>px <?= $customizer['login_btn_shadow'] * .1 ?>px <?= $customizer['login_btn_shadow_color'] ?>; + border: <?= $customizer['login_btn_border'] ?>px solid <?= $customizer['login_btn_border_color'] ?>; + background: <?= $customizer['login_btn_color'] ?>; + color: <?= $customizer['login_btn_font_color'] ?>; + background-image: linear-gradient(-180deg, <?= $customizer['login_btn_color'] ?> 0%, <?= $customizer['login_btn_shade_color'] ?> 90%); + border-radius: 5px; + } +.login-btn:hover, .login-btn:focus { + border-color: <?= $customizer['login_btn_border_color'] ?>; + background: <?= $customizer['login_btn_font_color'] ?>; + color: <?= $customizer['login_btn_color'] ?>; +} +/*------ MOVED FROM PLUGIN CSS FILE TO AVOID AFFECTING OTHER PARTS OF KANBOARD. STYLES SET HERE APPLY ONLY TO THE LOGIN PAGE. ------*/ +.form-actions { + text-align: center; + padding-top: unset; + padding-bottom: 10px; +} /* This moves the login button to the centre of the box and removes the useless padding above the login button. Adds padding to bottom of login button. */ + +label:nth-of-type(3n) { + color: grey; + text-align: center; +} /* This makes the 'remember me' smaller and centralised*/ + +.form-actions > .login-btn { + font-variant-caps: all-small-caps; + text-align: center; + transition: cubic-bezier(0.1, 0.75, 0.57, 1) 0.4s; + -webkit-transition: cubic-bezier(0.1, 0.75, 0.57, 1) 0.4s; +} /* This makes the title text of the login button all capitals. Also adds smoothing when hover on the login button */ + +label:nth-of-type(1) { + visibility: hidden; +} /* This hides (to maintain the gap) the text of the labels */ + +label:nth-of-type(2n) { + visibility: hidden; + margin-top: -5px; +} /* This hides (to maintain the gap) the text of the labels and also reduces the top margin */ + +input::-webkit-input-placeholder { + color: #000; + opacity: 1; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; +} /* This styles the placeholder to emphasise it. Cross-browser compatibility */ + +input::-moz-placeholder { + color: #000; + opacity: 1; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; +} /* This styles the placeholder to emphasise it. Cross-browser compatibility */ + +input:-ms-input-placeholder { + color: #000; + opacity: 1; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; +} /* This styles the placeholder to emphasise it. Cross-browser compatibility */ + +input::placeholder { + color: #000; + opacity: 1; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; +} /* This styles the placeholder to emphasise it. Cross-browser compatibility */ + +input[type="password"], input[type="text"]:not(.input-addon-field) { + margin: auto; + display: block; + border-radius: 5px; +} /* This centralises the input fields and makes the borders consistent with the outer form */ + +.form-required { display: none;} /* This removes the standard required asterisk */ + +</style> +<?php +if (function_exists('session_exists')) { +if (session_exists('redirectAfterLogin') && ! filter_var(session_get('redirectAfterLogin'), FILTER_VALIDATE_URL)) { + $redirect = session_get('redirectAfterLogin'); + if (strpos($redirect, 'Customizer') !== false) { + session_remove('redirectAfterLogin'); + } +} +} else { +if (isset($this->task->sessionStorage->redirectAfterLogin) && ! empty($this->task->sessionStorage->redirectAfterLogin) && ! filter_var($this->task->sessionStorage->redirectAfterLogin, FILTER_VALIDATE_URL)) { + $redirect = $this->task->sessionStorage->redirectAfterLogin; + if (strpos($redirect, 'Customizer') !== false) { + unset($this->task->sessionStorage->redirectAfterLogin); + } +} +} +?> diff --git a/plugins/Customizer/Template/layout/note.php b/plugins/Customizer/Template/layout/note.php new file mode 100644 index 00000000..b8454e64 --- /dev/null +++ b/plugins/Customizer/Template/layout/note.php @@ -0,0 +1,7 @@ +<?php global $customizer; ?> + +<div class="form-login"> + <div class="login-note"> + <?= $customizer['login_note'] ?> + </div> +</div> diff --git a/plugins/Customizer/Template/layout/preview_style.php b/plugins/Customizer/Template/layout/preview_style.php new file mode 100644 index 00000000..fdd9576f --- /dev/null +++ b/plugins/Customizer/Template/layout/preview_style.php @@ -0,0 +1,77 @@ +<?php global $customizer; ?> +<style> +.preview-form-login > a > img { + display: block; + margin: auto; + padding-top: 10px +} /* This aligns the logo to the text. Adds padding to top of logo. */ + + +.preview-form-login, .preview-form-note { + background-color: <?= $customizer['loginpanel_color'] ?>; + -webkit-box-shadow: 0px 0px <?= $customizer['login_shadow'] ?>px <?= $customizer['login_shadow'] * .1 ?>px; + -moz-box-shadow: 0px 0px <?= $customizer['login_shadow'] ?>px <?= $customizer['login_shadow'] * .1 ?>px; + box-shadow: 0px 0px <?= $customizer['login_shadow'] ?>px <?= $customizer['login_shadow'] * .1 ?>px; + padding: 10px; + border: <?= $customizer['login_border'] ?>px solid <?= $customizer['login_border_color'] ?>; + border-radius: 5px; + text-align: center; + max-width: max-content; + margin: 5% auto 0;; +} +.preview-login-btn { + width: <?= $customizer['login_btn_width'] ?>px; + -webkit-box-shadow: 0px 0px <?= $customizer['login_btn_shadow'] ?>px <?= $customizer['login_btn_shadow'] * .1 ?>px <?= $customizer['login_btn_shadow_color'] ?>; + -moz-box-shadow: 0px 0px <?= $customizer['login_btn_shadow'] ?>px <?= $customizer['login_btn_shadow'] * .1 ?>px <?= $customizer['login_btn_shadow_color'] ?>; + box-shadow: 0px 0px <?= $customizer['login_btn_shadow'] ?>px <?= $customizer['login_btn_shadow'] * .1 ?>px <?= $customizer['login_btn_shadow_color'] ?>; + border: <?= $customizer['login_btn_border'] ?>px solid <?= $customizer['login_btn_border_color'] ?>; + background: <?= $customizer['login_btn_color'] ?>; + color: <?= $customizer['login_btn_font_color'] ?>; + background-image: linear-gradient(-180deg, transparent 0%, <?= $customizer['login_btn_shade_color'] ?> 90%); + border-radius: 5px; + } + +.preview-login-btn:hover, .preview-login-btn:focus { + border-color: <?= $customizer['login_btn_border_color'] ?>; + background: <?= $customizer['login_btn_font_color'] ?>; + color: <?= $customizer['login_btn_color'] ?>; + background-image: unset; +} + +.preview-form-actions > .preview-login-btn { + font-variant-caps: all-small-caps; + text-align: center; + transition: cubic-bezier(0.1, 0.75, 0.57, 1) 0.4s; + -webkit-transition: cubic-bezier(0.1, 0.75, 0.57, 1) 0.4s; +} + + +.preview-form-actions { + text-align: center; + padding-top: unset; + padding-bottom: 10px; +} /* This moves the login button to the centre of the box and removes the useless padding above the login button. Adds padding to bottom of login button. */ + + + +.preview-form-required { display: none;} /* This removes the standard required asterisk */ + +.avatar-preview img, .avatar-preview div { + border-radius: <?= $this->task->configModel->get('av_radius','50') ?>%; +} +.avatar-preview .avatar-letter { + line-height: <?= $this->task->configModel->get('av_size','20') ?>px; + width: <?= $this->task->configModel->get('av_size','20') ?>px; + font-size: <?= $this->task->configModel->get('av_size','20') / 2 ?>px; +} + +.b-avatar-preview img, .b-avatar-preview div { + border-radius: <?= $this->task->configModel->get('b_av_radius','50') ?>%; +} +.b-avatar-preview .avatar-letter { + line-height: <?= $this->task->configModel->get('b_av_size','20') ?>px; + width: <?= $this->task->configModel->get('b_av_size','20') ?>px; + font-size: <?= $this->task->configModel->get('b_av_size','20') / 2 ?>px; +} + +</style> diff --git a/plugins/Customizer/Template/user_mod/show.php b/plugins/Customizer/Template/user_mod/show.php new file mode 100644 index 00000000..a4fbaf96 --- /dev/null +++ b/plugins/Customizer/Template/user_mod/show.php @@ -0,0 +1,58 @@ +<?php
+global $customizer;
+$user_theme['themeSelection'] = $this->task->userMetadataModel->get($user['id'], 'themeSelection', $this->task->configModel->get('themeSelection', ''));
+?>
+
+<div class="page-header">
+ <h2><?= t('Edit user') ?></h2>
+</div>
+
+<form method="post" id="ts" action="<?= $this->url->href('CustomizerConfigController', 'usertheme', array('plugin' => 'customizer', 'user_id' => $user['id'])) ?>" autocomplete="off">
+ <?= $this->form->csrf() ?>
+ <?= $this->form->hidden('id', $values) ?>
+ <fieldset>
+ <legend><?= t('Themes') ?></legend>
+ <?= $this->form->label(t('User Theme'), 'themeSelection') ?>
+ <?= $this->helper->themeHelper->reverseSelectOnChange('themeSelection', $customizer['themes'], $user_theme, $errors) ?>
+ </fieldset>
+</form>
+
+<form method="post" action="<?= $this->url->href('UserModificationController', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off">
+ <?= $this->form->csrf() ?>
+ <?= $this->form->hidden('id', $values) ?>
+
+ <fieldset>
+ <legend><?= t('Profile') ?></legend>
+ <?= $this->form->label(t('Username'), 'username') ?>
+ <?= $this->form->text('username', $values, $errors, array('autofocus', 'required', isset($values['is_ldap_user']) && $values['is_ldap_user'] == 1 && !$this->user->isAdmin() ? 'readonly' : '', 'maxlength="191"')) ?>
+
+ <?= $this->form->label(t('Name'), 'name') ?>
+ <?= $this->form->text('name', $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_name') ? '' : 'readonly')) ?>
+
+ <?= $this->form->label(t('Email'), 'email') ?>
+ <?= $this->form->email('email', $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_email') ? '' : 'readonly')) ?>
+ </fieldset>
+
+ <fieldset>
+ <legend><?= t('Preferences') ?></legend>
+ <?= $this->form->label(t('Timezone'), 'timezone') ?>
+ <?= $this->form->select('timezone', $timezones, $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_timezone') ? '' : 'disabled')) ?>
+
+ <?= $this->form->label(t('Language'), 'language') ?>
+ <?= $this->form->select('language', $languages, $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_language') ? '' : 'disabled')) ?>
+
+ <?= $this->form->label(t('Filter'), 'filter') ?>
+ <?= $this->form->text('filter', $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_filter') ? '' : 'readonly')) ?>
+ </fieldset>
+
+ <?php if ($this->user->isAdmin()): ?>
+ <fieldset>
+ <legend><?= t('Security') ?></legend>
+ <?= $this->form->label(t('Application role'), 'role') ?>
+ <?= $this->form->select('role', $roles, $values, $errors) ?>
+ </fieldset>
+ <?php endif ?>
+
+ <?= $this->modal->submitButtons() ?>
+</form>
+
\ No newline at end of file diff --git a/plugins/Customizer/_config.yml b/plugins/Customizer/_config.yml new file mode 100644 index 00000000..be854e84 --- /dev/null +++ b/plugins/Customizer/_config.yml @@ -0,0 +1,3 @@ +theme: jekyll-theme-cayman +plugins: + - jemoji diff --git a/plugins/Customizer/composer.json b/plugins/Customizer/composer.json new file mode 100644 index 00000000..8cc13120 --- /dev/null +++ b/plugins/Customizer/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "luizbills/css-generator": "^3.1" + } +} diff --git a/plugins/Customizer/composer.lock b/plugins/Customizer/composer.lock new file mode 100644 index 00000000..15ff3948 --- /dev/null +++ b/plugins/Customizer/composer.lock @@ -0,0 +1,164 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "7c8522680994b684990839bf7e954b59", + "packages": [ + { + "name": "luizbills/css-generator", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/luizbills/css-generator.php.git", + "reference": "5120ebd4e47f041c5e9bbe3395b05f1486264c26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/luizbills/css-generator.php/zipball/5120ebd4e47f041c5e9bbe3395b05f1486264c26", + "reference": "5120ebd4e47f041c5e9bbe3395b05f1486264c26", + "shasum": "" + }, + "require": { + "matthiasmullie/minify": "^1.3", + "php": ">=5.4.0" + }, + "type": "package", + "autoload": { + "psr-4": { + "luizbills\\CSS_Generator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Luiz Bills", + "email": "luizpbills@gmail.com" + } + ], + "description": "Write CSS programatically using PHP.", + "time": "2018-09-21T18:01:30+00:00" + }, + { + "name": "matthiasmullie/minify", + "version": "1.3.60", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/minify.git", + "reference": "ab7fea80ce5ce6549baaf272bc8bd926a7e08f90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/ab7fea80ce5ce6549baaf272bc8bd926a7e08f90", + "reference": "ab7fea80ce5ce6549baaf272bc8bd926a7e08f90", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "matthiasmullie/path-converter": "~1.1", + "php": ">=5.3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.0", + "matthiasmullie/scrapbook": "~1.0", + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "psr/cache-implementation": "Cache implementation to use with Minify::cache" + }, + "bin": [ + "bin/minifycss", + "bin/minifyjs" + ], + "type": "library", + "autoload": { + "psr-4": { + "MatthiasMullie\\Minify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "minify@mullie.eu", + "homepage": "http://www.mullie.eu", + "role": "Developer" + } + ], + "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", + "homepage": "http://www.minifier.org", + "keywords": [ + "JS", + "css", + "javascript", + "minifier", + "minify" + ], + "time": "2018-04-18T08:50:35+00:00" + }, + { + "name": "matthiasmullie/path-converter", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/path-converter.git", + "reference": "5e4b121c8b9f97c80835c1d878b0812ba1d607c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/5e4b121c8b9f97c80835c1d878b0812ba1d607c9", + "reference": "5e4b121c8b9f97c80835c1d878b0812ba1d607c9", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "MatthiasMullie\\PathConverter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "pathconverter@mullie.eu", + "homepage": "http://www.mullie.eu", + "role": "Developer" + } + ], + "description": "Relative path converter", + "homepage": "http://github.com/matthiasmullie/path-converter", + "keywords": [ + "converter", + "path", + "paths", + "relative" + ], + "time": "2018-10-25T15:19:41+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/plugins/Customizer/vendor/autoload.php b/plugins/Customizer/vendor/autoload.php new file mode 100644 index 00000000..fe7dc899 --- /dev/null +++ b/plugins/Customizer/vendor/autoload.php @@ -0,0 +1,7 @@ +<?php + +// autoload.php @generated by Composer + +require_once __DIR__ . '/composer/autoload_real.php'; + +return ComposerAutoloaderInit1a4d3188031158859b352a6b53ee769d::getLoader(); diff --git a/plugins/Customizer/vendor/bin/minifycss b/plugins/Customizer/vendor/bin/minifycss new file mode 100644 index 00000000..04f60a4b --- /dev/null +++ b/plugins/Customizer/vendor/bin/minifycss @@ -0,0 +1 @@ +../matthiasmullie/minify/bin/minifycss
\ No newline at end of file diff --git a/plugins/Customizer/vendor/bin/minifyjs b/plugins/Customizer/vendor/bin/minifyjs new file mode 100644 index 00000000..61124467 --- /dev/null +++ b/plugins/Customizer/vendor/bin/minifyjs @@ -0,0 +1 @@ +../matthiasmullie/minify/bin/minifyjs
\ No newline at end of file diff --git a/plugins/Customizer/vendor/composer/ClassLoader.php b/plugins/Customizer/vendor/composer/ClassLoader.php new file mode 100644 index 00000000..dc02dfb1 --- /dev/null +++ b/plugins/Customizer/vendor/composer/ClassLoader.php @@ -0,0 +1,445 @@ +<?php + +/* + * This file is part of Composer. + * + * (c) Nils Adermann <naderman@naderman.de> + * Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Jordi Boggiano <j.boggiano@seld.be> + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/plugins/Customizer/vendor/composer/LICENSE b/plugins/Customizer/vendor/composer/LICENSE new file mode 100644 index 00000000..f27399a0 --- /dev/null +++ b/plugins/Customizer/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/plugins/Customizer/vendor/composer/autoload_classmap.php b/plugins/Customizer/vendor/composer/autoload_classmap.php new file mode 100644 index 00000000..7a91153b --- /dev/null +++ b/plugins/Customizer/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ +<?php + +// autoload_classmap.php @generated by Composer + +$vendorDir = dirname(dirname(__FILE__)); +$baseDir = dirname($vendorDir); + +return array( +); diff --git a/plugins/Customizer/vendor/composer/autoload_namespaces.php b/plugins/Customizer/vendor/composer/autoload_namespaces.php new file mode 100644 index 00000000..b7fc0125 --- /dev/null +++ b/plugins/Customizer/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ +<?php + +// autoload_namespaces.php @generated by Composer + +$vendorDir = dirname(dirname(__FILE__)); +$baseDir = dirname($vendorDir); + +return array( +); diff --git a/plugins/Customizer/vendor/composer/autoload_psr4.php b/plugins/Customizer/vendor/composer/autoload_psr4.php new file mode 100644 index 00000000..05c26176 --- /dev/null +++ b/plugins/Customizer/vendor/composer/autoload_psr4.php @@ -0,0 +1,12 @@ +<?php + +// autoload_psr4.php @generated by Composer + +$vendorDir = dirname(dirname(__FILE__)); +$baseDir = dirname($vendorDir); + +return array( + 'luizbills\\CSS_Generator\\' => array($vendorDir . '/luizbills/css-generator/src'), + 'MatthiasMullie\\PathConverter\\' => array($vendorDir . '/matthiasmullie/path-converter/src'), + 'MatthiasMullie\\Minify\\' => array($vendorDir . '/matthiasmullie/minify/src'), +); diff --git a/plugins/Customizer/vendor/composer/autoload_real.php b/plugins/Customizer/vendor/composer/autoload_real.php new file mode 100644 index 00000000..49558bf6 --- /dev/null +++ b/plugins/Customizer/vendor/composer/autoload_real.php @@ -0,0 +1,52 @@ +<?php + +// autoload_real.php @generated by Composer + +class ComposerAutoloaderInit1a4d3188031158859b352a6b53ee769d +{ + private static $loader; + + public static function loadClassLoader($class) + { + if ('Composer\Autoload\ClassLoader' === $class) { + require __DIR__ . '/ClassLoader.php'; + } + } + + public static function getLoader() + { + if (null !== self::$loader) { + return self::$loader; + } + + spl_autoload_register(array('ComposerAutoloaderInit1a4d3188031158859b352a6b53ee769d', 'loadClassLoader'), true, true); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(); + spl_autoload_unregister(array('ComposerAutoloaderInit1a4d3188031158859b352a6b53ee769d', 'loadClassLoader')); + + $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit1a4d3188031158859b352a6b53ee769d::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + return $loader; + } +} diff --git a/plugins/Customizer/vendor/composer/autoload_static.php b/plugins/Customizer/vendor/composer/autoload_static.php new file mode 100644 index 00000000..11ddf899 --- /dev/null +++ b/plugins/Customizer/vendor/composer/autoload_static.php @@ -0,0 +1,44 @@ +<?php + +// autoload_static.php @generated by Composer + +namespace Composer\Autoload; + +class ComposerStaticInit1a4d3188031158859b352a6b53ee769d +{ + public static $prefixLengthsPsr4 = array ( + 'l' => + array ( + 'luizbills\\CSS_Generator\\' => 24, + ), + 'M' => + array ( + 'MatthiasMullie\\PathConverter\\' => 29, + 'MatthiasMullie\\Minify\\' => 22, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'luizbills\\CSS_Generator\\' => + array ( + 0 => __DIR__ . '/..' . '/luizbills/css-generator/src', + ), + 'MatthiasMullie\\PathConverter\\' => + array ( + 0 => __DIR__ . '/..' . '/matthiasmullie/path-converter/src', + ), + 'MatthiasMullie\\Minify\\' => + array ( + 0 => __DIR__ . '/..' . '/matthiasmullie/minify/src', + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit1a4d3188031158859b352a6b53ee769d::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit1a4d3188031158859b352a6b53ee769d::$prefixDirsPsr4; + + }, null, ClassLoader::class); + } +} diff --git a/plugins/Customizer/vendor/composer/installed.json b/plugins/Customizer/vendor/composer/installed.json new file mode 100644 index 00000000..9823b6e4 --- /dev/null +++ b/plugins/Customizer/vendor/composer/installed.json @@ -0,0 +1,154 @@ +[ + { + "name": "luizbills/css-generator", + "version": "v3.1.1", + "version_normalized": "3.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/luizbills/css-generator.php.git", + "reference": "5120ebd4e47f041c5e9bbe3395b05f1486264c26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/luizbills/css-generator.php/zipball/5120ebd4e47f041c5e9bbe3395b05f1486264c26", + "reference": "5120ebd4e47f041c5e9bbe3395b05f1486264c26", + "shasum": "" + }, + "require": { + "matthiasmullie/minify": "^1.3", + "php": ">=5.4.0" + }, + "time": "2018-09-21T18:01:30+00:00", + "type": "package", + "installation-source": "dist", + "autoload": { + "psr-4": { + "luizbills\\CSS_Generator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Luiz Bills", + "email": "luizpbills@gmail.com" + } + ], + "description": "Write CSS programatically using PHP." + }, + { + "name": "matthiasmullie/minify", + "version": "1.3.60", + "version_normalized": "1.3.60.0", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/minify.git", + "reference": "ab7fea80ce5ce6549baaf272bc8bd926a7e08f90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/ab7fea80ce5ce6549baaf272bc8bd926a7e08f90", + "reference": "ab7fea80ce5ce6549baaf272bc8bd926a7e08f90", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "matthiasmullie/path-converter": "~1.1", + "php": ">=5.3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.0", + "matthiasmullie/scrapbook": "~1.0", + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "psr/cache-implementation": "Cache implementation to use with Minify::cache" + }, + "time": "2018-04-18T08:50:35+00:00", + "bin": [ + "bin/minifycss", + "bin/minifyjs" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "MatthiasMullie\\Minify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "minify@mullie.eu", + "homepage": "http://www.mullie.eu", + "role": "Developer" + } + ], + "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", + "homepage": "http://www.minifier.org", + "keywords": [ + "JS", + "css", + "javascript", + "minifier", + "minify" + ] + }, + { + "name": "matthiasmullie/path-converter", + "version": "1.1.2", + "version_normalized": "1.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/path-converter.git", + "reference": "5e4b121c8b9f97c80835c1d878b0812ba1d607c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/5e4b121c8b9f97c80835c1d878b0812ba1d607c9", + "reference": "5e4b121c8b9f97c80835c1d878b0812ba1d607c9", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "time": "2018-10-25T15:19:41+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "MatthiasMullie\\PathConverter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "pathconverter@mullie.eu", + "homepage": "http://www.mullie.eu", + "role": "Developer" + } + ], + "description": "Relative path converter", + "homepage": "http://github.com/matthiasmullie/path-converter", + "keywords": [ + "converter", + "path", + "paths", + "relative" + ] + } +] diff --git a/plugins/Customizer/vendor/luizbills/css-generator/.gitignore b/plugins/Customizer/vendor/luizbills/css-generator/.gitignore new file mode 100644 index 00000000..55940e57 --- /dev/null +++ b/plugins/Customizer/vendor/luizbills/css-generator/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock
\ No newline at end of file diff --git a/plugins/Customizer/vendor/luizbills/css-generator/LICENSE b/plugins/Customizer/vendor/luizbills/css-generator/LICENSE new file mode 100644 index 00000000..7f9fca91 --- /dev/null +++ b/plugins/Customizer/vendor/luizbills/css-generator/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Luiz Bills + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/Customizer/vendor/luizbills/css-generator/README.md b/plugins/Customizer/vendor/luizbills/css-generator/README.md new file mode 100644 index 00000000..28dee232 --- /dev/null +++ b/plugins/Customizer/vendor/luizbills/css-generator/README.md @@ -0,0 +1,102 @@ +# CSS Generator + +Write CSS programatically using PHP. + +## Install + +```php +composer require luizbills/css-generator +``` + +## Usage + +```php +use luizbills\CSS_Generator\Generator as CSS_Generator; + +$options = [ + // default values + // 'indentation' => ' ', // 4 spaces +]; +$css = new CSS_Generator( $options ); + +// single selector +$css->add_rule( '.color-white', [ 'color' => '#fff' ] ); + +$css->open_block( 'media', 'screen and (min-width: 30em)' ); + +// multiple selectors +$css->add_rule( [ 'html', 'body' ], [ + 'background-color' => 'black', + 'color' => 'white' +] ); + +$css->close_block(); // close a block + +$css->open_block( 'supports', '(display: grid)' ); + +$css->add_rule( '.grid', [ + 'display' => 'grid', +] ); + +// nested block +$css->open_block( 'media', 'screen and (max-width: 30em)' ); + +$css->add_rule( '.grid-sm', [ + 'display' => 'grid', +] ); + +$css->close_blocks(); // close all blocks + +$minify = false; +echo $css->get_output( $minify ); +``` + +output: +```css +.color-white { + color: #fff; +} +@media screen and (min-width: 30em) { + html, + body { + background-color: black; + color: white; + } +} +@supports (display: grid) { + .grid { + display: grid; + } + @media screen and (max-width: 30em) { + .grid-sm { + display: grid; + } + } +} +``` + +Changing `$minify` to `true` will outputs: +```css +.color-white{color:#fff}@media screen and (min-width:30em){body,html{background-color:#000;color:#fff}}@supports (display:grid){.grid{display:grid}@media screen and (max-width:30em){.grid-sm{display:grid}}} +``` + +There is also a method `add_raw` that adds any string to your css. Useful to comments or include a framework. +```php +$css = new CSS_Generator(); + +$css->add_rule( '.color-white', [ 'color' => '#fff' ] ); +$css->add_raw('/* my comment */ a { text-decoration: none }'); + +echo $css->get_output(); +``` + +output: +```css +.color-white{ + color:#fff; +} +/* my comment */ a { text-decoration: none } +``` + +## License +MIT License © 2018 Luiz Bills diff --git a/plugins/Customizer/vendor/luizbills/css-generator/composer.json b/plugins/Customizer/vendor/luizbills/css-generator/composer.json new file mode 100644 index 00000000..c6e9417c --- /dev/null +++ b/plugins/Customizer/vendor/luizbills/css-generator/composer.json @@ -0,0 +1,21 @@ +{ + "name": "luizbills/css-generator", + "description": "Write CSS programatically using PHP.", + "type": "package", + "require": { + "php": ">=5.4.0", + "matthiasmullie/minify": "^1.3" + }, + "license": "MIT", + "authors": [ + { + "name": "Luiz Bills", + "email": "luizpbills@gmail.com" + } + ], + "autoload": { + "psr-4": { + "luizbills\\CSS_Generator\\": "src" + } + } +} diff --git a/plugins/Customizer/vendor/luizbills/css-generator/demo/demo.php b/plugins/Customizer/vendor/luizbills/css-generator/demo/demo.php new file mode 100644 index 00000000..69e6a4a1 --- /dev/null +++ b/plugins/Customizer/vendor/luizbills/css-generator/demo/demo.php @@ -0,0 +1,41 @@ +<?php + +require_once __DIR__ . '/../vendor/autoload.php'; + +use luizbills\CSS_Generator\Generator as CSS_Generator; + +$options = [ + 'indentation' => ' ', // 2 spaces +]; +$css = new CSS_Generator( $options ); + +// single selector +$css->add_rule( '.color-white', [ 'color' => '#fff' ] ); + +$css->open_block( 'media', 'screen and (min-width: 30em)' ); + +// multiple selectors +$css->add_rule( [ 'html', 'body' ], [ + 'background-color' => 'black', + 'color' => 'white' +] ); + +$css->close_block(); // close a block + +$css->open_block( 'supports', '(display: grid)' ); + +$css->add_rule( '.grid', [ + 'display' => 'grid', +] ); + +// nested block +$css->open_block( 'media', 'screen and (max-width: 30em)' ); + +$css->add_rule( '.grid-sm', [ + 'display' => 'grid', +] ); + +$css->close_blocks(); // close all blocks + +$minify = false; +echo $css->get_output( $minify );
\ No newline at end of file diff --git a/plugins/Customizer/vendor/luizbills/css-generator/src/Generator.php b/plugins/Customizer/vendor/luizbills/css-generator/src/Generator.php new file mode 100644 index 00000000..6ef80f00 --- /dev/null +++ b/plugins/Customizer/vendor/luizbills/css-generator/src/Generator.php @@ -0,0 +1,102 @@ +<?php +/** + * CSS Generator + * Write css programatically using PHP. + * + * @author Luiz Bills <luizpbills@gmail.comm> + * @copyright 2018 Luiz Bills + * @license MIT +*/ +namespace luizbills\CSS_Generator; + +use MatthiasMullie\Minify; + +class Generator { + const VERSION = '3.1.0'; + + protected $raw = ''; + protected $block_level = 0; + protected $linebreak = "\n"; + protected $minified = null; // for cache + protected $options = null; + protected $default_options = [ + 'indentation' => ' ', // 4 spaces + ]; + + public function __construct ( $options = [] ) { + $this->options = array_merge( $this->default_options, $options ); + } + + public function get_output ( $compress = false ) { + $this->close_blocks(); + if ( $compress ) { + return $this->minify(); + } + return $this->raw; + } + + protected function minify () { + if ( ! is_null( $this->minified ) ) { + return $this->minified; + } + + $minifier = new Minify\CSS( $this->raw ); + $this->minified = $minifier->minify(); + return $this->minified; + } + + public function add_raw ( $string ) { + $this->raw .= $string; + $this->clear_cache(); + } + + public function add_rule ( $selectors, $declarations_array ) { + $declarations = []; + $selector_indentation = str_repeat( $this->options['indentation'], $this->block_level ); + $declaration_indentation = str_repeat( $this->options['indentation'], $this->block_level + 1 ); + + if ( ! is_array( $selectors ) ) { + $selectors = [ $selectors ]; + } + + foreach ( $selectors as $key => $value ) { + $selectors[ $key ] = $selector_indentation . trim( $value ); + } + + foreach ( $declarations_array as $key => $value ) { + $declarations[] = $declaration_indentation . trim( $key ) . ': ' . trim( $value ) . ';' . $this->linebreak; + } + + $this->raw .= implode( ',' . $this->linebreak, $selectors ) . ' {'; + $this->raw .= $this->linebreak . implode( '', $declarations ); + $this->raw .= $selector_indentation . '}' . $this->linebreak; + + $this->clear_cache(); + } + + public function open_block ( $type, $props = '' ) { + $block_indentation = str_repeat( $this->options['indentation'], $this->block_level ); + $this->raw .= $block_indentation . '@' . $type . ' ' . trim( $props ) . ' {' . $this->linebreak; + $this->block_level++; + $this->clear_cache(); + } + + public function close_block () { + if ( $this->block_level > 0 ) { + $this->block_level--; + $block_indentation = str_repeat( $this->options['indentation'], $this->block_level ); + $this->raw .= $block_indentation . '}' . $this->linebreak; + $this->clear_cache(); + } + } + + public function close_blocks () { + while ( $this->block_level > 0 ) { + $this->close_block(); + } + } + + protected function clear_cache() { + $this->minified = null; + } +} diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/CONTRIBUTING.md b/plugins/Customizer/vendor/matthiasmullie/minify/CONTRIBUTING.md new file mode 100644 index 00000000..226cf976 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# How to contribute + + +## Issues + +When [filing bugs](https://github.com/matthiasmullie/minify/issues/new), +try to be as thorough as possible: +* What version did you use? +* What did you try to do? ***Please post the relevant parts of your code.*** +* What went wrong? ***Please include error messages, if any.*** +* What was the expected result? + + +## Pull requests + +Bug fixes and general improvements to the existing codebase are always welcome. +New features are also welcome, but will be judged on an individual basis. If +you'd rather not risk wasting your time implementing a new feature only to see +it turned down, please start the discussion by +[opening an issue](https://github.com/matthiasmullie/minify/issues/new). + +Don't forget to add your changes to the [changelog](CHANGELOG.md). + + +### Testing + +Please include tests for every change or addition to the code. +To run the complete test suite: + +```sh +vendor/bin/phpunit +``` + +When submitting a new pull request, please make sure that that the test suite +passes (Travis CI will run it & report back on your pull request.) + +To run the tests on Windows, run `tests/convert_symlinks_to_windows_style.sh` +from the command line in order to convert Linux-style test symlinks to +Windows-style. + + +### Coding standards + +All code must follow [PSR-2](http://www.php-fig.org/psr/psr-2/). Just make sure +to run php-cs-fixer before submitting the code, it'll take care of the +formatting for you: + +```sh +vendor/bin/php-cs-fixer fix src +vendor/bin/php-cs-fixer fix tests +``` + +Document the code thoroughly! + + +## License + +Note that minify is MIT-licensed, which basically allows anyone to do +anything they like with it, without restriction. diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/Dockerfile b/plugins/Customizer/vendor/matthiasmullie/minify/Dockerfile new file mode 100644 index 00000000..d17f9d74 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/Dockerfile @@ -0,0 +1,13 @@ +ARG version=cli +FROM php:$version + +COPY . /var/www +WORKDIR /var/www + +RUN apt-get update +RUN apt-get install -y zip unzip zlib1g-dev +RUN docker-php-ext-install zip +RUN docker-php-ext-install pcntl +RUN curl -sS https://getcomposer.org/installer | php +RUN mv composer.phar /usr/local/bin/composer +RUN composer install diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/LICENSE b/plugins/Customizer/vendor/matthiasmullie/minify/LICENSE new file mode 100644 index 00000000..0c0d08a7 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2012 Matthias Mullie + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/bin/minifycss b/plugins/Customizer/vendor/matthiasmullie/minify/bin/minifycss new file mode 100644 index 00000000..6a681a85 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/bin/minifycss @@ -0,0 +1,45 @@ +#!/usr/bin/env php +<?php +use MatthiasMullie\Minify; + +// command line utility to minify CSS +if (file_exists(__DIR__ . '/../../../autoload.php')) { + // if composer install + require_once __DIR__ . '/../../../autoload.php'; +} else { + require_once __DIR__ . '/../src/Minify.php'; + require_once __DIR__ . '/../src/CSS.php'; + require_once __DIR__ . '/../src/Exception.php'; +} + +error_reporting(E_ALL); +// check PHP setup for cli arguments +if (!isset($_SERVER['argv']) && !isset($argv)) { + fwrite(STDERR, 'Please enable the "register_argc_argv" directive in your php.ini' . PHP_EOL); + exit(1); +} elseif (!isset($argv)) { + $argv = $_SERVER['argv']; +} +// check if path to file given +if (!isset($argv[1])) { + fwrite(STDERR, 'Argument expected: path to file' . PHP_EOL); + exit(1); +} +// check if script run in cli environment +if ('cli' !== php_sapi_name()) { + fwrite(STDERR, $argv[1] . ' must be run in the command line' . PHP_EOL); + exit(1); +} +// check if source file exists +if (!file_exists($argv[1])) { + fwrite(STDERR, 'Source file "' . $argv[1] . '" not found' . PHP_EOL); + exit(1); +} + +try { + $minifier = new Minify\CSS($argv[1]); + echo $minifier->minify(); +} catch (Exception $e) { + fwrite(STDERR, $e->getMessage(), PHP_EOL); + exit(1); +} diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/bin/minifyjs b/plugins/Customizer/vendor/matthiasmullie/minify/bin/minifyjs new file mode 100644 index 00000000..4cbe63ff --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/bin/minifyjs @@ -0,0 +1,45 @@ +#!/usr/bin/env php +<?php +use MatthiasMullie\Minify; + +// command line utility to minify JS +if (file_exists(__DIR__ . '/../../../autoload.php')) { + // if composer install + require_once __DIR__ . '/../../../autoload.php'; +} else { + require_once __DIR__ . '/../src/Minify.php'; + require_once __DIR__ . '/../src/JS.php'; + require_once __DIR__ . '/../src/Exception.php'; +} + +error_reporting(E_ALL); +// check PHP setup for cli arguments +if (!isset($_SERVER['argv']) && !isset($argv)) { + fwrite(STDERR, 'Please enable the "register_argc_argv" directive in your php.ini' . PHP_EOL); + exit(1); +} elseif (!isset($argv)) { + $argv = $_SERVER['argv']; +} +// check if path to file given +if (!isset($argv[1])) { + fwrite(STDERR, 'Argument expected: path to file' . PHP_EOL); + exit(1); +} +// check if script run in cli environment +if ('cli' !== php_sapi_name()) { + fwrite(STDERR, $argv[1] . ' must be run in the command line' . PHP_EOL); + exit(1); +} +// check if source file exists +if (!file_exists($argv[1])) { + fwrite(STDERR, 'Source file "' . $argv[1] . '" not found' . PHP_EOL); + exit(1); +} + +try { + $minifier = new Minify\JS($argv[1]); + echo $minifier->minify(); +} catch (Exception $e) { + fwrite(STDERR, $e->getMessage(), PHP_EOL); + exit(1); +} diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/composer.json b/plugins/Customizer/vendor/matthiasmullie/minify/composer.json new file mode 100644 index 00000000..6d81b4f9 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/composer.json @@ -0,0 +1,38 @@ +{ + "name": "matthiasmullie/minify", + "type": "library", + "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", + "keywords": ["minify", "minifier", "css", "js", "javascript"], + "homepage": "http://www.minifier.org", + "license": "MIT", + "authors": [ + { + "name": "Matthias Mullie", + "homepage": "http://www.mullie.eu", + "email": "minify@mullie.eu", + "role": "Developer" + } + ], + "require": { + "php": ">=5.3.0", + "ext-pcre": "*", + "matthiasmullie/path-converter": "~1.1" + }, + "require-dev": { + "matthiasmullie/scrapbook": "~1.0", + "phpunit/phpunit": "~4.8", + "friendsofphp/php-cs-fixer": "~2.0" + }, + "suggest": { + "psr/cache-implementation": "Cache implementation to use with Minify::cache" + }, + "autoload": { + "psr-4": { + "MatthiasMullie\\Minify\\": "src/" + } + }, + "bin": [ + "bin/minifycss", + "bin/minifyjs" + ] +} diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_after.txt b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_after.txt new file mode 100644 index 00000000..5c8cba7f --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_after.txt @@ -0,0 +1,7 @@ +in +public +extends +private +protected +implements +instanceof
\ No newline at end of file diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_before.txt b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_before.txt new file mode 100644 index 00000000..5abf3579 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_before.txt @@ -0,0 +1,26 @@ +do +in +let +new +var +case +else +enum +void +with +class +const +yield +delete +export +import +public +static +typeof +extends +package +private +function +protected +implements +instanceof
\ No newline at end of file diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_reserved.txt b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_reserved.txt new file mode 100644 index 00000000..2a3ad3c0 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/keywords_reserved.txt @@ -0,0 +1,63 @@ +do +if +in +for +let +new +try +var +case +else +enum +eval +null +this +true +void +with +break +catch +class +const +false +super +throw +while +yield +delete +export +import +public +return +static +switch +typeof +default +extends +finally +package +private +continue +debugger +function +arguments +interface +protected +implements +instanceof +abstract +boolean +byte +char +double +final +float +goto +int +long +native +short +synchronized +throws +transient +volatile
\ No newline at end of file diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators.txt b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators.txt new file mode 100644 index 00000000..e66229ae --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators.txt @@ -0,0 +1,46 @@ ++ +- +* +/ +% += ++= +-= +*= +/= +%= +<<= +>>= +>>>= +&= +^= +|= +& +| +^ +~ +<< +>> +>>> +== +=== +!= +!== +> +< +>= +<= +&& +|| +! +. +[ +] +? +: +, +; +( +) +{ +}
\ No newline at end of file diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators_after.txt b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators_after.txt new file mode 100644 index 00000000..71a9b709 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators_after.txt @@ -0,0 +1,43 @@ ++ +- +* +/ +% += ++= +-= +*= +/= +%= +<<= +>>= +>>>= +&= +^= +|= +& +| +^ +<< +>> +>>> +== +=== +!= +!== +> +< +>= +<= +&& +|| +. +[ +] +? +: +, +; +( +) +}
\ No newline at end of file diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators_before.txt b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators_before.txt new file mode 100644 index 00000000..ff50d870 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/data/js/operators_before.txt @@ -0,0 +1,43 @@ ++ +- +* +/ +% += ++= +-= +*= +/= +%= +<<= +>>= +>>>= +&= +^= +|= +& +| +^ +~ +<< +>> +>>> +== +=== +!= +!== +> +< +>= +<= +&& +|| +! +. +[ +? +: +, +; +( +{ diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/docker-compose.yml b/plugins/Customizer/vendor/matthiasmullie/minify/docker-compose.yml new file mode 100644 index 00000000..5413e24b --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/docker-compose.yml @@ -0,0 +1,31 @@ +version: '2.1' +services: + php: + build: + context: . + dockerfile: Dockerfile + volumes: + - ./src:/var/www/src + - ./data:/var/www/data + - ./tests:/var/www/tests + - ./phpunit.xml.dist:/var/www/phpunit.xml.dist + '7.2': + extends: php + build: + args: + version: 7.2-cli + '7.1': + extends: php + build: + args: + version: 7.1-cli + '7.0': + extends: php + build: + args: + version: 7.0-cli + '5.6': + extends: php + build: + args: + version: 5.6-cli diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/src/CSS.php b/plugins/Customizer/vendor/matthiasmullie/minify/src/CSS.php new file mode 100644 index 00000000..e92bb300 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/src/CSS.php @@ -0,0 +1,736 @@ +<?php +/** + * CSS Minifier + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie <minify@mullie.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ + +namespace MatthiasMullie\Minify; + +use MatthiasMullie\Minify\Exceptions\FileImportException; +use MatthiasMullie\PathConverter\ConverterInterface; +use MatthiasMullie\PathConverter\Converter; + +/** + * CSS minifier + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @package Minify + * @author Matthias Mullie <minify@mullie.eu> + * @author Tijs Verkoyen <minify@verkoyen.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +class CSS extends Minify +{ + /** + * @var int maximum inport size in kB + */ + protected $maxImportSize = 5; + + /** + * @var string[] valid import extensions + */ + protected $importExtensions = array( + 'gif' => 'data:image/gif', + 'png' => 'data:image/png', + 'jpe' => 'data:image/jpeg', + 'jpg' => 'data:image/jpeg', + 'jpeg' => 'data:image/jpeg', + 'svg' => 'data:image/svg+xml', + 'woff' => 'data:application/x-font-woff', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'xbm' => 'image/x-xbitmap', + ); + + /** + * Set the maximum size if files to be imported. + * + * Files larger than this size (in kB) will not be imported into the CSS. + * Importing files into the CSS as data-uri will save you some connections, + * but we should only import relatively small decorative images so that our + * CSS file doesn't get too bulky. + * + * @param int $size Size in kB + */ + public function setMaxImportSize($size) + { + $this->maxImportSize = $size; + } + + /** + * Set the type of extensions to be imported into the CSS (to save network + * connections). + * Keys of the array should be the file extensions & respective values + * should be the data type. + * + * @param string[] $extensions Array of file extensions + */ + public function setImportExtensions(array $extensions) + { + $this->importExtensions = $extensions; + } + + /** + * Move any import statements to the top. + * + * @param string $content Nearly finished CSS content + * + * @return string + */ + protected function moveImportsToTop($content) + { + if (preg_match_all('/(;?)(@import (?<url>url\()?(?P<quotes>["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) { + // remove from content + foreach ($matches[0] as $import) { + $content = str_replace($import, '', $content); + } + + // add to top + $content = implode(';', $matches[2]).';'.trim($content, ';'); + } + + return $content; + } + + /** + * Combine CSS from import statements. + * + * @import's will be loaded and their content merged into the original file, + * to save HTTP requests. + * + * @param string $source The file to combine imports for + * @param string $content The CSS content to combine imports for + * @param string[] $parents Parent paths, for circular reference checks + * + * @return string + * + * @throws FileImportException + */ + protected function combineImports($source, $content, $parents) + { + $importRegexes = array( + // @import url(xxx) + '/ + # import statement + @import + + # whitespace + \s+ + + # open url() + url\( + + # (optional) open path enclosure + (?P<quotes>["\']?) + + # fetch path + (?P<path>.+?) + + # (optional) close path enclosure + (?P=quotes) + + # close url() + \) + + # (optional) trailing whitespace + \s* + + # (optional) media statement(s) + (?P<media>[^;]*) + + # (optional) trailing whitespace + \s* + + # (optional) closing semi-colon + ;? + + /ix', + + // @import 'xxx' + '/ + + # import statement + @import + + # whitespace + \s+ + + # open path enclosure + (?P<quotes>["\']) + + # fetch path + (?P<path>.+?) + + # close path enclosure + (?P=quotes) + + # (optional) trailing whitespace + \s* + + # (optional) media statement(s) + (?P<media>[^;]*) + + # (optional) trailing whitespace + \s* + + # (optional) closing semi-colon + ;? + + /ix', + ); + + // find all relative imports in css + $matches = array(); + foreach ($importRegexes as $importRegex) { + if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) { + $matches = array_merge($matches, $regexMatches); + } + } + + $search = array(); + $replace = array(); + + // loop the matches + foreach ($matches as $match) { + // get the path for the file that will be imported + $importPath = dirname($source).'/'.$match['path']; + + // only replace the import with the content if we can grab the + // content of the file + if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) { + continue; + } + + // check if current file was not imported previously in the same + // import chain. + if (in_array($importPath, $parents)) { + throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.'); + } + + // grab referenced file & minify it (which may include importing + // yet other @import statements recursively) + $minifier = new static($importPath); + $importContent = $minifier->execute($source, $parents); + + // check if this is only valid for certain media + if (!empty($match['media'])) { + $importContent = '@media '.$match['media'].'{'.$importContent.'}'; + } + + // add to replacement array + $search[] = $match[0]; + $replace[] = $importContent; + } + + // replace the import statements + return str_replace($search, $replace, $content); + } + + /** + * Import files into the CSS, base64-ized. + * + * @url(image.jpg) images will be loaded and their content merged into the + * original file, to save HTTP requests. + * + * @param string $source The file to import files for + * @param string $content The CSS content to import files for + * + * @return string + */ + protected function importFiles($source, $content) + { + $regex = '/url\((["\']?)(.+?)\\1\)/i'; + if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + $search = array(); + $replace = array(); + + // loop the matches + foreach ($matches as $match) { + $extension = substr(strrchr($match[2], '.'), 1); + if ($extension && !array_key_exists($extension, $this->importExtensions)) { + continue; + } + + // get the path for the file that will be imported + $path = $match[2]; + $path = dirname($source).'/'.$path; + + // only replace the import with the content if we're able to get + // the content of the file, and it's relatively small + if ($this->canImportFile($path) && $this->canImportBySize($path)) { + // grab content && base64-ize + $importContent = $this->load($path); + $importContent = base64_encode($importContent); + + // build replacement + $search[] = $match[0]; + $replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')'; + } + } + + // replace the import statements + $content = str_replace($search, $replace, $content); + } + + return $content; + } + + /** + * Minify the data. + * Perform CSS optimizations. + * + * @param string[optional] $path Path to write the data to + * @param string[] $parents Parent paths, for circular reference checks + * + * @return string The minified data + */ + public function execute($path = null, $parents = array()) + { + $content = ''; + + // loop CSS data (raw data and files) + foreach ($this->data as $source => $css) { + /* + * Let's first take out strings & comments, since we can't just + * remove whitespace anywhere. If whitespace occurs inside a string, + * we should leave it alone. E.g.: + * p { content: "a test" } + */ + $this->extractStrings(); + $this->stripComments(); + $css = $this->replace($css); + + $css = $this->stripWhitespace($css); + $css = $this->shortenHex($css); + $css = $this->shortenZeroes($css); + $css = $this->shortenFontWeights($css); + $css = $this->stripEmptyTags($css); + + // restore the string we've extracted earlier + $css = $this->restoreExtractedData($css); + + $source = is_int($source) ? '' : $source; + $parents = $source ? array_merge($parents, array($source)) : $parents; + $css = $this->combineImports($source, $css, $parents); + $css = $this->importFiles($source, $css); + + /* + * If we'll save to a new path, we'll have to fix the relative paths + * to be relative no longer to the source file, but to the new path. + * If we don't write to a file, fall back to same path so no + * conversion happens (because we still want it to go through most + * of the move code, which also addresses url() & @import syntax...) + */ + $converter = $this->getPathConverter($source, $path ?: $source); + $css = $this->move($converter, $css); + + // combine css + $content .= $css; + } + + $content = $this->moveImportsToTop($content); + + return $content; + } + + /** + * Moving a css file should update all relative urls. + * Relative references (e.g. ../images/image.gif) in a certain css file, + * will have to be updated when a file is being saved at another location + * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper). + * + * @param ConverterInterface $converter Relative path converter + * @param string $content The CSS content to update relative urls for + * + * @return string + */ + protected function move(ConverterInterface $converter, $content) + { + /* + * Relative path references will usually be enclosed by url(). @import + * is an exception, where url() is not necessary around the path (but is + * allowed). + * This *could* be 1 regular expression, where both regular expressions + * in this array are on different sides of a |. But we're using named + * patterns in both regexes, the same name on both regexes. This is only + * possible with a (?J) modifier, but that only works after a fairly + * recent PCRE version. That's why I'm doing 2 separate regular + * expressions & combining the matches after executing of both. + */ + $relativeRegexes = array( + // url(xxx) + '/ + # open url() + url\( + + \s* + + # open path enclosure + (?P<quotes>["\'])? + + # fetch path + (?P<path>.+?) + + # close path enclosure + (?(quotes)(?P=quotes)) + + \s* + + # close url() + \) + + /ix', + + // @import "xxx" + '/ + # import statement + @import + + # whitespace + \s+ + + # we don\'t have to check for @import url(), because the + # condition above will already catch these + + # open path enclosure + (?P<quotes>["\']) + + # fetch path + (?P<path>.+?) + + # close path enclosure + (?P=quotes) + + /ix', + ); + + // find all relative urls in css + $matches = array(); + foreach ($relativeRegexes as $relativeRegex) { + if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) { + $matches = array_merge($matches, $regexMatches); + } + } + + $search = array(); + $replace = array(); + + // loop all urls + foreach ($matches as $match) { + // determine if it's a url() or an @import match + $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url'); + + $url = $match['path']; + if ($this->canImportByPath($url)) { + // attempting to interpret GET-params makes no sense, so let's discard them for awhile + $params = strrchr($url, '?'); + $url = $params ? substr($url, 0, -strlen($params)) : $url; + + // fix relative url + $url = $converter->convert($url); + + // now that the path has been converted, re-apply GET-params + $url .= $params; + } + + /* + * Urls with control characters above 0x7e should be quoted. + * According to Mozilla's parser, whitespace is only allowed at the + * end of unquoted urls. + * Urls with `)` (as could happen with data: uris) should also be + * quoted to avoid being confused for the url() closing parentheses. + * And urls with a # have also been reported to cause issues. + * Urls with quotes inside should also remain escaped. + * + * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation + * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378 + * @see https://github.com/matthiasmullie/minify/issues/193 + */ + $url = trim($url); + if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) { + $url = $match['quotes'] . $url . $match['quotes']; + } + + // build replacement + $search[] = $match[0]; + if ($type === 'url') { + $replace[] = 'url('.$url.')'; + } elseif ($type === 'import') { + $replace[] = '@import "'.$url.'"'; + } + } + + // replace urls + return str_replace($search, $replace, $content); + } + + /** + * Shorthand hex color codes. + * #FF0000 -> #F00. + * + * @param string $content The CSS content to shorten the hex color codes for + * + * @return string + */ + protected function shortenHex($content) + { + $content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?=[; }])/i', '#$1$2$3', $content); + + // we can shorten some even more by replacing them with their color name + $colors = array( + '#F0FFFF' => 'azure', + '#F5F5DC' => 'beige', + '#A52A2A' => 'brown', + '#FF7F50' => 'coral', + '#FFD700' => 'gold', + '#808080' => 'gray', + '#008000' => 'green', + '#4B0082' => 'indigo', + '#FFFFF0' => 'ivory', + '#F0E68C' => 'khaki', + '#FAF0E6' => 'linen', + '#800000' => 'maroon', + '#000080' => 'navy', + '#808000' => 'olive', + '#CD853F' => 'peru', + '#FFC0CB' => 'pink', + '#DDA0DD' => 'plum', + '#800080' => 'purple', + '#F00' => 'red', + '#FA8072' => 'salmon', + '#A0522D' => 'sienna', + '#C0C0C0' => 'silver', + '#FFFAFA' => 'snow', + '#D2B48C' => 'tan', + '#FF6347' => 'tomato', + '#EE82EE' => 'violet', + '#F5DEB3' => 'wheat', + ); + + return preg_replace_callback( + '/(?<=[: ])('.implode(array_keys($colors), '|').')(?=[; }])/i', + function ($match) use ($colors) { + return $colors[strtoupper($match[0])]; + }, + $content + ); + } + + /** + * Shorten CSS font weights. + * + * @param string $content The CSS content to shorten the font weights for + * + * @return string + */ + protected function shortenFontWeights($content) + { + $weights = array( + 'normal' => 400, + 'bold' => 700, + ); + + $callback = function ($match) use ($weights) { + return $match[1].$weights[$match[2]]; + }; + + return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content); + } + + /** + * Shorthand 0 values to plain 0, instead of e.g. -0em. + * + * @param string $content The CSS content to shorten the zero values for + * + * @return string + */ + protected function shortenZeroes($content) + { + // we don't want to strip units in `calc()` expressions: + // `5px - 0px` is valid, but `5px - 0` is not + // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but + // `10 * 0` is invalid + // best to just leave `calc()`s alone, even if they could be optimized + // (which is a whole other undertaking, where units & order of + // operations all need to be considered...) + $calcs = $this->findCalcs($content); + $content = str_replace($calcs, array_keys($calcs), $content); + + // reusable bits of code throughout these regexes: + // before & after are used to make sure we don't match lose unintended + // 0-like values (e.g. in #000, or in http://url/1.0) + // units can be stripped from 0 values, or used to recognize non 0 + // values (where wa may be able to strip a .0 suffix) + $before = '(?<=[:(, ])'; + $after = '(?=[ ,);}])'; + $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)'; + + // strip units after zeroes (0px -> 0) + // NOTE: it should be safe to remove all units for a 0 value, but in + // practice, Webkit (especially Safari) seems to stumble over at least + // 0%, potentially other units as well. Only stripping 'px' for now. + // @see https://github.com/matthiasmullie/minify/issues/60 + $content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content); + + // strip 0-digits (.0 -> 0) + $content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content); + // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px + $content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content); + // strip trailing 0: 50.00 -> 50, 50.00px -> 50px + $content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content); + // strip leading 0: 0.1 -> .1, 01.1 -> 1.1 + $content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content); + + // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0) + $content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content); + + // IE doesn't seem to understand a unitless flex-basis value (correct - + // it goes against the spec), so let's add it in again (make it `%`, + // which is only 1 char: 0%, 0px, 0 anything, it's all just the same) + // @see https://developer.mozilla.org/nl/docs/Web/CSS/flex + $content = preg_replace('/flex:([0-9]+\s[0-9]+\s)0([;\}])/', 'flex:${1}0%${2}', $content); + $content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content); + + // restore `calc()` expressions + $content = str_replace(array_keys($calcs), $calcs, $content); + + return $content; + } + + /** + * Strip empty tags from source code. + * + * @param string $content + * + * @return string + */ + protected function stripEmptyTags($content) + { + $content = preg_replace('/(?<=^)[^\{\};]+\{\s*\}/', '', $content); + $content = preg_replace('/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content); + + return $content; + } + + /** + * Strip comments from source code. + */ + protected function stripComments() + { + $this->registerPattern('/\/\*.*?\*\//s', ''); + } + + /** + * Strip whitespace. + * + * @param string $content The CSS content to strip the whitespace for + * + * @return string + */ + protected function stripWhitespace($content) + { + // remove leading & trailing whitespace + $content = preg_replace('/^\s*/m', '', $content); + $content = preg_replace('/\s*$/m', '', $content); + + // replace newlines with a single space + $content = preg_replace('/\s+/', ' ', $content); + + // remove whitespace around meta characters + // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex + $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content); + $content = preg_replace('/([\[(:])\s+/', '$1', $content); + $content = preg_replace('/\s+([\]\)])/', '$1', $content); + $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content); + + // whitespace around + and - can only be stripped inside some pseudo- + // classes, like `:nth-child(3+2n)` + // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or + // selectors like `div.weird- p` + $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type'); + $content = preg_replace('/:('.implode('|', $pseudos).')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content); + + // remove semicolon/whitespace followed by closing bracket + $content = str_replace(';}', '}', $content); + + return trim($content); + } + + /** + * Find all `calc()` occurrences. + * + * @param string $content The CSS content to find `calc()`s in. + * + * @return string[] + */ + protected function findCalcs($content) + { + $results = array(); + preg_match_all('/calc(\(.+?)(?=$|;|calc\()/', $content, $matches, PREG_SET_ORDER); + + foreach ($matches as $match) { + $length = strlen($match[1]); + $expr = ''; + $opened = 0; + + for ($i = 0; $i < $length; $i++) { + $char = $match[1][$i]; + $expr .= $char; + if ($char === '(') { + $opened++; + } elseif ($char === ')' && --$opened === 0) { + break; + } + } + + $results['calc('.count($results).')'] = 'calc'.$expr; + } + + return $results; + } + + /** + * Check if file is small enough to be imported. + * + * @param string $path The path to the file + * + * @return bool + */ + protected function canImportBySize($path) + { + return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024; + } + + /** + * Check if file a file can be imported, going by the path. + * + * @param string $path + * + * @return bool + */ + protected function canImportByPath($path) + { + return preg_match('/^(data:|https?:|\\/)/', $path) === 0; + } + + /** + * Return a converter to update relative paths to be relative to the new + * destination. + * + * @param string $source + * @param string $target + * + * @return ConverterInterface + */ + protected function getPathConverter($source, $target) + { + return new Converter($source, $target); + } +} diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/src/Exception.php b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exception.php new file mode 100644 index 00000000..d03898f0 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exception.php @@ -0,0 +1,20 @@ +<?php +/** + * Base Exception + * + * @deprecated Use Exceptions\BasicException instead + * + * @author Matthias Mullie <minify@mullie.eu> + */ +namespace MatthiasMullie\Minify; + +/** + * Base Exception Class + * @deprecated Use Exceptions\BasicException instead + * + * @package Minify + * @author Matthias Mullie <minify@mullie.eu> + */ +abstract class Exception extends \Exception +{ +} diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php new file mode 100644 index 00000000..af5e81bc --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php @@ -0,0 +1,23 @@ +<?php +/** + * Basic exception + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie <minify@mullie.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify\Exceptions; + +use MatthiasMullie\Minify\Exception; + +/** + * Basic Exception Class + * + * @package Minify\Exception + * @author Matthias Mullie <minify@mullie.eu> + */ +abstract class BasicException extends Exception +{ +} diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php new file mode 100644 index 00000000..912a2c90 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php @@ -0,0 +1,21 @@ +<?php +/** + * File Import Exception + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie <minify@mullie.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify\Exceptions; + +/** + * File Import Exception Class + * + * @package Minify\Exception + * @author Matthias Mullie <minify@mullie.eu> + */ +class FileImportException extends BasicException +{ +} diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/IOException.php b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/IOException.php new file mode 100644 index 00000000..b172eb48 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/src/Exceptions/IOException.php @@ -0,0 +1,21 @@ +<?php +/** + * IO Exception + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie <minify@mullie.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify\Exceptions; + +/** + * IO Exception Class + * + * @package Minify\Exception + * @author Matthias Mullie <minify@mullie.eu> + */ +class IOException extends BasicException +{ +} diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/src/JS.php b/plugins/Customizer/vendor/matthiasmullie/minify/src/JS.php new file mode 100644 index 00000000..361afe35 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/src/JS.php @@ -0,0 +1,598 @@ +<?php +/** + * JavaScript minifier + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie <minify@mullie.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify; + +/** + * JavaScript Minifier Class + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @package Minify + * @author Matthias Mullie <minify@mullie.eu> + * @author Tijs Verkoyen <minify@verkoyen.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +class JS extends Minify +{ + /** + * Var-matching regex based on http://stackoverflow.com/a/9337047/802993. + * + * Note that regular expressions using that bit must have the PCRE_UTF8 + * pattern modifier (/u) set. + * + * @var string + */ + const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b'; + + /** + * Full list of JavaScript reserved words. + * Will be loaded from /data/js/keywords_reserved.txt. + * + * @see https://mathiasbynens.be/notes/reserved-keywords + * + * @var string[] + */ + protected $keywordsReserved = array(); + + /** + * List of JavaScript reserved words that accept a <variable, value, ...> + * after them. Some end of lines are not the end of a statement, like with + * these keywords. + * + * E.g.: we shouldn't insert a ; after this else + * else + * console.log('this is quite fine') + * + * Will be loaded from /data/js/keywords_before.txt + * + * @var string[] + */ + protected $keywordsBefore = array(); + + /** + * List of JavaScript reserved words that accept a <variable, value, ...> + * before them. Some end of lines are not the end of a statement, like when + * continued by one of these keywords on the newline. + * + * E.g.: we shouldn't insert a ; before this instanceof + * variable + * instanceof String + * + * Will be loaded from /data/js/keywords_after.txt + * + * @var string[] + */ + protected $keywordsAfter = array(); + + /** + * List of all JavaScript operators. + * + * Will be loaded from /data/js/operators.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operators = array(); + + /** + * List of JavaScript operators that accept a <variable, value, ...> after + * them. Some end of lines are not the end of a statement, like with these + * operators. + * + * Note: Most operators are fine, we've only removed ++ and --. + * ++ & -- have to be joined with the value they're in-/decrementing. + * + * Will be loaded from /data/js/operators_before.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operatorsBefore = array(); + + /** + * List of JavaScript operators that accept a <variable, value, ...> before + * them. Some end of lines are not the end of a statement, like when + * continued by one of these operators on the newline. + * + * Note: Most operators are fine, we've only removed ), ], ++, --, ! and ~. + * There can't be a newline separating ! or ~ and whatever it is negating. + * ++ & -- have to be joined with the value they're in-/decrementing. + * ) & ] are "special" in that they have lots or usecases. () for example + * is used for function calls, for grouping, in if () and for (), ... + * + * Will be loaded from /data/js/operators_after.txt + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators + * + * @var string[] + */ + protected $operatorsAfter = array(); + + /** + * {@inheritdoc} + */ + public function __construct() + { + call_user_func_array(array('parent', '__construct'), func_get_args()); + + $dataDir = __DIR__.'/../data/js/'; + $options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES; + $this->keywordsReserved = file($dataDir.'keywords_reserved.txt', $options); + $this->keywordsBefore = file($dataDir.'keywords_before.txt', $options); + $this->keywordsAfter = file($dataDir.'keywords_after.txt', $options); + $this->operators = file($dataDir.'operators.txt', $options); + $this->operatorsBefore = file($dataDir.'operators_before.txt', $options); + $this->operatorsAfter = file($dataDir.'operators_after.txt', $options); + } + + /** + * Minify the data. + * Perform JS optimizations. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + public function execute($path = null) + { + $content = ''; + + /* + * Let's first take out strings, comments and regular expressions. + * All of these can contain JS code-like characters, and we should make + * sure any further magic ignores anything inside of these. + * + * Consider this example, where we should not strip any whitespace: + * var str = "a test"; + * + * Comments will be removed altogether, strings and regular expressions + * will be replaced by placeholder text, which we'll restore later. + */ + $this->extractStrings('\'"`'); + $this->stripComments(); + $this->extractRegex(); + + // loop files + foreach ($this->data as $source => $js) { + // take out strings, comments & regex (for which we've registered + // the regexes just a few lines earlier) + $js = $this->replace($js); + + $js = $this->propertyNotation($js); + $js = $this->shortenBools($js); + $js = $this->stripWhitespace($js); + + // combine js: separating the scripts by a ; + $content .= $js.";"; + } + + // clean up leftover `;`s from the combination of multiple scripts + $content = ltrim($content, ';'); + $content = (string) substr($content, 0, -1); + + /* + * Earlier, we extracted strings & regular expressions and replaced them + * with placeholder text. This will restore them. + */ + $content = $this->restoreExtractedData($content); + + return $content; + } + + /** + * Strip comments from source code. + */ + protected function stripComments() + { + // single-line comments + $this->registerPattern('/\/\/.*$/m', ''); + + // multi-line comments + $this->registerPattern('/\/\*.*?\*\//s', ''); + } + + /** + * JS can have /-delimited regular expressions, like: /ab+c/.match(string). + * + * The content inside the regex can contain characters that may be confused + * for JS code: e.g. it could contain whitespace it needs to match & we + * don't want to strip whitespace in there. + * + * The regex can be pretty simple: we don't have to care about comments, + * (which also use slashes) because stripComments() will have stripped those + * already. + * + * This method will replace all string content with simple REGEX# + * placeholder text, so we've rid all regular expressions from characters + * that may be misinterpreted. Original regex content will be saved in + * $this->extracted and after doing all other minifying, we can restore the + * original content via restoreRegex() + */ + protected function extractRegex() + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier) { + $count = count($minifier->extracted); + $placeholder = '"'.$count.'"'; + $minifier->extracted[$placeholder] = $match[0]; + + return $placeholder; + }; + + // match all chars except `/` and `\` + // `\` is allowed though, along with whatever char follows (which is the + // one being escaped) + // this should allow all chars, except for an unescaped `/` (= the one + // closing the regex) + // then also ignore bare `/` inside `[]`, where they don't need to be + // escaped: anything inside `[]` can be ignored safely + $pattern = '\\/(?:[^\\[\\/\\\\\n\r]+|(?:\\\\.)+|(?:\\[(?:[^\\]\\\\\n\r]+|(?:\\\\.)+)+\\])+)++\\/[gimuy]*'; + + // a regular expression can only be followed by a few operators or some + // of the RegExp methods (a `\` followed by a variable or value is + // likely part of a division, not a regex) + $keywords = array('do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof'); + $before = '([=:,;\+\-\*\/\}\(\{\[&\|!]|^|'.implode('|', $keywords).')\s*'; + $propertiesAndMethods = array( + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2 + 'constructor', + 'flags', + 'global', + 'ignoreCase', + 'multiline', + 'source', + 'sticky', + 'unicode', + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2 + 'compile(', + 'exec(', + 'test(', + 'toSource(', + 'toString(', + ); + $delimiters = array_fill(0, count($propertiesAndMethods), '/'); + $propertiesAndMethods = array_map('preg_quote', $propertiesAndMethods, $delimiters); + $after = '(?=\s*([\.,;\)\}&\|+]|\/\/|$|\.('.implode('|', $propertiesAndMethods).')))'; + $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback); + + // regular expressions following a `)` are rather annoying to detect... + // quite often, `/` after `)` is a division operator & if it happens to + // be followed by another one (or a comment), it is likely to be + // confused for a regular expression + // however, it's perfectly possible for a regex to follow a `)`: after + // a single-line `if()`, `while()`, ... statement, for example + // since, when they occur like that, they're always the start of a + // statement, there's only a limited amount of ways they can be useful: + // by calling the regex methods directly + // if a regex following `)` is not followed by `.<property or method>`, + // it's quite likely not a regex + $before = '\)\s*'; + $after = '(?=\s*\.('.implode('|', $propertiesAndMethods).'))'; + $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback); + + // 1 more edge case: a regex can be followed by a lot more operators or + // keywords if there's a newline (ASI) in between, where the operator + // actually starts a new statement + // (https://github.com/matthiasmullie/minify/issues/56) + $operators = $this->getOperatorsForRegex($this->operatorsBefore, '/'); + $operators += $this->getOperatorsForRegex($this->keywordsReserved, '/'); + $after = '(?=\s*\n\s*('.implode('|', $operators).'))'; + $this->registerPattern('/'.$pattern.$after.'/', $callback); + } + + /** + * Strip whitespace. + * + * We won't strip *all* whitespace, but as much as possible. The thing that + * we'll preserve are newlines we're unsure about. + * JavaScript doesn't require statements to be terminated with a semicolon. + * It will automatically fix missing semicolons with ASI (automatic semi- + * colon insertion) at the end of line causing errors (without semicolon.) + * + * Because it's sometimes hard to tell if a newline is part of a statement + * that should be terminated or not, we'll just leave some of them alone. + * + * @param string $content The content to strip the whitespace for + * + * @return string + */ + protected function stripWhitespace($content) + { + // uniform line endings, make them all line feed + $content = str_replace(array("\r\n", "\r"), "\n", $content); + + // collapse all non-line feed whitespace into a single space + $content = preg_replace('/[^\S\n]+/', ' ', $content); + + // strip leading & trailing whitespace + $content = str_replace(array(" \n", "\n "), "\n", $content); + + // collapse consecutive line feeds into just 1 + $content = preg_replace('/\n+/', "\n", $content); + + $operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/'); + $operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/'); + $operators = $this->getOperatorsForRegex($this->operators, '/'); + $keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/'); + $keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/'); + + // strip whitespace that ends in (or next line begin with) an operator + // that allows statements to be broken up over multiple lines + unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']); + $content = preg_replace( + array( + '/('.implode('|', $operatorsBefore).')\s+/', + '/\s+('.implode('|', $operatorsAfter).')/', + ), '\\1', $content + ); + + // make sure + and - can't be mistaken for, or joined into ++ and -- + $content = preg_replace( + array( + '/(?<![\+\-])\s*([\+\-])(?![\+\-])/', + '/(?<![\+\-])([\+\-])\s*(?![\+\-])/', + ), '\\1', $content + ); + + // collapse whitespace around reserved words into single space + $content = preg_replace('/(^|[;\}\s])\K('.implode('|', $keywordsBefore).')\s+/', '\\2 ', $content); + $content = preg_replace('/\s+('.implode('|', $keywordsAfter).')(?=([;\{\s]|$))/', ' \\1', $content); + + /* + * We didn't strip whitespace after a couple of operators because they + * could be used in different contexts and we can't be sure it's ok to + * strip the newlines. However, we can safely strip any non-line feed + * whitespace that follows them. + */ + $operatorsDiffBefore = array_diff($operators, $operatorsBefore); + $operatorsDiffAfter = array_diff($operators, $operatorsAfter); + $content = preg_replace('/('.implode('|', $operatorsDiffBefore).')[^\S\n]+/', '\\1', $content); + $content = preg_replace('/[^\S\n]+('.implode('|', $operatorsDiffAfter).')/', '\\1', $content); + + /* + * Whitespace after `return` can be omitted in a few occasions + * (such as when followed by a string or regex) + * Same for whitespace in between `)` and `{`, or between `{` and some + * keywords. + */ + $content = preg_replace('/\breturn\s+(["\'\/\+\-])/', 'return$1', $content); + $content = preg_replace('/\)\s+\{/', '){', $content); + $content = preg_replace('/}\n(else|catch|finally)\b/', '}$1', $content); + + /* + * Get rid of double semicolons, except where they can be used like: + * "for(v=1,_=b;;)", "for(v=1;;v++)" or "for(;;ja||(ja=true))". + * I'll safeguard these double semicolons inside for-loops by + * temporarily replacing them with an invalid condition: they won't have + * a double semicolon and will be easy to spot to restore afterwards. + */ + $content = preg_replace('/\bfor\(([^;]*);;([^;]*)\)/', 'for(\\1;-;\\2)', $content); + $content = preg_replace('/;+/', ';', $content); + $content = preg_replace('/\bfor\(([^;]*);-;([^;]*)\)/', 'for(\\1;;\\2)', $content); + + /* + * Next, we'll be removing all semicolons where ASI kicks in. + * for-loops however, can have an empty body (ending in only a + * semicolon), like: `for(i=1;i<3;i++);`, of `for(i in list);` + * Here, nothing happens during the loop; it's just used to keep + * increasing `i`. With that ; omitted, the next line would be expected + * to be the for-loop's body... Same goes for while loops. + * I'm going to double that semicolon (if any) so after the next line, + * which strips semicolons here & there, we're still left with this one. + */ + $content = preg_replace('/(for\([^;\{]*;[^;\{]*;[^;\{]*\));(\}|$)/s', '\\1;;\\2', $content); + $content = preg_replace('/(for\([^;\{]+\s+in\s+[^;\{]+\));(\}|$)/s', '\\1;;\\2', $content); + /* + * Below will also keep `;` after a `do{}while();` along with `while();` + * While these could be stripped after do-while, detecting this + * distinction is cumbersome, so I'll play it safe and make sure `;` + * after any kind of `while` is kept. + */ + $content = preg_replace('/(while\([^;\{]+\));(\}|$)/s', '\\1;;\\2', $content); + + /* + * We also can't strip empty else-statements. Even though they're + * useless and probably shouldn't be in the code in the first place, we + * shouldn't be stripping the `;` that follows it as it breaks the code. + * We can just remove those useless else-statements completely. + * + * @see https://github.com/matthiasmullie/minify/issues/91 + */ + $content = preg_replace('/else;/s', '', $content); + + /* + * We also don't really want to terminate statements followed by closing + * curly braces (which we've ignored completely up until now) or end-of- + * script: ASI will kick in here & we're all about minifying. + * Semicolons at beginning of the file don't make any sense either. + */ + $content = preg_replace('/;(\}|$)/s', '\\1', $content); + $content = ltrim($content, ';'); + + // get rid of remaining whitespace af beginning/end + return trim($content); + } + + /** + * We'll strip whitespace around certain operators with regular expressions. + * This will prepare the given array by escaping all characters. + * + * @param string[] $operators + * @param string $delimiter + * + * @return string[] + */ + protected function getOperatorsForRegex(array $operators, $delimiter = '/') + { + // escape operators for use in regex + $delimiters = array_fill(0, count($operators), $delimiter); + $escaped = array_map('preg_quote', $operators, $delimiters); + + $operators = array_combine($operators, $escaped); + + // ignore + & - for now, they'll get special treatment + unset($operators['+'], $operators['-']); + + // dot can not just immediately follow a number; it can be confused for + // decimal point, or calling a method on it, e.g. 42 .toString() + $operators['.'] = '(?<![0-9]\s)\.'; + + // don't confuse = with other assignment shortcuts (e.g. +=) + $chars = preg_quote('+-*\=<>%&|', $delimiter); + $operators['='] = '(?<!['.$chars.'])\='; + + return $operators; + } + + /** + * We'll strip whitespace around certain keywords with regular expressions. + * This will prepare the given array by escaping all characters. + * + * @param string[] $keywords + * @param string $delimiter + * + * @return string[] + */ + protected function getKeywordsForRegex(array $keywords, $delimiter = '/') + { + // escape keywords for use in regex + $delimiter = array_fill(0, count($keywords), $delimiter); + $escaped = array_map('preg_quote', $keywords, $delimiter); + + // add word boundaries + array_walk($keywords, function ($value) { + return '\b'.$value.'\b'; + }); + + $keywords = array_combine($keywords, $escaped); + + return $keywords; + } + + /** + * Replaces all occurrences of array['key'] by array.key. + * + * @param string $content + * + * @return string + */ + protected function propertyNotation($content) + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $keywords = $this->keywordsReserved; + $callback = function ($match) use ($minifier, $keywords) { + $property = trim($minifier->extracted[$match[1]], '\'"'); + + /* + * Check if the property is a reserved keyword. In this context (as + * property of an object literal/array) it shouldn't matter, but IE8 + * freaks out with "Expected identifier". + */ + if (in_array($property, $keywords)) { + return $match[0]; + } + + /* + * See if the property is in a variable-like format (e.g. + * array['key-here'] can't be replaced by array.key-here since '-' + * is not a valid character there. + */ + if (!preg_match('/^'.$minifier::REGEX_VARIABLE.'$/u', $property)) { + return $match[0]; + } + + return '.'.$property; + }; + + /* + * Figure out if previous character is a variable name (of the array + * we want to use property notation on) - this is to make sure + * standalone ['value'] arrays aren't confused for keys-of-an-array. + * We can (and only have to) check the last character, because PHP's + * regex implementation doesn't allow unfixed-length look-behind + * assertions. + */ + preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar); + $previousChar = $previousChar[1]; + + /* + * Make sure word preceding the ['value'] is not a keyword, e.g. + * return['x']. Because -again- PHP's regex implementation doesn't allow + * unfixed-length look-behind assertions, I'm just going to do a lot of + * separate look-behind assertions, one for each keyword. + */ + $keywords = $this->getKeywordsForRegex($keywords); + $keywords = '(?<!'.implode(')(?<!', $keywords).')'; + + return preg_replace_callback('/(?<='.$previousChar.'|\])'.$keywords.'\[\s*(([\'"])[0-9]+\\2)\s*\]/u', $callback, $content); + } + + /** + * Replaces true & false by !0 and !1. + * + * @param string $content + * + * @return string + */ + protected function shortenBools($content) + { + /* + * 'true' or 'false' could be used as property names (which may be + * followed by whitespace) - we must not replace those! + * Since PHP doesn't allow variable-length (to account for the + * whitespace) lookbehind assertions, I need to capture the leading + * character and check if it's a `.` + */ + $callback = function ($match) { + if (trim($match[1]) === '.') { + return $match[0]; + } + + return $match[1].($match[2] === 'true' ? '!0' : '!1'); + }; + $content = preg_replace_callback('/(^|.\s*)\b(true|false)\b(?!:)/', $callback, $content); + + // for(;;) is exactly the same as while(true), but shorter :) + $content = preg_replace('/\bwhile\(!0\){/', 'for(;;){', $content); + + // now make sure we didn't turn any do ... while(true) into do ... for(;;) + preg_match_all('/\bdo\b/', $content, $dos, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + // go backward to make sure positional offsets aren't altered when $content changes + $dos = array_reverse($dos); + foreach ($dos as $do) { + $offsetDo = $do[0][1]; + + // find all `while` (now `for`) following `do`: one of those must be + // associated with the `do` and be turned back into `while` + preg_match_all('/\bfor\(;;\)/', $content, $whiles, PREG_OFFSET_CAPTURE | PREG_SET_ORDER, $offsetDo); + foreach ($whiles as $while) { + $offsetWhile = $while[0][1]; + + $open = substr_count($content, '{', $offsetDo, $offsetWhile - $offsetDo); + $close = substr_count($content, '}', $offsetDo, $offsetWhile - $offsetDo); + if ($open === $close) { + // only restore `while` if amount of `{` and `}` are the same; + // otherwise, that `for` isn't associated with this `do` + $content = substr_replace($content, 'while(!0)', $offsetWhile, strlen('for(;;)')); + break; + } + } + } + + return $content; + } +} diff --git a/plugins/Customizer/vendor/matthiasmullie/minify/src/Minify.php b/plugins/Customizer/vendor/matthiasmullie/minify/src/Minify.php new file mode 100644 index 00000000..bfbc1ad2 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/minify/src/Minify.php @@ -0,0 +1,459 @@ +<?php +/** + * Abstract minifier class + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @author Matthias Mullie <minify@mullie.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +namespace MatthiasMullie\Minify; + +use MatthiasMullie\Minify\Exceptions\IOException; +use Psr\Cache\CacheItemInterface; + +/** + * Abstract minifier class. + * + * Please report bugs on https://github.com/matthiasmullie/minify/issues + * + * @package Minify + * @author Matthias Mullie <minify@mullie.eu> + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ +abstract class Minify +{ + /** + * The data to be minified. + * + * @var string[] + */ + protected $data = array(); + + /** + * Array of patterns to match. + * + * @var string[] + */ + protected $patterns = array(); + + /** + * This array will hold content of strings and regular expressions that have + * been extracted from the JS source code, so we can reliably match "code", + * without having to worry about potential "code-like" characters inside. + * + * @var string[] + */ + public $extracted = array(); + + /** + * Init the minify class - optionally, code may be passed along already. + */ + public function __construct(/* $data = null, ... */) + { + // it's possible to add the source through the constructor as well ;) + if (func_num_args()) { + call_user_func_array(array($this, 'add'), func_get_args()); + } + } + + /** + * Add a file or straight-up code to be minified. + * + * @param string|string[] $data + * + * @return static + */ + public function add($data /* $data = null, ... */) + { + // bogus "usage" of parameter $data: scrutinizer warns this variable is + // not used (we're using func_get_args instead to support overloading), + // but it still needs to be defined because it makes no sense to have + // this function without argument :) + $args = array($data) + func_get_args(); + + // this method can be overloaded + foreach ($args as $data) { + if (is_array($data)) { + call_user_func_array(array($this, 'add'), $data); + continue; + } + + // redefine var + $data = (string) $data; + + // load data + $value = $this->load($data); + $key = ($data != $value) ? $data : count($this->data); + + // replace CR linefeeds etc. + // @see https://github.com/matthiasmullie/minify/pull/139 + $value = str_replace(array("\r\n", "\r"), "\n", $value); + + // store data + $this->data[$key] = $value; + } + + return $this; + } + + /** + * Minify the data & (optionally) saves it to a file. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + public function minify($path = null) + { + $content = $this->execute($path); + + // save to path + if ($path !== null) { + $this->save($content, $path); + } + + return $content; + } + + /** + * Minify & gzip the data & (optionally) saves it to a file. + * + * @param string[optional] $path Path to write the data to + * @param int[optional] $level Compression level, from 0 to 9 + * + * @return string The minified & gzipped data + */ + public function gzip($path = null, $level = 9) + { + $content = $this->execute($path); + $content = gzencode($content, $level, FORCE_GZIP); + + // save to path + if ($path !== null) { + $this->save($content, $path); + } + + return $content; + } + + /** + * Minify the data & write it to a CacheItemInterface object. + * + * @param CacheItemInterface $item Cache item to write the data to + * + * @return CacheItemInterface Cache item with the minifier data + */ + public function cache(CacheItemInterface $item) + { + $content = $this->execute(); + $item->set($content); + + return $item; + } + + /** + * Minify the data. + * + * @param string[optional] $path Path to write the data to + * + * @return string The minified data + */ + abstract public function execute($path = null); + + /** + * Load data. + * + * @param string $data Either a path to a file or the content itself + * + * @return string + */ + protected function load($data) + { + // check if the data is a file + if ($this->canImportFile($data)) { + $data = file_get_contents($data); + + // strip BOM, if any + if (substr($data, 0, 3) == "\xef\xbb\xbf") { + $data = substr($data, 3); + } + } + + return $data; + } + + /** + * Save to file. + * + * @param string $content The minified data + * @param string $path The path to save the minified data to + * + * @throws IOException + */ + protected function save($content, $path) + { + $handler = $this->openFileForWriting($path); + + $this->writeToFile($handler, $content); + + @fclose($handler); + } + + /** + * Register a pattern to execute against the source content. + * + * @param string $pattern PCRE pattern + * @param string|callable $replacement Replacement value for matched pattern + */ + protected function registerPattern($pattern, $replacement = '') + { + // study the pattern, we'll execute it more than once + $pattern .= 'S'; + + $this->patterns[] = array($pattern, $replacement); + } + + /** + * We can't "just" run some regular expressions against JavaScript: it's a + * complex language. E.g. having an occurrence of // xyz would be a comment, + * unless it's used within a string. Of you could have something that looks + * like a 'string', but inside a comment. + * The only way to accurately replace these pieces is to traverse the JS one + * character at a time and try to find whatever starts first. + * + * @param string $content The content to replace patterns in + * + * @return string The (manipulated) content + */ + protected function replace($content) + { + $processed = ''; + $positions = array_fill(0, count($this->patterns), -1); + $matches = array(); + + while ($content) { + // find first match for all patterns + foreach ($this->patterns as $i => $pattern) { + list($pattern, $replacement) = $pattern; + + // we can safely ignore patterns for positions we've unset earlier, + // because we know these won't show up anymore + if (!isset($positions[$i])) { + continue; + } + + // no need to re-run matches that are still in the part of the + // content that hasn't been processed + if ($positions[$i] >= 0) { + continue; + } + + $match = null; + if (preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE)) { + $matches[$i] = $match; + + // we'll store the match position as well; that way, we + // don't have to redo all preg_matches after changing only + // the first (we'll still know where those others are) + $positions[$i] = $match[0][1]; + } else { + // if the pattern couldn't be matched, there's no point in + // executing it again in later runs on this same content; + // ignore this one until we reach end of content + unset($matches[$i], $positions[$i]); + } + } + + // no more matches to find: everything's been processed, break out + if (!$matches) { + $processed .= $content; + break; + } + + // see which of the patterns actually found the first thing (we'll + // only want to execute that one, since we're unsure if what the + // other found was not inside what the first found) + $discardLength = min($positions); + $firstPattern = array_search($discardLength, $positions); + $match = $matches[$firstPattern][0][0]; + + // execute the pattern that matches earliest in the content string + list($pattern, $replacement) = $this->patterns[$firstPattern]; + $replacement = $this->replacePattern($pattern, $replacement, $content); + + // figure out which part of the string was unmatched; that's the + // part we'll execute the patterns on again next + $content = (string) substr($content, $discardLength); + $unmatched = (string) substr($content, strpos($content, $match) + strlen($match)); + + // move the replaced part to $processed and prepare $content to + // again match batch of patterns against + $processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched)); + $content = $unmatched; + + // first match has been replaced & that content is to be left alone, + // the next matches will start after this replacement, so we should + // fix their offsets + foreach ($positions as $i => $position) { + $positions[$i] -= $discardLength + strlen($match); + } + } + + return $processed; + } + + /** + * This is where a pattern is matched against $content and the matches + * are replaced by their respective value. + * This function will be called plenty of times, where $content will always + * move up 1 character. + * + * @param string $pattern Pattern to match + * @param string|callable $replacement Replacement value + * @param string $content Content to match pattern against + * + * @return string + */ + protected function replacePattern($pattern, $replacement, $content) + { + if (is_callable($replacement)) { + return preg_replace_callback($pattern, $replacement, $content, 1, $count); + } else { + return preg_replace($pattern, $replacement, $content, 1, $count); + } + } + + /** + * Strings are a pattern we need to match, in order to ignore potential + * code-like content inside them, but we just want all of the string + * content to remain untouched. + * + * This method will replace all string content with simple STRING# + * placeholder text, so we've rid all strings from characters that may be + * misinterpreted. Original string content will be saved in $this->extracted + * and after doing all other minifying, we can restore the original content + * via restoreStrings(). + * + * @param string[optional] $chars + * @param string[optional] $placeholderPrefix + */ + protected function extractStrings($chars = '\'"', $placeholderPrefix = '') + { + // PHP only supports $this inside anonymous functions since 5.4 + $minifier = $this; + $callback = function ($match) use ($minifier, $placeholderPrefix) { + // check the second index here, because the first always contains a quote + if ($match[2] === '') { + /* + * Empty strings need no placeholder; they can't be confused for + * anything else anyway. + * But we still needed to match them, for the extraction routine + * to skip over this particular string. + */ + return $match[0]; + } + + $count = count($minifier->extracted); + $placeholder = $match[1].$placeholderPrefix.$count.$match[1]; + $minifier->extracted[$placeholder] = $match[1].$match[2].$match[1]; + + return $placeholder; + }; + + /* + * The \\ messiness explained: + * * Don't count ' or " as end-of-string if it's escaped (has backslash + * in front of it) + * * Unless... that backslash itself is escaped (another leading slash), + * in which case it's no longer escaping the ' or " + * * So there can be either no backslash, or an even number + * * multiply all of that times 4, to account for the escaping that has + * to be done to pass the backslash into the PHP string without it being + * considered as escape-char (times 2) and to get it in the regex, + * escaped (times 2) + */ + $this->registerPattern('/(['.$chars.'])(.*?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback); + } + + /** + * This method will restore all extracted data (strings, regexes) that were + * replaced with placeholder text in extract*(). The original content was + * saved in $this->extracted. + * + * @param string $content + * + * @return string + */ + protected function restoreExtractedData($content) + { + if (!$this->extracted) { + // nothing was extracted, nothing to restore + return $content; + } + + $content = strtr($content, $this->extracted); + + $this->extracted = array(); + + return $content; + } + + /** + * Check if the path is a regular file and can be read. + * + * @param string $path + * + * @return bool + */ + protected function canImportFile($path) + { + $parsed = parse_url($path); + if ( + // file is elsewhere + isset($parsed['host']) || + // file responds to queries (may change, or need to bypass cache) + isset($parsed['query']) + ) { + return false; + } + + return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path); + } + + /** + * Attempts to open file specified by $path for writing. + * + * @param string $path The path to the file + * + * @return resource Specifier for the target file + * + * @throws IOException + */ + protected function openFileForWriting($path) + { + if (($handler = @fopen($path, 'w')) === false) { + throw new IOException('The file "'.$path.'" could not be opened for writing. Check if PHP has enough permissions.'); + } + + return $handler; + } + + /** + * Attempts to write $content to the file specified by $handler. $path is used for printing exceptions. + * + * @param resource $handler The resource to write to + * @param string $content The content to write + * @param string $path The path to the file (for exception printing only) + * + * @throws IOException + */ + protected function writeToFile($handler, $content, $path = '') + { + if (($result = @fwrite($handler, $content)) === false || ($result < strlen($content))) { + throw new IOException('The file "'.$path.'" could not be written to. Check your disk space and file permissions.'); + } + } +} diff --git a/plugins/Customizer/vendor/matthiasmullie/path-converter/LICENSE b/plugins/Customizer/vendor/matthiasmullie/path-converter/LICENSE new file mode 100644 index 00000000..491295ad --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/path-converter/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2015 Matthias Mullie + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/Customizer/vendor/matthiasmullie/path-converter/composer.json b/plugins/Customizer/vendor/matthiasmullie/path-converter/composer.json new file mode 100644 index 00000000..1cb6a4c5 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/path-converter/composer.json @@ -0,0 +1,28 @@ +{ + "name": "matthiasmullie/path-converter", + "type": "library", + "description": "Relative path converter", + "keywords": ["relative", "path", "converter", "paths"], + "homepage": "http://github.com/matthiasmullie/path-converter", + "license": "MIT", + "authors": [ + { + "name": "Matthias Mullie", + "homepage": "http://www.mullie.eu", + "email": "pathconverter@mullie.eu", + "role": "Developer" + } + ], + "require": { + "php": ">=5.3.0", + "ext-pcre": "*" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "autoload": { + "psr-4": { + "MatthiasMullie\\PathConverter\\": "src/" + } + } +} diff --git a/plugins/Customizer/vendor/matthiasmullie/path-converter/src/Converter.php b/plugins/Customizer/vendor/matthiasmullie/path-converter/src/Converter.php new file mode 100644 index 00000000..519d3c84 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/path-converter/src/Converter.php @@ -0,0 +1,196 @@ +<?php + +namespace MatthiasMullie\PathConverter; + +/** + * Convert paths relative from 1 file to another. + * + * E.g. + * ../../images/icon.jpg relative to /css/imports/icons.css + * becomes + * ../images/icon.jpg relative to /css/minified.css + * + * Please report bugs on https://github.com/matthiasmullie/path-converter/issues + * + * @author Matthias Mullie <pathconverter@mullie.eu> + * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved + * @license MIT License + */ +class Converter implements ConverterInterface +{ + /** + * @var string + */ + protected $from; + + /** + * @var string + */ + protected $to; + + /** + * @param string $from The original base path (directory, not file!) + * @param string $to The new base path (directory, not file!) + * @param string $root Root directory (defaults to `getcwd`) + */ + public function __construct($from, $to, $root = '') + { + $shared = $this->shared($from, $to); + if ($shared === '') { + // when both paths have nothing in common, one of them is probably + // absolute while the other is relative + $root = $root ?: getcwd(); + $from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root.'/'.$from); + $to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root.'/'.$to); + + // or traveling the tree via `..` + // attempt to resolve path, or assume it's fine if it doesn't exist + $from = @realpath($from) ?: $from; + $to = @realpath($to) ?: $to; + } + + $from = $this->dirname($from); + $to = $this->dirname($to); + + $from = $this->normalize($from); + $to = $this->normalize($to); + + $this->from = $from; + $this->to = $to; + } + + /** + * Normalize path. + * + * @param string $path + * + * @return string + */ + protected function normalize($path) + { + // deal with different operating systems' directory structure + $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/'); + + /* + * Example: + * /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif + * to + * /home/forkcms/frontend/core/layout/images/img.gif + */ + do { + $path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count); + } while ($count); + + return $path; + } + + /** + * Figure out the shared path of 2 locations. + * + * Example: + * /home/forkcms/frontend/core/layout/images/img.gif + * and + * /home/forkcms/frontend/cache/minified_css + * share + * /home/forkcms/frontend + * + * @param string $path1 + * @param string $path2 + * + * @return string + */ + protected function shared($path1, $path2) + { + // $path could theoretically be empty (e.g. no path is given), in which + // case it shouldn't expand to array(''), which would compare to one's + // root / + $path1 = $path1 ? explode('/', $path1) : array(); + $path2 = $path2 ? explode('/', $path2) : array(); + + $shared = array(); + + // compare paths & strip identical ancestors + foreach ($path1 as $i => $chunk) { + if (isset($path2[$i]) && $path1[$i] == $path2[$i]) { + $shared[] = $chunk; + } else { + break; + } + } + + return implode('/', $shared); + } + + /** + * Convert paths relative from 1 file to another. + * + * E.g. + * ../images/img.gif relative to /home/forkcms/frontend/core/layout/css + * should become: + * ../../core/layout/images/img.gif relative to + * /home/forkcms/frontend/cache/minified_css + * + * @param string $path The relative path that needs to be converted + * + * @return string The new relative path + */ + public function convert($path) + { + // quit early if conversion makes no sense + if ($this->from === $this->to) { + return $path; + } + + $path = $this->normalize($path); + // if we're not dealing with a relative path, just return absolute + if (strpos($path, '/') === 0) { + return $path; + } + + // normalize paths + $path = $this->normalize($this->from.'/'.$path); + + // strip shared ancestor paths + $shared = $this->shared($path, $this->to); + $path = mb_substr($path, mb_strlen($shared)); + $to = mb_substr($this->to, mb_strlen($shared)); + + // add .. for every directory that needs to be traversed to new path + $to = str_repeat('../', count(array_filter(explode('/', $to)))); + + return $to.ltrim($path, '/'); + } + + /** + * Attempt to get the directory name from a path. + * + * @param string $path + * + * @return string + */ + protected function dirname($path) + { + if (@is_file($path)) { + return dirname($path); + } + + if (@is_dir($path)) { + return rtrim($path, '/'); + } + + // no known file/dir, start making assumptions + + // ends in / = dir + if (mb_substr($path, -1) === '/') { + return rtrim($path, '/'); + } + + // has a dot in the name, likely a file + if (preg_match('/.*\..*$/', basename($path)) !== 0) { + return dirname($path); + } + + // you're on your own here! + return $path; + } +} diff --git a/plugins/Customizer/vendor/matthiasmullie/path-converter/src/ConverterInterface.php b/plugins/Customizer/vendor/matthiasmullie/path-converter/src/ConverterInterface.php new file mode 100644 index 00000000..dc1b7657 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/path-converter/src/ConverterInterface.php @@ -0,0 +1,24 @@ +<?php + +namespace MatthiasMullie\PathConverter; + +/** + * Convert file paths. + * + * Please report bugs on https://github.com/matthiasmullie/path-converter/issues + * + * @author Matthias Mullie <pathconverter@mullie.eu> + * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved + * @license MIT License + */ +interface ConverterInterface +{ + /** + * Convert file paths. + * + * @param string $path The path to be converted + * + * @return string The new path + */ + public function convert($path); +} diff --git a/plugins/Customizer/vendor/matthiasmullie/path-converter/src/NoConverter.php b/plugins/Customizer/vendor/matthiasmullie/path-converter/src/NoConverter.php new file mode 100644 index 00000000..2fcfd0f2 --- /dev/null +++ b/plugins/Customizer/vendor/matthiasmullie/path-converter/src/NoConverter.php @@ -0,0 +1,23 @@ +<?php + +namespace MatthiasMullie\PathConverter; + +/** + * Don't convert paths. + * + * Please report bugs on https://github.com/matthiasmullie/path-converter/issues + * + * @author Matthias Mullie <pathconverter@mullie.eu> + * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved + * @license MIT License + */ +class NoConverter implements ConverterInterface +{ + /** + * {@inheritdoc} + */ + public function convert($path) + { + return $path; + } +} |