| @@ -0,0 +1,34 @@ | |||
| RewriteEngine on | |||
| RewriteBase /textfiles/ | |||
| # Send requests without parameters to textfiles.php. | |||
| # RewriteRule ^$ textfiles.php [L] | |||
| # Send requests for index.php to textfiles.php. | |||
| # RewriteRule ^index\.php$ textfiles.php [L] | |||
| # Send requests for index.html to textfiles.php. | |||
| # RewriteRule ^index\.html$ textfiles.php [L] | |||
| # Send requests for textfiles.css to textfiles.css. | |||
| RewriteRule ^textfiles\.css$ textfiles.css [L] | |||
| # Send requests for textfiles.js to textfiles.js. | |||
| RewriteRule ^textfiles\.js$ textfiles.js [L] | |||
| # Send requests for textfiles.php to textfiles.php. | |||
| RewriteRule ^textfiles\.php$ textfiles.php [L] | |||
| # Send requests for textfiles.html to textfiles.php. | |||
| # NOTE: THIS IS NOT A TYPO! | |||
| RewriteRule ^textfiles\.html$ textfiles.php [L] | |||
| # Send requests for files that exist to textfiles.php (raw). | |||
| RewriteCond %{REQUEST_URI} ^\/textfiles\/(.+)/raw$ | |||
| RewriteCond /file/path/to/your/textfiles/%1 -f | |||
| RewriteRule ^(.+)/raw$ textfiles.php?f=$1&m=raw [QSA,L] | |||
| # Send requests for files that exist to textfiles.php. | |||
| RewriteCond %{REQUEST_URI} ^\/textfiles\/(.+)$ | |||
| RewriteCond /file/path/to/your/textfiles/%1 -f | |||
| RewriteRule ^(.+)$ textfiles.php?f=$1 [QSA,L] | |||
| @@ -0,0 +1,153 @@ | |||
| html { | |||
| box-sizing: border-box; | |||
| font-size: 20px; | |||
| } | |||
| *, *::before, *::after { | |||
| box-sizing: inherit; | |||
| } | |||
| html, body { | |||
| margin: 0; | |||
| padding: 0; | |||
| } | |||
| .compensator { | |||
| width: 15rem; | |||
| height: 4.5rem; | |||
| float: right; | |||
| pointer-events: none; | |||
| } | |||
| @media only screen and (max-width: 520px) { | |||
| .compensator { | |||
| display: none; | |||
| } | |||
| } | |||
| pre { | |||
| font-family: Inconsolata, monospace; | |||
| -moz-tab-size: 4; | |||
| tab-size: 4; | |||
| white-space: pre-wrap; | |||
| overflow-wrap: break-word; | |||
| padding: 0.5em; | |||
| } | |||
| #ui-elements-container { | |||
| position: fixed; | |||
| top: 0; | |||
| left: 0; | |||
| width: 100%; | |||
| height: 100vh; | |||
| pointer-events: none; | |||
| } | |||
| #ui-elements-container > * { | |||
| pointer-events: auto; | |||
| } | |||
| #controls { | |||
| position: absolute; | |||
| top: 0; | |||
| right: 0; | |||
| -webkit-user-select: none; | |||
| -moz-user-select: none; | |||
| user-select: none; | |||
| } | |||
| @media only screen and (max-width: 520px) { | |||
| #controls { | |||
| display: flex; | |||
| flex-flow: row-reverse; | |||
| align-items: flex-end; | |||
| width: 100%; | |||
| top: unset; | |||
| bottom: 0; | |||
| } | |||
| } | |||
| #controls .buttons { | |||
| display: flex; | |||
| flex-flow: row-reverse wrap; | |||
| background-color: #fff; | |||
| padding: 1px; | |||
| } | |||
| @media only screen and (max-width: 520px) { | |||
| #controls .buttons { | |||
| flex-flow: column; | |||
| } | |||
| } | |||
| #controls .button { | |||
| background-color: #e00; | |||
| font-size: 1.5em; | |||
| display: flex; | |||
| flex-flow: column; | |||
| justify-content: center; | |||
| align-items: center; | |||
| width: 3.5rem; | |||
| height: 3.5rem; | |||
| text-decoration: none; | |||
| font-family: Font Awesome; | |||
| color: #fff; | |||
| padding: 1px 0 0 0; | |||
| border: none; | |||
| -webkit-appearance: none; | |||
| -moz-appearance: none; | |||
| cursor: pointer; | |||
| } | |||
| #controls .button + .button { | |||
| margin: 0 1px 0 0; | |||
| } | |||
| @media only screen and (max-width: 520px) { | |||
| #controls .button + .button { | |||
| margin: 1px 0 0 0; | |||
| } | |||
| } | |||
| @media only screen and (hover:hover) and (pointer:fine) { | |||
| #controls .button:hover { | |||
| color: gold; | |||
| } | |||
| } | |||
| #controls .button:active { | |||
| transform: scale(0.975); | |||
| } | |||
| #controls .button:focus { | |||
| outline: none; | |||
| } | |||
| #controls .button::after { | |||
| content: attr(data-label); | |||
| display: block; | |||
| font-family: sans-serif; | |||
| font-size: 0.5rem; | |||
| font-weight: bold; | |||
| margin: 6px 0 0 0; | |||
| text-transform: uppercase; | |||
| } | |||
| #controls .message { | |||
| display: block; | |||
| margin: 1px 0 0 0; | |||
| padding: 0.25em 0.5em; | |||
| text-align: center; | |||
| font-family: sans-serif; | |||
| font-size: 0.75em; | |||
| color: #b00; | |||
| } | |||
| #controls .message:not(:empty) { | |||
| background-color: #fff; | |||
| } | |||
| @media only screen and (max-width: 520px) { | |||
| #controls .message { | |||
| margin: 0 0 1px 1px; | |||
| flex: 1 1 100%; | |||
| padding: 0.5em 0.75em; | |||
| font-size: 0.875em; | |||
| } | |||
| #controls .message:not(:empty) { | |||
| outline: 1px solid #fff; | |||
| border: 1px solid #e00; | |||
| } | |||
| } | |||
| #scratchpad { | |||
| opacity: 0; | |||
| z-index: -1; | |||
| pointer-events: none; | |||
| } | |||
| @@ -0,0 +1,93 @@ | |||
| <?php | |||
| $filename = $_GET['f']; | |||
| $stylesheet = "textfiles.css"; | |||
| $stylesheet_href = $stylesheet . "?v=" . filemtime($stylesheet); | |||
| ?> | |||
| <html> | |||
| <head> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||
| <link href='https://your.web.site/FontAwesome.css' type='text/css' rel='stylesheet' /> | |||
| <link href='<?php echo $stylesheet_href; ?>' type='text/css' rel='stylesheet' /> | |||
| <script type='text/javascript' src='textfiles.js'></script> | |||
| </head> | |||
| <body> | |||
| <div class='compensator'> | |||
| </div> | |||
| <pre> | |||
| <?php echo htmlentities(file_get_contents($filename)); ?> | |||
| </pre> | |||
| <div id='ui-elements-container'> | |||
| <div id='controls'> | |||
| <div class='buttons'> | |||
| <button | |||
| type='button' | |||
| class='button copy-raw-link' | |||
| title='Copy raw link to clipboard [k]' | |||
| data-label='Raw link' | |||
| data-main-action='copyRawLinkButtonClicked' | |||
| data-success-message='Raw file URL copied to clipboard.' | |||
| accesskey='k' | |||
| ></button> | |||
| <a | |||
| class='button raw' | |||
| title='View raw file [r]' | |||
| data-label='View raw' | |||
| accesskey='r' | |||
| href='<?php echo str_replace('.txt', '', $filename); ?>/raw' | |||
| ></a> | |||
| <button | |||
| type='button' | |||
| class='button copy-text' | |||
| title='Copy text to clipboard [c]' | |||
| data-label='Copy text' | |||
| data-main-action='copyTextButtonClicked' | |||
| data-success-message='Text copied to clipboard.' | |||
| accesskey='c' | |||
| ></button> | |||
| <button | |||
| type='button' | |||
| class='button copy-link' | |||
| title='Copy link to clipboard [l]' | |||
| data-label='Copy link' | |||
| data-main-action='copyLinkButtonClicked' | |||
| data-success-message='URL copied to clipboard.' | |||
| accesskey='l' | |||
| ></button> | |||
| </div> | |||
| <span class='message'></span> | |||
| </div> | |||
| <textarea id='scratchpad'></textarea> | |||
| </div> | |||
| </body> | |||
| <script type='text/javascript'> | |||
| copyLinkButtonClicked = () => { | |||
| copyTextToClipboard(location); | |||
| }; | |||
| copyRawLinkButtonClicked = () => { | |||
| copyTextToClipboard(location + "/raw"); | |||
| }; | |||
| copyTextButtonClicked = () => { | |||
| selectElementContents(document.querySelector("pre")); | |||
| document.execCommand("copy"); | |||
| }; | |||
| document.querySelectorAll("#controls button").forEach(button => { | |||
| button.addActivateEvent((event) => { | |||
| event.target.blur(); | |||
| window[event.target.dataset["mainAction"]](); | |||
| setMessage(event.target.dataset["successMessage"]); | |||
| }); | |||
| }); | |||
| setTimeout(() => { | |||
| selectElementContents(document.querySelector("pre")); | |||
| }, 0); | |||
| </script> | |||
| </html> | |||
| @@ -0,0 +1,46 @@ | |||
| /*******************************/ | |||
| /* EVENT LISTENER MANIPULATION */ | |||
| /*******************************/ | |||
| /* Adds an event listener to a button (or other clickable element), attaching | |||
| it to both "click" and "keyup" events (for use with keyboard navigation). | |||
| Optionally also attaches the listener to the 'mousedown' event, making the | |||
| element activate on mouse down instead of mouse up. */ | |||
| Element.prototype.addActivateEvent = function(func, includeMouseDown) { | |||
| let ael = this.activateEventListener = (event) => { if (event.button === 0 || event.key === ' ') func(event) }; | |||
| if (includeMouseDown) this.addEventListener("mousedown", ael); | |||
| this.addEventListener("click", ael); | |||
| this.addEventListener("keyup", ael); | |||
| } | |||
| /* Removes event listener from a clickable element, automatically detaching it | |||
| from all relevant event types. */ | |||
| Element.prototype.removeActivateEvent = function() { | |||
| let ael = this.activateEventListener; | |||
| this.removeEventListener("mousedown", ael); | |||
| this.removeEventListener("click", ael); | |||
| this.removeEventListener("keyup", ael); | |||
| } | |||
| /***********/ | |||
| /* HELPERS */ | |||
| /***********/ | |||
| function selectElementContents(element) { | |||
| var range = document.createRange(); | |||
| range.selectNodeContents(element); | |||
| var selection = window.getSelection(); | |||
| selection.removeAllRanges(); | |||
| selection.addRange(range); | |||
| } | |||
| function copyTextToClipboard(string) { | |||
| let scratchpad = document.querySelector("#scratchpad"); | |||
| scratchpad.value = string; | |||
| scratchpad.select(); | |||
| document.execCommand("copy"); | |||
| } | |||
| function setMessage(string) { | |||
| document.querySelector("#controls .message").innerText = string; | |||
| } | |||
| @@ -0,0 +1,41 @@ | |||
| <?php | |||
| $base_url = "https://your.web.site/textfiles/"; | |||
| if (isset($_GET['f'])) { | |||
| render_file(); | |||
| } else if ( isset($_POST['f']) | |||
| && isset($_POST['p']) | |||
| && $_POST['p'] == 'YOUR_API_KEY_GOES_HERE') { | |||
| new_file(); | |||
| } else { | |||
| header ('Content-type: text/plain; charset=utf-8'); | |||
| http_response_code(404); | |||
| die("NO FILE FOR YOU!! COME BACK, ONE YEAR!"); | |||
| } | |||
| ## Functions | |||
| function render_file() { | |||
| if ($_GET['m'] == 'raw') { | |||
| header ('Content-type: text/plain; charset=utf-8'); | |||
| echo file_get_contents($_GET['f']); | |||
| die; | |||
| } else { | |||
| header ('Content-type: text/html; charset=utf-8'); | |||
| include_once(__DIR__ . "/textfiles.html"); | |||
| die; | |||
| } | |||
| } | |||
| function new_file() { | |||
| `export LANG="en_US.UTF-8"`; | |||
| $tmp_file_path = trim(`mktemp -q ./XXXXXXXX`); | |||
| $tmp_file_name = `basename {$tmp_file_path}`; | |||
| `rm {$tmp_file_path}`; | |||
| file_put_contents("{$tmp_file_path}.txt", $_POST['f']); | |||
| global $base_url; | |||
| echo $base_url . $tmp_file_name; | |||
| } | |||
| ?> | |||