diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..7a8f771e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "_assets/js/vendor/ace"] + path = _assets/js/vendor/ace + url = https://github.com/ajaxorg/ace-builds diff --git a/_assets/css/fonts.css b/_assets/css/fonts.css new file mode 100644 index 00000000..1911d377 --- /dev/null +++ b/_assets/css/fonts.css @@ -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'; +} diff --git a/_assets/css/material/icons.woff2 b/_assets/css/material/icons.woff2 new file mode 100644 index 00000000..9fa21125 Binary files /dev/null and b/_assets/css/material/icons.woff2 differ diff --git a/_assets/css/normalize.css b/_assets/css/normalize.css new file mode 100644 index 00000000..9b77e0eb --- /dev/null +++ b/_assets/css/normalize.css @@ -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; +} diff --git a/_assets/css/roboto/medium-cyrillic-ext.woff2 b/_assets/css/roboto/medium-cyrillic-ext.woff2 new file mode 100644 index 00000000..f63bc9a1 Binary files /dev/null and b/_assets/css/roboto/medium-cyrillic-ext.woff2 differ diff --git a/_assets/css/roboto/medium-cyrillic.woff2 b/_assets/css/roboto/medium-cyrillic.woff2 new file mode 100644 index 00000000..b3ca824d Binary files /dev/null and b/_assets/css/roboto/medium-cyrillic.woff2 differ diff --git a/_assets/css/roboto/medium-greek-ext.woff2 b/_assets/css/roboto/medium-greek-ext.woff2 new file mode 100644 index 00000000..7e1a8078 Binary files /dev/null and b/_assets/css/roboto/medium-greek-ext.woff2 differ diff --git a/_assets/css/roboto/medium-greek.woff2 b/_assets/css/roboto/medium-greek.woff2 new file mode 100644 index 00000000..314cf3f8 Binary files /dev/null and b/_assets/css/roboto/medium-greek.woff2 differ diff --git a/_assets/css/roboto/medium-latin-ext.woff2 b/_assets/css/roboto/medium-latin-ext.woff2 new file mode 100644 index 00000000..604b8935 Binary files /dev/null and b/_assets/css/roboto/medium-latin-ext.woff2 differ diff --git a/_assets/css/roboto/medium-latin.woff2 b/_assets/css/roboto/medium-latin.woff2 new file mode 100644 index 00000000..5f96609d Binary files /dev/null and b/_assets/css/roboto/medium-latin.woff2 differ diff --git a/_assets/css/roboto/medium-vietnamese.woff2 b/_assets/css/roboto/medium-vietnamese.woff2 new file mode 100644 index 00000000..d92b7125 Binary files /dev/null and b/_assets/css/roboto/medium-vietnamese.woff2 differ diff --git a/_assets/css/roboto/normal-cyrillic-ext.woff2 b/_assets/css/roboto/normal-cyrillic-ext.woff2 new file mode 100644 index 00000000..e4546e49 Binary files /dev/null and b/_assets/css/roboto/normal-cyrillic-ext.woff2 differ diff --git a/_assets/css/roboto/normal-cyrillic.woff2 b/_assets/css/roboto/normal-cyrillic.woff2 new file mode 100644 index 00000000..d08397f7 Binary files /dev/null and b/_assets/css/roboto/normal-cyrillic.woff2 differ diff --git a/_assets/css/roboto/normal-greek-ext.woff2 b/_assets/css/roboto/normal-greek-ext.woff2 new file mode 100644 index 00000000..ed0b13ca Binary files /dev/null and b/_assets/css/roboto/normal-greek-ext.woff2 differ diff --git a/_assets/css/roboto/normal-greek.woff2 b/_assets/css/roboto/normal-greek.woff2 new file mode 100644 index 00000000..f630772d Binary files /dev/null and b/_assets/css/roboto/normal-greek.woff2 differ diff --git a/_assets/css/roboto/normal-latin-ext.woff2 b/_assets/css/roboto/normal-latin-ext.woff2 new file mode 100644 index 00000000..0c7aec28 Binary files /dev/null and b/_assets/css/roboto/normal-latin-ext.woff2 differ diff --git a/_assets/css/roboto/normal-latin.woff2 b/_assets/css/roboto/normal-latin.woff2 new file mode 100644 index 00000000..120796bb Binary files /dev/null and b/_assets/css/roboto/normal-latin.woff2 differ diff --git a/_assets/css/roboto/normal-vietnamese.woff2 b/_assets/css/roboto/normal-vietnamese.woff2 new file mode 100644 index 00000000..7936b665 Binary files /dev/null and b/_assets/css/roboto/normal-vietnamese.woff2 differ diff --git a/_assets/css/styles.css b/_assets/css/styles.css new file mode 100644 index 00000000..eb9d8a5b --- /dev/null +++ b/_assets/css/styles.css @@ -0,0 +1,1207 @@ +body { + font-family: 'Roboto', sans-serif; + padding-top: 7.8em; + background-color: #f8f8f8; +} + +* { + box-sizing: border-box; +} + +*, +*:hover, +*:active, +*:focus { + outline: 0 +} + +a { + text-decoration: none; +} + +img { + max-width: 100%; +} + +audio, +video { + width: 100%; +} + +pre { + padding: 1em; + border: 1px solid #e6e6e6; + border-radius: 0.5em; + background-color: #f5f5f5; + white-space: pre-wrap; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + word-wrap: break-word; +} + +button { + border: 0; + padding: .5em 1em; + margin-left: .5em; + border-radius: .1em; + cursor: pointer; + background: #2196f3; + color: #fff; + border: 1px solid rgba(0, 0, 0, 0.05); + box-shadow: 0 0 5px rgba(0, 0, 0, 0.05); + transition: .1s ease all; +} + +button:hover { + background-color: #1E88E5; +} + +.mobile-only { + display: none !important; +} + +.container { + width: 95%; + max-width: 960px; + margin: 1em auto 0; +} + +i.spin { + animation: 1s spin linear infinite; +} + +.pdf { + width: 100%; + height: calc(100vh - 13em); +} + + +/* * * * * * * * * * * * * * * * + * EDITOR * + * * * * * * * * * * * * * * * */ + +#editor .source { + display: none; +} + +#editor .content { + background: #fff; + padding: 1em 0; +} + +#editor #ace, +#editor h2, +#editor .frontmatter { + width: 95%; + max-width: 960px; + margin: 1em auto 0; +} + +#editor h2 { + margin: 1.5em auto 1em; + color: rgba(0, 0, 0, 0.3); + font-weight: 500; +} + +#editor .ace_gutter { + background-color: #fff; +} + + +/* * * * * * * * * * * * * * * * + * EDITOR - MARKDOWN * + * * * * * * * * * * * * * * * */ + +.frontmatter { + column-count: 3; + column-gap: 1em; + column-fill: balance; + /* display: flex; */ + /* flex-wrap: wrap; */ + /* justify-content: space-between; */ + /* flex-grow: 1; */ +} + +.frontmatter label { + display: block; + width: calc(100% - 1em); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.frontmatter label, +.frontmatter h3 { + font-weight: 500; + margin: 0 0; + color: rgba(0, 0, 0, 0.6); +} + +.frontmatter input, +.frontmatter textarea { + display: block; + width: 100%; + border: 0; + margin-top: .5em; + padding: 0; + line-height: 1; +} + +.frontmatter .block, +.frontmatter fieldset[data-type="array"], +.button { + position: relative; + background: #fff; + border-radius: .2em; + border: 1px solid rgba(0, 0, 0, 0.075); + padding: .5em; + break-inside: avoid; + margin: 0 0 1em; + width: 100%; + display: inline-block; +} + +.frontmatter fieldset[data-type="object"] { + position: relative; + margin: 0; +} + +.frontmatter .button { + background-color: #2196f3; + color: #fff; + cursor: pointer; + text-align: center; +} + +[data-type="array-item"] { + position: relative; +} + +[data-type="array-item"] .action { + top: 0; + right: 0; +} + +.frontmatter textarea { + resize: none; +} + +[data-type="array-item"] input { + width: calc(100% - 1em); +} + +.block .action, +fieldset .action { + position: absolute; + top: .5em; + right: .5em; +} + +.block>.action, +fieldset>.action { + opacity: 0; +} + +.block:hover>.action, +fieldset:hover>.action { + opacity: 1; +} + +.block .action.add, +fieldset .action.add { + right: 1.5em; +} + +.frontmatter .action i { + padding: 0; + font-size: 1em; +} + +fieldset { + border: 0; + padding: 0; +} + +.frontmatter>fieldset h3, +.frontmatter>.group h3 { + font-size: 1.5em; + margin-bottom: .5em; +} + +fieldset h3, +.group h3 { + font-size: 0.9em; +} + + +/* * * * * * * * * * * * * * * * + * ACTION * + * * * * * * * * * * * * * * * */ + +.action { + display: inline-block; + cursor: pointer; + -webkit-transition: 0.2s ease all; + transition: 0.2s ease all; + border: 0; + margin: 0; + color: #546E7A; + border-radius: 50%; +} + +.action.disabled { + opacity: 0.2; + cursor: not-allowed; +} + +.action i { + padding: 0.4em; + -webkit-transition: 0.2s ease-in-out all; + transition: 0.2s ease-in-out all; + border-radius: 50%; +} + +.action:hover i { + background-color: rgba(0, 0, 0, .1); +} + +.action ul { + position: absolute; + top: 0; + color: #7d7d7d; + list-style: none; + margin: 0; + padding: 0; + flex-direction: column; + display: flex; +} + +.action ul li { + line-height: 1; + padding: .7em; + transition: .1s ease background-color; +} + +.action ul li:hover { + background-color: rgba(0, 0, 0, 0.04); +} + + +/* * * * * * * * * * * * * * * * + * NEW FILE/DIR * + * * * * * * * * * * * * * * * */ + +.floating { + position: fixed; + bottom: 1em; + right: 1em; +} + +.floating .action { + background-color: #2196f3 !important; + color: #fff; + box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px rgba(0, 0, 0, .12); +} + +#newdir { + position: fixed; + bottom: 1.3em; + right: 5em; + transition: .2s ease all; + opacity: 0; + border: 0; + box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24); + padding: .5em; + width: 22em; + border-radius: .2em; +} + +#newdir.enabled { + opacity: 1; +} + + +/* * * * * * * * * * * * * * * * + * HEADER * + * * * * * * * * * * * * * * * */ + +header { + z-index: 1000; + background-color: #fff; + border-bottom: 1px solid rgba(0, 0, 0, 0.075); + box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); + position: fixed; + top: 0; + left: 0; + width: 100%; + padding: 0; +} + +header a, +header a:hover { + color: inherit; +} + +header p i { + font-size: 1em !important; + color: rgba(255, 255, 255, .31); +} + +header>div { + display: flex; + width: 100%; + padding: 0.5em 0.5em 0.5em 1em; + align-items: center; +} + +header p { + display: inline-block; + margin: 0; + vertical-align: middle; +} + +header p a, +header p a:hover { + color: inherit; +} + +header .action span { + display: none; +} + +header>div div { + vertical-align: middle; + position: relative; +} + +#logout { + border-radius: 0; + margin-left: auto; + padding: .15em; +} + +#click-overlay { + display: none; + position: fixed; + cursor: pointer; + top: 0; + left: 0; + height: 100%; + width: 100%; +} + +#click-overlay.active { + display: block; +} + + +/* * * * * * * * * * * * * * * * + * TOP BAR * + * * * * * * * * * * * * * * * */ + +#top-bar { + height: 4em; +} + +#top-bar>div:nth-child(1) { + margin-right: 1em; + font-weight: 500; + font-size: 1.5em; + line-height: 2; +} + + +/* * * * * * * * * * * * * * * * + * SEARCH BAR * + * * * * * * * * * * * * * * * */ + +#search { + position: relative; + display: flex; + height: 100%; + padding: 0.75em; + vertical-align: middle; + border-radius: 0.3em; + background-color: #f5f5f5; + transition: .1s ease all; + width: 100%; + max-width: 25em; +} + +#search.active { + background-color: #fff; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px rgba(0, 0, 0, .12); +} + +#search.active i, +#search.active input { + color: #212121; +} + +#search i, +#search input { + vertical-align: middle; +} + +#search i { + margin-right: 0.3em; + user-select: none; +} + +#search input { + width: 100%; + border: 0; + outline: 0; + background-color: transparent; +} + +#search.active div { + visibility: visible; + opacity: 1; + top: 100%; +} + +#search ul { + padding: 0; + margin: 0; + list-style: none; +} + +#search li { + margin-bottom: .5em; +} + +#search>div { + position: absolute; + top: 0; + width: 100%; + left: 0; + z-index: 999999; + background-color: #fff; + text-align: left; + color: #ccc; + box-shadow: 0 2px 3px rgba(0, 0, 0, .06), 0 2px 2px rgba(0, 0, 0, .12); + padding: .5em; + border-bottom-left-radius: .3em; + border-bottom-right-radius: .3em; + transition: .1s ease all; + visibility: hidden; + opacity: 0; + overflow-x: hidden; + overflow-y: auto; + max-height: 50vh; +} + +#search>div div { + white-space: pre-wrap; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + word-wrap: break-word; +} + +#search>div p { + width: 100%; + text-align: center; + display: none; + margin: 0; + max-width: none; +} + +#search.ongoing p { + display: block; +} + +#search.active div i, +#sidebar #search.active div i { + color: #ccc; + text-align: center; + margin: 0 auto; + display: table; +} + +#search::-webkit-input-placeholder { + color: rgba(255, 255, 255, .5); +} + +#search:-moz-placeholder { + opacity: 1; + color: rgba(255, 255, 255, .5); +} + +#search::-moz-placeholder { + opacity: 1; + color: rgba(255, 255, 255, .5); +} + +#search:-ms-input-placeholder { + color: rgba(255, 255, 255, .5); +} + + +/* * * * * * * * * * * * * * * * + * BOTTOM BAR * + * * * * * * * * * * * * * * * */ + +#bottom-bar { + background-color: #fafafa; + border-top: 1px solid rgba(0, 0, 0, 0.075); + border-bottom: 1px solid rgba(0, 0, 0, 0.075); + height: 3.8em; +} + +#bottom-bar>div:first-child>* { + display: inline-block; + vertical-align: middle; +} + +#bottom-bar>div:first-child>i { + margin-right: .3em; +} + +#bottom-bar>*:first-child { + margin-right: auto; + max-width: calc(100% - 25em); + width: 100%; +} + +#bottom-bar p { + text-overflow: ellipsis; + overflow: hidden; + width: calc(100% - 3em); + white-space: nowrap; +} + +#more { + display: none; +} + +#file-only { + display: inline-block; + border-right: 1px solid rgba(0, 0, 0, 0.075); + padding-right: .3em; + margin-right: .3em; + transition: .2s ease opacity, visibility; + visibility: visible; +} + +#file-only.disabled { + opacity: 0; + visibility: hidden; +} + +#download ul.active { + top: 0; + right: 0; +} + +#more ul.active { + right: .5em; + top: 4.5em; +} + + +/* * * * * * * * * * * * * * * * + * DROPDOWN * + * * * * * * * * * * * * * * * */ + +.dropdown { + position: fixed; + top: -100%; + right: -100%; + visibility: hidden; + display: flex; + flex-direction: column; + border-radius: .1em; + border-top-left-radius: 0; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); + background: #fff; + z-index: 9999999; +} + +.dropdown.active { + visibility: visible; +} + +.dropdown .action { + padding: .7em; +} + +.dropdown i { + padding: 0; + vertical-align: middle; +} + +.dropdown span { + display: inline-block; + margin-left: .5em; + font-size: .9em; +} + + +/* * * * * * * * * * * * * * * * + * BREADCRUMBS * + * * * * * * * * * * * * * * * */ + +#previous { + margin-left: -.5em; +} + +#breadcrumbs { + min-width: 7em; +} + +#breadcrumbs.active { + top: 0; + left: 0; + right: auto; +} + + +/* * * * * * * * * * * * * * * * + * LISTING * + * * * * * * * * * * * * * * * */ + +#listing { + max-width: calc(100% - 1.2em); + width: 100%; +} + +#listing h2 { + margin: 0 0 0 0.5em; + font-size: 1em; + color: rgba(0, 0, 0, 0.2); + font-weight: 500; +} + +#listing .item div:last-of-type * { + text-overflow: ellipsis; + overflow: hidden; +} + +#listing>div { + display: flex; + padding: 0; + flex-wrap: wrap; + justify-content: flex-start; + position: relative; +} + +#listing .item { + background-color: #fff; + position: relative; + display: flex; + flex-wrap: nowrap; + color: #6f6f6f; + transition: .1s ease all; + align-items: center; + cursor: pointer; +} + +#listing .item div:last-of-type { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +#listing .item p { + margin: 0; +} + +#listing .item .size, +#listing .item .modified { + font-size: 0.9em; +} + +#listing .item .name { + font-weight: bold; +} + +#listing .item i { + font-size: 4em; + margin-right: 0.1em; + vertical-align: bottom; +} + +#listing h2.message, +.message { + text-align: center; + font-size: 3em; + margin: 1em auto; + display: block !important; + width: 95%; + color: rgba(0, 0, 0, 0.2); + font-weight: 500; +} + +.message i { + font-size: inherit; + vertical-align: middle; +} + + +/* * * * * * * * * * * * * * * * + * LISTING - MOSAIC * + * * * * * * * * * * * * * * * */ + +#listing.mosaic { + margin-top: 1em; +} + +#listing.mosaic .item { + width: calc(33% - 1em); + margin: .5em; + padding: 0.5em; + border-radius: 0.2em; + box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px rgba(0, 0, 0, .12); +} + +#listing.mosaic .item:hover { + box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24) !important; +} + +#listing.mosaic .header { + display: none; +} + +#listing.mosaic .item div:first-of-type { + width: 5em; +} + +#listing.mosaic .item div:last-of-type { + width: calc(100% - 5vw); +} + + +/* * * * * * * * * * * * * * * * + * LISTING - DETAIL * + * * * * * * * * * * * * * * * */ + +#listing.list { + flex-direction: column; + padding-top: 3.25em; + width: 100%; + max-width: 100%; + margin: 0; +} + +#listing.list .item { + width: 100%; + margin: 0; + border: 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding: 1em; +} + +#listing.list h2 { + display: none; +} + +#listing .item[aria-selected=true] { + background: #2196f3 !important; + color: #fff !important; +} + +#listing.list .item div:first-of-type { + width: 3em; +} + +#listing.list .item div:first-of-type i { + font-size: 2em; +} + +#listing.list .item div:last-of-type { + width: calc(100% - 3em); + display: flex; + align-items: center; +} + +#listing.list .item .name { + width: 50%; +} + +#listing.list .item .size { + width: 25%; +} + +#listing .item.header { + display: none !important; + background-color: #ccc; +} + +#listing.list .header i { + font-size: 1.5em; + vertical-align: middle; + margin-left: .2em; +} + +#listing.list .item.header { + display: flex !important; + background: #fafafa; + position: fixed; + width: 100%; + top: 7.8em; + left: 0; + z-index: 999; + padding: .85em; +} + +#listing.list .item.header>div:first-child { + width: 0; +} + +#listing.list .item.header .name { + margin-right: 3em; +} + +#listing.list .header { + display: flex; + background: #fafafa; + position: fixed; + width: 100%; + top: 7.8em; + left: 0; + z-index: 999; +} + +#listing.list .header a { + color: inherit; +} + +#listing.list .item.header>div:first-child { + width: 0; +} + +#listing.list .name { + font-weight: normal; +} + +#listing.list .item.header .name { + margin-right: 3em; +} + +#listing.list .header span { + vertical-align: middle; +} + +#listing.list .header i { + opacity: 0; + transition: .1s ease all; +} + +#listing.list .header p:hover i, +#listing.list .header .active i { + opacity: 1; +} + +#listing.list .item.header .active { + font-weight: bold; +} + + +/* * * * * * * * * * * * * * * * + * MULTIPLE SELECTION DIALOG * + * * * * * * * * * * * * * * * */ + +#multiple-selection { + position: fixed; + bottom: -4em; + left: 0; + z-index: 99999999; + width: 100%; + background-color: #2196f3; + height: 4em; + display: flex !important; + padding: 0.5em 0.5em 0.5em 1em; + justify-content: space-between; + align-items: center; + transition: .2s ease all; +} + +#multiple-selection.active { + bottom: 0; +} + +#multiple-selection * { + margin: 0; +} + +#multiple-selection p, +#multiple-selection i { + color: #fff; +} + + +/* * * * * * * * * * * * * * * * + * PROMPT * + * * * * * * * * * * * * * * * */ + +.overlay, +.prompt, +.help { + opacity: 0; + z-index: -1; + transition: .1s ease opacity, z-index; +} + +.overlay.active, +.prompt.active, +.help.active { + z-index: 9999999; + opacity: 1; +} + +.overlay { + background-color: rgba(0, 0, 0, 0.5); + position: fixed; + top: 0; + left: 0; + height: 0; + width: 0; +} + +.overlay.active { + height: 100%; + width: 100%; +} + +.prompt, +.help { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 99999999; + background: #fff; + border: 1px solid rgba(0, 0, 0, 0.075); + box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); + padding: 2em; + max-width: 25em; + width: 90%; + max-height: 95%; +} + +.prompt h3, +.help h3 { + margin: 0; + font-weight: 500; + font-size: 1.5em; +} + +.prompt p, +.help p { + font-size: .9em; + color: rgba(0, 0, 0, 0.8); + margin: .5em 0 1em; +} + +.prompt input { + width: 100%; + border: 1px solid #dadada; + line-height: 1; + padding: .3em; +} + +.prompt code { + word-wrap: break-word; +} + +.prompt div, +.help div { + margin-top: 1em; + display: flex; + justify-content: flex-start; + flex-direction: row-reverse; +} + +.prompt .cancel { + background-color: #ECEFF1; + color: #37474F; +} + +.prompt .cancel:hover { + background-color: #e9eaeb; +} + + +/* * * * * * * * * * * * * * * * + * PROMPT - MOVE * + * * * * * * * * * * * * * * * */ + +.prompt .file-list { + flex-direction: initial; + max-height: 50vh; + overflow: auto; +} + +.prompt .file-list ul { + list-style: none; + margin: 0; + padding: 0; + width: 100%; +} + +.prompt .file-list ul li { + width: 100%; + user-select: none; +} + +.prompt .file-list ul li[aria-selected=true] { + background: #2196f3 !important; + color: #fff !important; + transition: .1s ease all; +} + +.prompt .file-list ul li:hover { + background-color: #e9eaeb; + cursor: pointer; +} + +.prompt .file-list ul li:before { + content: "folder"; + color: #6f6f6f; + vertical-align: middle; + padding: 0 .25em; + line-height: 2em; +} + +.prompt .file-list ul li[aria-selected=true]:before { + color: white; +} + + +/* * * * * * * * * * * * * * * * + * HELP * + * * * * * * * * * * * * * * * */ + +.help { + max-width: 24em; + visibility: hidden; + top: -100%; + left: -100%; +} + +.help.active { + visibility: visible; + top: 50%; + left: 50%; +} + +.help ul { + padding: 0; + margin: 1em 0; + list-style: none; +} + + +/* * * * * * * * * * * * * * * * + * FOOTER * + * * * * * * * * * * * * * * * */ + +footer { + font-size: 0.6em; + margin: 2em 0 2em; + text-align: center; + color: grey; +} + +footer a, +footer a:hover { + color: inherit; +} + + +/* * * * * * * * * * * * * * * * + * MEDIA QUERIES * + * * * * * * * * * * * * * * * */ + +@media screen and (max-width: 850px) { + .frontmatter { + column-count: 2; + } +} + +@media screen and (max-width: 650px) { + body { + transition: .2s ease padding; + } + .mobile-only { + display: inherit !important; + } + #top-bar>div:nth-child(1) { + display: none; + } + #bottom-bar>*:first-child { + max-width: calc(100% - 16em) !important; + } + #main-actions { + position: fixed; + top: -100%; + right: -100%; + visibility: hidden; + display: flex; + flex-direction: column; + border-radius: .1em; + border-top-left-radius: 0; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); + background: #fff; + z-index: 9999999; + } + #main-actions.active { + right: .5em; + top: 4.5em; + visibility: visible; + } + #main-actions .action { + padding: .7em; + border-radius: 0; + align-items: center; + } + #main-actions .action:hover { + background-color: rgba(0, 0, 0, 0.04); + } + #main-actions i { + padding: 0; + vertical-align: middle; + } + #main-actions .action:hover i { + padding: 0; + background-color: transparent; + } + #main-actions span { + display: inline-block; + margin-left: .5em; + font-size: .9em; + } + #listing.list .item .size, + #listing.list .item .modified { + display: none; + } + #listing.list .item .name { + width: 100%; + } + .frontmatter { + column-count: 1; + } +} + +@media screen and (max-width: 450px) { + #bottom-bar p { + display: none !important; + } +} + + +/* * * * * * * * * * * * * * * * + * ANIMATIONS * + * * * * * * * * * * * * * * * */ + +@keyframes spin { + 100% { + -webkit-transform: rotate(-360deg); + transform: rotate(-360deg); + } +} diff --git a/_assets/js/common.js b/_assets/js/common.js new file mode 100644 index 00000000..76cae2a3 --- /dev/null +++ b/_assets/js/common.js @@ -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 = '
:array
or :object.
'
+ 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
+})
diff --git a/_assets/js/listing.js b/_assets/js/listing.js
new file mode 100644
index 00000000..baef101d
--- /dev/null
+++ b/_assets/js/listing.js
@@ -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)
+ }
+})
diff --git a/_assets/js/vendor/ace b/_assets/js/vendor/ace
new file mode 160000
index 00000000..0b01260b
--- /dev/null
+++ b/_assets/js/vendor/ace
@@ -0,0 +1 @@
+Subproject commit 0b01260b38f4db87b2b862e0c7f12e7e2e2905fd
diff --git a/_assets/js/vendor/form2js.js b/_assets/js/vendor/form2js.js
new file mode 100644
index 00000000..2614c194
--- /dev/null
+++ b/_assets/js/vendor/form2js.js
@@ -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;
+
+}));
diff --git a/_assets/templates/base.tmpl b/_assets/templates/base.tmpl
new file mode 100644
index 00000000..85b9a3ef
--- /dev/null
+++ b/_assets/templates/base.tmpl
@@ -0,0 +1,289 @@
+
+
+{{ $absURL := .Config.AbsoluteURL }}
+
+ File Manager
autorenew
+{{ .Name }}
{{ end }} +Multiple selection enabled
+Display Name:
+Content Length: Bytes
+Last Modified:
+ +MD5: show
SHA1: show
SHA256: show
SHA512: show
Not available yet
+ +Name + {{- if eq .Sort "name" -}} + {{- if eq .Order "asc" -}} + arrow_downward + {{- else -}} + arrow_upward + {{- end -}} + {{- else -}} + arrow_downward + {{- end -}} +
+File Size + {{- if eq .Sort "size" -}} + {{- if eq .Order "asc" -}} + arrow_downward + {{- else -}} + arrow_upward + {{- end -}} + {{- else -}} + arrow_downward + {{- end -}} +
+Last modified
+{{.Name}}
+ {{- if .IsDir}} +—
+ {{- else}} +{{.HumanSize}}
+ {{- end}} ++ +
+{{ .StringifyContent }}+ {{ end }} +