improvements
Former-commit-id: e648350a0ca0af302ba8b929ac653433beb0e61c
This commit is contained in:
		
							parent
							
								
									61a9a30c0e
								
							
						
					
					
						commit
						048c69af22
					
				| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
[submodule "_assets/js/vendor/ace"]
 | 
			
		||||
	path = _assets/js/vendor/ace
 | 
			
		||||
	url = https://github.com/ajaxorg/ace-builds
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,137 @@
 | 
			
		|||
@font-face {
 | 
			
		||||
  font-family: 'Roboto';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  src: local('Roboto'), local('Roboto-Regular'), url(roboto/normal-cyrillic-ext.woff2) format('woff2');
 | 
			
		||||
  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Roboto';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  src: local('Roboto'), local('Roboto-Regular'), url(roboto/normal-cyrillic.woff2) format('woff2');
 | 
			
		||||
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Roboto';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  src: local('Roboto'), local('Roboto-Regular'), url(roboto/normal-greek-ext.woff2) format('woff2');
 | 
			
		||||
  unicode-range: U+1F00-1FFF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Roboto';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  src: local('Roboto'), local('Roboto-Regular'), url(roboto/normal-greek.woff2) format('woff2');
 | 
			
		||||
  unicode-range: U+0370-03FF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Roboto';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  src: local('Roboto'), local('Roboto-Regular'), url(roboto/normal-vietnamese.woff2) format('woff2');
 | 
			
		||||
  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Roboto';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  src: local('Roboto'), local('Roboto-Regular'), url(roboto/normal-latin-ext.woff2) format('woff2');
 | 
			
		||||
  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Roboto';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  src: local('Roboto'), local('Roboto-Regular'), url(roboto/normal-latin.woff2) format('woff2');
 | 
			
		||||
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Roboto';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  src: local('Roboto Medium'), local('Roboto-Medium'), url(roboto/medium-cyrillic-ext.woff2) format('woff2');
 | 
			
		||||
  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Roboto';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  src: local('Roboto Medium'), local('Roboto-Medium'), url(roboto/medium-cyrillic.woff2) format('woff2');
 | 
			
		||||
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Roboto';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  src: local('Roboto Medium'), local('Roboto-Medium'), url(roboto/medium-greek-ext.woff2) format('woff2');
 | 
			
		||||
  unicode-range: U+1F00-1FFF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Roboto';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  src: local('Roboto Medium'), local('Roboto-Medium'), url(roboto/medium-greek.woff2) format('woff2');
 | 
			
		||||
  unicode-range: U+0370-03FF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Roboto';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  src: local('Roboto Medium'), local('Roboto-Medium'), url(roboto/medium-vietnamese.woff2) format('woff2');
 | 
			
		||||
  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Roboto';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  src: local('Roboto Medium'), local('Roboto-Medium'), url(roboto/medium-latin-ext.woff2) format('woff2');
 | 
			
		||||
  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Roboto';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  src: local('Roboto Medium'), local('Roboto-Medium'), url(roboto/medium-latin.woff2) format('woff2');
 | 
			
		||||
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Material Icons';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  src: local('Material Icons'), local('MaterialIcons-Regular'), url(material/icons.woff2) format('woff2');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.prompt .file-list ul li:before,
 | 
			
		||||
.material-icons {
 | 
			
		||||
  font-family: 'Material Icons';
 | 
			
		||||
  font-weight: normal;
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
  line-height: 1;
 | 
			
		||||
  letter-spacing: normal;
 | 
			
		||||
  text-transform: none;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  word-wrap: normal;
 | 
			
		||||
  direction: ltr;
 | 
			
		||||
  -webkit-font-smoothing: antialiased;
 | 
			
		||||
  text-rendering: optimizeLegibility;
 | 
			
		||||
  -moz-osx-font-smoothing: grayscale;
 | 
			
		||||
  font-feature-settings: 'liga';
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,461 @@
 | 
			
		|||
/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 1. Change the default font family in all browsers (opinionated).
 | 
			
		||||
 * 2. Correct the line height in all browsers.
 | 
			
		||||
 * 3. Prevent adjustments of font size after orientation changes in
 | 
			
		||||
 *    IE on Windows Phone and in iOS.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/* Document
 | 
			
		||||
   ========================================================================== */
 | 
			
		||||
 | 
			
		||||
html {
 | 
			
		||||
  font-family: sans-serif; /* 1 */
 | 
			
		||||
  line-height: 1.15; /* 2 */
 | 
			
		||||
  -ms-text-size-adjust: 100%; /* 3 */
 | 
			
		||||
  -webkit-text-size-adjust: 100%; /* 3 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Sections
 | 
			
		||||
   ========================================================================== */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Remove the margin in all browsers (opinionated).
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the correct display in IE 9-.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
article,
 | 
			
		||||
aside,
 | 
			
		||||
footer,
 | 
			
		||||
header,
 | 
			
		||||
nav,
 | 
			
		||||
section {
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Correct the font size and margin on `h1` elements within `section` and
 | 
			
		||||
 * `article` contexts in Chrome, Firefox, and Safari.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
h1 {
 | 
			
		||||
  font-size: 2em;
 | 
			
		||||
  margin: 0.67em 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Grouping content
 | 
			
		||||
   ========================================================================== */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the correct display in IE 9-.
 | 
			
		||||
 * 1. Add the correct display in IE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
figcaption,
 | 
			
		||||
figure,
 | 
			
		||||
main { /* 1 */
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the correct margin in IE 8.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
figure {
 | 
			
		||||
  margin: 1em 40px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 1. Add the correct box sizing in Firefox.
 | 
			
		||||
 * 2. Show the overflow in Edge and IE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
hr {
 | 
			
		||||
  box-sizing: content-box; /* 1 */
 | 
			
		||||
  height: 0; /* 1 */
 | 
			
		||||
  overflow: visible; /* 2 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 1. Correct the inheritance and scaling of font size in all browsers.
 | 
			
		||||
 * 2. Correct the odd `em` font sizing in all browsers.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
pre {
 | 
			
		||||
  font-family: monospace, monospace; /* 1 */
 | 
			
		||||
  font-size: 1em; /* 2 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Text-level semantics
 | 
			
		||||
   ========================================================================== */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 1. Remove the gray background on active links in IE 10.
 | 
			
		||||
 * 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
a {
 | 
			
		||||
  background-color: transparent; /* 1 */
 | 
			
		||||
  -webkit-text-decoration-skip: objects; /* 2 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Remove the outline on focused links when they are also active or hovered
 | 
			
		||||
 * in all browsers (opinionated).
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
a:active,
 | 
			
		||||
a:hover {
 | 
			
		||||
  outline-width: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 1. Remove the bottom border in Firefox 39-.
 | 
			
		||||
 * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
abbr[title] {
 | 
			
		||||
  border-bottom: none; /* 1 */
 | 
			
		||||
  text-decoration: underline; /* 2 */
 | 
			
		||||
  text-decoration: underline dotted; /* 2 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Prevent the duplicate application of `bolder` by the next rule in Safari 6.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
b,
 | 
			
		||||
strong {
 | 
			
		||||
  font-weight: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the correct font weight in Chrome, Edge, and Safari.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
b,
 | 
			
		||||
strong {
 | 
			
		||||
  font-weight: bolder;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 1. Correct the inheritance and scaling of font size in all browsers.
 | 
			
		||||
 * 2. Correct the odd `em` font sizing in all browsers.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
code,
 | 
			
		||||
kbd,
 | 
			
		||||
samp {
 | 
			
		||||
  font-family: monospace, monospace; /* 1 */
 | 
			
		||||
  font-size: 1em; /* 2 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the correct font style in Android 4.3-.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
dfn {
 | 
			
		||||
  font-style: italic;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the correct background and color in IE 9-.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
mark {
 | 
			
		||||
  background-color: #ff0;
 | 
			
		||||
  color: #000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the correct font size in all browsers.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
small {
 | 
			
		||||
  font-size: 80%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Prevent `sub` and `sup` elements from affecting the line height in
 | 
			
		||||
 * all browsers.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
sub,
 | 
			
		||||
sup {
 | 
			
		||||
  font-size: 75%;
 | 
			
		||||
  line-height: 0;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  vertical-align: baseline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub {
 | 
			
		||||
  bottom: -0.25em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sup {
 | 
			
		||||
  top: -0.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Embedded content
 | 
			
		||||
   ========================================================================== */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the correct display in IE 9-.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
audio,
 | 
			
		||||
video {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the correct display in iOS 4-7.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
audio:not([controls]) {
 | 
			
		||||
  display: none;
 | 
			
		||||
  height: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Remove the border on images inside links in IE 10-.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
img {
 | 
			
		||||
  border-style: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hide the overflow in IE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
svg:not(:root) {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Forms
 | 
			
		||||
   ========================================================================== */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 1. Change the font styles in all browsers (opinionated).
 | 
			
		||||
 * 2. Remove the margin in Firefox and Safari.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
button,
 | 
			
		||||
input,
 | 
			
		||||
optgroup,
 | 
			
		||||
select,
 | 
			
		||||
textarea {
 | 
			
		||||
  font-family: sans-serif; /* 1 */
 | 
			
		||||
  font-size: 100%; /* 1 */
 | 
			
		||||
  line-height: 1.15; /* 1 */
 | 
			
		||||
  margin: 0; /* 2 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Show the overflow in IE.
 | 
			
		||||
 * 1. Show the overflow in Edge.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
button,
 | 
			
		||||
input { /* 1 */
 | 
			
		||||
  overflow: visible;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Remove the inheritance of text transform in Edge, Firefox, and IE.
 | 
			
		||||
 * 1. Remove the inheritance of text transform in Firefox.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
button,
 | 
			
		||||
select { /* 1 */
 | 
			
		||||
  text-transform: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
 | 
			
		||||
 *    controls in Android 4.
 | 
			
		||||
 * 2. Correct the inability to style clickable types in iOS and Safari.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
button,
 | 
			
		||||
html [type="button"], /* 1 */
 | 
			
		||||
[type="reset"],
 | 
			
		||||
[type="submit"] {
 | 
			
		||||
  -webkit-appearance: button; /* 2 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Remove the inner border and padding in Firefox.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
button::-moz-focus-inner,
 | 
			
		||||
[type="button"]::-moz-focus-inner,
 | 
			
		||||
[type="reset"]::-moz-focus-inner,
 | 
			
		||||
[type="submit"]::-moz-focus-inner {
 | 
			
		||||
  border-style: none;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Restore the focus styles unset by the previous rule.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
button:-moz-focusring,
 | 
			
		||||
[type="button"]:-moz-focusring,
 | 
			
		||||
[type="reset"]:-moz-focusring,
 | 
			
		||||
[type="submit"]:-moz-focusring {
 | 
			
		||||
  outline: 1px dotted ButtonText;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Change the border, margin, and padding in all browsers (opinionated).
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
fieldset {
 | 
			
		||||
  border: 1px solid #c0c0c0;
 | 
			
		||||
  margin: 0 2px;
 | 
			
		||||
  padding: 0.35em 0.625em 0.75em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 1. Correct the text wrapping in Edge and IE.
 | 
			
		||||
 * 2. Correct the color inheritance from `fieldset` elements in IE.
 | 
			
		||||
 * 3. Remove the padding so developers are not caught out when they zero out
 | 
			
		||||
 *    `fieldset` elements in all browsers.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
legend {
 | 
			
		||||
  box-sizing: border-box; /* 1 */
 | 
			
		||||
  color: inherit; /* 2 */
 | 
			
		||||
  display: table; /* 1 */
 | 
			
		||||
  max-width: 100%; /* 1 */
 | 
			
		||||
  padding: 0; /* 3 */
 | 
			
		||||
  white-space: normal; /* 1 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 1. Add the correct display in IE 9-.
 | 
			
		||||
 * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
progress {
 | 
			
		||||
  display: inline-block; /* 1 */
 | 
			
		||||
  vertical-align: baseline; /* 2 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Remove the default vertical scrollbar in IE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
textarea {
 | 
			
		||||
  overflow: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 1. Add the correct box sizing in IE 10-.
 | 
			
		||||
 * 2. Remove the padding in IE 10-.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
[type="checkbox"],
 | 
			
		||||
[type="radio"] {
 | 
			
		||||
  box-sizing: border-box; /* 1 */
 | 
			
		||||
  padding: 0; /* 2 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Correct the cursor style of increment and decrement buttons in Chrome.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
[type="number"]::-webkit-inner-spin-button,
 | 
			
		||||
[type="number"]::-webkit-outer-spin-button {
 | 
			
		||||
  height: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 1. Correct the odd appearance in Chrome and Safari.
 | 
			
		||||
 * 2. Correct the outline style in Safari.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
[type="search"] {
 | 
			
		||||
  -webkit-appearance: textfield; /* 1 */
 | 
			
		||||
  outline-offset: -2px; /* 2 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
[type="search"]::-webkit-search-cancel-button,
 | 
			
		||||
[type="search"]::-webkit-search-decoration {
 | 
			
		||||
  -webkit-appearance: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 1. Correct the inability to style clickable types in iOS and Safari.
 | 
			
		||||
 * 2. Change font properties to `inherit` in Safari.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
::-webkit-file-upload-button {
 | 
			
		||||
  -webkit-appearance: button; /* 1 */
 | 
			
		||||
  font: inherit; /* 2 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Interactive
 | 
			
		||||
   ========================================================================== */
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Add the correct display in IE 9-.
 | 
			
		||||
 * 1. Add the correct display in Edge, IE, and Firefox.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
details, /* 1 */
 | 
			
		||||
menu {
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Add the correct display in all browsers.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
summary {
 | 
			
		||||
  display: list-item;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Scripting
 | 
			
		||||
   ========================================================================== */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the correct display in IE 9-.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
canvas {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the correct display in IE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
template {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Hidden
 | 
			
		||||
   ========================================================================== */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the correct display in IE 10-.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
[hidden] {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,685 @@
 | 
			
		|||
'use strict'
 | 
			
		||||
 | 
			
		||||
var tempID = '_fm_internal_temporary_id'
 | 
			
		||||
var ssl = (window.location.protocol === 'https:')
 | 
			
		||||
var templates = {}
 | 
			
		||||
var selectedItems = []
 | 
			
		||||
var overlay
 | 
			
		||||
var clickOverlay
 | 
			
		||||
 | 
			
		||||
// Removes an element, if exists, from an array
 | 
			
		||||
Array.prototype.removeElement = function (element) {
 | 
			
		||||
  var i = this.indexOf(element)
 | 
			
		||||
  if (i !== -1) {
 | 
			
		||||
    this.splice(i, 1)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Replaces an element inside an array by another
 | 
			
		||||
Array.prototype.replaceElement = function (oldElement, newElement) {
 | 
			
		||||
  var i = this.indexOf(oldElement)
 | 
			
		||||
  if (i !== -1) {
 | 
			
		||||
    this[i] = newElement
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sends a costum event to itself
 | 
			
		||||
Document.prototype.sendCostumEvent = function (text) {
 | 
			
		||||
  this.dispatchEvent(new window.CustomEvent(text))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Gets the content of a cookie
 | 
			
		||||
Document.prototype.getCookie = function (name) {
 | 
			
		||||
  var re = new RegExp('(?:(?:^|.*;\\s*)' + name + '\\s*\\=\\s*([^;]*).*$)|^.*$')
 | 
			
		||||
  return document.cookie.replace(re, '$1')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Remove the last directory of an url
 | 
			
		||||
var removeLastDirectoryPartOf = function (url) {
 | 
			
		||||
  var arr = url.split('/')
 | 
			
		||||
  if (arr.pop() === '') {
 | 
			
		||||
    arr.pop()
 | 
			
		||||
  }
 | 
			
		||||
  return (arr.join('/'))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getCSSRule (rules) {
 | 
			
		||||
  for (let i = 0; i < rules.length; i++) {
 | 
			
		||||
    rules[i] = rules[i].toLowerCase()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let result = null
 | 
			
		||||
  let find = Array.prototype.find
 | 
			
		||||
 | 
			
		||||
  find.call(document.styleSheets, styleSheet => {
 | 
			
		||||
    result = find.call(styleSheet.cssRules, cssRule => {
 | 
			
		||||
      let found = false
 | 
			
		||||
 | 
			
		||||
      if (cssRule instanceof CSSStyleRule) {
 | 
			
		||||
        for (let i = 0; i < rules.length; i++) {
 | 
			
		||||
          if (cssRule.selectorText.toLowerCase() === rules[i]) {
 | 
			
		||||
            found = true
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return found
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    return result != null
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* * * * * * * * * * * * * * * *
 | 
			
		||||
 *                             *
 | 
			
		||||
 *            BUTTONS          *
 | 
			
		||||
 *                             *
 | 
			
		||||
 * * * * * * * * * * * * * * * */
 | 
			
		||||
var buttons = {
 | 
			
		||||
  previousState: {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
buttons.setLoading = function (name) {
 | 
			
		||||
  if (typeof this[name] === 'undefined') return
 | 
			
		||||
  let i = this[name].querySelector('i')
 | 
			
		||||
 | 
			
		||||
  this.previousState[name] = i.innerHTML
 | 
			
		||||
  i.style.opacity = 0
 | 
			
		||||
 | 
			
		||||
  setTimeout(function () {
 | 
			
		||||
    i.classList.add('spin')
 | 
			
		||||
    i.innerHTML = 'autorenew'
 | 
			
		||||
    i.style.opacity = 1
 | 
			
		||||
  }, 200)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Changes an element to done animation
 | 
			
		||||
buttons.setDone = function (name, success = true) {
 | 
			
		||||
  let i = this[name].querySelector('i')
 | 
			
		||||
 | 
			
		||||
  i.style.opacity = 0
 | 
			
		||||
 | 
			
		||||
  let thirdStep = () => {
 | 
			
		||||
    i.innerHTML = this.previousState[name]
 | 
			
		||||
    i.style.opacity = null
 | 
			
		||||
 | 
			
		||||
    if (selectedItems.length === 0 && document.getElementById('listing')) {
 | 
			
		||||
      document.sendCostumEvent('changed-selected')
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let secondStep = () => {
 | 
			
		||||
    i.style.opacity = 0
 | 
			
		||||
    setTimeout(thirdStep, 200)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let firstStep = () => {
 | 
			
		||||
    i.classList.remove('spin')
 | 
			
		||||
    i.innerHTML = success
 | 
			
		||||
      ? 'done'
 | 
			
		||||
      : 'close'
 | 
			
		||||
    i.style.opacity = 1
 | 
			
		||||
    setTimeout(secondStep, 1000)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setTimeout(firstStep, 200)
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* * * * * * * * * * * * * * * *
 | 
			
		||||
 *                             *
 | 
			
		||||
 *            WEBDAV           *
 | 
			
		||||
 *                             *
 | 
			
		||||
 * * * * * * * * * * * * * * * */
 | 
			
		||||
var webdav = {}
 | 
			
		||||
 | 
			
		||||
webdav.convertURL = function (url) {
 | 
			
		||||
  return window.location.origin + url.replace(baseURL + '/', webdavURL + '/')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
webdav.move = function (oldLink, newLink) {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    let request = new window.XMLHttpRequest()
 | 
			
		||||
    let destination = newLink.replace(baseURL + '/', webdavURL + '/')
 | 
			
		||||
 | 
			
		||||
    destination = window.location.origin + destination.substring(prefixURL.length)
 | 
			
		||||
 | 
			
		||||
    request.open('MOVE', webdav.convertURL(oldLink), true)
 | 
			
		||||
    request.setRequestHeader('Destination', destination)
 | 
			
		||||
    request.onload = () => {
 | 
			
		||||
      if (request.status === 201 || request.status === 204) {
 | 
			
		||||
        resolve()
 | 
			
		||||
      } else {
 | 
			
		||||
        reject(request.statusText)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    request.onerror = () => reject(request.statusText)
 | 
			
		||||
    request.send()
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
webdav.put = function (link, body, headers = {}) {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    let request = new window.XMLHttpRequest()
 | 
			
		||||
    request.open('PUT', webdav.convertURL(link), true)
 | 
			
		||||
 | 
			
		||||
    for (let key in headers) {
 | 
			
		||||
      request.setRequestHeader(key, headers[key])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    request.onload = () => {
 | 
			
		||||
      if (request.status == 201) {
 | 
			
		||||
        resolve()
 | 
			
		||||
      } else {
 | 
			
		||||
        reject(request.statusText)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    request.onerror = () => reject(request.statusText)
 | 
			
		||||
    request.send(body)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
webdav.propfind = function (link, body, headers = {}) {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    let request = new window.XMLHttpRequest()
 | 
			
		||||
    request.open('PROPFIND', webdav.convertURL(link), true)
 | 
			
		||||
 | 
			
		||||
    for (let key in headers) {
 | 
			
		||||
      request.setRequestHeader(key, headers[key])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    request.onload = () => {
 | 
			
		||||
      if (request.status < 300) {
 | 
			
		||||
        resolve(request.responseText)
 | 
			
		||||
      } else {
 | 
			
		||||
        reject(request.statusText)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    request.onerror = () => reject(request.statusText)
 | 
			
		||||
    request.send(body)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
webdav.delete = function (link) {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    let request = new window.XMLHttpRequest()
 | 
			
		||||
    request.open('DELETE', webdav.convertURL(link), true)
 | 
			
		||||
    request.onload = () => {
 | 
			
		||||
      if (request.status === 204) {
 | 
			
		||||
        resolve()
 | 
			
		||||
      } else {
 | 
			
		||||
        reject(request.statusText)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    request.onerror = () => reject(request.statusText)
 | 
			
		||||
    request.send()
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
webdav.new = function (link) {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    let request = new window.XMLHttpRequest()
 | 
			
		||||
    request.open((link.endsWith('/') ? 'MKCOL' : 'PUT'), webdav.convertURL(link), true)
 | 
			
		||||
    request.onload = () => {
 | 
			
		||||
      if (request.status === 201) {
 | 
			
		||||
        resolve()
 | 
			
		||||
      } else {
 | 
			
		||||
        reject(request.statusText)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    request.onerror = () => reject(request.statusText)
 | 
			
		||||
    request.send()
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* * * * * * * * * * * * * * * *
 | 
			
		||||
 *                             *
 | 
			
		||||
 *            EVENTS           *
 | 
			
		||||
 *                             *
 | 
			
		||||
 * * * * * * * * * * * * * * * */
 | 
			
		||||
function closePrompt (event) {
 | 
			
		||||
  let prompt = document.querySelector('.prompt')
 | 
			
		||||
 | 
			
		||||
  if (!prompt) return
 | 
			
		||||
 | 
			
		||||
  if (typeof event !== 'undefined') {
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  document.querySelector('.overlay').classList.remove('active')
 | 
			
		||||
  prompt.classList.remove('active')
 | 
			
		||||
 | 
			
		||||
  setTimeout(() => {
 | 
			
		||||
    prompt.remove()
 | 
			
		||||
  }, 100)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function notImplemented (event) {
 | 
			
		||||
  event.preventDefault()
 | 
			
		||||
  clickOverlay.click()
 | 
			
		||||
 | 
			
		||||
  let clone = document.importNode(templates.message.content, true)
 | 
			
		||||
  clone.querySelector('h3').innerHTML = 'Not implemented'
 | 
			
		||||
  clone.querySelector('p').innerHTML = "Sorry, but this feature wasn't implemented yet."
 | 
			
		||||
 | 
			
		||||
  document.querySelector('body').appendChild(clone)
 | 
			
		||||
  document.querySelector('.overlay').classList.add('active')
 | 
			
		||||
  document.querySelector('.prompt').classList.add('active')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Prevent Default event
 | 
			
		||||
var preventDefault = function (event) {
 | 
			
		||||
  event.preventDefault()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function logoutEvent (event) {
 | 
			
		||||
  let request = new window.XMLHttpRequest()
 | 
			
		||||
  request.open('GET', window.location.pathname, true, 'username', 'password')
 | 
			
		||||
  request.send()
 | 
			
		||||
  request.onreadystatechange = function () {
 | 
			
		||||
    if (request.readyState === 4) {
 | 
			
		||||
      window.location = '/'
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function openEvent (event) {
 | 
			
		||||
  if (event.currentTarget.classList.contains('disabled')) {
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let link = '?raw=true'
 | 
			
		||||
 | 
			
		||||
  if (selectedItems.length) {
 | 
			
		||||
    link = document.getElementById(selectedItems[0]).dataset.url + link
 | 
			
		||||
  } else {
 | 
			
		||||
    link = window.location.pathname + link
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  window.open(link)
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getHash (event, hash) {
 | 
			
		||||
  event.preventDefault()
 | 
			
		||||
 | 
			
		||||
  let request = new window.XMLHttpRequest()
 | 
			
		||||
  let link
 | 
			
		||||
 | 
			
		||||
  if (selectedItems.length) {
 | 
			
		||||
    link = document.getElementById(selectedItems[0]).dataset.url
 | 
			
		||||
  } else {
 | 
			
		||||
    link = window.location.pathname
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  request.open('GET', `${link}?checksum=${hash}`, true)
 | 
			
		||||
 | 
			
		||||
  request.onload = () => {
 | 
			
		||||
    if (request.status >= 300) {
 | 
			
		||||
      console.log(request.statusText)
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    event.target.parentElement.innerHTML = request.responseText
 | 
			
		||||
  }
 | 
			
		||||
  request.onerror = (e) => console.log(e)
 | 
			
		||||
  request.send()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function infoEvent (event) {
 | 
			
		||||
  event.preventDefault()
 | 
			
		||||
  if (event.currentTarget.classList.contains('disabled')) {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let dir = false
 | 
			
		||||
  let link
 | 
			
		||||
 | 
			
		||||
  if (selectedItems.length) {
 | 
			
		||||
    link = document.getElementById(selectedItems[0]).dataset.url
 | 
			
		||||
    dir = document.getElementById(selectedItems[0]).dataset.dir
 | 
			
		||||
  } else {
 | 
			
		||||
    if (document.getElementById('listing') !== null) {
 | 
			
		||||
      dir = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    link = window.location.pathname
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  buttons.setLoading('info', false)
 | 
			
		||||
 | 
			
		||||
  webdav.propfind(link)
 | 
			
		||||
    .then((text) => {
 | 
			
		||||
      let parser = new window.DOMParser()
 | 
			
		||||
      let xml = parser.parseFromString(text, 'text/xml')
 | 
			
		||||
      let clone = document.importNode(templates.info.content, true)
 | 
			
		||||
 | 
			
		||||
      let value = xml.getElementsByTagName('displayname')
 | 
			
		||||
      if (value.length > 0) {
 | 
			
		||||
        clone.getElementById('display_name').innerHTML = value[0].innerHTML
 | 
			
		||||
      } else {
 | 
			
		||||
        clone.getElementById('display_name').innerHTML = xml.getElementsByTagName('D:displayname')[0].innerHTML
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      value = xml.getElementsByTagName('getcontentlength')
 | 
			
		||||
      if (value.length > 0) {
 | 
			
		||||
        clone.getElementById('content_length').innerHTML = value[0].innerHTML
 | 
			
		||||
      } else {
 | 
			
		||||
        clone.getElementById('content_length').innerHTML = xml.getElementsByTagName('D:getcontentlength')[0].innerHTML
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      value = xml.getElementsByTagName('getlastmodified')
 | 
			
		||||
      if (value.length > 0) {
 | 
			
		||||
        clone.getElementById('last_modified').innerHTML = value[0].innerHTML
 | 
			
		||||
      } else {
 | 
			
		||||
        clone.getElementById('last_modified').innerHTML = xml.getElementsByTagName('D:getlastmodified')[0].innerHTML
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (dir === true || dir === 'true') {
 | 
			
		||||
        clone.querySelector('.file-only').style.display = 'none'
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      document.querySelector('body').appendChild(clone)
 | 
			
		||||
      document.querySelector('.overlay').classList.add('active')
 | 
			
		||||
      document.querySelector('.prompt').classList.add('active')
 | 
			
		||||
      buttons.setDone('info', true)
 | 
			
		||||
    })
 | 
			
		||||
    .catch(e => {
 | 
			
		||||
      buttons.setDone('info', false)
 | 
			
		||||
      console.log(e)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function deleteOnSingleFile () {
 | 
			
		||||
  closePrompt()
 | 
			
		||||
  buttons.setLoading('delete')
 | 
			
		||||
 | 
			
		||||
  webdav.delete(window.location.pathname)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      window.location.pathname = removeLastDirectoryPartOf(window.location.pathname)
 | 
			
		||||
    })
 | 
			
		||||
    .catch(e => {
 | 
			
		||||
      buttons.setDone('delete', false)
 | 
			
		||||
      console.log(e)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function deleteOnListing () {
 | 
			
		||||
  closePrompt()
 | 
			
		||||
  buttons.setLoading('delete')
 | 
			
		||||
 | 
			
		||||
  let promises = []
 | 
			
		||||
 | 
			
		||||
  for (let id of selectedItems) {
 | 
			
		||||
    promises.push(webdav.delete(document.getElementById(id).dataset.url))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Promise.all(promises)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      listing.reload()
 | 
			
		||||
      buttons.setDone('delete')
 | 
			
		||||
    })
 | 
			
		||||
    .catch(e => {
 | 
			
		||||
      console.log(e)
 | 
			
		||||
      buttons.setDone('delete', false)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Handles the delete button event
 | 
			
		||||
function deleteEvent (event) {
 | 
			
		||||
  let single = false
 | 
			
		||||
 | 
			
		||||
  if (!selectedItems.length) {
 | 
			
		||||
    selectedItems = ['placeholder']
 | 
			
		||||
    single = true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let clone = document.importNode(templates.question.content, true)
 | 
			
		||||
  clone.querySelector('h3').innerHTML = 'Delete files'
 | 
			
		||||
 | 
			
		||||
  if (single) {
 | 
			
		||||
    clone.querySelector('form').addEventListener('submit', deleteOnSingleFile)
 | 
			
		||||
    clone.querySelector('p').innerHTML = `Are you sure you want to delete this file/folder?`
 | 
			
		||||
  } else {
 | 
			
		||||
    clone.querySelector('form').addEventListener('submit', deleteOnListing)
 | 
			
		||||
    clone.querySelector('p').innerHTML = `Are you sure you want to delete ${selectedItems.length} file(s)?`
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clone.querySelector('input').remove()
 | 
			
		||||
  clone.querySelector('.ok').innerHTML = 'Delete'
 | 
			
		||||
 | 
			
		||||
  document.body.appendChild(clone)
 | 
			
		||||
  document.querySelector('.overlay').classList.add('active')
 | 
			
		||||
  document.querySelector('.prompt').classList.add('active')
 | 
			
		||||
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function resetSearchText () {
 | 
			
		||||
  let box = document.querySelector('#search > div div')
 | 
			
		||||
 | 
			
		||||
  if (user.AllowCommands) {
 | 
			
		||||
    box.innerHTML = `Search or use one of your supported commands: ${user.Commands.join(", ")}.`
 | 
			
		||||
  } else {
 | 
			
		||||
    box.innerHTML = 'Type and press enter to search.'
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function searchEvent (event) {
 | 
			
		||||
  if (this.value.length === 0) {
 | 
			
		||||
    resetSearchText()
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let value = this.value,
 | 
			
		||||
    search = document.getElementById('search'),
 | 
			
		||||
    scrollable = document.querySelector('#search > div'),
 | 
			
		||||
    box = document.querySelector('#search > div div'),
 | 
			
		||||
    pieces = value.split(' '),
 | 
			
		||||
    supported = false
 | 
			
		||||
 | 
			
		||||
  user.Commands.forEach(function (cmd) {
 | 
			
		||||
    if (cmd == pieces[0]) {
 | 
			
		||||
      supported = true
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  if (!supported || !user.AllowCommands) {
 | 
			
		||||
    box.innerHTML = 'Press enter to search.'
 | 
			
		||||
  } else {
 | 
			
		||||
    box.innerHTML = 'Press enter to execute.'
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (event.keyCode === 13) {
 | 
			
		||||
    box.innerHTML = ''
 | 
			
		||||
    search.classList.add('ongoing')
 | 
			
		||||
 | 
			
		||||
    let url = window.location.host + window.location.pathname
 | 
			
		||||
 | 
			
		||||
    if (document.getElementById('editor')) {
 | 
			
		||||
      url = removeLastDirectoryPartOf(url)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let protocol = ssl ? 'wss:' : 'ws:'
 | 
			
		||||
 | 
			
		||||
    if (supported && user.AllowCommands) {
 | 
			
		||||
      let conn = new window.WebSocket(`${protocol}//${url}?command=true`)
 | 
			
		||||
 | 
			
		||||
      conn.onopen = function () {
 | 
			
		||||
        conn.send(value)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      conn.onmessage = function (event) {
 | 
			
		||||
        box.innerHTML = event.data
 | 
			
		||||
        scrollable.scrollTop = scrollable.scrollHeight
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      conn.onclose = function (event) {
 | 
			
		||||
        search.classList.remove('ongoing')
 | 
			
		||||
        listing.reload()
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    box.innerHTML = '<ul></ul>'
 | 
			
		||||
 | 
			
		||||
    let ul = box.querySelector('ul')
 | 
			
		||||
    let conn = new window.WebSocket(`${protocol}//${url}?search=true`)
 | 
			
		||||
 | 
			
		||||
    conn.onopen = function () {
 | 
			
		||||
      conn.send(value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    conn.onmessage = function (event) {
 | 
			
		||||
      ul.innerHTML += '<li><a href="' + event.data + '">' + event.data + '</a></li>'
 | 
			
		||||
      scrollable.scrollTop = scrollable.scrollHeight
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    conn.onclose = function (event) {
 | 
			
		||||
      search.classList.remove('ongoing')
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setupSearch () {
 | 
			
		||||
  let search = document.getElementById('search')
 | 
			
		||||
  let searchInput = search.querySelector('input')
 | 
			
		||||
  let searchDiv = search.querySelector('div')
 | 
			
		||||
  let hover = false
 | 
			
		||||
  let focus = false
 | 
			
		||||
 | 
			
		||||
  resetSearchText()
 | 
			
		||||
 | 
			
		||||
  searchInput.addEventListener('focus', event => {
 | 
			
		||||
    focus = true
 | 
			
		||||
    search.classList.add('active')
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  searchDiv.addEventListener('mouseover', event => {
 | 
			
		||||
    hover = true
 | 
			
		||||
    search.classList.add('active')
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  searchInput.addEventListener('blur', event => {
 | 
			
		||||
    focus = false
 | 
			
		||||
    if (hover) return
 | 
			
		||||
    search.classList.remove('active')
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  search.addEventListener('mouseleave', event => {
 | 
			
		||||
    hover = false
 | 
			
		||||
    if (focus) return
 | 
			
		||||
    search.classList.remove('active')
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  search.addEventListener('click', event => {
 | 
			
		||||
    search.classList.add('active')
 | 
			
		||||
    search.querySelector('input').focus()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  searchInput.addEventListener('keyup', searchEvent)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function closeHelp (event) {
 | 
			
		||||
  event.preventDefault()
 | 
			
		||||
 | 
			
		||||
  document.querySelector('.help').classList.remove('active')
 | 
			
		||||
  document.querySelector('.overlay').classList.remove('active')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function openHelp (event) {
 | 
			
		||||
  closePrompt(event)
 | 
			
		||||
 | 
			
		||||
  document.querySelector('.help').classList.add('active')
 | 
			
		||||
  document.querySelector('.overlay').classList.add('active')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
window.addEventListener('keydown', (event) => {
 | 
			
		||||
  if (event.keyCode === 27) {
 | 
			
		||||
    if (document.querySelector('.help.active')) {
 | 
			
		||||
      closeHelp(event)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (event.keyCode === 46) {
 | 
			
		||||
    deleteEvent(event)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (event.keyCode === 112) {
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
    openHelp(event)
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
/* * * * * * * * * * * * * * * *
 | 
			
		||||
 *                             *
 | 
			
		||||
 *           BOOTSTRAP         *
 | 
			
		||||
 *                             *
 | 
			
		||||
 * * * * * * * * * * * * * * * */
 | 
			
		||||
 | 
			
		||||
document.addEventListener('DOMContentLoaded', function (event) {
 | 
			
		||||
  overlay = document.querySelector('.overlay')
 | 
			
		||||
  clickOverlay = document.querySelector('#click-overlay')
 | 
			
		||||
 | 
			
		||||
  buttons.logout = document.getElementById('logout')
 | 
			
		||||
  buttons.open = document.getElementById('open')
 | 
			
		||||
  buttons.delete = document.getElementById('delete')
 | 
			
		||||
  buttons.previous = document.getElementById('previous')
 | 
			
		||||
  buttons.info = document.getElementById('info')
 | 
			
		||||
 | 
			
		||||
  // Attach event listeners
 | 
			
		||||
  buttons.logout.addEventListener('click', logoutEvent)
 | 
			
		||||
  buttons.open.addEventListener('click', openEvent)
 | 
			
		||||
  buttons.info.addEventListener('click', infoEvent)
 | 
			
		||||
 | 
			
		||||
  templates.question = document.querySelector('#question-template')
 | 
			
		||||
  templates.info = document.querySelector('#info-template')
 | 
			
		||||
  templates.message = document.querySelector('#message-template')
 | 
			
		||||
  templates.move = document.querySelector('#move-template')
 | 
			
		||||
 | 
			
		||||
  if (user.AllowEdit) {
 | 
			
		||||
    buttons.delete.addEventListener('click', deleteEvent)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let dropdownButtons = document.querySelectorAll('.action[data-dropdown]')
 | 
			
		||||
  Array.from(dropdownButtons).forEach(button => {
 | 
			
		||||
    button.addEventListener('click', event => {
 | 
			
		||||
      button.querySelector('ul').classList.toggle('active')
 | 
			
		||||
      clickOverlay.classList.add('active')
 | 
			
		||||
 | 
			
		||||
      clickOverlay.addEventListener('click', event => {
 | 
			
		||||
        button.querySelector('ul').classList.remove('active')
 | 
			
		||||
        clickOverlay.classList.remove('active')
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  overlay.addEventListener('click', event => {
 | 
			
		||||
    if (document.querySelector('.help.active')) {
 | 
			
		||||
      closeHelp(event)
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    closePrompt(event)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  let mainActions = document.getElementById('main-actions')
 | 
			
		||||
 | 
			
		||||
  document.getElementById('more').addEventListener('click', event => {
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
    event.stopPropagation()
 | 
			
		||||
 | 
			
		||||
    clickOverlay.classList.add('active')
 | 
			
		||||
    mainActions.classList.add('active')
 | 
			
		||||
 | 
			
		||||
    clickOverlay.addEventListener('click', event => {
 | 
			
		||||
      mainActions.classList.remove('active')
 | 
			
		||||
      clickOverlay.classList.remove('active')
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  setupSearch()
 | 
			
		||||
  return false
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,278 @@
 | 
			
		|||
'use strict'
 | 
			
		||||
 | 
			
		||||
var editor = {}
 | 
			
		||||
 | 
			
		||||
editor.textareaAutoGrow = function () {
 | 
			
		||||
  let autogrow = function () {
 | 
			
		||||
    console.log(this.style.height)
 | 
			
		||||
    this.style.height = 'auto'
 | 
			
		||||
    this.style.height = (this.scrollHeight) + 'px'
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let textareas = document.getElementsByTagName('textarea')
 | 
			
		||||
 | 
			
		||||
  let addAutoGrow = () => {
 | 
			
		||||
    Array.from(textareas).forEach(textarea => {
 | 
			
		||||
      autogrow.bind(textarea)()
 | 
			
		||||
      textarea.addEventListener('keyup', autogrow)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addAutoGrow()
 | 
			
		||||
  window.addEventListener('resize', addAutoGrow)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
editor.toggleSourceEditor = function (event) {
 | 
			
		||||
  event.preventDefault()
 | 
			
		||||
 | 
			
		||||
  if (document.querySelector('[data-kind="content-only"]')) {
 | 
			
		||||
    window.location = window.location.pathname + '?visual=true'
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  window.location = window.location.pathname + '?visual=false'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function deleteFrontMatterItem (event) {
 | 
			
		||||
  event.preventDefault()
 | 
			
		||||
  document.getElementById(this.dataset.delete).remove()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function makeFromBaseTemplate (id, type, name, parent) {
 | 
			
		||||
  let clone = document.importNode(templates.base.content, true)
 | 
			
		||||
  clone.querySelector('fieldset').id = id
 | 
			
		||||
  clone.querySelector('fieldset').dataset.type = type
 | 
			
		||||
  clone.querySelector('h3').innerHTML = name
 | 
			
		||||
  clone.querySelector('.delete').dataset.delete = id
 | 
			
		||||
  clone.querySelector('.delete').addEventListener('click', deleteFrontMatterItem)
 | 
			
		||||
  clone.querySelector('.add').addEventListener('click', addFrontMatterItem)
 | 
			
		||||
 | 
			
		||||
  if (parent.classList.contains('frontmatter')) {
 | 
			
		||||
    parent.insertBefore(clone, document.querySelector('div.button.add'))
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  parent.appendChild(clone)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function makeFromArrayItemTemplate (id, number, parent) {
 | 
			
		||||
  let clone = document.importNode(templates.arrayItem.content, true)
 | 
			
		||||
  clone.querySelector('[data-type="array-item"]').id = `${id}-${number}`
 | 
			
		||||
  clone.querySelector('input').name = id
 | 
			
		||||
  clone.querySelector('input').id = id
 | 
			
		||||
  clone.querySelector('div.action').dataset.delete = `${id}-${number}`
 | 
			
		||||
  clone.querySelector('div.action').addEventListener('click', deleteFrontMatterItem)
 | 
			
		||||
  parent.querySelector('.group').appendChild(clone)
 | 
			
		||||
  document.getElementById(`${id}-${number}`).querySelector('input').focus()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function makeFromObjectItemTemplate (id, name, parent) {
 | 
			
		||||
  let clone = document.importNode(templates.objectItem.content, true)
 | 
			
		||||
  clone.querySelector('.block').id = `block-${id}`
 | 
			
		||||
  clone.querySelector('.block').dataset.content = id
 | 
			
		||||
  clone.querySelector('label').for = id
 | 
			
		||||
  clone.querySelector('label').innerHTML = name
 | 
			
		||||
  clone.querySelector('input').name = id
 | 
			
		||||
  clone.querySelector('input').id = id
 | 
			
		||||
  clone.querySelector('.action').dataset.delete = `block-${id}`
 | 
			
		||||
  clone.querySelector('.action').addEventListener('click', deleteFrontMatterItem)
 | 
			
		||||
 | 
			
		||||
  parent.appendChild(clone)
 | 
			
		||||
  document.getElementById(id).focus()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function addFrontMatterItemPrompt (parent) {
 | 
			
		||||
  return function (event) {
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
 | 
			
		||||
    let value = event.currentTarget.querySelector('input').value
 | 
			
		||||
    if (value === '') {
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    closePrompt(event)
 | 
			
		||||
 | 
			
		||||
    let name = value.substring(0, value.lastIndexOf(':')),
 | 
			
		||||
      type = value.substring(value.lastIndexOf(':') + 1, value.length)
 | 
			
		||||
 | 
			
		||||
    if (type !== '' && type !== 'array' && type !== 'object') {
 | 
			
		||||
      name = value
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    name = name.replace(' ', '_')
 | 
			
		||||
 | 
			
		||||
    let id = name
 | 
			
		||||
 | 
			
		||||
    if (parent.id != '') {
 | 
			
		||||
      id = parent.id + '.' + id
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (type == 'array' || type == 'object') {
 | 
			
		||||
      if (parent.dataset.type == 'parent') {
 | 
			
		||||
        makeFromBaseTemplate(id, type, name, document.querySelector('.frontmatter'))
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      makeFromBaseTemplate(id, type, name, block)
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let group = parent.querySelector('.group')
 | 
			
		||||
 | 
			
		||||
    if (group == null) {
 | 
			
		||||
      parent.insertAdjacentHTML('afterbegin', '<div class="group"></div>')
 | 
			
		||||
      group = parent.querySelector('.group')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    makeFromObjectItemTemplate(id, name, group)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function addFrontMatterItem (event) {
 | 
			
		||||
  event.preventDefault()
 | 
			
		||||
 | 
			
		||||
  let parent = event.currentTarget.parentNode,
 | 
			
		||||
    type = parent.dataset.type
 | 
			
		||||
 | 
			
		||||
  // If the block is an array
 | 
			
		||||
  if (type === 'array') {
 | 
			
		||||
    let id = parent.id + '[]',
 | 
			
		||||
      count = parent.querySelectorAll('.group > div').length,
 | 
			
		||||
      fieldsets = parent.getElementsByTagName('fieldset')
 | 
			
		||||
 | 
			
		||||
    if (fieldsets.length > 0) {
 | 
			
		||||
      let itemType = fieldsets[0].dataset.type,
 | 
			
		||||
        itemID = parent.id + '[' + fieldsets.length + ']',
 | 
			
		||||
        itemName = fieldsets.length
 | 
			
		||||
 | 
			
		||||
      makeFromBaseTemplate(itemID, itemType, itemName, parent)
 | 
			
		||||
    } else {
 | 
			
		||||
      makeFromArrayItemTemplate(id, count, parent)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (type == 'object' || type == 'parent') {
 | 
			
		||||
    let clone = document.importNode(templates.question.content, true)
 | 
			
		||||
    clone.querySelector('form').id = tempID
 | 
			
		||||
    clone.querySelector('h3').innerHTML = 'New field'
 | 
			
		||||
    clone.querySelector('p').innerHTML = 'Write the field name and then press enter. If you want to create an array or an object, end the name with <code>:array</code> or <code>:object.</code>'
 | 
			
		||||
    clone.querySelector('.ok').innerHTML = 'Create'
 | 
			
		||||
    clone.querySelector('form').addEventListener('submit', addFrontMatterItemPrompt(parent))
 | 
			
		||||
    clone.querySelector('form').classList.add('active')
 | 
			
		||||
    document.querySelector('body').appendChild(clone)
 | 
			
		||||
 | 
			
		||||
    document.querySelector('.overlay').classList.add('active')
 | 
			
		||||
    document.getElementById(tempID).classList.add('active')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
document.addEventListener('DOMContentLoaded', (event) => {
 | 
			
		||||
  if (!document.getElementById('editor')) return
 | 
			
		||||
 | 
			
		||||
  editor.textareaAutoGrow()
 | 
			
		||||
 | 
			
		||||
  templates.arrayItem = document.getElementById('array-item-template')
 | 
			
		||||
  templates.base = document.getElementById('base-template')
 | 
			
		||||
  templates.objectItem = document.getElementById('object-item-template')
 | 
			
		||||
  templates.temporary = document.getElementById('temporary-template')
 | 
			
		||||
 | 
			
		||||
  buttons.save = document.querySelector('#save')
 | 
			
		||||
  buttons.editSource = document.querySelector('#edit-source')
 | 
			
		||||
 | 
			
		||||
  if (buttons.editSource) {
 | 
			
		||||
    buttons.editSource.addEventListener('click', editor.toggleSourceEditor)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let container = document.getElementById('editor'),
 | 
			
		||||
    kind = container.dataset.kind,
 | 
			
		||||
    rune = container.dataset.rune
 | 
			
		||||
 | 
			
		||||
  if (kind != 'frontmatter-only') {
 | 
			
		||||
    let editor = document.querySelector('.content #ace'),
 | 
			
		||||
      mode = editor.dataset.mode,
 | 
			
		||||
      textarea = document.querySelector('textarea[name="content"]'),
 | 
			
		||||
      aceEditor = ace.edit('ace'),
 | 
			
		||||
      options = {
 | 
			
		||||
        wrap: true,
 | 
			
		||||
        maxLines: Infinity,
 | 
			
		||||
        theme: 'ace/theme/github',
 | 
			
		||||
        showPrintMargin: false,
 | 
			
		||||
        fontSize: '1em',
 | 
			
		||||
        minLines: 20
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    aceEditor.getSession().setMode('ace/mode/' + mode)
 | 
			
		||||
    aceEditor.getSession().setValue(textarea.value)
 | 
			
		||||
    aceEditor.getSession().on('change', function () {
 | 
			
		||||
      textarea.value = aceEditor.getSession().getValue()
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    if (mode == 'markdown') options.showGutter = false
 | 
			
		||||
    aceEditor.setOptions(options)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let deleteFrontMatterItemButtons = document.getElementsByClassName('delete')
 | 
			
		||||
  Array.from(deleteFrontMatterItemButtons).forEach(button => {
 | 
			
		||||
    button.addEventListener('click', deleteFrontMatterItem)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  let addFrontMatterItemButtons = document.getElementsByClassName('add')
 | 
			
		||||
  Array.from(addFrontMatterItemButtons).forEach(button => {
 | 
			
		||||
    button.addEventListener('click', addFrontMatterItem)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  let saveContent = function () {
 | 
			
		||||
    let data = form2js(document.querySelector('form'))
 | 
			
		||||
 | 
			
		||||
    if (typeof data.content === 'undefined' && kind !== 'frontmatter-only') {
 | 
			
		||||
      data.content = ''
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (typeof data.content === 'number') {
 | 
			
		||||
      data.content = data.content.toString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let request = new XMLHttpRequest()
 | 
			
		||||
 | 
			
		||||
    buttons.setLoading('save')
 | 
			
		||||
 | 
			
		||||
    webdav.put(window.location.pathname, JSON.stringify(data), {
 | 
			
		||||
      'Kind': kind,
 | 
			
		||||
      'Rune': rune
 | 
			
		||||
    })
 | 
			
		||||
      .then(() => {
 | 
			
		||||
        buttons.setDone('save')
 | 
			
		||||
      })
 | 
			
		||||
      .catch(e => {
 | 
			
		||||
        console.log(e)
 | 
			
		||||
        buttons.setDone('save', false)
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  document.querySelector('#save').addEventListener('click', event => {
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
    saveContent()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  document.querySelector('form').addEventListener('submit', (event) => {
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
    saveContent()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  window.addEventListener('keydown', (event) => {
 | 
			
		||||
    if (event.ctrlKey || event.metaKey) {
 | 
			
		||||
      switch (String.fromCharCode(event.which).toLowerCase()) {
 | 
			
		||||
        case 's':
 | 
			
		||||
          event.preventDefault()
 | 
			
		||||
          saveContent()
 | 
			
		||||
          break
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return false
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,580 @@
 | 
			
		|||
'use strict'
 | 
			
		||||
 | 
			
		||||
var listing = {
 | 
			
		||||
  selectMultiple: false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.reload = function (callback) {
 | 
			
		||||
  let request = new XMLHttpRequest()
 | 
			
		||||
 | 
			
		||||
  request.open('GET', window.location)
 | 
			
		||||
  request.setRequestHeader('Minimal', 'true')
 | 
			
		||||
  request.send()
 | 
			
		||||
  request.onreadystatechange = function () {
 | 
			
		||||
    if (request.readyState === 4) {
 | 
			
		||||
      if (request.status === 200) {
 | 
			
		||||
        document.querySelector('body main').innerHTML = request.responseText
 | 
			
		||||
        listing.addDoubleTapEvent()
 | 
			
		||||
 | 
			
		||||
        if (typeof callback === 'function') {
 | 
			
		||||
          callback()
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.itemDragStart = function (event) {
 | 
			
		||||
  let el = event.target
 | 
			
		||||
 | 
			
		||||
  for (let i = 0; i < 5; i++) {
 | 
			
		||||
    if (!el.classList.contains('item')) {
 | 
			
		||||
      el = el.parentElement
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  event.dataTransfer.setData('id', el.id)
 | 
			
		||||
  event.dataTransfer.setData('name', el.querySelector('.name').innerHTML)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.itemDragOver = function (event) {
 | 
			
		||||
  event.preventDefault()
 | 
			
		||||
  let el = event.target
 | 
			
		||||
 | 
			
		||||
  for (let i = 0; i < 5; i++) {
 | 
			
		||||
    if (!el.classList.contains('item')) {
 | 
			
		||||
      el = el.parentElement
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  el.style.opacity = 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.itemDrop = function (e) {
 | 
			
		||||
  e.preventDefault()
 | 
			
		||||
 | 
			
		||||
  let el = e.target,
 | 
			
		||||
    id = e.dataTransfer.getData('id'),
 | 
			
		||||
    name = e.dataTransfer.getData('name')
 | 
			
		||||
 | 
			
		||||
  if (id == '' || name == '') return
 | 
			
		||||
 | 
			
		||||
  for (let i = 0; i < 5; i++) {
 | 
			
		||||
    if (!el.classList.contains('item')) {
 | 
			
		||||
      el = el.parentElement
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (el.id === id) return
 | 
			
		||||
 | 
			
		||||
  let oldLink = document.getElementById(id).dataset.url,
 | 
			
		||||
    newLink = el.dataset.url + name
 | 
			
		||||
 | 
			
		||||
  webdav.move(oldLink, newLink)
 | 
			
		||||
    .then(() => listing.reload())
 | 
			
		||||
    .catch(e => console.log(e))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.documentDrop = function (event) {
 | 
			
		||||
  event.preventDefault()
 | 
			
		||||
  let dt = event.dataTransfer,
 | 
			
		||||
    files = dt.files,
 | 
			
		||||
    el = event.target,
 | 
			
		||||
    items = document.getElementsByClassName('item')
 | 
			
		||||
 | 
			
		||||
  for (let i = 0; i < 5; i++) {
 | 
			
		||||
    if (el != null && !el.classList.contains('item')) {
 | 
			
		||||
      el = el.parentElement
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (files.length > 0) {
 | 
			
		||||
    if (el != null && el.classList.contains('item') && el.dataset.dir == 'true') {
 | 
			
		||||
      listing.handleFiles(files, el.querySelector('.name').innerHTML + '/')
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    listing.handleFiles(files, '')
 | 
			
		||||
  } else {
 | 
			
		||||
    Array.from(items).forEach(file => {
 | 
			
		||||
      file.style.opacity = 1
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.rename = function (event) {
 | 
			
		||||
  if (!selectedItems.length || selectedItems.length > 1) {
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let item = document.getElementById(selectedItems[0])
 | 
			
		||||
 | 
			
		||||
  if (item.classList.contains('disabled')) {
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let link = item.dataset.url,
 | 
			
		||||
    field = item.querySelector('.name'),
 | 
			
		||||
    name = field.innerHTML
 | 
			
		||||
 | 
			
		||||
  let submit = (event) => {
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
 | 
			
		||||
    let newName = event.currentTarget.querySelector('input').value,
 | 
			
		||||
      newLink = removeLastDirectoryPartOf(link) + '/' + newName
 | 
			
		||||
 | 
			
		||||
    closePrompt(event)
 | 
			
		||||
    buttons.setLoading('rename')
 | 
			
		||||
 | 
			
		||||
    webdav.move(link, newLink).then(() => {
 | 
			
		||||
      listing.reload(() => {
 | 
			
		||||
        newName = btoa(newName)
 | 
			
		||||
        selectedItems = [newName]
 | 
			
		||||
        document.getElementById(newName).setAttribute('aria-selected', true)
 | 
			
		||||
        listing.handleSelectionChange()
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      buttons.setDone('rename')
 | 
			
		||||
    }).catch(error => {
 | 
			
		||||
      field.innerHTML = name
 | 
			
		||||
      buttons.setDone('rename', false)
 | 
			
		||||
      console.log(error)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let clone = document.importNode(templates.question.content, true)
 | 
			
		||||
  clone.querySelector('h3').innerHTML = 'Rename'
 | 
			
		||||
  clone.querySelector('input').value = name
 | 
			
		||||
  clone.querySelector('.ok').innerHTML = 'Rename'
 | 
			
		||||
  clone.querySelector('form').addEventListener('submit', submit)
 | 
			
		||||
 | 
			
		||||
  document.querySelector('body').appendChild(clone)
 | 
			
		||||
  document.querySelector('.overlay').classList.add('active')
 | 
			
		||||
  document.querySelector('.prompt').classList.add('active')
 | 
			
		||||
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.handleFiles = function (files, base) {
 | 
			
		||||
  buttons.setLoading('upload')
 | 
			
		||||
 | 
			
		||||
  let promises = []
 | 
			
		||||
 | 
			
		||||
  for (let file of files) {
 | 
			
		||||
    promises.push(webdav.put(window.location.pathname + base + file.name, file))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Promise.all(promises)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      listing.reload()
 | 
			
		||||
      buttons.setDone('upload')
 | 
			
		||||
    })
 | 
			
		||||
    .catch(e => {
 | 
			
		||||
      console.log(e)
 | 
			
		||||
      buttons.setDone('upload', false)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.unselectAll = function () {
 | 
			
		||||
  let items = document.getElementsByClassName('item')
 | 
			
		||||
  Array.from(items).forEach(link => {
 | 
			
		||||
    link.setAttribute('aria-selected', false)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  selectedItems = []
 | 
			
		||||
 | 
			
		||||
  listing.handleSelectionChange()
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.handleSelectionChange = function (event) {
 | 
			
		||||
  listing.redefineDownloadURLs()
 | 
			
		||||
 | 
			
		||||
  let selectedNumber = selectedItems.length,
 | 
			
		||||
    fileAction = document.getElementById('file-only')
 | 
			
		||||
 | 
			
		||||
  if (selectedNumber) {
 | 
			
		||||
    fileAction.classList.remove('disabled')
 | 
			
		||||
 | 
			
		||||
    if (selectedNumber > 1) {
 | 
			
		||||
      buttons.open.classList.add('disabled')
 | 
			
		||||
      buttons.rename.classList.add('disabled')
 | 
			
		||||
      buttons.info.classList.add('disabled')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (selectedNumber == 1) {
 | 
			
		||||
      if (document.getElementById(selectedItems[0]).dataset.dir == 'true') {
 | 
			
		||||
        buttons.open.classList.add('disabled')
 | 
			
		||||
      } else {
 | 
			
		||||
        buttons.open.classList.remove('disabled')
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      buttons.info.classList.remove('disabled')
 | 
			
		||||
      buttons.rename.classList.remove('disabled')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  buttons.info.classList.remove('disabled')
 | 
			
		||||
  fileAction.classList.add('disabled')
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.redefineDownloadURLs = function () {
 | 
			
		||||
  let files = ''
 | 
			
		||||
 | 
			
		||||
  for (let i = 0; i < selectedItems.length; i++) {
 | 
			
		||||
    let url = document.getElementById(selectedItems[i]).dataset.url
 | 
			
		||||
    files += url.replace(window.location.pathname, '') + ','
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  files = files.substring(0, files.length - 1)
 | 
			
		||||
  files = encodeURIComponent(files)
 | 
			
		||||
 | 
			
		||||
  let links = document.querySelectorAll('#download ul a')
 | 
			
		||||
  Array.from(links).forEach(link => {
 | 
			
		||||
    link.href = '?download=' + link.dataset.format + '&files=' + files
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.openItem = function (event) {
 | 
			
		||||
  window.location = event.currentTarget.dataset.url
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.selectItem = function (event) {
 | 
			
		||||
  let el = event.currentTarget
 | 
			
		||||
 | 
			
		||||
  if (selectedItems.length != 0) event.preventDefault()
 | 
			
		||||
  if (selectedItems.indexOf(el.id) == -1) {
 | 
			
		||||
    if (!event.ctrlKey && !listing.selectMultiple) listing.unselectAll()
 | 
			
		||||
 | 
			
		||||
    el.setAttribute('aria-selected', true)
 | 
			
		||||
    selectedItems.push(el.id)
 | 
			
		||||
  } else {
 | 
			
		||||
    el.setAttribute('aria-selected', false)
 | 
			
		||||
    selectedItems.removeElement(el.id)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  listing.handleSelectionChange()
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.newFileButton = function (event) {
 | 
			
		||||
  event.preventDefault()
 | 
			
		||||
 | 
			
		||||
  let clone = document.importNode(templates.question.content, true)
 | 
			
		||||
  clone.querySelector('h3').innerHTML = 'New file'
 | 
			
		||||
  clone.querySelector('p').innerHTML = 'End with a trailing slash to create a dir.'
 | 
			
		||||
  clone.querySelector('.ok').innerHTML = 'Create'
 | 
			
		||||
  clone.querySelector('form').addEventListener('submit', listing.newFilePrompt)
 | 
			
		||||
 | 
			
		||||
  document.querySelector('body').appendChild(clone)
 | 
			
		||||
  document.querySelector('.overlay').classList.add('active')
 | 
			
		||||
  document.querySelector('.prompt').classList.add('active')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.newFilePrompt = function (event) {
 | 
			
		||||
  event.preventDefault()
 | 
			
		||||
  buttons.setLoading('new')
 | 
			
		||||
 | 
			
		||||
  let name = event.currentTarget.querySelector('input').value
 | 
			
		||||
 | 
			
		||||
  webdav.new(window.location.pathname + name)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      buttons.setDone('new')
 | 
			
		||||
      listing.reload()
 | 
			
		||||
    })
 | 
			
		||||
    .catch(e => {
 | 
			
		||||
      console.log(e)
 | 
			
		||||
      buttons.setDone('new', false)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
  closePrompt(event)
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.updateColumns = function (event) {
 | 
			
		||||
  let columns = Math.floor(document.getElementById('listing').offsetWidth / 300),
 | 
			
		||||
    items = getCSSRule(['#listing.mosaic .item', '.mosaic#listing .item'])
 | 
			
		||||
 | 
			
		||||
  items.style.width = `calc(${100/columns}% - 1em)`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.addDoubleTapEvent = function () {
 | 
			
		||||
  let items = document.getElementsByClassName('item'),
 | 
			
		||||
    touches = {
 | 
			
		||||
      id: '',
 | 
			
		||||
      count: 0
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Array.from(items).forEach(file => {
 | 
			
		||||
    file.addEventListener('touchstart', event => {
 | 
			
		||||
      if (touches.id != file.id) {
 | 
			
		||||
        touches.id = file.id
 | 
			
		||||
        touches.count = 1
 | 
			
		||||
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          touches.count = 0
 | 
			
		||||
        }, 300)
 | 
			
		||||
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      touches.count++
 | 
			
		||||
 | 
			
		||||
      if (touches.count > 1) {
 | 
			
		||||
        window.location = file.dataset.url
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Keydown events
 | 
			
		||||
window.addEventListener('keydown', (event) => {
 | 
			
		||||
  if (event.keyCode == 27) {
 | 
			
		||||
    listing.unselectAll()
 | 
			
		||||
 | 
			
		||||
    if (document.querySelectorAll('.prompt').length) {
 | 
			
		||||
      closePrompt(event)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (event.keyCode == 113) {
 | 
			
		||||
    listing.rename()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (event.ctrlKey || event.metaKey) {
 | 
			
		||||
    switch (String.fromCharCode(event.which).toLowerCase()) {
 | 
			
		||||
      case 's':
 | 
			
		||||
        event.preventDefault()
 | 
			
		||||
        window.location = '?download=true'
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
window.addEventListener('resize', () => {
 | 
			
		||||
  listing.updateColumns()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
listing.selectMoveFolder = function (event) {
 | 
			
		||||
  if (event.target.getAttribute('aria-selected') === 'true') {
 | 
			
		||||
    event.target.setAttribute('aria-selected', false)
 | 
			
		||||
    return
 | 
			
		||||
  } else {
 | 
			
		||||
    if (document.querySelector('.file-list li[aria-selected=true]')) {
 | 
			
		||||
      document.querySelector('.file-list li[aria-selected=true]').setAttribute('aria-selected', false)
 | 
			
		||||
    }
 | 
			
		||||
    event.target.setAttribute('aria-selected', true)
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.getJSON = function (link) {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    let request = new XMLHttpRequest()
 | 
			
		||||
    request.open('GET', link)
 | 
			
		||||
    request.setRequestHeader('Accept', 'application/json')
 | 
			
		||||
    request.onload = () => {
 | 
			
		||||
      if (request.status == 200) {
 | 
			
		||||
        resolve(request.responseText)
 | 
			
		||||
      } else {
 | 
			
		||||
        reject(request.statusText)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    request.onerror = () => reject(request.statusText)
 | 
			
		||||
    request.send()
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.moveMakeItem = function (url, name) {
 | 
			
		||||
  let node = document.createElement('li'),
 | 
			
		||||
    count = 0
 | 
			
		||||
 | 
			
		||||
  node.dataset.url = url
 | 
			
		||||
  node.innerHTML = name
 | 
			
		||||
  node.setAttribute('aria-selected', false)
 | 
			
		||||
 | 
			
		||||
  node.addEventListener('dblclick', listing.moveDialogNext)
 | 
			
		||||
  node.addEventListener('click', listing.selectMoveFolder)
 | 
			
		||||
  node.addEventListener('touchstart', event => {
 | 
			
		||||
    count++
 | 
			
		||||
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      count = 0
 | 
			
		||||
    }, 300)
 | 
			
		||||
 | 
			
		||||
    if (count > 1) {
 | 
			
		||||
      listing.moveDialogNext(event)
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return node
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.moveDialogNext = function (event) {
 | 
			
		||||
  let request = new XMLHttpRequest(),
 | 
			
		||||
    prompt = document.querySelector('form.prompt.active'),
 | 
			
		||||
    list = prompt.querySelector('div.file-list ul')
 | 
			
		||||
 | 
			
		||||
  prompt.addEventListener('submit', listing.moveSelected)
 | 
			
		||||
 | 
			
		||||
  listing.getJSON(event.target.dataset.url)
 | 
			
		||||
    .then((data) => {
 | 
			
		||||
      let dirs = 0
 | 
			
		||||
 | 
			
		||||
      prompt.querySelector('ul').innerHTML = ''
 | 
			
		||||
      prompt.querySelector('code').innerHTML = event.target.dataset.url
 | 
			
		||||
 | 
			
		||||
      if (event.target.dataset.url != baseURL + '/') {
 | 
			
		||||
        let node = listing.moveMakeItem(removeLastDirectoryPartOf(event.target.dataset.url) + '/', '..')
 | 
			
		||||
        list.appendChild(node)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (JSON.parse(data) == null) {
 | 
			
		||||
        prompt.querySelector('p').innerHTML = `There aren't any folders in this directory.`
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (let f of JSON.parse(data)) {
 | 
			
		||||
        if (f.IsDir === true) {
 | 
			
		||||
          dirs++
 | 
			
		||||
          list.appendChild(listing.moveMakeItem(f.URL, f.Name))
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (dirs === 0)
 | 
			
		||||
        prompt.querySelector('p').innerHTML = `There aren't any folders in this directory.`
 | 
			
		||||
    })
 | 
			
		||||
    .catch(e => console.log(e))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.moveSelected = function (event) {
 | 
			
		||||
  event.preventDefault()
 | 
			
		||||
 | 
			
		||||
  let promises = []
 | 
			
		||||
  buttons.setLoading('move')
 | 
			
		||||
 | 
			
		||||
  for (let file of selectedItems) {
 | 
			
		||||
    let fileElement = document.getElementById(file),
 | 
			
		||||
      destFolder = event.target.querySelector('p code').innerHTML
 | 
			
		||||
 | 
			
		||||
    if (event.currentTarget.querySelector('li[aria-selected=true]') != null) {
 | 
			
		||||
      destFolder = event.currentTarget.querySelector('li[aria-selected=true]').dataset.url
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let destPath = '/' + destFolder + '/' + fileElement.querySelector('.name').innerHTML
 | 
			
		||||
    destPath = destPath.replace('//', '/')
 | 
			
		||||
 | 
			
		||||
    promises.push(webdav.move(fileElement.dataset.url, destPath))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Promise.all(promises)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      closePrompt(event)
 | 
			
		||||
      buttons.setDone('move')
 | 
			
		||||
      listing.reload()
 | 
			
		||||
    })
 | 
			
		||||
    .catch(e => {
 | 
			
		||||
      console.log(e)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
listing.moveEvent = function (event) {
 | 
			
		||||
  if (event.currentTarget.classList.contains('disabled'))
 | 
			
		||||
    return
 | 
			
		||||
 | 
			
		||||
  listing.getJSON(window.location.pathname)
 | 
			
		||||
    .then((data) => {
 | 
			
		||||
      let prompt = document.importNode(templates.move.content, true),
 | 
			
		||||
        list = prompt.querySelector('div.file-list ul'),
 | 
			
		||||
        dirs = 0
 | 
			
		||||
 | 
			
		||||
      prompt.querySelector('form').addEventListener('submit', listing.moveSelected)
 | 
			
		||||
      prompt.querySelector('code').innerHTML = window.location.pathname
 | 
			
		||||
 | 
			
		||||
      if (window.location.pathname !== baseURL + '/') {
 | 
			
		||||
        list.appendChild(listing.moveMakeItem(removeLastDirectoryPartOf(window.location.pathname) + '/', '..'))
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (let f of JSON.parse(data)) {
 | 
			
		||||
        if (f.IsDir === true) {
 | 
			
		||||
          dirs++
 | 
			
		||||
          list.appendChild(listing.moveMakeItem(f.URL, f.Name))
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (dirs === 0) {
 | 
			
		||||
        prompt.querySelector('p').innerHTML = `There aren't any folders in this directory.`
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      document.body.appendChild(prompt)
 | 
			
		||||
      document.querySelector('.overlay').classList.add('active')
 | 
			
		||||
      document.querySelector('.prompt').classList.add('active')
 | 
			
		||||
    })
 | 
			
		||||
    .catch(e => console.log(e))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
document.addEventListener('DOMContentLoaded', event => {
 | 
			
		||||
  listing.updateColumns()
 | 
			
		||||
  listing.addDoubleTapEvent()
 | 
			
		||||
 | 
			
		||||
  buttons.rename = document.getElementById('rename')
 | 
			
		||||
  buttons.upload = document.getElementById('upload')
 | 
			
		||||
  buttons.new = document.getElementById('new')
 | 
			
		||||
  buttons.download = document.getElementById('download')
 | 
			
		||||
  buttons.move = document.getElementById('move')
 | 
			
		||||
 | 
			
		||||
  document.getElementById('multiple-selection-activate').addEventListener('click', event => {
 | 
			
		||||
    listing.selectMultiple = true
 | 
			
		||||
    clickOverlay.click()
 | 
			
		||||
 | 
			
		||||
    document.getElementById('multiple-selection').classList.add('active')
 | 
			
		||||
    document.querySelector('body').style.paddingBottom = '4em'
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  document.getElementById('multiple-selection-cancel').addEventListener('click', event => {
 | 
			
		||||
    listing.selectMultiple = false
 | 
			
		||||
 | 
			
		||||
    document.querySelector('body').style.paddingBottom = '0'
 | 
			
		||||
    document.getElementById('multiple-selection').classList.remove('active')
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  if (user.AllowEdit) {
 | 
			
		||||
    buttons.move.addEventListener('click', listing.moveEvent)
 | 
			
		||||
    buttons.rename.addEventListener('click', listing.rename)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let items = document.getElementsByClassName('item')
 | 
			
		||||
 | 
			
		||||
  if (user.AllowNew) {
 | 
			
		||||
    buttons.upload.addEventListener('click', (event) => {
 | 
			
		||||
      document.getElementById('upload-input').click()
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    buttons.new.addEventListener('click', listing.newFileButton)
 | 
			
		||||
 | 
			
		||||
    // Drag and Drop
 | 
			
		||||
    document.addEventListener('dragover', function (event) {
 | 
			
		||||
      event.preventDefault()
 | 
			
		||||
    }, false)
 | 
			
		||||
 | 
			
		||||
    document.addEventListener('dragenter', (event) => {
 | 
			
		||||
      Array.from(items).forEach(file => {
 | 
			
		||||
        file.style.opacity = 0.5
 | 
			
		||||
      })
 | 
			
		||||
    }, false)
 | 
			
		||||
 | 
			
		||||
    document.addEventListener('dragend', (event) => {
 | 
			
		||||
      Array.from(items).forEach(file => {
 | 
			
		||||
        file.style.opacity = 1
 | 
			
		||||
      })
 | 
			
		||||
    }, false)
 | 
			
		||||
 | 
			
		||||
    document.addEventListener('drop', listing.documentDrop, false)
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
Subproject commit 0b01260b38f4db87b2b862e0c7f12e7e2e2905fd
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,356 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Copyright (c) 2010 Maxim Vasiliev
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Maxim Vasiliev
 | 
			
		||||
 * Date: 09.09.2010
 | 
			
		||||
 * Time: 19:02:33
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
(function (root, factory)
 | 
			
		||||
{
 | 
			
		||||
	if (typeof exports !== 'undefined' && typeof module !== 'undefined' && module.exports) {
 | 
			
		||||
		// NodeJS
 | 
			
		||||
		module.exports = factory();
 | 
			
		||||
	}
 | 
			
		||||
	else if (typeof define === 'function' && define.amd)
 | 
			
		||||
	{
 | 
			
		||||
		// AMD. Register as an anonymous module.
 | 
			
		||||
		define(factory);
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		// Browser globals
 | 
			
		||||
		root.form2js = factory();
 | 
			
		||||
	}
 | 
			
		||||
}(this, function ()
 | 
			
		||||
{
 | 
			
		||||
	"use strict";
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Returns form values represented as Javascript object
 | 
			
		||||
	 * "name" attribute defines structure of resulting object
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param rootNode {Element|String} root form element (or it's id) or array of root elements
 | 
			
		||||
	 * @param delimiter {String} structure parts delimiter defaults to '.'
 | 
			
		||||
	 * @param skipEmpty {Boolean} should skip empty text values, defaults to true
 | 
			
		||||
	 * @param nodeCallback {Function} custom function to get node value
 | 
			
		||||
	 * @param useIdIfEmptyName {Boolean} if true value of id attribute of field will be used if name of field is empty
 | 
			
		||||
	 */
 | 
			
		||||
	function form2js(rootNode, delimiter, skipEmpty, nodeCallback, useIdIfEmptyName, getDisabled)
 | 
			
		||||
	{
 | 
			
		||||
		getDisabled = getDisabled ? true : false;
 | 
			
		||||
		if (typeof skipEmpty == 'undefined' || skipEmpty == null) skipEmpty = true;
 | 
			
		||||
		if (typeof delimiter == 'undefined' || delimiter == null) delimiter = '.';
 | 
			
		||||
		if (arguments.length < 5) useIdIfEmptyName = false;
 | 
			
		||||
 | 
			
		||||
		rootNode = typeof rootNode == 'string' ? document.getElementById(rootNode) : rootNode;
 | 
			
		||||
 | 
			
		||||
		var formValues = [],
 | 
			
		||||
			currNode,
 | 
			
		||||
			i = 0;
 | 
			
		||||
 | 
			
		||||
		/* If rootNode is array - combine values */
 | 
			
		||||
		if (rootNode.constructor == Array || (typeof NodeList != "undefined" && rootNode.constructor == NodeList))
 | 
			
		||||
		{
 | 
			
		||||
			while(currNode = rootNode[i++])
 | 
			
		||||
			{
 | 
			
		||||
				formValues = formValues.concat(getFormValues(currNode, nodeCallback, useIdIfEmptyName, getDisabled));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			formValues = getFormValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return processNameValues(formValues, skipEmpty, delimiter);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Processes collection of { name: 'name', value: 'value' } objects.
 | 
			
		||||
	 * @param nameValues
 | 
			
		||||
	 * @param skipEmpty if true skips elements with value == '' or value == null
 | 
			
		||||
	 * @param delimiter
 | 
			
		||||
	 */
 | 
			
		||||
	function processNameValues(nameValues, skipEmpty, delimiter)
 | 
			
		||||
	{
 | 
			
		||||
		var result = {},
 | 
			
		||||
			arrays = {},
 | 
			
		||||
			i, j, k, l,
 | 
			
		||||
			value,
 | 
			
		||||
			nameParts,
 | 
			
		||||
			currResult,
 | 
			
		||||
			arrNameFull,
 | 
			
		||||
			arrName,
 | 
			
		||||
			arrIdx,
 | 
			
		||||
			namePart,
 | 
			
		||||
			name,
 | 
			
		||||
			_nameParts;
 | 
			
		||||
 | 
			
		||||
		for (i = 0; i < nameValues.length; i++)
 | 
			
		||||
		{
 | 
			
		||||
			value = nameValues[i].value;
 | 
			
		||||
 | 
			
		||||
			if (skipEmpty && (value === '' || value === null)) continue;
 | 
			
		||||
 | 
			
		||||
			name = nameValues[i].name;
 | 
			
		||||
			_nameParts = name.split(delimiter);
 | 
			
		||||
			nameParts = [];
 | 
			
		||||
			currResult = result;
 | 
			
		||||
			arrNameFull = '';
 | 
			
		||||
 | 
			
		||||
			for(j = 0; j < _nameParts.length; j++)
 | 
			
		||||
			{
 | 
			
		||||
				namePart = _nameParts[j].split('][');
 | 
			
		||||
				if (namePart.length > 1)
 | 
			
		||||
				{
 | 
			
		||||
					for(k = 0; k < namePart.length; k++)
 | 
			
		||||
					{
 | 
			
		||||
						if (k == 0)
 | 
			
		||||
						{
 | 
			
		||||
							namePart[k] = namePart[k] + ']';
 | 
			
		||||
						}
 | 
			
		||||
						else if (k == namePart.length - 1)
 | 
			
		||||
						{
 | 
			
		||||
							namePart[k] = '[' + namePart[k];
 | 
			
		||||
						}
 | 
			
		||||
						else
 | 
			
		||||
						{
 | 
			
		||||
							namePart[k] = '[' + namePart[k] + ']';
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						arrIdx = namePart[k].match(/([a-z_]+)?\[([a-z_][a-z0-9_]+?)\]/i);
 | 
			
		||||
						if (arrIdx)
 | 
			
		||||
						{
 | 
			
		||||
							for(l = 1; l < arrIdx.length; l++)
 | 
			
		||||
							{
 | 
			
		||||
								if (arrIdx[l]) nameParts.push(arrIdx[l]);
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						else{
 | 
			
		||||
							nameParts.push(namePart[k]);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
					nameParts = nameParts.concat(namePart);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for (j = 0; j < nameParts.length; j++)
 | 
			
		||||
			{
 | 
			
		||||
				namePart = nameParts[j];
 | 
			
		||||
 | 
			
		||||
				if (namePart.indexOf('[]') > -1 && j == nameParts.length - 1)
 | 
			
		||||
				{
 | 
			
		||||
					arrName = namePart.substr(0, namePart.indexOf('['));
 | 
			
		||||
					arrNameFull += arrName;
 | 
			
		||||
 | 
			
		||||
					if (!currResult[arrName]) currResult[arrName] = [];
 | 
			
		||||
					currResult[arrName].push(value);
 | 
			
		||||
				}
 | 
			
		||||
				else if (namePart.indexOf('[') > -1)
 | 
			
		||||
				{
 | 
			
		||||
					arrName = namePart.substr(0, namePart.indexOf('['));
 | 
			
		||||
					arrIdx = namePart.replace(/(^([a-z_]+)?\[)|(\]$)/gi, '');
 | 
			
		||||
 | 
			
		||||
					/* Unique array name */
 | 
			
		||||
					arrNameFull += '_' + arrName + '_' + arrIdx;
 | 
			
		||||
 | 
			
		||||
					/*
 | 
			
		||||
					 * Because arrIdx in field name can be not zero-based and step can be
 | 
			
		||||
					 * other than 1, we can't use them in target array directly.
 | 
			
		||||
					 * Instead we're making a hash where key is arrIdx and value is a reference to
 | 
			
		||||
					 * added array element
 | 
			
		||||
					 */
 | 
			
		||||
 | 
			
		||||
					if (!arrays[arrNameFull]) arrays[arrNameFull] = {};
 | 
			
		||||
					if (arrName != '' && !currResult[arrName]) currResult[arrName] = [];
 | 
			
		||||
 | 
			
		||||
					if (j == nameParts.length - 1)
 | 
			
		||||
					{
 | 
			
		||||
						if (arrName == '')
 | 
			
		||||
						{
 | 
			
		||||
							currResult.push(value);
 | 
			
		||||
							arrays[arrNameFull][arrIdx] = convertValue(currResult[currResult.length - 1]);
 | 
			
		||||
						}
 | 
			
		||||
						else
 | 
			
		||||
						{
 | 
			
		||||
							currResult[arrName].push(value);
 | 
			
		||||
							arrays[arrNameFull][arrIdx] = convertValue(currResult[arrName][currResult[arrName].length - 1]);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						if (!arrays[arrNameFull][arrIdx])
 | 
			
		||||
						{
 | 
			
		||||
							if ((/^[0-9a-z_]+\[?/i).test(nameParts[j+1])) currResult[arrName].push({});
 | 
			
		||||
							else currResult[arrName].push([]);
 | 
			
		||||
 | 
			
		||||
							arrays[arrNameFull][arrIdx] = convertValue(currResult[arrName][currResult[arrName].length - 1]);
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					currResult = convertValue(arrays[arrNameFull][arrIdx]);
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					arrNameFull += namePart;
 | 
			
		||||
 | 
			
		||||
					if (j < nameParts.length - 1) /* Not the last part of name - means object */
 | 
			
		||||
					{
 | 
			
		||||
						if (!currResult[namePart]) currResult[namePart] = {};
 | 
			
		||||
						currResult = convertValue(currResult[namePart]);
 | 
			
		||||
					}
 | 
			
		||||
					else
 | 
			
		||||
					{
 | 
			
		||||
						currResult[namePart] = convertValue(value);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    function convertValue(value) {
 | 
			
		||||
        if (value == "true") return true;
 | 
			
		||||
        if (value == "false") return false;
 | 
			
		||||
        if (!isNaN(value)) return parseInt(value);
 | 
			
		||||
        return value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getFormValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled)
 | 
			
		||||
    {
 | 
			
		||||
        var result = extractNodeValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled);
 | 
			
		||||
        return result.length > 0 ? result : getSubFormValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getSubFormValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled)
 | 
			
		||||
	{
 | 
			
		||||
		var result = [],
 | 
			
		||||
			currentNode = rootNode.firstChild;
 | 
			
		||||
 | 
			
		||||
		while (currentNode)
 | 
			
		||||
		{
 | 
			
		||||
			result = result.concat(extractNodeValues(currentNode, nodeCallback, useIdIfEmptyName, getDisabled));
 | 
			
		||||
			currentNode = currentNode.nextSibling;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    function extractNodeValues(node, nodeCallback, useIdIfEmptyName, getDisabled) {
 | 
			
		||||
        if (node.disabled && !getDisabled) return [];
 | 
			
		||||
 | 
			
		||||
        var callbackResult, fieldValue, result, fieldName = getFieldName(node, useIdIfEmptyName);
 | 
			
		||||
 | 
			
		||||
        callbackResult = nodeCallback && nodeCallback(node);
 | 
			
		||||
 | 
			
		||||
        if (callbackResult && callbackResult.name) {
 | 
			
		||||
            result = [callbackResult];
 | 
			
		||||
        }
 | 
			
		||||
        else if (fieldName != '' && node.nodeName.match(/INPUT|TEXTAREA/i)) {
 | 
			
		||||
            fieldValue = getFieldValue(node, getDisabled);
 | 
			
		||||
            if (null === fieldValue) {
 | 
			
		||||
                result = [];
 | 
			
		||||
            } else {
 | 
			
		||||
                result = [ { name: fieldName, value: fieldValue} ];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (fieldName != '' && node.nodeName.match(/SELECT/i)) {
 | 
			
		||||
	        fieldValue = getFieldValue(node, getDisabled);
 | 
			
		||||
	        result = [ { name: fieldName.replace(/\[\]$/, ''), value: fieldValue } ];
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            result = getSubFormValues(node, nodeCallback, useIdIfEmptyName, getDisabled);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	function getFieldName(node, useIdIfEmptyName)
 | 
			
		||||
	{
 | 
			
		||||
		if (node.name && node.name != '') return node.name;
 | 
			
		||||
		else if (useIdIfEmptyName && node.id && node.id != '') return node.id;
 | 
			
		||||
		else return '';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	function getFieldValue(fieldNode, getDisabled)
 | 
			
		||||
	{
 | 
			
		||||
		if (fieldNode.disabled && !getDisabled) return null;
 | 
			
		||||
 | 
			
		||||
		switch (fieldNode.nodeName) {
 | 
			
		||||
			case 'INPUT':
 | 
			
		||||
			case 'TEXTAREA':
 | 
			
		||||
				switch (fieldNode.type.toLowerCase()) {
 | 
			
		||||
					case 'radio':
 | 
			
		||||
			if (fieldNode.checked && fieldNode.value === "false") return false;
 | 
			
		||||
					case 'checkbox':
 | 
			
		||||
                        if (fieldNode.checked && fieldNode.value === "true") return true;
 | 
			
		||||
                        if (!fieldNode.checked && fieldNode.value === "true") return false;
 | 
			
		||||
			if (fieldNode.checked) return fieldNode.value;
 | 
			
		||||
						break;
 | 
			
		||||
 | 
			
		||||
					case 'button':
 | 
			
		||||
					case 'reset':
 | 
			
		||||
					case 'submit':
 | 
			
		||||
					case 'image':
 | 
			
		||||
						return '';
 | 
			
		||||
						break;
 | 
			
		||||
 | 
			
		||||
					default:
 | 
			
		||||
						return fieldNode.value;
 | 
			
		||||
						break;
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
			case 'SELECT':
 | 
			
		||||
				return getSelectedOptionValue(fieldNode);
 | 
			
		||||
				break;
 | 
			
		||||
 | 
			
		||||
			default:
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return null;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function getSelectedOptionValue(selectNode)
 | 
			
		||||
	{
 | 
			
		||||
		var multiple = selectNode.multiple,
 | 
			
		||||
			result = [],
 | 
			
		||||
			options,
 | 
			
		||||
			i, l;
 | 
			
		||||
 | 
			
		||||
		if (!multiple) return selectNode.value;
 | 
			
		||||
 | 
			
		||||
		for (options = selectNode.getElementsByTagName("option"), i = 0, l = options.length; i < l; i++)
 | 
			
		||||
		{
 | 
			
		||||
			if (options[i].selected) result.push(options[i].value);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return form2js;
 | 
			
		||||
 | 
			
		||||
}));
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,289 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
{{ $absURL := .Config.AbsoluteURL }}
 | 
			
		||||
<head>
 | 
			
		||||
    <title>{{.Name}}</title>
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <link rel="stylesheet" href="{{ .Config.AbsoluteURL }}/_internal/css/normalize.css">
 | 
			
		||||
    <link rel="stylesheet" href="{{ .Config.AbsoluteURL }}/_internal/css/fonts.css">
 | 
			
		||||
    <link rel="stylesheet" href="{{ .Config.AbsoluteURL }}/_internal/css/styles.css">
 | 
			
		||||
    {{- if ne .User.StyleSheet "" -}}
 | 
			
		||||
    <style>{{ CSS .User.StyleSheet }}</style>
 | 
			
		||||
    {{- end -}}
 | 
			
		||||
 | 
			
		||||
    <script>
 | 
			
		||||
    var user = JSON.parse('{{ Marshal .User }}'),
 | 
			
		||||
        webdavURL = "{{.Config.AbsoluteWebdavURL }}",
 | 
			
		||||
        baseURL = "{{.Config.AbsoluteURL}}",
 | 
			
		||||
        prefixURL = "{{ .Config.PrefixURL }}";
 | 
			
		||||
    </script>
 | 
			
		||||
    <script src="{{ .Config.AbsoluteURL }}/_internal/js/common.js" defer></script>
 | 
			
		||||
    {{- if .IsDir }}
 | 
			
		||||
    <script src="{{ .Config.AbsoluteURL }}/_internal/js/listing.js" defer></script>
 | 
			
		||||
    {{- else }}
 | 
			
		||||
    <script src="{{ .Config.AbsoluteURL }}/_internal/js/vendor/ace/src-min/ace.js" defer></script>
 | 
			
		||||
    <script src="{{ .Config.AbsoluteURL }}/_internal/js/vendor/form2js.js" defer></script>
 | 
			
		||||
    <script src="{{ .Config.AbsoluteURL }}/_internal/js/editor.js" defer></script>
 | 
			
		||||
    {{- end }}
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <header>
 | 
			
		||||
        <div id="top-bar">
 | 
			
		||||
            <div><p>File Manager</p></div>
 | 
			
		||||
            <div id="search">
 | 
			
		||||
                <i class="material-icons" title="Search">search</i>
 | 
			
		||||
                <input type="text" aria-label="Write here to search" placeholder="Search or execute a command...">
 | 
			
		||||
                <div>
 | 
			
		||||
                    <div>Loading...</div>
 | 
			
		||||
                    <p><i class="material-icons spin">autorenew</i></p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="action" id="logout" tabindex="0" role="button" aria-label="Log out">
 | 
			
		||||
                <i class="material-icons" title="Logout">exit_to_app</i>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div id="bottom-bar">
 | 
			
		||||
            <div>
 | 
			
		||||
                {{- if ne .Name "/"}}
 | 
			
		||||
                <div data-dropdown tabindex="0" aria-label="Previous" role="button" class="action" id="previous">
 | 
			
		||||
                    <i class="material-icons" title="Previous">subdirectory_arrow_left</i>
 | 
			
		||||
                    <ul class="dropdown" id="breadcrumbs">
 | 
			
		||||
                    {{- range $item := .BreadcrumbMap }}
 | 
			
		||||
                        <a tabindex="0" href="{{ $absURL }}{{ $item.URL }}"><li>{{ $item.Name }}</li></a>
 | 
			
		||||
                    {{- end }}
 | 
			
		||||
                    </ul>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{- end }}
 | 
			
		||||
 | 
			
		||||
                {{ if ne .Name "/"}}<p id="current-file">{{ .Name }}</p>{{ end }}
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="actions{{ if .IsDir }} disabled{{ end }}" id="file-only">
 | 
			
		||||
                {{- if and (not .IsDir) (.User.AllowEdit) }}
 | 
			
		||||
                {{- if .Editor}}
 | 
			
		||||
 | 
			
		||||
                {{- if eq .Data.Mode "markdown" }}
 | 
			
		||||
                <div tabindex="0" role="button" aria-label="Preview" class="action" id="preview" onclick="notImplemented(event);">
 | 
			
		||||
                    <i class="material-icons" title="Preview">remove_red_eye</i>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{- end }}
 | 
			
		||||
 | 
			
		||||
                {{- if eq .Data.Visual true }}
 | 
			
		||||
                <div tabindex="0" role="button" aria-label="Toggle edit source" class="action" id="edit-source">
 | 
			
		||||
                    <i class="material-icons" title="Toggle edit source">code</i>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{- end }}
 | 
			
		||||
                {{- end }}
 | 
			
		||||
 | 
			
		||||
                <div tabindex="0" role="button" aria-label="Save" class="action" id="save">
 | 
			
		||||
                    <i class="material-icons" title="Save">save</i>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{- end }}
 | 
			
		||||
 | 
			
		||||
                {{- if .IsDir }}
 | 
			
		||||
                <div tabindex="0" role="button" aria-label="See raw" class="action" id="open">
 | 
			
		||||
                    <i class="material-icons" title="See raw">open_in_new</i>
 | 
			
		||||
                    <span>See raw</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{- end }}
 | 
			
		||||
 | 
			
		||||
                {{- if and (.User.AllowEdit) (.IsDir) }}
 | 
			
		||||
                <div tabindex="0" role="button" aria-label="Move" class="action" id="move">
 | 
			
		||||
                    <i class="material-icons" title="Move">forward</i>
 | 
			
		||||
                    <span>Move file</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{- end }}
 | 
			
		||||
 | 
			
		||||
                {{- if and .IsDir .User.AllowEdit }}
 | 
			
		||||
                <div tabindex="0" role="button" aria-label="Edit" class="action" id="rename">
 | 
			
		||||
                    <i class="material-icons" title="Edit">mode_edit</i>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{- end }}
 | 
			
		||||
 | 
			
		||||
                {{- if and .User.AllowEdit .IsDir }}
 | 
			
		||||
                <div tabindex="0" role="button" aria-label="Delete" class="action" id="delete">
 | 
			
		||||
                    <i class="material-icons" title="Delete">delete</i><span>Delete</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{- end }}
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div tabindex="0" role="button" aria-label="Moew" class="action mobile-only" id="more">
 | 
			
		||||
                <i class="material-icons">more_vert</i>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="actions" id="main-actions">
 | 
			
		||||
                {{- if .IsDir }}
 | 
			
		||||
                <div role="button" class="action" id="view">
 | 
			
		||||
                    {{- if eq .Display "mosaic" }}
 | 
			
		||||
                        <a tabindex="0" aria-label="Switch to list" title="Switch View" href="?display=list">
 | 
			
		||||
                            <i class="material-icons">view_list</i><span>Switch view</span>
 | 
			
		||||
                        </a>
 | 
			
		||||
                    {{- else }}
 | 
			
		||||
                        <a tabindex="0" aria-label="Switch to Mosaic" title="Switch View" href="?display=mosaic">
 | 
			
		||||
                            <i class="material-icons">view_module</i><span>Switch view</span>
 | 
			
		||||
                        </a>
 | 
			
		||||
                    {{- end }}
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div tabindex="0" role="button" aria-label="Select multiple" class="action mobile-only" id="multiple-selection-activate">
 | 
			
		||||
                    <i class="material-icons">check_circle</i><span>Select</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{- end }}
 | 
			
		||||
 | 
			
		||||
                {{- if and (.User.AllowNew) (.IsDir) }}
 | 
			
		||||
                <div tabindex="0" aria-label="Upload" role="button" class="action" id="upload">
 | 
			
		||||
                    <i class="material-icons" title="Upload">file_upload</i><span>Upload</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{- end }}
 | 
			
		||||
 | 
			
		||||
                {{- if not .IsDir }}
 | 
			
		||||
                <div tabindex="0" role="button" aria-label="See raw" class="action" id="open">
 | 
			
		||||
                    <i class="material-icons" title="See raw">open_in_new</i>
 | 
			
		||||
                    <span>See raw</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{- end }}
 | 
			
		||||
 | 
			
		||||
                {{- if and .User.AllowEdit (not .IsDir) }}
 | 
			
		||||
                <div tabindex="0" role="button" aria-label="Delete" class="action" id="delete">
 | 
			
		||||
                    <i class="material-icons" title="Delete">delete</i><span>Delete</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{- end }}
 | 
			
		||||
 | 
			
		||||
                <div {{ if .IsDir }}data-dropdown{{ end }} tabindex="0" role="button" aria-label="Download" class="action" id="download">
 | 
			
		||||
                    {{- if not .IsDir}}<a href="?download=true">{{ end }}
 | 
			
		||||
                    <i class="material-icons" title="Download">file_download</i><span>Download</span>
 | 
			
		||||
                    {{- if not .IsDir}}</a>{{ end }}
 | 
			
		||||
 | 
			
		||||
                    {{- if .IsDir }}
 | 
			
		||||
                    <ul class="dropdown" id="download-drop">
 | 
			
		||||
                        <a tabindex="0" aria-label="Download as Zip" data-format="zip" href="?download=zip"><li>zip</li></a>
 | 
			
		||||
                        <a tabindex="0" aria-label="Download as Tar" data-format="tar" href="?download=tar"><li>tar</li></a>
 | 
			
		||||
                        <a tabindex="0" aria-label="Download as TarGz" data-format="targz" href="?download=targz"><li>tar.gz</li></a>
 | 
			
		||||
                        <a tabindex="0" aria-label="Download as TarBz2" data-format="tarbz2" href="?download=tarbz2"><li>tar.bz2</li></a>
 | 
			
		||||
                        <a tabindex="0" aria-label="Download as TarXz" data-format="tarbz2" href="?download=tarxz"><li>tar.xz</li></a>
 | 
			
		||||
                    </ul>
 | 
			
		||||
                    {{- end }}
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div tabindex="0" role="button" aria-label="Info" class="action" id="info">
 | 
			
		||||
                    <i class="material-icons" title="Info">info</i><span>Info</span>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div id="click-overlay"></div>
 | 
			
		||||
    </header>
 | 
			
		||||
 | 
			
		||||
    <div id="multiple-selection" class="mobile-only">
 | 
			
		||||
        <p>Multiple selection enabled</p>
 | 
			
		||||
        <div tabindex="0" role="button" class="action" id="multiple-selection-cancel">
 | 
			
		||||
            <i class="material-icons" title="Clear">clear</i>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <main>
 | 
			
		||||
        {{- template "content" . }}
 | 
			
		||||
    </main>
 | 
			
		||||
 | 
			
		||||
    <div class="overlay"></div>
 | 
			
		||||
 | 
			
		||||
    {{- if and (.User.AllowNew) (.IsDir) }}
 | 
			
		||||
    <div class="floating">
 | 
			
		||||
        <div tabindex="0" role="button" class="action" id="new">
 | 
			
		||||
            <i class="material-icons" title="New file or directory">add</i>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {{- end }}
 | 
			
		||||
 | 
			
		||||
    <template id="question-template">
 | 
			
		||||
        <form class="prompt">
 | 
			
		||||
            <h3></h3>
 | 
			
		||||
            <p></p>
 | 
			
		||||
            <input autofocus type="text">
 | 
			
		||||
            <div>
 | 
			
		||||
                <button type="submit" autofocus class="ok">OK</button>
 | 
			
		||||
                <button class="cancel" onclick="closePrompt(event);">Cancel</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
    </template>
 | 
			
		||||
 | 
			
		||||
    <template id="info-template">
 | 
			
		||||
        <div class="prompt">
 | 
			
		||||
            <h3>File Information</h3>
 | 
			
		||||
            <p><strong>Display Name:</strong> <span id="display_name"></span></p>
 | 
			
		||||
            <p><strong>Content Length:</strong> <span id="content_length"></span> Bytes</p>
 | 
			
		||||
            <p><strong>Last Modified:</strong> <span id="last_modified"></span></p>
 | 
			
		||||
 | 
			
		||||
            <section class="file-only">
 | 
			
		||||
                <p><strong>MD5:</strong> <code id="md5"><a href="#" onclick="getHash(event, 'md5')">show</a></code></p>
 | 
			
		||||
                <p><strong>SHA1:</strong> <code id="sha1"><a href="#" onclick="getHash(event, 'sha1')">show</a></code></p>
 | 
			
		||||
                <p><strong>SHA256:</strong> <code id="sha256"><a href="#" onclick="getHash(event, 'sha256')">show</a></code></p>
 | 
			
		||||
                <p><strong>SHA512:</strong> <code id="sha512"><a href="#" onclick="getHash(event, 'sha512')">show</a></code></p>
 | 
			
		||||
            </section>
 | 
			
		||||
 | 
			
		||||
            <div>
 | 
			
		||||
                <button type="submit" onclick="closePrompt(event);" class="ok">OK</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </template>
 | 
			
		||||
 | 
			
		||||
    <template id="message-template">
 | 
			
		||||
        <div class="prompt">
 | 
			
		||||
            <h3></h3>
 | 
			
		||||
            <p></p>
 | 
			
		||||
            <div>
 | 
			
		||||
                <button type="submit" onclick="closePrompt(event);" class="ok">OK</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </template>
 | 
			
		||||
 | 
			
		||||
    <template id="move-template">
 | 
			
		||||
        <form class="prompt">
 | 
			
		||||
            <h3>Move</h3>
 | 
			
		||||
            <p>Choose new house for your file(s)/folder(s):</p>
 | 
			
		||||
 | 
			
		||||
            <div class="file-list">
 | 
			
		||||
                <ul>
 | 
			
		||||
                </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <p>Currently navigating on: <code></code>.</p>
 | 
			
		||||
 | 
			
		||||
            <div>
 | 
			
		||||
                <button type="submit" autofocus class="ok">Move</button>
 | 
			
		||||
                <button class="cancel" onclick="closePrompt(event);">Cancel</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
    </template>
 | 
			
		||||
 | 
			
		||||
    <div class="help">
 | 
			
		||||
        <h3>Help</h3>
 | 
			
		||||
 | 
			
		||||
        <ul>
 | 
			
		||||
            <li><strong>F1</strong> - this information</li>
 | 
			
		||||
            <li><strong>F2</strong> - rename file</li>
 | 
			
		||||
            <li><strong>DEL</strong> - delete selected items</li>
 | 
			
		||||
            <li><strong>ESC</strong> - clear selection and/or close the prompt</li>
 | 
			
		||||
            <li><strong>CTRL + S</strong> - save a file or download the directory where you are</li>
 | 
			
		||||
            <li><strong>CTRL + Click</strong> - select multiple files or directories</li>
 | 
			
		||||
            <li><strong>Double click</strong> - open a file or directory</li>
 | 
			
		||||
            <li><strong>Click</strong> - select file or directory</li>
 | 
			
		||||
        </ul>
 | 
			
		||||
 | 
			
		||||
        <p>Not available yet</p>
 | 
			
		||||
 | 
			
		||||
        <ul>
 | 
			
		||||
            <li><strong>Alt + Click</strong> - select a group of files</li>
 | 
			
		||||
        </ul>
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
            <button type="submit" onclick="closeHelp(event);" class="ok">OK</button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <footer>Served with <a rel="noopener noreferrer" href="https://caddyserver.com">Caddy</a> and <a rel="noopener noreferrer" href="https://github.com/hacdias/caddy-filemanager">File Manager</a>.</footer>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,57 @@
 | 
			
		|||
{{ define "content" }}
 | 
			
		||||
{{- with .Data }}
 | 
			
		||||
<form id="editor" {{ if eq .Mode "markdown" }}class="markdown"{{ end }} data-kind="{{ .Class }}" data-rune="{{ if eq .Class "complete" }}{{ .FrontMatter.Rune }}{{ end }}">
 | 
			
		||||
    {{- if or (eq .Class "frontmatter-only") (eq .Class "complete") }}
 | 
			
		||||
    {{- if (eq .Class "complete")}}
 | 
			
		||||
    <h2>Metadata</h2>
 | 
			
		||||
    {{- end }}
 | 
			
		||||
    <div class="frontmatter" data-type="parent">
 | 
			
		||||
        {{- template "blocks" .FrontMatter.Content }}
 | 
			
		||||
        <div class="button add">Add field</div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {{- end }}
 | 
			
		||||
 | 
			
		||||
    {{ if or (eq .Class "content-only") (eq .Class "complete") }}
 | 
			
		||||
        {{ if (eq .Class "complete")}}
 | 
			
		||||
        <h2>Body</h2>
 | 
			
		||||
        {{ end }}
 | 
			
		||||
        <div class="content">
 | 
			
		||||
            <div id="ace" data-mode="{{ .Mode }}"></div>
 | 
			
		||||
            <textarea class="source" name="content">{{ .Content }}</textarea>
 | 
			
		||||
        </div>
 | 
			
		||||
    {{ end }}
 | 
			
		||||
</form>
 | 
			
		||||
{{- end }}
 | 
			
		||||
 | 
			
		||||
<template id="base-template">
 | 
			
		||||
    <fieldset id="" data-type="">
 | 
			
		||||
        <h3></h3>
 | 
			
		||||
        <div class="action add">
 | 
			
		||||
            <i class="material-icons">add</i>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="action delete" data-delete="">
 | 
			
		||||
            <i class="material-icons">close</i>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="group"></div>
 | 
			
		||||
    </fieldset>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<template id="object-item-template">
 | 
			
		||||
    <div class="block" id="block-${bid}" data-content="${bid}">
 | 
			
		||||
        <label for="${bid}">${name}</label>
 | 
			
		||||
        <input name="${bid}" id="${bid}" type="text" data-parent-type="object"></input>
 | 
			
		||||
        <div class="action delete" data-delete="block-${bid}">
 | 
			
		||||
            <i class="material-icons">close</i>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<template id="array-item-template">
 | 
			
		||||
    <div id="" data-type="array-item">
 | 
			
		||||
        <input name="" id="" type="text" data-parent-type="array"></input>
 | 
			
		||||
        <div class="action delete" data-delete="">
 | 
			
		||||
            <i class="material-icons">close</i>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
{{ end }}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,56 @@
 | 
			
		|||
{{ define "blocks" }}
 | 
			
		||||
{{ if .Fields }}<div class="group">{{ end }}
 | 
			
		||||
{{- range $key, $value := .Fields }}
 | 
			
		||||
    {{- if eq $value.Parent.Type "array" }}
 | 
			
		||||
        <div id="{{ $value.Name }}-{{ $key }}" data-type="array-item">
 | 
			
		||||
            {{- template "value" $value }}
 | 
			
		||||
            <div class="action delete" data-delete="{{ $value.Name }}-{{ $key }}">
 | 
			
		||||
                <i class="material-icons" title="Close">close</i>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    {{- else }}
 | 
			
		||||
        <div class="block" id="block-{{ $value.Name }}" data-content="{{ $value.Name }}">
 | 
			
		||||
            <label for="{{ $value.Name }}">{{ $value.Title }}</label>
 | 
			
		||||
            {{ template "value" $value }}
 | 
			
		||||
            <div class="action delete" data-delete="block-{{ $value.Name }}">
 | 
			
		||||
                <i class="material-icons" title="Close">close</i>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    {{- end }}
 | 
			
		||||
{{- end }}
 | 
			
		||||
{{- if .Fields }}</div>{{ end }}
 | 
			
		||||
 | 
			
		||||
{{- range $key, $value := .Arrays }}
 | 
			
		||||
{{- template "fielset" $value }}
 | 
			
		||||
{{- end }}
 | 
			
		||||
 | 
			
		||||
{{- range $key, $value := .Objects }}
 | 
			
		||||
{{- template "fielset" $value }}
 | 
			
		||||
{{- end }}
 | 
			
		||||
 | 
			
		||||
{{ end }}
 | 
			
		||||
 | 
			
		||||
{{ define "value" }}
 | 
			
		||||
{{- if eq .HTMLType "textarea" }}
 | 
			
		||||
    <textarea class="scroll" name="{{ .Name }}" id="{{.Name }}" data-parent-type="{{ .Parent.Type }}">{{ .Content.Other }}</textarea>
 | 
			
		||||
{{- else if eq .HTMLType "datetime" }}
 | 
			
		||||
    <input name="{{ .Name }}" id="{{ .Name }}" value="{{ .Content.Other.Format "2006-01-02T15:04" }}" type="datetime-local" data-parent-type="{{ .Parent.Type }}"></input>
 | 
			
		||||
{{- else }}
 | 
			
		||||
    <input name="{{ .Name }}" id="{{ .Name }}" value="{{ .Content.Other }}" type="{{ .HTMLType }}" data-parent-type="{{ .Parent.Type }}"></input>
 | 
			
		||||
{{- end }}
 | 
			
		||||
{{ end }}
 | 
			
		||||
 | 
			
		||||
{{ define "fielset" }}
 | 
			
		||||
<fieldset id="{{ .Name }}" data-type="{{ .Type }}">
 | 
			
		||||
    {{- if not (eq .Title "") }}
 | 
			
		||||
        <h3>{{ .Name }}</h3>
 | 
			
		||||
    {{- end }}
 | 
			
		||||
    <div class="action add">
 | 
			
		||||
        <i class="material-icons" title="Add">add</i>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="action delete" data-delete="{{ .Name }}">
 | 
			
		||||
        <i class="material-icons" title="Close">close</i>
 | 
			
		||||
    </div>
 | 
			
		||||
    {{- template "blocks" .Content }}
 | 
			
		||||
</fieldset>
 | 
			
		||||
{{ end }}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,103 @@
 | 
			
		|||
{{ define "content" }}
 | 
			
		||||
<div class="container {{ .Display }}" id="listing">
 | 
			
		||||
{{- with .Data -}}
 | 
			
		||||
    <div>
 | 
			
		||||
        <div class="item header">
 | 
			
		||||
            <div></div>
 | 
			
		||||
            <div>
 | 
			
		||||
                <p class="name{{ if eq .Sort "name" }} active{{ end }}"><span>Name</span>
 | 
			
		||||
                    {{- if eq .Sort "name" -}}
 | 
			
		||||
                    {{- if eq .Order "asc" -}}
 | 
			
		||||
                    <a href="?sort=name&order=desc"><i class="material-icons">arrow_downward</i></a>
 | 
			
		||||
                    {{- else -}}
 | 
			
		||||
                    <a href="?sort=name&order=asc"><i class="material-icons">arrow_upward</i></a>
 | 
			
		||||
                    {{- end -}}
 | 
			
		||||
                    {{- else -}}
 | 
			
		||||
                    <a href="?sort=name&order=desc"><i class="material-icons">arrow_downward</i></a>
 | 
			
		||||
                    {{- end -}}
 | 
			
		||||
                </p>
 | 
			
		||||
                <p class="size{{ if eq .Sort "size" }} active{{ end }}"><span>File Size</span>
 | 
			
		||||
                    {{- if eq .Sort "size" -}}
 | 
			
		||||
                    {{- if eq .Order "asc" -}}
 | 
			
		||||
                    <a href="?sort=size&order=desc"><i class="material-icons">arrow_downward</i></a>
 | 
			
		||||
                    {{- else -}}
 | 
			
		||||
                    <a href="?sort=size&order=asc"><i class="material-icons">arrow_upward</i></a>
 | 
			
		||||
                    {{- end -}}
 | 
			
		||||
                    {{- else -}}
 | 
			
		||||
                    <a href="?sort=size&order=desc"><i class="material-icons">arrow_downward</i></a>
 | 
			
		||||
                    {{- end -}}
 | 
			
		||||
                </p>
 | 
			
		||||
                <p class="modified">Last modified</p>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    {{ if and (eq .NumDirs 0) (eq .NumFiles 0) }}
 | 
			
		||||
    <h2 class="message">It feels lonely here :'(</h2>
 | 
			
		||||
    {{ end }}
 | 
			
		||||
    
 | 
			
		||||
    {{- if not (eq .NumDirs 0)}}
 | 
			
		||||
    <h2>Folders</h2>
 | 
			
		||||
    <div>
 | 
			
		||||
    {{- range .Items }}
 | 
			
		||||
        {{- if (.IsDir) }}
 | 
			
		||||
        {{ template "item" .}}
 | 
			
		||||
        {{- end }}
 | 
			
		||||
    {{- end }}
 | 
			
		||||
    </div>
 | 
			
		||||
    {{- end }}
 | 
			
		||||
        
 | 
			
		||||
    {{- if not (eq .NumFiles 0)}}
 | 
			
		||||
    <h2>Files</h2>
 | 
			
		||||
    <div>
 | 
			
		||||
    {{- range .Items }}
 | 
			
		||||
        {{- if (not .IsDir) }}
 | 
			
		||||
        {{ template "item" .}}
 | 
			
		||||
        {{- end }}
 | 
			
		||||
    {{- end }}
 | 
			
		||||
    </div>
 | 
			
		||||
    {{- end }}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<input style="display:none" type="file" id="upload-input" onchange="listing.handleFiles(this.files, '')" value="Upload" multiple>
 | 
			
		||||
{{- end -}}
 | 
			
		||||
{{- end -}}
 | 
			
		||||
 | 
			
		||||
{{ define "item" }}
 | 
			
		||||
<div ondragstart="listing.itemDragStart(event)" 
 | 
			
		||||
    {{ if .IsDir}}ondragover="listing.itemDragOver(event)" ondrop="listing.itemDrop(event)"{{ end }} 
 | 
			
		||||
    draggable="true" 
 | 
			
		||||
    class="item" 
 | 
			
		||||
    onclick="listing.selectItem(event)"
 | 
			
		||||
    ondblclick="listing.openItem(event)"
 | 
			
		||||
    data-dir="{{ .IsDir }}" 
 | 
			
		||||
    data-url="{{ .URL }}"
 | 
			
		||||
    id="{{ EncodeBase64 .Name }}">
 | 
			
		||||
    <div>
 | 
			
		||||
        {{- if .IsDir}}
 | 
			
		||||
        <i class="material-icons">folder</i>
 | 
			
		||||
        {{- else}}
 | 
			
		||||
        {{ if eq .Type "image" }}
 | 
			
		||||
        <i class="material-icons">insert_photo</i>
 | 
			
		||||
        {{ else if eq .Type "audio" }}
 | 
			
		||||
        <i class="material-icons">volume_up</i>
 | 
			
		||||
        {{ else if eq .Type "video" }}
 | 
			
		||||
        <i class="material-icons">movie</i>
 | 
			
		||||
        {{ else }}
 | 
			
		||||
        <i class="material-icons">insert_drive_file</i>
 | 
			
		||||
        {{ end }}
 | 
			
		||||
        {{- end}}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div>
 | 
			
		||||
        <p class="name">{{.Name}}</p>
 | 
			
		||||
        {{- if .IsDir}}
 | 
			
		||||
        <p class="size" data-order="-1">—</p>
 | 
			
		||||
        {{- else}}
 | 
			
		||||
        <p class="size" data-order="{{.Size}}">{{.HumanSize}}</p>
 | 
			
		||||
        {{- end}}
 | 
			
		||||
        <p class="modified">
 | 
			
		||||
        <time datetime="{{.HumanModTime "2006-01-02T15:04:05Z"}}">{{.HumanModTime "2 Jan 2006 03:04 PM"}}</time>
 | 
			
		||||
        </p>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{{ end }}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
{{ template "content" . }}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
{{ define "content" }}
 | 
			
		||||
{{ with .Data}}
 | 
			
		||||
<main class="container">
 | 
			
		||||
  {{ if eq .Type "image" }}
 | 
			
		||||
  <center><img src="{{ .URL }}?raw=true"></center>
 | 
			
		||||
  {{ else if eq .Type "audio" }}
 | 
			
		||||
  <audio src="{{ .URL }}?raw=true" controls></audio>
 | 
			
		||||
  {{ else if eq .Type "video" }}
 | 
			
		||||
  <video src="{{ .URL }}?raw=true" controls>
 | 
			
		||||
    Sorry, your browser doesn't support embedded videos,
 | 
			
		||||
    but don't worry, you can <a href="?download=true">download it</a>
 | 
			
		||||
    and watch it with your favorite video player!
 | 
			
		||||
  </video>
 | 
			
		||||
  {{ else if eq .Extension ".pdf" }}
 | 
			
		||||
  <object class="pdf" data="{{ .URL }}?raw=true"></object>
 | 
			
		||||
  {{ else if eq .Type "blob" }}
 | 
			
		||||
  <a href="?download=true"><h2 class="message">Download <i class="material-icons">file_download</i></h2></a>
 | 
			
		||||
  {{ else}}
 | 
			
		||||
  <pre>{{ .StringifyContent }}</pre>
 | 
			
		||||
  {{ end }}
 | 
			
		||||
</main>
 | 
			
		||||
{{ end }}
 | 
			
		||||
{{ end }}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/hacdias/filemanager"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var m *filemanager.FileManager
 | 
			
		||||
 | 
			
		||||
func handler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	_, err := m.ServeHTTP(w, r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Print(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	m = filemanager.New()
 | 
			
		||||
	http.HandleFunc("/", handler)
 | 
			
		||||
	http.ListenAndServe(":8080", nil)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -49,10 +49,10 @@ type User struct {
 | 
			
		|||
 | 
			
		||||
// Assets are the static and front-end assets, such as JS, CSS and HTML templates.
 | 
			
		||||
type Assets struct {
 | 
			
		||||
	requiredJS rice.Box // JS that is always required to have in order to be usable.
 | 
			
		||||
	Templates  rice.Box
 | 
			
		||||
	CSS        rice.Box
 | 
			
		||||
	JS         rice.Box
 | 
			
		||||
	requiredJS *rice.Box // JS that is always required to have in order to be usable.
 | 
			
		||||
	Templates  *rice.Box
 | 
			
		||||
	CSS        *rice.Box
 | 
			
		||||
	JS         *rice.Box
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Rule is a dissalow/allow rule.
 | 
			
		||||
| 
						 | 
				
			
			@ -66,16 +66,65 @@ type Rule struct {
 | 
			
		|||
// CommandFunc ...
 | 
			
		||||
type CommandFunc func(r *http.Request, c *FileManager, u *User) error
 | 
			
		||||
 | 
			
		||||
// AbsoluteURL ...
 | 
			
		||||
func New() *FileManager {
 | 
			
		||||
	m := &FileManager{
 | 
			
		||||
		User: &User{
 | 
			
		||||
			AllowCommands: true,
 | 
			
		||||
			AllowEdit:     true,
 | 
			
		||||
			AllowNew:      true,
 | 
			
		||||
			Commands:      []string{"git", "svn", "hg"},
 | 
			
		||||
			Rules: []*Rule{{
 | 
			
		||||
				Regex:  true,
 | 
			
		||||
				Allow:  false,
 | 
			
		||||
				Regexp: regexp.MustCompile("\\/\\..+"),
 | 
			
		||||
			}},
 | 
			
		||||
		},
 | 
			
		||||
		Users:      map[string]*User{},
 | 
			
		||||
		BeforeSave: func(r *http.Request, c *FileManager, u *User) error { return nil },
 | 
			
		||||
		AfterSave:  func(r *http.Request, c *FileManager, u *User) error { return nil },
 | 
			
		||||
		Assets: &Assets{
 | 
			
		||||
			Templates:  rice.MustFindBox("./_assets/templates"),
 | 
			
		||||
			CSS:        rice.MustFindBox("./_assets/css"),
 | 
			
		||||
			requiredJS: rice.MustFindBox("./_assets/js"),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m.SetScope(".")
 | 
			
		||||
	m.SetBaseURL("/")
 | 
			
		||||
	m.SetWebDavURL("/webdav")
 | 
			
		||||
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m FileManager) AbsoluteURL() string {
 | 
			
		||||
	return m.PrefixURL + m.BaseURL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AbsoluteWebdavURL ...
 | 
			
		||||
func (m FileManager) AbsoluteWebdavURL() string {
 | 
			
		||||
	return m.PrefixURL + m.WebDavURL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *FileManager) SetBaseURL(url string) {
 | 
			
		||||
	url = strings.TrimPrefix(url, "/")
 | 
			
		||||
	url = strings.TrimSuffix(url, "/")
 | 
			
		||||
	url = "/" + url
 | 
			
		||||
	m.BaseURL = strings.TrimSuffix(url, "/")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *FileManager) SetWebDavURL(url string) {
 | 
			
		||||
	m.WebDavURL = m.BaseURL + "/" + strings.TrimPrefix(url, "/")
 | 
			
		||||
	m.User.Handler = &webdav.Handler{
 | 
			
		||||
		Prefix:     m.WebDavURL,
 | 
			
		||||
		FileSystem: m.FileSystem,
 | 
			
		||||
		LockSystem: webdav.NewMemLS(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *User) SetScope(scope string) {
 | 
			
		||||
	u.Scope = strings.TrimSuffix(scope, "/")
 | 
			
		||||
	u.FileSystem = webdav.Dir(u.Scope)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Allowed checks if the user has permission to access a directory/file.
 | 
			
		||||
func (u User) Allowed(url string) bool {
 | 
			
		||||
	var rule *Rule
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										13
									
								
								http.go
								
								
								
								
							
							
						
						
									
										13
									
								
								http.go
								
								
								
								
							| 
						 | 
				
			
			@ -6,10 +6,15 @@ import (
 | 
			
		|||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy/caddyhttp/httpserver"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func matchURL(first, second string) bool {
 | 
			
		||||
	first = strings.ToLower(first)
 | 
			
		||||
	second = strings.ToLower(second)
 | 
			
		||||
 | 
			
		||||
	return strings.HasPrefix(first, second)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
 | 
			
		||||
func (c *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	var (
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +26,7 @@ func (c *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, er
 | 
			
		|||
 | 
			
		||||
	// Checks if the URL matches the Assets URL. Returns the asset if the
 | 
			
		||||
	// method is GET and Status Forbidden otherwise.
 | 
			
		||||
	if httpserver.Path(r.URL.Path).Matches(c.BaseURL + AssetsURL) {
 | 
			
		||||
	if matchURL(r.URL.Path, c.BaseURL+AssetsURL) {
 | 
			
		||||
		if r.Method == http.MethodGet {
 | 
			
		||||
			return serveAssets(w, r, c)
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +42,7 @@ func (c *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, er
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// Checks if the request URL is for the WebDav server
 | 
			
		||||
	if httpserver.Path(r.URL.Path).Matches(c.WebDavURL) {
 | 
			
		||||
	if matchURL(r.URL.Path, c.WebDavURL) {
 | 
			
		||||
		// Checks for user permissions relatively to this PATH
 | 
			
		||||
		if !user.Allowed(strings.TrimPrefix(r.URL.Path, c.WebDavURL)) {
 | 
			
		||||
			return http.StatusForbidden, nil
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ import (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
// AssetsURL is the url of the assets
 | 
			
		||||
const AssetsURL = "/_filemanagerinternal"
 | 
			
		||||
const AssetsURL = "/_internal"
 | 
			
		||||
 | 
			
		||||
// Serve provides the needed assets for the front-end
 | 
			
		||||
func serveAssets(w http.ResponseWriter, r *http.Request, m *FileManager) (int, error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -27,8 +27,13 @@ func serveAssets(w http.ResponseWriter, r *http.Request, m *FileManager) (int, e
 | 
			
		|||
		filename = strings.Replace(filename, "/js/", "", 1)
 | 
			
		||||
		file, err = m.Assets.requiredJS.Bytes(filename)
 | 
			
		||||
	case strings.HasPrefix(filename, "/vendor"):
 | 
			
		||||
		if m.Assets.JS != nil {
 | 
			
		||||
			filename = strings.Replace(filename, "/vendor/", "", 1)
 | 
			
		||||
			file, err = m.Assets.JS.Bytes(filename)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fallthrough
 | 
			
		||||
	default:
 | 
			
		||||
		err = errors.New("not found")
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										4
									
								
								info.go
								
								
								
								
							
							
						
						
									
										4
									
								
								info.go
								
								
								
								
							| 
						 | 
				
			
			@ -77,7 +77,7 @@ var textExtensions = [...]string{
 | 
			
		|||
 | 
			
		||||
// RetrieveFileType obtains the mimetype and a simplified internal Type
 | 
			
		||||
// using the first 512 bytes from the file.
 | 
			
		||||
func (i FileInfo) RetrieveFileType() error {
 | 
			
		||||
func (i *FileInfo) RetrieveFileType() error {
 | 
			
		||||
	i.Mimetype = mime.TypeByExtension(i.Extension)
 | 
			
		||||
 | 
			
		||||
	if i.Mimetype == "" {
 | 
			
		||||
| 
						 | 
				
			
			@ -128,7 +128,7 @@ func (i FileInfo) RetrieveFileType() error {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// Reads the file.
 | 
			
		||||
func (i FileInfo) Read() error {
 | 
			
		||||
func (i *FileInfo) Read() error {
 | 
			
		||||
	if len(i.content) != 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue