From 3149e78fb62b206def8dbdea4fd08534f3422b3c Mon Sep 17 00:00:00 2001 From: Jesper Wrang Date: Sun, 10 Sep 2017 22:14:30 +0200 Subject: [PATCH] Requests to pushshift is now client-side. More feedback to users when loading. --- server.go | 78 +------ static/done.png | Bin 0 -> 2571 bytes static/loading.gif | Bin 0 -> 70036 bytes static/script.js | 494 ++++++++++++++++++++++++------------------ static/style.css | 57 ++++- templates/header.html | 9 +- templates/thread.html | 6 +- 7 files changed, 345 insertions(+), 299 deletions(-) create mode 100644 static/done.png create mode 100644 static/loading.gif diff --git a/server.go b/server.go index 9fca51b..1b9342a 100644 --- a/server.go +++ b/server.go @@ -20,11 +20,9 @@ import ( // consts const ( - userAgent = "Javascript:" + appName + ":" + version + "s (by /u/" + userName + ")" - tokenURL = "https://www.reddit.com/api/v1/access_token" - commentIDsURL = "https://api.pushshift.io/reddit/submission/comment_ids/" - commentsURL = "https://api.pushshift.io/reddit/comment/search?ids=" - tmplFolder = "templates/" + userAgent = "Javascript:" + appName + ":" + version + "s (by /u/" + userName + ")" + tokenURL = "https://www.reddit.com/api/v1/access_token" + tmplFolder = "templates/" ) var ( @@ -49,25 +47,6 @@ type tokenResponse struct { Scope string `json:"scope"` } -type pushshiftIDsAPI struct { - Data []string `json:"data"` -} - -type pushshiftCommentsAPI struct { - Data []pushshiftComment `json:"data"` -} - -type pushshiftComment struct { - Author string `json:"author"` - AuthorFlairText string `json:"author_flair_text"` - Body string `json:"body"` - CreatedUTC int32 `json:"created_utc"` - ID string `json:"id"` - IsSubmitter bool `json:"is_submitter"` - ParentID string `json:"parent_id"` - Score int32 `json:"score"` -} - type threadPageData struct { Token string Subreddit string @@ -85,7 +64,6 @@ func main() { http.HandleFunc("/", pageHandler(mainHandler)) http.HandleFunc("/r/", pageHandler(threadHandler)) - http.HandleFunc("/comments/", pageHandler(commentHandler)) // Run locally for debugging/testing if debugMode { @@ -173,65 +151,21 @@ func threadHandler(w http.ResponseWriter, r *http.Request) { return } - resp, _ := client.Get(commentIDsURL + pathParts[4]) - if resp.StatusCode != 200 { - handleError(w, "Trouble getting removed comments from pushshift") - return - } - token, err := getAPIToken() if err != nil { handleError(w, err.Error()) return } - body, err := ioutil.ReadAll(resp.Body) - var dataStructure pushshiftIDsAPI - json.Unmarshal(body, &dataStructure) - data := &threadPageData{ - Token: token, - Subreddit: pathParts[2], - ThreadID: pathParts[4], - CommentIDs: dataStructure.Data, + Token: token, + Subreddit: pathParts[2], + ThreadID: pathParts[4], } renderTemplate(w, "thread", data) } -func commentHandler(w http.ResponseWriter, r *http.Request) { - commentIDs := r.FormValue("c") - if commentIDs == "" { - handleError(w, "Not enough arguments provided") - return - } - - resp, err := client.Get(commentsURL + commentIDs) - - if err != nil { - handleError(w, "Trouble getting removed comments from pushshift1") - return - } - - body, _ := ioutil.ReadAll(resp.Body) - var c pushshiftCommentsAPI - err = json.Unmarshal(body, &c) - - if err != nil { - handleError(w, "Trouble parsing removed comments from pushshift2") - return - } - - jsonString, err := json.Marshal(c) - - if err != nil { - handleError(w, "Trouble parsing removed comments from pushshift3") - return - } - - fmt.Fprintf(w, string(jsonString)) -} - func getAPIToken() (string, error) { req, _ := http.NewRequest("POST", tokenURL, strings.NewReader(tokenData.Encode())) req.Header.Add("User-Agent", userAgent) diff --git a/static/done.png b/static/done.png new file mode 100644 index 0000000000000000000000000000000000000000..722eef8119325802f6b5f47d9ea4129b09484623 GIT binary patch literal 2571 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 thread.json()) +async function loadPage() { + loadingImage.src = loadingImageSrc + setLoadingText("Loading thread...") - generate_thread(json) - comment_ids.push(...get_comment_ids(json)) + // Get thread from reddit and all comment IDs (inc. removed ones) from pushshift + const requests = [ + fetch(redditThreadURL, redditInit).then(json), + fetch(pushshiftIDsURL).then(json) + ] - await get_morechildren_ids() - - const smaller_threads = await Promise.all(continuethisthread_ids.map(id =>fetch(thread_url + "/_/" + id.split("_")[1], reddit_init))) - const smaller_threads_json = await Promise.all(smaller_threads.map(x => x.json())) - smaller_threads_json.forEach(thread => comment_ids.push(...get_comment_ids(thread))) + // Bottleneck here is allways the reddit api + const [thread, allCommentIDs] = await Promise.all(requests) - const removed_comments = await get_removed_comments() - generate_comments(removed_comments) + generateThread(thread) + + // Get all the comments from the first request + setLoadingText("Loading comments from reddit...") + commentIDs.push(...getCommentIDs(thread)) + + // Recursivly extract all IDs from "show more"-comments + await getMorechildrenIDs() + + // Do same thing for "continue this thread"-comments + const smallerThreads = await Promise.all(continuethisthreadIDs.map(id => fetch(redditThreadURL + "/_/" + id.split("_")[1], redditInit).then(json))) + //const smallerThreadsJson = await Promise.all(smallerThreads.map(x => x.json())) + smallerThreads.forEach(thread => commentIDs.push(...getCommentIDs(thread))) + + setLoadingText("Getting removed comments...") + const removedComments = await getRemovedComments(allCommentIDs.data) + + setLoadingText("Generating comments...") + generateComments(removedComments) + + setLoadingText("") + loadingImage.src = doneImageSrc } // ------------------------------------------------------------------------------ // ----------------------- Functions for getting IDs ---------------------------- // ------------------------------------------------------------------------------ -async function get_removed_comments() { - const ids_diff = all_ids.filter(x => !comment_ids.includes(x)) - generate_comment_info(ids_diff.length) - console.log("All ids :", all_ids) - console.log("Comments ids (dup): ", comment_ids) - console.log("With/without dups: ", comment_ids.length, new Set(comment_ids).size) - console.log("Difference: ", ids_diff) - console.log("Lookup size:", comment_lookup.size) - return fetch_multiple(removed_comments_url, ids_diff, null, ["data"], true) +function getCommentIDs(thread) { + return flattenArray(thread[1].data.children.map(extractIDFromComment)) } -async function get_morechildren_ids() { - //const responses = await Promise.all(morechildren_ids.map(id_array => fetch(morechildren_url + id_array.join(), reddit_init))) - const responses_arrays = await Promise.all(morechildren_ids.map(id_array => fetch_multiple(morechildren_url, id_array, reddit_init))) - const responses = flatten_array(responses_arrays) - morechildren_ids.length = 0 - responses.forEach(extract_morechildren_ids) - - if(morechildren_ids.length !== 0) { - await get_morechildren_ids() - } -} - -function extract_morechildren_ids(comments) { - comments.jquery[14][3][0].forEach(comment => { - if(comment.kind == "more") { - if(comment.data.id === "_") { - console.log("From more, continue: " + comment.data.parent_id) - continuethisthread_ids.push(comment.data.parent_id) - } else { - - const children = comment.data.children - - if(children.length < comment.data.count) { - morechildren_ids.push(children) - } - comment_ids.push(...children) - } - } else { - comment_ids.push(comment.data.id) - comment_lookup.set(comment.data.id, comment.data) - } - }); -} -function get_comment_ids(data) { - return flatten_array(data[1].data.children.map(extract_id_from_comment)) -} - -function extract_id_from_comment(comment) { +function extractIDFromComment(comment) { const data = comment.data // "Show more"-comments - if(comment.kind == "more") { - //console.log(data.id) - //console.log(data.children) - //console.log(data.children.length + "/" + data.count) - //console.log("-----------------------") - - if(data.id === "_") { + if(comment.kind == "more") { + if(data.id === "_") { // = "continue this thread"-comment console.log("From thread countinue: "+ data.parent_id) - continuethisthread_ids.push(data.parent_id) - return [] - } else if(data.children.length < data.count){ - morechildren_ids.push(data.children) - // ???????????????????? maybe wrong, data.id? ytest this - //return [] //----------------------------------- + continuethisthreadIDs.push(data.parent_id) + } else if(data.children.length < data.count){ // "Load more"-comment (that is missing some of its children) + morechildrenIDs.push(data.children) } return data.children } // Normal comment - let replies_ids = [data.id] + const repliesIDs = [data.id] if(data.replies) { - data.replies.data.children.forEach(child => replies_ids.push(...extract_id_from_comment(child))) + data.replies.data.children.forEach(child => repliesIDs.push(...extractIDFromComment(child))) delete data.replies } - comment_lookup.set(data.id, data) - return replies_ids + commentLookup.set(data.id, data) + return repliesIDs } +async function getMorechildrenIDs() { + //const responses = await Promise.all(morechildren_ids.map(id_array => fetch(morechildren_url + id_array.join(), reddit_init))) + const responsesArrays = await Promise.all(morechildrenIDs.map(idArray => fetchMultiple(redditMorechildrenURL, idArray, redditInit))) + const responses = flattenArray(responsesArrays) + morechildrenIDs.length = 0 + responses.forEach(extractMorechildrenIDs) + + if(morechildrenIDs.length !== 0) { + await getMorechildrenIDs() + } +} + +function extractMorechildrenIDs(comments) { + comments.jquery[14][3][0].forEach(comment => { + if(comment.kind == "more") { + if(comment.data.id === "_") { + console.log("From more, continue: " + comment.data.parent_id) + continuethisthreadIDs.push(comment.data.parent_id) + } else { + const children = comment.data.children + + if(children.length < comment.data.count) { + morechildrenIDs.push(children) + } + commentIDs.push(...children) + } + } else { + commentIDs.push(comment.data.id) + commentLookup.set(comment.data.id, comment.data) + } + }); +} + + +async function getRemovedComments(allCommentIDs) { + const idsDiff = allCommentIDs.filter(x => !commentIDs.includes(x)) + generateCommentInfo(idsDiff.length) + console.log("All ids :", allCommentIDs) + console.log("Comments ids (dup): ", commentIDs) + console.log("With/without dups: ", commentIDs.length, new Set(commentIDs).size) + console.log("Difference: ", idsDiff) + console.log("Lookup size:", commentLookup.size) + return fetchMultiple(pushshiftCommentsURL, idsDiff, null, ["data"], true) +} + + // ------------------------------------------------------------------------------ // ----------------------- Comment generating functions ------------------------- // ------------------------------------------------------------------------------ -async function generate_comments(removed_comments) { - const comment_section = document.createElement("div") - comment_section.id = thread_id - main_div.insertBefore(comment_section, loading_comments) +async function generateComments(removedComments) { + const commentSection = generateHTML(`
`) + mainDiv.appendChild(commentSection) - removed_comments.forEach(comment => { + removedComments.forEach(comment => { comment["removed"] = true - comment_lookup.set(comment.id, comment) + commentLookup.set(comment.id, comment) }) - const comments_to_lookup = removed_comments.map(comment => comment.id) - await get_comments_to_generate(comments_to_lookup) - - comments_to_create.sort(function(a, b) { - const a_score = comment_lookup.get(a).score - const b_score = comment_lookup.get(b).score + const commentsToLookup = removedComments.map(comment => comment.id) + await getCommentsToGenerate(commentsToLookup) + + commentsToCreate.sort(function(a, b) { + const aScore = commentLookup.get(a).score + const bScore = commentLookup.get(b).score - return a_score === b_score ? 0 : (a_score > b_score ? 1 : -1) + return aScore === bScore ? 0 : (aScore > bScore ? 1 : -1) }); - create_comments() + createComments() } -async function get_comments_to_generate(comments_to_lookup) { - const new_comments_to_lookup = [] - - comments_to_lookup.forEach(id => { - const parent_id = comment_lookup.get(id).parent_id.split("_")[1] +async function getCommentsToGenerate(commentsToLookup) { + const newCommentsToLookup = [] + const commentsToFetch = [] - if(parent_id === thread_id) {} // Has no parent (is parent of thread) - else if(comments_to_create.includes(parent_id)) {} // Parent already exists, do nothing - else if(new_comments_to_lookup.includes(parent_id)) {} // Parent already exists (this iteration) - else if(comment_lookup.has(parent_id)) { - new_comments_to_lookup.push(parent_id) + commentsToLookup.forEach(id => { + const parentID = commentLookup.get(id).parent_id.split("_")[1] + + if(parentID === threadID) {} // Has no parent (is parent of thread) + else if(commentsToCreate.includes(parentID)) {} // Parent already exists, do nothing + else if(newCommentsToLookup.includes(parentID)) {} // Parent already exists (this iteration) + else if(commentLookup.has(parentID)) { + newCommentsToLookup.push(parentID) } else{ - console.error("Comment doesn't exists, HALP") - console.log(id) - console.log(parent_id) + console.error("Comment doesn't exists, but lets try it anyway :D") + console.log("ID: " + id) + console.log("Parent: " + parent_id) + + commentsToFetch.push(parentID) } - comments_to_create.push(id) + commentsToCreate.push(id) }) - if(new_comments_to_lookup.length !== 0) { - await get_comments_to_generate(new_comments_to_lookup) + if(commentsToFetch.length !== 0) { + //await + console.log("fuck it") + } + + if(newCommentsToLookup.length !== 0) { + await getCommentsToGenerate(newCommentsToLookup) } } -function create_comments() { - const created_comment_ids = [thread_id] - let id, parent_id - let did_something +function createComments() { + const createdCommentIDs = [threadID] + let id, parentID + let didSomething - while(comments_to_create.length > 0) { - did_something = false + while(commentsToCreate.length > 0) { + didSomething = false - for(let i = comments_to_create.length -1; i >= 0; i--) { - id = comments_to_create[i] - parent_id = comment_lookup.get(id).parent_id.split("_")[1] + for(let i = commentsToCreate.length -1; i >= 0; i--) { + id = commentsToCreate[i] + parentID = commentLookup.get(id).parent_id.split("_")[1] - if(created_comment_ids.includes(parent_id)) { - document.getElementById(parent_id).appendChild(create_comment(comment_lookup.get(id))) - created_comment_ids.push(id) - comments_to_create.splice(i, 1) - did_something = true + if(createdCommentIDs.includes(parentID)) { + document.getElementById(parentID).appendChild(createComment(commentLookup.get(id))) + createdCommentIDs.push(id) + commentsToCreate.splice(i, 1) + didSomething = true } } - if(!did_something) { + // Fail safe (parents missing for all comments left, should happend but oh well :D) + if(!didSomething) { console.error("Didn't generate all comments correctly") break } @@ -206,35 +239,40 @@ function create_comments() { // ------------------------- HTML-generating functions -------------------------- // ------------------------------------------------------------------------------ -function generate_thread(data) { +function generateThread(data) { const thread = data[0].data.children[0].data - const thread_div = document.createElement("div") - thread_div.id = "thread" - thread_div.innerHTML = ` -
-
-
${pretty_score(thread.score)}
-
-
-
-
- ${thread.link_flair_text !== null ? ''+thread.link_flair_text+'' : ''} - ${thread.title} - (${thread.domain}) -
- submitted ${pretty_date(thread.created_utc)} by - ${thread.author} to - /r/${subreddit} + totalComments = thread.num_comments + + const threadDiv = document.createElement("div") + threadDiv.id = "thread" + threadDiv.innerHTML = ` +
+
+
+
${prettyScore(thread.score)}
+
+
+
+
+ ${thread.link_flair_text !== null ? ''+thread.link_flair_text+'' : ''} + ${thread.title} + (${thread.domain}) +
+ submitted ${prettyDate(thread.created_utc)} by + ${thread.author} to + /r/${subreddit} +
+ ${thread.selftext !== '' ? '
'+markdown.render(thread.selftext)+'
':''} +
${totalComments} comments
+ ${thread.media !== null ? parseHTML(thread.media_embed.content) : ''} + ${imageHosts.includes(thread.domain) ? '' : ''}
- ${thread.selftext !== '' ? '
'+markdown.render(thread.selftext)+'
':''}
` - - main_div.insertBefore(thread_div, loading_comments) - total_comments = thread.num_comments + mainDiv.appendChild(threadDiv) } -function create_comment(comment) { - const comment_div = document.createElement("div") +function createComment(comment) { + /*const comment_div = document.createElement("div") comment_div.id = comment.id comment_div.className = "comment comment-" + (comment.hasOwnProperty("removed") ? "removed" : (comment.depth % 2 == 0 ? "even" : "odd")); comment_div.innerHTML = ` @@ -249,85 +287,121 @@ function create_comment(comment) { permalink
` - return comment_div + return comment_div*/ + return generateHTML(` +
+
+ [–] + ${comment.author} + ${prettyScore(comment.score)} point${(comment.score == 1) ? '': 's'} + ${prettyDate(comment.created_utc) } +
+
${markdown.render(comment.body)}
+ +
+ `) } -function generate_comment_info(removed_comments) { - const comment_info = document.createElement("div") - comment_info.innerHTML = ` -
- removed comments: ${removed_comments}/${total_comments} (${ (100 * removed_comments / total_comments).toFixed(1)}%) +function generateCommentInfo(removedCommentsAmount) { + mainDiv.appendChild(generateHTML(` +
+
+ removed comments: ${removedCommentsAmount}/${totalComments} (${ (100 * removedCommentsAmount / totalComments).toFixed(1)}%) +
+
sorted by: top
-
sorted by: top
- ` - main_div.insertBefore(comment_info, loading_comments) + `)) } +// Works for everything except iframes/script-tags, use "old fashion"-way in those cases (createElement etc...) +function generateHTML(html) { + return htmlParser.parseFromString(html, "text/html").documentElement +} + +// "<" => "<" +function parseHTML(html) { + const dummy = document.createElement('div') + dummy.innerHTML = html + return dummy.childNodes.length === 0 ? "" : dummy.childNodes[0].nodeValue; +} + + // ------------------------------------------------------------------------------ // ----------------------------- AJAX-functions --------------------------------- // ------------------------------------------------------------------------------ -async function fetch_multiple(url, data, init, json_walkdown=[], flattening=false) { - const responses = await Promise.all(split_array(data, max_ids_per_call).map(single_request_data => fetch(url + single_request_data.join(), init))) +async function fetchMultiple(url, data, init, jsonWalkdown=[], flattening=false) { + const responses = await Promise.all(splitArray(data, maxIDsPerRequest).map(singleRequestData => fetch(url + singleRequestData.join(), init))) let json = await Promise.all(responses.map(x => x.json())) - json_walkdown.forEach(property =>{ + jsonWalkdown.forEach(property =>{ json.forEach((x, i) => json[i] = json[i][property]) }) - return flattening ? flatten_array(json) : json + return flattening ? flattenArray(json) : json } // ------------------------------------------------------------------------------ // ---------------------- Other less interesting functions ---------------------- // ------------------------------------------------------------------------------ -function split_array(array, size) { - const array_split = [] +function splitArray(array, size) { + const arraySplit = [] for(let i = 0, len = array.length ; i < len; i += size) { - array_split.push(array.slice(i, i + size)) + arraySplit.push(array.slice(i, i + size)) } - return array_split + return arraySplit } -function flatten_array(arrays) { +function flattenArray(arrays) { return arrays.reduce((array, item) => { return array.concat(item) }, []) } -// UTC -> "Reddit time format" -function pretty_date(created_utc) { - const current_utc = Math.floor((new Date()).getTime() / 1000) - const second_diff = current_utc - created_utc - const day_diff = Math.floor(second_diff / 86400) - - if(day_diff < 0) - return '' - - if(day_diff == 0) { - if(second_diff < 10) - return "just now" - if(second_diff < 60) - return second_diff + " seconds ago" - if(second_diff < 120) - return "a minute ago" - if(second_diff < 3600) - return Math.floor(second_diff / 60) + " minutes ago" - if(second_diff < 7200) - return "an hour ago" - if(second_diff < 86400) - return Math.floor(second_diff / 3600) + " hours ago" - } - if(day_diff == 1) - return "Yesterday" - if(day_diff < 7) - return day_diff + " days ago" - if(day_diff < 31) - return Math.floor(day_diff / 7) + " weeks ago" - if(day_diff < 365) - return Math.floor(day_diff / 30) + " months ago" - return Math.floor(day_diff / 365) + " years ago" +function setLoadingText(text) { + loadingText.innerHTML = text } -function pretty_score(score) { +function json(x) { + return x.json() +} + +// UTC -> "Reddit time format" (5 hours ago, just now, etc...) +function prettyDate(createdUTC) { + const currentUTC = Math.floor((new Date()).getTime() / 1000) + const secondDiff = currentUTC - createdUTC + const dayDiff = Math.floor(secondDiff / 86400) + + if(dayDiff < 0) + return '' + + if(dayDiff == 0) { + if(secondDiff < 10) + return "just now" + if(secondDiff < 60) + return secondDiff + " seconds ago" + if(secondDiff < 120) + return "a minute ago" + if(secondDiff < 3600) + return Math.floor(secondDiff / 60) + " minutes ago" + if(secondDiff < 7200) + return "an hour ago" + if(secondDiff < 86400) + return Math.floor(secondDiff / 3600) + " hours ago" + } + if(dayDiff == 1) + return "Yesterday" + if(dayDiff < 7) + return dayDiff + " days ago" + if(dayDiff < 31) + return Math.floor(dayDiff / 7) + " weeks ago" + if(dayDiff < 365) + return Math.floor(dayDiff / 30) + " months ago" + return Math.floor(dayDiff / 365) + " years ago" +} + +// 12000 => 1.2k +function prettyScore(score) { return score >= 10000 ? (score / 1000).toFixed(1) + "k" : score } \ No newline at end of file diff --git a/static/style.css b/static/style.css index ca8255f..d882ca5 100644 --- a/static/style.css +++ b/static/style.css @@ -43,18 +43,21 @@ a:hover { #header { background-color: #ca302c;/*#19171c;/*#181818*/ - padding: 20px 10px 10px; + display: flex; + justify-content: space-between; + padding: 10px; } -#header a { +#header-title-link { + align-self: center; color: #fff;/*#ddd;*/ transition: color 0.3s; + margin: 12px 0; } -#header a:hover { +#header-title-link:hover { color: #FF8B88;/*#ca302c*/; text-decoration: none; - } #header-title { @@ -62,6 +65,24 @@ a:hover { display: inline; } + + +#loading { + color: #fff; + display: flex; + align-items: center; +} + +#loading-text { + margin: 0 20px 0 0; +} + +#loading-image { + height: 64px; + width: 64px; + border-radius: 32px; +} + #main { margin: 15px; } @@ -94,6 +115,7 @@ a:hover { #thread { display: flex; + flex-grow: 1; } @@ -133,15 +155,13 @@ a:hover { float: left; font-size: 13px; font-weight: bold; - text-align: center; - + text-align: center; } #thread-content { flex: 1; float: left; margin-left: 3px; - max-width: 840px; } .link-flair { @@ -174,16 +194,29 @@ a:hover { margin-top: 2px; } +#total-comments { + color: #828282; + font-size: 10px; + margin: 4px 0; +} + #thread-selftext { border: 1px solid #666; - border-radius: 7px; - margin: 5px 0; - padding: 5px 10px; + border-radius: 7px; + margin: 5px 0 7px; + padding: 5px 10px; + max-width: 840px; +} + +#thread-image { + max-width: 768px; + max-height: 768px; } #comment-info { color: #ca302c; margin-top: 10px; + font-weight: bold; padding-bottom: 5px; border-bottom: 1px dotted #808080; font-size: 16px; @@ -196,7 +229,7 @@ a:hover { } .comment { - margin: 0 8px 8px 0; + margin: 0 0 8px 0; padding: 5px 8px 5px /*14px*/ 30px; border: 1px solid #333; border-radius: 3px; @@ -237,7 +270,7 @@ a:hover { } .comment-removed { - background-color: #633636; + background-color: #840c09/*#633636*/; } .comment-odd { diff --git a/templates/header.html b/templates/header.html index 33e0144..7be3867 100644 --- a/templates/header.html +++ b/templates/header.html @@ -3,11 +3,16 @@ Removeddit - + +
\ No newline at end of file diff --git a/templates/thread.html b/templates/thread.html index 818a100..f696ea6 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -1,9 +1,9 @@
- \ No newline at end of file + + \ No newline at end of file