diff options
| -rw-r--r-- | CHANGELOG.md | 290 | ||||
| -rw-r--r-- | Gruntfile.js | 6 | ||||
| -rw-r--r-- | css/pomf.css | 33 | ||||
| -rw-r--r-- | js/app.js | 170 | ||||
| -rw-r--r-- | js/cabinet.js | 66 | ||||
| -rw-r--r-- | js/cheesesteak.js | 232 | ||||
| -rw-r--r-- | js/pomf.js | 175 | ||||
| -rw-r--r-- | js/zepto.js | 1035 | ||||
| -rw-r--r-- | package.json | 3 | ||||
| -rw-r--r-- | pages/banners.swig | 5 | ||||
| -rw-r--r-- | pages/banners/donate.swig | 5 | ||||
| -rw-r--r-- | pages/banners/virusscans.swig | 5 | ||||
| -rw-r--r-- | pages/index.swig | 2 | ||||
| -rw-r--r-- | pages/layout.swig | 3 | ||||
| -rw-r--r-- | pages/nojs.swig | 2 | ||||
| -rw-r--r-- | schema.sql | 2 | ||||
| -rwxr-xr-x | static/classes/Response.class.php | 101 | ||||
| -rwxr-xr-x | static/classes/UploadException.class.php | 8 | ||||
| -rw-r--r-- | static/classes/UploadedFile.class.php | 18 | ||||
| -rw-r--r-- | static/includes/database.inc.php | 6 | ||||
| -rw-r--r-- | static/includes/settings.inc.php | 71 | ||||
| -rw-r--r-- | static/upload.php | 92 |
22 files changed, 742 insertions, 1588 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8c41b14 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,290 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## `2.1.0` - Unreleased + +### Added + +- `CHANGELOG.md`. (Juuso Lapinlampi) +- HTML `<meta name="generator">` with Pomf version. Generated with grunt-swig. + (Juuso Lapinlampi) +- PHPDoc DocBlocks. (Juuso Lapinlampi) + +### Changed + +- All client-side JavaScript replaced with more efficient, smaller vanilla + JavaScript. (Luminarys) +- Luminarys added to `package.json` contributors. (Juuso Lapinlampi) +- Missing essential PHP includes and classes are now errors (`require_once`) + instead of warnings (`include_once`). (Austin Gillmann) + +### Deprecated + +- Swig banners. Use Swig templates or an alternative. (Juuso Lapinlampi) +- Unused file expiration (`expire`). This feature may be removed in the next + major version. (Juuso Lapinlampi) +- `get_sha1()` function and `$sha1` variable. This API may be changed or removed + in the next major version, switching to SHA-256 hashes. (Juuso Lapinlampi) +- `csv_error`, `csv_success`, `html_error`, `html_success`, `json_error`, + `json_success`, `text_error` and `text_success` functions. The functions will + be renamed to `camelCase` format (e.g. `csvError`, `htmlSuccess`). (Juuso + Lapinlampi) +- Output buffering (`ob_start`). This feature may be removed in the next + major version. (Juuso Lapinlampi) + +### Fixed + +- Code cleanliness of `upload_file()` in `upload.php`. (Juuso Lapinlampi) + +## `2.0.2` - 2016-05-20 + +### Changed + +- `npm bugs` URL replaced with an email address. (Juuso Lapinlampi) + +### Fixed + +- LibreJS `@source` URL to git.pantsu.cat's new cgit tree. (Juuso Lapinlampi) +- `package.json` package name to lowercase as per NPM rules. (Juuso Lapinlampi) +- Alphabetical order of grunt-swig options in `Gruntfile.js`. (Juuso Lapinlampi) +- "Getting help" IRC contact for developers in `README.md`. (Juuso Lapinlampi) +- Missing Bootstrap license header in `pomf.css`. (Juuso Lapinlampi) + +## `2.0.1` - 2016-01-28 + +### Changed + +- Gyazo Client renamed to Gyazowin in tools page to match the actual project + name. (Juuso Lapinlampi) + +### Fixed + +- ShareX URL and settings in tools page. (Juuso Lapinlampi) +- Gyazowin URL in tools page. (Juuso Lapinlampi) +- PHPCS lint error about a missing newline after `text_error()`. (Juuso + Lapinlampi) +- PHP warnings about undeclared variables in `html_success()` and + `text_success()`. (Juuso Lapinlampi) + +### Removed + +- Unused, commented `a:focus` `outline-offset` in CSS. (Luminarys) +- Unused `check_fileapi.swig` inclusion in `nojs.swig`. (Juuso Lapinlampi) +- Unused `span.old` and `section.ded` CSS rules leftover from 2.0.0 release. + (Eliot Whalan) + +## `2.0.0` - 2016-01-10 + +### Added + +- "Other services" page for Pantsu.cat. (Eliot Whalan) +- `lang` attributes for screen reader accessibility. (Eliot Whalan) +- Configuration option `POMF_FILES_LENGTH` for number of random characters to + use in a generated filename. (Juuso Lapinlampi) +- JavaScript-free upload. (Eliot Whalan) +- HTML response API (for JavaScript-free upload). (Eliot Whalan) +- Text response API. (Eliot Whalan) +- limf and 1339secure to tools page. (Eliot Whalan, Juuso Lapinlampi) +- LibreJS compatibility. (Eliot Whalan) +- License headers in source code. (Juuso Lapinlampi) +- Link to Pantsu.cat's Tor hidden service. (Eliot Whalan) +- Credits, contributing, features, demo and getting help sections to + `README.md`. (Juuso Lapinlampi) +- Git mailmap. (Juuso Lapinlampi) + +### Changed + +- Rebrand from Pomf.se to Pantsu.cat. (Eliot Whalan) +- Viewports no longer have a maximum scale. (Eliot Whalan) +- All tool configurations on tools page to use Pantsu.cat for uploading. (Eliot + Whalan) +- "Autist neckbeard" warning on front page for users without JavaScript. (Eliot + Whalan) +- Response Content-Type charset is now all uppercase (UTF-8). (Juuso Lapinlampi) +- npm dependencies have been updated to latest versions as of release. Fixes + deprecation warnings. (Eliot Whalan) +- npm dependencies accept caret (minor release) instead of tilde (patch + release). (Eliot Whalan) +- Increase user-configured maximum upload size to 128MiB. (Eliot Whalan) +- The upload button is now a `<button>` element instead of `<a>` hyperlink. + Improves semantics and accessibility. (Juuso Lapinlampi) +- Banners are no longer displayed on the FAQ page. (Juuso Lapinlampi) +- FAQ has been rewritten from scratch to explain what Pantsu.cat is and what its + policies are. Semantically correct and prettier. (Juuso Lapinlampi) + +### Deprecated + +- Gyazo response API. Use text response instead. (Juuso Lapinlampi) +- JavaScript ES5 code. A future release will update the JavaScript source to + modern ES6. Browser compatibility may be affected. (Juuso Lapinlampi) + +### Removed + +- All unmaintained and dead tools from tools page. (Eliot Whalan) +- Internet Explorer 8 quirks mode compatibility in uglified JavaScript. (Juuso + Lapinlampi) +- Moe panel from core, including most of the code in core for it. It is now an + (unsupported) extension. (Eliot Whalan, Juuso Lapinlampi) +- Unnecessary TODO section from `README.md`. (Harry H) +- Code and images for non-free kawaii anime girls. The latter cannot be + redistributed with the source. (Juuso Lapinlampi) +- Unused Grunt `cssmin` task's banner option. (Juuso Lapinlampi) +- Grunt HTML minification task. (Juuso Lapinlampi) +- Unused Grunt `mkdir` task. (Juuso Lapinlampi) +- Unused "POCKY~" CSS style rules. (Juuso Lapinlampi) +- Unused, commented Mozilla file input JavaScript code. (Juuso Lapinlampi) + +### Fixed + +- Missing whitespace in upload button label. (Eliot Whalan) +- Favicon HTML markup now uses modern `rel="icon"` markup. (Eliot Whalan) +- Refactored `generate_name()` for bug fixing. (Juuso Lapinlampi) +- Use UNIX socket in `POMF_DB_CONN` configuration option for faster database + queries. (Juuso Lapinlampi) +- Format all PHP code to PSR-2 style guide, fixes lint errors. (Juuso + Lapinlampi) +- Decode `…` correctly when the file is too big. (Juuso Lapinlampi) +- Remove other `…` decoding hack in `case 413`. (Juuso Lapinlampi) +- Whitespace in Swig templates, PHP and JavaScript. (Juuso Lapinlampi) +- `README.md` header outline, fixes accessibility. (Juuso Lapinlampi) +- Add missing authors to `LICENSE` and other files. (Juuso Lapinlampi) +- Format and lint all JavaScript code to Airbnb ES5 style. About 350 lint + errors found, each carefully fixed by hand. (Juuso Lapinlampi) +- Ambiguity in choice of words for licensing (MIT to Expat). No actual change + of license. (Eliot Whalan) +- Use strict PHP comparison for upload `$tries`. (Juuso Lapinlampi) +- Remove multiple instances of code duplication in JavaScript. (Juuso + Lapinlampi) +- Virus scan banner is now informative (`.alert-info`), not an error + (`.alert-error`). (Juuso Lapinlampi) +- Reduced `npm install` time from 15-30 seconds to mere ~5 seconds (on modern + Intel i7-3770K machine), resulting in faster builds. (Juuso Lapinlampi) +- Minor `README.md` documentation errors. (Juuso Lapinlampi) +- WCAG 2.0 contrast (section 1.4.3) in `.alert-info`. (Juuso Lapinlampi) +- Capitalization of "ShareX" heading on tools page. (Juuso Lapinlampi) + +## `1.0.0+8757e9a` - 2015-08-15 + +Last Git repository snapshot in `nokonoko/Pomf` before forking to `pantsu/pomf`. +No formal release. + +### Added + +- Gyazo and CSV response types. (Peter Lejeck) +- Flattr to donation banner. (Peter Lejeck) +- Label and message to Bitcoin URL in donation banner. (Peter Lejeck) +- CSS for donation buttons. (Peter Lejeck) +- Moe panel for login/administration. (Eric Johansson) +- Alternative email contact for file deletion in FAQ. (Eric Johansson) +- CSS for old and dead tools. (Peter Lejeck) +- Double dot file extensions support. Configurable in `settings.inc.php`. (Peter + Lejeck) +- Disclaimer about unsupported web browsers in FAQ. (Eric Johansson) +- Initial file expiration support for moe panel. (Eric Johansson) +- MySQL schema (`schema.sql`) for installing Pomf. (Eric Johansson, cenci0) +- MySQL schema (`update.sql`) to help migrate old databases to the new moe panel + schema. (cenci0, Austin Gillman) +- HTTP compression support in `upload.php`. (cenci0) +- Configuration option `POMF_URL`. (cenci0) +- `diverse_array` and `refiles` API in `upload.php`. (cenci0) +- HTML minification using grunt-contrib-htmlmin. (cenci0) +- Instructions in `README.md` on how to enable compression in Apache webserver. + (cenci0) +- Todo section to `README.md`. (Eric Johansson) +- Advice to disable PHP execution for uploaded `.php` files. (Eric Johansson) +- Configurable `{{max_upload_size`}} option in `Gruntfile.js` to replace + hardcoded 50MiB value. Defaults to value 50. (Kieran Harkin) + +### Changed + +- `POMF_DB_CONN` default host to `127.0.0.1`. (Eric Johansson) +- Paddings on `.alert` boxes simplified. (Peter Lejeck) +- Donation plea. (Peter Lejeck) +- Donate buttons are unified and on a single row. (Peter Lejeck) +- PayPal's encrypted `<form>` replaced with a link in donation banner. (Peter + Lejeck) +- Virus scans banner to match the donate banner scheme. +- Donate banner is now an `.alert-info` instead of `.alert-error`. (Peter + Lejeck) +- `generate_name` algorithm. Generates random lowercase letters from `a` to `z` + instead of a mix of CRC-32B and random lowercase letters. (Eric Johansson) +- Tools page tool names and API status updated. (Eric Johansson, Peter Lejeck) +- Gyazo `generate_name` algorithm to `FxxAxxG.png`, where `x` is a random + lowercase letter. Removes CRC-32B checksums and `$grill` parameter from the + function. (Eric Johansson) +- Increased length of `generate_name` return string by one character. (Eric + Johansson) +- Default `POMF_FILES_RETRIES` increased from 5 to 15. (Eric Johansson, Peter + Lejeck) +- `nav.swig` links to new places of interest of other Pomf.se projects and + social media. (Eric Johansson) +- Genericize defaults function into merge utility in `cheesesteak.js`. (Peter + Lejeck) +- `README.md` now uses a hyperlink to `@nekunekus` Twitter account. (Eric + Johansson) +- Refactored the core PHP code in Pomf. Introduced classes. (cenci0) +- `.alert-info`, `.alert-error`, `nav a` and `a` CSS colors to satisfy WCAG 2.0 + requirements on contrast (section 1.4.3). Patch by Juuso Lapinlampi. (Eric + Johansson) + +### Fixed + +- `git clone` URL in `README.md`. (Eric Johansson) +- PHPDoc comments in `upload.php`. (Peter Lejeck) +- A bug in `refiles()`. Deferences `$file`. (Peter Lejeck, catboy) +- Broken tools download URLs. (Eric Johansson) +- Uploading files without file extension. (Eric Johansson) +- Bitcoin wallet URI in donation banner. (Eric Johansson) +- ShareX settings URL in tools. (Eric Johansson) +- Gyazo Client author's Twitter URL. (Eric Johansson) +- Code indentation in `upload.php`. (Peter Lejeck) +- Reduntant `settings.inc.php` inclusion twice in `upload.php`. (benwaffle) +- Moe panel incorrectly returning 5 entries instead of none in search while + there's no search query. (benwaffle) +- Cross-site scripting vulnerability in moe panel from arbitrary `originalname`. + No CVE requested. Reported by Juuso Lapinlampi. (Eric Johansson) +- Copy-pasted code rolled into a `for` loop in `generate_name`. (Michael + "wafflestealer654") +- HTML validation for `case 413` in `pomf.js`. (cenci0) +- `README.md` title from Pomf.se to Pomf. (Eric Johansson) +- Pass full URL back to the client in response. (Kieran Harkin) + +### Removed + +- Link to mail newsletter in FAQ. (Eric Johansson) +- Unused `blackniggers/kittens.php`. Previously used for Gyazo client, now + replaced. (Eric Johansson) +- Unused `get_crc32` function in `UploadedFile.class.php`. Reported by Juuso + Lapinlampi. (Eric Johansson) + +## `v1.0.0` - 2013-10-31 + +Initial unofficial release in `nokonoko/Pomf`. Changes are since initial commit. + +### Added + +- `LICENSE` with Expat license. (Eric Johansson) +- Email and Twitter contacts to `README.md`. (Eric Johansson) +- Frequently asked questions (FAQ). (Eric Johansson) +- Favicon. (Eric Johansson) +- Kawaii anime girls (`grill.php`). (Eric Johansson) +- Web interface with JavaScript. (Eric Johansson, Peter Lejeck) +- Tools page. (Eric Johansson) +- `upload.php`, `UploadedFile.class.php`, `database.inc.php`, + `settings.inc.php`. (Eric Johansson, Peter Lejeck) +- Gyazo support (`blackniggers/kittens.php`) with `generate_name` function. + (Eric Johansson) +- Grunt buildsystem. (Peter Lejeck) +- Swig pages. (Peter Lejeck) +- Swig banners. (Peter Lejeck) +- `README.md` documentation. (Peter Lejeck) + +### Changed + +- `.gitignore` will only ignore `dist/` and `node_modules`. (Peter Lejeck) + +### Removed + +- `.gitattributes`. (Eric Johansson) diff --git a/Gruntfile.js b/Gruntfile.js index 9e95c5b..488e5b7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -11,6 +11,7 @@ module.exports = function (grunt) { generateRobotstxt: false, generateSitemap: false, max_upload_size: 128, + pkgVersion: '<%= pkg.version %>', production: false, siteUrl: 'https://pantsu.cat/', src: [ @@ -33,10 +34,7 @@ module.exports = function (grunt) { dist: { files: { 'dist/pomf.min.js': [ - 'js/zepto.js', - 'js/cheesesteak.js', - 'js/cabinet.js', - 'js/pomf.js' + 'js/app.js' ] } } diff --git a/css/pomf.css b/css/pomf.css index 66fa382..676845e 100644 --- a/css/pomf.css +++ b/css/pomf.css @@ -239,7 +239,7 @@ nav > ul > li:last-child:after { .error#upload-filelist { color: #891A18; } -.error#upload-filelist .file-size { +.error#upload-filelist .progress-percent { color: #B94A48; } .error#upload-filelist .file-progress { @@ -261,13 +261,12 @@ nav > ul > li:last-child:after { text-overflow: ellipsis; white-space: nowrap; } -.file-size, +.file-progress, .file-url { display: inline-block; float: right; font-size: 0.9em; margin-left: 8px; - color: #5C5C5C; vertical-align: middle; } .file-url a { @@ -276,9 +275,29 @@ nav > ul > li:last-child:after { .file-url a:hover { color: #1C1C1C; } +.progress-percent { + float: right; +} + +progress[value] { + /* Reset the default appearance */ + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + + border: none; + +} + +progress[value]::-webkit-progress-bar { + background-color: #eee; + border-radius: 2ps; + box-shadow: 0 2px 5px rgba(0,0,0,0.25) inset; +} + /* completed rows */ .completed .file-progress, -.completed .file-size { +.completed .progress-percent { display: none; } .completed .file-url { @@ -341,14 +360,8 @@ nav > ul > li:last-child:after { margin-bottom: 12px; } .file-progress { - float: left; width: 70%; } - .file-size { - float: left; - max-width: 30%; - overflow: hidden; - } .file-name, .file-url { width: 100%; max-width: 100%; diff --git a/js/app.js b/js/app.js new file mode 100644 index 0000000..5b29a63 --- /dev/null +++ b/js/app.js @@ -0,0 +1,170 @@ +/** + * Copyright (c) 2016 Luminarys <postmaster@gensok.io> + * + * 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. + */ + +function addRow(file) { + var row = document.createElement('li'); + + var name = document.createElement('span'); + name.textContent = file.name; + name.className = 'file-name'; + + var progressIndicator = document.createElement('span'); + progressIndicator.className = 'progress-percent'; + progressIndicator.textContent = '0%'; + + var progressBar = document.createElement('progress'); + progressBar.className = 'file-progress'; + progressBar.setAttribute("max", "100"); + progressBar.setAttribute("value", "0"); + + row.appendChild(name); + row.appendChild(progressBar); + row.appendChild(progressIndicator); + + document.getElementById('upload-filelist').appendChild(row); + return row; +} + +function handleUploadProgress(evt) { + var xhr = evt.target; + var bar = xhr.bar; + var percentIndicator = xhr.percent; + + if (evt.lengthComputable) { + var progressPercent = Math.floor((evt.loaded / evt.total) * 100); + bar.setAttribute("value", progressPercent); + percentIndicator.textContent = progressPercent + '%'; + } +} + +function handleUploadComplete(evt) { + var xhr = evt.target; + var bar = xhr.bar; + var row = xhr.row; + var percentIndicator = xhr.percent; + + percentIndicator.style.visibility = "hidden"; + bar.style.visibility = "hidden"; + row.removeChild(bar); + row.removeChild(percentIndicator); + var respStatus = xhr.status; + + var url = document.createElement('span'); + url.className = 'file-url'; + row.appendChild(url); + + var link = document.createElement('a'); + if (respStatus === 200) { + var response = JSON.parse(xhr.responseText); + if (response.success) { + link.textContent = response.files[0].url.replace(/.*?:\/\//g, ""); + link.href = response.files[0].url; + url.appendChild(link); + } else { + bar.innerHTML = 'Error: ' + response.reason; + } + } else if (respStatus === 413) { + link.textContent = 'File Too big!'; + url.appendChild(link); + } else { + link.textContent = 'Server error!'; + url.appendChild(link); + } +} + +function uploadFile(file, row) { + var bar = row.querySelector('.file-progress'); + var percentIndicator = row.querySelector('.progress-percent'); + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/upload.php'); + + xhr["row"] = row; + xhr["bar"] = bar; + xhr["percent"] = percentIndicator; + xhr.upload["bar"] = bar; + xhr.upload["percent"] = percentIndicator; + + xhr.addEventListener('load', handleUploadComplete, false); + xhr.upload.onprogress = handleUploadProgress; + + var form = new FormData(); + form.append('files[]', file); + xhr.send(form); +} + +function stopDefaultEvent(evt) { + evt.stopPropagation(); + evt.preventDefault(); +} + +function handleDrag(state, element, evt) { + stopDefaultEvent(evt); + if (state.dragCount == 1) { + element.textContent = "Drop it here~"; + } + state.dragCount += 1; +} + +function handleDragAway(state, element, evt) { + stopDefaultEvent(evt); + state.dragCount -= 1; + if (state.dragCount == 0) { + element.textContent = "Select or drop file(s)"; + } +} + +function handleDragDrop(state, element, evt) { + stopDefaultEvent(evt); + handleDragAway(state, element, evt); + var len = evt.dataTransfer.files.length; + for (var i = 0; i < len; i++) { + var file = evt.dataTransfer.files[i]; + var row = addRow(file); + uploadFile(file, row); + } +} + +function uploadFiles(evt) { + var len = evt.target.files.length; + for (var i = 0; i < len; i++) { + var file = evt.target.files[i]; + var row = addRow(file); + uploadFile(file, row); + } +} + +function selectFiles(target, evt) { + stopDefaultEvent(evt); + target.click(); +} + +var state = { dragCount: 0 }; +var uploadButton = document.getElementById('upload-btn'); +window.addEventListener('dragenter', handleDrag.bind(this, state, uploadButton), false); +window.addEventListener('dragleave', handleDragAway.bind(this, state, uploadButton), false); +window.addEventListener('drop', handleDragAway.bind(this, state, uploadButton), false); +window.addEventListener('dragover', stopDefaultEvent, false); + +var uploadInput = document.getElementById('upload-input'); +uploadInput.addEventListener('change', uploadFiles); +uploadButton.addEventListener('click', selectFiles.bind(this, uploadInput)); +uploadButton.addEventListener('drop', handleDragDrop.bind(this, state, uploadButton), false); diff --git a/js/cabinet.js b/js/cabinet.js deleted file mode 100644 index ba28a33..0000000 --- a/js/cabinet.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2013 Peter Lejeck <peter.lejeck@gmail.com> - * - * 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. - */ - -(function(cabinet) { - cabinet.hasFileAPI = function hasFileAPI() { - return Boolean(window.FormData); - }; - - $.fn.cabinet = function(uploadInput) { - var $input = $(uploadInput); - - var _this = this; - var passthrough = function(outer, inner, prevent, fn) { - _this.on(outer, function(evt) { - if (inner === 'click') { - $input[0].click(); - } else { - $input.trigger(inner); - } - - if (prevent) { - evt.preventDefault(); - } - - if (fn) { - fn(evt); - } - }, false); - }; - - $input[0].filelist = Object.create(FileList); - - $input.on('change', function(evt) { - this.filelist = evt.target.files; - _this.change(); - }); - - passthrough('dragenter', 'dragenter', true); - passthrough('dragover', 'dragover', true); - passthrough('dragleave', 'dragleave', true); - passthrough('click', 'click', false); - passthrough('drop', 'dragleave', true, function(evt) { - $input[0].filelist = evt.dataTransfer.files; - _this.change(); - }); - }; -})($); diff --git a/js/cheesesteak.js b/js/cheesesteak.js deleted file mode 100644 index 6e20880..0000000 --- a/js/cheesesteak.js +++ /dev/null @@ -1,232 +0,0 @@ -/** - * Copyright (c) 2013 Peter Lejeck <peter.lejeck@gmail.com> - * - * 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. - */ - -(function() { - // Lightweight EventEmitter implementation - var EventEmitter = function() {}; - - // Utility to convert bytes into human units - var humanSize = { - get: function humanSize() { - var units = [ - 'B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB', - ]; - var base = Math.floor(Math.log(this.size) / Math.log(1024)); - return (this.size / Math.pow(1024, base)).toFixed(2) + ' ' + units[base]; - }, - }; - - var percentUploaded = { - get: function percentUploaded() { - return this.uploadedSize / this.size; - }, - }; - - // b gets merged as defaults for a - var merge = function merge(base, overlay) { - var out = base; - var key; - - for (key in overlay) { - if (typeof overlay[key] === 'object') { - out[key] = merge(base[key], overlay[key]); - } else { - out[key] = overlay[key]; - } - } - - return out; - }; - - var FileListUploader = function(url, files, opts) { - var request = opts || {}; - - this.url = url; - this.files = files; - this.opts = merge({ - field: 'files[]', - method: 'POST', - data: {}, - }, request); - }; - - EventEmitter.prototype.on = function(evt, fn) { - this._events = this._events || {}; - this._events[evt] = this._events[evt] || []; - this._events[evt].push(fn); - }; - - EventEmitter.prototype.off = function(evt, fn) { - if (!this.hasOwnProperty('_events') || evt in this._events === false) { - return; - } - - this._events[evt].splice(this._events[evt].indexOf(fn), 1); - }; - - EventEmitter.prototype.emit = function(evt) { - var i; - var len; - - // Check whether the event we want to emit actually exists - if (!this.hasOwnProperty('_events') || evt in this._events === false) { - return; - } - - len = this._events[evt].length; - - // Call all functions that are bound to the event - for (i = 0; i < len; i++) { - this._events[evt][i].apply(this, Array.prototype.slice.call(arguments, - 1)); - } - }; - - // Copy the stuff from Array to FileList - FileList.prototype.forEach = Array.prototype.forEach; - FileList.prototype.every = Array.prototype.every; - FileList.prototype.some = Array.prototype.some; - FileList.prototype.filter = Array.prototype.filter; - FileList.prototype.map = Array.prototype.map; - FileList.prototype.reduce = Array.prototype.reduce; - FileList.prototype.reduceRight = Array.prototype.reduceRight; - - Object.defineProperty(FileList.prototype, 'size', { - get: function getSize() { - return this.reduce(function(prev, curr) { - return prev + curr.size; - }, 0); - }, - }); - - Object.defineProperty(FileList.prototype, 'humanSize', humanSize); - Object.defineProperty(File.prototype, 'humanSize', humanSize); - - Object.defineProperty(FileList.prototype, 'percentUploaded', - percentUploaded); - Object.defineProperty(File.prototype, 'percentUploaded', percentUploaded); - - Object.defineProperty(FileList.prototype, 'uploadedSize', { - get: function getUploadedSize() { - return this.reduce(function(prev, curr) { - return prev + (curr.uploadedSize || 0); - }, 0); - }, - }); - - // Object URL stuff - Object.defineProperty(File.prototype, 'url', { - get: function getURL() { - return window.URL.createObjectURL(this); - }, - }); - File.revokeURL = function revokeURL(url) { - window.URL.revokeObjectURL(url); - }; - - FileListUploader.prototype = Object.create(EventEmitter.prototype); - FileListUploader.prototype.upload = function(cb) { - var opts = this.opts; - var files = this.files; - var _this = this; - var len = files.length; - var data = new FormData(); - var xhr = new XMLHttpRequest(); - var i; - - if (cb) { - this.on('uploadcomplete', cb); - } - - files.forEach(function(file) { - data.append(opts.field, file); - }); - - xhr.open(opts.method, this.url, true); - - function initProgressBar() { - for (i = 0; i < len; i++) { - files[i].uploadedSize = 0; - } - } - - xhr.upload.addEventListener('loadstart', initProgressBar); - - function updateProgressBar(evt) { - var size; - - if (evt.lengthComputable) { - size = evt.loaded; - - /** - * We know the size of the files, the order they're in, and how - * much we've uploaded. Based on this data, we can do some magic - * to figure out which file we're on, and how much we've uploaded - * of that file. - */ - - // TODO: This math has trouble on later uploads. Leak somewhere. - for (i = 0; i < len; i++) { - files[i].uploadedSize = Math.min(size, files[i].size); - size -= files[i].uploadedSize; - if (size <= 0) { - files.current = files[i]; - size = 0; - } - } - } - - _this.emit('uploadprogress', evt, files); - } - - xhr.upload.addEventListener('progress', updateProgressBar, false); - - // The upload is complete, now tell the user to wait for URLs. - function postUpload(evt) { - _this.emit('uploadcomplete', evt); - } - - xhr.upload.addEventListener('load', postUpload); - - // Tell the browser the upload completed and wait for response. - function postProgress(evt) { - _this.emit('progress', evt); - } - - xhr.addEventListener('progress', postProgress); - - // Send a success/error response. Nothing more to do. - function uploadFinished(evt) { - _this.emit('load', evt, xhr.responseText); - } - - xhr.addEventListener('load', uploadFinished); - - xhr.send(data); - - // TODO: Add more event passthroughs, maybe abstract it into a helper? - }; - - FileList.prototype.upload = function uploadFileList(url, opts) { - return new FileListUploader(url, this, opts); - }; -})(); diff --git a/js/pomf.js b/js/pomf.js deleted file mode 100644 index cf91e35..0000000 --- a/js/pomf.js +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright (c) 2013 Peter Lejeck <peter.lejeck@gmail.com> - * Copyright (c) 2015 the Pantsu.cat developers <hostmaster@pantsu.cat> - * - * 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. - */ - -$(function() { - var $uploadInput = $('#upload-input'); - var $uploadBtn = $('#upload-btn'); - var $uploadFiles = $('#upload-filelist'); - - var $btnContent = ''; - - var MAX_SIZE = (function(node) { - var max = node.attr('data-max-size') || '10MiB'; - var num = parseInt(/([0-9,]+).*/.exec(max)[1].replace(',', ''), 10); - var unit = /(?:([KMGTPEZY])(i)?B|([BKMGTPEZY]))/.exec(max) || ['B', '', '']; - - var oneUnit = Math.pow( - (unit[2] === 'i' ? 1024 : 1000), - 'BKMGTPEZY'.indexOf(unit[1]) - ); - - return num * oneUnit; - })($uploadInput); - - var createRow = function(filename, size, extra) { - var $rowItem = $('<li class=file>'); - var $rowName = $('<span class=file-name>'); - var $rowProg = $('<div class="file-progress progress-outer">'); - var $rowSize = $('<span class=file-size>'); - var $rowUrl = $('<span class=file-url>'); - - $rowItem.addClass(extra || ''); - - $('<div class=progress-inner>').appendTo($rowProg); - - $rowItem.attr('data-filename', escape(filename)); - $rowName.text(filename); - $rowSize.text(size); - - $rowItem.append($rowName, $rowProg, $rowSize, $rowUrl); - - return $rowItem; - }; - - if (!$.hasFileAPI()) { - $('#no-file-api').show(); - $uploadBtn.hide(); - } - - $uploadBtn.cabinet($uploadInput); - - $uploadBtn.on('dragenter', function(evt) { - if (this === evt.target) { - $(this).addClass('drop'); - $btnContent = $(this).html(); - $(this).html('Drop it here~'); - } - }); - - $uploadBtn.on('drop', function() { - $(this).trigger('dragleave'); - }); - - $uploadBtn.on('dragleave', function(evt) { - var node = evt.target; - do { - if (node === this) { - $(this).removeClass('drop'); - $(this).html($btnContent); - break; - } - } while (node === node.parentNode); - }); - - $uploadBtn.on('change', function() { - var files = $uploadInput[0].filelist; - var totalRow = createRow('', files.humanSize, 'total'); - var $totalName = $('.file-name', totalRow); - var UPLOAD_ERR_MAX_SIZE = 'onii-chan y-your upload is t-too big…'; - var UPLOAD_ERR_FAILED = 'Something went wrong; try again later.'; - var up = files.upload('upload.php'); - - var eachRow = function(_files, fn) { - var hits = {}; - _files.forEach(function(file) { - var row = $($('li[data-filename="' + - escape(file.name) + '"]')[hits[file.name] || 0]); - ++hits[file.name]; - fn.call(row, row, file, _files); - }); - }; - - $uploadFiles.empty().removeClass('error completed'); - - files.forEach(function(file) { - createRow(file.name, file.humanSize).appendTo($uploadFiles); - }); - - totalRow.appendTo($uploadFiles); - - if (files.size > MAX_SIZE) { - $uploadFiles.addClass('error'); - $totalName.html(UPLOAD_ERR_MAX_SIZE); - return; - } - - up.on('uploadprogress', function(evt, _files) { - eachRow(_files, function(row, file) { - $('.progress-inner', row).width((file.percentUploaded * 100) + '%'); - }); - - $('.progress-inner', totalRow).width((_files.percentUploaded * 100) + '%'); - }); - - up.on('uploadcomplete', function() { - $('.progress-inner').width('100%'); - $totalName.html('Grabbing URLs…'); - }); - - up.on('load', function(evt, response) { - var res = JSON.parse(response); - - switch (evt.target.status) { - case 200: - if (!res.success) { - $uploadFiles.addClass('error'); - $totalName.text(UPLOAD_ERR_FAILED); - break; - } - - eachRow(res.files, function(row, file) { - var $link = $('<a>'); - - $link.attr('href', file.url) - .attr('target', '_BLANK') - .text(file.url.replace('http://', '').replace('https://', '')); - - $('.file-url', row).append($link); - }); - - $uploadFiles.addClass('completed'); - $totalName.text('Done!'); - break; - case 413: - $uploadFiles.addClass('error completed'); - $totalName.html(UPLOAD_ERR_MAX_SIZE); - break; - default: - $uploadFiles.addClass('error completed'); - $totalName.text(UPLOAD_ERR_FAILED); - } - }); - - up.upload(); - }); -}); diff --git a/js/zepto.js b/js/zepto.js deleted file mode 100644 index ab89703..0000000 --- a/js/zepto.js +++ /dev/null @@ -1,1035 +0,0 @@ -/* Zepto v1.0-7-g579f376 - zepto event - zeptojs.com/license */ - - -var Zepto = (function() { - var undefined, key, $, classList, emptyArray = [], slice = emptyArray.slice, filter = emptyArray.filter, - document = window.document, - elementDisplay = {}, classCache = {}, - getComputedStyle = document.defaultView.getComputedStyle, - cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1,'opacity': 1, 'z-index': 1, 'zoom': 1 }, - fragmentRE = /^\s*<(\w+|!)[^>]*>/, - tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, - rootNodeRE = /^(?:body|html)$/i, - - // special attributes that should be get/set via method calls - methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'], - - adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ], - table = document.createElement('table'), - tableRow = document.createElement('tr'), - containers = { - 'tr': document.createElement('tbody'), - 'tbody': table, 'thead': table, 'tfoot': table, - 'td': tableRow, 'th': tableRow, - '*': document.createElement('div') - }, - readyRE = /complete|loaded|interactive/, - classSelectorRE = /^\.([\w-]+)$/, - idSelectorRE = /^#([\w-]*)$/, - tagSelectorRE = /^[\w-]+$/, - class2type = {}, - toString = class2type.toString, - zepto = {}, - camelize, uniq, - tempParent = document.createElement('div') - - zepto.matches = function(element, selector) { - if (!element || element.nodeType !== 1) return false - var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector || - element.oMatchesSelector || element.matchesSelector - if (matchesSelector) return matchesSelector.call(element, selector) - // fall back to performing a selector: - var match, parent = element.parentNode, temp = !parent - if (temp) (parent = tempParent).appendChild(element) - match = ~zepto.qsa(parent, selector).indexOf(element) - temp && tempParent.removeChild(element) - return match - } - - function type(obj) { - return obj == null ? String(obj) : - class2type[toString.call(obj)] || "object" - } - - function isFunction(value) { return type(value) == "function" } - function isWindow(obj) { return obj != null && obj == obj.window } - function isDocument(obj) { return obj != null && obj.nodeType == obj.DOCUMENT_NODE } - function isObject(obj) { return type(obj) == "object" } - function isPlainObject(obj) { - return isObject(obj) && !isWindow(obj) && obj.__proto__ == Object.prototype - } - function isArray(value) { return value instanceof Array } - function likeArray(obj) { return typeof obj.length == 'number' } - - function compact(array) { return filter.call(array, function(item){ return item != null }) } - function flatten(array) { return array.length > 0 ? $.fn.concat.apply([], array) : array } - camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) } - function dasherize(str) { - return str.replace(/::/g, '/') - .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') - .replace(/([a-z\d])([A-Z])/g, '$1_$2') - .replace(/_/g, '-') - .toLowerCase() - } - uniq = function(array){ return filter.call(array, function(item, idx){ return array.indexOf(item) == idx }) } - - function classRE(name) { - return name in classCache ? - classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)')) - } - - function maybeAddPx(name, value) { - return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value - } - - function defaultDisplay(nodeName) { - var element, display - if (!elementDisplay[nodeName]) { - element = document.createElement(nodeName) - document.body.appendChild(element) - display = getComputedStyle(element, '').getPropertyValue("display") - element.parentNode.removeChild(element) - display == "none" && (display = "block") - elementDisplay[nodeName] = display - } - return elementDisplay[nodeName] - } - - function children(element) { - return 'children' in element ? - slice.call(element.children) : - $.map(element.childNodes, function(node){ if (node.nodeType == 1) return node }) - } - - // `$.zepto.fragment` takes a html string and an optional tag name - // to generate DOM nodes nodes from the given html string. - // The generated DOM nodes are returned as an array. - // This function can be overriden in plugins for example to make - // it compatible with browsers that don't support the DOM fully. - zepto.fragment = function(html, name, properties) { - if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>") - if (name === undefined) name = fragmentRE.test(html) && RegExp.$1 - if (!(name in containers)) name = '*' - - var nodes, dom, container = containers[name] - container.innerHTML = '' + html - dom = $.each(slice.call(container.childNodes), function(){ - container.removeChild(this) - }) - if (isPlainObject(properties)) { - nodes = $(dom) - $.each(properties, function(key, value) { - if (methodAttributes.indexOf(key) > -1) nodes[key](value) - else nodes.attr(key, value) - }) - } - return dom - } - - // `$.zepto.Z` swaps out the prototype of the given `dom` array - // of nodes with `$.fn` and thus supplying all the Zepto functions - // to the array. Note that `__proto__` is not supported on Internet - // Explorer. This method can be overriden in plugins. - zepto.Z = function(dom, selector) { - dom = dom || [] - dom.__proto__ = $.fn - dom.selector = selector || '' - return dom - } - - // `$.zepto.isZ` should return `true` if the given object is a Zepto - // collection. This method can be overriden in plugins. - zepto.isZ = function(object) { - return object instanceof zepto.Z - } - - // `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and - // takes a CSS selector and an optional context (and handles various - // special cases). - // This method can be overriden in plugins. - zepto.init = function(selector, context) { - // If nothing given, return an empty Zepto collection - if (!selector) return zepto.Z() - // If a function is given, call it when the DOM is ready - else if (isFunction(selector)) return $(document).ready(selector) - // If a Zepto collection is given, juts return it - else if (zepto.isZ(selector)) return selector - else { - var dom - // normalize array if an array of nodes is given - if (isArray(selector)) dom = compact(selector) - // Wrap DOM nodes. If a plain object is given, duplicate it. - else if (isObject(selector)) - dom = [isPlainObject(selector) ? $.extend({}, selector) : selector], selector = null - // If it's a html fragment, create nodes from it - else if (fragmentRE.test(selector)) - dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null - // If there's a context, create a collection on that context first, and select - // nodes from there - else if (context !== undefined) return $(context).find(selector) - // And last but no least, if it's a CSS selector, use it to select nodes. - else dom = zepto.qsa(document, selector) - // create a new Zepto collection from the nodes found - return zepto.Z(dom, selector) - } - } - - // `$` will be the base `Zepto` object. When calling this - // function just call `$.zepto.init, which makes the implementation - // details of selecting nodes and creating Zepto collections - // patchable in plugins. - $ = function(selector, context){ - return zepto.init(selector, context) - } - - function extend(target, source, deep) { - for (key in source) - if (deep && (isPlainObject(source[key]) || isArray(source[key]))) { - if (isPlainObject(source[key]) && !isPlainObject(target[key])) - target[key] = {} - if (isArray(source[key]) && !isArray(target[key])) - target[key] = [] - extend(target[key], source[key], deep) - } - else if (source[key] !== undefined) target[key] = source[key] - } - - // Copy all but undefined properties from one or more - // objects to the `target` object. - $.extend = function(target){ - var deep, args = slice.call(arguments, 1) - if (typeof target == 'boolean') { - deep = target - target = args.shift() - } - args.forEach(function(arg){ extend(target, arg, deep) }) - return target - } - - // `$.zepto.qsa` is Zepto's CSS selector implementation which - // uses `document.querySelectorAll` and optimizes for some special cases, like `#id`. - // This method can be overriden in plugins. - zepto.qsa = function(element, selector){ - var found - return (isDocument(element) && idSelectorRE.test(selector)) ? - ( (found = element.getElementById(RegExp.$1)) ? [found] : [] ) : - (element.nodeType !== 1 && element.nodeType !== 9) ? [] : - slice.call( - classSelectorRE.test(selector) ? element.getElementsByClassName(RegExp.$1) : - tagSelectorRE.test(selector) ? element.getElementsByTagName(selector) : - element.querySelectorAll(selector) - ) - } - - function filtered(nodes, selector) { - return selector == null ? $(nodes) : $(nodes).filter(selector) - } - - $.contains = function(parent, node) { - return parent !== node && parent.contains(node) - } - - function funcArg(context, arg, idx, payload) { - return isFunction(arg) ? arg.call(context, idx, payload) : arg - } - - function setAttribute(node, name, value) { - value == null ? node.removeAttribute(name) : node.setAttribute(name, value) - } - - // access className property while respecting SVGAnimatedString - function className(node, value){ - var klass = node.className, - svg = klass && klass.baseVal !== undefined - - if (value === undefined) return svg ? klass.baseVal : klass - svg ? (klass.baseVal = value) : (node.className = value) - } - - // "true" => true - // "false" => false - // "null" => null - // "42" => 42 - // "42.5" => 42.5 - // JSON => parse if valid - // String => self - function deserializeValue(value) { - var num - try { - return value ? - value == "true" || - ( value == "false" ? false : - value == "null" ? null : - !isNaN(num = Number(value)) ? num : - /^[\[\{]/.test(value) ? $.parseJSON(value) : - value ) - : value - } catch(e) { - return value - } - } - - $.type = type - $.isFunction = isFunction - $.isWindow = isWindow - $.isArray = isArray - $.isPlainObject = isPlainObject - - $.isEmptyObject = function(obj) { - var name - for (name in obj) return false - return true - } - - $.inArray = function(elem, array, i){ - return emptyArray.indexOf.call(array, elem, i) - } - - $.camelCase = camelize - $.trim = function(str) { - return str == null ? "" : String.prototype.trim.call(str) - } - - // plugin compatibility - $.uuid = 0 - $.support = { } - $.expr = { } - - $.map = function(elements, callback){ - var value, values = [], i, key - if (likeArray(elements)) - for (i = 0; i < elements.length; i++) { - value = callback(elements[i], i) - if (value != null) values.push(value) - } - else - for (key in elements) { - value = callback(elements[key], key) - if (value != null) values.push(value) - } - return flatten(values) - } - - $.each = function(elements, callback){ - var i, key - if (likeArray(elements)) { - for (i = 0; i < elements.length; i++) - if (callback.call(elements[i], i, elements[i]) === false) return elements - } else { - for (key in elements) - if (callback.call(elements[key], key, elements[key]) === false) return elements - } - - return elements - } - - $.grep = function(elements, callback){ - return filter.call(elements, callback) - } - - if (window.JSON) $.parseJSON = JSON.parse - - // Populate the class2type map - $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { - class2type[ "[object " + name + "]" ] = name.toLowerCase() - }) - - // Define methods that will be available on all - // Zepto collections - $.fn = { - // Because a collection acts like an array - // copy over these useful array functions. - forEach: emptyArray.forEach, - reduce: emptyArray.reduce, - push: emptyArray.push, - sort: emptyArray.sort, - indexOf: emptyArray.indexOf, - concat: emptyArray.concat, - - // `map` and `slice` in the jQuery API work differently - // from their array counterparts - map: function(fn){ - return $($.map(this, function(el, i){ return fn.call(el, i, el) })) - }, - slice: function(){ - return $(slice.apply(this, arguments)) - }, - - ready: function(callback){ - if (readyRE.test(document.readyState)) callback($) - else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false) - return this - }, - get: function(idx){ - return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length] - }, - toArray: function(){ return this.get() }, - size: function(){ - return this.length - }, - remove: function(){ - return this.each(function(){ - if (this.parentNode != null) - this.parentNode.removeChild(this) - }) - }, - each: function(callback){ - emptyArray.every.call(this, function(el, idx){ - return callback.call(el, idx, el) !== false - }) - return this - }, - filter: function(selector){ - if (isFunction(selector)) return this.not(this.not(selector)) - return $(filter.call(this, function(element){ - return zepto.matches(element, selector) - })) - }, - add: function(selector,context){ - return $(uniq(this.concat($(selector,context)))) - }, - is: function(selector){ - return this.length > 0 && zepto.matches(this[0], selector) - }, - not: function(selector){ - var nodes=[] - if (isFunction(selector) && selector.call !== undefined) - this.each(function(idx){ - if (!selector.call(this,idx)) nodes.push(this) - }) - else { - var excludes = typeof selector == 'string' ? this.filter(selector) : - (likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector) - this.forEach(function(el){ - if (excludes.indexOf(el) < 0) nodes.push(el) - }) - } - return $(nodes) - }, - has: function(selector){ - return this.filter(function(){ - return isObject(selector) ? - $.contains(this, selector) : - $(this).find(selector).size() - }) - }, - eq: function(idx){ - return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1) - }, - first: function(){ - var el = this[0] - return el && !isObject(el) ? el : $(el) - }, - last: function(){ - var el = this[this.length - 1] - return el && !isObject(el) ? el : $(el) - }, - find: function(selector){ - var result, $this = this - if (typeof selector == 'object') - result = $(selector).filter(function(){ - var node = this - return emptyArray.some.call($this, function(parent){ - return $.contains(parent, node) - }) - }) - else if (this.length == 1) result = $(zepto.qsa(this[0], selector)) - else result = this.map(function(){ return zepto.qsa(this, selector) }) - return result - }, - closest: function(selector, context){ - var node = this[0], collection = false - if (typeof selector == 'object') collection = $(selector) - while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector))) - node = node !== context && !isDocument(node) && node.parentNode - return $(node) - }, - parents: function(selector){ - var ancestors = [], nodes = this - while (nodes.length > 0) - nodes = $.map(nodes, function(node){ - if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) { - ancestors.push(node) - return node - } - }) - return filtered(ancestors, selector) - }, - parent: function(selector){ - return filtered(uniq(this.pluck('parentNode')), selector) - }, - children: function(selector){ - return filtered(this.map(function(){ return children(this) }), selector) - }, - contents: function() { - return this.map(function() { return slice.call(this.childNodes) }) - }, - siblings: function(selector){ - return filtered(this.map(function(i, el){ - return filter.call(children(el.parentNode), function(child){ return child!==el }) - }), selector) - }, - empty: function(){ - return this.each(function(){ this.innerHTML = '' }) - }, - // `pluck` is borrowed from Prototype.js - pluck: function(property){ - return $.map(this, function(el){ return el[property] }) - }, - show: function(){ - return this.each(function(){ - this.style.display == "none" && (this.style.display = null) - if (getComputedStyle(this, '').getPropertyValue("display") == "none") - this.style.display = defaultDisplay(this.nodeName) - }) - }, - replaceWith: function(newContent){ - return this.before(newContent).remove() - }, - wrap: function(structure){ - var func = isFunction(structure) - if (this[0] && !func) - var dom = $(structure).get(0), - clone = dom.parentNode || this.length > 1 - - return this.each(function(index){ - $(this).wrapAll( - func ? structure.call(this, index) : - clone ? dom.cloneNode(true) : dom - ) - }) - }, - wrapAll: function(structure){ - if (this[0]) { - $(this[0]).before(structure = $(structure)) - var children - // drill down to the inmost element - while ((children = structure.children()).length) structure = children.first() - $(structure).append(this) - } - return this - }, - wrapInner: function(structure){ - var func = isFunction(structure) - return this.each(function(index){ - var self = $(this), contents = self.contents(), - dom = func ? structure.call(this, index) : structure - contents.length ? contents.wrapAll(dom) : self.append(dom) - }) - }, - unwrap: function(){ - this.parent().each(function(){ - $(this).replaceWith($(this).children()) - }) - return this - }, - clone: function(){ - return this.map(function(){ return this.cloneNode(true) }) - }, - hide: function(){ - return this.css("display", "none") - }, - toggle: function(setting){ - return this.each(function(){ - var el = $(this) - ;(setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide() - }) - }, - prev: function(selector){ return $(this.pluck('previousElementSibling')).filter(selector || '*') }, - next: function(selector){ return $(this.pluck('nextElementSibling')).filter(selector || '*') }, - html: function(html){ - return html === undefined ? - (this.length > 0 ? this[0].innerHTML : null) : - this.each(function(idx){ - var originHtml = this.innerHTML - $(this).empty().append( funcArg(this, html, idx, originHtml) ) - }) - }, - text: function(text){ - return text === undefined ? - (this.length > 0 ? this[0].textContent : null) : - this.each(function(){ this.textContent = text }) - }, - attr: function(name, value){ - var result - return (typeof name == 'string' && value === undefined) ? - (this.length == 0 || this[0].nodeType !== 1 ? undefined : - (name == 'value' && this[0].nodeName == 'INPUT') ? this.val() : - (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result - ) : - this.each(function(idx){ - if (this.nodeType !== 1) return - if (isObject(name)) for (key in name) setAttribute(this, key, name[key]) - else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name))) - }) - }, - removeAttr: function(name){ - return this.each(function(){ this.nodeType === 1 && setAttribute(this, name) }) - }, - prop: function(name, value){ - return (value === undefined) ? - (this[0] && this[0][name]) : - this.each(function(idx){ - this[name] = funcArg(this, value, idx, this[name]) - }) - }, - data: function(name, value){ - var data = this.attr('data-' + dasherize(name), value) - return data !== null ? deserializeValue(data) : undefined - }, - val: function(value){ - return (value === undefined) ? - (this[0] && (this[0].multiple ? - $(this[0]).find('option').filter(function(o){ return this.selected }).pluck('value') : - this[0].value) - ) : - this.each(function(idx){ - this.value = funcArg(this, value, idx, this.value) - }) - }, - offset: function(coordinates){ - if (coordinates) return this.each(function(index){ - var $this = $(this), - coords = funcArg(this, coordinates, index, $this.offset()), - parentOffset = $this.offsetParent().offset(), - props = { - top: coords.top - parentOffset.top, - left: coords.left - parentOffset.left - } - - if ($this.css('position') == 'static') props['position'] = 'relative' - $this.css(props) - }) - if (this.length==0) return null - var obj = this[0].getBoundingClientRect() - return { - left: obj.left + window.pageXOffset, - top: obj.top + window.pageYOffset, - width: Math.round(obj.width), - height: Math.round(obj.height) - } - }, - css: function(property, value){ - if (arguments.length < 2 && typeof property == 'string') - return this[0] && (this[0].style[camelize(property)] || getComputedStyle(this[0], '').getPropertyValue(property)) - - var css = '' - if (type(property) == 'string') { - if (!value && value !== 0) - this.each(function(){ this.style.removeProperty(dasherize(property)) }) - else - css = dasherize(property) + ":" + maybeAddPx(property, value) - } else { - for (key in property) - if (!property[key] && property[key] !== 0) - this.each(function(){ this.style.removeProperty(dasherize(key)) }) - else - css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';' - } - - return this.each(function(){ this.style.cssText += ';' + css }) - }, - index: function(element){ - return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0]) - }, - hasClass: function(name){ - return emptyArray.some.call(this, function(el){ - return this.test(className(el)) - }, classRE(name)) - }, - addClass: function(name){ - return this.each(function(idx){ - classList = [] - var cls = className(this), newName = funcArg(this, name, idx, cls) - newName.split(/\s+/g).forEach(function(klass){ - if (!$(this).hasClass(klass)) classList.push(klass) - }, this) - classList.length && className(this, cls + (cls ? " " : "") + classList.join(" ")) - }) - }, - removeClass: function(name){ - return this.each(function(idx){ - if (name === undefined) return className(this, '') - classList = className(this) - funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass){ - classList = classList.replace(classRE(klass), " ") - }) - className(this, classList.trim()) - }) - }, - toggleClass: function(name, when){ - return this.each(function(idx){ - var $this = $(this), names = funcArg(this, name, idx, className(this)) - names.split(/\s+/g).forEach(function(klass){ - (when === undefined ? !$this.hasClass(klass) : when) ? - $this.addClass(klass) : $this.removeClass(klass) - }) - }) - }, - scrollTop: function(){ - if (!this.length) return - return ('scrollTop' in this[0]) ? this[0].scrollTop : this[0].scrollY - }, - position: function() { - if (!this.length) return - - var elem = this[0], - // Get *real* offsetParent - offsetParent = this.offsetParent(), - // Get correct offsets - offset = this.offset(), - parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset() - - // Subtract element margins - // note: when an element has margin: auto the offsetLeft and marginLeft - // are the same in Safari causing offset.left to incorrectly be 0 - offset.top -= parseFloat( $(elem).css('margin-top') ) || 0 - offset.left -= parseFloat( $(elem).css('margin-left') ) || 0 - - // Add offsetParent borders - parentOffset.top += parseFloat( $(offsetParent[0]).css('border-top-width') ) || 0 - parentOffset.left += parseFloat( $(offsetParent[0]).css('border-left-width') ) || 0 - - // Subtract the two offsets - return { - top: offset.top - parentOffset.top, - left: offset.left - parentOffset.left - } - }, - offsetParent: function() { - return this.map(function(){ - var parent = this.offsetParent || document.body - while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static") - parent = parent.offsetParent - return parent - }) - } - } - - // for now - $.fn.detach = $.fn.remove - - // Generate the `width` and `height` functions - ;['width', 'height'].forEach(function(dimension){ - $.fn[dimension] = function(value){ - var offset, el = this[0], - Dimension = dimension.replace(/./, function(m){ return m[0].toUpperCase() }) - if (value === undefined) return isWindow(el) ? el['inner' + Dimension] : - isDocument(el) ? el.documentElement['offset' + Dimension] : - (offset = this.offset()) && offset[dimension] - else return this.each(function(idx){ - el = $(this) - el.css(dimension, funcArg(this, value, idx, el[dimension]())) - }) - } - }) - - function traverseNode(node, fun) { - fun(node) - for (var key in node.childNodes) traverseNode(node.childNodes[key], fun) - } - - // Generate the `after`, `prepend`, `before`, `append`, - // `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods. - adjacencyOperators.forEach(function(operator, operatorIndex) { - var inside = operatorIndex % 2 //=> prepend, append - - $.fn[operator] = function(){ - // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings - var argType, nodes = $.map(arguments, function(arg) { - argType = type(arg) - return argType == "object" || argType == "array" || arg == null ? - arg : zepto.fragment(arg) - }), - parent, copyByClone = this.length > 1 - if (nodes.length < 1) return this - - return this.each(function(_, target){ - parent = inside ? target : target.parentNode - - // convert all methods to a "before" operation - target = operatorIndex == 0 ? target.nextSibling : - operatorIndex == 1 ? target.firstChild : - operatorIndex == 2 ? target : - null - - nodes.forEach(function(node){ - if (copyByClone) node = node.cloneNode(true) - else if (!parent) return $(node).remove() - - traverseNode(parent.insertBefore(node, target), function(el){ - if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' && - (!el.type || el.type === 'text/javascript') && !el.src) - window['eval'].call(window, el.innerHTML) - }) - }) - }) - } - - // after => insertAfter - // prepend => prependTo - // before => insertBefore - // append => appendTo - $.fn[inside ? operator+'To' : 'insert'+(operatorIndex ? 'Before' : 'After')] = function(html){ - $(html)[operator](this) - return this - } - }) - - zepto.Z.prototype = $.fn - - // Export internal API functions in the `$.zepto` namespace - zepto.uniq = uniq - zepto.deserializeValue = deserializeValue - $.zepto = zepto - - return $ -})() - -window.Zepto = Zepto -'$' in window || (window.$ = Zepto) - -;(function($){ - var $$ = $.zepto.qsa, handlers = {}, _zid = 1, specialEvents={}, - hover = { mouseenter: 'mouseover', mouseleave: 'mouseout' } - - specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents' - - function zid(element) { - return element._zid || (element._zid = _zid++) - } - function findHandlers(element, event, fn, selector) { - event = parse(event) - if (event.ns) var matcher = matcherFor(event.ns) - return (handlers[zid(element)] || []).filter(function(handler) { - return handler - && (!event.e || handler.e == event.e) - && (!event.ns || matcher.test(handler.ns)) - && (!fn || zid(handler.fn) === zid(fn)) - && (!selector || handler.sel == selector) - }) - } - function parse(event) { - var parts = ('' + event).split('.') - return {e: parts[0], ns: parts.slice(1).sort().join(' ')} - } - function matcherFor(ns) { - return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)') - } - - function eachEvent(events, fn, iterator){ - if ($.type(events) != "string") $.each(events, iterator) - else events.split(/\s/).forEach(function(type){ iterator(type, fn) }) - } - - function eventCapture(handler, captureSetting) { - return handler.del && - (handler.e == 'focus' || handler.e == 'blur') || - !!captureSetting - } - - function realEvent(type) { - return hover[type] || type - } - - function add(element, events, fn, selector, getDelegate, capture){ - var id = zid(element), set = (handlers[id] || (handlers[id] = [])) - eachEvent(events, fn, function(event, fn){ - var handler = parse(event) - handler.fn = fn - handler.sel = selector - // emulate mouseenter, mouseleave - if (handler.e in hover) fn = function(e){ - var related = e.relatedTarget - if (!related || (related !== this && !$.contains(this, related))) - return handler.fn.apply(this, arguments) - } - handler.del = getDelegate && getDelegate(fn, event) - var callback = handler.del || fn - handler.proxy = function (e) { - var result = callback.apply(element, [e].concat(e.data)) - if (result === false) e.preventDefault(), e.stopPropagation() - return result - } - handler.i = set.length - set.push(handler) - element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) - }) - } - function remove(element, events, fn, selector, capture){ - var id = zid(element) - eachEvent(events || '', fn, function(event, fn){ - findHandlers(element, event, fn, selector).forEach(function(handler){ - delete handlers[id][handler.i] - element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture)) - }) - }) - } - - $.event = { add: add, remove: remove } - - $.proxy = function(fn, context) { - if ($.isFunction(fn)) { - var proxyFn = function(){ return fn.apply(context, arguments) } - proxyFn._zid = zid(fn) - return proxyFn - } else if (typeof context == 'string') { - return $.proxy(fn[context], fn) - } else { - throw new TypeError("expected function") - } - } - - $.fn.bind = function(event, callback){ - return this.each(function(){ - add(this, event, callback) - }) - } - $.fn.unbind = function(event, callback){ - return this.each(function(){ - remove(this, event, callback) - }) - } - $.fn.one = function(event, callback){ - return this.each(function(i, element){ - add(this, event, callback, null, function(fn, type){ - return function(){ - var result = fn.apply(element, arguments) - remove(element, type, fn) - return result - } - }) - }) - } - - var returnTrue = function(){return true}, - returnFalse = function(){return false}, - ignoreProperties = /^([A-Z]|layer[XY]$)/, - eventMethods = { - preventDefault: 'isDefaultPrevented', - stopImmediatePropagation: 'isImmediatePropagationStopped', - stopPropagation: 'isPropagationStopped' - } - function createProxy(event) { - var key, proxy = { originalEvent: event } - for (key in event) - if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key] - - $.each(eventMethods, function(name, predicate) { - proxy[name] = function(){ - this[predicate] = returnTrue - return event[name].apply(event, arguments) - } - proxy[predicate] = returnFalse - }) - return proxy - } - - // emulates the 'defaultPrevented' property for browsers that have none - function fix(event) { - if (!('defaultPrevented' in event)) { - event.defaultPrevented = false - var prevent = event.preventDefault - event.preventDefault = function() { - this.defaultPrevented = true - prevent.call(this) - } - } - } - - $.fn.delegate = function(selector, event, callback){ - return this.each(function(i, element){ - add(element, event, callback, selector, function(fn){ - return function(e){ - var evt, match = $(e.target).closest(selector, element).get(0) - if (match) { - evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element}) - return fn.apply(match, [evt].concat([].slice.call(arguments, 1))) - } - } - }) - }) - } - $.fn.undelegate = function(selector, event, callback){ - return this.each(function(){ - remove(this, event, callback, selector) - }) - } - - $.fn.live = function(event, callback){ - $(document.body).delegate(this.selector, event, callback) - return this - } - $.fn.die = function(event, callback){ - $(document.body).undelegate(this.selector, event, callback) - return this - } - - $.fn.on = function(event, selector, callback){ - return !selector || $.isFunction(selector) ? - this.bind(event, selector || callback) : this.delegate(selector, event, callback) - } - $.fn.off = function(event, selector, callback){ - return !selector || $.isFunction(selector) ? - this.unbind(event, selector || callback) : this.undelegate(selector, event, callback) - } - - $.fn.trigger = function(event, data){ - if (typeof event == 'string' || $.isPlainObject(event)) event = $.Event(event) - fix(event) - event.data = data - return this.each(function(){ - // items in the collection might not be DOM elements - // (todo: possibly support events on plain old objects) - if('dispatchEvent' in this) this.dispatchEvent(event) - }) - } - - // triggers event handlers on current element just as if an event occurred, - // doesn't trigger an actual event, doesn't bubble - $.fn.triggerHandler = function(event, data){ - var e, result - this.each(function(i, element){ - e = createProxy(typeof event == 'string' ? $.Event(event) : event) - e.data = data - e.target = element - $.each(findHandlers(element, event.type || event), function(i, handler){ - result = handler.proxy(e) - if (e.isImmediatePropagationStopped()) return false - }) - }) - return result - } - - // shortcut methods for `.bind(event, fn)` for each event type - ;('focusin focusout load resize scroll unload click dblclick '+ - 'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave '+ - 'change select keydown keypress keyup error').split(' ').forEach(function(event) { - $.fn[event] = function(callback) { - return callback ? - this.bind(event, callback) : - this.trigger(event) - } - }) - - ;['focus', 'blur'].forEach(function(name) { - $.fn[name] = function(callback) { - if (callback) this.bind(name, callback) - else this.each(function(){ - try { this[name]() } - catch(e) {} - }) - return this - } - }) - - $.Event = function(type, props) { - if (typeof type != 'string') props = type, type = props.type - var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true - if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name]) - event.initEvent(type, bubbles, true, null, null, null, null, null, null, null, null, null, null, null, null) - event.isDefaultPrevented = function(){ return this.defaultPrevented } - return event - } - -})(Zepto) diff --git a/package.json b/package.json index c3aa3b8..21894d7 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "Eric Johansson <neku@pomf.se>", "Peter Lejeck <peter.lejeck@gmail.com>", "cenci0 <alchimist94@gmail.com>", - "Juuso Lapinlampi <wub@partyvan.eu>" + "Juuso Lapinlampi <wub@partyvan.eu>", + "Luminarys <postmaster@gensok.io>" ], "license": "MIT", "bugs": { diff --git a/pages/banners.swig b/pages/banners.swig index e8f0cbd..ce25555 100644 --- a/pages/banners.swig +++ b/pages/banners.swig @@ -19,6 +19,11 @@ 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. #} +{# +This template is deprecated since version 2.1.0. It may be removed in a future +release. +#} + {% for banner in banners %} {% include banner %} {% endfor %} diff --git a/pages/banners/donate.swig b/pages/banners/donate.swig index f4706c9..6a9b73b 100644 --- a/pages/banners/donate.swig +++ b/pages/banners/donate.swig @@ -19,6 +19,11 @@ 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. #} +{# +This template is deprecated since version 2.1.0. It may be removed in a future +release. +#} + <p class="alert alert-info"> <strong>pomf.se is a free service, but our hosting bill isn't</strong> — we need almost $100 a month for that. Donations help keep pomf.se free, online, and fast. Everything (after fees) goes to the hosting bill. <span class="donate-btns"> diff --git a/pages/banners/virusscans.swig b/pages/banners/virusscans.swig index 4a3d5a8..16a3608 100644 --- a/pages/banners/virusscans.swig +++ b/pages/banners/virusscans.swig @@ -19,6 +19,11 @@ 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. #} +{# +This template is deprecated since version 2.1.0. It may be removed in a future +release. +#} + <p class="alert alert-info"> <strong>Virus scans are now a daily routine</strong> — if a file gets detected it will be removed without further notice. </p> diff --git a/pages/index.swig b/pages/index.swig index ff7db42..d9eb263 100644 --- a/pages/index.swig +++ b/pages/index.swig @@ -30,7 +30,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. {% include "check_fileapi.swig" %} {% include "upload_form.swig" %} </div> -{% include "banners.swig" %} +{% include "banners.swig" %} {# Deprecated since version 2.1.0. #} {% include "nav.swig" %} <script src="pomf.min.js"></script> {% endblock %} diff --git a/pages/layout.swig b/pages/layout.swig index 3eb9eff..348a7c9 100644 --- a/pages/layout.swig +++ b/pages/layout.swig @@ -1,6 +1,6 @@ {# Copyright (c) 2013 Peter Lejeck <peter.lejeck@gmail.com> -Copyright (c) 2015 the Pantsu.cat developers <hostmaster@pantsu.cat> +Copyright (c) 2015, 2016 the Pantsu.cat developers <hostmaster@pantsu.cat> 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 @@ -25,6 +25,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="generator" content="Pomf {{pkgVersion}}"> <title>Pantsu.cat · {% block title %}Kawaii File Hosting{% endblock %}</title> <link rel="icon" href="favicon.ico"> <link rel="stylesheet" href="pomf.min.css"> diff --git a/pages/nojs.swig b/pages/nojs.swig index 9facb91..5552b11 100644 --- a/pages/nojs.swig +++ b/pages/nojs.swig @@ -28,6 +28,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. <p class="lead">Max upload size is {{max_upload_size}}MiB, read the <a href="faq.html">FAQ</a></p> {% include "upload_form_nojs.swig" %} </div> -{% include "banners.swig" %} +{% include "banners.swig" %} {# Deprecated since version 2.1.0. #} {% include "nav.swig" %} {% endblock %} @@ -13,7 +13,7 @@ CREATE TABLE `files` ( `filename` varchar(30) default NULL, `size` int(10) unsigned default NULL, `date` date default NULL, - `expire` date default NULL, + `expire` date default NULL, -- Deprecated since version 2.1.0 `delid` char(40) default NULL, `user` int(10) unsigned default '0', `dir` int(2) default '0', diff --git a/static/classes/Response.class.php b/static/classes/Response.class.php index 27dbd74..081baa6 100755 --- a/static/classes/Response.class.php +++ b/static/classes/Response.class.php @@ -1,8 +1,11 @@ <?php /** - * Copyright (c) 2015 cenci0 <alchimist94@gmail.com> - * Copyright (c) 2015, 2016 the Pantsu.cat developers <hostmaster@pantsu.cat> + * Prints file upload API responses depending on desired response type format. + * + * @copyright Copyright (c) 2015 cenci0 <alchimist94@gmail.com> + * @copyright Copyright (c) 2015, 2016 the Pantsu.cat developers + * <hostmaster@pantsu.cat> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,10 +26,30 @@ * SOFTWARE. */ +/** + * The Response class is a do-it-all for getting responses out in different + * formats. + * + * @todo Create sub-classes to split and extend this god object. + */ class Response { + /** + * Indicates response type used for routing. + * + * Valid strings are 'csv', 'html', 'json' and 'text'. + * + * @var string $type Response type + */ private $type; + /** + * Indicates requested response type. + * + * Valid strings are 'csv', 'html', 'json', 'gyazo' and 'text'. + * + * @param string|null $response_type Response type + */ public function __construct($response_type = null) { switch ($response_type) { @@ -59,6 +82,13 @@ class Response } } + /** + * Routes error messages depending on response type. + * + * @param int $code HTTP status code number. + * @param int $desc Descriptive error message. + * @return void + */ public function error($code, $desc) { $response = null; @@ -82,6 +112,12 @@ class Response echo $response; } + /** + * Routes success messages depending on response type. + * + * @param mixed[] $files + * @return void + */ public function send($files) { $response = null; @@ -101,15 +137,29 @@ class Response break; } - http_response_code(200); + http_response_code(200); // "200 OK". Success. echo $response; } + /** + * Indicates with CSV body the request was invalid. + * + * @deprecated 2.1.0 Will be renamed to camelCase format. + * @param int $description Descriptive error message. + * @return string Error message in CSV format. + */ private static function csv_error($description) { return '"error"'."\r\n"."\"$description\""."\r\n"; } + /** + * Indicates with CSV body the request was successful. + * + * @deprecated 2.1.0 Will be renamed to camelCase format. + * @param mixed[] $files + * @return string Success message in CSV format. + */ private static function csv_success($files) { $result = '"name","url","hash","size"'."\r\n"; @@ -123,11 +173,26 @@ class Response return $result; } + /** + * Indicates with HTML body the request was invalid. + * + * @deprecated 2.1.0 Will be renamed to camelCase format. + * @param int $code HTTP status code number. + * @param int $description Descriptive error message. + * @return string Error message in HTML format. + */ private static function html_error($code, $description) { return '<p>ERROR: ('.$code.') '.$description.'</p>'; } + /** + * Indicates with HTML body the request was successful. + * + * @deprecated 2.1.0 Will be renamed to camelCase format. + * @param mixed[] $files + * @return string Success message in HTML format. + */ private static function html_success($files) { $result = ''; @@ -139,6 +204,14 @@ class Response return $result; } + /** + * Indicates with JSON body the request was invalid. + * + * @deprecated 2.1.0 Will be renamed to camelCase format. + * @param int $code HTTP status code number. + * @param int $description Descriptive error message. + * @return string Error message in pretty-printed JSON format. + */ private static function json_error($code, $description) { return json_encode(array( @@ -148,6 +221,13 @@ class Response ), JSON_PRETTY_PRINT); } + /** + * Indicates with JSON body the request was successful. + * + * @deprecated 2.1.0 Will be renamed to camelCase format. + * @param mixed[] $files + * @return string Success message in pretty-printed JSON format. + */ private static function json_success($files) { return json_encode(array( @@ -156,11 +236,26 @@ class Response ), JSON_PRETTY_PRINT); } + /** + * Indicates with plain text body the request was invalid. + * + * @deprecated 2.1.0 Will be renamed to camelCase format. + * @param int $code HTTP status code number. + * @param int $description Descriptive error message. + * @return string Error message in plain text format. + */ private static function text_error($code, $description) { return 'ERROR: ('.$code.') '.$description; } + /** + * Indicates with plain text body the request was successful. + * + * @deprecated 2.1.0 Will be renamed to camelCase format. + * @param mixed[] $files + * @return string Success message in plain text format. + */ private static function text_success($files) { $result = ''; diff --git a/static/classes/UploadException.class.php b/static/classes/UploadException.class.php index dbe47eb..7740ee6 100755 --- a/static/classes/UploadException.class.php +++ b/static/classes/UploadException.class.php @@ -1,6 +1,11 @@ <?php /** + * Returns a human readable error description for file upload errors. + * + * @author Dan Brown <danbrown@php.net> + * @author Michiel Thalen + * @copyright * -------------------------------------------------------------------- * The PHP License, version 3.01 * Copyright (c) 1999 - 2014 The PHP Group. All rights reserved. @@ -71,9 +76,6 @@ * <http://www.zend.com>. */ -// Exception class extension from: http://php.net/manual/en/features.file-upload.errors.php#89374 -// Handles files error returning a proper error description - class UploadException extends Exception { public function __construct($code) diff --git a/static/classes/UploadedFile.class.php b/static/classes/UploadedFile.class.php index ac39646..ccbaea8 100644 --- a/static/classes/UploadedFile.class.php +++ b/static/classes/UploadedFile.class.php @@ -1,7 +1,11 @@ <?php /** - * Copyright (c) 2013, 2014 Peter Lejeck <peter.lejeck@gmail.com> + * Returns information about uploaded files. + * + * @copyright Copyright (c) 2013, 2014 Peter Lejeck <peter.lejeck@gmail.com> + * @copyright Copyright (c) 2016 the Pantsu.cat developers + * <hostmaster@pantsu.cat> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,13 +35,19 @@ class UploadedFile public $tempfile; public $error; - /* Cached checksums */ + /** + * SHA-1 checksum + * + * @deprecated 2.1.0 To be replaced with SHA-256 hashing. + * @var string 40 digit hexadecimal hash (160 bits) + */ private $sha1; /** - * Generates the SHA1 or returns the cached SHA1 hash for the file. + * Generates the SHA-1 or returns the cached SHA-1 hash for the file. * - * @return string + * @deprecated 2.1.0 To be replaced with SHA-256 hashing. + * @return string|false $sha1 */ public function get_sha1() { diff --git a/static/includes/database.inc.php b/static/includes/database.inc.php index 0374afa..7a1d4f9 100644 --- a/static/includes/database.inc.php +++ b/static/includes/database.inc.php @@ -1,7 +1,9 @@ <?php /** - * Copyright (c) 2013 Peter Lejeck <peter.lejeck@gmail.com> + * Prepares a PDO connection between Pomf and a database server. + * + * @copyright Copyright (c) 2013 Peter Lejeck <peter.lejeck@gmail.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,7 +24,7 @@ * SOFTWARE. */ -include_once 'settings.inc.php'; +require_once 'settings.inc.php'; /* NOTE: we don't have to unref the PDO because we're not long-running */ $db = new PDO(POMF_DB_CONN, POMF_DB_USER, POMF_DB_PASS); diff --git a/static/includes/settings.inc.php b/static/includes/settings.inc.php index 6072249..872fde0 100644 --- a/static/includes/settings.inc.php +++ b/static/includes/settings.inc.php @@ -1,9 +1,12 @@ <?php /** - * Copyright (c) 2013, 2014 Peter Lejeck <peter.lejeck@gmail.com> - * Copyright (c) 2015 cenci0 <alchimist94@gmail.com> - * Copyright (c) 2015 the Pantsu.cat developers <hostmaster@pantsu.cat> + * User configurable settings for Pomf. + * + * @copyright Copyright (c) 2013, 2014 Peter Lejeck <peter.lejeck@gmail.com> + * @copyright Copyright (c) 2015 cenci0 <alchimist94@gmail.com> + * @copyright Copyright (c) 2015, 2016 the Pantsu.cat developers + * <hostmaster@pantsu.cat> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,22 +27,70 @@ * SOFTWARE. */ -// PDO socket -// mysql.sock path can be different from /tmp/mysql.sock, see /etc/my.cnf +/** + * PDO connection socket + * + * Database connection to use for communication. Currently, MySQL is the only + * DSN prefix supported. + * + * @see http://php.net/manual/en/ref.pdo-mysql.connection.php PHP manual for + * PDO_MYSQL DSN. + * @param string POMF_DB_CONN DSN:host|unix_socket=hostname|path;dbname=database + */ define('POMF_DB_CONN', 'mysql:unix_socket=/tmp/mysql.sock;dbname=pomf'); -// MySQL user and password + +/** + * PDO database login credentials + */ + +/** @param string POMF_DB_NAME Database username */ define('POMF_DB_USER', 'pomf'); +/** @param string POMF_DB_PASS Database password */ define('POMF_DB_PASS', '***'); -// Root location of files +/** + * File system location where to store uploaded files + * + * @param string Path to directory with trailing delimiter + */ define('POMF_FILES_ROOT', '/mnt/pantsu/http/files/'); -// Maximum number of iterations while generating a new filename + +/** + * Maximum number of iterations while generating a new filename + * + * Pomf uses an algorithm to generate random filenames. Sometimes a file may + * exist under a randomly generated filename, so we count tries and keep trying. + * If this value is exceeded, we give up trying to generate a new filename. + * + * @param int POMF_FILES_RETRIES Number of attempts to retry + */ define('POMF_FILES_RETRIES', 15); -// Number of random characters to use in a new filename + +/** + * The length of generated filename (without file extension) + * + * @param int POMF_FILES_LENGTH Number of random alphabetical ASCII characters + * to use + */ define('POMF_FILES_LENGTH', 6); -// URL to prepend to output (include trailing slash) + +/** + * URI to prepend to links for uploaded files + * + * @param string POMF_URL URI with trailing delimiter + */ define('POMF_URL', 'https://i.pantsu.cat/'); +/** + * Double dot file extensions + * + * Pomf keeps the last file extension for the uploaded file. In other words, an + * uploaded file with `.tar.gz` extension will be given a random filename which + * ends in `.gz` unless configured here to ignore discards for `.tar.gz`. + * + * @param string[] $doubledots Array of double dot file extensions strings + * without the first prefixing dot + */ $doubledots = array_map('strrev', array( 'tar.gz', 'tar.bz', diff --git a/static/upload.php b/static/upload.php index a37c549..ebedeab 100644 --- a/static/upload.php +++ b/static/upload.php @@ -1,10 +1,14 @@ <?php /** - * Copyright (c) 2013, 2014 Peter Lejeck <peter.lejeck@gmail.com> - * Copyright (c) 2013, 2014, 2015 Eric Johansson <neku@pomf.se> - * Copyright (c) 2015 cenci0 <alchimist94@gmail.com> - * Copyright (c) 2015 the Pantsu.cat developers <hostmaster@pantsu.cat> + * Handles POST uploads, generates filenames, moves files around and commits + * uploaded metadata to database. + * + * @copyright Copyright (c) 2013, 2014 Peter Lejeck <peter.lejeck@gmail.com> + * @copyright Copyright (c) 2013, 2014, 2015 Eric Johansson <neku@pomf.se> + * @copyright Copyright (c) 2015 cenci0 <alchimist94@gmail.com> + * @copyright Copyright (c) 2015, 2016 the Pantsu.cat developers + * <hostmaster@pantsu.cat> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,16 +30,17 @@ */ // Check if we can compress our output; if we can, we'll do it +// This feature is deprecated since version 2.1.0. if (ini_get('zlib.output_compression') !== 'Off' && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) { ob_start('ob_gzhandler'); } -include_once 'classes/Response.class.php'; -include_once 'classes/UploadException.class.php'; -include_once 'classes/UploadedFile.class.php'; -include_once 'includes/database.inc.php'; +require_once 'classes/Response.class.php'; +require_once 'classes/UploadException.class.php'; +require_once 'classes/UploadedFile.class.php'; +require_once 'includes/database.inc.php'; /** * Generates a random name for the file, retrying until we get an unused one. @@ -65,7 +70,8 @@ function generate_name($file) do { // Iterate until we reach the maximum number of retries if ($tries-- === 0) { - throw new Exception('Gave up trying to find an unused name', 500); + throw new Exception('Gave up trying to find an unused name', + 500); // HTTP status code "500 Internal Server Error" } $chars = range('a', 'z'); @@ -128,37 +134,45 @@ function upload_file($file) // Generate a name for the file $newname = generate_name($file); + // Store the file's full file path in memory + $uploadDir = POMF_FILES_ROOT; + $uploadFile = $uploadDir . $newname; + // Attempt to move it to the static directory - if (move_uploaded_file($file->tempfile, POMF_FILES_ROOT.$newname)) { - // Need to change permissions for the new file to make it world readable - if (chmod(POMF_FILES_ROOT.$newname, 0644)) { - // Add it to the database - $q = $db->prepare('INSERT INTO files (hash, originalname, filename, size, date, '. - 'expire, delid) VALUES (:hash, :orig, :name, :size, :date, '. - ':exp, :del)'); - - // Common parameters binding - $q->bindValue(':hash', $file->get_sha1(), PDO::PARAM_STR); - $q->bindValue(':orig', strip_tags($file->name), PDO::PARAM_STR); - $q->bindValue(':name', $newname, PDO::PARAM_STR); - $q->bindValue(':size', $file->size, PDO::PARAM_INT); - $q->bindValue(':date', date('Y-m-d'), PDO::PARAM_STR); - $q->bindValue(':exp', null, PDO::PARAM_STR); - $q->bindValue(':del', sha1($file->tempfile), PDO::PARAM_STR); - $q->execute(); - - return array( - 'hash' => $file->get_sha1(), - 'name' => $file->name, - 'url' => POMF_URL.$newname, - 'size' => $file->size, - ); - } else { - throw new Exception('Failed to change file permissions', 500); - } - } else { - throw new Exception('Failed to move file to destination', 500); + if (!move_uploaded_file($file->tempfile, $uploadFile)) { + throw new Exception('Failed to move file to destination', + 500); // HTTP status code "500 Internal Server Error" + } + + // Need to change permissions for the new file to make it world readable + if (!chmod($uploadFile, 0644)) { + throw new Exception('Failed to change file permissions', + 500); // HTTP status code "500 Internal Server Error" } + + // Add it to the database + // 'expire' support is deprecated since version 2.1.0. It may be + // removed in a future release. + $q = $db->prepare('INSERT INTO files (hash, originalname, filename, size, date, '. + 'expire, delid) VALUES (:hash, :orig, :name, :size, :date, '. + ':exp, :del)'); + + // Common parameters binding + $q->bindValue(':hash', $file->get_sha1(), PDO::PARAM_STR); + $q->bindValue(':orig', strip_tags($file->name), PDO::PARAM_STR); + $q->bindValue(':name', $newname, PDO::PARAM_STR); + $q->bindValue(':size', $file->size, PDO::PARAM_INT); + $q->bindValue(':date', date('Y-m-d'), PDO::PARAM_STR); + $q->bindValue(':exp', null, PDO::PARAM_STR); // Deprecated since version 2.1.0 + $q->bindValue(':del', sha1($file->tempfile), PDO::PARAM_STR); + $q->execute(); + + return array( + 'hash' => $file->get_sha1(), + 'name' => $file->name, + 'url' => POMF_URL.$newname, + 'size' => $file->size, + ); } /** @@ -201,7 +215,7 @@ function refiles($files) $f->tempfile = $file['tmp_name']; $f->error = $file['error']; // 'expire' doesn't exist neither in $_FILES nor in UploadedFile; - // commented out for future implementation + // it was never implemented and has been deprecated since version 2.1.0. //$f->expire = $file['expire']; $result[] = $f; } |
