Improvements
- Use the same CSS and files as SSOwat - Introduce locale - Add support for community apps
960
assets/css/ynh-style.css
Normal file
737
assets/css/ynhpanel.css
Normal file
BIN
assets/fonts/sourcesanspro-bold-webfont.eot
Executable file
BIN
assets/fonts/sourcesanspro-bold-webfont.ttf
Executable file
BIN
assets/fonts/sourcesanspro-regular-webfont.eot
Executable file
BIN
assets/fonts/sourcesanspro-regular-webfont.ttf
Executable file
BIN
assets/fonts/ynh_ssowat/ynh_ssowat.eot
Normal file
17
assets/fonts/ynh_ssowat/ynh_ssowat.svg
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<metadata>Copyright (C) 2014 by original authors @ fontello.com</metadata>
|
||||||
|
<defs>
|
||||||
|
<font id="ynh_ssowat" horiz-adv-x="1000" >
|
||||||
|
<font-face font-family="ynh_ssowat" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
|
||||||
|
<missing-glyph horiz-adv-x="1000" />
|
||||||
|
<glyph glyph-name="user" unicode="" d="m786 66q0-67-41-106t-108-39h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q5 0 24-12t41-27 60-27 75-12 74 12 61 27 41 27 24 12q34 0 62-11t48-30 34-45 24-55 15-60 8-61 2-58z m-179 498q0-88-63-151t-151-63-152 63-62 151 62 152 152 63 151-63 63-152z" horiz-adv-x="785.7" />
|
||||||
|
<glyph glyph-name="lock" unicode="" d="m179 421h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />
|
||||||
|
<glyph glyph-name="pencil" unicode="" d="m203-7l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
|
||||||
|
<glyph glyph-name="trash" unicode="" d="m286 439v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m143 0v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m142 0v-321q0-8-5-13t-12-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q7 0 12-5t5-13z m72-404v529h-500v-529q0-12 4-22t8-15 6-5h464q2 0 6 5t8 15 4 22z m-375 601h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m518-18v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q22 0 44-15t30-35l39-93h173q8 0 13-5t5-13z" horiz-adv-x="785.7" />
|
||||||
|
<glyph glyph-name="angle-left" unicode="" d="m350 546q0-7-6-12l-219-220 219-219q6-6 6-13t-6-13l-28-28q-5-5-12-5t-13 5l-260 260q-6 6-6 13t6 13l260 260q5 6 13 6t12-6l28-28q6-5 6-13z" horiz-adv-x="357.1" />
|
||||||
|
<glyph glyph-name="off" unicode="" d="m857 350q0-87-34-166t-91-137-137-92-166-34-167 34-136 92-92 137-34 166q0 102 45 191t126 151q24 18 54 14t46-28q18-23 14-53t-28-47q-54-41-84-101t-30-127q0-58 22-111t62-91 91-61 111-23 110 23 92 61 61 91 22 111q0 68-30 127t-84 101q-24 18-28 47t14 53q17 24 47 28t53-14q81-61 126-151t45-191z m-357 429v-358q0-29-21-50t-50-21-51 21-21 50v358q0 29 21 50t51 21 50-21 21-50z" horiz-adv-x="857.1" />
|
||||||
|
</font>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
BIN
assets/fonts/ynh_ssowat/ynh_ssowat.ttf
Normal file
BIN
assets/fonts/ynh_ssowat/ynh_ssowat.woff
Normal file
BIN
assets/icons/apple-touch-icon-114x114.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
assets/icons/apple-touch-icon-120x120.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
assets/icons/apple-touch-icon-144x144.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
assets/icons/apple-touch-icon-152x152.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
assets/icons/apple-touch-icon-57x57.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
assets/icons/apple-touch-icon-60x60.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
assets/icons/apple-touch-icon-72x72.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
assets/icons/apple-touch-icon-76x76.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/icons/apple-touch-icon-precomposed.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
assets/icons/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
12
assets/icons/browserconfig.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square70x70logo src="/mstile-70x70.png"/>
|
||||||
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
|
<square310x310logo src="/mstile-310x310.png"/>
|
||||||
|
<wide310x150logo src="/mstile-310x150.png"/>
|
||||||
|
<TileColor>#da532c</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
BIN
assets/icons/favicon-160x160.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
assets/icons/favicon-16x16.png
Normal file
After Width: | Height: | Size: 578 B |
BIN
assets/icons/favicon-196x196.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
assets/icons/favicon-32x32.png
Normal file
After Width: | Height: | Size: 905 B |
BIN
assets/icons/favicon-96x96.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
assets/icons/favicon.ico
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
assets/icons/mstile-144x144.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
assets/icons/mstile-150x150.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
assets/icons/mstile-310x150.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
assets/icons/mstile-310x310.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
assets/icons/mstile-70x70.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
38
assets/img/logo-ynh.svg
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
|
||||||
|
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
|
||||||
|
]>
|
||||||
|
<svg version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
|
||||||
|
x="0px" y="0px" width="98px" height="85px" viewBox="0 0 98 85" overflow="visible" enable-background="new 0 0 98 85"
|
||||||
|
xml:space="preserve">
|
||||||
|
<defs>
|
||||||
|
</defs>
|
||||||
|
<g id="XMLID_2_">
|
||||||
|
<g>
|
||||||
|
<path d="M97.25,51.25c-2.02,4.98-8.33,5.67-14,7c-0.61,6.29,3.05,10.95-1,16c-6.41-0.26-7.47-5.86-7-13c-1,0-2,0-3,0
|
||||||
|
c-2.09,2.77,0.9,4.52,0,8c-1.12,4.34-7.88,7.91-11,7c-2.18-0.64-5.96-6.63-5-12c2.82-2.71,2.76,3.12,6,3c5.05-7.84-9.63-8.55-8-17
|
||||||
|
c1.24-6.42,11.66-9.66,15-1c1.54,4.21-5.17,0.16-5,3c-0.28,1.62,0.95,1.72,1,3c2.52,0.77,1.68-2.16,3-3c1.86-1.17,3.09-0.75,6-1
|
||||||
|
c2.45-2.55,1.08-8.92,4-11c3.87,0.46,6.08,2.59,6,7C91.26,46.36,94.55,46.3,97.25,51.25z"/>
|
||||||
|
<path d="M87.25,13.25c0.61,3.21,2.32,4.98,2,8c-0.34,3.21-2.9,8.83-4,9c-1.17,0.18-1.34,1.78-2,2c-4.66,1.57-12.39-1.48-14-7
|
||||||
|
c-1.16-3.97,1.9-13.37,4-17c1.3-2.25,1.22-2.99,5-4c2.41-0.65,3.65-2.25,6,0c0.47,0.45,1.3,0.49,1.85,0.89
|
||||||
|
c-0.199,0,2,3.14,2.15,4.11C88.57,11.32,87.02,12.03,87.25,13.25z M79.25,22.25c1.78-1.89,3.29-4.04,3-8
|
||||||
|
C77.74,12.58,74.92,21.55,79.25,22.25z"/>
|
||||||
|
<path d="M67.25,21.25c-0.07,5.81,2.48,10.7,0,15c-6.73,1.06-7.24-4.1-11-6c-1.94,1.39-1.49,5.18-3,7c-3.78,0.44-4.69-1.97-7-3
|
||||||
|
c2.47-7.81,1.26-18.98,2-26c8.58-0.58,7.68,8.32,12,12c0.52-4.34-0.36-15.52,3-20C70.58,3.54,67.34,13.24,67.25,21.25z"/>
|
||||||
|
<path d="M52.25,55.25c1.93,8.41,0.12,22.69-12,20c-1.59-0.35-8.42-5.22-9-7c-1.62-5,0.34-13.34,3-16
|
||||||
|
C39.28,47.22,45.73,50.61,52.25,55.25z M39.25,66.25c4.55,0.96,6.3-4.2,4-7C39.62,59.28,38.86,62.19,39.25,66.25z"/>
|
||||||
|
<path d="M39.25,8.25c5.58,0.9,6.4,6.81,5,15c-1.43,8.38-3.02,14.59-9,15c-9.57,0.65-12.25-16.69-9-29c8.32,1.27,6.59,10.36,6,17
|
||||||
|
c2.71,0.83,2.2-0.85,3-2C37.3,21.29,38.07,13.86,39.25,8.25z"/>
|
||||||
|
<path d="M28.25,62.25c0.1,5.67,4.4,11.33,2,17c-4.32-1.01-6.57-4.09-9-7c-3.15-0.48-2.26,3.07-6,2c-0.67,5.06,2.29,7.57-1,10
|
||||||
|
c-4.7-0.63-6.66-4-8-8c-2.61-1.38-5.48-2.52-6-6c0.14-3.53,4.48-2.85,7-4c0.47-5.53-1.41-13.41,2-16c8.31,0.49,8.21,7.13,7,15
|
||||||
|
c4.36,0.29,4.94-4.35,5-7c0.06-2.43-1.82-8.26,2-11c3.06-0.73,2.94,1.73,6,1C32.6,52.95,28.17,57.69,28.25,62.25z"/>
|
||||||
|
<path d="M24.25,12.25c1.07,7.07-3.86,8.14-6,12c0.21,6.88-0.47,12.86-2,18c-5.86-1.32-8.7-10.38-6-17c-0.33-3.52-5.26-4.22-7-8
|
||||||
|
c-0.3-0.66-0.47-4.43-1-7c-0.91-4.37-1.45-5.69,1-9c8.16-0.49,7.21,8.13,9,14c5.05,0.39,3.91-5.42,8-6
|
||||||
|
C21.23,10.6,22.92,11.25,24.25,12.25z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
38
assets/js/global.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
var liMenu = document.querySelectorAll('#apps a')
|
||||||
|
, colors = ['redbg','purpledarkbg','darkbluebg','orangebg','greenbg','darkbluebg','purpledarkbg','yellowbg','lightpinkbg','pinkbg','turquoisebg','yellowbg','lightbluebg','purpledarkbg', 'bluebg']
|
||||||
|
, addMailAlias = document.getElementById('add-mailalias')
|
||||||
|
, addMaildrop = document.getElementById('add-maildrop')
|
||||||
|
;
|
||||||
|
|
||||||
|
liMenu && [].forEach.call(liMenu, function(el, i) {
|
||||||
|
// Select a color value from the App label
|
||||||
|
randomColorNumber = parseInt(el.textContent, 36) % colors.length;
|
||||||
|
//randomColorNumber = i%colors.length; // Old value
|
||||||
|
// Add color class.
|
||||||
|
el.classList.add(colors[randomColorNumber]);
|
||||||
|
// Set first-letter data attribute.
|
||||||
|
el.querySelector('.first-letter').setAttribute('data-first-letter',el.textContent.substring(0, 2));
|
||||||
|
});
|
||||||
|
|
||||||
|
addMailAlias && addMailAlias.addEventListener('click', function(){
|
||||||
|
// Clone last input.
|
||||||
|
var inputAliasClone = document.querySelector('.mailalias-input').cloneNode(true);
|
||||||
|
// Empty value.
|
||||||
|
inputAliasClone.value = '';
|
||||||
|
// Append to form-group.
|
||||||
|
addMailAlias.parentNode.insertBefore(inputAliasClone, addMailAlias);
|
||||||
|
});
|
||||||
|
|
||||||
|
addMaildrop && addMaildrop.addEventListener('click', function(){
|
||||||
|
// Clone last input.
|
||||||
|
var inputDropClone = document.querySelector('.maildrop-input').cloneNode(true);
|
||||||
|
// Empty value.
|
||||||
|
inputDropClone.value = '';
|
||||||
|
// Append to form-group.
|
||||||
|
addMaildrop.parentNode.insertBefore(inputDropClone, addMaildrop);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
303
assets/js/ynhpanel.js
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
/* ----------------------------------------------------------
|
||||||
|
Utilities
|
||||||
|
---------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* Console log fix
|
||||||
|
-------------------------- */
|
||||||
|
if (typeof(console) === 'undefined') {
|
||||||
|
var console = {};
|
||||||
|
console.log = console.error = console.info = console.debug = console.warn = console.trace = console.dir = console.dirxml = console.group = console.groupEnd = console.time = console.timeEnd = console.assert = console.profile = function() {};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Array utilities
|
||||||
|
https://github.com/Darklg/JavaScriptUtilities/blob/master/assets/js/vanilla-js/libs/vanilla-arrays.js
|
||||||
|
-------------------------- */
|
||||||
|
Array.contains = function(needle, haystack) {
|
||||||
|
var i = 0,
|
||||||
|
length = haystack.length;
|
||||||
|
|
||||||
|
for (; i < length; i++) {
|
||||||
|
if (haystack[i] === needle) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
Array.each = function(arrayToParse, callback) {
|
||||||
|
var i = 0,
|
||||||
|
length = arrayToParse.length;
|
||||||
|
for (; i < length; i++) {
|
||||||
|
callback(arrayToParse[i]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* CSS classes utilities
|
||||||
|
https://github.com/Darklg/JavaScriptUtilities/blob/master/assets/js/vanilla-js/libs/vanilla-classes.js
|
||||||
|
-------------------------- */
|
||||||
|
Element.getClassNames = function(element) {
|
||||||
|
var classNames = [],
|
||||||
|
elementClassName = element.className;
|
||||||
|
if (elementClassName !== '') {
|
||||||
|
elementClassName = elementClassName.replace(/\s+/g, ' ');
|
||||||
|
classNames = elementClassName.split(' ');
|
||||||
|
}
|
||||||
|
return classNames;
|
||||||
|
};
|
||||||
|
Element.hasClass = function(element, className) {
|
||||||
|
if (element.classList) {
|
||||||
|
return element.classList.contains(className);
|
||||||
|
}
|
||||||
|
return Array.contains(className, Element.getClassNames(element));
|
||||||
|
};
|
||||||
|
Element.addClass = function(element, className) {
|
||||||
|
if (element.classList) {
|
||||||
|
element.classList.add(className);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Element.hasClass(element, className)) {
|
||||||
|
var elementClasses = Element.getClassNames(element);
|
||||||
|
elementClasses.push(className);
|
||||||
|
element.className = elementClasses.join(' ');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Element.removeClass = function(element, className) {
|
||||||
|
if (element.classList) {
|
||||||
|
element.classList.remove(className);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var elementClasses = Element.getClassNames(element);
|
||||||
|
var newElementClasses = [];
|
||||||
|
var i = 0,
|
||||||
|
arLength = elementClasses.length;
|
||||||
|
for (; i < arLength; i++) {
|
||||||
|
if (elementClasses[i] !== className) {
|
||||||
|
newElementClasses.push(elementClasses[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element.className = newElementClasses.join(' ');
|
||||||
|
};
|
||||||
|
Element.toggleClass = function(element, className) {
|
||||||
|
if (!Element.hasClass(element, className)) {
|
||||||
|
Element.addClass(element, className);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Element.removeClass(element, className);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Add Event
|
||||||
|
https://github.com/Darklg/JavaScriptUtilities/blob/master/assets/js/vanilla-js/libs/vanilla-events.js
|
||||||
|
-------------------------- */
|
||||||
|
window.addEvent = function(el, eventName, callback) {
|
||||||
|
if (el.addEventListener) {
|
||||||
|
el.addEventListener(eventName, callback, false);
|
||||||
|
}
|
||||||
|
else if (el.attachEvent) {
|
||||||
|
el.attachEvent("on" + eventName, function(e) {
|
||||||
|
return callback.call(el, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.eventPreventDefault = function(event) {
|
||||||
|
return (event.preventDefault) ? event.preventDefault() : event.returnValue = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Draggable
|
||||||
|
|
||||||
|
Sources :
|
||||||
|
http://jsfiddle.net/5t3Ju/
|
||||||
|
http://stackoverflow.com/questions/9334084/moveable-draggable-div
|
||||||
|
http://jsfiddle.net/tovic/Xcb8d/light/
|
||||||
|
-------------------------- */
|
||||||
|
|
||||||
|
var dragg = function(id) {
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
this.elem = document.getElementById(id),
|
||||||
|
this.selected = null, // Selected element
|
||||||
|
this.dragged = false, // Dragging status
|
||||||
|
this.x_pos = 0, this.y_pos = 0, // Stores x & y coordinates of the mouse pointer
|
||||||
|
this.x_elem = 0, this.y_elem = 0; // Stores top, left values (edge) of the element
|
||||||
|
|
||||||
|
// Start dragging
|
||||||
|
window.addEvent(elem, 'mousedown', function(e){
|
||||||
|
// Prevent firefox native D'n'D behavior
|
||||||
|
window.eventPreventDefault(e);
|
||||||
|
|
||||||
|
selected = elem;
|
||||||
|
x_elem = x_pos - selected.offsetLeft;
|
||||||
|
y_elem = y_pos - selected.offsetTop;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Will be called when user dragging an element
|
||||||
|
window.addEvent(window, 'mousemove', function(e){
|
||||||
|
// Get position
|
||||||
|
x_pos = document.all ? window.event.clientX : e.pageX;
|
||||||
|
y_pos = document.all ? window.event.clientY : e.pageY;
|
||||||
|
|
||||||
|
if (selected !== null) {
|
||||||
|
dragged = true;
|
||||||
|
selected.style.left = (x_pos - x_elem) + 'px';
|
||||||
|
selected.style.top = (y_pos - y_elem) + 'px';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Destroy the object when we are done
|
||||||
|
window.addEvent(window, 'mouseup', function(e){
|
||||||
|
selected = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle click event
|
||||||
|
window.addEvent(elem, 'click', function(e){
|
||||||
|
// Prevent default event
|
||||||
|
window.eventPreventDefault(e);
|
||||||
|
|
||||||
|
// Do not prapagate to other click event if dragged out
|
||||||
|
if (dragged) {
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
}
|
||||||
|
// Reset dragging status
|
||||||
|
dragged = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Smallest DOMReady
|
||||||
|
http://dustindiaz.com/smallest-domready-ever
|
||||||
|
-------------------------- */
|
||||||
|
function domReady(cb) {
|
||||||
|
/in/.test(document.readyState) // in = loadINg
|
||||||
|
? setTimeout('domReady('+cb+')', 9)
|
||||||
|
: cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------
|
||||||
|
Main
|
||||||
|
---------------------------------------------------------- */
|
||||||
|
domReady(function(){
|
||||||
|
// Don't do this in iframe
|
||||||
|
if (window.self !== window.top) {return false;}
|
||||||
|
|
||||||
|
// Set and store meta viewport
|
||||||
|
var meta_viewport = document.querySelector('meta[name="viewport"]');
|
||||||
|
if (meta_viewport === null) {
|
||||||
|
meta_viewport = document.createElement('meta');
|
||||||
|
meta_viewport.setAttribute('name', "viewport");
|
||||||
|
meta_viewport.setAttribute('content', "");
|
||||||
|
document.getElementsByTagName('head')[0].insertBefore(meta_viewport, null);
|
||||||
|
}
|
||||||
|
meta_viewport = document.querySelector('meta[name="viewport"]');
|
||||||
|
meta_viewport_content = meta_viewport.getAttribute('content');
|
||||||
|
|
||||||
|
// Add portal stylesheet
|
||||||
|
var portalStyle = document.createElement("link");
|
||||||
|
portalStyle.setAttribute("rel", "stylesheet");
|
||||||
|
portalStyle.setAttribute("type", "text/css");
|
||||||
|
portalStyle.setAttribute("href", '/ynhpanel.css');
|
||||||
|
document.getElementsByTagName("head")[0].insertBefore(portalStyle, null);
|
||||||
|
|
||||||
|
// Create portal link
|
||||||
|
var portal = document.createElement('a');
|
||||||
|
portal.setAttribute('id', 'ynh-overlay-switch');
|
||||||
|
portal.setAttribute('href', '/yunohost/sso/');
|
||||||
|
portal.setAttribute('class', 'disableAjax');
|
||||||
|
document.body.insertBefore(portal, null);
|
||||||
|
|
||||||
|
// Portal link is draggable, for user convenience
|
||||||
|
dragg('ynh-overlay-switch');
|
||||||
|
|
||||||
|
|
||||||
|
// Create overlay element
|
||||||
|
var overlay = document.createElement("div");
|
||||||
|
overlay.setAttribute("id","ynh-overlay");
|
||||||
|
overlay.setAttribute("style","display:none");
|
||||||
|
|
||||||
|
document.body.insertBefore(overlay, null);
|
||||||
|
|
||||||
|
//Color Application
|
||||||
|
var colors = ['redbg','purpledarkbg','darkbluebg','orangebg','greenbg','darkbluebg','purpledarkbg','yellowbg','lightpinkbg','pinkbg','turquoisebg','yellowbg','lightbluebg','purpledarkbg', 'bluebg'];
|
||||||
|
|
||||||
|
// Get user's app
|
||||||
|
var r = new XMLHttpRequest();
|
||||||
|
r.open("GET", "/ynhpanel.json", true);
|
||||||
|
r.onreadystatechange = function () {
|
||||||
|
// Die if error
|
||||||
|
if (r.readyState != 4 || r.status != 200) return;
|
||||||
|
|
||||||
|
// Response is JSON
|
||||||
|
response = JSON.parse(r.responseText);
|
||||||
|
|
||||||
|
// Add overlay header
|
||||||
|
overlay.innerHTML += '<div id="ynh-user" class="ynh-wrapper info">' +
|
||||||
|
'<ul class="ul-reset user-menu"><li><a class="icon icon-connexion disableAjax" href="'+ response.portal_url +'?action=logout">'+response.t_logout+'</a></li></ul>'+
|
||||||
|
'<a class="user-container user-container-info disableAjax" href="'+ response.portal_url +'edit.html">' +
|
||||||
|
'<h2 class="user-username">'+ response.uid +'</h2>' +
|
||||||
|
'<small class="user-fullname">'+ response.givenName + ' ' + response.sn +'</small>' +
|
||||||
|
'<span class="user-mail">'+ response.mail +'</span>' +
|
||||||
|
'</a>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
|
||||||
|
// Add application links
|
||||||
|
var links = [];
|
||||||
|
Array.prototype.forEach.call(response.app, function(app, n){
|
||||||
|
randomColorNumber = parseInt(app.name, 36) % colors.length;
|
||||||
|
links.push('<li><a class="'+colors[randomColorNumber]+' disableAjax" href="//'+app.url+'"><span class="first-letter" data-first-letter="'+ app.name.substr(0,2) +'"></span><span class="name">'+app.name+'</span></a></li>');
|
||||||
|
});
|
||||||
|
overlay.innerHTML += '<div id="ynh-apps" class="ynh-wrapper apps"><ul class="listing-apps">'+ links.join("\n") +'</ul></div>';
|
||||||
|
|
||||||
|
// Add footer links
|
||||||
|
overlay.innerHTML += '<div id="ynh-footer" class="ynh-wrapper footer"><nav>' + "\n" +
|
||||||
|
'<a class="link-profile-edit" href="/yunohost/sso/edit.html">'+ response.t_footerlink_edit +'</a>' + "\n" +
|
||||||
|
'<a class="link-documentation" href="//yunohost.org/docs" target="_blank">'+ response.t_footerlink_documentation +'</a>' + "\n" +
|
||||||
|
'<a class="link-documentation" href="//yunohost.org/support" target="_blank">'+ response.t_footerlink_support +'</a>' + "\n" +
|
||||||
|
'<a class="link-admin" href="/yunohost/admin/" target="_blank">'+ response.t_footerlink_administration +'</a>' + "\n" +
|
||||||
|
'</nav></div>';
|
||||||
|
|
||||||
|
// Add overlay to DOM
|
||||||
|
var btn = document.getElementById('logo'),
|
||||||
|
yunoverlay = document.getElementById('ynh-overlay'),
|
||||||
|
user = document.getElementById('ynh-user'),
|
||||||
|
apps = document.getElementById('ynh-apps');
|
||||||
|
|
||||||
|
var pfx = ["webkit", "moz", "MS", "o", ""];
|
||||||
|
function PrefixedEvent(element, type, callback) {
|
||||||
|
for (var p = 0; p < pfx.length; p++) {
|
||||||
|
if (!pfx[p]) type = type.toLowerCase();
|
||||||
|
element.addEventListener(pfx[p]+type, callback, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind YNH Button
|
||||||
|
window.addEvent(portal, 'click', function(e){
|
||||||
|
// Prevent default click
|
||||||
|
window.eventPreventDefault(e);
|
||||||
|
// Toggle overlay on YNHPortal button
|
||||||
|
//Element.toggleClass(overlay, 'visible');
|
||||||
|
Element.toggleClass(portal, 'visible');
|
||||||
|
Element.toggleClass(document.querySelector('html'), 'ynh-panel-active');
|
||||||
|
|
||||||
|
|
||||||
|
if(yunoverlay.classList.contains('ynh-active')) {
|
||||||
|
meta_viewport.setAttribute('content', meta_viewport_content);
|
||||||
|
yunoverlay.classList.add('ynh-fadeOut');
|
||||||
|
PrefixedEvent(yunoverlay, "AnimationEnd", function(){
|
||||||
|
if(yunoverlay.classList.contains('ynh-fadeOut')) {
|
||||||
|
yunoverlay.classList.remove('ynh-active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}else {
|
||||||
|
meta_viewport.setAttribute('content', "width=device-width");
|
||||||
|
yunoverlay.classList.remove('ynh-fadeOut');
|
||||||
|
yunoverlay.classList.add('ynh-active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
r.send();
|
||||||
|
|
||||||
|
});
|
1
assets/js/ynhpanel.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
233
index.php
|
@ -1,115 +1,160 @@
|
||||||
<?php
|
<?php
|
||||||
|
// Retrieve the app from the URL using GET
|
||||||
$app = htmlentities($_GET['app']);
|
$app = htmlentities($_GET['app']);
|
||||||
if(isset($_POST['server']) AND !empty($_POST['server'])) {
|
|
||||||
$server = preg_replace('#^https?://#', '', $_POST['server']);
|
// Retrieve the correct locale JSON
|
||||||
|
$user_locale = substr(Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']), 0, 2);
|
||||||
|
// Check if the user locale exists, otherwise switch to "en"
|
||||||
|
if (!file_exists("locales/".$user_locale.".json")) { $user_locale = 'en'; }
|
||||||
|
$locale=json_decode(file_get_contents("locales/".$user_locale.".json"), true);
|
||||||
|
|
||||||
|
// Parse the official app list
|
||||||
|
$official_json=file_get_contents("https://app.yunohost.org/official.json");
|
||||||
|
$official=json_decode($official_json, true);
|
||||||
|
|
||||||
|
// Check whether the app is official, community, or neither
|
||||||
|
if(array_key_exists($app, $official)) {
|
||||||
|
// The app is in the official list
|
||||||
|
$app_status = 'official'; // The app is in the official list
|
||||||
|
$app_name = $official[$app]['manifest']['name']; // Saves the app name
|
||||||
|
$app_git = $official[$app]['git']['url']; // Saves the git URL
|
||||||
|
$app_state = $official[$app]['state']; // Saves the app state
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Parse the community app list
|
||||||
|
$community_json=file_get_contents("https://app.yunohost.org/community.json");
|
||||||
|
$community=json_decode($community_json, true);
|
||||||
|
|
||||||
|
// Check if the app is community
|
||||||
|
if(array_key_exists($app, $community)) {
|
||||||
|
// The app is in the community list
|
||||||
|
$app_status = 'community'; // The app is in the community list
|
||||||
|
$app_name = $community[$app]['manifest']['name']; // Saves the app name
|
||||||
|
$app_git = $community[$app]['git']['url']; // Saves the git URL
|
||||||
|
$app_state = $community[$app]['state']; // Saves the app state
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// The app is neither in the official, nor in the community list
|
||||||
|
$app_status = null;
|
||||||
|
$app_name = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user submitted his or her server and the app is official, redirects to the server
|
||||||
|
if(isset($_POST['server']) AND !empty($_POST['server']) AND $app_status == 'official') {
|
||||||
|
$server = rtrim(preg_replace('#^https?://#', '', $_POST['server']),"/");
|
||||||
$url = 'https://'.$server.'/yunohost/admin/#/apps/install/'.$app;
|
$url = 'https://'.$server.'/yunohost/admin/#/apps/install/'.$app;
|
||||||
header('Location: '.$url);
|
header('Location: '.$url);
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="utf-8">
|
||||||
<meta name="format-detection" content="telephone=no" />
|
<title><?php echo str_replace("{app_name}", $app_name, $locale['title']); ?></title>
|
||||||
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1" />
|
|
||||||
<meta name="robots" content="noindex, nofollow">
|
|
||||||
<title>Install with YunoHost</title>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
<!-- Responsive -->
|
||||||
font-family: sans-serif; /* 1 */
|
<meta name="format-detection" content="telephone=no" />
|
||||||
-ms-text-size-adjust: 100%; /* 2 */
|
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1" />
|
||||||
-webkit-text-size-adjust: 100%; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
<!-- Do not index SSOWat pages -->
|
||||||
background: #41444f;
|
<meta name="robots" content="noindex, nofollow">
|
||||||
font-family: arial;
|
|
||||||
overflow-y: scroll;
|
|
||||||
font-size: 1em;
|
|
||||||
line-height:1.5;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
height :auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 0;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo img {
|
|
||||||
margin-top: 4%;
|
|
||||||
width: 4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
width: 90%;
|
|
||||||
margin: 2% 5%;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form {
|
<!-- Stylesheets -->
|
||||||
max-width: 21em;
|
<link rel="stylesheet" href="assets/css/ynh-style.css">
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
<!-- Icons -->
|
||||||
border:none;
|
<link rel="shortcut icon" href="assets/icons/favicon.ico">
|
||||||
box-shadow:none;
|
<link rel="apple-touch-icon" sizes="57x57" href="assets/icons/apple-touch-icon-57x57.png">
|
||||||
position: relative;
|
<link rel="apple-touch-icon" sizes="114x114" href="assets/icons/apple-touch-icon-114x114.png">
|
||||||
margin-bottom: 1em;
|
<link rel="apple-touch-icon" sizes="72x72" href="assets/icons/apple-touch-icon-72x72.png">
|
||||||
}
|
<link rel="apple-touch-icon" sizes="144x144" href="assets/icons/apple-touch-icon-144x144.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="60x60" href="assets/icons/apple-touch-icon-60x60.png">
|
||||||
input[type="submit"] {
|
<link rel="apple-touch-icon" sizes="120x120" href="assets/icons/apple-touch-icon-120x120.png">
|
||||||
width: 100%;
|
<link rel="apple-touch-icon" sizes="76x76" href="assets/icons/apple-touch-icon-76x76.png">
|
||||||
padding: 0.8em 1.5em;
|
<link rel="apple-touch-icon" sizes="152x152" href="assets/icons/apple-touch-icon-152x152.png">
|
||||||
font-size: 1.1em;
|
<link rel="icon" type="image/png" href="assets/icons/favicon-196x196.png" sizes="196x196">
|
||||||
background: #999;
|
<link rel="icon" type="image/png" href="assets/icons/favicon-160x160.png" sizes="160x160">
|
||||||
display: inline-block;
|
<link rel="icon" type="image/png" href="assets/icons/favicon-96x96.png" sizes="96x96">
|
||||||
padding: 0.5em 1em;
|
<link rel="icon" type="image/png" href="assets/icons/favicon-16x16.png" sizes="16x16">
|
||||||
line-height: normal;
|
<link rel="icon" type="image/png" href="assets/icons/favicon-32x32.png" sizes="32x32">
|
||||||
text-decoration: none;
|
<meta name="msapplication-TileColor" content="#41444f">
|
||||||
color: #FFF;
|
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
|
||||||
background: #2980b9;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.1s ease;
|
|
||||||
-webkit-transition: all 0.1s ease;
|
|
||||||
border:0;
|
|
||||||
cursor:pointer;
|
|
||||||
}
|
|
||||||
input[type="submit"]:hover {background: #3498db;}
|
|
||||||
|
|
||||||
input[type="text"] {
|
|
||||||
background: #fff;
|
|
||||||
color: #41444f;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.8em 0.8em 0.8em 3em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1 id="logo" class="logo">
|
<h1 id="logo" class="logo">
|
||||||
<img src="logo-ynh-white.svg"/>
|
<img src="logo-ynh-white.svg"/>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="overlay">
|
|
||||||
<div class="wrapper">
|
<div class="overlay">
|
||||||
<form class="form" name="input" action="" method="post">
|
|
||||||
<input id="server" type="text" name="server" placeholder="Link to your YunoHost server" autofocus required>
|
<div class="ynh-wrapper login">
|
||||||
<input type="submit" value="Install <?php echo $app; ?>">
|
|
||||||
</form>
|
<?php
|
||||||
|
// The app is official, display an install form
|
||||||
|
if($app_status == 'official') {
|
||||||
|
?>
|
||||||
|
<form class="login-form" name="input" action="" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="icon icon-connexion" for="server"><span class="element-invisible"><?php echo $locale['server_link']; ?></span></label>
|
||||||
|
<input id="server" type="text" name="server" placeholder="<?php echo $locale['server_link']; ?>" class="form-text" autofocus required>
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="<?php echo str_replace("{app_name}", $app_name, $locale['install_button']); ?>" class="btn classic-btn large-btn">
|
||||||
|
</form>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
// The app is community, display a specific form and a warning
|
||||||
|
else if($app_status == 'community') {
|
||||||
|
?>
|
||||||
|
<div class="wrapper messages warning">
|
||||||
|
<p><?php echo str_replace(["{app_name}", "{app_state}"], [$app_name, $locale[$app_state]], $locale['community_warning']); ?></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// If the user submitted his or her server and the app is community, redirects to the server
|
||||||
|
if(isset($_POST['server']) AND !empty($_POST['server']) AND $app_status == 'community') {
|
||||||
|
$server = rtrim(preg_replace('#^https?://#', '', $_POST['server']),"/");
|
||||||
|
$url = 'https://'.$server.'/yunohost/admin/#/apps/install/apps';
|
||||||
|
?>
|
||||||
|
<form class="login-form" name="input" action="<?php echo $url; ?>" method="get">
|
||||||
|
<p style="text-align:center;color:white;"><?php echo $locale['community_instructions']; ?></p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="icon icon-pencil" for="git"><span class="element-invisible"><?php echo $app_git; ?></span></label>
|
||||||
|
<input id="git" type="text" name="git" value="<?php echo $app_git; ?>" class="form-text" readonly onClick="this.select();">
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="<?php echo $locale['community_redirect']; ?>" class="btn classic-btn large-btn">
|
||||||
|
</form>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Display the server form
|
||||||
|
?>
|
||||||
|
|
||||||
|
<form class="login-form" name="input" action="" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="icon icon-connexion" for="server"><span class="element-invisible"><?php echo $locale['server_link']; ?></span></label>
|
||||||
|
<input id="server" type="text" name="server" placeholder="<?php echo $locale['server_link']; ?>" class="form-text" autofocus required>
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="<?php echo str_replace("{app_name}", $app_name, $locale['install_button']); ?> (community)" class="btn classic-btn large-btn">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php } ?>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
// The app is neither official, nor community
|
||||||
|
else {
|
||||||
|
?>
|
||||||
|
<div class="wrapper messages danger">
|
||||||
|
<p><?php echo $locale['app_notfound']; ?></p>
|
||||||
</div>
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script src="assets/js/global.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
12
locales/en.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"title": "Install {app_name} with YunoHost",
|
||||||
|
"server_link": "Link to your YunoHost server",
|
||||||
|
"install_button": "Install {app_name}",
|
||||||
|
"community_warning": "<strong>WARNING</strong>: {app_name} is <strong>NOT officially supported</strong> by the YunoHost team yet. Install it <strong>at your own risk</strong>. The community maintainer has described this app to be in a {app_state} state.",
|
||||||
|
"community_instructions": "Copy the following link. You will then be redirected to your server apps installation page in which you will need to paste this link.",
|
||||||
|
"community_redirect": "Go to your server",
|
||||||
|
"app_notfound": "This application is neither officially nor community maintained.",
|
||||||
|
"working": "working",
|
||||||
|
"inprogress": "in progress",
|
||||||
|
"notworking": "not working"
|
||||||
|
}
|
12
locales/fr.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"title": "Installer {app_name} avec YunoHost",
|
||||||
|
"server_link": "Lien vers votre serveur YunoHost",
|
||||||
|
"install_button": "Installer {app_name}",
|
||||||
|
"community_warning": "<strong>ATTENTION</strong> : {app_name} <strong>N'EST PAS officiellement prise en charge</strong> pour le moment par l'équipe YunoHost. Installez cette application <strong>à vos risques et périls</strong>. Le mainteneur de cette application la décrit comme étant dans un état {app_state}.",
|
||||||
|
"community_instructions": "Merci de copier le lien suivant. Vous serez ensuite redirigé vers la page d'installation d'applications de votre serveur dans laquelle vous devrez coller ce lien.",
|
||||||
|
"community_redirect": "Accéder au serveur",
|
||||||
|
"app_notfound": "Cette application n'est maintenue ni officiellemment, ni par la communauté.",
|
||||||
|
"working": "fonctionnel",
|
||||||
|
"inprogress": "non fonctionnel",
|
||||||
|
"notworking": "en cours de développement"
|
||||||
|
}
|