├── .htaccess ├── LICENSE ├── README.md ├── _notes └── .htaccess ├── config.php ├── css ├── simple.css ├── simple.min.css ├── styles.css └── styles.min.css ├── favicon.ico ├── index.php ├── js ├── notelist.js ├── notelist.min.js ├── script.js ├── script.min.js ├── simple.js └── simple.min.js ├── modules ├── common.php ├── copy.php ├── create_notes_folder.php ├── css │ ├── lastsaved.css │ ├── lastsaved.min.css │ ├── menu.css │ ├── menu.min.css │ ├── modal.css │ └── modal.min.css ├── header.php ├── js │ ├── copy.js │ ├── copy.min.js │ ├── lastsaved.js │ ├── lastsaved.min.js │ ├── menu.js │ ├── menu.min.js │ ├── modal.js │ ├── modal.min.js │ ├── password.js │ ├── password.min.js │ ├── tinyago.js │ ├── tinyago.min.js │ ├── view.js │ └── view.min.js ├── lastsaved.php ├── menu.php ├── password.php ├── protect.php └── protect_form.php ├── nginx.conf.example ├── notelist.php ├── passwordHelp.html ├── setup.php ├── simple.php └── view.php /.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | 3 | #RewriteCond %{HTTPS} off 4 | #RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L] 5 | 6 | # if there is a view parameter in the querystring then use view.php 7 | RewriteCond %{QUERY_STRING} ^view [NC] 8 | RewriteRule ^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*)$ view.php?note=$1 [L,QSA] 9 | # the [L] flag will stop any futher processing 10 | 11 | RewriteCond %{QUERY_STRING} ^simple [NC] 12 | RewriteRule ^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*)$ simple.php?note=$1 [L,QSA] 13 | # the [L] flag will stop any futher processing 14 | 15 | #allow 0-9 a-z and hyphens/dashes 16 | RewriteRule ^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*)$ index.php?note=$1 [L,QSA] 17 | 18 | 19 | Header set X-Robots-Tag: "noindex, nofollow" 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 domOrielton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # minimal-web-notepad 3 | 4 | This is a fork of the excellent [pereorga/minimalist-web-notepad](https://github.com/pereorga/minimalist-web-notepad) with additional functionality - the additional code does add size so not minimalist but still minimal at 10kb when minified and gzipped. If you want to go really minimalist then pereorga's implementation is under 3kb and that's not even minified! Password functionality is implemented by adding a header line to the text file which isn't displayed on the note ** be aware this does not encypt the contents, just limits access ** . The only server requirements are an Apache webserver with mod_rewrite enabled or an nginx webserver with ngx_http_rewrite_module and PHP enabled. 5 | 6 | ![edit screenshot](https://raw.github.com/domOrielton/minimal-web-notepad/screenshots/mn_edit.png) 7 | 8 | Added functionality to [pereorga's](https://github.com/pereorga/minimalist-web-notepad) original: 9 | 10 | - view option for note with URLs hyperlinked (very useful for mobile) 11 | - password protection with option for read only access 12 | - view only link 13 | - show last saved time of note 14 | - copy note url, view only url and note text to clipboard 15 | - view note in sans-serif or mono font 16 | - ability to download note 17 | - list of available notes 18 | - turn features on and off to reduce page size if needed 19 | 20 | See demo at http://note.rf.gd/ or http://note.rf.gd/some-note-name-here. The demo doesn't have https so you will see password warnings in your browser - *do not* use it for anything other than a test. 21 | 22 | Screenshots 23 | ------------ 24 | 25 | **Note in View mode** 26 | 27 | ![view screenshot](https://raw.github.com/domOrielton/minimal-web-notepad/screenshots/mn_view.png) 28 | 29 | **Responsive menu for mobile compatibility** 30 | 31 | ![mobile menu screenshot](https://raw.github.com/domOrielton/minimal-web-notepad/screenshots/mn_mobile_menu_expanded.png) ![mobile menu screenshot](https://raw.github.com/domOrielton/minimal-web-notepad/screenshots/mn_mobile_menu.png) 32 | 33 | **Mono font** 34 | 35 | ![mono display screenshot](https://raw.github.com/domOrielton/minimal-web-notepad/screenshots/mn_mono.png) 36 | 37 | **Password protection** 38 | 39 | ![password protection screenshot](https://raw.github.com/domOrielton/minimal-web-notepad/screenshots/mn_password.png) 40 | 41 | **Password prompt for protected note** 42 | 43 | ![password prompt screenshot](https://raw.github.com/domOrielton/minimal-web-notepad/screenshots/mn_password_prompt.png) 44 | 45 | The 'View as Read Only' link shows the note text and nothing else 46 | 47 | **Links for copying to clipboard** 48 | 49 | ![copy screenshot](https://raw.github.com/domOrielton/minimal-web-notepad/screenshots/mn_copy.png) 50 | 51 | **Note list** - generally only used for a URL that is not public, although the page is password protected 52 | 53 | ![note list screenshot](https://raw.github.com/domOrielton/minimal-web-notepad/screenshots/mn_notelist.png) 54 | 55 | If you don't want the note list to show then either set the $allow_noteslist parameter to false at the top of index.php or just rename `notelist.php` to something else. The password for the note list page is at the top of `notelist.php` - Protect\with('modules/protect_form.php', 'change the password here'); 56 | 57 | **Alternative editing view** 58 | 59 | There is also an alternative editing view that can be accessed by adding ?simple after the note e.g. /quick?simple. I personally find this view very useful for adding very quick notes on my phone - it has a small edit area at the top of the page and when you enter text and hit newline it adds it to the note and moves it to to the view that takes up the rest of the page. This view section shows URLs as clickable links. You can't set passwords on this view but it does honor them. 60 | 61 | ![copy screenshot](https://raw.github.com/domOrielton/minimal-web-notepad/screenshots/mn_simple.png) 62 | 63 | Installation 64 | ------------ 65 | 66 | No configuration should be needed as long as mod_rewrite is enabled and the web server is allowed to write to the `_notes` data directory. This data directory is set in `config.php` so if you want to change it to the folder used by the original pereorga/minimalist-web-notepad version then change it there. All the notes are stored as text files so a server running Apache (or Nginx) should be enough, no databases required. 67 | 68 | If notes aren't saving then please check the permissions on the `_notes` directory - 0755 or 744 should be all that is needed. 69 | 70 | ![permissions screenshot](https://raw.github.com/domOrielton/minimal-web-notepad/screenshots/mn_permissions.png) 71 | 72 | There is also a `setup.php` page that can be used to check the `_notes` directory exists and can be written to. If you are having difficulty saving notes it may be worth deleting the `_notes` directory and then going to the `setup.php` page to create the folder. If all is working ok then you can delete the `setup.php` file if you wish. 73 | 74 | There may be scenarios where the $base_url variable in `config.php` needs to be replaced with the hardcoded URL path of your installation. If that is the case just replace the line in `config.php` beginning with `$base_url = dirname('//'` with `$base_url ='http://actualURL.com/notes'` replacing actualURL.com/notes with whatever is relevant for your installation. 75 | 76 | ### On Apache 77 | 78 | You may need to enable mod_rewrite and set up `.htaccess` files in your site configuration. 79 | See [How To Set Up mod_rewrite for Apache](https://www.digitalocean.com/community/tutorials/how-to-set-up-mod_rewrite-for-apache-on-ubuntu-14-04). 80 | 81 | ## On nginx 82 | 83 | On nginx, you will need to ensure nginx.conf is configured correctly to ensure the application works as expected. 84 | Please check the nginx.conf.example file or the [view without password issue](https://github.com/domOrielton/minimal-web-notepad/issues/4). Credit to [eonegh](https://github.com/eonegh) for the example file. 85 | -------------------------------------------------------------------------------- /_notes/.htaccess: -------------------------------------------------------------------------------- 1 | Deny from all 2 | -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /css/simple.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | background: #ebeef1; 4 | } 5 | 6 | .container { 7 | position: absolute; 8 | top: 20px; 9 | right: 20px; 10 | bottom: 20px; 11 | left: 20px; 12 | } 13 | 14 | .contentAdd, .content { 15 | font-size: 100%; 16 | margin: 0; 17 | padding: 20px; 18 | overflow-y: auto; 19 | resize: none; 20 | width: 100%; 21 | height: 10%; 22 | min-height: 10%; 23 | -webkit-box-sizing: border-box; 24 | -moz-box-sizing: border-box; 25 | box-sizing: border-box; 26 | border: 1px #ddd solid; 27 | outline: none; 28 | font-family: sans-serif; 29 | } 30 | 31 | .content { 32 | height: 90%; 33 | min-height: 90%; 34 | } 35 | 36 | .content br { 37 | display: block; 38 | line-height: 1.5em; 39 | } 40 | 41 | @media screen and (max-width: 600px) { 42 | .contentAdd { 43 | padding-top: 5px !important; 44 | } 45 | } 46 | 47 | #printable { 48 | display: none; 49 | } 50 | 51 | @media print { 52 | .container { 53 | display: none; 54 | } 55 | 56 | #printable { 57 | display: block; 58 | white-space: pre-wrap; 59 | word-break: break-word; 60 | } 61 | } 62 | 63 | .wordwrap { 64 | /* used for the view functionality */ 65 | white-space: pre-wrap; 66 | /* CSS3 */ 67 | white-space: -moz-pre-wrap; 68 | /* Firefox */ 69 | white-space: -pre-wrap; 70 | /* Opera <7 */ 71 | white-space: -o-pre-wrap; 72 | /* Opera 7 */ 73 | word-wrap: break-word; 74 | /* IE */ 75 | } 76 | 77 | .footer { 78 | font-size: 100%; 79 | font-family: monospace; 80 | position: absolute; 81 | bottom: 0; 82 | left: 20px; 83 | text-align: left; 84 | } 85 | 86 | @media print { 87 | .footer { 88 | display: none; 89 | } 90 | } 91 | 92 | .navbar { 93 | overflow: hidden; 94 | position: fixed; 95 | bottom: 0; 96 | min-width: 100px; 97 | background-color: #ebeef1; 98 | } 99 | 100 | .navbar a { 101 | float: left; 102 | display: block; 103 | color: black; 104 | text-align: center; 105 | padding: 1px 5px; 106 | text-decoration: underline; 107 | } 108 | 109 | .navbar a:hover { 110 | /*background-color: #ddd;*/ 111 | color: black; 112 | } 113 | 114 | .navbar a.active { 115 | color: black; 116 | } 117 | -------------------------------------------------------------------------------- /css/simple.min.css: -------------------------------------------------------------------------------- 1 | body{margin:0;background:#ebeef1}.container{position:absolute;top:20px;right:20px;bottom:20px;left:20px}.contentAdd,.content{font-size:100%;margin:0;padding:20px;overflow-y:auto;resize:none;width:100%;height:10%;min-height:10%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border:1px #ddd solid;outline:0;font-family:sans-serif}.content{height:90%;min-height:90%}.content br{display:block;line-height:1.5em}@media screen and (max-width:600px){.contentAdd{padding-top:5px!important}}#printable{display:none}@media print{.container{display:none}#printable{display:block;white-space:pre-wrap;word-break:break-word}}.wordwrap{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}.footer{font-size:100%;font-family:monospace;position:absolute;bottom:0;left:20px;text-align:left}@media print{.footer{display:none}}.navbar{overflow:hidden;position:fixed;bottom:0;min-width:100px;background-color:#ebeef1}.navbar a{float:left;display:block;color:black;text-align:center;padding:1px 5px;text-decoration:underline}.navbar a:hover{color:black}.navbar a.active{color:black} -------------------------------------------------------------------------------- /css/styles.css: -------------------------------------------------------------------------------- 1 | /*! Minimalist Web Notepad | https://github.com/pereorga/minimalist-web-notepad */ 2 | body { 3 | margin: 0; 4 | background: #ebeef1; 5 | } 6 | 7 | .container { 8 | position: absolute; 9 | top: 20px; 10 | right: 20px; 11 | bottom: 20px; 12 | left: 20px; 13 | } 14 | 15 | .content { 16 | font-size: 100%; 17 | margin: 0; 18 | padding: 20px; 19 | overflow-y: auto; 20 | resize: none; 21 | width: 100%; 22 | height: 100%; 23 | min-height: 100%; 24 | -webkit-box-sizing: border-box; 25 | -moz-box-sizing: border-box; 26 | box-sizing: border-box; 27 | border: 1px #ddd solid; 28 | outline: none; 29 | /* comment font- settings out to return to default monospace font */ 30 | font-family: sans-serif; 31 | font-size: medium; 32 | } 33 | 34 | #printable { 35 | display: none; 36 | } 37 | 38 | .hidden {visibility: hidden;} 39 | 40 | a { 41 | cursor: pointer; 42 | color: blue; 43 | } 44 | 45 | a:hover, a.hover { 46 | text-decoration: underline; 47 | } 48 | 49 | @media print { 50 | #content { 51 | display: none; 52 | } 53 | 54 | #printable { 55 | display: block; 56 | white-space: pre-wrap; 57 | word-break: break-word; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /css/styles.min.css: -------------------------------------------------------------------------------- 1 | /*! Minimalist Web Notepad | https://github.com/pereorga/minimalist-web-notepad */body{margin:0;background:#ebeef1}.container{position:absolute;top:20px;right:20px;bottom:20px;left:20px}.content{font-size:100%;margin:0;padding:20px;overflow-y:auto;resize:none;width:100%;height:100%;min-height:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border:1px #ddd solid;outline:0;font-family:sans-serif;font-size:medium}#printable{display:none}.hidden{visibility:hidden}a{cursor:pointer;color:blue}a:hover,a.hover{text-decoration:underline}@media print{#content{display:none}#printable{display:block;white-space:pre-wrap;word-break:break-word}} 2 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domOrielton/minimal-web-notepad/098fcd6787e20bdf9e16e795c31b778570ffdb6c/favicon.ico -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | <?php print $_GET['note']; ?> 82 | 83 | 84 | 85 | 86 |
87 | 90 |
91 |

 92 | 	
 93 |   '.PHP_EOL;
100 |     echo "".PHP_EOL;
101 |   }
102 | 	if ($include_Header) { checkHeader($path, null, true); } //check if the removePassword be shown ?>
103 | 
104 | 
105 | 


--------------------------------------------------------------------------------
/js/notelist.js:
--------------------------------------------------------------------------------
 1 | // from https://www.w3schools.com/howto/howto_js_sort_table.asp
 2 | function sortTable(n) {
 3 |   var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
 4 |   table = document.getElementById("notelistTable");
 5 |   switching = true;
 6 |   //Set the sorting direction to ascending:
 7 |   dir = "asc";
 8 |   /*Make a loop that will continue until
 9 |   no switching has been done:*/
10 |   while (switching) {
11 |     //start by saying: no switching is done:
12 |     switching = false;
13 |     rows = table.getElementsByTagName("TR");
14 |     /*Loop through all table rows (except the
15 |     first, which contains table headers):*/
16 |     for (i = 1; i < (rows.length - 1); i++) {
17 |       //start by saying there should be no switching:
18 |       shouldSwitch = false;
19 |       /*Get the two elements you want to compare,
20 |       one from current row and one from the next:*/
21 |       x = rows[i].getElementsByTagName("TD")[n];
22 |       y = rows[i + 1].getElementsByTagName("TD")[n];
23 |       /*check if the two rows should switch place,
24 |       based on the direction, asc or desc:*/
25 |       if (dir == "asc") {
26 |         if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
27 |           //if so, mark as a switch and break the loop:
28 |           shouldSwitch = true;
29 |           break;
30 |         }
31 |       } else if (dir == "desc") {
32 |         if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
33 |           //if so, mark as a switch and break the loop:
34 |           shouldSwitch = true;
35 |           break;
36 |         }
37 |       }
38 |     }
39 |     if (shouldSwitch) {
40 |       /*If a switch has been marked, make the switch
41 |       and mark that a switch has been done:*/
42 |       rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
43 |       switching = true;
44 |       //Each time a switch is done, increase this count by 1:
45 |       switchcount++;
46 |     } else {
47 |       /*If no switching has been done AND the direction is "asc",
48 |       set the direction to "desc" and run the while loop again.*/
49 |       if (switchcount == 0 && dir == "asc") {
50 |         dir = "desc";
51 |         switching = true;
52 |       }
53 |     }
54 |   }
55 | }
56 | // from https://www.w3schools.com/howto/howto_js_filter_table.asp
57 | function filterTable() {
58 |   // Declare variables
59 |   var input, filter, table, tr, td, i;
60 |   input = document.getElementById("filterNotes");
61 |   filter = input.value.toUpperCase();
62 |   table = document.getElementById("notelistTable");
63 |   tr = table.getElementsByTagName("tr");
64 | 
65 |   // Loop through all table rows, and hide those who don't match the search query
66 |   for (i = 0; i < tr.length; i++) {
67 |     td = tr[i].getElementsByTagName("td")[0];
68 |     if (td) {
69 |       if (td.innerHTML.toUpperCase().indexOf(filter) > -1) {
70 |         tr[i].style.display = "";
71 |       } else {
72 |         tr[i].style.display = "none";
73 |       }
74 |     }
75 |   }
76 | }
77 | 


--------------------------------------------------------------------------------
/js/notelist.min.js:
--------------------------------------------------------------------------------
1 | function sortTable(a){var j,k,b,e,h,g,f,c,d=0;j=document.getElementById("notelistTable");b=true;c="asc";while(b){b=false;k=j.getElementsByTagName("TR");for(e=1;e<(k.length-1);e++){f=false;h=k[e].getElementsByTagName("TD")[a];g=k[e+1].getElementsByTagName("TD")[a];if(c=="asc"){if(h.innerHTML.toLowerCase()>g.innerHTML.toLowerCase()){f=true;break}}else{if(c=="desc"){if(h.innerHTML.toLowerCase()-1){e[b].style.display=""}else{e[b].style.display="none"}}}};


--------------------------------------------------------------------------------
/js/script.js:
--------------------------------------------------------------------------------
 1 | function uploadContent(force) {
 2 | 
 3 |   force = force || false; //force needed to add password even though the text is not changed
 4 |   // If textarea value changes.
 5 |   if (content !== textarea.value || force) {
 6 |     var temp = textarea.value;
 7 |     var request = new XMLHttpRequest();
 8 |     var saved = false;
 9 |     request.open('POST', window.location.href, true);
10 |     request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
11 | 
12 |     request.onreadystatechange = function() {
13 |       //if (this.readyState !== 4 || this.status !== 200) {
14 |       //  saved = "error";
15 |       //} // for lastUpdated status
16 |       // the password page won't show correctly if the textarea is there and the session has timed out but
17 |       //   we can still search for text displayed in the form as it is in the responseText
18 |       if (this.responseText.search("Invalid password") !== -1) {
19 |         location.reload();
20 |       }
21 |       //if (this.responseText.search("saved") > 0) {
22 |       //  saved = true;
23 |       //}
24 |       //} // will give a true here is password is set on blank note as note gets deleted
25 |       //if (this.responseText.search("deleted") !== -1) {
26 |       //  saved = true;
27 |       //} // will give a true here is password is set on blank note as note gets deleted
28 |       if (typeof lastUpdated === "function" ) {
29 |         lastUpdated(this.responseText);
30 |       } // check if the lastupdated functions are loaded
31 |     };
32 | 
33 |     request.onload = function() {
34 |       if (request.readyState === 4) {
35 | 
36 |         // Request has ended, check again after 1 second.
37 |         content = temp;
38 |         setTimeout(uploadContent, 1500); //increased time from 1 to 1.5 seconds to offset header check
39 |       }
40 |     }
41 | 
42 |     request.onerror = function() {
43 |       //saved = "error"; // for lastUpdated status
44 |       // Try again after 1 second
45 |       setTimeout(uploadContent, 1000);
46 |     }
47 | 
48 |     // Send the request.
49 |     var requestToSend = 'text=' + encodeURIComponent(temp);
50 |     if (typeof passwordRequest_Add === "function") {
51 |       requestToSend = passwordRequest_Add(requestToSend);
52 |     } //check if the password functions are loaded
53 |     if (typeof passwordRequest_Remove === "function") {
54 |       requestToSend = passwordRequest_Remove(requestToSend);
55 |     } //check if the password functions are loaded
56 |     request.send(requestToSend);
57 | 
58 |     // Make the content available to print.
59 |     printable.removeChild(printable.firstChild);
60 |     printable.appendChild(document.createTextNode(temp));
61 | 
62 |     if (document.getElementById("contentWithLinks")) {
63 |       // used by the simple view
64 |       document.getElementById("contentWithLinks").innerHTML = linkify(document.getElementById("printable").innerHTML).replace(/\r\n|\r|\n/g, "
"); 65 | var objDiv = document.getElementById("contentWithLinks").scrollHeight; //scroll to bottom as text added there 66 | } 67 | 68 | } else { 69 | 70 | // Content has not changed, check again after 1 second. 71 | setTimeout(uploadContent, 1000); 72 | if (typeof lastSaved === "function") { 73 | lastSaved(); 74 | } //check if the lastupdated functions are loaded 75 | } 76 | } 77 | 78 | var textarea = document.getElementById('content'); 79 | var printable = document.getElementById('printable'); 80 | var content = textarea.value; 81 | 82 | // Make the content available to print. 83 | printable.appendChild(document.createTextNode(content)); 84 | 85 | // Enable TABs to indent. Based on https://stackoverflow.com/a/14166052/1391963 86 | textarea.onkeydown = function(e) { 87 | if (e.keyCode === 9 || e.which === 9) { 88 | e.preventDefault(); 89 | var s = this.selectionStart; 90 | this.value = this.value.substring(0, this.selectionStart) + '\t' + this.value.substring(this.selectionEnd); 91 | this.selectionEnd = s + 1; 92 | } 93 | } 94 | 95 | textarea.focus(); 96 | uploadContent(); 97 | -------------------------------------------------------------------------------- /js/script.min.js: -------------------------------------------------------------------------------- 1 | function uploadContent(f){f=f||false;if(content!==textarea.value||f){var b=textarea.value;var e=new XMLHttpRequest();var d=false;e.open("POST",window.location.href,true);e.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");e.onreadystatechange=function(){if(this.responseText.search("Invalid password")!==-1){location.reload()}if(typeof lastUpdated==="function"){lastUpdated(this.responseText)}};e.onload=function(){if(e.readyState===4){content=b;setTimeout(uploadContent,1500)}};e.onerror=function(){setTimeout(uploadContent,1000)};var c="text="+encodeURIComponent(b);if(typeof passwordRequest_Add==="function"){c=passwordRequest_Add(c)}if(typeof passwordRequest_Remove==="function"){c=passwordRequest_Remove(c)}e.send(c);printable.removeChild(printable.firstChild);printable.appendChild(document.createTextNode(b));if(document.getElementById("contentWithLinks")){document.getElementById("contentWithLinks").innerHTML=linkify(document.getElementById("printable").innerHTML).replace(/\r\n|\r|\n/g,"
");var a=document.getElementById("contentWithLinks").scrollHeight}}else{setTimeout(uploadContent,1000);if(typeof lastSaved==="function"){lastSaved()}}}var textarea=document.getElementById("content");var printable=document.getElementById("printable");var content=textarea.value;printable.appendChild(document.createTextNode(content));textarea.onkeydown=function(b){if(b.keyCode===9||b.which===9){b.preventDefault();var a=this.selectionStart;this.value=this.value.substring(0,this.selectionStart)+"\t"+this.value.substring(this.selectionEnd);this.selectionEnd=a+1}};textarea.focus();uploadContent(); -------------------------------------------------------------------------------- /js/simple.js: -------------------------------------------------------------------------------- 1 | var textareaAdd = document.getElementById('contentAdd'); // used by the simple view 2 | 3 | textareaAdd.onkeydown = function(e) { 4 | if (e.keyCode === 9 || e.which === 9) { 5 | e.preventDefault(); 6 | var s = this.selectionStart; 7 | this.value = this.value.substring(0, this.selectionStart) + '\t' + this.value.substring(this.selectionEnd); 8 | this.selectionEnd = s + 1; 9 | } 10 | if (e.keyCode === 13 || e.which === 13) { 11 | e.preventDefault(); 12 | document.getElementById("content").value += '\n' + textareaAdd.value; 13 | this.value = ''; 14 | } 15 | } 16 | 17 | textareaAdd.focus(); 18 | -------------------------------------------------------------------------------- /js/simple.min.js: -------------------------------------------------------------------------------- 1 | var textareaAdd=document.getElementById("contentAdd");textareaAdd.onkeydown=function(b){if(b.keyCode===9||b.which===9){b.preventDefault();var a=this.selectionStart;this.value=this.value.substring(0,this.selectionStart)+"\t"+this.value.substring(this.selectionEnd);this.selectionEnd=a+1}if(b.keyCode===13||b.which===13){b.preventDefault();document.getElementById("content").value+="\n"+textareaAdd.value;this.value=""}};textareaAdd.focus(); -------------------------------------------------------------------------------- /modules/common.php: -------------------------------------------------------------------------------- 1 | console.log( 'Debug Objects: " . date('H:i:s'). " " . $output . "' );".PHP_EOL; 39 | } 40 | 41 | function writeToLog($txt) 42 | { 43 | file_put_contents('log.txt', date('Y-m-d H:i:s'). "\t" .$txt.PHP_EOL, FILE_APPEND | LOCK_EX); 44 | } 45 | -------------------------------------------------------------------------------- /modules/copy.php: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /modules/create_notes_folder.php: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /modules/css/lastsaved.css: -------------------------------------------------------------------------------- 1 | .savedStatus { 2 | /* bottomRightCorner */ 3 | font-size: 100%; 4 | font-family: monospace; 5 | position: absolute; 6 | bottom: 1px; 7 | right: 20px; 8 | } 9 | 10 | @media print { 11 | .savedStatus { 12 | display: none; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /modules/css/lastsaved.min.css: -------------------------------------------------------------------------------- 1 | .savedStatus{font-size:100%;font-family:monospace;position:absolute;bottom:1px;right:20px}@media print{.savedStatus{display:none}} -------------------------------------------------------------------------------- /modules/css/menu.css: -------------------------------------------------------------------------------- 1 | .wordwrap { 2 | /* used for the view functionality */ 3 | white-space: pre-wrap; 4 | /* CSS3 */ 5 | white-space: -moz-pre-wrap; 6 | /* Firefox */ 7 | white-space: -pre-wrap; 8 | /* Opera <7 */ 9 | white-space: -o-pre-wrap; 10 | /* Opera 7 */ 11 | word-wrap: break-word; 12 | /* IE */ 13 | } 14 | 15 | .footer { 16 | font-size: 100%; 17 | font-family: monospace; 18 | position: absolute; 19 | bottom: 0; 20 | left: 20px; 21 | text-align: left; 22 | } 23 | 24 | @media print { 25 | .footer { 26 | display: none; 27 | } 28 | } 29 | 30 | /* navbar css - https://www.w3schools.com/howto/howto_js_bottom_nav_responsive.asp */ 31 | .navbar { 32 | overflow: hidden; 33 | position: fixed; 34 | bottom: 0; 35 | min-width: 100px; 36 | background-color: #ebeef1; 37 | } 38 | 39 | .navbar a { 40 | float: left; 41 | display: block; 42 | color: black; 43 | text-align: center; 44 | padding: 1px 5px; 45 | text-decoration: underline; 46 | } 47 | 48 | .navbar a:hover { 49 | /*background-color: #ddd;*/ 50 | color: black; 51 | } 52 | 53 | .navbar a.active { 54 | color: black; 55 | } 56 | 57 | .navbar .icon { 58 | display: none; 59 | } 60 | 61 | @media screen and (max-width: 600px) { 62 | .navbar a:not(:nth-child(2)) { 63 | display: none; 64 | } 65 | 66 | .navbar a { 67 | font-size: 17px; 68 | } 69 | 70 | .navbar a.icon { 71 | position: relative; 72 | left: 0; 73 | bottom: 0; 74 | /*float: left;*/ 75 | display: block; 76 | } 77 | } 78 | 79 | @media screen and (max-width: 600px) { 80 | .navbar.responsive .icon { 81 | position: absolute; 82 | left: 0; 83 | bottom: 0; 84 | } 85 | 86 | .navbar.responsive a:not(:first-child) { 87 | float: none; 88 | display: block; 89 | text-align: left; 90 | margin-left: 40px !important; 91 | margin: 20px; 92 | } 93 | } 94 | 95 | /* end of navbar */ 96 | /* overlay from https://raventools.com/blog/create-a-modal-dialog-using-css-and-javascript/ */ 97 | #overlay { 98 | visibility: hidden; 99 | position: absolute; 100 | left: 0px; 101 | top: 0px; 102 | width: 100%; 103 | height: 100%; 104 | text-align: center; 105 | z-index: 1000; 106 | font-family: sans-serif; 107 | font-size: medium; 108 | } 109 | 110 | #overlay div { 111 | width: 250px; 112 | margin: 100px auto; 113 | background-color: #fff; 114 | border: 1px solid grey; 115 | padding: 15px; 116 | text-align: center; 117 | } 118 | 119 | body { 120 | height: 100%; 121 | margin: 0; 122 | padding: 0; 123 | } 124 | -------------------------------------------------------------------------------- /modules/css/menu.min.css: -------------------------------------------------------------------------------- 1 | .wordwrap{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}.footer{font-size:100%;font-family:monospace;position:absolute;bottom:0;left:20px;text-align:left}@media print{.footer{display:none}}.navbar{overflow:hidden;position:fixed;bottom:0;min-width:100px;background-color:#ebeef1}.navbar a{float:left;display:block;color:black;text-align:center;padding:1px 5px;text-decoration:underline}.navbar a:hover{color:black}.navbar a.active{color:black}.navbar .icon{display:none}@media screen and (max-width:600px){.navbar a:not(:nth-child(2)){display:none}.navbar a{font-size:17px}.navbar a.icon{position:relative;left:0;bottom:0;display:block}}@media screen and (max-width:600px){.navbar.responsive .icon{position:absolute;left:0;bottom:0}.navbar.responsive a:not(:first-child){float:none;display:block;text-align:left;margin-left:40px!important;margin:20px}}#overlay{visibility:hidden;position:absolute;left:0;top:0;width:100%;height:100%;text-align:center;z-index:1000;font-family:sans-serif;font-size:medium}#overlay div{width:250px;margin:100px auto;background-color:#fff;border:1px solid grey;padding:15px;text-align:center}body{height:100%;margin:0;padding:0} -------------------------------------------------------------------------------- /modules/css/modal.css: -------------------------------------------------------------------------------- 1 | .modal_password, .modal_copy { 2 | position: fixed; 3 | left: 0; 4 | top: 0; 5 | width: 100%; 6 | height: 100%; 7 | background-color: rgba(0, 0, 0, 0.5); 8 | opacity: 0; 9 | visibility: hidden; 10 | transform: scale(1.1); 11 | } 12 | 13 | .modal-content { 14 | font-family: sans-serif; 15 | position: absolute; 16 | top: 50%; 17 | left: 50%; 18 | transform: translate(-50%, -50%); 19 | background-color: white; 20 | padding: 1rem 3rem; 21 | /*width: 24rem; */ 22 | border-radius: 0.5rem; 23 | } 24 | 25 | @media screen and (max-width: 600px) { 26 | .modal-content { 27 | width: 90%; 28 | padding: 1rem 1rem; 29 | } 30 | } 31 | 32 | .close-button_password, .close-button_copy { 33 | float: right; 34 | width: 1.5rem; 35 | line-height: 1.5rem; 36 | text-align: center; 37 | cursor: pointer; 38 | border-radius: 0.25rem; 39 | /* background-color: lightgray; */ 40 | } 41 | 42 | .show-modal { 43 | opacity: 1; 44 | visibility: visible; 45 | transform: scale(1.0); 46 | } 47 | 48 | .input, 49 | .submit { 50 | display: inline-block; 51 | padding: 10px 15px; 52 | margin-top: 10px; 53 | font-size: 10px; 54 | border-radius: 0; 55 | -webkit-appearance: none; 56 | } 57 | 58 | /* https://martinwolf.org/before-2018/blog/2015/07/equal-height-input-and-submit-button/ */ 59 | .input { 60 | border: 1px solid lightgray; 61 | } 62 | 63 | #allowReadOnlyView { 64 | margin-left:0; 65 | margin-top: 5px; 66 | } 67 | 68 | .submit { 69 | color: #fff; 70 | background-color: #337ab7; 71 | border-color: #2e6da4;; 72 | /** 73 | * If the input field has a border, 74 | * you need it here too to achieve equal heights. 75 | */ 76 | border: 1px solid transparent; 77 | } 78 | 79 | /* Firefox Fix - * Without that the submit button will be too high. */ 80 | .submit::-moz-focus-inner { 81 | border: 0; 82 | } 83 | -------------------------------------------------------------------------------- /modules/css/modal.min.css: -------------------------------------------------------------------------------- 1 | .modal_password,.modal_copy{position:fixed;left:0;top:0;width:100%;height:100%;background-color:rgba(0,0,0,0.5);opacity:0;visibility:hidden;transform:scale(1.1)}.modal-content{font-family:sans-serif;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background-color:white;padding:1rem 3rem;border-radius:.5rem}@media screen and (max-width:600px){.modal-content{width:90%;padding:1rem 1rem}}.close-button_password,.close-button_copy{float:right;width:1.5rem;line-height:1.5rem;text-align:center;cursor:pointer;border-radius:.25rem}.show-modal{opacity:1;visibility:visible;transform:scale(1.0)}.input,.submit{display:inline-block;padding:10px 15px;margin-top:10px;font-size:10px;border-radius:0;-webkit-appearance:none}.input{border:1px solid lightgray}#allowReadOnlyView{margin-left:0;margin-top:5px}.submit{color:#fff;background-color:#337ab7;border-color:#2e6da4;border:1px solid transparent}.submit::-moz-focus-inner{border:0} -------------------------------------------------------------------------------- /modules/header.php: -------------------------------------------------------------------------------- 1 | if (typeof showRemovePassword === 'function') {showRemovePassword();}".PHP_EOL; // showRemovePassword if defined (in modal.js) 41 | if ($allowReadOnlyView == '1') { 42 | echo "".PHP_EOL; 43 | } // check allowReadOnlyView if set in the header 44 | } 45 | 46 | if (!isset($_SESSION)) { 47 | session_start(); 48 | } 49 | 50 | if ($allowReadOnlyView == '1' and basename($_SERVER['PHP_SELF']) == 'view.php') { /* viewing the page in readonly is ok from the view.php is ok so don't prompt for password */ 51 | } else { 52 | // if readonly view is not allowed and the calling page is not view.php then prompt for the password 53 | require_once 'modules/protect.php'; 54 | Protect\with('modules/protect_form.php', '', $passwordhash, $allowReadOnlyView); 55 | } 56 | } 57 | } 58 | return $headerFound; 59 | } 60 | 61 | function setHeader($allow_password) 62 | { 63 | // Add header data to the first line of the file - this will not be shown on the note 64 | if (!empty($_POST['notepwd'])) { 65 | $passwordhash = password_hash($_POST['notepwd'], PASSWORD_DEFAULT); 66 | } else { 67 | $passwordhash = ''; 68 | } 69 | if (!empty($_POST['allowReadOnlyView'])) { 70 | $allowReadOnlyView = $_POST['allowReadOnlyView']; 71 | } else { 72 | $allowReadOnlyView = 0; 73 | } 74 | $scope = current_url(); 75 | $session_key = 'password_protect_'.preg_replace('/\W+/', '_', $scope); 76 | if (!isset($_SESSION)) { 77 | session_start(); 78 | } 79 | // if the password and allowReadOnlyView hasn't been set from the form fields then get it from the session values 80 | // this solves the issue of not having the password on the page after it has been set 81 | if (isset($_SESSION[$session_key])) { 82 | if ($passwordhash == '' && $_SESSION[$session_key]) { 83 | $passwordhash=$_SESSION[$session_key.'hash']; 84 | } 85 | if (!empty($_POST['allowReadOnlyView']) == '' && $_SESSION[$session_key]) { 86 | $allowReadOnlyView=$_SESSION[$session_key.'allowReadOnlyView']; 87 | } 88 | } 89 | $noteHash = base64_encode($_GET['note']); // could also use rtrim(strtr(base64_encode($_GET['note']), '+/', '-_'), '='); to remove the = chars used for padding 90 | $header = "[header] " . "¬eName=" . $_GET['note'] . "¬eHash=" . $noteHash; // create the basic header content, really just an identifier 91 | $pwd = ""; 92 | if ($allow_password) { 93 | $pwd = "&password=" . $passwordhash; //. "&pwd=" . (isset($_POST['notepwd']) ? $_POST['notepwd'] : ''); 94 | $_SESSION[$session_key] = true; 95 | $_SESSION[$session_key.'hash'] = $passwordhash; 96 | // logic for allowing readonly view of note without password 97 | $pwd = $pwd . "&allowReadOnlyView=" . $allowReadOnlyView; 98 | $_SESSION[$session_key.'allowReadOnlyView'] = $allowReadOnlyView; 99 | // TODO: if the page is simple then rely on session setting 100 | } // create the password string for the header 101 | if (!empty($_POST['removePassword'])) { 102 | if ($_POST['removePassword'] == "1") { 103 | $pwd=""; 104 | unset($_SESSION[$session_key]); 105 | unset($_SESSION[$session_key.'hash']); 106 | unset($_SESSION[$session_key.'allowReadOnlyView']); 107 | } //remove the password 108 | } 109 | $header = $header . $pwd . "\n"; //add the password string to the header 110 | // check if anything has been added to the header (password or other) 111 | if (trim($header) == $header) { 112 | // if nothing has been added then no header is needed so set it to empty 113 | $header = ""; 114 | } 115 | return ($header); 116 | } 117 | 118 | function stripFirstLine($text) 119 | { 120 | // from http://stackoverflow.com/questions/7740405/php-delete-the-first-line-of-a-text-and-return-the-rest 121 | return substr($text, strpos($text, "\n")+1); //using \n instead of PHP_EOL as better compatibility moving files between Linux and Windows 122 | } 123 | -------------------------------------------------------------------------------- /modules/js/copy.js: -------------------------------------------------------------------------------- 1 | function copyToClipboard(elementId) { 2 | //https://jsfiddle.net/alvaroAV/a2pt16yq/ 3 | 4 | // Create an auxiliary hidden input 5 | var aux = document.createElement("textarea"); 6 | 7 | // Get the text from the element passed into the input 8 | if (elementId == 'copyURL') { 9 | aux.value = window.location.href; //get the url not the content 10 | } 11 | else if (elementId == 'copyURLViewOnly') { 12 | aux.value = window.location.href + '?view'; //get the view only url not the content 13 | } 14 | else { 15 | aux.value = document.getElementById(elementId).innerHTML; 16 | } 17 | 18 | // Append the aux input to the body 19 | document.body.appendChild(aux); 20 | 21 | // Highlight the content 22 | aux.select(); 23 | 24 | // Execute the copy command 25 | document.execCommand("copy"); 26 | 27 | // Remove the input from the body 28 | document.body.removeChild(aux); 29 | 30 | document.getElementById("copyMessage").innerHTML = "
Copied"; 31 | } 32 | -------------------------------------------------------------------------------- /modules/js/copy.min.js: -------------------------------------------------------------------------------- 1 | function copyToClipboard(b){var a=document.createElement("textarea");if(b=="copyURL"){a.value=window.location.href}else{if(b=="copyURLViewOnly"){a.value=window.location.href+"?view"}else{a.value=document.getElementById(b).innerHTML}}document.body.appendChild(a);a.select();document.execCommand("copy");document.body.removeChild(a);document.getElementById("copyMessage").innerHTML="
Copied"}; -------------------------------------------------------------------------------- /modules/js/lastsaved.js: -------------------------------------------------------------------------------- 1 | function lastUpdated(responseText) { 2 | // called when a note is saved 3 | var now = new Date(); 4 | var saved = ''; 5 | saved += ('0' + now.getHours()).slice(-2) + ':' + ('0' + now.getMinutes()).slice(-2) + ':' + ('0' + now.getSeconds()).slice(-2); 6 | var selector = document.getElementById('savedStatus'); 7 | selector.setAttribute('datetime', now.getTime()); 8 | //update the status so the user can see it has been saved 9 | document.getElementById("savedStatus").innerHTML = lastText() + saved; 10 | if (responseText.search("error") !== -1 && responseText.search("saved") > 0) { 11 | selector.setAttribute('datetime', ''); 12 | document.getElementById("savedStatus").innerHTML = responseText; 13 | } 14 | } 15 | 16 | function lastSaved() { 17 | // Update how long ago it was saved (comment out next 2 lines if do not want a status that is dynamic (showing how long ago it was last saved) 18 | // this will fire if there is no change to the file - basically just a clock 19 | var lastSave = document.getElementById('savedStatus').getAttribute("datetime"); 20 | if (lastSave) { 21 | var result = lastSave; 22 | if (isNaN(lastSave)) { 23 | var myDate = new Date(lastSave); 24 | result = myDate.getTime(); 25 | } 26 | var savedStatus = lastText() + ago(result) + ' ago'; 27 | //update the saved status if it has changed 28 | if (savedStatus != document.getElementById("savedStatus").innerHTML) document.getElementById("savedStatus").innerHTML = savedStatus; 29 | } 30 | } 31 | 32 | function lastText() { 33 | var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); 34 | var lastSavedText = "Last saved: "; 35 | if (w < 600) { 36 | lastSavedText = "Saved: "; 37 | } 38 | return lastSavedText; 39 | } 40 | -------------------------------------------------------------------------------- /modules/js/lastsaved.min.js: -------------------------------------------------------------------------------- 1 | function lastUpdated(d){var b=new Date();var c="";c+=("0"+b.getHours()).slice(-2)+":"+("0"+b.getMinutes()).slice(-2)+":"+("0"+b.getSeconds()).slice(-2);var a=document.getElementById("savedStatus");a.setAttribute("datetime",b.getTime());document.getElementById("savedStatus").innerHTML=lastText()+c;if(d.search("error")!==-1&&d.search("saved")>0){a.setAttribute("datetime","");document.getElementById("savedStatus").innerHTML=d}}function lastSaved(){var c=document.getElementById("savedStatus").getAttribute("datetime");if(c){var a=c;if(isNaN(c)){var b=new Date(c);a=b.getTime()}var d=lastText()+ago(a)+" ago";if(d!=document.getElementById("savedStatus").innerHTML){document.getElementById("savedStatus").innerHTML=d}}}function lastText(){var a=Math.max(document.documentElement.clientWidth,window.innerWidth||0);var b="Last saved: ";if(a<600){b="Saved: "}return b}; -------------------------------------------------------------------------------- /modules/js/menu.js: -------------------------------------------------------------------------------- 1 | // based on https://www.w3schools.com/howto/howto_js_bottom_nav_responsive.asp 2 | function navbarResponsive() { 3 | var x = document.getElementById("navbar"); 4 | if (x.className === "navbar") { 5 | x.className += " responsive"; 6 | } else { 7 | x.className = "navbar"; 8 | } 9 | } 10 | 11 | var menuButton = document.getElementById("menuButton"); 12 | 13 | function windowOnClick_navbar(event) { 14 | // hide responsive menu if displayed by clicking outside of it 15 | if (event.target.id !== "menuButton") { 16 | var el = document.getElementById('navbar'); 17 | if (el.className.indexOf('responsive') > -1) { 18 | el.className = 'navbar'; 19 | } 20 | } 21 | } 22 | 23 | window.addEventListener("click", windowOnClick_navbar); 24 | 25 | function downloadFile() { 26 | var link = document.createElement("a"); 27 | var content = document.getElementById('content').value; 28 | link.href = "data:application/txt," + encodeURIComponent(content) 29 | link.download = "note_" + document.title + ".txt"; 30 | link.dispatchEvent(new MouseEvent('click', { 31 | bubbles: true, 32 | cancelable: true, 33 | view: window 34 | })); 35 | // a [save as] dialog will be shown 36 | //window.open("data:application/txt," + encodeURIComponent(content), "_self"); 37 | } 38 | 39 | function toggleMonospace(lnk_obj) { 40 | var x = document.getElementById("content"); 41 | lnk_obj.innerHTML = (lnk_obj.innerHTML == 'mono') ? 'sans' : 'mono'; 42 | if (x.style.fontFamily == "monospace") { 43 | x.style.fontFamily = "sans-serif"; 44 | } else { 45 | x.style.fontFamily = "monospace"; 46 | } 47 | } 48 | 49 | function deleteFile() { 50 | var r = confirm("Are you sure you want to delete this note?"); 51 | if (r == true) { 52 | document.getElementById('content').value = ''; 53 | uploadContent(); 54 | alert('Note deleted'); 55 | // Note has been deleted so open a new note (going back to the same note, not a new note, looks confusing) 56 | var url = window.location.href; 57 | window.location.href = url.substring(0, url.lastIndexOf('/')); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /modules/js/menu.min.js: -------------------------------------------------------------------------------- 1 | function navbarResponsive(){var a=document.getElementById("navbar");if(a.className==="navbar"){a.className+=" responsive"}else{a.className="navbar"}}var menuButton=document.getElementById("menuButton");function windowOnClick_navbar(b){if(b.target.id!=="menuButton"){var a=document.getElementById("navbar");if(a.className.indexOf("responsive")>-1){a.className="navbar"}}}window.addEventListener("click",windowOnClick_navbar);function downloadFile(){var b=document.createElement("a");var a=document.getElementById("content").value;b.href="data:application/txt,"+encodeURIComponent(a);b.download="note_"+document.title+".txt";b.dispatchEvent(new MouseEvent("click",{bubbles:true,cancelable:true,view:window}))}function toggleMonospace(b){var a=document.getElementById("content");b.innerHTML=(b.innerHTML=="mono")?"sans":"mono";if(a.style.fontFamily=="monospace"){a.style.fontFamily="sans-serif"}else{a.style.fontFamily="monospace"}}function deleteFile(){var b=confirm("Are you sure you want to delete this note?");if(b==true){document.getElementById("content").value="";uploadContent();alert("Note deleted");var a=window.location.href;window.location.href=a.substring(0,a.lastIndexOf("/"))}}; -------------------------------------------------------------------------------- /modules/js/modal.js: -------------------------------------------------------------------------------- 1 | //make sure the querySelector code is run *after* the page content loads 2 | // based on https://sabe.io/tutorials/how-to-create-modal-popup-box 3 | var modal_password = document.querySelector(".modal_password"); 4 | var modal_copy = document.querySelector(".modal_copy"); 5 | var closeButton_password = document.querySelector(".close-button_password"); 6 | var closeButton_copy = document.querySelector(".close-button_copy"); 7 | 8 | function toggleModal_Password() { 9 | //todo: add parameter for which modal to just a single function? 10 | inputboxLocation.innerHTML = "" 11 | modal_password.classList.toggle("show-modal"); 12 | document.getElementById("notepwd").focus(); 13 | } 14 | 15 | function toggleModal_Copy() { 16 | //todo: add parameter for which modal? 17 | document.getElementById("copyMessage").innerHTML = "

"; 18 | modal_copy.classList.toggle("show-modal"); 19 | } 20 | 21 | function windowOnClick_Modal(event) { 22 | // modal window windows if clicking outside of them 23 | if (event.target === modal_password) { 24 | toggleModal_Password(); 25 | } 26 | if (event.target === modal_copy) { 27 | toggleModal_Copy(); 28 | } 29 | } 30 | 31 | if (closeButton_password) closeButton_password.addEventListener("click", toggleModal_Password); 32 | if (closeButton_copy) closeButton_copy.addEventListener("click", toggleModal_Copy); 33 | window.addEventListener("click", windowOnClick_Modal); 34 | 35 | function showRemovePassword() { 36 | el = document.getElementById("removePassword"); 37 | el.style.display = (el.style.display == "none") ? "inline" : "none"; 38 | } 39 | 40 | function checkallowReadOnlyView() { 41 | el = document.getElementById('allowReadOnlyView'); 42 | el.checked = true; 43 | } 44 | 45 | function removePassword() { 46 | inputboxLocation.innerHTML = "" 47 | el = document.getElementById("overlay"); 48 | el.style.visibility = (el.style.visibility == "visible") ? "hidden" : "visible"; 49 | document.getElementById("notepwd").focus(); 50 | } 51 | 52 | function isValid(str) { 53 | return str.replace(/^\s+/g, '').length; // boolean ('true' if field is empty) 54 | } 55 | -------------------------------------------------------------------------------- /modules/js/modal.min.js: -------------------------------------------------------------------------------- 1 | var modal_password=document.querySelector(".modal_password");var modal_copy=document.querySelector(".modal_copy");var closeButton_password=document.querySelector(".close-button_password");var closeButton_copy=document.querySelector(".close-button_copy");function toggleModal_Password(){inputboxLocation.innerHTML="";modal_password.classList.toggle("show-modal");document.getElementById("notepwd").focus()}function toggleModal_Copy(){document.getElementById("copyMessage").innerHTML="

";modal_copy.classList.toggle("show-modal")}function windowOnClick_Modal(a){if(a.target===modal_password){toggleModal_Password()}if(a.target===modal_copy){toggleModal_Copy()}}if(closeButton_password){closeButton_password.addEventListener("click",toggleModal_Password)}if(closeButton_copy){closeButton_copy.addEventListener("click",toggleModal_Copy)}window.addEventListener("click",windowOnClick_Modal);function showRemovePassword(){el=document.getElementById("removePassword");el.style.display=(el.style.display=="none")?"inline":"none"}function checkallowReadOnlyView(){el=document.getElementById("allowReadOnlyView");el.checked=true}function removePassword(){inputboxLocation.innerHTML="";el=document.getElementById("overlay");el.style.visibility=(el.style.visibility=="visible")?"hidden":"visible";document.getElementById("notepwd").focus()}function isValid(a){return a.replace(/^\s+/g,"").length}; -------------------------------------------------------------------------------- /modules/js/password.js: -------------------------------------------------------------------------------- 1 | function passwordRequest_Add(requestToSend) { 2 | var notepwd = (document.getElementById("notepwd")) ? document.getElementById("notepwd").value : ''; 3 | if (!isEmpty(notepwd)) { 4 | requestToSend = requestToSend + '¬epwd=' + encodeURIComponent(notepwd); 5 | } 6 | if ( document.getElementById("allowReadOnlyView").checked ) { 7 | requestToSend = requestToSend + '&allowReadOnlyView=1'; 8 | } 9 | return requestToSend; 10 | } 11 | 12 | function passwordRequest_Remove(requestToSend) { 13 | var removePassword = (document.getElementById("hdnRemovePassword")) ? document.getElementById("hdnRemovePassword").value : ''; 14 | if (!isEmpty(removePassword)) { 15 | requestToSend = requestToSend + '&removePassword=' + encodeURIComponent(removePassword); 16 | } 17 | return requestToSend; 18 | } 19 | 20 | function metadataGet(requestToSend) { 21 | if (!isEmpty(notepwd)) { 22 | requestToSend = requestToSend + '&metadataGet=1'; 23 | } 24 | return requestToSend; 25 | } 26 | 27 | function isEmpty(value) { 28 | return (value == null || value.length === 0); 29 | } 30 | 31 | function passwordRemove() { 32 | document.getElementById("hdnRemovePassword").value = "1"; 33 | uploadContent(true); 34 | toggleModal_Password(); 35 | document.getElementById("hdnRemovePassword").value = ""; 36 | showRemovePassword(); 37 | } 38 | 39 | function submitPassword() { 40 | if (isValid(notepwd.value)) { 41 | uploadContent(true); 42 | pwdMessage.innerHTML = ""; 43 | showRemovePassword(); 44 | toggleModal_Password(); 45 | } else { 46 | pwdMessage.innerHTML = "Please enter a password
"; 47 | } 48 | } 49 | 50 | // https://www.w3schools.com/howto/howto_js_trigger_button_enter.asp 51 | var passwordInput = document.getElementById("notepwd"); 52 | // Execute a function when the user releases a key on the keyboard 53 | if (passwordInput) { 54 | passwordInput.addEventListener("keyup", function(event) { 55 | event.preventDefault(); // Cancel the default action, if needed 56 | if (event.keyCode === 13) { // Number 13 is the "Enter" key on the keyboard 57 | document.getElementById("submitpwd").click(); // Trigger the button element with a click 58 | } 59 | }); } 60 | -------------------------------------------------------------------------------- /modules/js/password.min.js: -------------------------------------------------------------------------------- 1 | function passwordRequest_Add(a){var b=(document.getElementById("notepwd"))?document.getElementById("notepwd").value:"";if(!isEmpty(b)){a=a+"¬epwd="+encodeURIComponent(b)}if(document.getElementById("allowReadOnlyView").checked){a=a+"&allowReadOnlyView=1"}return a}function passwordRequest_Remove(b){var a=(document.getElementById("hdnRemovePassword"))?document.getElementById("hdnRemovePassword").value:"";if(!isEmpty(a)){b=b+"&removePassword="+encodeURIComponent(a)}return b}function metadataGet(a){if(!isEmpty(notepwd)){a=a+"&metadataGet=1"}return a}function isEmpty(a){return(a==null||a.length===0)}function passwordRemove(){document.getElementById("hdnRemovePassword").value="1";uploadContent(true);toggleModal_Password();document.getElementById("hdnRemovePassword").value="";showRemovePassword()}function submitPassword(){if(isValid(notepwd.value)){uploadContent(true);pwdMessage.innerHTML="";showRemovePassword();toggleModal_Password()}else{pwdMessage.innerHTML="Please enter a password
"}}var passwordInput=document.getElementById("notepwd");if(passwordInput){passwordInput.addEventListener("keyup",function(a){a.preventDefault();if(a.keyCode===13){document.getElementById("submitpwd").click()}})}; -------------------------------------------------------------------------------- /modules/js/tinyago.js: -------------------------------------------------------------------------------- 1 | function ago(val) { 2 | // https://github.com/odyniec/tinyAgo-js 3 | // val is datetime in milliseconds 4 | val = 0 | (Date.now() - val) / 1000; 5 | var unit, length = { 6 | second: 60, 7 | minute: 60, 8 | hour: 24, 9 | day: 7, 10 | week: 4.35, 11 | month: 12, 12 | year: 10000 13 | }, 14 | result; 15 | 16 | for (unit in length) { 17 | result = val % length[unit]; 18 | if (!(val = 0 | val / length[unit])) 19 | return result + ' ' + (result - 1 ? unit + 's' : unit); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /modules/js/tinyago.min.js: -------------------------------------------------------------------------------- 1 | function ago(d){d=0|(Date.now()-d)/1000;var c,b={second:60,minute:60,hour:24,day:7,week:4.35,month:12,year:10000},a;for(c in b){a=d%b[c];if(!(d=0|d/b[c])){return a+" "+(a-1?c+"s":c)}}}; -------------------------------------------------------------------------------- /modules/js/view.js: -------------------------------------------------------------------------------- 1 | function linkify(inputText) { 2 | // from https://stackoverflow.com/questions/37684/how-to-replace-plain-urls-with-links 3 | var replacedText, replacePattern1, replacePattern2, replacePattern3; 4 | 5 | //URLs starting with http://, https://, or ftp:// 6 | replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; 7 | replacedText = inputText.replace(replacePattern1, '$1'); 8 | 9 | //URLs starting with "www." (without // before it, or it'd re-link the ones done above). 10 | replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; 11 | replacedText = replacedText.replace(replacePattern2, '$1$2'); 12 | 13 | //Change email addresses to mailto:: links. 14 | replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim; 15 | replacedText = replacedText.replace(replacePattern3, '$1'); 16 | 17 | return replacedText; 18 | } 19 | 20 | function CreateViewable(noBorder) { 21 | var newNode = document.createElement("div"); // Create the new node to insert 22 | newNode.id = "contentWithLinks"; // give it an id attribute called 'newSpan' 23 | var referenceNode = document.getElementById("content"); // Get the reference node 24 | referenceNode.parentNode.insertBefore(newNode, referenceNode); // Insert the new node before the reference node 25 | document.getElementById("contentWithLinks").innerHTML = linkify(document.getElementById("printable").innerHTML).replace(/\r\n|\r|\n/g, "
"); 26 | 27 | //added for IE9 compatibility 28 | //https://www.w3schools.com/howto/howto_js_add_class.asp 29 | var element, name, arr; 30 | element = document.getElementById("contentWithLinks"); 31 | name = "content wordwrap"; 32 | arr = element.className.split(" "); 33 | if (arr.indexOf(name) == -1) { 34 | element.className += " " + name; 35 | } 36 | if (noBorder) element.style.border='none'; 37 | } 38 | 39 | function toggleView(lnk_obj) { 40 | var x = document.getElementById("content"); 41 | lnk_obj.innerHTML = (lnk_obj.innerHTML == 'view') ? 'edit' : 'view'; 42 | if (x.style.display === "none") { 43 | x.style.display = "block"; 44 | var element = document.getElementById("contentWithLinks"); 45 | element.parentNode.removeChild(element); 46 | } else { 47 | x.style.display = "none"; 48 | CreateViewable(); 49 | } 50 | } 51 | 52 | function viewOnly() { 53 | //no textarea available with the view option so show the links enabled view 54 | var x = document.getElementById("content"); 55 | x.style.display = "none"; 56 | CreateViewable(true); 57 | } 58 | 59 | // Go back to Edit (from View with links) by pressing the Esc key 60 | document.onkeydown = function(evt) { 61 | evt = evt || window.event; 62 | if (evt.keyCode == 27) { 63 | if (document.getElementById("password_modal").className == 'modal_password show-modal') { 64 | toggleModal_Password(); 65 | return; 66 | } 67 | if (document.getElementById("content").style.display == "none") { 68 | toggleView(document.getElementById("a_view")); 69 | } 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /modules/js/view.min.js: -------------------------------------------------------------------------------- 1 | function linkify(e){var b,d,c,a;d=/(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;b=e.replace(d,'$1');c=/(^|[^\/])(www\.[\S]+(\b|$))/gim;b=b.replace(c,'$1$2');a=/(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;b=b.replace(a,'$1');return b}function CreateViewable(f){var e=document.createElement("div");e.id="contentWithLinks";var b=document.getElementById("content");b.parentNode.insertBefore(e,b);document.getElementById("contentWithLinks").innerHTML=linkify(document.getElementById("printable").innerHTML).replace(/\r\n|\r|\n/g,"
");var d,c,a;d=document.getElementById("contentWithLinks");c="content wordwrap";a=d.className.split(" ");if(a.indexOf(c)==-1){d.className+=" "+c}if(f){d.style.border="none"}}function toggleView(b){var a=document.getElementById("content");b.innerHTML=(b.innerHTML=="view")?"edit":"view";if(a.style.display==="none"){a.style.display="block";var c=document.getElementById("contentWithLinks");c.parentNode.removeChild(c)}else{a.style.display="none";CreateViewable()}}function viewOnly(){var a=document.getElementById("content");a.style.display="none";CreateViewable(true)}document.onkeydown=function(a){a=a||window.event;if(a.keyCode==27){if(document.getElementById("password_modal").className=="modal_password show-modal"){toggleModal_Password();return}if(document.getElementById("content").style.display=="none"){toggleView(document.getElementById("a_view"))}}}; -------------------------------------------------------------------------------- /modules/lastsaved.php: -------------------------------------------------------------------------------- 1 | 2 |
">
5 | 6 | 7 | -------------------------------------------------------------------------------- /modules/menu.php: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 29 | 30 | ".PHP_EOL; ?> 31 | -------------------------------------------------------------------------------- /modules/password.php: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /modules/protect.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | <?php if (isset($_GET['note'])) print $_GET['note']; ?> 17 | 18 | 19 |
20 |
21 | 23 | Invalid password 24 | 26 | Password to access:
27 | 28 | 29 |

This note has a password
Create a new note

30 | 31 |

">View as Read Only

32 | 33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /nginx.conf.example: -------------------------------------------------------------------------------- 1 | # this is just an example of what is reuired in the nginx.conf file to support url rewrite 2 | # ensure the location matches the location minimal-web-notepad is served from 3 | # example based on solution from @eonegh - see https://github.com/domOrielton/minimal-web-notepad/issues/4 4 | 5 | location / { 6 | if ($query_string ~ "^view") { 7 | rewrite ^/([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*)$ /view.php?note=$1 last; 8 | } 9 | if ($query_string ~ "^simple") { 10 | rewrite ^/([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*)$ /simple.php?note=$1 last; 11 | } 12 | rewrite ^/([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*)$ /index.php?note=$1 last; 13 | } 14 | } 15 | add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive"; 16 | -------------------------------------------------------------------------------- /notelist.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | = $lengths[$j] && $j < count($lengths)-1; $j++) { 38 | $difference /= $lengths[$j]; 39 | } 40 | 41 | $difference = round($difference); 42 | 43 | if ($difference != 1) { 44 | $periods[$j].= "s"; 45 | } 46 | 47 | return "$difference $periods[$j] $tense "; 48 | } 49 | 50 | function human_filesize($bytes, $decimals = 2) 51 | { 52 | // from user contribs on php filesize page 53 | $sz = 'bkMGTP'; 54 | $szWords = array('bytes','Kb','Mb','Gb','Tb','Pb'); 55 | $factor = floor((strlen($bytes) - 1) / 3); 56 | if (@$sz[$factor] == 'b') { 57 | $decimals = 0; 58 | } 59 | return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' .@$szWords[$factor]; 60 | } 61 | 62 | ?> 63 | 64 | 65 | 66 | Note list 67 | 68 | 69 | 82 | 83 | 84 | New note

85 | 86 | 87 | 88 | 89 |
90 | 91 | 92 | 93 | 94 | 95 | $counterMax) { 101 | echo ""; 102 | break; //have a max number of notes to show 103 | } 104 | echo ""; 105 | echo ""; 106 | echo "
NameLast ModifiedFile Size
Max number of notes to list reached (". $counterMax . ")
".$value . " " . ago(filemtime($data_directory.'/'.$value)) . "" . human_filesize(filesize($data_directory.'/'.$value)) . "" . PHP_EOL; 107 | $counter++; 108 | } 109 | ?> 110 |
111 | 112 | 113 | -------------------------------------------------------------------------------- /passwordHelp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Notes - password protection 8 | 18 | 19 | 20 | 21 | Some information on the password protection of notes: 22 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /setup.php: -------------------------------------------------------------------------------- 1 | '; 13 | 14 | // make sure there is an .htaccess file there 15 | // if there is not then create one 16 | $file = $data_directory .'/.htaccess'; 17 | if (makehtaccess($file)) { echo '.htaccess file ok in the ' . $data_directory. ' data directory
'; } else { echo '.htaccess file exists in the ' . $data_directory .' data directory
'; } 18 | } else { 19 | echo $data_directory . ' folder does not exist and could not be created
'; 20 | } 21 | 22 | // based on https://raw.githubusercontent.com/bolt/htaccess_tester/master/htaccess_tester.php 23 | if (is_readable(__DIR__.'/.htaccess') ) { 24 | 25 | echo "

The .htaccess file exists in this folder and is readable to the webserver. These are its contents which should match the repos version:

\n
";
26 |     echo file_get_contents(__DIR__.'/.htaccess');
27 |     echo "
"; 28 | 29 | } else { 30 | 31 | echo "

Error: The .htaccess file does not exist in this folder or it is not readable to the webserver.

Ensure a .htaccess file is here and matches the repos version. Make sure it is readable to the webserver.

"; 32 | die(); 33 | 34 | } 35 | 36 | // https://stackoverflow.com/questions/109188/how-do-i-check-if-a-directory-is-writeable-in-php 37 | if ( ! is_writable(dirname($data_directory))) { 38 | echo dirname($data_directory) . ' must be writable! Please change the permissions to 0744 or 0755
'; 39 | } else { 40 | echo $data_directory . ' is writable
'; 41 | } 42 | 43 | 44 | // https://stackoverflow.com/questions/2303372/create-a-folder-if-it-doesnt-already-exist 45 | function makeDir($path) 46 | { 47 | return is_dir($path) || mkdir($path, 0744); 48 | } 49 | 50 | // https://stackoverflow.com/questions/20580017/php-create-a-file-if-not-exists 51 | function makehtaccess($file) 52 | { 53 | if(!is_file($file)){ 54 | $contents = 'Deny from all'; // deny all 55 | return file_put_contents($file, $contents); // save content to the file 56 | } 57 | } 58 | ?> 59 | -------------------------------------------------------------------------------- /simple.php: -------------------------------------------------------------------------------- 1 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | <?php print $_GET['note']; ?> 62 | 63 | 64 | 65 | 66 |
67 | 68 | 71 |
72 |

73 |     
74 |     
75 |     
76 |   	
82 |     
83 |     
84 | 
85 | 
86 | 


--------------------------------------------------------------------------------
/view.php:
--------------------------------------------------------------------------------
 1 | 
22 | 
23 | 
24 |     
25 |     
26 | 		
27 |     <?php print $_GET['note']; ?>
28 |     
29 |     
30 |     
31 | 
32 | 
33 |   
34 |
37 |
38 |
39 | 42 | 43 | 44 | --------------------------------------------------------------------------------