diff --git a/lib/helper/helper_main.go b/lib/helper/helper_main.go index bb82982..edd4f15 100644 --- a/lib/helper/helper_main.go +++ b/lib/helper/helper_main.go @@ -16,6 +16,28 @@ func GetDomainBase() string { return domainBase } +func GetMaximumPadSize() int { + // Lookup if the maximum pad size variable exists. + maxPadSize, exists := os.LookupEnv("MAXIMUM_PAD_SIZE") + + // Check if this environment variable has bee nset + if !exists { + // Set the variable ourselves to the default string value + maxPadSize = "524288" + } + + // Try and convert the string into an integer + rez, err := strconv.Atoi(maxPadSize) + // Check if the conversion has failed + if err != nil { + // Simply return the default + return 524288 + } + + // Return the resulting value + return rez +} + func GetCacheMapLimit() int { cacheMapLimit, domainExists := os.LookupEnv("CACHE_MAP_LIMIT") diff --git a/lib/objects/objects_post.go b/lib/objects/objects_post.go index e41398d..035d9b5 100644 --- a/lib/objects/objects_post.go +++ b/lib/objects/objects_post.go @@ -1,10 +1,13 @@ package objects import ( + "errors" "fmt" "os" "path/filepath" "time" + + "github.com/JustKato/FreePad/lib/helper" ) type Post struct { @@ -73,6 +76,11 @@ func GetPost(fileName string) Post { func WritePost(p Post) error { + maximumPadSize := helper.GetMaximumPadSize() + if len(p.Content) > maximumPadSize { + return errors.New("The pad is too big, please limit to the maximum of " + fmt.Sprint(maximumPadSize) + " characters") + } + // Get the base storage directory and make sure it exists storageDir := getStorageDirectory() diff --git a/lib/routes/routes_home.go b/lib/routes/routes_home.go index 5bb64e2..ecc88cd 100644 --- a/lib/routes/routes_home.go +++ b/lib/routes/routes_home.go @@ -23,6 +23,9 @@ func HomeRoutes(router *gin.Engine) { // Get the post we are looking for. postName := c.Param("post") + // Get the maximum pad size, so that we may notify the client-side to match server-side + maximumPadSize := helper.GetMaximumPadSize() + // Sanitize the postName newPostName, err := url.QueryUnescape(postName) if err == nil { @@ -33,10 +36,11 @@ func HomeRoutes(router *gin.Engine) { post := objects.GetPost(postName) c.HTML(200, "page.html", gin.H{ - "title": postName, - "post_content": post.Content, - "last_modified": post.LastModified, - "domain_base": helper.GetDomainBase(), + "title": postName, + "post_content": post.Content, + "maximumPadSize": maximumPadSize, + "last_modified": post.LastModified, + "domain_base": helper.GetDomainBase(), }) }) diff --git a/static/js/fileSaver.js b/static/js/fileSaver.js new file mode 100644 index 0000000..6d493b2 --- /dev/null +++ b/static/js/fileSaver.js @@ -0,0 +1,3 @@ +(function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,"undefined"!=typeof module&&(module.exports=g)}); + +//# sourceMappingURL=FileSaver.min.js.map \ No newline at end of file diff --git a/static/js/pad-scripts.js b/static/js/pad-scripts.js new file mode 100644 index 0000000..1fb819f --- /dev/null +++ b/static/js/pad-scripts.js @@ -0,0 +1,186 @@ + +function sendMyData(el) { + const formData = new FormData(); + + // Check if the writing watch was sending something already + if ( !!window.writingWatch ) { + // Clear old timeout + clearTimeout(window.writingWatch); + } + + if ( el.value.length > maximumPadSize ) { + let err = new Error(`Your Pad is too big! Please keep it limited to ${maximumPadSize} characters!`); + alert(err); + throw err; + } + + el.setAttribute(`readonly`, `1`); + + formData.set("content", el.value); + + updateStatus(`Attempting to save...`, `text-warning`); + + fetch(window.location.href.toString(), { + body: formData, + method: "post", + }) + .then( resp => { + resp.json() + .then( e => { + document.getElementById(`last_modified_`).value = e.pad.last_modified; + updateStatus(`Succesfully Saved`, `text-success`); + }) + .catch( err => { + updateStatus(`Failed to Save`, `text-danger`); + console.error(err); + }) + }) + .catch( err => { + updateStatus(`Failed to Save`, `text-danger`); + console.error(err); + }) + .finally( () => { + el.removeAttribute(`readonly`); + }) +} + +function toggleWritingWatch(el) { + + // Check if the writing watch was sending something already + if ( !!window.writingWatch ) { + // Clear old timeout + clearTimeout(window.writingWatch); + } + + // Set a timeout for the action + window.writingWatch = setTimeout( () => { + // Send out the data + sendMyData(el) + }, 750) + +} + +function updateStatus(txt, cls) { + + const loading_status = document.getElementById(`loading_status`) + + loading_status.value = txt; + loading_status.classList.remove("text-danger", "text-warning", "text-success", "text-white", "text-primary"); + loading_status.classList.add(cls); +} + +function getLocalArchives() { + + let a = localStorage.getItem("archives"); + + // Check if we had anything in storage for the archives + if ( a == null ) { + // There were nothing in storage + return []; + } + + try { + // Try and parse the json + a = JSON.parse(a); + } catch ( err ) { + // Return null of the fail + return []; + } + + return a; +} + +function storeArchives(archives) { + + // Check if the provided list is an array + if ( !Array.isArray(archives) ) return; + + // Set the current archives + localStorage.setItem('archives', JSON.stringify(archives)); +} + +function renderArchivesSelection() { + + // Get the archives selection + const archivesSelection = document.getElementById(`archives-selection`); + const rowTemplate = document.getElementById(`archive-selection-example`); + // Clear any old optiosn + archivesSelection.querySelectorAll(`.dropdown-item:not(#do-archive-button):not(#archive-selection-example)`).forEach( el => { + // Remove the element + el.remove(); + }) + + // Get the current list of available archives + for ( let a of getLocalArchives() ) { + // Clone the template row + const row = rowTemplate.cloneNode(true); + + // Remove the id from the row + row.removeAttribute(`id`); + // Append the row to the selection menu + archivesSelection.appendChild(row); + + const ts = new Date(a.ts); + + // Update the display date + row.querySelector(`.archive-date`).textContent = ts.toLocaleString(); + + // Add an event listener + row.addEventListener(`click`, e => { + + let resp = confirm("Load contents of pad from memory? This will overwrite the current pad for everyone."); + + if ( !!resp ) { + document.getElementById(`pad-content`).value = a.content; + } + }) + + } + +} + +function saveLocalArchive() { + let resp = confirm("Save a local copy of the current Pad?"); + + if ( !resp ) { + // Do not + return; + } + + // Get all of the previous archives, append this one to them + let myArchives = getLocalArchives(); + + myArchives.push({ + ts: new Date().getTime(), + content: document.getElementById(`pad-content`).value, + }); + + // Store the archives + storeArchives(myArchives); + + // Re-Render the archives selection + renderArchivesSelection(); + + // Save + alert(`Saved`); + +} + +document.addEventListener(`DOMContentLoaded`, e => { + + { // Textarea Focusing + const textarea = document.getElementById(`pad-content`); + + // Focus + textarea.focus(); + // Scroll + textarea.scrollTop = textarea.scrollHeight; + // Move cursor + textarea.setSelectionRange(textarea.value.length, textarea.value.length); + } + + { // Archives + renderArchivesSelection() + } + +}) \ No newline at end of file diff --git a/static/js/pad.js b/static/js/pad.js new file mode 100644 index 0000000..f32c252 --- /dev/null +++ b/static/js/pad.js @@ -0,0 +1,21 @@ +class Pad { + + title = ''; + content = ''; + timestmap = ''; + + constructor(t, ts) { + this.title = t; + this.content = document.getElementById(`pad-content`).value; + this.timestmap = ts; + } + + downloadPadContents() { + // Create a new blob of the contents of the pad + var blob = new Blob([ document.getElementById(`pad-content`).value ], { type: "text/plain;charset=utf-8" }); + + // Save the blob as + saveAs(blob, `${this.title}.txt`); + } + +} \ No newline at end of file diff --git a/templates/pages/index.html b/templates/pages/index.html index 28cbc52..c09dcac 100644 --- a/templates/pages/index.html +++ b/templates/pages/index.html @@ -28,10 +28,12 @@
diff --git a/templates/pages/page.html b/templates/pages/page.html index 18bf967..7627642 100644 --- a/templates/pages/page.html +++ b/templates/pages/page.html @@ -1,11 +1,25 @@ {{ template "inc/header.html" .}} + +
@@ -17,7 +31,9 @@
- +

{{.title}}

+ +
@@ -56,7 +72,57 @@
-