diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 63339e38..00000000 --- a/.editorconfig +++ /dev/null @@ -1,18 +0,0 @@ -# EditorConfig is awesome: http://EditorConfig.org - -# top-most EditorConfig file -root = true - -# Unix-style newlines with a newline ending every file -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true - -# 4 space indentation -[*.go] -indent_style = tab -indent_size = 4 diff --git a/.gitignore b/.gitignore deleted file mode 100644 index d287cd3e..00000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -debug diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e6077a69..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "_embed/public/ace"] - path = _embed/public/ace - url = https://github.com/ajaxorg/ace-builds diff --git a/.jsbeautifyrc b/.jsbeautifyrc deleted file mode 100644 index 71f718e7..00000000 --- a/.jsbeautifyrc +++ /dev/null @@ -1,22 +0,0 @@ -{ - "html": { - "brace_style": "collapse", - "indent_scripts": "normal", - "max_preserve_newlines": 1, - "preserve_newlines": true, - "unformatted": ["a", "sub", "sup", "b", "i", "u"], - "wrap_line_length": 0 - }, - "css": { - "end_with_newline": false, - "newline_between_rules": true, - "selector_separator": " ", - "selector_separator_newline": true - }, - "js": { - "indent_with_tabs": false, - "preserve_newlines": true, - "max_preserve_newlines": 2, - "jslint_happy": true - } -} diff --git a/_embed/public/ace b/_embed/public/ace deleted file mode 160000 index 784ffa86..00000000 --- a/_embed/public/ace +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 784ffa862c5351e0d300370f61471b1eb95ebcf1 diff --git a/_embed/public/css/fonts.css b/_embed/public/css/fonts.css deleted file mode 100644 index 1911d377..00000000 --- a/_embed/public/css/fonts.css +++ /dev/null @@ -1,137 +0,0 @@ -@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/_embed/public/css/material/icons.woff2 b/_embed/public/css/material/icons.woff2 deleted file mode 100644 index 9fa21125..00000000 Binary files a/_embed/public/css/material/icons.woff2 and /dev/null differ diff --git a/_embed/public/css/normalize.css b/_embed/public/css/normalize.css deleted file mode 100644 index 9b77e0eb..00000000 --- a/_embed/public/css/normalize.css +++ /dev/null @@ -1,461 +0,0 @@ -/*! 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/_embed/public/css/roboto/medium-cyrillic-ext.woff2 b/_embed/public/css/roboto/medium-cyrillic-ext.woff2 deleted file mode 100644 index f63bc9a1..00000000 Binary files a/_embed/public/css/roboto/medium-cyrillic-ext.woff2 and /dev/null differ diff --git a/_embed/public/css/roboto/medium-cyrillic.woff2 b/_embed/public/css/roboto/medium-cyrillic.woff2 deleted file mode 100644 index b3ca824d..00000000 Binary files a/_embed/public/css/roboto/medium-cyrillic.woff2 and /dev/null differ diff --git a/_embed/public/css/roboto/medium-greek-ext.woff2 b/_embed/public/css/roboto/medium-greek-ext.woff2 deleted file mode 100644 index 7e1a8078..00000000 Binary files a/_embed/public/css/roboto/medium-greek-ext.woff2 and /dev/null differ diff --git a/_embed/public/css/roboto/medium-greek.woff2 b/_embed/public/css/roboto/medium-greek.woff2 deleted file mode 100644 index 314cf3f8..00000000 Binary files a/_embed/public/css/roboto/medium-greek.woff2 and /dev/null differ diff --git a/_embed/public/css/roboto/medium-latin-ext.woff2 b/_embed/public/css/roboto/medium-latin-ext.woff2 deleted file mode 100644 index 604b8935..00000000 Binary files a/_embed/public/css/roboto/medium-latin-ext.woff2 and /dev/null differ diff --git a/_embed/public/css/roboto/medium-latin.woff2 b/_embed/public/css/roboto/medium-latin.woff2 deleted file mode 100644 index 5f96609d..00000000 Binary files a/_embed/public/css/roboto/medium-latin.woff2 and /dev/null differ diff --git a/_embed/public/css/roboto/medium-vietnamese.woff2 b/_embed/public/css/roboto/medium-vietnamese.woff2 deleted file mode 100644 index d92b7125..00000000 Binary files a/_embed/public/css/roboto/medium-vietnamese.woff2 and /dev/null differ diff --git a/_embed/public/css/roboto/normal-cyrillic-ext.woff2 b/_embed/public/css/roboto/normal-cyrillic-ext.woff2 deleted file mode 100644 index e4546e49..00000000 Binary files a/_embed/public/css/roboto/normal-cyrillic-ext.woff2 and /dev/null differ diff --git a/_embed/public/css/roboto/normal-cyrillic.woff2 b/_embed/public/css/roboto/normal-cyrillic.woff2 deleted file mode 100644 index d08397f7..00000000 Binary files a/_embed/public/css/roboto/normal-cyrillic.woff2 and /dev/null differ diff --git a/_embed/public/css/roboto/normal-greek-ext.woff2 b/_embed/public/css/roboto/normal-greek-ext.woff2 deleted file mode 100644 index ed0b13ca..00000000 Binary files a/_embed/public/css/roboto/normal-greek-ext.woff2 and /dev/null differ diff --git a/_embed/public/css/roboto/normal-greek.woff2 b/_embed/public/css/roboto/normal-greek.woff2 deleted file mode 100644 index f630772d..00000000 Binary files a/_embed/public/css/roboto/normal-greek.woff2 and /dev/null differ diff --git a/_embed/public/css/roboto/normal-latin-ext.woff2 b/_embed/public/css/roboto/normal-latin-ext.woff2 deleted file mode 100644 index 0c7aec28..00000000 Binary files a/_embed/public/css/roboto/normal-latin-ext.woff2 and /dev/null differ diff --git a/_embed/public/css/roboto/normal-latin.woff2 b/_embed/public/css/roboto/normal-latin.woff2 deleted file mode 100644 index 120796bb..00000000 Binary files a/_embed/public/css/roboto/normal-latin.woff2 and /dev/null differ diff --git a/_embed/public/css/roboto/normal-vietnamese.woff2 b/_embed/public/css/roboto/normal-vietnamese.woff2 deleted file mode 100644 index 7936b665..00000000 Binary files a/_embed/public/css/roboto/normal-vietnamese.woff2 and /dev/null differ diff --git a/_embed/public/css/styles.css b/_embed/public/css/styles.css deleted file mode 100644 index eb9d8a5b..00000000 --- a/_embed/public/css/styles.css +++ /dev/null @@ -1,1207 +0,0 @@ -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/_embed/public/js/common.js b/_embed/public/js/common.js deleted file mode 100644 index 76cae2a3..00000000 --- a/_embed/public/js/common.js +++ /dev/null @@ -1,685 +0,0 @@ -'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 = '' - - 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 += '
  • ' + event.data + '
  • ' - 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 -}) diff --git a/_embed/public/js/editor.js b/_embed/public/js/editor.js deleted file mode 100644 index 38ff40c7..00000000 --- a/_embed/public/js/editor.js +++ /dev/null @@ -1,278 +0,0 @@ -'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', '
    ') - 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 :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/_embed/public/js/listing.js b/_embed/public/js/listing.js deleted file mode 100644 index baef101d..00000000 --- a/_embed/public/js/listing.js +++ /dev/null @@ -1,580 +0,0 @@ -'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/_embed/public/js/vendor/form2js.js b/_embed/public/js/vendor/form2js.js deleted file mode 100644 index 2614c194..00000000 --- a/_embed/public/js/vendor/form2js.js +++ /dev/null @@ -1,356 +0,0 @@ -/** - * 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/_embed/templates/base.tmpl b/_embed/templates/base.tmpl deleted file mode 100644 index 38fbeaf5..00000000 --- a/_embed/templates/base.tmpl +++ /dev/null @@ -1,292 +0,0 @@ - - -{{ $absURL := .Config.AbsoluteURL }} - - {{.Name}} - - - - - - {{- if ne .User.StyleSheet "" -}} - - {{- end -}} - - - - {{- if .IsDir }} - - {{- else }} - - - - {{- end }} - {{- if .Config.HugoEnabled }} - - {{- end }} - - -
    -
    -

    File Manager

    - - -
    - exit_to_app -
    -
    - -
    -
    - {{- if ne .Name "/"}} - - {{- end }} - - {{ if ne .Name "/"}}

    {{ .Name }}

    {{ end }} -
    - -
    - {{- if and (not .IsDir) (.User.AllowEdit) }} - {{- if .Editor}} - - {{- if eq .Data.Mode "markdown" }} -
    - remove_red_eye -
    - {{- end }} - - {{- if eq .Data.Visual true }} -
    - code -
    - {{- end }} - {{- end }} - -
    - save -
    - {{- end }} - - {{- if .IsDir }} -
    - open_in_new - See raw -
    - {{- end }} - - {{- if and (.User.AllowEdit) (.IsDir) }} -
    - forward - Move file -
    - {{- end }} - - {{- if and .IsDir .User.AllowEdit }} -
    - mode_edit -
    - {{- end }} - - {{- if and .User.AllowEdit .IsDir }} -
    - deleteDelete -
    - {{- end }} -
    - -
    - more_vert -
    - -
    - {{- if .IsDir }} -
    - {{- if eq .Display "mosaic" }} - - view_listSwitch view - - {{- else }} - - view_moduleSwitch view - - {{- end }} -
    - -
    - check_circleSelect -
    - {{- end }} - - {{- if and (.User.AllowNew) (.IsDir) }} -
    - file_uploadUpload -
    - {{- end }} - - {{- if not .IsDir }} -
    - open_in_new - See raw -
    - {{- end }} - - {{- if and .User.AllowEdit (not .IsDir) }} -
    - deleteDelete -
    - {{- end }} - -
    - {{- if not .IsDir}}{{ end }} - file_downloadDownload - {{- if not .IsDir}}{{ end }} - - {{- if .IsDir }} - - {{- end }} -
    - -
    - infoInfo -
    -
    -
    - -
    -
    - -
    -

    Multiple selection enabled

    -
    - clear -
    -
    - -
    - {{- template "content" . }} -
    - -
    - - {{- if and (.User.AllowNew) (.IsDir) }} -
    -
    - add -
    -
    - {{- end }} - - - - - - - - - -
    -

    Help

    - - - -

    Not available yet

    - - - -
    - -
    -
    - - - - diff --git a/_embed/templates/editor.tmpl b/_embed/templates/editor.tmpl deleted file mode 100644 index d02238d8..00000000 --- a/_embed/templates/editor.tmpl +++ /dev/null @@ -1,57 +0,0 @@ -{{ define "content" }} -{{- with .Data }} -
    - {{- if or (eq .Class "frontmatter-only") (eq .Class "complete") }} - {{- if (eq .Class "complete")}} -

    Metadata

    - {{- end }} -
    - {{- template "blocks" .FrontMatter.Content }} -
    Add field
    -
    - {{- end }} - - {{ if or (eq .Class "content-only") (eq .Class "complete") }} - {{ if (eq .Class "complete")}} -

    Body

    - {{ end }} -
    -
    - -
    - {{ end }} -
    -{{- end }} - - - - - - -{{ end }} diff --git a/_embed/templates/frontmatter.tmpl b/_embed/templates/frontmatter.tmpl deleted file mode 100644 index 3389da90..00000000 --- a/_embed/templates/frontmatter.tmpl +++ /dev/null @@ -1,56 +0,0 @@ -{{ define "blocks" }} -{{ if .Fields }}
    {{ end }} -{{- range $key, $value := .Fields }} - {{- if eq $value.Parent.Type "array" }} -
    - {{- template "value" $value }} -
    - close -
    -
    - {{- else }} -
    - - {{ template "value" $value }} -
    - close -
    -
    - {{- end }} -{{- end }} -{{- if .Fields }}
    {{ end }} - -{{- range $key, $value := .Arrays }} -{{- template "fielset" $value }} -{{- end }} - -{{- range $key, $value := .Objects }} -{{- template "fielset" $value }} -{{- end }} - -{{ end }} - -{{ define "value" }} -{{- if eq .HTMLType "textarea" }} - -{{- else if eq .HTMLType "datetime" }} - -{{- else }} - -{{- end }} -{{ end }} - -{{ define "fielset" }} -
    - {{- if not (eq .Title "") }} -

    {{ .Name }}

    - {{- end }} -
    - add -
    -
    - close -
    - {{- template "blocks" .Content }} -
    -{{ end }} diff --git a/_embed/templates/listing.tmpl b/_embed/templates/listing.tmpl deleted file mode 100644 index da94b962..00000000 --- a/_embed/templates/listing.tmpl +++ /dev/null @@ -1,103 +0,0 @@ -{{ define "content" }} -
    -{{- with .Data -}} -
    -
    -
    -
    -

    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

    -
    -
    -
    - - {{ if and (eq .NumDirs 0) (eq .NumFiles 0) }} -

    It feels lonely here :'(

    - {{ end }} - - {{- if not (eq .NumDirs 0)}} -

    Folders

    -
    - {{- range .Items }} - {{- if (.IsDir) }} - {{ template "item" .}} - {{- end }} - {{- end }} -
    - {{- end }} - - {{- if not (eq .NumFiles 0)}} -

    Files

    -
    - {{- range .Items }} - {{- if (not .IsDir) }} - {{ template "item" .}} - {{- end }} - {{- end }} -
    - {{- end }} -
    - - -{{- end -}} -{{- end -}} - -{{ define "item" }} -
    -
    - {{- if .IsDir}} - folder - {{- else}} - {{ if eq .Type "image" }} - insert_photo - {{ else if eq .Type "audio" }} - volume_up - {{ else if eq .Type "video" }} - movie - {{ else }} - insert_drive_file - {{ end }} - {{- end}} -
    -
    -

    {{.Name}}

    - {{- if .IsDir}} -

    - {{- else}} -

    {{.HumanSize}}

    - {{- end}} -

    - -

    -
    -
    -{{ end }} diff --git a/_embed/templates/minimal.tmpl b/_embed/templates/minimal.tmpl deleted file mode 100644 index 66e0068d..00000000 --- a/_embed/templates/minimal.tmpl +++ /dev/null @@ -1 +0,0 @@ -{{ template "content" . }} diff --git a/_embed/templates/single.tmpl b/_embed/templates/single.tmpl deleted file mode 100644 index c85ab5a4..00000000 --- a/_embed/templates/single.tmpl +++ /dev/null @@ -1,23 +0,0 @@ -{{ define "content" }} -{{ with .Data}} -
    - {{ if eq .Type "image" }} -
    - {{ else if eq .Type "audio" }} - - {{ else if eq .Type "video" }} - - {{ else if eq .Extension ".pdf" }} - - {{ else if eq .Type "blob" }} -

    Download file_download

    - {{ else}} -
    {{ .StringifyContent }}
    - {{ end }} -
    -{{ end }} -{{ end }} diff --git a/assets/assets.go b/assets/assets.go deleted file mode 100644 index 2c4744c2..00000000 --- a/assets/assets.go +++ /dev/null @@ -1,33 +0,0 @@ -package assets - -import ( - "mime" - "net/http" - "path/filepath" - "strings" - - "github.com/hacdias/caddy-filemanager/config" -) - -// BaseURL is the url of the assets -const BaseURL = "/_filemanagerinternal" - -// Serve provides the needed assets for the front-end -func Serve(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) { - // gets the filename to be used with Assets function - filename := strings.Replace(r.URL.Path, c.BaseURL+BaseURL, "public", 1) - file, err := Asset(filename) - if err != nil { - return http.StatusNotFound, nil - } - - // Get the file extension and its mimetype - extension := filepath.Ext(filename) - mediatype := mime.TypeByExtension(extension) - - // Write the header with the Content-Type and write the file - // content to the buffer - w.Header().Set("Content-Type", mediatype) - w.Write(file) - return 200, nil -} diff --git a/assets/binary.go.REMOVED.git-id b/assets/binary.go.REMOVED.git-id deleted file mode 100644 index 8fc30cca..00000000 --- a/assets/binary.go.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -4c30378a214b5b33410a74961df51cbc21bd6122 \ No newline at end of file diff --git a/config/commands.go b/config/commands.go deleted file mode 100644 index 60c3b881..00000000 --- a/config/commands.go +++ /dev/null @@ -1,62 +0,0 @@ -package config - -import ( - "log" - "net/http" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/mholt/caddy" -) - -// CommandFunc ... -type CommandFunc func(r *http.Request, c *Config, u *User) error - -// CommandRunner ... -func CommandRunner(c *caddy.Controller) (CommandFunc, error) { - fn := func(r *http.Request, c *Config, u *User) error { return nil } - - args := c.RemainingArgs() - if len(args) == 0 { - return fn, c.ArgErr() - } - - nonblock := false - if len(args) > 1 && args[len(args)-1] == "&" { - // Run command in background; non-blocking - nonblock = true - args = args[:len(args)-1] - } - - command, args, err := caddy.SplitCommandAndArgs(strings.Join(args, " ")) - if err != nil { - return fn, c.Err(err.Error()) - } - - fn = func(r *http.Request, c *Config, u *User) error { - path := strings.Replace(r.URL.Path, c.WebDavURL, "", 1) - path = u.Scope + "/" + path - path = filepath.Clean(path) - - for i := range args { - args[i] = strings.Replace(args[i], "{path}", path, -1) - } - - cmd := exec.Command(command, args...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if nonblock { - log.Printf("[INFO] Nonblocking Command:\"%s %s\"", command, strings.Join(args, " ")) - return cmd.Start() - } - - log.Printf("[INFO] Blocking Command:\"%s %s\"", command, strings.Join(args, " ")) - return cmd.Run() - } - - return fn, nil -} diff --git a/config/config.go b/config/config.go deleted file mode 100644 index bfad2ad7..00000000 --- a/config/config.go +++ /dev/null @@ -1,261 +0,0 @@ -package config - -import ( - "fmt" - "io/ioutil" - "net/http" - "regexp" - "strconv" - "strings" - - "golang.org/x/net/webdav" - - "github.com/mholt/caddy" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// Config is a configuration for browsing in a particular path. -type Config struct { - *User - PrefixURL string - BaseURL string - WebDavURL string - HugoEnabled bool // Enables the Hugo plugin for File Manager - Users map[string]*User - BeforeSave CommandFunc - AfterSave CommandFunc -} - -// AbsoluteURL ... -func (c Config) AbsoluteURL() string { - return c.PrefixURL + c.BaseURL -} - -// AbsoluteWebdavURL ... -func (c Config) AbsoluteWebdavURL() string { - return c.PrefixURL + c.WebDavURL -} - -// Rule is a dissalow/allow rule -type Rule struct { - Regex bool - Allow bool - Path string - Regexp *regexp.Regexp -} - -// Parse parses the configuration set by the user so it can -// be used by the middleware -func Parse(c *caddy.Controller) ([]Config, error) { - var ( - configs []Config - err error - user *User - ) - - appendConfig := func(cfg Config) error { - for _, c := range configs { - if c.Scope == cfg.Scope { - return fmt.Errorf("duplicate file managing config for %s", c.Scope) - } - } - configs = append(configs, cfg) - return nil - } - - for c.Next() { - // Initialize the configuration with the default settings - cfg := Config{User: &User{}} - cfg.Scope = "." - cfg.FileSystem = webdav.Dir(cfg.Scope) - cfg.BaseURL = "" - cfg.HugoEnabled = false - cfg.Users = map[string]*User{} - cfg.AllowCommands = true - cfg.AllowEdit = true - cfg.AllowNew = true - cfg.Commands = []string{"git", "svn", "hg"} - cfg.BeforeSave = func(r *http.Request, c *Config, u *User) error { return nil } - cfg.AfterSave = func(r *http.Request, c *Config, u *User) error { return nil } - cfg.Rules = []*Rule{{ - Regex: true, - Allow: false, - Regexp: regexp.MustCompile("\\/\\..+"), - }} - - // Get the baseURL - args := c.RemainingArgs() - - if len(args) > 0 { - cfg.BaseURL = args[0] - } - - cfg.BaseURL = strings.TrimPrefix(cfg.BaseURL, "/") - cfg.BaseURL = strings.TrimSuffix(cfg.BaseURL, "/") - cfg.BaseURL = "/" + cfg.BaseURL - cfg.WebDavURL = "" - - if cfg.BaseURL == "/" { - cfg.BaseURL = "" - } - - // Set the first user, the global user - user = cfg.User - - for c.NextBlock() { - switch c.Val() { - case "before_save": - if cfg.BeforeSave, err = CommandRunner(c); err != nil { - return configs, err - } - case "after_save": - if cfg.AfterSave, err = CommandRunner(c); err != nil { - return configs, err - } - case "webdav": - if !c.NextArg() { - return configs, c.ArgErr() - } - - prefix := c.Val() - prefix = strings.TrimPrefix(prefix, "/") - prefix = strings.TrimSuffix(prefix, "/") - cfg.WebDavURL = prefix - case "show": - if !c.NextArg() { - return configs, c.ArgErr() - } - - user.Scope = c.Val() - user.Scope = strings.TrimSuffix(user.Scope, "/") - user.FileSystem = webdav.Dir(user.Scope) - case "styles": - if !c.NextArg() { - return configs, c.ArgErr() - } - - var tplBytes []byte - tplBytes, err = ioutil.ReadFile(c.Val()) - if err != nil { - return configs, err - } - user.StyleSheet = string(tplBytes) - case "allow_new": - if !c.NextArg() { - return configs, c.ArgErr() - } - - user.AllowNew, err = strconv.ParseBool(c.Val()) - if err != nil { - return configs, err - } - case "allow_edit": - if !c.NextArg() { - return configs, c.ArgErr() - } - - user.AllowEdit, err = strconv.ParseBool(c.Val()) - if err != nil { - return configs, err - } - case "allow_commands": - if !c.NextArg() { - return configs, c.ArgErr() - } - - user.AllowCommands, err = strconv.ParseBool(c.Val()) - if err != nil { - return configs, err - } - case "allow_command": - if !c.NextArg() { - return configs, c.ArgErr() - } - - user.Commands = append(user.Commands, c.Val()) - case "block_command": - if !c.NextArg() { - return configs, c.ArgErr() - } - - index := 0 - - for i, val := range user.Commands { - if val == c.Val() { - index = i - } - } - - user.Commands = append(user.Commands[:index], user.Commands[index+1:]...) - case "allow", "allow_r", "block", "block_r": - ruleType := c.Val() - - if !c.NextArg() { - return configs, c.ArgErr() - } - - if c.Val() == "dotfiles" && !strings.HasSuffix(ruleType, "_r") { - ruleType += "_r" - } - - rule := &Rule{ - Allow: ruleType == "allow" || ruleType == "allow_r", - Regex: ruleType == "allow_r" || ruleType == "block_r", - } - - if rule.Regex && c.Val() == "dotfiles" { - rule.Regexp = regexp.MustCompile("\\/\\..+") - } else if rule.Regex { - rule.Regexp = regexp.MustCompile(c.Val()) - } else { - rule.Path = c.Val() - } - - user.Rules = append(user.Rules, rule) - // NEW USER BLOCK? - default: - val := c.Val() - - // Checks if it's a new user - if !strings.HasSuffix(val, ":") { - fmt.Println("Unknown option " + val) - } - - // Get the username, sets the current user, and initializes it - val = strings.TrimSuffix(val, ":") - cfg.Users[val] = &User{} - - // Initialize the new user - user = cfg.Users[val] - user.AllowCommands = cfg.AllowCommands - user.AllowEdit = cfg.AllowEdit - user.AllowNew = cfg.AllowEdit - user.Commands = cfg.Commands - user.Scope = cfg.Scope - user.FileSystem = cfg.FileSystem - user.Rules = cfg.Rules - user.StyleSheet = cfg.StyleSheet - } - } - - if cfg.WebDavURL == "" { - cfg.WebDavURL = "webdav" - } - - caddyConf := httpserver.GetConfig(c) - - cfg.PrefixURL = strings.TrimSuffix(caddyConf.Addr.Path, "/") - cfg.WebDavURL = cfg.BaseURL + "/" + strings.TrimPrefix(cfg.WebDavURL, "/") - cfg.Handler = &webdav.Handler{ - Prefix: cfg.WebDavURL, - FileSystem: cfg.FileSystem, - LockSystem: webdav.NewMemLS(), - } - - if err := appendConfig(cfg); err != nil { - return configs, err - } - } - - return configs, nil -} diff --git a/config/user.go b/config/user.go deleted file mode 100644 index 4f4f47f6..00000000 --- a/config/user.go +++ /dev/null @@ -1,42 +0,0 @@ -package config - -import ( - "strings" - - "golang.org/x/net/webdav" -) - -// User contains the configuration for each user -type User struct { - Scope string `json:"-"` // Path the user have access - FileSystem webdav.FileSystem `json:"-"` // The virtual file system the user have access - Handler *webdav.Handler `json:"-"` // The WebDav HTTP Handler - StyleSheet string `json:"-"` // Costum stylesheet - AllowNew bool // Can create files and folders - AllowEdit bool // Can edit/rename files - AllowCommands bool // Can execute commands - Commands []string // Available Commands - Rules []*Rule `json:"-"` // Access rules -} - -// Allowed checks if the user has permission to access a directory/file -func (u User) Allowed(url string) bool { - var rule *Rule - i := len(u.Rules) - 1 - - for i >= 0 { - rule = u.Rules[i] - - if rule.Regex { - if rule.Regexp.MatchString(url) { - return rule.Allow - } - } else if strings.HasPrefix(url, rule.Path) { - return rule.Allow - } - - i-- - } - - return true -} diff --git a/file/info.go b/file/info.go deleted file mode 100644 index cd1c6f71..00000000 --- a/file/info.go +++ /dev/null @@ -1,164 +0,0 @@ -package file - -import ( - "io/ioutil" - "mime" - "net/http" - "net/url" - "os" - "path/filepath" - "strings" - "time" - - humanize "github.com/dustin/go-humanize" - "github.com/hacdias/caddy-filemanager/config" - "github.com/hacdias/caddy-filemanager/utils/errors" -) - -// Info contains the information about a particular file or directory -type Info struct { - Name string - Size int64 - URL string - Extension string - ModTime time.Time - Mode os.FileMode - IsDir bool - Path string // Relative path to Caddyfile - VirtualPath string // Relative path to u.FileSystem - Mimetype string - Content []byte - Type string - UserAllowed bool // Indicates if the user has enough permissions -} - -// GetInfo gets the file information and, in case of error, returns the -// respective HTTP error code -func GetInfo(url *url.URL, c *config.Config, u *config.User) (*Info, int, error) { - var err error - - i := &Info{URL: c.PrefixURL + url.Path} - i.VirtualPath = strings.Replace(url.Path, c.BaseURL, "", 1) - i.VirtualPath = strings.TrimPrefix(i.VirtualPath, "/") - i.VirtualPath = "/" + i.VirtualPath - - i.Path = u.Scope + i.VirtualPath - i.Path = filepath.Clean(i.Path) - - info, err := os.Stat(i.Path) - if err != nil { - return i, errors.ErrorToHTTPCode(err, false), err - } - - i.Name = info.Name() - i.ModTime = info.ModTime() - i.Mode = info.Mode() - i.IsDir = info.IsDir() - i.Size = info.Size() - i.Extension = filepath.Ext(i.Name) - return i, 0, nil -} - -var textExtensions = [...]string{ - ".md", ".markdown", ".mdown", ".mmark", - ".asciidoc", ".adoc", ".ad", - ".rst", - ".json", ".toml", ".yaml", ".csv", ".xml", ".rss", ".conf", ".ini", - ".tex", ".sty", - ".css", ".sass", ".scss", - ".js", - ".html", - ".txt", ".rtf", - ".sh", ".bash", ".ps1", ".bat", ".cmd", - ".php", ".pl", ".py", - "Caddyfile", - ".c", ".cc", ".h", ".hh", ".cpp", ".hpp", ".f90", - ".f", ".bas", ".d", ".ada", ".nim", ".cr", ".java", ".cs", ".vala", ".vapi", -} - -// RetrieveFileType obtains the mimetype and a simplified internal Type -// using the first 512 bytes from the file. -func (i *Info) RetrieveFileType() error { - i.Mimetype = mime.TypeByExtension(i.Extension) - - if i.Mimetype == "" { - err := i.Read() - if err != nil { - return err - } - - i.Mimetype = http.DetectContentType(i.Content) - } - - if strings.HasPrefix(i.Mimetype, "video") { - i.Type = "video" - return nil - } - - if strings.HasPrefix(i.Mimetype, "audio") { - i.Type = "audio" - return nil - } - - if strings.HasPrefix(i.Mimetype, "image") { - i.Type = "image" - return nil - } - - if strings.HasPrefix(i.Mimetype, "text") { - i.Type = "text" - return nil - } - - if strings.HasPrefix(i.Mimetype, "application/javascript") { - i.Type = "text" - return nil - } - - // If the type isn't text (and is blob for example), it will check some - // common types that are mistaken not to be text. - for _, extension := range textExtensions { - if strings.HasSuffix(i.Name, extension) { - i.Type = "text" - return nil - } - } - - i.Type = "blob" - return nil -} - -// Reads the file. -func (i *Info) Read() error { - if len(i.Content) != 0 { - return nil - } - - var err error - i.Content, err = ioutil.ReadFile(i.Path) - if err != nil { - return err - } - return nil -} - -// StringifyContent returns the string version of Raw -func (i Info) StringifyContent() string { - return string(i.Content) -} - -// HumanSize returns the size of the file as a human-readable string -// in IEC format (i.e. power of 2 or base 1024). -func (i Info) HumanSize() string { - return humanize.IBytes(uint64(i.Size)) -} - -// HumanModTime returns the modified time of the file as a human-readable string. -func (i Info) HumanModTime(format string) string { - return i.ModTime.Format(format) -} - -// CanBeEdited checks if the extension of a file is supported by the editor -func (i Info) CanBeEdited() bool { - return i.Type == "text" -} diff --git a/file/listing.go b/file/listing.go deleted file mode 100644 index 4b48a0c0..00000000 --- a/file/listing.go +++ /dev/null @@ -1,186 +0,0 @@ -package file - -import ( - "context" - "net/url" - "os" - "path" - "sort" - "strings" - - "github.com/hacdias/caddy-filemanager/config" - - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// A Listing is the context used to fill out a template. -type Listing struct { - // The name of the directory (the last element of the path) - Name string - // The full path of the request relatively to a File System - Path string - // The items (files and folders) in the path - Items []Info - // The number of directories in the listing - NumDirs int - // The number of files (items that aren't directories) in the listing - NumFiles int - // Which sorting order is used - Sort string - // And which order - Order string - // If ≠0 then Items have been limited to that many elements - ItemsLimitedTo int - httpserver.Context `json:"-"` -} - -// GetListing gets the information about a specific directory and its files. -func GetListing(u *config.User, filePath string, baseURL string) (*Listing, error) { - // Gets the directory information using the Virtual File System of - // the user configuration. - file, err := u.FileSystem.OpenFile(context.TODO(), filePath, os.O_RDONLY, 0) - if err != nil { - return nil, err - } - defer file.Close() - - // Reads the directory and gets the information about the files. - files, err := file.Readdir(-1) - if err != nil { - return nil, err - } - - var ( - fileinfos []Info - dirCount, fileCount int - ) - - for _, f := range files { - name := f.Name() - allowed := u.Allowed("/" + name) - - if !allowed { - continue - } - - if f.IsDir() { - name += "/" - dirCount++ - } else { - fileCount++ - } - - // Absolute URL - url := url.URL{Path: baseURL + name} - - i := Info{ - Name: f.Name(), - Size: f.Size(), - ModTime: f.ModTime(), - Mode: f.Mode(), - IsDir: f.IsDir(), - URL: url.String(), - UserAllowed: allowed, - } - i.RetrieveFileType() - - fileinfos = append(fileinfos, i) - } - - return &Listing{ - Name: path.Base(filePath), - Path: filePath, - Items: fileinfos, - NumDirs: dirCount, - NumFiles: fileCount, - }, nil -} - -// ApplySort applies the sort order using .Order and .Sort -func (l Listing) ApplySort() { - // Check '.Order' to know how to sort - if l.Order == "desc" { - switch l.Sort { - case "name": - sort.Sort(sort.Reverse(byName(l))) - case "size": - sort.Sort(sort.Reverse(bySize(l))) - case "time": - sort.Sort(sort.Reverse(byTime(l))) - default: - // If not one of the above, do nothing - return - } - } else { // If we had more Orderings we could add them here - switch l.Sort { - case "name": - sort.Sort(byName(l)) - case "size": - sort.Sort(bySize(l)) - case "time": - sort.Sort(byTime(l)) - default: - sort.Sort(byName(l)) - return - } - } -} - -// Implement sorting for Listing -type byName Listing -type bySize Listing -type byTime Listing - -// By Name -func (l byName) Len() int { - return len(l.Items) -} - -func (l byName) Swap(i, j int) { - l.Items[i], l.Items[j] = l.Items[j], l.Items[i] -} - -// Treat upper and lower case equally -func (l byName) Less(i, j int) bool { - if l.Items[i].IsDir && !l.Items[j].IsDir { - return true - } - - if !l.Items[i].IsDir && l.Items[j].IsDir { - return false - } - - return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name) -} - -// By Size -func (l bySize) Len() int { - return len(l.Items) -} - -func (l bySize) Swap(i, j int) { - l.Items[i], l.Items[j] = l.Items[j], l.Items[i] -} - -const directoryOffset = -1 << 31 // = math.MinInt32 -func (l bySize) Less(i, j int) bool { - iSize, jSize := l.Items[i].Size, l.Items[j].Size - if l.Items[i].IsDir { - iSize = directoryOffset + iSize - } - if l.Items[j].IsDir { - jSize = directoryOffset + jSize - } - return iSize < jSize -} - -// By Time -func (l byTime) Len() int { - return len(l.Items) -} -func (l byTime) Swap(i, j int) { - l.Items[i], l.Items[j] = l.Items[j], l.Items[i] -} -func (l byTime) Less(i, j int) bool { - return l.Items[i].ModTime.Before(l.Items[j].ModTime) -} diff --git a/filemanager.go b/filemanager.go index a17983a8..41d48a9a 100644 --- a/filemanager.go +++ b/filemanager.go @@ -4,18 +4,9 @@ package filemanager import ( - e "errors" "net/http" - "os" - "path/filepath" - "strings" - "github.com/hacdias/caddy-filemanager/assets" - "github.com/hacdias/caddy-filemanager/config" - "github.com/hacdias/caddy-filemanager/file" - "github.com/hacdias/caddy-filemanager/handlers" - "github.com/hacdias/caddy-filemanager/page" - "github.com/hacdias/caddy-filemanager/wrapper" + "github.com/hacdias/filemanager" "github.com/mholt/caddy/caddyhttp/httpserver" ) @@ -23,173 +14,18 @@ import ( // directories in the given paths are specified. type FileManager struct { Next httpserver.Handler - Configs []config.Config + Configs []*filemanager.FileManager } // ServeHTTP determines if the request is for this plugin, and if all prerequisites are met. func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { - var ( - c *config.Config - fi *file.Info - code int - err error - user *config.User - ) - for i := range f.Configs { // Checks if this Path should be handled by File Manager. if !httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) { continue } - c = &f.Configs[i] - - // 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 + assets.BaseURL) { - if r.Method == http.MethodGet { - return assets.Serve(w, r, c) - } - - return http.StatusForbidden, nil - } - - // Obtains the user. See https://github.com/mholt/caddy/blob/master/caddyhttp/basicauth/basicauth.go#L66 - username, _ := r.Context().Value(httpserver.RemoteUserCtxKey).(string) - if _, ok := c.Users[username]; ok { - user = c.Users[username] - } else { - user = c.User - } - - // Checks if the request URL is for the WebDav server - if httpserver.Path(r.URL.Path).Matches(c.WebDavURL) { - // Checks for user permissions relatively to this PATH - if !user.Allowed(strings.TrimPrefix(r.URL.Path, c.WebDavURL)) { - return http.StatusForbidden, nil - } - - switch r.Method { - case "GET", "HEAD": - // Excerpt from RFC4918, section 9.4: - // - // GET, when applied to a collection, may return the contents of an - // "index.html" resource, a human-readable view of the contents of - // the collection, or something else altogether. - // - // It was decided on https://github.com/hacdias/caddy-filemanager/issues/85 - // that GET, for collections, will return the same as PROPFIND method. - path := strings.Replace(r.URL.Path, c.WebDavURL, "", 1) - path = user.Scope + "/" + path - path = filepath.Clean(path) - - var i os.FileInfo - i, err = os.Stat(path) - if err != nil { - // Is there any error? WebDav will handle it... no worries. - break - } - - if i.IsDir() { - r.Method = "PROPFIND" - - if r.Method == "HEAD" { - w = wrapper.NewResponseWriterNoBody(w) - } - } - case "PROPPATCH", "MOVE", "PATCH", "PUT", "DELETE": - if !user.AllowEdit { - return http.StatusForbidden, nil - } - case "MKCOL", "COPY": - if !user.AllowNew { - return http.StatusForbidden, nil - } - } - - // Preprocess the PUT request if it's the case - if r.Method == http.MethodPut { - if err = c.BeforeSave(r, c, user); err != nil { - return http.StatusInternalServerError, err - } - - if handlers.PreProccessPUT(w, r, c, user) != nil { - return http.StatusInternalServerError, err - } - } - - c.Handler.ServeHTTP(w, r) - if err = c.AfterSave(r, c, user); err != nil { - return http.StatusInternalServerError, err - } - - return 0, nil - } - - w.Header().Set("x-frame-options", "SAMEORIGIN") - w.Header().Set("x-content-type", "nosniff") - w.Header().Set("x-xss-protection", "1; mode=block") - - // Checks if the User is allowed to access this file - if !user.Allowed(strings.TrimPrefix(r.URL.Path, c.BaseURL)) { - if r.Method == http.MethodGet { - return page.PrintErrorHTML( - w, http.StatusForbidden, - e.New("You don't have permission to access this page."), - ) - } - - return http.StatusForbidden, nil - } - - if r.URL.Query().Get("search") != "" { - return handlers.Search(w, r, c, user) - } - - if r.URL.Query().Get("command") != "" { - return handlers.Command(w, r, c, user) - } - - if r.Method == http.MethodGet { - // Gets the information of the directory/file - fi, code, err = file.GetInfo(r.URL, c, user) - if err != nil { - if r.Method == http.MethodGet { - return page.PrintErrorHTML(w, code, err) - } - return code, err - } - - // If it's a dir and the path doesn't end with a trailing slash, - // redirect the user. - if fi.IsDir && !strings.HasSuffix(r.URL.Path, "/") { - http.Redirect(w, r, c.PrefixURL+r.URL.Path+"/", http.StatusTemporaryRedirect) - return 0, nil - } - - switch { - case r.URL.Query().Get("download") != "": - code, err = handlers.Download(w, r, c, fi) - case r.URL.Query().Get("raw") == "true" && !fi.IsDir: - http.ServeFile(w, r, fi.Path) - code, err = 0, nil - case !fi.IsDir && r.URL.Query().Get("checksum") != "": - code, err = handlers.Checksum(w, r, c, fi) - case fi.IsDir: - code, err = handlers.ServeListing(w, r, c, user, fi) - default: - code, err = handlers.ServeSingle(w, r, c, user, fi) - } - - if err != nil { - code, err = page.PrintErrorHTML(w, code, err) - } - - return code, err - } - - return http.StatusNotImplemented, nil - + return f.Configs[i].ServeHTTP(w, r) } return f.Next.ServeHTTP(w, r) diff --git a/frontmatter/frontmatter.go b/frontmatter/frontmatter.go deleted file mode 100644 index a0deb86c..00000000 --- a/frontmatter/frontmatter.go +++ /dev/null @@ -1,276 +0,0 @@ -package frontmatter - -import ( - "bytes" - "encoding/json" - "errors" - "log" - "reflect" - "sort" - "strconv" - "strings" - - "gopkg.in/yaml.v2" - - "github.com/BurntSushi/toml" - "github.com/hacdias/caddy-filemanager/utils/variables" - - "github.com/spf13/cast" -) - -const ( - mainName = "#MAIN#" - objectType = "object" - arrayType = "array" -) - -var mainTitle = "" - -// Pretty creates a new FrontMatter object -func Pretty(content []byte) (*Content, string, error) { - data, err := Unmarshal(content) - - if err != nil { - return &Content{}, "", err - } - - kind := reflect.ValueOf(data).Kind() - - if kind == reflect.Invalid { - return &Content{}, "", nil - } - - object := new(Block) - object.Type = objectType - object.Name = mainName - - if kind == reflect.Map { - object.Type = objectType - } else if kind == reflect.Slice || kind == reflect.Array { - object.Type = arrayType - } - - return rawToPretty(data, object), mainTitle, nil -} - -// Unmarshal returns the data of the frontmatter -func Unmarshal(content []byte) (interface{}, error) { - mark := rune(content[0]) - var data interface{} - - switch mark { - case '-': - // If it's YAML - if err := yaml.Unmarshal(content, &data); err != nil { - return nil, err - } - case '+': - // If it's TOML - content = bytes.Replace(content, []byte("+"), []byte(""), -1) - if _, err := toml.Decode(string(content), &data); err != nil { - return nil, err - } - case '{', '[': - // If it's JSON - if err := json.Unmarshal(content, &data); err != nil { - return nil, err - } - default: - return nil, errors.New("Invalid frontmatter type") - } - - return data, nil -} - -// Marshal encodes the interface in a specific format -func Marshal(data interface{}, mark rune) ([]byte, error) { - b := new(bytes.Buffer) - - switch mark { - case '+': - enc := toml.NewEncoder(b) - err := enc.Encode(data) - if err != nil { - return nil, err - } - return b.Bytes(), nil - case '{': - by, err := json.MarshalIndent(data, "", " ") - if err != nil { - return nil, err - } - b.Write(by) - _, err = b.Write([]byte("\n")) - if err != nil { - return nil, err - } - return b.Bytes(), nil - case '-': - by, err := yaml.Marshal(data) - if err != nil { - return nil, err - } - b.Write(by) - _, err = b.Write([]byte("...")) - if err != nil { - return nil, err - } - return b.Bytes(), nil - default: - return nil, errors.New("Unsupported Format provided") - } -} - -// Content is the block content -type Content struct { - Other interface{} - Fields []*Block - Arrays []*Block - Objects []*Block -} - -// Block is a block -type Block struct { - Name string - Title string - Type string - HTMLType string - Content *Content - Parent *Block -} - -func rawToPretty(config interface{}, parent *Block) *Content { - objects := []*Block{} - arrays := []*Block{} - fields := []*Block{} - - cnf := map[string]interface{}{} - kind := reflect.TypeOf(config) - - switch kind { - case reflect.TypeOf(map[interface{}]interface{}{}): - for key, value := range config.(map[interface{}]interface{}) { - cnf[key.(string)] = value - } - case reflect.TypeOf([]map[string]interface{}{}): - for index, value := range config.([]map[string]interface{}) { - cnf[strconv.Itoa(index)] = value - } - case reflect.TypeOf([]map[interface{}]interface{}{}): - for index, value := range config.([]map[interface{}]interface{}) { - cnf[strconv.Itoa(index)] = value - } - case reflect.TypeOf([]interface{}{}): - for index, value := range config.([]interface{}) { - cnf[strconv.Itoa(index)] = value - } - default: - cnf = config.(map[string]interface{}) - } - - for name, element := range cnf { - if variables.IsMap(element) { - objects = append(objects, handleObjects(element, parent, name)) - } else if variables.IsSlice(element) { - arrays = append(arrays, handleArrays(element, parent, name)) - } else { - if name == "title" && parent.Name == mainName { - mainTitle = element.(string) - } - fields = append(fields, handleFlatValues(element, parent, name)) - } - } - - sort.Sort(sortByTitle(fields)) - sort.Sort(sortByTitle(arrays)) - sort.Sort(sortByTitle(objects)) - return &Content{ - Fields: fields, - Arrays: arrays, - Objects: objects, - } -} - -type sortByTitle []*Block - -func (f sortByTitle) Len() int { return len(f) } -func (f sortByTitle) Swap(i, j int) { f[i], f[j] = f[j], f[i] } -func (f sortByTitle) Less(i, j int) bool { - return strings.ToLower(f[i].Name) < strings.ToLower(f[j].Name) -} - -func handleObjects(content interface{}, parent *Block, name string) *Block { - c := new(Block) - c.Parent = parent - c.Type = objectType - c.Title = name - - if parent.Name == mainName { - c.Name = c.Title - } else if parent.Type == arrayType { - c.Name = parent.Name + "[" + name + "]" - } else { - c.Name = parent.Name + "." + c.Title - } - - c.Content = rawToPretty(content, c) - return c -} - -func handleArrays(content interface{}, parent *Block, name string) *Block { - c := new(Block) - c.Parent = parent - c.Type = arrayType - c.Title = name - - if parent.Name == mainName { - c.Name = name - } else { - c.Name = parent.Name + "." + name - } - - c.Content = rawToPretty(content, c) - return c -} - -func handleFlatValues(content interface{}, parent *Block, name string) *Block { - c := new(Block) - c.Parent = parent - - switch content.(type) { - case bool: - c.Type = "boolean" - case int, float32, float64: - c.Type = "number" - default: - c.Type = "string" - } - - c.Content = &Content{Other: content} - - switch strings.ToLower(name) { - case "description": - c.HTMLType = "textarea" - case "date", "publishdate": - c.HTMLType = "datetime" - c.Content = &Content{Other: cast.ToTime(content)} - default: - c.HTMLType = "text" - } - - if parent.Type == arrayType { - c.Name = parent.Name + "[]" - c.Title = content.(string) - } else if parent.Type == objectType { - c.Title = name - c.Name = parent.Name + "." + name - - if parent.Name == mainName { - c.Name = name - } - } else { - log.Panic("Parent type not allowed in handleFlatValues.") - } - - return c -} diff --git a/frontmatter/runes.go b/frontmatter/runes.go deleted file mode 100644 index b4ad1dc2..00000000 --- a/frontmatter/runes.go +++ /dev/null @@ -1,58 +0,0 @@ -package frontmatter - -import ( - "bytes" - "errors" - "strings" -) - -// HasRune checks if the file has the frontmatter rune -func HasRune(file []byte) bool { - return strings.HasPrefix(string(file), "---") || - strings.HasPrefix(string(file), "+++") || - strings.HasPrefix(string(file), "{") -} - -// AppendRune appends the frontmatter rune to a file -func AppendRune(frontmatter []byte, mark rune) []byte { - frontmatter = bytes.TrimSpace(frontmatter) - - switch mark { - case '-': - return []byte("---\n" + string(frontmatter) + "\n---") - case '+': - return []byte("+++\n" + string(frontmatter) + "\n+++") - case '{': - return []byte("{\n" + string(frontmatter) + "\n}") - } - - return frontmatter -} - -// RuneToStringFormat converts the rune to a string with the format -func RuneToStringFormat(mark rune) (string, error) { - switch mark { - case '-': - return "yaml", nil - case '+': - return "toml", nil - case '{', '}': - return "json", nil - default: - return "", errors.New("Unsupported format type") - } -} - -// StringFormatToRune converts the format name to its rune -func StringFormatToRune(format string) (rune, error) { - switch format { - case "yaml": - return '-', nil - case "toml": - return '+', nil - case "json": - return '{', nil - default: - return '0', errors.New("Unsupported format type") - } -} diff --git a/frontmatter/runes_test.go b/frontmatter/runes_test.go deleted file mode 100644 index 6d120948..00000000 --- a/frontmatter/runes_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package frontmatter - -import "testing" - -type hasRuneTest struct { - File []byte - Return bool -} - -var testHasRune = []hasRuneTest{ - hasRuneTest{ - File: []byte(`--- -Lorem ipsum dolor sit amet, consectetur adipiscing elit. -Sed auctor libero eget ante fermentum commodo. ----`), - Return: true, - }, - hasRuneTest{ - File: []byte(`+++ -Lorem ipsum dolor sit amet, consectetur adipiscing elit. -Sed auctor libero eget ante fermentum commodo. -+++`), - Return: true, - }, - hasRuneTest{ - File: []byte(`{ - "json": "Lorem ipsum dolor sit amet" -}`), - Return: true, - }, - hasRuneTest{ - File: []byte(`+`), - Return: false, - }, - hasRuneTest{ - File: []byte(`++`), - Return: false, - }, - hasRuneTest{ - File: []byte(`-`), - Return: false, - }, - hasRuneTest{ - File: []byte(`--`), - Return: false, - }, - hasRuneTest{ - File: []byte(`Lorem ipsum`), - Return: false, - }, -} - -func TestHasRune(t *testing.T) { - for _, test := range testHasRune { - if HasRune(test.File) != test.Return { - t.Error("Incorrect value on HasRune") - } - } -} - -type appendRuneTest struct { - Before []byte - After []byte - Mark rune -} - -var testAppendRuneTest = []appendRuneTest{} - -func TestAppendRune(t *testing.T) { - for i, test := range testAppendRuneTest { - if !compareByte(AppendRune(test.Before, test.Mark), test.After) { - t.Errorf("Incorrect value on AppendRune of Test %d", i) - } - } -} - -func compareByte(a, b []byte) bool { - if a == nil && b == nil { - return true - } - - if a == nil || b == nil { - return false - } - - if len(a) != len(b) { - return false - } - - for i := range a { - if a[i] != b[i] { - return false - } - } - - return true -} - -var testRuneToStringFormat = map[rune]string{ - '-': "yaml", - '+': "toml", - '{': "json", - '}': "json", - '1': "", - 'a': "", -} - -func TestRuneToStringFormat(t *testing.T) { - for mark, format := range testRuneToStringFormat { - val, _ := RuneToStringFormat(mark) - if val != format { - t.Errorf("Incorrect value on RuneToStringFormat of %v; want: %s; got: %s", mark, format, val) - } - } -} - -var testStringFormatToRune = map[string]rune{ - "yaml": '-', - "toml": '+', - "json": '{', - "lorem": '0', -} - -func TestStringFormatToRune(t *testing.T) { - for format, mark := range testStringFormatToRune { - val, _ := StringFormatToRune(format) - if val != mark { - t.Errorf("Incorrect value on StringFormatToRune of %s; want: %v; got: %v", format, mark, val) - } - } -} diff --git a/handlers/checksum.go b/handlers/checksum.go deleted file mode 100644 index cf5c2a64..00000000 --- a/handlers/checksum.go +++ /dev/null @@ -1,54 +0,0 @@ -package handlers - -import ( - "crypto/md5" - "crypto/sha1" - "crypto/sha256" - "crypto/sha512" - "encoding/hex" - e "errors" - "hash" - "io" - "net/http" - "os" - - "github.com/hacdias/caddy-filemanager/config" - "github.com/hacdias/caddy-filemanager/file" - "github.com/hacdias/caddy-filemanager/utils/errors" -) - -// Checksum calculates the hash of a file. Supports MD5, SHA1, SHA256 and SHA512. -func Checksum(w http.ResponseWriter, r *http.Request, c *config.Config, i *file.Info) (int, error) { - query := r.URL.Query().Get("checksum") - - file, err := os.Open(i.Path) - if err != nil { - return errors.ErrorToHTTPCode(err, true), err - } - - defer file.Close() - - var h hash.Hash - - switch query { - case "md5": - h = md5.New() - case "sha1": - h = sha1.New() - case "sha256": - h = sha256.New() - case "sha512": - h = sha512.New() - default: - return http.StatusBadRequest, e.New("Unknown HASH type") - } - - _, err = io.Copy(h, file) - if err != nil { - return http.StatusInternalServerError, err - } - - val := hex.EncodeToString(h.Sum(nil)) - w.Write([]byte(val)) - return http.StatusOK, nil -} diff --git a/handlers/command.go b/handlers/command.go deleted file mode 100644 index 0b2ee12e..00000000 --- a/handlers/command.go +++ /dev/null @@ -1,136 +0,0 @@ -package handlers - -import ( - "bytes" - "net/http" - "os/exec" - "path/filepath" - "strings" - "time" - - "github.com/gorilla/websocket" - "github.com/hacdias/caddy-filemanager/config" -) - -var upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, -} - -var ( - cmdNotImplemented = []byte("Command not implemented.") - cmdNotAllowed = []byte("Command not allowed.") -) - -// Command handles the requests for VCS related commands: git, svn and mercurial -func Command(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) { - // Upgrades the connection to a websocket and checks for errors. - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - return 0, err - } - defer conn.Close() - - var ( - message []byte - command []string - ) - - // Starts an infinite loop until a valid command is captured. - for { - _, message, err = conn.ReadMessage() - if err != nil { - return http.StatusInternalServerError, err - } - - command = strings.Split(string(message), " ") - if len(command) != 0 { - break - } - } - - // Check if the command is allowed - allowed := false - - for _, cmd := range u.Commands { - if cmd == command[0] { - allowed = true - } - } - - if !allowed { - err = conn.WriteMessage(websocket.BinaryMessage, cmdNotAllowed) - if err != nil { - return http.StatusInternalServerError, err - } - - return 0, nil - } - - // Check if the program is talled is installed on the computer. - if _, err = exec.LookPath(command[0]); err != nil { - err = conn.WriteMessage(websocket.BinaryMessage, cmdNotImplemented) - if err != nil { - return http.StatusInternalServerError, err - } - - return http.StatusNotImplemented, nil - } - - // Gets the path and initializes a buffer. - path := strings.Replace(r.URL.Path, c.BaseURL, c.Scope, 1) - path = filepath.Clean(path) - buff := new(bytes.Buffer) - - // Sets up the command executation. - cmd := exec.Command(command[0], command[1:]...) - cmd.Dir = path - cmd.Stderr = buff - cmd.Stdout = buff - - // Starts the command and checks for errors. - err = cmd.Start() - if err != nil { - return http.StatusInternalServerError, err - } - - // Set a 'done' variable to check whetever the command has already finished - // running or not. This verification is done using a goroutine that uses the - // method .Wait() from the command. - done := false - go func() { - err = cmd.Wait() - done = true - }() - - // Function to print the current information on the buffer to the connection. - print := func() error { - by := buff.Bytes() - if len(by) > 0 { - err = conn.WriteMessage(websocket.TextMessage, by) - if err != nil { - return err - } - } - - return nil - } - - // While the command hasn't finished running, continue sending the output - // to the client in intervals of 100 milliseconds. - for !done { - if err = print(); err != nil { - return http.StatusInternalServerError, err - } - - time.Sleep(100 * time.Millisecond) - } - - // After the command is done executing, send the output one more time to the - // browser to make sure it gets the latest information. - if err = print(); err != nil { - return http.StatusInternalServerError, err - } - - return 0, nil -} diff --git a/handlers/download.go b/handlers/download.go deleted file mode 100644 index 1f768630..00000000 --- a/handlers/download.go +++ /dev/null @@ -1,97 +0,0 @@ -package handlers - -import ( - "io" - "io/ioutil" - "net/http" - "net/url" - "os" - "path/filepath" - "strings" - - "github.com/hacdias/caddy-filemanager/config" - "github.com/hacdias/caddy-filemanager/file" - "github.com/mholt/archiver" -) - -// Download creates an archive in one of the supported formats (zip, tar, -// tar.gz or tar.bz2) and sends it to be downloaded. -func Download(w http.ResponseWriter, r *http.Request, c *config.Config, i *file.Info) (int, error) { - query := r.URL.Query().Get("download") - - if !i.IsDir { - w.Header().Set("Content-Disposition", "attachment; filename="+i.Name) - http.ServeFile(w, r, i.Path) - return 0, nil - } - - files := []string{} - names := strings.Split(r.URL.Query().Get("files"), ",") - - if len(names) != 0 { - for _, name := range names { - name, err := url.QueryUnescape(name) - - if err != nil { - return http.StatusInternalServerError, err - } - - files = append(files, filepath.Join(i.Path, name)) - } - - } else { - files = append(files, i.Path) - } - - if query == "true" { - query = "zip" - } - - var ( - extension string - temp string - err error - tempfile string - ) - - temp, err = ioutil.TempDir("", "") - if err != nil { - return http.StatusInternalServerError, err - } - - defer os.RemoveAll(temp) - tempfile = filepath.Join(temp, "temp") - - switch query { - case "zip": - extension, err = ".zip", archiver.Zip.Make(tempfile, files) - case "tar": - extension, err = ".tar", archiver.Tar.Make(tempfile, files) - case "targz": - extension, err = ".tar.gz", archiver.TarGz.Make(tempfile, files) - case "tarbz2": - extension, err = ".tar.bz2", archiver.TarBz2.Make(tempfile, files) - case "tarxz": - extension, err = ".tar.xz", archiver.TarXZ.Make(tempfile, files) - default: - return http.StatusNotImplemented, nil - } - - if err != nil { - return http.StatusInternalServerError, err - } - - file, err := os.Open(temp + "/temp") - if err != nil { - return http.StatusInternalServerError, err - } - - name := i.Name - if name == "." || name == "" { - name = "download" - } - - w.Header().Set("Content-Disposition", "attachment; filename="+name+extension) - io.Copy(w, file) - return http.StatusOK, nil -} diff --git a/handlers/editor.go b/handlers/editor.go deleted file mode 100644 index 8440734b..00000000 --- a/handlers/editor.go +++ /dev/null @@ -1,121 +0,0 @@ -package handlers - -import ( - "bytes" - "errors" - "net/http" - "path/filepath" - "strings" - - "github.com/hacdias/caddy-filemanager/file" - "github.com/hacdias/caddy-filemanager/frontmatter" - "github.com/spf13/hugo/parser" -) - -// Editor contains the information for the editor page -type Editor struct { - Class string - Mode string - Visual bool - Content string - FrontMatter struct { - Content *frontmatter.Content - Rune rune - } -} - -// GetEditor gets the editor based on a FileInfo struct -func GetEditor(r *http.Request, i *file.Info) (*Editor, error) { - var err error - - // Create a new editor variable and set the mode - e := new(Editor) - e.Mode = editorMode(i.Name) - e.Class = editorClass(e.Mode) - - if e.Class == "frontmatter-only" || e.Class == "complete" { - e.Visual = true - } - - if r.URL.Query().Get("visual") == "false" { - e.Class = "content-only" - } - - hasRune := frontmatter.HasRune(i.Content) - - if e.Class == "frontmatter-only" && !hasRune { - e.FrontMatter.Rune, err = frontmatter.StringFormatToRune(e.Mode) - if err != nil { - goto Error - } - i.Content = frontmatter.AppendRune(i.Content, e.FrontMatter.Rune) - hasRune = true - } - - if e.Class == "frontmatter-only" && hasRune { - e.FrontMatter.Content, _, err = frontmatter.Pretty(i.Content) - if err != nil { - goto Error - } - } - - if e.Class == "complete" && hasRune { - var page parser.Page - // Starts a new buffer and parses the file using Hugo's functions - buffer := bytes.NewBuffer(i.Content) - page, err = parser.ReadFrom(buffer) - - if err != nil { - goto Error - } - - // Parses the page content and the frontmatter - e.Content = strings.TrimSpace(string(page.Content())) - e.FrontMatter.Rune = rune(i.Content[0]) - e.FrontMatter.Content, _, err = frontmatter.Pretty(page.FrontMatter()) - } - - if e.Class == "complete" && !hasRune { - err = errors.New("Complete but without rune") - } - -Error: - if e.Class == "content-only" || err != nil { - e.Class = "content-only" - e.Content = i.StringifyContent() - } - - return e, nil -} - -func editorClass(mode string) string { - switch mode { - case "json", "toml", "yaml": - return "frontmatter-only" - case "markdown", "asciidoc", "rst": - return "complete" - } - - return "content-only" -} - -func editorMode(filename string) string { - mode := strings.TrimPrefix(filepath.Ext(filename), ".") - - switch mode { - case "md", "markdown", "mdown", "mmark": - mode = "markdown" - case "asciidoc", "adoc", "ad": - mode = "asciidoc" - case "rst": - mode = "rst" - case "html", "htm": - mode = "html" - case "js": - mode = "javascript" - case "go": - mode = "golang" - } - - return mode -} diff --git a/handlers/listing.go b/handlers/listing.go deleted file mode 100644 index c0d7d148..00000000 --- a/handlers/listing.go +++ /dev/null @@ -1,148 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - "strconv" - "strings" - - "github.com/hacdias/caddy-filemanager/config" - "github.com/hacdias/caddy-filemanager/file" - "github.com/hacdias/caddy-filemanager/page" - "github.com/hacdias/caddy-filemanager/utils/errors" - "github.com/mholt/caddy/caddyhttp/httpserver" -) - -// ServeListing presents the user with a listage of a directory folder. -func ServeListing(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User, i *file.Info) (int, error) { - var err error - - // Loads the content of the directory - listing, err := file.GetListing(u, i.VirtualPath, c.PrefixURL+r.URL.Path) - if err != nil { - return errors.ErrorToHTTPCode(err, true), err - } - - listing.Context = httpserver.Context{ - Root: http.Dir(u.Scope), - Req: r, - URL: r.URL, - } - - cookieScope := c.BaseURL - if cookieScope == "" { - cookieScope = "/" - } - - // Copy the query values into the Listing struct - var limit int - listing.Sort, listing.Order, limit, err = handleSortOrder(w, r, cookieScope) - if err != nil { - return http.StatusBadRequest, err - } - - listing.ApplySort() - - if limit > 0 && limit <= len(listing.Items) { - listing.Items = listing.Items[:limit] - listing.ItemsLimitedTo = limit - } - - if strings.Contains(r.Header.Get("Accept"), "application/json") { - marsh, err := json.Marshal(listing.Items) - if err != nil { - return http.StatusInternalServerError, err - } - - w.Header().Set("Content-Type", "application/json; charset=utf-8") - if _, err := w.Write(marsh); err != nil { - return http.StatusInternalServerError, err - } - - return http.StatusOK, nil - } - - displayMode := r.URL.Query().Get("display") - - if displayMode == "" { - if displayCookie, err := r.Cookie("display"); err == nil { - displayMode = displayCookie.Value - } - } - - if displayMode == "" || (displayMode != "mosaic" && displayMode != "list") { - displayMode = "mosaic" - } - - http.SetCookie(w, &http.Cookie{ - Name: "display", - Value: displayMode, - Path: cookieScope, - Secure: r.TLS != nil, - }) - - page := &page.Page{ - Minimal: r.Header.Get("Minimal") == "true", - Info: &page.Info{ - Name: listing.Name, - Path: i.VirtualPath, - IsDir: true, - User: u, - Config: c, - Display: displayMode, - Data: listing, - }, - } - - return page.PrintAsHTML(w, "listing") -} - -// handleSortOrder gets and stores for a Listing the 'sort' and 'order', -// and reads 'limit' if given. The latter is 0 if not given. Sets cookies. -func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, limit int, err error) { - sort = r.URL.Query().Get("sort") - order = r.URL.Query().Get("order") - limitQuery := r.URL.Query().Get("limit") - - // If the query 'sort' or 'order' is empty, use defaults or any values - // previously saved in Cookies. - switch sort { - case "": - sort = "name" - if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil { - sort = sortCookie.Value - } - case "name", "size", "type": - http.SetCookie(w, &http.Cookie{ - Name: "sort", - Value: sort, - Path: scope, - Secure: r.TLS != nil, - }) - } - - switch order { - case "": - order = "asc" - if orderCookie, orderErr := r.Cookie("order"); orderErr == nil { - order = orderCookie.Value - } - case "asc", "desc": - http.SetCookie(w, &http.Cookie{ - Name: "order", - Value: order, - Path: scope, - Secure: r.TLS != nil, - }) - } - - if limitQuery != "" { - limit, err = strconv.Atoi(limitQuery) - // If the 'limit' query can't be interpreted as a number, return err. - if err != nil { - return - } - } - - return -} diff --git a/handlers/put.go b/handlers/put.go deleted file mode 100644 index 4b98e59b..00000000 --- a/handlers/put.go +++ /dev/null @@ -1,144 +0,0 @@ -package handlers - -import ( - "bytes" - "encoding/json" - "errors" - "io/ioutil" - "net/http" - "path/filepath" - "strconv" - "strings" - - "github.com/hacdias/caddy-filemanager/config" - "github.com/hacdias/caddy-filemanager/frontmatter" -) - -// PreProccessPUT is used to update a file that was edited -func PreProccessPUT( - w http.ResponseWriter, - r *http.Request, - c *config.Config, - u *config.User, -) (err error) { - var ( - data = map[string]interface{}{} - file []byte - kind string - rawBuffer = new(bytes.Buffer) - ) - - kind = r.Header.Get("kind") - rawBuffer.ReadFrom(r.Body) - - if kind != "" { - err = json.Unmarshal(rawBuffer.Bytes(), &data) - - if err != nil { - return - } - } - - switch kind { - case "frontmatter-only": - if file, err = ParseFrontMatterOnlyFile(data, r.URL.Path); err != nil { - return - } - case "content-only": - mainContent := data["content"].(string) - mainContent = strings.TrimSpace(mainContent) - file = []byte(mainContent) - case "complete": - var mark rune - - if v := r.Header.Get("Rune"); v != "" { - var n int - n, err = strconv.Atoi(v) - if err != nil { - return err - } - - mark = rune(n) - } - - if file, err = ParseCompleteFile(data, r.URL.Path, mark); err != nil { - return - } - default: - file = rawBuffer.Bytes() - } - - // Overwrite the request Body - r.Body = ioutil.NopCloser(bytes.NewReader(file)) - return -} - -// ParseFrontMatterOnlyFile parses a frontmatter only file -func ParseFrontMatterOnlyFile(data interface{}, filename string) ([]byte, error) { - frontmatter := strings.TrimPrefix(filepath.Ext(filename), ".") - f, err := ParseFrontMatter(data, frontmatter) - fString := string(f) - - // If it's toml or yaml, strip frontmatter identifier - if frontmatter == "toml" { - fString = strings.TrimSuffix(fString, "+++\n") - fString = strings.TrimPrefix(fString, "+++\n") - } - - if frontmatter == "yaml" { - fString = strings.TrimSuffix(fString, "---\n") - fString = strings.TrimPrefix(fString, "---\n") - } - - f = []byte(fString) - return f, err -} - -// ParseFrontMatter is the frontmatter parser -func ParseFrontMatter(data interface{}, front string) ([]byte, error) { - var mark rune - - switch front { - case "toml": - mark = '+' - case "json": - mark = '{' - case "yaml": - mark = '-' - default: - return nil, errors.New("Unsupported Format provided") - } - - return frontmatter.Marshal(data, mark) -} - -// ParseCompleteFile parses a complete file -func ParseCompleteFile(data map[string]interface{}, filename string, mark rune) ([]byte, error) { - mainContent := "" - - if _, ok := data["content"]; ok { - // The main content of the file - mainContent = data["content"].(string) - mainContent = "\n\n" + strings.TrimSpace(mainContent) + "\n" - - // Removes the main content from the rest of the frontmatter - delete(data, "content") - } - - if _, ok := data["date"]; ok { - data["date"] = data["date"].(string) + ":00" - } - - front, err := frontmatter.Marshal(data, mark) - if err != nil { - return []byte{}, err - } - - front = frontmatter.AppendRune(front, mark) - - // Generates the final file - f := new(bytes.Buffer) - f.Write(front) - f.Write([]byte(mainContent)) - return f.Bytes(), nil -} diff --git a/handlers/search.go b/handlers/search.go deleted file mode 100644 index 8a1265d3..00000000 --- a/handlers/search.go +++ /dev/null @@ -1,118 +0,0 @@ -package handlers - -import ( - "net/http" - "os" - "path/filepath" - "strings" - - "github.com/gorilla/websocket" - "github.com/hacdias/caddy-filemanager/config" -) - -type searchOptions struct { - CaseInsensitive bool - Terms []string -} - -func parseSearch(value string) *searchOptions { - opts := &searchOptions{ - CaseInsensitive: strings.Contains(value, "case:insensitive"), - } - - // removes the options from the value - value = strings.Replace(value, "case:insensitive", "", -1) - value = strings.Replace(value, "case:sensitive", "", -1) - value = strings.TrimSpace(value) - - if opts.CaseInsensitive { - value = strings.ToLower(value) - } - - // if the value starts with " and finishes what that character, we will - // only search for that term - if value[0] == '"' && value[len(value)-1] == '"' { - unique := strings.TrimPrefix(value, "\"") - unique = strings.TrimSuffix(unique, "\"") - - opts.Terms = []string{unique} - return opts - } - - opts.Terms = strings.Split(value, " ") - return opts -} - -// Search ... -func Search(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User) (int, error) { - // Upgrades the connection to a websocket and checks for errors. - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - return 0, err - } - defer conn.Close() - - var ( - value string - search *searchOptions - message []byte - ) - - // Starts an infinite loop until a valid command is captured. - for { - _, message, err = conn.ReadMessage() - if err != nil { - return http.StatusInternalServerError, err - } - - if len(message) != 0 { - value = string(message) - break - } - } - - search = parseSearch(value) - scope := strings.Replace(r.URL.Path, c.BaseURL, "", 1) - scope = strings.TrimPrefix(scope, "/") - scope = "/" + scope - scope = u.Scope + scope - scope = strings.Replace(scope, "\\", "/", -1) - scope = filepath.Clean(scope) - - err = filepath.Walk(scope, func(path string, f os.FileInfo, err error) error { - if search.CaseInsensitive { - path = strings.ToLower(path) - } - - path = strings.Replace(path, "\\", "/", -1) - is := false - - for _, term := range search.Terms { - if is { - break - } - - if strings.Contains(path, term) { - if !u.Allowed(path) { - return nil - } - - is = true - } - } - - if !is { - return nil - } - - path = strings.TrimPrefix(path, scope) - path = strings.TrimPrefix(path, "/") - return conn.WriteMessage(websocket.TextMessage, []byte(path)) - }) - - if err != nil { - return http.StatusInternalServerError, err - } - - return http.StatusOK, nil -} diff --git a/handlers/single.go b/handlers/single.go deleted file mode 100644 index 2d804247..00000000 --- a/handlers/single.go +++ /dev/null @@ -1,55 +0,0 @@ -package handlers - -import ( - "net/http" - "strings" - - "github.com/hacdias/caddy-filemanager/config" - "github.com/hacdias/caddy-filemanager/file" - "github.com/hacdias/caddy-filemanager/page" - "github.com/hacdias/caddy-filemanager/utils/errors" -) - -// ServeSingle serves a single file in an editor (if it is editable), shows the -// plain file, or downloads it if it can't be shown. -func ServeSingle(w http.ResponseWriter, r *http.Request, c *config.Config, u *config.User, i *file.Info) (int, error) { - var err error - - if err = i.RetrieveFileType(); err != nil { - return errors.ErrorToHTTPCode(err, true), err - } - - p := &page.Page{ - Info: &page.Info{ - Name: i.Name, - Path: i.VirtualPath, - IsDir: false, - Data: i, - User: u, - Config: c, - }, - } - - // If the request accepts JSON, we send the file information. - if strings.Contains(r.Header.Get("Accept"), "application/json") { - return p.PrintAsJSON(w) - } - - if i.Type == "text" { - if err = i.Read(); err != nil { - return errors.ErrorToHTTPCode(err, true), err - } - } - - if i.CanBeEdited() && u.AllowEdit { - p.Data, err = GetEditor(r, i) - p.Editor = true - if err != nil { - return http.StatusInternalServerError, err - } - - return p.PrintAsHTML(w, "frontmatter", "editor") - } - - return p.PrintAsHTML(w, "single") -} diff --git a/page/error.go b/page/error.go deleted file mode 100644 index cdd470f7..00000000 --- a/page/error.go +++ /dev/null @@ -1,65 +0,0 @@ -package page - -import ( - "net/http" - "strconv" - "strings" -) - -const errTemplate = ` - - - TITLE - - - - - - -
    -

    TITLE

    - -

    Try reloading the page or hitting the back button. If this error persists, it seems that you may have found a bug! Please create an issue at hacdias/caddy-filemanager repository on GitHub with the code below.

    - - CODE -
    -` - -// PrintErrorHTML prints the error page -func PrintErrorHTML(w http.ResponseWriter, code int, err error) (int, error) { - tpl := errTemplate - tpl = strings.Replace(tpl, "TITLE", strconv.Itoa(code)+" "+http.StatusText(code), -1) - tpl = strings.Replace(tpl, "CODE", err.Error(), -1) - - _, err = w.Write([]byte(tpl)) - - if err != nil { - return http.StatusInternalServerError, err - } - return http.StatusOK, nil -} diff --git a/page/page.go b/page/page.go deleted file mode 100644 index fd53f207..00000000 --- a/page/page.go +++ /dev/null @@ -1,171 +0,0 @@ -// Package page is used to render the HTML to the end user -package page - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "html/template" - "log" - "net/http" - "strings" - - "github.com/hacdias/caddy-filemanager/assets" - "github.com/hacdias/caddy-filemanager/config" - "github.com/hacdias/caddy-filemanager/utils/variables" -) - -// Page contains the informations and functions needed to show the Page -type Page struct { - *Info - Minimal bool -} - -// Info contains the information of a Page -type Info struct { - Name string - Path string - IsDir bool - User *config.User - Config *config.Config - Data interface{} - Editor bool - Display string - Token string -} - -// BreadcrumbMapItem ... -type BreadcrumbMapItem struct { - Name string - URL string -} - -// BreadcrumbMap returns p.Path where every element is a map -// of URLs and path segment names. -func (i Info) BreadcrumbMap() []BreadcrumbMapItem { - result := []BreadcrumbMapItem{} - - if len(i.Path) == 0 { - return result - } - - // skip trailing slash - lpath := i.Path - if lpath[len(lpath)-1] == '/' { - lpath = lpath[:len(lpath)-1] - } - - parts := strings.Split(lpath, "/") - for i, part := range parts { - if i == len(parts)-1 { - continue - } - - if i == 0 && part == "" { - result = append([]BreadcrumbMapItem{{ - Name: "/", - URL: "/", - }}, result...) - continue - } - - result = append([]BreadcrumbMapItem{{ - Name: part, - URL: strings.Join(parts[:i+1], "/") + "/", - }}, result...) - } - - return result -} - -// PreviousLink returns the path of the previous folder -func (i Info) PreviousLink() string { - path := strings.TrimSuffix(i.Path, "/") - path = strings.TrimPrefix(path, "/") - path = i.Config.AbsoluteURL() + "/" + path - path = path[0 : len(path)-len(i.Name)] - - if len(path) < len(i.Config.AbsoluteURL()+"/") { - return "" - } - - return path -} - -// PrintAsHTML formats the page in HTML and executes the template -func (p Page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, error) { - // Create the functions map, then the template, check for erros and - // execute the template if there aren't errors - functions := template.FuncMap{ - "Defined": variables.Defined, - "CSS": func(s string) template.CSS { - return template.CSS(s) - }, - "Marshal": func(v interface{}) template.JS { - a, _ := json.Marshal(v) - return template.JS(a) - }, - "EncodeBase64": func(s string) string { - return base64.StdEncoding.EncodeToString([]byte(s)) - }, - } - - if p.Minimal { - templates = append(templates, "minimal") - } else { - templates = append(templates, "base") - } - - var tpl *template.Template - - // For each template, add it to the the tpl variable - for i, t := range templates { - // Get the template from the assets - Page, err := assets.Asset("templates/" + t + ".tmpl") - - // Check if there is some error. If so, the template doesn't exist - if err != nil { - log.Print(err) - return http.StatusInternalServerError, err - } - - // If it's the first iteration, creates a new template and add the - // functions map - if i == 0 { - tpl, err = template.New(t).Funcs(functions).Parse(string(Page)) - } else { - tpl, err = tpl.Parse(string(Page)) - } - - if err != nil { - log.Print(err) - return http.StatusInternalServerError, err - } - } - - buf := &bytes.Buffer{} - err := tpl.Execute(buf, p.Info) - - if err != nil { - return http.StatusInternalServerError, err - } - - w.Header().Set("Content-Type", "text/html; charset=utf-8") - _, err = buf.WriteTo(w) - return http.StatusOK, err -} - -// PrintAsJSON prints the current Page information in JSON -func (p Page) PrintAsJSON(w http.ResponseWriter) (int, error) { - marsh, err := json.MarshalIndent(p.Info.Data, "", " ") - if err != nil { - return http.StatusInternalServerError, err - } - - w.Header().Set("Content-Type", "application/json; charset=utf-8") - if _, err := w.Write(marsh); err != nil { - return http.StatusInternalServerError, err - } - - return http.StatusOK, nil -} diff --git a/pre-build.sh b/pre-build.sh deleted file mode 100644 index 66e8c638..00000000 --- a/pre-build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -go get github.com/jteeuwen/go-bindata/go-bindata - -go-bindata -pkg assets -prefix "_embed" \ - -o assets/binary.go -ignore "^.*theme-([^g]|g[^i]|gi[^t]|git[^h]|gith[^u]|githu[^b]).*\.js$" \ - _embed/templates/... _embed/public/js/... _embed/public/css/... _embed/public/ace/src-min/... \ - -git add -A diff --git a/setup.go b/setup.go index a7c97b1b..f491d875 100644 --- a/setup.go +++ b/setup.go @@ -1,7 +1,18 @@ package filemanager import ( - "github.com/hacdias/caddy-filemanager/config" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "strings" + + "github.com/hacdias/filemanager" "github.com/mholt/caddy" "github.com/mholt/caddy/caddyhttp/httpserver" ) @@ -15,7 +26,7 @@ func init() { // setup configures a new FileManager middleware instance. func setup(c *caddy.Controller) error { - configs, err := config.Parse(c) + configs, err := parse(c) if err != nil { return err } @@ -26,3 +37,199 @@ func setup(c *caddy.Controller) error { return nil } + +func parse(c *caddy.Controller) ([]*filemanager.FileManager, error) { + var ( + configs []*filemanager.FileManager + err error + ) + + for c.Next() { + var ( + m = filemanager.New(".") + u = m.User + name = "" + ) + + // Get the baseURL + args := c.RemainingArgs() + + if len(args) > 0 { + m.SetBaseURL(args[0]) + m.SetWebDavURL("/webdav") + } + + for c.NextBlock() { + switch c.Val() { + case "before_save": + /* if cfg.BeforeSave, err = CommandRunner(c); err != nil { + return configs, err + } */ + case "after_save": + /* if cfg.AfterSave, err = CommandRunner(c); err != nil { + return configs, err + } */ + case "webdav": + if !c.NextArg() { + return configs, c.ArgErr() + } + + m.SetWebDavURL(c.Val()) + case "show": + if !c.NextArg() { + return configs, c.ArgErr() + } + + m.SetScope(c.Val(), name) + case "styles": + if !c.NextArg() { + return configs, c.ArgErr() + } + + var tplBytes []byte + tplBytes, err = ioutil.ReadFile(c.Val()) + if err != nil { + return configs, err + } + + u.StyleSheet = string(tplBytes) + case "allow_new": + if !c.NextArg() { + return configs, c.ArgErr() + } + + u.AllowNew, err = strconv.ParseBool(c.Val()) + if err != nil { + return configs, err + } + case "allow_edit": + if !c.NextArg() { + return configs, c.ArgErr() + } + + u.AllowEdit, err = strconv.ParseBool(c.Val()) + if err != nil { + return configs, err + } + case "allow_commands": + if !c.NextArg() { + return configs, c.ArgErr() + } + + u.AllowCommands, err = strconv.ParseBool(c.Val()) + if err != nil { + return configs, err + } + case "allow_command": + if !c.NextArg() { + return configs, c.ArgErr() + } + + u.Commands = append(u.Commands, c.Val()) + case "block_command": + if !c.NextArg() { + return configs, c.ArgErr() + } + + index := 0 + + for i, val := range u.Commands { + if val == c.Val() { + index = i + } + } + + u.Commands = append(u.Commands[:index], u.Commands[index+1:]...) + case "allow", "allow_r", "block", "block_r": + ruleType := c.Val() + + if !c.NextArg() { + return configs, c.ArgErr() + } + + if c.Val() == "dotfiles" && !strings.HasSuffix(ruleType, "_r") { + ruleType += "_r" + } + + rule := &filemanager.Rule{ + Allow: ruleType == "allow" || ruleType == "allow_r", + Regex: ruleType == "allow_r" || ruleType == "block_r", + } + + if rule.Regex && c.Val() == "dotfiles" { + rule.Regexp = regexp.MustCompile("\\/\\..+") + } else if rule.Regex { + rule.Regexp = regexp.MustCompile(c.Val()) + } else { + rule.Path = c.Val() + } + + u.Rules = append(u.Rules, rule) + default: + // Is it a new user? Is it? + val := c.Val() + + // Checks if it's a new user! + if !strings.HasSuffix(val, ":") { + fmt.Println("Unknown option " + val) + } + + // Get the username, sets the current user, and initializes it + val = strings.TrimSuffix(val, ":") + m.NewUser(val) + name = val + } + } + + configs = append(configs, m) + } + + return configs, nil +} + +// CommandRunner ... +func CommandRunner(c *caddy.Controller) (filemanager.Command, error) { + fn := func(r *http.Request, c *filemanager.FileManager, u *filemanager.User) error { return nil } + + args := c.RemainingArgs() + if len(args) == 0 { + return fn, c.ArgErr() + } + + nonblock := false + if len(args) > 1 && args[len(args)-1] == "&" { + // Run command in background; non-blocking + nonblock = true + args = args[:len(args)-1] + } + + command, args, err := caddy.SplitCommandAndArgs(strings.Join(args, " ")) + if err != nil { + return fn, c.Err(err.Error()) + } + + fn = func(r *http.Request, c *filemanager.FileManager, u *filemanager.User) error { + path := strings.Replace(r.URL.Path, c.WebDavURL, "", 1) + path = u.Scope() + "/" + path + path = filepath.Clean(path) + + for i := range args { + args[i] = strings.Replace(args[i], "{path}", path, -1) + } + + cmd := exec.Command(command, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if nonblock { + log.Printf("[INFO] Nonblocking Command:\"%s %s\"", command, strings.Join(args, " ")) + return cmd.Start() + } + + log.Printf("[INFO] Blocking Command:\"%s %s\"", command, strings.Join(args, " ")) + return cmd.Run() + } + + return fn, nil +} diff --git a/utils/errors/errors.go b/utils/errors/errors.go deleted file mode 100644 index 37781c1a..00000000 --- a/utils/errors/errors.go +++ /dev/null @@ -1,24 +0,0 @@ -package errors - -import ( - "net/http" - "os" -) - -// ErrorToHTTPCode converts errors to HTTP Status Code. -func ErrorToHTTPCode(err error, gone bool) int { - switch { - case os.IsPermission(err): - return http.StatusForbidden - case os.IsNotExist(err): - if !gone { - return http.StatusNotFound - } - - return http.StatusGone - case os.IsExist(err): - return http.StatusGone - default: - return http.StatusInternalServerError - } -} diff --git a/utils/variables/types.go b/utils/variables/types.go deleted file mode 100644 index ee43dad3..00000000 --- a/utils/variables/types.go +++ /dev/null @@ -1,13 +0,0 @@ -package variables - -import "reflect" - -// IsMap checks if some variable is a map -func IsMap(sth interface{}) bool { - return reflect.ValueOf(sth).Kind() == reflect.Map -} - -// IsSlice checks if some variable is a slice -func IsSlice(sth interface{}) bool { - return reflect.ValueOf(sth).Kind() == reflect.Slice -} diff --git a/utils/variables/types_test.go b/utils/variables/types_test.go deleted file mode 100644 index 9955b9b2..00000000 --- a/utils/variables/types_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package variables - -import "testing" - -type interfaceToBool struct { - Value interface{} - Result bool -} - -var testIsMap = []*interfaceToBool{ - &interfaceToBool{"teste", false}, - &interfaceToBool{453478, false}, - &interfaceToBool{-984512, false}, - &interfaceToBool{true, false}, - &interfaceToBool{map[string]bool{}, true}, - &interfaceToBool{map[int]bool{}, true}, - &interfaceToBool{map[interface{}]bool{}, true}, - &interfaceToBool{[]string{}, false}, -} - -func TestIsMap(t *testing.T) { - for _, test := range testIsMap { - if IsMap(test.Value) != test.Result { - t.Errorf("Incorrect value on IsMap for %v; want: %v; got: %v", test.Value, test.Result, !test.Result) - } - } -} - -var testIsSlice = []*interfaceToBool{ - &interfaceToBool{"teste", false}, - &interfaceToBool{453478, false}, - &interfaceToBool{-984512, false}, - &interfaceToBool{true, false}, - &interfaceToBool{map[string]bool{}, false}, - &interfaceToBool{map[int]bool{}, false}, - &interfaceToBool{map[interface{}]bool{}, false}, - &interfaceToBool{[]string{}, true}, - &interfaceToBool{[]int{}, true}, - &interfaceToBool{[]bool{}, true}, - &interfaceToBool{[]interface{}{}, true}, -} - -func TestIsSlice(t *testing.T) { - for _, test := range testIsSlice { - if IsSlice(test.Value) != test.Result { - t.Errorf("Incorrect value on IsSlice for %v; want: %v; got: %v", test.Value, test.Result, !test.Result) - } - } -} diff --git a/utils/variables/variables.go b/utils/variables/variables.go deleted file mode 100644 index 7a0168b4..00000000 --- a/utils/variables/variables.go +++ /dev/null @@ -1,47 +0,0 @@ -package variables - -import ( - "errors" - "log" - "reflect" -) - -// Defined checks if variable is defined in a struct -func Defined(data interface{}, field string) bool { - t := reflect.Indirect(reflect.ValueOf(data)).Type() - - if t.Kind() != reflect.Struct { - log.Print("Non-struct type not allowed.") - return false - } - - _, b := t.FieldByName(field) - return b -} - -// Dict allows to send more than one variable into a template -func Dict(values ...interface{}) (map[string]interface{}, error) { - if len(values)%2 != 0 { - return nil, errors.New("invalid dict call") - } - dict := make(map[string]interface{}, len(values)/2) - for i := 0; i < len(values); i += 2 { - key, ok := values[i].(string) - if !ok { - return nil, errors.New("dict keys must be strings") - } - dict[key] = values[i+1] - } - - return dict, nil -} - -// StringInSlice checks if a slice contains a string -func StringInSlice(a string, list []string) (bool, int) { - for i, b := range list { - if b == a { - return true, i - } - } - return false, 0 -} diff --git a/utils/variables/variables_test.go b/utils/variables/variables_test.go deleted file mode 100644 index ec76d459..00000000 --- a/utils/variables/variables_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package variables - -import "testing" - -type testDefinedData struct { - f1 string - f2 bool - f3 int - f4 func() -} - -type testDefined struct { - data interface{} - field string - result bool -} - -var testDefinedCases = []testDefined{ - {testDefinedData{}, "f1", true}, - {testDefinedData{}, "f2", true}, - {testDefinedData{}, "f3", true}, - {testDefinedData{}, "f4", true}, - {testDefinedData{}, "f5", false}, - {[]string{}, "", false}, - {map[string]int{"oi": 4}, "", false}, - {"asa", "", false}, - {"int", "", false}, -} - -func TestDefined(t *testing.T) { - for _, pair := range testDefinedCases { - v := Defined(pair.data, pair.field) - if v != pair.result { - t.Error( - "For", pair.data, - "expected", pair.result, - "got", v, - ) - } - } -} diff --git a/wrapper/response_writer.go b/wrapper/response_writer.go deleted file mode 100644 index 0af79376..00000000 --- a/wrapper/response_writer.go +++ /dev/null @@ -1,29 +0,0 @@ -package wrapper - -import "net/http" - -// ResponseWriterNoBody is a wrapper used to suprress the body of the response -// to a request. Mainly used for HEAD requests. -type ResponseWriterNoBody struct { - http.ResponseWriter -} - -// NewResponseWriterNoBody creates a new ResponseWriterNoBody. -func NewResponseWriterNoBody(w http.ResponseWriter) *ResponseWriterNoBody { - return &ResponseWriterNoBody{w} -} - -// Header executes the Header method from the http.ResponseWriter. -func (w ResponseWriterNoBody) Header() http.Header { - return w.ResponseWriter.Header() -} - -// Write suprresses the body. -func (w ResponseWriterNoBody) Write(data []byte) (int, error) { - return 0, nil -} - -// WriteHeader writes the header to the http.ResponseWriter. -func (w ResponseWriterNoBody) WriteHeader(statusCode int) { - w.ResponseWriter.WriteHeader(statusCode) -}