commit fef01747d8ea9c582be81d2561cdac2c2e9b72f5 Author: Jesper Wrang Date: Fri Aug 25 00:27:44 2017 +0200 Init commit diff --git a/Unsaved Document 1 b/Unsaved Document 1 new file mode 100644 index 0000000..0bc2414 --- /dev/null +++ b/Unsaved Document 1 @@ -0,0 +1,116 @@ +[ + { + 'data':{ + 'before':None, + 'after':None, + 'children':[ + { + 'data':{ + 'created_utc':1502406327.0, + 'created':1502435127.0, + 'subreddit_id':'t5_3njjl', + 'banned_at_utc':None, + 'collapsed_reason':None, + 'author_flair_text':None, + 'stickied':False, + 'subreddit_type':'public', + 'score_hidden':False, + 'edited':False, + 'banned_by':None, + 'replies':'', + 'score':1, + 'report_reasons':None, + 'approved_at_utc':None, + 'ups':1, + 'link_id':'t3_6sx4fk', + 'collapsed':False, + 'saved':False, + 'num_reports':None, + 'depth':0, + 'subreddit_name_prefixed':'r/TestForApp', + 'can_gild':True, + 'parent_id':'t3_6sx4fk', + 'is_submitter':True, + 'can_mod_post':False, + 'body':'asdsad', + 'archived':False, + 'removal_reason':None, + 'author':'Jubbeart', + 'mod_reports':[ + + ], + 'approved_by':None, + 'downs':0, + 'controversiality':0, + 'name':'t1_dlg744z', + 'subreddit':'TestForApp', + 'likes':None, + 'distinguished':None, + 'body_html':'

asdsad

\n
', + 'user_reports':[ + + ], + 'id':'dlg744z', + 'gilded':0, + 'author_flair_css_class':None + }, + 'kind':'t1' + }, + { + 'data':{ + 'created_utc':1502406332.0, + 'created':1502435132.0, + 'subreddit_id':'t5_3njjl', + 'banned_at_utc':None, + 'collapsed_reason':None, + 'author_flair_text':None, + 'stickied':False, + 'subreddit_type':'public', + 'score_hidden':False, + 'edited':False, + 'banned_by':None, + 'replies':'', + 'score':1, + 'report_reasons':None, + 'approved_at_utc':None, + 'ups':1, + 'link_id':'t3_6sx4fk', + 'collapsed':False, + 'saved':False, + 'num_reports':None, + 'depth':0, + 'subreddit_name_prefixed':'r/TestForApp', + 'can_gild':True, + 'parent_id':'t3_6sx4fk', + 'is_submitter':True, + 'can_mod_post':False, + 'body':'asdasdas1123123123\n', + 'archived':False, + 'removal_reason':None, + 'author':'Jubbeart', + 'mod_reports':[ + + ], + 'approved_by':None, + 'downs':0, + 'controversiality':0, + 'name':'t1_dlg749h', + 'subreddit':'TestForApp', + 'likes':None, + 'distinguished':None, + 'body_html':'

asdasdas1123123123

\n
', + 'user_reports':[ + + ], + 'id':'dlg749h', + 'gilded':0, + 'author_flair_css_class':None + }, + 'kind':'t1' + } + ], + 'modhash':'' + }, + 'kind':'Listing' + } +] diff --git a/Unsaved Document 2 b/Unsaved Document 2 new file mode 100644 index 0000000..81c55f2 --- /dev/null +++ b/Unsaved Document 2 @@ -0,0 +1,47 @@ +'created_utc':1502406327.0, + 'created':1502435127.0, + 'subreddit_id':'t5_3njjl', + 'banned_at_utc':None, + 'collapsed_reason':None, + 'author_flair_text':None, + 'stickied':False, + 'subreddit_type':'public', + 'score_hidden':False, + 'edited':False, + 'banned_by':None, + 'replies':'', + 'score':1, + 'report_reasons':None, + 'approved_at_utc':None, + 'ups':1, + 'link_id':'t3_6sx4fk', + 'collapsed':False, + 'saved':False, + 'num_reports':None, + 'depth':0, + 'subreddit_name_prefixed':'r/TestForApp', + 'can_gild':True, + 'parent_id':'t3_6sx4fk', + 'is_submitter':True, + 'can_mod_post':False, + 'body':'asdsad', + 'archived':False, + 'removal_reason':None, + 'author':'Jubbeart', + 'mod_reports':[ + + ], + 'approved_by':None, + 'downs':0, + 'controversiality':0, + 'name':'t1_dlg744z', + 'subreddit':'TestForApp', + 'likes':None, + 'distinguished':None, + 'body_html':'

asdsad

\n
', + 'user_reports':[ + + ], + 'id':'dlg744z', + 'gilded':0, + 'author_flair_css_class':None diff --git a/Unsaved Document 3 b/Unsaved Document 3 new file mode 100644 index 0000000..20e3865 --- /dev/null +++ b/Unsaved Document 3 @@ -0,0 +1,47 @@ +'created_utc':1502406332.0, + 'created':1502435132.0, + 'subreddit_id':'t5_3njjl', + 'banned_at_utc':None, + 'collapsed_reason':None, + 'author_flair_text':None, + 'stickied':False, + 'subreddit_type':'public', + 'score_hidden':False, + 'edited':False, + 'banned_by':None, + 'replies':'', + 'score':1, + 'report_reasons':None, + 'approved_at_utc':None, + 'ups':1, + 'link_id':'t3_6sx4fk', + 'collapsed':False, + 'saved':False, + 'num_reports':None, + 'depth':0, + 'subreddit_name_prefixed':'r/TestForApp', + 'can_gild':True, + 'parent_id':'t3_6sx4fk', + 'is_submitter':True, + 'can_mod_post':False, + 'body':'asdasdas1123123123\n', + 'archived':False, + 'removal_reason':None, + 'author':'Jubbeart', + 'mod_reports':[ + + ], + 'approved_by':None, + 'downs':0, + 'controversiality':0, + 'name':'t1_dlg749h', + 'subreddit':'TestForApp', + 'likes':None, + 'distinguished':None, + 'body_html':'

asdasdas1123123123

\n
', + 'user_reports':[ + + ], + 'id':'dlg749h', + 'gilded':0, + 'author_flair_css_class':None diff --git a/__pycache__/app.cpython-35.pyc b/__pycache__/app.cpython-35.pyc new file mode 100644 index 0000000..9e059f8 Binary files /dev/null and b/__pycache__/app.cpython-35.pyc differ diff --git a/__pycache__/devapp.cpython-35.pyc b/__pycache__/devapp.cpython-35.pyc new file mode 100644 index 0000000..61f2c5a Binary files /dev/null and b/__pycache__/devapp.cpython-35.pyc differ diff --git a/app.py b/app.py new file mode 100644 index 0000000..df61dd8 --- /dev/null +++ b/app.py @@ -0,0 +1,86 @@ +from flask import Flask, abort, request, render_template +import urllib.parse +import requests +import requests.auth +import datetime + +import json + +APP_NAME = 'TEST' +USERNAME = 'Jubbeart' +VERSION = '0.1' + +CLIENT_ID = '' +CLIENT_SECRET = '' +USER_AGENT = 'Javascript:{}:v{} (by /u/{})'.format(APP_NAME, VERSION, USERNAME) + +TOKEN_URL = 'https://www.reddit.com/api/v1/access_token' +API_URL = 'https://oauth.reddit.com/' +COMMENT_IDS_URL = 'https://api.pushshift.io/reddit/submission/comment_ids/{}' +COMMENTS_URL = 'https://api.pushshift.io/reddit/comment/search?ids={}' + +FILTER_BODY = [ + 'title', 'author', 'num_comments', + 'subreddit', 'score', 'upvote_ratio', + 'created_utc', 'locked', 'link_flair_text', + 'is_self', 'url', 'selftext_html', + 'id' +] + +FILTER_COMMENT = [ + 'score', 'author', 'created_utc', + 'body_html', 'id', 'controversiality', + + 'collapsed_reason', 'collapsed', +] + +app = Flask(__name__) + +@app.route('/') +def homepage(): + return render_template('frontpage.html') + + +@app.route('/r//comments/') +def thread(subreddit, thread_id): + thread_id = thread_id.split('/')[0] + comment_ids = requests.get(COMMENT_IDS_URL.format(thread_id)).json()["data"] + token = get_token() + + return render_template( + 'thread.html', + token=token, + comment_ids=comment_ids, + thread_id=thread_id, + subreddit=subreddit + ) + +@app.route('/comments/') +def comments(): + ids = None + + try: + ids = request.args.get('c') + except: + return '' + + if ids: + return requests.get(COMMENTS_URL.format(ids)).content + else: + return '' + + +def get_token(old_token=None): + post_data = {'grant_type': 'client_credentials'} + headers = {'User-Agent': USER_AGENT} + client_auth = requests.auth.HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET) + response = requests.post( + TOKEN_URL, + auth=client_auth, + data=post_data, + headers=headers + ) + return response.json()['access_token'] + +if __name__ == '__main__': + app.run(debug=True, port=65010) diff --git a/attributestosave b/attributestosave new file mode 100644 index 0000000..e69de29 diff --git a/devapp.py b/devapp.py new file mode 100644 index 0000000..4709dd7 --- /dev/null +++ b/devapp.py @@ -0,0 +1,2 @@ +from app import create_app +app = create_app() diff --git a/front-page b/front-page new file mode 100644 index 0000000..e9d86a5 --- /dev/null +++ b/front-page @@ -0,0 +1,17 @@ +fun image + +#About + +this site was inspired by Go1dfish's ceddit.com but it not meant as a replace for Reddit. + +#TODO + +#Thanks To: +bold(Jason) href=blog - making all this possible +/r/redditdev +markdown-shit + +#Links & Contact +Feedback, questions: removeddit [at] gmail.com +Source code: github@ + diff --git a/old_code b/old_code new file mode 100644 index 0000000..25d7d7f --- /dev/null +++ b/old_code @@ -0,0 +1,105 @@ +/* +function get_comments(json) { + var comments = []; + var comments_json = json[1].data.children; + + for(var i = 0; i < comments_json.length; i++) { + get_comment_data(comments_json[i], comments); + } + + return comments; +} + + +var stripped_comment_tags = ["author_flair_css_class", "approved_by", +"approved_at_utc", "archived", "author_flair_text", +"body_html", "banned_at_utc", "banned_by", +"can_gild", "can_mod_post", "created", "collapsed", "collapsed_reason", +"downs", "distinguished", "gilded", +"edited", "likes", "link_id", "name", "mod_reports", "num_reports", +"removal_reason", "report_reasons", +"saved", "subreddit", "subreddit_type", "subreddit_name_prefixed", "subreddit_id", +"ups", "user_reports"]; + + + + +// Strips unnecessary tags from comment, adds to comment array +// Do this recursivly to all replies +// Params comment:JSON object, comments: array +function get_comment_data(comment, comments) { + if(comment.kind !== "t1") { + alert(comment.kind); + return; + } + + var comment_data = comment.data; + + for(var i = 0; i < stripped_comment_tags.length; i++) { + delete comment_data[stripped_comment_tags[i]]; + } + + if(comment_data.replies) { + for(var i = 0; i < comment_data.replies.data.children.length; i++) { + get_comment_data(comment_data.replies.data.children[i], comments); + } + } + + if(comment_data.parent_id.indexOf("_") !== -1) { + comment_data.parent_id = comment_data.parent_id.split("_")[1]; + } + + delete comment_data["replies"]; + comments.push(comment_data); +} +*/ + + +old_code: + + + """headers = {'Authorization': 'bearer ' + token, 'User-Agent': USER_AGENT} + url = API_URL + THREAD_PATH.format(subreddit, thread_id) + + response = requests.get(url, headers=headers) + thread_json = None + + try: + thread_json = response.json() + except: + return 'Error parsing the thread' + + thread = { + 'body': get_thread_body(thread_json), + 'comments': get_thread_comments(thread_json) + } + + return str(get_thread_comments(thread_json)) + #return str(get_thread_comments(thread_json)) + '/' + str(thread['body']['num_comments']) + +def get_thread_body(thread_json): + body_json = thread_json[0]['data']['children'][0]['data'] + filtered_body = {k:v for k,v in body_json.items() if k in FILTER_BODY} + return filtered_body + +def get_thread_comments(thread_json): + comments_json = thread_json[1]['data']['children'] + filtered_comments = [] + + for comment_json in comments_json: + comment_data = get_comment_data(comment_json) + filtered_comments.append(comment_data) + + return filtered_comments + +def get_comment_data(comment): + comment_data = comment['data'] + filtered_comment = {k:v for k,v in comment_data.items() if k in FILTER_COMMENT} + filtered_comment['replies'] = [] + + if comment_data['replies']: + for child_comment in comment_data['replies']['data']['children']: + filtered_comment['replies'].append(get_comment_data(child_comment)) + + return filtered_comment + """ diff --git a/static/functions.js b/static/functions.js new file mode 100644 index 0000000..e69de29 diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000..3a5bc6b --- /dev/null +++ b/static/script.js @@ -0,0 +1,411 @@ +"use strict"; + +const markdown = SnuOwnd.getParser(); + +const thread_url = `https://oauth.reddit.com/r/${subreddit}/comments/${thread_id}`; +const morechildren_url = `https://oauth.reddit.com/api/morechildren?link_id=t3_${thread_id}&children=`; +const single_comment_url = `https://oauth.reddit.com/r/${subreddit}/api/info/?id=t1_`; +const removed_comments_url = "/comments/?c="; + +const main_div = document.getElementById("main"); + +const loading_comments = document.getElementById("loading-comments"); + +const comment_ids = new Set(); +const comment_lookup = new Map(); +const comments_to_generate = []; +const comments_to_generate_ids = new Set(); +let ajax_calls_left = 0; +let total_comments; + +get(thread_url, response => { + const json = JSON.parse(response); + //generate_thread(json[0].data.children[0].data); + //get_comment_ids(json[1].data.children); +}); + +function generate_thread(thread) { + const thread_div = document.createElement("div"); + thread_div.id = "thread"; + + const thread_string = ` +
+
+
${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} +
+ ${thread.selftext !== '' ? '
'+markdown.render(thread.selftext)+'
':''} +
+ `; + + thread_div.innerHTML = thread_string; + main_div.insertBefore(thread_div, loading_comments); + total_comments = thread.num_comments; +} + +function get_comment_ids(comments) { + comments.forEach(comment => extract_id_from_comment(comment)); + + if(ajax_calls_left == 0) { + get_all_comment_ids(); + } +} + +function extract_id_from_comment(comment) { + // "Show more"-comments + if(comment.kind == "more") { + const children = comment.data.children; + + // Some "show more"-comments were missing, need to do more ajax-calls + if(children.length < comment.data.count){ + ajax_calls_left++; + get(morechildren_url + children.join(), response => handle_show_more_comments(JSON.parse(response))); + } + + if(children.length > 0) { + comment_ids.add(...children); + } + } + // Normal comment + else { + const data = comment.data; + + if(data.replies) { + data.replies.data.children.forEach(child => extract_id_from_comment(child)); + } + + delete data.replies; + comment_lookup.set(data.id, data); + comment_ids.add(data.id); + } +} + +function handle_show_more_comments(comments) { + + comments.jquery[14][3][0].forEach(comment => { + if(comment.kind == "more") { + let children = comment.data.children; + + if(children.length < comment.data.count) { + ajax_calls_left++; + get(morechildren_url + children.join, response => handle_show_more_comments(JSON.parse(response))); + } + + comment_ids.add(...children); + } else { + comment_ids.add(comment.data.id); + comment_lookup.set(comment.data.id, comment.data); + } + }); + +/* + + for(var i = 0, len = comments.length; i < len; i++){ + if(comments[i].kind == "more") { + var children = comments[i].data.children; + + if(children.length < comments[i].data.count) { + ajax_calls_left++; + get(morechildren_url + thread_id + "&children=" + children.join(), function(data){ + handle_show_more_comments(JSON.parse(data)) + }); + } + + add_id(children); + + } else { + add_id(comments[i].data.id); + comment_lookup[comments[i].data.id] = comments[i].data; + } + } +*/ + ajax_calls_left--; + + if(ajax_calls_left == 0) { + get_all_comment_ids(); + } +} + + +function get_all_comment_ids() { + const ids_diff = new Set([...all_ids].filter(x => !comment_ids.has(x))); + console.log(all_ids); + console.log(comment_ids); + console.log(ids_diff); + + if(ids_diff.size > 100) { + alert("very many removal, aborting"); + return; + } + + get(removed_comments_url + [...ids_diff].join(), response => create_comments(JSON.parse(response).data)); +} + +function create_comments(comments) { + create_comment_info(comments.length); + + comments.forEach(comment => { + comment["removed"] = true; + comments_to_generate.push(comment); + comments_to_generate_ids.add(comment.id); + comment_lookup.set(comment.id, comment); + }); + + //create_parent_comments(); + +/* + for(var i = 0; i < comments.length; i++) { + comment = create_comment(comments[i], true); + comment_section.appendChild(comment); + comment_lookup[comments[i].id] = comments[i]; + } + */ + //add_parent_comments(); + +} + +function create_parent_comments() { + let done = true; + let parent_id; + let comment; + + for(let i = comments_to_generate.length - 1; i >= 0; i--) { + //console.log(comments[i]); + comment = comments_to_generate[i]; + + parent_id = comment_lookup.get(comment.id).parent_id.split("_")[1]; + + // Done with this comment + if(parent_id == thread_id || comments_to_generate_ids.has(parent_id)) { } + // comment exists in lookup but hasnt been added to the queue yet + else if(comment_lookup.has(parent_id)) { + comments_to_generate.push(comment); + comments_to_generate_ids.add(comment.id); + done = false; + } + // parent comment doesn't exists + else { + get(single_comment_url + parent_id, response => { + let new_comment = JSON.parse(data).data.children[0].data; + + comments_to_generate.push(new_comment); + comments_to_generate_ids.add(new_comment.id); + create_parent_comments(); + console.log(parent_id, " created via /api/info ???"); + }); + done = false; + console.log(i, " doesn't exist"); + } + + console.log(i); + } + + if(!done) { + create_parent_comments(); + } else { + generate_comments(); + } + +} + +function generate_comments() { + const comment_section = document.createElement("div"); + comment_section.id = "comments"; + main_div.insertBefore(comment_section, loading_comments); + + sort_comments(); + comments_to_generate.forEach(comment => { + comment_section.appendChild(create_comment(comment)); + }); +} + +function create_comment(comment) { + const comment_div = document.createElement("div"); + comment_div.className = "comment " + (comment.hasOwnProperty("removed") ? "comment-removed" : ""); + comment_div.id = comment.id; + + const comment_html = ` +
+ [–] + ${comment.author} + ${comment.score} point${(comment.score == 1) ? '': 's'} + ${pretty_date(comment.created_utc) } +
+
${markdown.render(comment.body)}
+ + `; + + comment_div.innerHTML = comment_html; + return comment_div; +} + +/*function create_parent_comments() { + var comment_section = document.getElementById("comments"); + var comments = comment_section.children; + var parent_id; + var comment; + var created_comments = []; + var done = true; + + for(var i = comments.length - 1; i >= 0; i--) { + //console.log(comments[i]); + + parent_id = comment_lookup[comments[i].id].parent_id; + + if(parent_id.includes("_")) { + parent_id = parent_id.split("_")[1]; + } + + // Done with this comment + if(parent_id == thread_id) { + //console.log(i, "done"); + } + // Check if parent already exists + else if(comments_on_display.has(parent_id)) { + document.getElementById(parent_id).appendChild(comments[i]); + //console.log(i, "comment already exists"); + done = false; + } + // comment_data exists but parent comment hasn't been created yet + else if(comment_lookup.hasOwnProperty(parent_id)) { + //created_comments.push(create_comment(comments_data[parent_id])); + comment_section.appendChild(create_comment(comment_lookup[parent_id], false)); + //console.log(i, " comment data exists"); + //done = false; + } + // parent comment doesn't exists + else { + get(single_comment_url + parent_id, function(data){ + var comment = create_comment(JSON.parse(data).data.children[0].data, false); + comment_section.appendChild(); + add_parent_comments(); + console.log(parent_id, " created via /api/info ???"); + }); + + console.log(i, " doesn't exist"); + } + } + + if(!done) { + add_parent_comments(); + } else { + fix_background_color(); + sort_comments(); + } + +} +*/ + +function create_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)}%) +
+
sorted by: top
+ `; + + main_div.insertBefore(comment_info, loading_comments); +} + +function fix_background_color(root = document.getElementById("comments"), depth = 0) { + var comments = document.getElementById("comments").children; + + + + for(var i = 0, len = comments.length; i < len; i++) { + //if(!comments. + + //comments. + } + +} +//https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement +function sort_comments() { + comments_to_generate.sort(function(a, b) { + if(a.score == b.score) { + return 0; + } + return a.score < b.score ? 1 : -1; + }); +} + +// ---------------- Other less interesting functions ------------------------ + +function get(url, callback) { + const xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + callback(this.responseText); + } else if (this.readyState == 4) { + alert(this.status + " error: " + this.responseText); + // Error handle + } + } + + xhttp.open("GET", url, true); + xhttp.setRequestHeader("Authorization", "bearer " + token); + xhttp.send(); +} + +function add_id(id) { + if(id instanceof Array) { + comment_ids.add(...id); + } else { + comment_ids.add(id); + } +} + +// 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 pretty_score(score) { + if(score >= 10000) { + return (score / 1000).toFixed(1) + "k"; + } else { + return score; + } +} \ No newline at end of file diff --git a/static/snuownd.js b/static/snuownd.js new file mode 100644 index 0000000..1fcccd5 --- /dev/null +++ b/static/snuownd.js @@ -0,0 +1,3950 @@ +/** + @preserve snuownd.js - javascript port of reddit's "snudown" markdown parser + https://github.com/gamefreak/snuownd + */ +/** + * @license Copyright (c) 2009, Natacha Porté + * Copyright (c) 2011, Vicent Marti + * Copyright (c) 2012, Scott McClaugherty + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +// up to date with commit e518f5077b072599dea87596f4ce3c1d03e443c0 + +/** +@module SnuOwnd +*/ +(function(exports){ + function _isspace(c) {return c == ' ' || c == '\n';} + function isspace(c) {return /[\x09-\x0d ]/.test(c);} + function isalnum(c) { return /[A-Za-z0-9]/.test(c); } + function isalpha(c) { return /[A-Za-z]/.test(c); } + function isdigit(c) { return /[0-9]/.test(c); } + function isxdigit(c) { return /[0-9a-fA-F]/.test(c); } + function ispunct(c) {return /[\x20-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]/.test(c); } + + function urlHexCode(number) { + var hex_str = '0123456789ABCDEF'; + return '%'+hex_str[(number&0xf0)>>4]+hex_str[(number&0x0f)>>0]; + } + + function escapeUTF8Char(char) { + var code = char.charCodeAt(0); + if (code < 0x80) { + return urlHexCode(code); + } else if((code > 0x7f) && (code < 0x0800)) { + var seq = urlHexCode(code >> 6 & 0xff | 0xc0); + seq += urlHexCode(code >> 0 & 0x3f | 0x80); + return seq; + } else { + var seq = urlHexCode(code >> 12 & 0xff | 0xe0); + seq += urlHexCode(code >> 6 & 0x3f | 0x80); + seq += urlHexCode(code >> 0 & 0x3f | 0x80); + return seq; + } + } + + function find_block_tag (str) { + var wordList = [ + 'p', 'dl', 'div', 'math', + 'table', 'ul', 'del', 'form', + 'blockquote', 'figure', 'ol', 'fieldset', + 'h1', 'h6', 'pre', 'script', + 'h5', 'noscript', 'style', 'iframe', + 'h4', 'ins', 'h3', 'h2' + ]; + if (wordList.indexOf(str.toLowerCase()) != -1) { + return str.toLowerCase(); + } + return ''; + } + + function sdhtml_is_tag(tag_data, tagname) { + var i; + var closed = 0; + var tag_size = tag_data.length; + + if (tag_size < 3 || tag_data[0] != '<') return HTML_TAG_NONE; + + i = 1; + + if (tag_data[i] == '/') { + closed = 1; + i++; + } + + var tagname_c = 0; + for (; i < tag_size; ++i, ++tagname_c) { + if (tagname_c >= tagname.length) break; + + if (tag_data[i] != tagname[tagname_c]) return HTML_TAG_NONE; + } + + if (i == tag_size) return HTML_TAG_NONE; + + if (isspace(tag_data[i]) || tag_data[i] == '>') + return closed ? HTML_TAG_CLOSE : HTML_TAG_OPEN; + + return HTML_TAG_NONE; + } + + function unscape_text(out, src) { + var i = 0, org; + while (i < src.s.length) { + org = i; + while (i < src.s.length && src.s[i] != '\\') i++; + + if (i > org) out.s += src.s.slice(org, i); + + if (i + 1 >= src.s.length) break; + + out.s += src.s[i + 1]; + i += 2; + } + } + + + // Parsers tend to choke on entities with values greater than this + var MAX_NUM_ENTITY_VAL = 0x10ffff; + + function is_valid_numeric_entity(entity_val) { + /* Some XML parsers will choke on entities with certain + * values (mostly control characters.) + * + * According to lxml these are all problematic: + * + * [xrange(0, 8), + * xrange(11, 12), + * xrange(14, 31), + * xrange(55296, 57343), + * xrange(65534, 65535)] + */ + return (entity_val > 8 + && (entity_val !== 11 && entity_val > 12) + && (entity_val < 14 || entity_val > 31) + && (entity_val < 55296 || entity_val > 57343) + && (entity_val !== 65534 && entity_val !== 65535) + && entity_val <= MAX_NUM_ENTITY_VAL); + } + // Any numeric entity longer than this is obviously above MAX_ENTITY_CHAR + // used to avoid dealing with overflows. + var MAX_NUM_ENTITY_LEN = 7; + var VALID_ENTITIES = [ + 'Æ', 'Á', 'Â', 'À', 'Α', 'Å', + 'Ã', 'Ä', 'Β', 'Ç', 'Χ', '‡', 'Δ', + 'Ð', 'É', 'Ê', 'È', 'Ε', 'Η', 'Ë', + 'Γ', 'Í', 'Î', 'Ì', 'Ι', 'Ï', 'Κ', + 'Λ', 'Μ', 'Ñ', 'Ν', 'Œ', 'Ó', 'Ô', + 'Ò', 'Ω', 'Ο', 'Ø', 'Õ', 'Ö', 'Φ', + 'Π', '″', 'Ψ', 'Ρ', 'Š', 'Σ', 'Þ', 'Τ', + 'Θ', 'Ú', 'Û', 'Ù', 'Υ', 'Ü', 'Ξ', + 'Ý', 'Ÿ', 'Ζ', 'á', 'â', '´', 'æ', + 'à', 'ℵ', 'α', '&', '∧', '∠', ''', + 'å', '≈', 'ã', 'ä', '„', 'β', '¦', + '•', '∩', 'ç', '¸', '¢', 'χ', 'ˆ', + '♣', '≅', '©', '↵', '∪', '¤', '⇓', + '†', '↓', '°', 'δ', '♦', '÷', + + 'é', 'ê', 'è', '∅', ' ', ' ', + 'ε', '≡', 'η', 'ð', 'ë', '€', '∃', + 'ƒ', '∀', '½', '¼', '¾', '⁄', + 'γ','≥', '>', '⇔', '↔', '♥', '…', + 'í', 'î', '¡', 'ì', 'ℑ', + + '∞', + '∫', 'ι', '¿', '∈', 'ï', 'κ', '⇐', + 'λ', '⟨', '«', '←', '⌈', '“', '≤', + '⌊', '∗', '◊', '‎', '‹', '‘', '<', + '¯', '—', 'µ', '·', '−', 'μ', '∇', + ' ', '–', '≠', '∋', '¬', '∉', '⊄', 'ñ', + 'ν', 'ó', 'ô', 'œ', 'ò', '‾', 'ω', + 'ο', '⊕', '∨', 'ª', 'º', 'ø', 'õ', + '⊗', 'ö', '¶', '∂', '‰', '⊥', 'φ', + 'π', 'ϖ', '±', '£', '′', '∏', '∝', + 'ψ', '"', '⇒', '√', '⟩', '»', '→', + '⌉', '”', 'ℜ', '®', '⌋', 'ρ', '‏', + '›', '’', '‚', 'š', '⋅', '§', '­', + 'σ', 'ς', '∼', '♠', '⊂', '⊆', '∑', + '¹', '²', '³', '⊃', '⊇', 'ß', 'τ', + '∴', 'θ', 'ϑ', ' ', 'þ', '˜', + '×', '™', '⇑', 'ú', '↑', 'û', 'ù', + '¨', 'ϒ', 'υ', 'ü', '℘', 'ξ', 'ý', +'¥', 'ÿ', 'ζ', '‍', '‌', +]; + + /** + * According to the OWASP rules: + * + * & --> & + * < --> < + * > --> > + * " --> " + * ' --> ' ' is not recommended + * / --> / forward slash is included as it helps end an HTML entity + * + */ + var HTML_ESCAPE_TABLE = [ + 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0, 7, 7, 0, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]; + + var HTML_ESCAPES = ["", """, "&", "'", "/", "<", ">", "" /* throw out control characters */ ]; + + function escape_html(out, src, secure) { + var i = 0, org, esc = 0; + while (i < src.length) { + org = i; + while (i < src.length && !(esc = HTML_ESCAPE_TABLE[src.charCodeAt(i)])) + i++; + + if (i > org) out.s += src.slice(org, i); + + /* escaping */ + if (i >= src.length) break; + + /* The forward slash is only escaped in secure mode */ + if (src[i] == '/' && !secure) { + out.s += '/'; + } else if (HTML_ESCAPE_TABLE[src.charCodeAt(i)] == 7) { + /* skip control characters */ + } else { + out.s += HTML_ESCAPES[esc]; + } + + i++; + } + } + + + /* + * The following characters will not be escaped: + * + * -_.+!*'(),%#@?=;:/,+&$ alphanum + * + * Note that this character set is the addition of: + * + * - The characters which are safe to be in an URL + * - The characters which are *not* safe to be in + * an URL because they are RESERVED characters. + * + * We asume (lazily) that any RESERVED char that + * appears inside an URL is actually meant to + * have its native function (i.e. as an URL + * component/separator) and hence needs no escaping. + * + * There are two exceptions: the chacters & (amp) + * and ' (single quote) do not appear in the table. + * They are meant to appear in the URL as components, + * yet they require special HTML-entity escaping + * to generate valid HTML markup. + * + * All other characters will be escaped to %XX. + * + */ + var HREF_SAFE = [ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]; + + function escape_href(out, src) { + var i = 0, org; + + + while (i < src.length) { + org = i; + /* Skip by characters that don't need special processing */ + while (i < src.length && HREF_SAFE[src.charCodeAt(i)] === 1) i++; + + if (i > org) out.s += src.slice(org, i); + + /* escaping */ + if (i >= src.length) break; + + /* throw out control characters */ + if (HREF_SAFE[src.charCodeAt(i)] == 2) { + i++; + continue; + } + + switch (src[i]) { + /* amp appears all the time in URLs, but needs + * HTML-entity escaping to be inside an href */ + case '&': + out.s += '&'; + break; + + /* the single quote is a valid URL character + * according to the standard; it needs HTML + * entity escaping too */ + case '\'': + out.s += '''; + break; + + /* the space can be escaped to %20 or a plus + * sign. we're going with the generic escape + * for now. the plus thing is more commonly seen + * when building GET strings */ + /* + //This was disabled + case ' ': + out.s += '+' + break; + //*/ + + /* every other character goes with a %XX escaping */ + default: + out.s += escapeUTF8Char(src[i]); + /* + var cc = src.charCodeAt(i); + hex_str[1] = hex_chars[(cc >> 4) & 0xF]; + hex_str[2] = hex_chars[cc & 0xF]; + out.s += hex_str.join(''); + */ + } + + i++; + } + } + + + // function autolink_delim(uint8_t *data, size_t link_end, size_t offset, size_t size) + function autolink_delim(data_, link_end, offset, size) { + var data = data_.slice(offset) + var cclose, copen = 0; + var i; + + for (i = 0; i < link_end; ++i) + if (data[i] == '<') { + link_end = i; + break; + } + + while (link_end > 0) { + var c = data[link_end - 1]; + if (c === "\0") break; + + if ("?!.,".indexOf(c) !== -1) { + link_end--; + } else if (c === ';') { + var new_end = link_end - 2; + + while (new_end > 0 && isalpha(data[new_end])) new_end--; + + if (new_end < link_end - 2 && data[new_end] == '&') + link_end = new_end; + else link_end--; + } + else break; + } + + if (link_end == 0) return 0; + + cclose = data[link_end - 1]; + + switch (cclose) { + case '"': copen = '"'; break; + case '\'': copen = '\''; break; + case ')': copen = '('; break; + case ']': copen = '['; break; + case '}': copen = '{'; break; + } + + if (copen != 0) { + var closing = 0; + var opening = 0; + var j = 0; + + /* Try to close the final punctuation sign in this same line; + * if we managed to close it outside of the URL, that means that it's + * not part of the URL. If it closes inside the URL, that means it + * is part of the URL. + * + * Examples: + * + * foo http://www.pokemon.com/Pikachu_(Electric) bar + * => http://www.pokemon.com/Pikachu_(Electric) + * + * foo (http://www.pokemon.com/Pikachu_(Electric)) bar + * => http://www.pokemon.com/Pikachu_(Electric) + * + * foo http://www.pokemon.com/Pikachu_(Electric)) bar + * => http://www.pokemon.com/Pikachu_(Electric)) + * + * (foo http://www.pokemon.com/Pikachu_(Electric)) bar + * => foo http://www.pokemon.com/Pikachu_(Electric) + */ + + while (j < link_end) { + if (data[j] == copen) opening++; + else if (data[j] == cclose) closing++; + + j++; + } + + if (closing != opening) link_end--; + } + + return link_end; + } + + function check_domain(data, allow_short) { + var i, np = 0; + + if (!isalnum(data[0])) return 0; + + for (i = 1; i < data.length - 1; ++i) { + if (data[i] == '.') np++; + else if (!isalnum(data[i]) && data[i] != '-') break; + } + + /* a valid domain needs to have at least a dot. + * that's as far as we get */ + if (allow_short) { + /* We don't need a valid domain in the strict sence (with + * at least one dot; so just make sure it's composed of valid + * domain characters and return the length of the valid + * sequence. */ + return i; + } else { + return np ? i : 0; + } + } + + function sd_autolink_issafe(link) { + var valid_uris = [ + "http://", "https://", "ftp://", "mailto://", + "/", "git://", "steam://", "irc://", "news://", "mumble://", + "ssh://", "ircs://", "ts3server://", "#"]; + + var i; + + for (i = 0; i < valid_uris.length; ++i) { + var len = valid_uris[i].length; + + if (link.length > len && + link.toLowerCase().indexOf(valid_uris[i]) == 0 && + /[A-Za-z0-9#\/?]/.test(link[len])) + return 1; + } + + return 0; + } + + + function sd_autolink__url(rewind_p, link, data_, offset, size, flags) { + var data = data_.slice(offset); + var link_end, rewind = 0, domain_len; + + if (size < 4 || data_[offset+1] != '/' || data_[offset+2] != '/') return 0; + + while (rewind < offset && isalpha(data_[offset-rewind - 1])) rewind++; + + if (!sd_autolink_issafe(data_.substr(offset-rewind, size+rewind))) return 0; + link_end = "://".length; + + domain_len = check_domain(data.slice(link_end), flags & SD_AUTOLINK_SHORT_DOMAINS); + if (domain_len == 0) return 0; + + link_end += domain_len; + while (link_end < size && !isspace(data_[offset+link_end])) link_end++; + + link_end = autolink_delim(data_, link_end, offset, size); + + if (link_end == 0) return 0; + + //TODO + link.s += data_.substr(offset-rewind, link_end+rewind); + rewind_p.p = rewind; + + return link_end; + } + + function sd_autolink__subreddit(rewind_p, link, data_, offset, size) { + var data = data_.slice(offset); + var link_end; + var allMinus = false; + + if (size < 3) return 0; + + /* make sure this / is part of /r/ */ + if (data.indexOf('/r/') != 0) return 0; + + link_end = "/r/".length; + if (data.substr(link_end-1, 4).toLowerCase() == "all-") { + allMinus = true; + } + do { + var start = link_end; + var max_length = 24; + /* special case: /r/reddit.com (the only subreddit with a '.') */ + if ( size >= link_end+10 && data.substr(link_end, 10).toLowerCase() == 'reddit.com') { + link_end += 10; + max_length = 10; + } else { + /* If not the special case make sure it starts with (t:)?[A-Za-z0-9] */ + /* support autolinking to timereddits, /r/t:when (1 April 2012) */ + if ( size > link_end+2 && data.substr(link_end, 2) == 't:') + link_end += 2; /* Jump over the 't:' */ + + /* the first character of a subreddit name must be a letter or digit */ + if (!isalnum(data[link_end])) + return 0; + link_end += 1; + } + + /* consume valid characters ([A-Za-z0-9_]) until we run out */ + while (link_end < size && (isalnum(data[link_end]) || + data[link_end] == '_')) + link_end++; + + /* valid subreddit names are between 3 and 21 characters, with + * some subreddits having 2-character names. Don't bother with + * autolinking for anything outside this length range. + * (chksrname function in reddit/.../validator.py) */ + if ( link_end-start < 2 || link_end-start > max_length ) + return 0; + + /* If we are linking to a multireddit, continue */ + } while ( link_end < size && (data[link_end] == '+' || (allMinus && data[link_end] == '-')) && link_end++ ); + if (link_end < size && data[link_end] == '/') { + while (link_end < size && (isalnum(data[link_end]) || + data[link_end] == '_' || + data[link_end] == '/' || + data[link_end] == '-')) { + link_end++; + } + } + /* make the link */ + link.s += data.slice(0, link_end); + rewind_p.p = 0; + + return link_end; + } + + function sd_autolink__username(rewind_p, link, data_, offset, size) { + var data = data_.slice(offset); + var link_end; + + if (size < 6) return 0; + + /* make sure this / is part of /u/ */ + if (data.indexOf('/u/') != 0) return 0; + + /* the first letter of a username must... well, be valid, we don't care otherwise */ + link_end = "/u/".length; + if (!isalnum(data[link_end]) && data[link_end] != '_' && data[link_end] != '-') + return 0; + link_end += 1; + + /* consume valid characters ([A-Za-z0-9_-/]) until we run out */ + while (link_end < size && (isalnum(data[link_end]) || + data[link_end] == '_' || + data[link_end] == '/' || + data[link_end] == '-')) + link_end++; + + /* make the link */ + link.s += data.slice(0, link_end); + rewind_p.p = 0; + + return link_end; + } + + function sd_autolink__email(rewind_p, link, data_, offset, size, flags) { + var data = data_.slice(offset); + var link_end, rewind; + var nb = 0, np = 0; + + for (rewind = 0; rewind < offset; ++rewind) { + var c = data_[offset-rewind - 1]; + + if (c == '\0') break; + + if (isalnum(c)) continue; + if (".+-_".indexOf(c) != -1) continue; + break; + } + + if (rewind == 0) return 0; + + for (link_end = 0; link_end < size; ++link_end) { + var c = data_[offset+link_end]; + + if (isalnum(c)) continue; + + if (c == '@') nb++; + else if (c == '.' && link_end < size - 1) np++; + else if (c != '-' && c != '_') break; + } + + if (link_end < 2 || nb != 1 || np == 0) return 0; + + //TODO + link_end = autolink_delim(data_, link_end, offset, size); + + if (link_end == 0) return 0; + + // link.s += data_.slice(offset - rewind, link_end + rewind + link.s += data_.substr(offset - rewind, link_end + rewind); + rewind_p.p = rewind; + + return link_end; + } + + function sd_autolink__www(rewind_p, link, data_, offset, size, flags) { + var data = data_.slice(offset); + var link_end; + + if (offset > 0 && !ispunct(data_[offset-1]) && !isspace(data_[offset-1])) + return 0; + + // if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0) + if (size < 4 || (data.slice(0,4) != 'www.')) return 0; + + link_end = check_domain(data, 0); + + if (link_end == 0) + return 0; + + while (link_end < size && !isspace(data[link_end])) link_end++; + + link_end = autolink_delim(data_, link_end, offset, size); + + if (link_end == 0) return 0; + + link.s += data.slice(0, link_end); + rewind_p.p = 0; + + return link_end; + } + + /** + Initialize a Callbacks object. + + @constructor + @param {Object.} callbacks A set of callbacks to use as the methods on this object. + */ + function Callbacks(callbacks) { + if (callbacks) { + for (var name in callbacks) { + if (name in this) this[name] = callbacks[name]; + } + } + } + + Callbacks.prototype = { + /** + Renders a code block. + + Syntax highlighting specific to lanugage may be performed here. + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The input text. + @param {Buffer} language The name of the code langage. + @param {?Object} context A renderer specific context object. + */ + blockcode: null, + /** + Renders a blockquote. + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The input text. + @param {?Object} context A renderer specific context object. + */ + blockquote: null, + /** + Renders a block of HTML code. + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The input text. + @param {?Object} context A renderer specific context object. + */ + blockhtml: null, + /** + Renders a header. + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The input text. + @param {Number} level The header level. + @param {?Object} context A renderer specific context object. + */ + header: null, + /** + Renders a horizontal rule. + + @method + @param {Buffer} out The output string buffer to append to. + @param {?Object} context A renderer specific context object. + */ + hrule: null, + /** + Renders a list. +

+ This method handles the list wrapper, which in terms of HTML would be <ol> or <ul>. + This method is not responsible for handling list elements, all such processing should + already have occured on text pased to the method . All that it is intended + to do is to wrap the text parameter in anything needed. +

+ + @example + out.s += "<ul>" + text.s + "</ul>" + + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The input that goes inside the list. + @param {Number} flags A bitfield holding a portion of the render state. The only bit that this should be concerned with is MKD_LIST_ORDERED + @param {?Object} context A renderer specific context object. + */ + list: null, + /** + Renders a list. +

+ Wraps the text in a list element. +

+ + @example + out.s += "<li>" + text.s + "</li>" + + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The contents of the list element. + @param {Number} flags A bitfield holding a portion of the render state. The only bit that this should be concerned with is MKD_LI_BLOCK. + @param {?Object} context A renderer specific context object. + */ + listitem: null, + /** + Renders a paragraph. + + @example + + out.s += "<p>" + text.s + "</p>"; + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The input text. + @param {?Object} context A renderer specific context object. + */ + paragraph: null, + /** + Renders a table. + + @example + + out.s += ""; + out.s += header.s; + out.s += ""; + out.s += body.s; + out.s += "
"; + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} head The table header. + @param {Buffer} body The table body. + @param {?Object} context A renderer specific context object. + */ + table: null, + /** + Renders a table row. + + @example + + out.s += "<tr>" + text.s + "</tr>"; + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The input text. + @param {?Object} context A renderer specific context object. + */ + table_row: null, + /** + Renders a table cell. + + @example + + out.s += "<td>" + text.s + "</td>"; + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The input text. + @param {Number} flags A bit filed indicating a portion of the output state. Relevant bits are: MKD_TABLE_HEADER, MKD_TABLE_ALIGN_CENTER. MKD_TABLE_ALIGN_L, and MKD_TABLE_ALIGN_R. + @param {?Object} context A renderer specific context object. + */ + table_cell: null, + /** + Renders a link that was autodetected. + + @example + + out.s += "<a href=\""+ text.s + "\">" + text.s + "</a>"; + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The address being linked to. + @param {Number} type Equal to MKDA_NORMAL or MKDA_EMAIL + @param {?Object} context A renderer specific context object. + @returns {Boolean} Whether or not the tag was rendered. + */ + autolink: null, + /** + Renders inline code. + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The text being wrapped. + @param {?Object} context A renderer specific context object. + @returns {Boolean} Whether or not the tag was rendered. + */ + codespan: null, + /** + Renders text with double emphasis. Default is equivalent to the HTML <strong> tag. + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The text being wrapped. + @param {?Object} context A renderer specific context object. + @returns {Boolean} Whether or not the tag was rendered. + */ + double_emphasis: null, + /** + Renders text with single emphasis. Default is equivalent to the HTML <em> tag. + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The text being wrapped. + @param {?Object} context A renderer specific context object. + @returns {Boolean} Whether or not the tag was rendered. + */ + emphasis: null, + /** + Renders an image. + + @example + + out.s = "<img src=\"" + link.s + "\" title=\"" + title.s + "\" alt=\"" + alt.s + "\"/>";" + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} link The address of the image. + @param {Buffer} title Title text for the image + @param {Buffer} alt Alt text for the image + @param {?Object} context A renderer specific context object. + @returns {Boolean} Whether or not the tag was rendered. + */ + image: null, + /** + Renders line break. + + @example + + out.s += "<br/>"; + + @method + @param {Buffer} out The output string buffer to append to. + @param {?Object} context A renderer specific context object. + @returns {Boolean} Whether or not the tag was rendered. + */ + linebreak: null, + /** + Renders a link. + + @example + + out.s = "<a href=\"" + link.s + "\" title=\"" + title.s + "\">" + content.s + "</a>"; + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} link The link address. + @param {Buffer} title Title text for the link. + @param {Buffer} content Link text. + @param {?Object} context A renderer specific context object. + @returns {Boolean} Whether or not the tag was rendered. + */ + link: null, + /** + Copies and potentially escapes some HTML. + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The input text. + @param {?Object} context A renderer specific context object. + @returns {Boolean} Whether or not the tag was rendered. + */ + raw_html_tag: null, + /** + Renders text with triple emphasis. Default is equivalent to both the <em> and <strong> HTML tags. + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The text being wrapped. + @param {?Object} context A renderer specific context object. + @returns {Boolean} Whether or not the tag was rendered. + */ + triple_emphasis: null, + /** + Renders text crossd out. + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The text being wrapped. + @param {?Object} context A renderer specific context object. + @returns {Boolean} Whether or not the tag was rendered. + */ + strikethrough: null, + /** + Renders text as superscript. + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The text being wrapped. + @param {?Object} context A renderer specific context object. + @returns {Boolean} Whether or not the tag was rendered. + */ + superscript: null, + /** + Escapes an HTML entity. + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The text being wrapped. + @param {?Object} context A renderer specific context object. + */ + entity: null, + /** + Renders plain text. + + @method + @param {Buffer} out The output string buffer to append to. + @param {Buffer} text The text being rendered. + @param {?Object} context A renderer specific context object. + */ + normal_text: null, + /** + Creates opening boilerplate for a table of contents. + + @method + @param {Buffer} out The output string buffer to append to. + @param {?Object} context A renderer specific context object. + */ + doc_header: null, + /** + Creates closing boilerplate for a table of contents. + + @method + @param {Buffer} out The output string buffer to append to. + @param {?Object} context A renderer specific context object. + */ + doc_footer: null + }; + + + /** + A renderer object + + @constructor + @param {Callbacks} callbacks The callbacks object to use for the renderer. + @param {?Callbacks} context Renderer specific context information. + */ + function Renderer(callbacks, context) { + this.callbacks = callbacks; + this.context = context; + } + + /** + Instantiates a custom Renderer object. + + @param {Callbacks} callbacks The callbacks object to use for the renderer. + @param {?Callbacks} context Renderer specific context information. + @returns {Renderer} + */ + function createCustomRenderer(callbacks, context) { + return new Renderer(callbacks, context) + } + exports.createCustomRenderer = createCustomRenderer; + + function defaultRenderState() { + return { + nofollow: 0, + target: null, + tocData: { + headerCount: 0, + currentLevel: 0, + levelOffset: 0 + }, + toc_id_prefix: null, + html_element_whitelist: DEFAULT_HTML_ELEMENT_WHITELIST, + html_attr_whitelist: DEFAULT_HTML_ATTR_WHITELIST, + flags: 0, + //(flags != undefined?flags:HTML_SKIP_HTML | HTML_SKIP_IMAGES | HTML_SAFELINK | HTML_ESCAPE | HTML_USE_XHTML), + /* extra callbacks */ + // void (*link_attributes)(struct buf *ob, const struct buf *url, void *self); + link_attributes: function link_attributes(out, url, options) { + + if (options.nofollow) out.s += ' rel="nofollow"'; + + if (options.target != null) { + out.s += ' target="' + options.target + '"'; + } + } + }; + } + exports.defaultRenderState = defaultRenderState; + /** + Produces a renderer object that will match Reddit's output. + @param {?Number=} flags A bitfield containing flags specific to the reddit HTML renderer. Passing undefined, null, or null value will produce reddit exact output. + @returns {Renderer} A renderer object that will match Reddit's output. + */ + function getRedditRenderer(flags) { + var state =defaultRenderState(); + if (flags == null) { + state.flags = DEFAULT_BODY_FLAGS; + } else { + state.flags = flags; + } + + var renderer = new Renderer(getRedditCallbacks() , state); + if (renderer.context.flags & HTML_SKIP_IMAGES) + renderer.callbacks.image = null; + + if (renderer.context.flags & HTML_SKIP_LINKS) { + renderer.callbacks.link = null; + renderer.callbacks.autolink = null; + } + + if (renderer.context.flags & HTML_SKIP_HTML || renderer.context.flags & HTML_ESCAPE) + renderer.callbacks.blockhtml = null; + return renderer; + } + exports.getRedditRenderer = getRedditRenderer; + + /** + Produces a renderer object that will match Reddit's for a table of contents. + @returns {Renderer} A renderer object that will match Reddit's output. + */ + function getTocRenderer() { + var state = defaultRenderState(); + state.flags = HTML_TOC | HTML_SKIP_HTML; + var renderer = new Renderer(getTocCallbacks(), state); + return renderer; + } + exports.getTocRenderer = getTocRenderer; + + /** + Create a Callbacks object with the given callback table. + + @param {Object.} callbacks A table of callbacks to place int a callbacks object. + @returns {Callbacks} A callbacks object holding the provided callbacks. + */ + function createCustomCallbacks(callbacks) { + return new Callbacks(callbacks); + } + exports.createCustomCallbacks = createCustomCallbacks; + + /** + Produce a callbacks object that matches Reddit's output. + @returns {Callbacks} A callbacks object that matches Reddit's output. + */ + function getRedditCallbacks(){ + return new Callbacks({ + blockcode: cb_blockcode, + blockquote: cb_blockquote, + blockhtml: cb_blockhtml, + header: cb_header, + hrule: cb_hrule, + list: cb_list, + listitem: cb_listitem, + paragraph: cb_paragraph, + table: cb_table, + table_row: cb_table_row, + table_cell: cb_table_cell, + autolink: cb_autolink, + codespan: cb_codespan, + double_emphasis: cb_double_emphasis, + emphasis: cb_emphasis, + image: cb_image, + linebreak: cb_linebreak, + link: cb_link, + raw_html_tag: cb_raw_html_tag, + triple_emphasis: cb_triple_emphasis, + strikethrough: cb_strikethrough, + superscript: cb_superscript, + entity: null, + normal_text: cb_normal_text, + doc_header: null, + doc_footer: cb_reset_toc + }); + } + exports.getRedditCallbacks = getRedditCallbacks; + + /** + Produce a callbacks object for rendering a table of contents. + @returns {Callbacks} A callbacks object for rendering a table of contents. + */ + function getTocCallbacks() { + return new Callbacks({ + blockcode: null, + blockquote: null, + blockhtml: null, + header: cb_toc_header, + hrule: null, + list: null, + listitem: null, + paragraph: null, + table: null, + table_row: null, + table_cell: null, + autolink: null, + codespan: cb_codespan, + double_emphasis: cb_double_emphasis, + emphasis: cb_emphasis, + image: null, + linebreak: null, + link: cb_toc_link, + raw_html_tag: null, + triple_emphasis: cb_triple_emphasis, + strikethrough: cb_strikethrough, + superscript: cb_superscript, + entity: null, + normal_text: null, + doc_header: null, + doc_footer: cb_toc_finalize + }); + } + exports.getTocCallbacks = getTocCallbacks; + + /* block level callbacks - NULL skips the block */ + // void (*blockcode)(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque); + function cb_blockcode(out, text, lang, options) { + if (out.s.length) out.s += '\n'; + + if (lang && lang.s.length) { + var i, cls; + out.s += '
';
+		} else
+			out.s += '
';
+
+		if (text) escape_html(out, text.s, false);
+
+		out.s += '
\n'; + } + + // void (*blockquote)(struct buf *ob, const struct buf *text, void *opaque); + function cb_blockquote(out, text, options) { + if (out.s.length) out.s += '\n'; + out.s += '
\n'; + if (text) out.s += text.s; + out.s += '
\n'; + } + + // void (*blockhtml)(struct buf *ob,const struct buf *text, void *opaque); + function cb_blockhtml(out, text, options) { + var org, sz; + if (!text) return; + sz = text.s.length; + while (sz > 0 && text.s[sz - 1] == '\n') sz--; + org = 0; + while (org < sz && text.s[org] == '\n') org++; + if (org >= sz) return; + if (out.s.length) out.s += '\n'; + out.s += text.s.slice(org, sz); + out.s += '\n'; + } + + // header(Buffer out, Buffer text, int level, void *opaque); + function cb_header(out, text, level, options) { + if (out.s.length) out.s += '\n'; + + if (options.flags & HTML_TOC) { + out.s += ''; + } else { + out.s += ''; + } + + if (text) out.s += text.s; + out.s += '\n'; + } + + // void (*hrule)(struct buf *ob, void *opaque); + function cb_hrule(out, options) { + if (out.s.length) out.s += '\n'; + out.s += (options.flags & HTML_USE_XHTML) ? '
\n' : '
\n'; + } + + // void (*list)(struct buf *ob, const struct buf *text, int flags, void *opaque); + function cb_list(out, text, flags, options) { + if (out.s.length) out.s += '\n'; + out.s += (flags&MKD_LIST_ORDERED?'
    \n':'
      \n'); + if (text) out.s += text.s; + out.s += (flags&MKD_LIST_ORDERED?'
\n':'\n'); + } + + // void (*listitem)(struct buf *ob, const struct buf *text, int flags, void *opaque); + function cb_listitem(out, text, flags, options) { + out.s += '
  • '; + if (text) { + var size = text.s.length; + while (size && text.s[size - 1] == '\n') size--; + out.s += text.s.slice(0, size); + } + out.s += '
  • \n'; + } + + // void (*paragraph)(struct buf *ob, const struct buf *text, void *opaque); + function cb_paragraph(out, text, options) { + var i = 0; + + if (out.s.length) out.s += '\n'; + + if (!text || !text.s.length) return; + + while (i < text.s.length && isspace(text.s[i])) i++; + + if (i == text.s.length) return; + + out.s += '

    '; + if (options.flags & HTML_HARD_WRAP) { + var org; + while (i < text.s.length) { + org = i; + while (i < text.s.length && text.data[i] != '\n') + i++; + + if (i > org) out.s += text.s.slice(org, i); + + /* + * do not insert a line break if this newline + * is the last character on the paragraph + */ + if (i >= text.s.length - 1) break; + + cb_linebreak(out, options); + i++; + } + } else { + out.s += text.s.slice(i); + } + out.s += '

    \n'; + } + + // void (*table)(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque); + function cb_table(out, header, body, options) { + if (out.s.length) out.s += '\n'; + out.s += '\n'; + if (header) out.s += header.s; + out.s += '\n'; + if (body) out.s += body.s; + out.s += '
    \n'; + } + + // void (*table_row)(struct buf *ob, const struct buf *text, void *opaque); + function cb_table_row(out, text, options) { + out.s += '\n'; + if (text) out.s += text.s; + out.s += '\n'; + } + + // void (*table_cell)(struct buf *ob, const struct buf *text, int flags, void *opaque); + function cb_table_cell(out, text, flags, options, col_span) { + if (flags & MKD_TABLE_HEADER) { + out.s += ' 1) { + out.s += " colspan=\"" + col_span + "\" "; + } + switch (flags & MKD_TABLE_ALIGNMASK) { + case MKD_TABLE_ALIGN_CENTER: + out.s += ' align="center">'; + break; + + case MKD_TABLE_ALIGN_L: + out.s += ' align="left">'; + break; + + case MKD_TABLE_ALIGN_R: + out.s += ' align="right">'; + break; + default: + out.s += '>'; + } + + if (text) out.s += text.s; + + if (flags & MKD_TABLE_HEADER) { + out.s += '\n'; + } else { + out.s += '\n'; + } + } + + /* span level callbacks - NULL or return 0 prints the span verbatim */ + // int (*autolink)(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque); + function cb_autolink(out, link, type, options) { + var offset = 0; + + if (!link || !link.s.length) return 0; + + if ((options.flags & HTML_SAFELINK) != 0 && + !sd_autolink_issafe(link.s) && type != MKDA_EMAIL) + return 0; + + out.s += ''; + return 1; + } + + // int (*emphasis)(struct buf *ob, const struct buf *text, void *opaque); + function cb_emphasis(out, text, options) { + if (!text || !text.s.length) return 0; + out.s += '' + text.s + ''; + return 1; + } + + // int (*image)(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque); + function cb_image(out, link, title, alt, options) { + if (!link || !link.s.length) return 0; + + out.s += '';
+
+		if (alt && alt.s.length) escape_html(out, alt.s, false);
+
+		if (title && title.s.length) {
+			out.s += '':'">'); + return 1; + } + + + // int (*linebreak)(struct buf *ob, void *opaque); + function cb_linebreak(out, options) { + out.s += (options.flags&HTML_USE_XHTML?'
    \n':'
    \n'); + return 1; + } + + // int (*link)(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque); + function cb_link(out, link, title, content, options) { + if (link != null && (options.flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link.s)) return 0; + + out.s += '
    "; + return; + } + + out.s += tagname; + var i = 1 + tagname.length; + + attr = new Buffer; + value = new Buffer; + + for(;i < text.s.length && !done; i++) { + c = text.s[i]; + done = 0; + reset = 0; + done_attr = 0; + + switch(c) { + case '>': + done = 1; + break; + case '\'': + case '"': + if (!seen_equals) { + reset = 1; + } else if(!in_str) { + in_str = c; + } else if(in_str == c) { + in_str = 0; + done_attr = 1; + } else { + value.s += c; + } + break; + case ' ': + if (in_str) { + value.s += ' '; + } else { + reset = 1; + } + break; + case '=': + if (seen_equals) { + reset = 1; + break; + } + seen_equals = 1; + break; + default: + if (seen_equals && in_str || !seen_equals) { + if (seen_equals) { + value.s += c; + } else { + attr.s += c; + } + } + break; + } + + if (done_attr) { + var valid = 0; + for (z = 0; whitelist[z]; z++) { + if (whitelist[z].length != attr.s.length) { + continue; + } + for (x = 0; x < attr.s.length; x++) { + if (whitelist[z][x].toLowerCase() != attr.s[x].toLowerCase()) { + break; + } + } + if (x == attr.s.length) { + valid = 1; + break; + } + } + if (valid && value.s.length && attr.s.length) { + // console.log("VALD", value.s.length, attr.s.length); + out.s += ' '; + escape_html(out, attr.s, false); + out.s += '="'; + escape_html(out, value.s, false) + out.s += '"'; + } + reset = 1; + } + if (reset) { + seen_equals = 0; + in_str = 0; + attr = new Buffer; + value = new Buffer; + } + } + out.s += '>'; + } + + // int (*raw_html_tag)(struct buf *ob, const struct buf *tag, void *opaque); + function cb_raw_html_tag(out, text, options) { + var whitelist = options.html_element_whitelist; + + /* Items on the whitelist ignore all other flags and just output */ + if (((options.flags & HTML_ALLOW_ELEMENT_WHITELIST) != 0) && whitelist) { + for(var i = 0; whitelist[i]; i++) { + var tagtype = sdhtml_is_tag(text.s, whitelist[i]); + if(tagtype != HTML_TAG_NONE) { + rndr_html_tag(out, text, options, whitelist[i], options.html_attr_whitelist, tagtype); + return 1; + } + } + } + + /* HTML_ESCAPE overrides SKIP_HTML, SKIP_STYLE, SKIP_LINKS and SKIP_IMAGES + * It doens't see if there are any valid tags, just escape all of them. */ + if((options.flags & HTML_ESCAPE) != 0) { + escape_html(out, text.s, false); + return 1; + } + + if ((options.flags & HTML_SKIP_HTML) != 0) return 1; + + if ((options.flags & HTML_SKIP_STYLE) != 0 && + sdhtml_is_tag(text.s, "style")) + return 1; + + if ((options.flags & HTML_SKIP_LINKS) != 0 && + sdhtml_is_tag(text.s, "a")) + return 1; + + if ((options.flags & HTML_SKIP_IMAGES) != 0 && + sdhtml_is_tag(text.s, "img")) + return 1; + + out.s += text.s; + return 1; + } + + // int (*triple_emphasis)(struct buf *ob, const struct buf *text, void *opaque); + function cb_triple_emphasis(out, text, options) { + if (!text || !text.s.length) return 0; + out.s += '' + text.s + ''; + return 1; + } + + // int (*strikethrough)(struct buf *ob, const struct buf *text, void *opaque); + function cb_strikethrough(out, text, options) { + if (!text || !text.s.length) return 0; + out.s += '' + text.s + ''; + return 1; + } + + // int (*superscript)(struct buf *ob, const struct buf *text, void *opaque); + function cb_superscript(out, text, options) { + if (!text || !text.s.length) return 0; + out.s += '' + text.s + ''; + return 1; + } + + /* low level callbacks - NULL copies input directly into the output */ + //do not use + // void (*entity)(struct buf *ob, const struct buf *entity, void *opaque); + // function cb_entity(out, entity, options) {} + + // void (*normal_text)(struct buf *ob, const struct buf *text, void *opaque); + function cb_normal_text(out, text, options) { + if (text) escape_html(out, text.s, false); + } + + // toc_header(struct buf *ob, const struct buf *text, int level, void *opaque) + function cb_toc_header(out, text, level, options) { + /* set the level offset if this is the first header + * we're parsing for the document */ + if (options.tocData.currentLevel== 0) { + out.s += '
    \n'; + options.tocData.levelOffset = level - 1; + } + level -= options.tocData.levelOffset; + + if (level > options.tocData.currentLevel) { + while (level > options.tocData.currentLevel) { + out.s += '
      \n
    • \n'; + options.tocData.currentLevel++; + } + } else if (level < options.tocData.currentLevel) { + out.s += '
    • \n'; + while (level < options.tocData.currentLevel) { + out.s += '
    \n\n'; + options.tocData.currentLevel--; + } + out.s += '
  • \n'; + } else { + out.s += '
  • \n
  • \n'; + } + + out.s += ''; + if (text) escape_html(out, text.s, false); + out.s += '\n'; + } + + //toc_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque) + function cb_toc_link(out, link, title, content, options) { + if (content && content.s) + out.s += content.s; + return 1; + } + + + // reset_toc(struct buf *ob, void *opaque) + function cb_reset_toc(out, options) { + options.tocData = { + headerCount: 0, + currentLevel: 0, + levelOffset: 0 + }; + } + + //toc_finalize(struct buf *ob, void *opaque) + function cb_toc_finalize(out, options) { + var hasToc = false; + while (options.tocData.currentLevel > 0) { + out.s += '
  • \n\n'; + options.tocData.currentLevel--; + hasToc = true; + } + if (hasToc) { + out.s += '
    \n'; + } + cb_reset_toc(out, options); + } + + /* header and footer */ + // doc_header(Buffer out}, context); + // doc_header: null, + // doc_footer(Buffer out, context); + // doc_footer: null + + + /* char_emphasis • single and double emphasis parsing */ + //Buffer, md, str, int + function char_emphasis(out, md, data_, offset) { + var data = data_.slice(offset); + var size = data.length; + var c = data[0]; + var ret; + + if (size > 2 && data[1] != c) { + /* whitespace cannot follow an opening emphasis; + * strikethrough only takes two characters '~~' */ + if (c == '~' || _isspace(data[1]) || (ret = parse_emph1(out, md, data, c)) == 0) + return 0; + + return ret + 1; + } + + if (data.length > 3 && data[1] == c && data[2] != c) { + if (_isspace(data[2]) || (ret = parse_emph2(out, md, data, c)) == 0) + return 0; + + return ret + 2; + } + + if (data.length > 4 && data[1] == c && data[2] == c && data[3] != c) { + if (c == '~' || _isspace(data[3]) || (ret = parse_emph3(out, md, data, c)) == 0) + return 0; + + return ret + 3; + } + + return 0; + } + + /* char_codespan - '`' parsing a code span (assuming codespan != 0) */ + function char_codespan(out, md, data_, offset) { + var data = data_.slice(offset); + var end, nb = 0, i, f_begin, f_end; + + /* counting the number of backticks in the delimiter */ + while (nb < data.length && data[nb] == '`') + nb++; + + /* finding the next delimiter */ + i = 0; + for (end = nb; end < data.length && i < nb; end++) { + if (data[end] == '`') i++; + else i = 0; + } + + if (i < nb && end >= data.length) + return 0; /* no matching delimiter */ + + /* trimming outside whitespaces */ + f_begin = nb; + while (f_begin < end && data[f_begin] == ' ') f_begin++; + + f_end = end - nb; + while (f_end > nb && data[f_end-1] == ' ') f_end--; + + /* real code span */ + if (f_begin < f_end) { + var work = new Buffer(data.slice(f_begin, f_end)); + if (!md.callbacks.codespan(out, work, md.context)) + end = 0; + } else { + if (!md.callbacks.codespan(out, null, md.context)) + end = 0; + } + + return end; + } + + /* char_linebreak - '\n' preceded by two spaces (assuming linebreak != 0) */ + function char_linebreak(out, md, data_, offset) { + var data = data_.slice(offset); + if (offset < 2 || data_[offset-1] != ' ' || data_[offset-2] != ' ') + return 0; + + /* removing the last space from ob and rendering */ + var len = out.s.length; + while (len && out.s[len - 1] == ' ') len--; + out.s = out.s.slice(0, len); + + return md.callbacks.linebreak(out, md.context) ? 1 : 0; + } + + /* char_link - '[': parsing a link or an image */ + function char_link(out, md, data_, offset) { + var data = data_.slice(offset); + var is_img = (offset && data_[offset - 1] == '!'), level; + var i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0; + //4 bufs + var content = null; + var link = null; + var title = null; + var u_link = null; + var org_work_size = md.spanStack.length; + var text_has_nl = 0, ret = 0; + var in_title = 0, qtype = 0; + + function cleanup() { + md.spanStack.length = org_work_size; + return ret ? i : 0; + } + + /* checking whether the correct renderer exists */ + if ((is_img && !md.callbacks.image) || (!is_img && !md.callbacks.link)) + return cleanup(); + /* looking for the matching closing bracket */ + for (level = 1; i < data.length; i++) { + if (data[i] == '\n') text_has_nl = 1; + else if (data[i - 1] == '\\') continue; + else if (data[i] == '[') level++; + else if (data[i] == ']') { + level--; + if (level <= 0) break; + } + } + + if (i >= data.length) return cleanup(); + + txt_e = i; + i++; + + /* skip any amount of whitespace or newline */ + /* (this is much more laxist than original markdown syntax) */ + while (i < data.length && _isspace(data[i])) i++; + + /* inline style link */ + if (i < data.length && data[i] == '(') { + /* skipping initial whitespace */ + i++; + + while (i < data.length && _isspace(data[i])) i++; + + link_b = i; + + /* looking for link end: ' " ) */ + while (i < data.length) { + if (data[i] == '\\') i += 2; + else if (data[i] == ')') break; + else if (i >= 1 && _isspace(data[i-1]) && (data[i] == '\'' || data[i] == '"')) break; + else i++; + } + + if (i >= data.length) return cleanup(); + link_e = i; + + /* looking for title end if present */ + if (data[i] == '\'' || data[i] == '"') { + qtype = data[i]; + in_title = 1; + i++; + title_b = i; + + while (i < data.length) { + if (data[i] == '\\') i += 2; + else if (data[i] == qtype) {in_title = 0; i++;} + else if ((data[i] == ')') && !in_title) break; + else i++; + } + + if (i >= data.length) return cleanup(); + + /* skipping whitespaces after title */ + title_e = i - 1; + while (title_e > title_b && _isspace(data[title_e])) title_e--; + + /* checking for closing quote presence */ + if (data[title_e] != '\'' && data[title_e] != '"') { + title_b = title_e = 0; + link_e = i; + } + } + + /* remove whitespace at the end of the link */ + while (link_e > link_b && _isspace(data[link_e - 1])) link_e--; + + /* remove optional angle brackets around the link */ + if (data[link_b] == '<') link_b++; + if (data[link_e - 1] == '>') link_e--; + + /* building escaped link and title */ + if (link_e > link_b) { + link = new Buffer(); + md.spanStack.push(link); + link.s += data.slice(link_b, link_e); + } + + if (title_e > title_b) { + title = new Buffer(); + md.spanStack.push(title); + title.s += data.slice(title_b, title_e); + } + + i++; + } + + /* reference style link */ + else if (i < data.length && data[i] == '[') { + var id = new Buffer(); + var lr = null; + + /* looking for the id */ + i++; + link_b = i; + while (i < data.length && data[i] != ']') i++; + if (i >= data.length) return cleanup(); + link_e = i; + + /* finding the link_ref */ + if (link_b == link_e) { + if (text_has_nl) { + var b = new Buffer(); + md.spanStack.push(b); + var j; + + for (j = 1; j < txt_e; j++) { + if (data[j] != '\n') + b.s += data[j]; + else if (data[j - 1] != ' ') + b.s += ' '; + } + + id.s = b.s; + } else { + id.s = data.slice(1); + } + } else { + id.s = data.slice(link_b, link_e); + } + + //TODO + lr = md.refs[id.s]; + if (!lr) return cleanup(); + + /* keeping link and title from link_ref */ + link = lr.link; + title = lr.title; + i++; + } + + /* shortcut reference style link */ + else { + var id = new Buffer(); + var lr = null; + + /* crafting the id */ + if (text_has_nl) { + var b = new Buffer(); + md.spanStack.push(b); + + var j; + for (j = 1; j < txt_e; j++) { + if (data[j] != '\n') b.s += data[j]; + else if (data[j - 1] != ' ') b.s += ' '; + } + + id.s = b.s; + } else { + id.s = data.slice(1, txt_e); + } + + /* finding the link_ref */ + lr = md.refs[id.s]; + if (!lr) return cleanup(); + + /* keeping link and title from link_ref */ + link = lr.link; + title = lr.title; + + /* rewinding the whitespace */ + i = txt_e + 1; + } + + /* building content: img alt is escaped, link content is parsed */ + if (txt_e > 1) { + content = new Buffer(); + md.spanStack.push(content); + if (is_img) { + content.s += data.slice(1, txt_e); + } else { + /* disable autolinking when parsing inline the + * content of a link */ + md.inLinkBody = 1; + parse_inline(content, md, data.slice(1, txt_e)); + md.inLinkBody = 0; + } + } + + if (link) { + u_link = new Buffer(); + md.spanStack.push(u_link); + unscape_text(u_link, link); + } else { + return cleanup(); + } + + /* calling the relevant rendering function */ + if (is_img) { + if (out.s.length && out.s[out.s.length - 1] == '!') + out.s = out.s.slice(0, -1); + + ret = md.callbacks.image(out, u_link, title, content, md.context); + } else { + ret = md.callbacks.link(out, u_link, title, content, md.context); + } + + /* cleanup */ + // cleanup: + // rndr->work_bufs[BUFFER_SPAN].size = (int)org_work_size; + // return ret ? i : 0; + return cleanup(); + } + + + /* char_langle_tag - '<' when tags or autolinks are allowed */ + function char_langle_tag(out, md, data_, offset) { + var data = data_.slice(offset); + var altype = {p:MKDA_NOT_AUTOLINK}; + var end = tag_length(data, altype); + var work = new Buffer(data.slice(0, end)); + var ret = 0; + + if (end > 2) { + if (md.callbacks.autolink && altype.p != MKDA_NOT_AUTOLINK) { + var u_link = new Buffer(); + md.spanStack.push(u_link); + work.s = data.substr(1 , end - 2); + unscape_text(u_link, work); + ret = md.callbacks.autolink(out, u_link, altype.p, md.context); + md.spanStack.pop(); + } + else if (md.callbacks.raw_html_tag) + ret = md.callbacks.raw_html_tag(out, work, md.context); + } + + if (!ret) return 0; + else return end; + } + + + /* char_escape - '\\' backslash escape */ + function char_escape(out, md, data_, offset) { + var data = data_.slice(offset); + var escape_chars = "\\`*_{}[]()#+-.!:|&<>/^~"; + var work = new Buffer(); + + if (data.length > 1) { + if (escape_chars.indexOf(data[1]) == -1) return 0; + + if (md.callbacks.normal_text) { + work.s = data[1]; + md.callbacks.normal_text(out, work, md.context); + } + else out.s += data[1]; + } else if (data.length == 1) { + out.s += data[0]; + } + + return 2; + } + + + + /* char_entity - '&' escaped when it doesn't belong to an entity */ + //-/* valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; */ + function char_entity(out, md, data_, offset) { + var data = data_.slice(offset); + var end = 1, content_start, content_end; + var numeric = false, hex = false, entity_base, entity_val; + var work = new Buffer(); + + if (end < data.length && data[end] === '#') { + numeric = true; + end++; + } + + if (end < data.length && numeric && data[end].toLowerCase() === 'x') { + hex = true; + end++; + } + + content_start = end; + while (end < data.length) { + var c = data[end]; + if (hex) { + if (!isxdigit(c)) break; + } else if (numeric) { + if (!isdigit(c)) break; + } else if (!isalnum(c)) { + break; + } + end++; + } + + + if (end > content_start && end < data.length && data[end] === ';') { + /* well formed entity */ + end++; + } else { + return 0; /* not an entity */ + } + + /* way too long to be a valid numeric entity */ + if (numeric && content_end - content_start > MAX_NUM_ENTITY_LEN) { + return 0; + } + + /* Validate the entity's contents */ + if (numeric) { + if (hex) entity_base = 16; + else entity_base = 10; + entity_val = parseInt(data.slice(content_start), entity_base); + if (!is_valid_numeric_entity(entity_val)) return 0; + } else { + if (VALID_ENTITIES.indexOf(data.slice(0, end)) === -1) return 0; + } + + if (md.callbacks.entity) { + work.s = data.slice(0, end); + md.callbacks.entity(out, work, md.context); + } + else out.s += data.slice(0, end); + + return end; + } + + function char_autolink_url(out, md, data_, offset) { + var data = data_.slice(offset); + var link = null; + var link_len, rewind = {p: null}; + + if (!md.callbacks.autolink || md.inLinkBody) return 0; + + link = new Buffer(); + md.spanStack.push(link); + + if ((link_len = sd_autolink__url(rewind, link, data_, offset, data.length, 0)) > 0) { + if (rewind.p > 0) out.truncate(out.s.length - rewind.p); + md.callbacks.autolink(out, link, MKDA_NORMAL, md.context); + } + + md.spanStack.pop(); + return link_len; + } + + + function char_autolink_email(out, md, data_, offset) { + var data = data_.slice(offset); + var link = null; + var link_len, rewind = {p: null}; + + if (!md.callbacks.autolink || md.inLinkBody) return 0; + + link = new Buffer(); + md.spanStack.push(link); + + if ((link_len = sd_autolink__email(rewind, link, data_, offset, data.length, 0)) > 0) { + if (rewind.p > 0) out.truncate(out.s.length - rewind.p); + md.callbacks.autolink(out, link, MKDA_EMAIL, md.context); + } + + md.spanStack.pop(); + return link_len; + } + + + function char_autolink_www(out, md, data_, offset) { + var data = data_.slice(offset); + var link = null, link_url = null, link_text = null; + var link_len, rewind = {p: null}; + + if (!md.callbacks.link || md.inLinkBody) return 0; + + link = new Buffer(); + md.spanStack.push(link); + + if ((link_len = sd_autolink__www(rewind, link, data_, offset, data.length, 0)) > 0) { + link_url = new Buffer(); + md.spanStack.push(link_url); + link_url.s += 'http://'; + link_url.s += link.s; + + if (rewind.p > 0) out.truncate(out.s.length - rewind.p); + + if (md.callbacks.normal_text) { + link_text = new Buffer(); + md.spanStack.push(link_text); + md.callbacks.normal_text(link_text, link, md.context); + md.callbacks.link(out, link_url, null, link_text, md.context); + md.spanStack.pop(); + } else { + md.callbacks.link(out, link_url, null, link, md.context); + } + md.spanStack.pop(); + } + + md.spanStack.pop(); + return link_len; + } + + function char_autolink_subreddit_or_username(out, md, data_, offset) { + var data = data_.slice(offset); + var link = null; + var link_len, rewind = {p: null}; + + if (!md.callbacks.autolink || md.inLinkBody) return 0; + + link = new Buffer(); + md.spanStack.push(link); + if ((link_len = sd_autolink__subreddit(rewind, link, data_, offset, data.length)) > 0) { + //don't slice because the rewind pointer will always be 0 + if (rewind.p > 0) out.truncate(out.s.length - rewind.p); + md.callbacks.autolink(out, link, MKDA_NORMAL, md.context); + } else if ((link_len = sd_autolink__username(rewind, link, data_, offset, data.length)) > 0) { + //don't slice because the rewind pointer will always be 0 + if (rewind.p > 0) out.truncate(out.s.length - rewind.p); + md.callbacks.autolink(out, link, MKDA_NORMAL, md.context); + } + md.spanStack.pop(); + + return link_len; + } + + function char_superscript(out, md, data_, offset) { + var data = data_.slice(offset); + var size = data.length; + var sup_start, sup_len; + var sup = null; + + if (!md.callbacks.superscript) return 0; + + if (size < 2) return 0; + + if (data[1] == '(') { + sup_start = sup_len = 2; + + while (sup_len < size && data[sup_len] != ')' && data[sup_len - 1] != '\\') sup_len++; + + if (sup_len == size) return 0; + } else { + sup_start = sup_len = 1; + + while (sup_len < size && !_isspace(data[sup_len])) sup_len++; + } + + if (sup_len - sup_start == 0) return (sup_start == 2) ? 3 : 0; + + sup = new Buffer(); + md.spanStack.push(sup); + parse_inline(sup, md, data.slice(sup_start, sup_len)); + md.callbacks.superscript(out, sup, md.context); + md.spanStack.pop(); + + return (sup_start == 2) ? sup_len + 1 : sup_len; + } + + + var markdown_char_ptrs = [ + null, + char_emphasis, + char_codespan, + char_linebreak, + char_link, + char_langle_tag, + char_escape, + char_entity, + char_autolink_url, + char_autolink_email, + char_autolink_www, + char_autolink_subreddit_or_username, + char_superscript + ]; + + var MKD_LIST_ORDERED = 1; + var MKD_LI_BLOCK = 2; /*
  • containing block data */ + var MKD_LI_END = 8; /* internal list flag */ + + var enumCounter = 0; + var MD_CHAR_NONE = enumCounter++; + var MD_CHAR_EMPHASIS = enumCounter++; + var MD_CHAR_CODESPAN = enumCounter++; + var MD_CHAR_LINEBREAK = enumCounter++; + var MD_CHAR_LINK = enumCounter++; + var MD_CHAR_LANGLE = enumCounter++; + var MD_CHAR_ESCAPE = enumCounter++; + var MD_CHAR_ENTITITY = enumCounter++; + var MD_CHAR_AUTOLINK_URL = enumCounter++; + var MD_CHAR_AUTOLINK_EMAIL = enumCounter++; + var MD_CHAR_AUTOLINK_WWW = enumCounter++; + var MD_CHAR_AUTOLINK_SUBREDDIT_OR_USERNAME = enumCounter++; + var MD_CHAR_SUPERSCRIPT = enumCounter++; + + var SD_AUTOLINK_SHORT_DOMAINS = (1 << 0); + + enumCounter = 0; + var MKDA_NOT_AUTOLINK = enumCounter++; /* used internally when it is not an autolink*/ + var MKDA_NORMAL = enumCounter++; /* normal http/http/ftp/mailto/etc link */ + var MKDA_EMAIL = enumCounter++; /* e-mail link without explit mailto: */ + + var MKDEXT_NO_INTRA_EMPHASIS = (1 << 0); + var MKDEXT_TABLES = (1 << 1); + var MKDEXT_FENCED_CODE = (1 << 2); + var MKDEXT_AUTOLINK = (1 << 3); + var MKDEXT_STRIKETHROUGH = (1 << 4); + // var MKDEXT_LAX_HTML_BLOCKS = (1 << 5); + var MKDEXT_SPACE_HEADERS = (1 << 6); + var MKDEXT_SUPERSCRIPT = (1 << 7); + var MKDEXT_LAX_SPACING = (1 << 8) + var MKDEXT_NO_EMAIL_AUTOLINK = (1 << 9) + + var HTML_SKIP_HTML = (1 << 0); + var HTML_SKIP_STYLE = (1 << 1); + var HTML_SKIP_IMAGES = (1 << 2); + var HTML_SKIP_LINKS = (1 << 3); + var HTML_EXPAND_TABS = (1 << 4); + var HTML_SAFELINK = (1 << 5); + var HTML_TOC = (1 << 6); + var HTML_HARD_WRAP = (1 << 7); + var HTML_USE_XHTML = (1 << 8); + var HTML_ESCAPE = (1 << 9); + var HTML_ALLOW_ELEMENT_WHITELIST = (1 << 10); + + var MKD_TABLE_ALIGN_L = 1; + var MKD_TABLE_ALIGN_R = 2; + var MKD_TABLE_ALIGN_CENTER = 3; + var MKD_TABLE_ALIGNMASK = 3; + var MKD_TABLE_HEADER = 4 + + var HTML_TAG_NONE = 0; + var HTML_TAG_OPEN = 1; + var HTML_TAG_CLOSE = 2; + + + /** + * A string buffer wrapper because JavaScript doesn't have mutable strings. + * @constructor + * @param {string=} str Optional string to initialize the Buffer with. + */ + function Buffer(str) { + this.s = str || ""; + }; + + /** + */ + Buffer.prototype.truncate = function(size) { + if (this.s.length < size) throw new RangeError("Buffer smaller than desired size"); + if (size < 0) throw new RangeError("Size argument is negative"); + this.s = this.s.slice(0, size); + } + + + /** + * A Markdown parser object. + * @constructor + */ + function Markdown() { + + //Becase javascript strings are immutable they must be wrapped with Buffer() + this.spanStack = []; + this.blockStack = []; + this.extensions = MKDEXT_NO_INTRA_EMPHASIS | MKDEXT_SUPERSCRIPT | MKDEXT_AUTOLINK | MKDEXT_STRIKETHROUGH | MKDEXT_TABLES | MKDEXT_TABLES; + var renderer = getRedditRenderer(); + this.context = renderer.context; + this.callbacks = renderer.callbacks; + this.inLinkBody = 0; + this.activeChars = {}; + this.refs = {}; + this.nestingLimit = 16; + this.maxTableCols = 64; + }; + + + /* is_empty - returns the line length when it is empty, 0 otherwise */ + function is_empty(data) { + var i; + for (i = 0; i < data.length && data[i] != '\n'; i++) + if (data[i] != ' ') return 0; + + return i + 1; + } + + + /* is_hrule - returns whether a line is a horizontal rule */ + function is_hrule(data) { + var i = 0, n = 0; + var c; + + /* skipping initial spaces */ + if (data.length < 3) return 0; + if (data[0] == ' ') { i++; + if (data[1] == ' ') { i++; + if (data[2] == ' ') { i++; } } } + + /* looking at the hrule uint8_t */ + if (i + 2 >= data.length + || (data[i] != '*' && data[i] != '-' && data[i] != '_')) + return 0; + c = data[i]; + + /* the whole line must be the char or whitespace */ + while (i < data.length && data[i] != '\n') { + if (data[i] == c) n++; + else if (data[i] != ' ') + return 0; + + i++; + } + + return n >= 3; + } + + + /* check if a line begins with a code fence; return the + * width if it is */ + function prefix_codefence(data) { + var i = 0, n = 0; + var c; + + /* skipping initial spaces */ + if (data.length < 3) return 0; + if (data[0] == ' ') { i++; + if (data[1] == ' ') { i++; + if (data[2] == ' ') { i++; } } } + + /* looking at the hrule uint8_t */ + if (i + 2 >= data.length || !(data[i] == '~' || data[i] == '`')) + return 0; + + c = data[i]; + + /* the whole line must be the uint8_t or whitespace */ + while (i < data.length && data[i] == c) { + n++; i++; + } + + if (n < 3) return 0; + + return i; + } + + /* check if a line is a code fence; return its size if it */ + function is_codefence(data, syntax) { + var i = 0, syn_len = 0; + i = prefix_codefence(data); + if (i == 0) return 0; + + + while (i < data.length && data[i] == ' ') i++; + + var syn_start; + //syn_start = data + i; + syn_start = i; + + if (i < data.length && data[i] == '{') { + i++; syn_start++; + + while (i < data.length && data[i] != '}' && data[i] != '\n') { + syn_len++; i++; + } + + if (i == data.length || data[i] != '}') + return 0; + + /* strip all whitespace at the beginning and the end + * of the {} block */ + /*remember not to remove the +0, it helps me keep syncronised with snudown*/ + while (syn_len > 0 && _isspace(data[syn_start+0])) { + syn_start++; syn_len--; + } + + // while (syn_len > 0 && _isspace(syn_start[syn_len - 1])) + while (syn_len > 0 && _isspace(data[syn_start+syn_len - 1])) + syn_len--; + + i++; + } else { + while (i < data.length && !_isspace(data[i])) { + syn_len++; i++; + } + } + + if (syntax) syntax.s = data.substr(syn_start, syn_len); + // syntax->size = syn; + + while (i < data.length && data[i] != '\n') { + if (!_isspace(data[i])) return 0; + + i++; + } + + return i + 1; + } + + /* find_emph_char - looks for the next emph uint8_t, skipping other constructs */ + function find_emph_char(data, c) { + var i = 1; + while (i < data.length) { + while (i < data.length && data[i] != c && data[i] != '`' && data[i] != '[') + i++; + + if (i == data.length) return 0; + + if (data[i] == c) + return i; + + /* not counting escaped chars */ + if (i && data[i - 1] == '\\') { + i++; continue; + } + + if (data[i] == '`') { + var span_nb = 0, bt; + var tmp_i = 0; + + /* counting the number of opening backticks */ + while (i < data.length && data[i] == '`') { + i++; span_nb++; + } + + if (i >= data.length) return 0; + + /* finding the matching closing sequence */ + bt = 0; + while (i < data.length && bt < span_nb) { + if (!tmp_i && data[i] == c) tmp_i = i; + if (data[i] == '`') bt++; + else bt = 0; + i++; + } + + if (i >= data.length) return tmp_i; + } + /* skipping a link */ + else if (data[i] == '[') { + var tmp_i = 0; + var cc; + + i++; + while (i < data.length && data[i] != ']') { + if (!tmp_i && data[i] == c) tmp_i = i; + i++; + } + + i++; + while (i < data.length && (data[i] == ' ' || data[i] == '\n')) + i++; + + if (i >= data.length) return tmp_i; + + switch (data[i]) { + case '[': + cc = ']'; break; + + case '(': + cc = ')'; break; + + default: + if (tmp_i) + return tmp_i; + else + continue; + } + + i++; + while (i < data.length && data[i] != cc) { + if (!tmp_i && data[i] == c) tmp_i = i; + i++; + } + + if (i >= data.length) return tmp_i; + i++; + } + } + + return 0; + } + + /* parse_emph1 - parsing single emphase */ + /* closed by a symbol not preceded by whitespace and not followed by symbol */ + function parse_emph1(out, md, data_, c) { + var data = data_.slice(1); + var i = 0, len; + var r; + + if (!md.callbacks.emphasis) return 0; + + /* skipping one symbol if coming from emph3 */ + if (data.length > 1 && data[0] == c && data[1] == c) i = 1; + + while (i < data.length) { + len = find_emph_char(data.slice(i), c); + if (!len) return 0; + i += len; + if (i >= data.length) return 0; + + if (data[i] == c && !_isspace(data[i - 1])) { + if ((md.extensions & MKDEXT_NO_INTRA_EMPHASIS) && (c == '_')) { + if (!(i + 1 == data.length || _isspace(data[i + 1]) || ispunct(data[i + 1]))) + continue; + } + + var work = new Buffer(); + md.spanStack.push(work); + parse_inline(work, md, data.slice(0, i)); + r = md.callbacks.emphasis(out, work, md.context); + md.spanStack.pop(); + return r ? i + 1 : 0; + } + } + + return 0; + } + + /* parse_emph2 - parsing single emphase */ + function parse_emph2(out, md, data_, c) { + var data = data_.slice(2); + var i = 0, len; + var r; + + var render_method = (c == '~') ? md.callbacks.strikethrough : md.callbacks.double_emphasis; + + if (!render_method) return 0; + + while (i < data.length) { + len = find_emph_char(data.slice(i), c); + if (!len) return 0; + i += len; + + if (i + 1 < data.length && data[i] == c && data[i + 1] == c && i && !_isspace(data[i - 1])) { + var work = new Buffer(); + md.spanStack.push(work); + parse_inline(work, md, data.slice(0, i)); + r = render_method(out, work, md.context); + md.spanStack.pop(); + return r ? i + 2 : 0; + } + i++; + } + return 0; + } + + /* parse_emph3 • parsing single emphase */ + /* finds the first closing tag, and delegates to the other emph */ + function parse_emph3(out, md, data_, c) { + var data = data_.slice(3); + var i = 0, len; + var r; + + while (i < data.length) { + len = find_emph_char(data.slice(i), c); + if (!len) return 0; + i += len; + + /* skip whitespace preceded symbols */ + if (data[i] != c || _isspace(data[i - 1])) continue; + + if (i + 2 < data.length && data[i + 1] == c && data[i + 2] == c && md.callbacks.triple_emphasis) { + /* triple symbol found */ + var work = new Buffer(); + md.spanStack.push(work); + parse_inline(work, md, data.slice(0, i)); + r = md.callbacks.triple_emphasis(out, work, md.context); + md.spanStack.pop(); + return r ? i + 3 : 0; + + } else if (i + 1 < data.length && data[i + 1] == c) { + /* double symbol found, handing over to emph1 */ + len = parse_emph1(out, md, data_, c); + if (!len) return 0; + else return len - 2; + + } else { + /* single symbol found, handing over to emph2 */ + len = parse_emph2(out, md, data_, c); + if (!len) return 0; + else return len - 1; + } + } + return 0; + } + + function is_atxheader(md, data) { + if (data[0] != '#') return false; + + if (md.extensions & MKDEXT_SPACE_HEADERS) { + var level = 0; + + while (level < data.length && level < 6 && data[level] == '#') + level++; + + if (level < data.length && data[level] != ' ') + return false; + } + + return true; + } + + + /* is_headerline . returns whether the line is a setext-style hdr underline */ + function is_headerline(data) { + var i = 0; + var size = data.length; + + /* test of level 1 header */ + if (data[i] == '=') { + for (i = 1; i < size && data[i] == '='; i++) {} + while (i < size && data[i] == ' ') i++; + return (i >= size || data[i] == '\n') ? 1 : 0; } + + /* test of level 2 header */ + if (data[i] == '-') { + for (i = 1; i < size && data[i] == '-'; i++) {} + while (i < size && data[i] == ' ') i++; + return (i >= size || data[i] == '\n') ? 2 : 0; } + + return 0; + } + + function is_next_headerline(data) { + var size = data.length; + var i = 0; + + while (i < size && data[i] != '\n') i++; + + if (++i >= size) return 0; + + return is_headerline(data.slice(i)); + } + + /* prefix_quote - returns blockquote prefix length */ + function prefix_quote(data) { + var i = 0; + var size = data.length; + if (i < size && data[i] == ' ') i++; + if (i < size && data[i] == ' ') i++; + if (i < size && data[i] == ' ') i++; + + if (i < size && data[i] == '>') { + if (i + 1 < size && data[i + 1] == ' ') + return i + 2; + + return i + 1; + } + + return 0; + } + + /* prefix_code • returns prefix length for block code*/ + function prefix_code(data) { + if (data.length > 3 && data[0] == ' ' && data[1] == ' ' + && data[2] == ' ' && data[3] == ' ') return 4; + + return 0; + } + + /* prefix_oli - returns ordered list item prefix */ + function prefix_oli(data) { + var size = data.length; + var i = 0; + + if (i < size && data[i] == ' ') i++; + if (i < size && data[i] == ' ') i++; + if (i < size && data[i] == ' ') i++; + + if (i >= size || data[i] < '0' || data[i] > '9') return 0; + + while (i < size && data[i] >= '0' && data[i] <= '9') i++; + + if (i + 1 >= size || data[i] != '.' || data[i + 1] != ' ') return 0; + + if (is_next_headerline(data.slice(i))) return 0; + + return i + 2; + } + + /* prefix_uli - returns ordered list item prefix */ + function prefix_uli(data) { + var size = data.length; + var i = 0; + + if (i < size && data[i] == ' ') i++; + if (i < size && data[i] == ' ') i++; + if (i < size && data[i] == ' ') i++; + + if (i + 1 >= size || + (data[i] != '*' && data[i] != '+' && data[i] != '-') || + data[i + 1] != ' ') + return 0; + + if (is_next_headerline(data.slice(i))) return 0; + + return i + 2; + } + + /* is_mail_autolink - looks for the address part of a mail autolink and '>' */ + /* this is less strict than the original markdown e-mail address matching */ + function is_mail_autolink(data) { + var i = 0, nb = 0; + + /* address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@' */ + for (i = 0; i < data.length; ++i) { + if (isalnum(data[i])) + continue; + + switch (data[i]) { + case '@': + nb++; + + case '-': + case '.': + case '_': + break; + + case '>': + return (nb == 1) ? i + 1 : 0; + + default: + return 0; + } + } + + return 0; + } + + /* tag_length - returns the length of the given tag, or 0 is it's not valid */ + function tag_length(data, autolink) { + var i, j; + + /* a valid tag can't be shorter than 3 chars */ + if (data.length < 3) return 0; + + /* begins with a '<' optionally followed by '/', followed by letter or number */ + if (data[0] != '<') return 0; + i = (data[1] == '/') ? 2 : 1; + + if (!isalnum(data[i])) return 0; + + /* scheme test */ + autolink.p = MKDA_NOT_AUTOLINK; + + /* try to find the beginning of an URI */ + while (i < data.length && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-')) i++; + + if (i > 1 && data[i] == '@') { + if ((j = is_mail_autolink(data.slice(i))) != 0) { + autolink.p = MKDA_EMAIL; + return i + j; + } + } + + if (i > 2 && data[i] == ':') { + autolink.p = MKDA_NORMAL; + i++; + } + + /* completing autolink test: no whitespace or ' or " */ + if (i >= data.length) autolink.p = MKDA_NOT_AUTOLINK; + else if (autolink.p) { + j = i; + + while (i < data.length) { + if (data[i] == '\\') i += 2; + else if (data[i] == '>' || data[i] == '\'' || + data[i] == '"' || data[i] == ' ' || data[i] == '\n') + break; + else i++; + } + + if (i >= data.length) return 0; + if (i > j && data[i] == '>') return i + 1; + /* one of the forbidden chars has been found */ + autolink.p = MKDA_NOT_AUTOLINK; + } + + /* looking for sometinhg looking like a tag end */ + while (i < data.length && data[i] != '>') i++; + if (i >= data.length) return 0; + return i + 1; + } + + // parse_inline - parses inline markdown elements + //Buffer, md, String + function parse_inline(out, md, data) { + var i = 0, end = 0; + var action = 0; + var work = new Buffer(); + + if (md.spanStack.length + md.blockStack.length > md.nestingLimit) + return; + + while (i < data.length) { + /* copying inactive chars into the output */ + while (end < data.length && !(action = md.activeChars[data[end]])) { + end++; + } + + if (md.callbacks.normal_text) { + work.s = data.slice(i, end); + md.callbacks.normal_text(out, work, md.context); + } + else + out.s += data.slice(i, end); + + if (end >= data.length) break; + i = end; + + end = markdown_char_ptrs[action](out, md, data, i); + if (!end) /* no action from the callback */ + end = i + 1; + else { + i += end; + end = i; + } + } + } + + /* parse_atxheader - parsing of atx-style headers */ + function parse_atxheader(out, md, data) { + var level = 0; + var i, end, skip; + + while (level < data.length && level < 6 && data[level] == '#') level++; + + for (i = level; i < data.length && data[i] == ' '; i++) {} + + for (end = i; end < data.length && data[end] != '\n'; end++) {} + skip = end; + + while (end && data[end - 1] == '#') end--; + + while (end && data[end - 1] == ' ') end--; + + if (end > i) { + var work = new Buffer(); + md.spanStack.push(work); + + parse_inline(work, md, data.slice(i, end)); + + if (md.callbacks.header) + md.callbacks.header(out, work, level, md.context); + + md.spanStack.pop(); + } + + return skip; + } + + /* htmlblock_end - checking end of HTML block : [ \t]*\n[ \t*]\n */ + /* returns the length on match, 0 otherwise */ + // htmlblock_end(const char *tag, size_t tag_len, struct sd_markdown *rndr, uint8_t *data, size_t size) + function htmlblock_end(tag, md, data) { + var i, w; + + /* checking if tag is a match */ + //tag should already be lowercase + if (tag.length + 3 >= data.length || + data.slice(2).toLowerCase() != tag || + data[tag.length + 2] != '>') + return 0; + + /* checking white lines */ + i = tag.length + 3; + w = 0; + if (i < data.length && (w = is_empty(data.slice(i))) == 0) + return 0; /* non-blank after tag */ + i += w; + w = 0; + + if (i < data.length) w = is_empty(data.slice(i)); + + return i + w; + } + + /* parse_htmlblock - parsing of inline HTML block */ + //TODO + function parse_htmlblock(out, md, data, do_render) { + var i, j = 0; + var curtag = null; + var found; + var work = new Buffer(data); + + /* identification of the opening tag */ + if (data.length < 2 || data[0] != '<') return 0; + + i = 1; + while (i < data.length && data[i] != '>' && data[i] != ' ') i++; + + if (i < data.length) curtag = find_block_tag(data.slice(1)); + + /* handling of special cases */ + if (!curtag) { + + /* HTML comment, laxist form */ + if (data.length > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-') { + i = 5; + + while (i < data.length && !(data[i - 2] == '-' && data[i - 1] == '-' && data[i] == '>')) i++; + + i++; + + if (i < size) + j = is_empty(data.slice(i)); + + if (j) { + //TODO: HANDLE WORK!!! + // work.size = i + j; + work.s = data.slice(0, i + j); + if (do_render && md.callbacks.blockhtml) + md.callbacks.blockhtml(out, work, md.context); + return work.s.length; + } + } + + /* HR, which is the only self-closing block tag considered */ + if (data.length > 4 && (data[1] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R')) { + i = 3; + while (i < data.length && data[i] != '>') i++; + + if (i + 1 < data.length) { + i++; + j = is_empty(data.slice(i)); + if (j) { + work.s = data.slice(0, i + j); + if (do_render && md.callbacks.blockhtml) + md.callbacks.blockhtml(out, work, md.context); + return work.s.length; + } + } + } + + /* no special case recognised */ + return 0; + } + + /* looking for an unindented matching closing tag */ + /* followed by a blank line */ + i = 1; + found = 0; + + /* if not found, trying a second pass looking for indented match */ + /* but not if tag is "ins" or "del" (following original Markdown.pl) */ + if (curtag != 'ins' && curtag != 'del') { + var tag_size = curtag.length; + i = 1; + while (i < data.length) { + i++; + while (i < data.length && !(data[i - 1] == '<' && data[i] == '/')) + i++; + + if (i + 2 + tag_size >= data.length) + break; + + // j = htmlblock_end(tag, md, data + i - 1, size - i + 1); + //TODO + j = htmlblock_end(tag, md, data.slice(i - 1)); + + if (j) { + i += j - 1; + found = 1; + break; + } + } + } + + if (!found) return 0; + + /* the end of the block has been found */ + //TODO: + work.s = work.s.slice(0, i); + if (do_render && md.callbacks.blockhtml) + md.callbacks.blockhtml(out, work, md.context); + + return i; + } + + /* parse_blockquote - handles parsing of a blockquote fragment */ + function parse_blockquote(out, md, data) { + var size = data.length; + var beg, end = 0, pre, work_size = 0; + // uint8_t *work_data = 0; + var work_data = ""; + var work_data_cursor = 0; + + var out_ = new Buffer(); + md.blockStack.push(out_); + + beg = 0; + while (beg < size) { + for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) {} + + pre = prefix_quote(data.slice(beg, end)); + + if (pre) beg += pre; /* skipping prefix */ + + /* empty line followed by non-quote line */ + else if (is_empty(data.slice(beg, end)) && + (end >= size || (prefix_quote(data.slice(end)) == 0 && + !is_empty(data.slice(end))))) + break; + + if (beg < end) { /* copy into the in-place working buffer */ + /* bufput(work, data + beg, end - beg); */ + //TODO:!!! FIX THIS!!! + // if (!work_data) work_data = data.slice(beg, end); + // work_data = data + beg; + work_data += data.slice(beg, end); + /* + if (!work_data) work_data_cursor = beg; + else if (beg != work_data_cursor + work_size) + work_data += data.slice(beg, end); + */ + // memmove(work_data + work_size, data + beg, end - beg); + work_size += end - beg; + } + beg = end; + } + + parse_block(out_, md, work_data); + if (md.callbacks.blockquote) + md.callbacks.blockquote(out, out_, md.context); + md.blockStack.pop(); + return end; + } + + /* parse_paragraph - handles parsing of a regular paragraph */ + function parse_paragraph(out, md, data) { + var i = 0, end = 0; + var level = 0; + var size = data.length; + var work = new Buffer(data); + + while (i < size) { + for (end = i + 1; end < size && data[end - 1] != '\n'; end++) {/* empty */} + + if (prefix_quote(data.slice(i, end)) != 0) { + end = i; + break; + } + + var tempdata = data.slice(i); + if (is_empty(tempdata) || (level = is_headerline(tempdata)) != 0) break; + if (is_empty(tempdata)) break; + if ((level = is_headerline(tempdata)) != 0) break; + + if (is_atxheader(md, tempdata) + || is_hrule(tempdata) + || prefix_quote(tempdata)) { + end = i; + break; + } + + /* + * Early termination of a paragraph with the same logic + * as markdown 1.0.0. If this logic is applied, the + * Markdown 1.0.3 test suite wont pass cleanly. + * + * :: If the first character in a new line is not a letter + * lets check to see if there's some kind of block starting here + */ + if ((md.extensions & MKDEXT_LAX_SPACING) && !isalnum(data[i])) { + if (prefix_oli(tempdata) + || prefix_uli(tempdata)) { + end = i; + break; + } + /* see if an html block starts here */ + if (data[i] == '<' && md.callbacks.blockhtml + && parse_htmlblock(out, md, tempdata, 0)) { + end = i; + break + } + + /* see if a code fence starts here */ + if ((md.extensions && MKDEXT_FENCED_CODE) != 0 + && is_codefence(tempdata, null) != 0) { + end = i; + break; + } + } + + i = end; + } + + var work_size = i; + while (work_size && data[work_size - 1] == '\n') work_size--; + work.s = work.s.slice(0, work_size); + + if (!level) { + var tmp = new Buffer(); + md.blockStack.push(tmp); + parse_inline(tmp, md, work.s); + if (md.callbacks.paragraph) + md.callbacks.paragraph(out, tmp, md.context); + md.blockStack.pop(); + } else { + var header_work = null; + + if (work.size) { + var beg; + i = work.s.length; + // var work_size = work.s.length - 1; + // work.size -= 1; + + while (work_size && data[work_size] != '\n') + work_size -= 1; + + beg = work_size + 1; + while (work_size && data[work_size - 1] == '\n') + work_size -= 1; + + work.s = work.s.slice(0, work_size); + if (work_size > 0) { + var tmp = new Buffer(); + md.blockStack.push(tmp); + parse_inline(tmp, md, work.s); + + if (md.callbacks.paragraph) + md.callbacks.paragraph(out, tmp, md.context); + + md.blockStack.pop(); + work.s = work.s.slice(beg, i); + } + else work.s = work.s.slice(0, i); + } + + header_work = new Buffer(); + md.spanStack.push(header_work); + parse_inline(header_work, md, work.s); + + if (md.callbacks.header) + md.callbacks.header(out, header_work, level, md.context); + + md.spanStack.pop(); + } + + return end; + } + + /* parse_fencedcode - handles parsing of a block-level code fragment */ + function parse_fencedcode(out, md, data) { + var beg, end; + var work = null; + var lang = new Buffer(); + + beg = is_codefence(data, lang); + if (beg == 0) return 0; + + work = new Buffer(); + md.blockStack.push(work); + + while (beg < data.length) { + var fence_end; + var fence_trail = new Buffer(); + + fence_end = is_codefence(data.slice(beg), fence_trail); + if (fence_end != 0 && fence_trail.s.length == 0) { + beg += fence_end; + break; + } + + for (end = beg + 1; end < data.length && data[end - 1] != '\n'; end++) {} + + if (beg < end) { + /* verbatim copy to the working buffer, + escaping entities */ + var tempData = data.slice(beg, end); + if (is_empty(tempData)) work.s += '\n'; + else work.s += tempData; + } + beg = end; + } + + if (work.s.length && work.s[work.s.length - 1] != '\n') + work.s += '\n'; + + if (md.callbacks.blockcode) + md.callbacks.blockcode(out, work, lang.s.length ? lang : null, md.context); + + md.blockStack.pop(); + return beg; + } + + function parse_blockcode(out, md, data) { + var size = data.length; + var beg, end, pre; + + var work = null; + md.blockStack.push(work = new Buffer()); + + beg = 0; + while (beg < size) { + for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) {}; + pre = prefix_code(data.slice(beg, end)); + + if (pre) beg += pre; /* skipping prefix */ + else if (!is_empty(data.slice(beg, end))) + /* non-empty non-prefixed line breaks the pre */ + break; + + if (beg < end) { + /* verbatim copy to the working buffer, + escaping entities */ + if (is_empty(data.slice(beg, end))) work.s += '\n'; + else work.s += data.slice(beg, end); + } + beg = end; + } + + var work_size = work.s.length; + while (work_size && work.s[work_size - 1] == '\n') work_size -= 1; + work.s = work.s.slice(0, work_size); + + work.s += '\n'; + + if (md.callbacks.blockcode) + md.callbacks.blockcode(out, work, null, md.context); + + md.blockStack.pop(); + return beg; + } + + /* parse_listitem - parsing of a single list item */ + /* assuming initial prefix is already removed */ + //FLAGS is pointer + function parse_listitem(out, md, data, flags) { + var size = data.length; + var work = null, inter = null; + var beg = 0, end, pre, sublist = 0, orgpre = 0, i; + var in_empty = 0, has_inside_empty = 0, in_fence = 0; + + /* keeping track of the first indentation prefix */ + while (orgpre < 3 && orgpre < size && data[orgpre] == ' ') + orgpre++; + + //TODO + beg = prefix_uli(data); + if (!beg) beg = prefix_oli(data); + + if (!beg) return 0; + + /* skipping to the beginning of the following line */ + end = beg; + while (end < size && data[end - 1] != '\n') end++; + + /* getting working buffers */ + md.spanStack.push(work = new Buffer()); + md.spanStack.push(inter = new Buffer()); + + /* putting the first line into the working buffer */ + work.s += data.slice(beg, end); + beg = end; + + /* process the following lines */ + while (beg < size) { + var has_next_uli, has_next_oli; + end++; + + while (end < size && data[end - 1] != '\n') end++; + + /* process an empty line */ + if (is_empty(data.slice(beg, end))) { + in_empty = 1; + beg = end; + continue; + } + + /* calculating the indentation */ + i = 0; + while (i < 4 && beg + i < end && data[beg + i] == ' ') i++; + + pre = i; + + //TODO: Cache this slice? + if (md.flags & MKDEXT_FENCED_CODE) { + if (is_codefence(data.slice(beg+i, end), null) != 0) { + in_fence = !in_fence; + } + } + + /* only check for new list items if we are **not** in a fenced code block */ + if (!in_fence) { + has_next_uli = prefix_uli(data.slice(beg+i, end)); + has_next_oli = prefix_oli(data.slice(beg+i, end)); + } + + /* checking for ul/ol switch */ + if (in_empty && ( + ((flags.p & MKD_LIST_ORDERED) && has_next_uli) || + (!(flags.p & MKD_LIST_ORDERED) && has_next_oli))){ + flags.p |= MKD_LI_END; + break; /* the following item must have same list type */ + } + + /* checking for a new item */ + if ((has_next_uli && !is_hrule(data.slice(beg+i, end))) || has_next_oli) { + if (in_empty) has_inside_empty = 1; + + if (pre == orgpre) /* the following item must have */ + break; /* the same indentation */ + + if (!sublist) sublist = work.s.length; + } + /* joining only indented stuff after empty lines; + * note that now we only require 1 space of indentation + * to continue list */ + else if (in_empty && pre == 0) { + flags.p |= MKD_LI_END; + break; + } + else if (in_empty) { + work.s += '\n'; + has_inside_empty = 1; + } + + in_empty = 0; + + /* adding the line without prefix into the working buffer */ + work.s += data.slice(beg + i, end); + beg = end; + } + + /* render of li contents */ + if (has_inside_empty) flags.p |= MKD_LI_BLOCK; + + if (flags.p & MKD_LI_BLOCK) { + /* intermediate render of block li */ + if (sublist && sublist < work.s.length) { + parse_block(inter, md, work.s.slice(0, sublist)); + parse_block(inter, md, work.s.slice(sublist)); + } + else + parse_block(inter, md, work.s); + } else { + //TODO: + /* intermediate render of inline li */ + if (sublist && sublist < work.s.length) { + parse_inline(inter, md, work.s.slice(0, sublist)); + parse_block(inter, md, work.s.slice(sublist)); + } + else + parse_inline(inter, md, work.s); + } + + /* render of li itself */ + if (md.callbacks.listitem) + md.callbacks.listitem(out, inter, flags.p, md.context); + + md.spanStack.pop(); + md.spanStack.pop(); + return beg; + } + + + /* parse_list - parsing ordered or unordered list block */ + function parse_list(out, md, data, flags) { + var size = data.length; + var i = 0, j; + + var work = null; + md.blockStack.push(work = new Buffer()); + + while (i < size) { + var flag_p = {p: flags}; + j = parse_listitem(work, md, data.slice(i), flag_p); + flags = flag_p.p; + i += j; + + if (!j || (flags & MKD_LI_END)) break; + } + + if (md.callbacks.list) + md.callbacks.list(out, work, flags, md.context); + md.blockStack.pop(); + return i; + } + + function parse_table_row(out, md, data, columns, header_flag) { + var i = 0, col, cols_left; + var row_work = null; + + if (!md.callbacks.table_cell || !md.callbacks.table_row) return; + + md.spanStack.push(row_work = new Buffer()); + + if (i < data.length && data[i] == '|') i++; + + for (col = 0; col < columns.length && i < data.length; ++col) { + var cell_start, cell_end; + var cell_work; + + md.spanStack.push(cell_work = new Buffer()); + + while (i < data.length && _isspace(data[i])) i++; + + cell_start = i; + + while (i < data.length && data[i] != '|') i++; + + cell_end = i - 1; + + while (cell_end > cell_start && _isspace(data[cell_end])) cell_end--; + + // parse_inline(cell_work, rndr, data + cell_start, 1 + cell_end - cell_start); + parse_inline(cell_work, md, data.slice(cell_start, 1 + cell_end)); + md.callbacks.table_cell(row_work, cell_work, columns[col] | header_flag, md.context, 0); + + md.spanStack.pop(); + i++; + } + + cols_left = columns.length - col; + // for (; col < columns.length; ++col) { + if (cols_left > 0) { + var empty_cell = null; + md.callbacks.table_cell(row_work, empty_cell, columns[col] | header_flag, md.context, cols_left); + } + + md.callbacks.table_row(out, row_work, md.context); + + md.spanStack.pop(); + } + + function parse_table_header(out, md, data, columns) { + var i = 0, col, header_end, under_end; + + var pipes = 0; + while (i < data.length && data[i] != '\n') + if (data[i++] == '|') pipes++; + + if (i == data.length || pipes == 0) + return 0; + + header_end = i; + + while (header_end > 0 && _isspace(data[header_end - 1])) header_end--; + + if (data[0] == '|') pipes--; + + if (header_end && data[header_end - 1] == '|') pipes--; + + if (pipes + 1 > md.maxTableCols) return 0; + // columns.p = pipes + 1; + // column_data.p = new Array(columns.p); + columns.p = new Array(pipes + 1); + for (var k = 0; k < columns.p.length; k++) columns.p[k] = 0; + + /* Parse the header underline */ + i++; + if (i < data.length && data[i] == '|') i++; + + under_end = i; + while (under_end < data.length && data[under_end] != '\n') under_end++; + + for (col = 0; col < columns.p.length && i < under_end; ++col) { + var dashes = 0; + + while (i < under_end && data[i] == ' ') i++; + + if (data[i] == ':') { + i++; + columns.p[col] |= MKD_TABLE_ALIGN_L; + dashes++; + } + + while (i < under_end && data[i] == '-') { + i++; dashes++; + } + + if (i < under_end && data[i] == ':') { + i++; columns.p[col] |= MKD_TABLE_ALIGN_R; + dashes++; + } + + while (i < under_end && data[i] == ' ') i++; + + if (i < under_end && data[i] != '|') break; + + if (dashes < 1) break; + + i++; + } + + if (col < columns.p.length) return 0; + + parse_table_row(out, md, data.slice(0, header_end), columns.p, MKD_TABLE_HEADER); + + return under_end + 1; + } + + function parse_table(out, md, data) { + var i; + var header_work, body_work; + + var columns = {p: null}; + + md.spanStack.push(header_work = new Buffer()); + md.blockStack.push(body_work = new Buffer()); + + i = parse_table_header(header_work, md, data, columns); + if (i > 0) { + + while (i < data.length) { + var row_start; + var pipes = 0; + + row_start = i; + + while (i < data.length && data[i] != '\n') + if (data[i++] == '|') + pipes++; + + if (pipes == 0 || i == data.length) { + i = row_start; + break; + } + + parse_table_row(body_work, md, data.slice(row_start, i), columns.p, 0); + + i++; + } + + if (md.callbacks.table) + md.callbacks.table(out, header_work, body_work, md.context); + } + + md.spanStack.pop(); + md.blockStack.pop(); + return i; + } + + function parse_block(out, md, data) { + var beg = 0, end, i; + var textData; + + if (md.spanStack.length + + md.blockStack.length > md.nestingLimit) + return; + + while (beg < data.length) { + textData = data.slice(beg); + end = data.length - beg; + + if (is_atxheader(md, textData)) + beg += parse_atxheader(out, md, textData); + + else if (data[beg] == '<' && md.callbacks.blockhtml && + (i = parse_htmlblock(out, md, textData, 1)) != 0) + beg += i; + else if ((i = is_empty(textData)) != 0) + beg += i; + else if (is_hrule(textData)) { + if (md.callbacks.hrule) + md.callbacks.hrule(out, md.context); + + while (beg < data.length && data[beg] != '\n') beg++; + + beg++; + } + + else if ((md.extensions & MKDEXT_FENCED_CODE) != 0 && + (i = parse_fencedcode(out, md, textData)) != 0) + beg += i; + + else if ((md.extensions & MKDEXT_TABLES) != 0 && + (i = parse_table(out, md, textData)) != 0) + beg += i; + + else if (prefix_quote(textData)) + beg += parse_blockquote(out, md, textData); + + else if (prefix_code(textData)) + beg += parse_blockcode(out, md, textData); + + else if (prefix_uli(textData)) + beg += parse_list(out, md, textData, 0); + + else if (prefix_oli(textData)) + beg += parse_list(out, md, textData, MKD_LIST_ORDERED); + + else { + beg += parse_paragraph(out, md, textData); + } + } + } + + function is_ref(data, beg, end, md) { + /* int n; */ + var i = 0; + var idOffset, idEnd; + var linkOffset, linkEnd; + var titleOffset, titleEnd; + var lineEnd; + + /* up to 3 optional leading spaces */ + if (beg + 3 >= end) return 0; + if (data[beg] == ' ') { i = 1; + if (data[beg + 1] == ' ') { i = 2; + if (data[beg + 2] == ' ') { i = 3; + if (data[beg + 3] == ' ') return 0; } } } + i += beg; + + /* id part: anything but a newline between brackets */ + if (data[i] != '[') return 0; + i++; + idOffset = i; + while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']') i++; + if (i >= end || data[i] != ']') return 0; + idEnd = i; + + /* spacer: colon (space | tab)* newline? (space | tab)* */ + i++; + if (i >= end || data[i] != ':') return 0; + i++; + while (i < end && data[i] == ' ') i++; + if (i < end && (data[i] == '\n' || data[i] == '\r')) { + i++; + if (i < end && data[i] == '\r' && data[i - 1] == '\n') i++; } + while (i < end && data[i] == ' ') i++; + if (i >= end) return 0; + + /* link: whitespace-free sequence, optionally between angle brackets */ + if (data[i] == '<') i++; + + linkOffset = i; + while (i < end && data[i] != ' ' && data[i] != '\n' && data[i] != '\r') i++; + + if (data[i - 1] == '>') linkEnd = i - 1; else linkEnd = i; + + /* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */ + while (i < end && data[i] == ' ') i++; + if (i < end && data[i] != '\n' && data[i] != '\r' + && data[i] != '\'' && data[i] != '"' && data[i] != '(') + return 0; + lineEnd = 0; + /* computing end-of-line */ + if (i >= end || data[i] == '\r' || data[i] == '\n') lineEnd = i; + if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r') + lineEnd = i + 1; + + /* optional (space|tab)* spacer after a newline */ + if (lineEnd) { + i = lineEnd + 1; + while (i < end && data[i] == ' ') i++; + } + + /* optional title: any non-newline sequence enclosed in '"() + alone on its line */ + titleOffset = titleEnd = 0; + if (i + 1 < end && (data[i] == '\'' || data[i] == '"' || data[i] == '(')) { + i++; + titleOffset = i; + /* looking for EOL */ + while (i < end && data[i] != '\n' && data[i] != '\r') i++; + if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r') + titleEnd = i + 1; + else titleEnd = i; + /* stepping back */ + i -= 1; + while (i > titleOffset && data[i] == ' ') + i -= 1; + if (i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')')) { + lineEnd = titleEnd; + titleEnd = i; + } + } + + if (!lineEnd || linkEnd == linkOffset) + return 0; /* garbage after the link empty link */ + + var id = data.slice(idOffset, idEnd); + var link = data.slice(linkOffset, linkEnd); + var title = null; + if (titleEnd > titleOffset) title = data.slice(titleOffset, titleEnd); + md.refs[id] = { + id: id, + link: new Buffer(link), + title: new Buffer(title) + }; + return lineEnd; + } + + function expand_tabs(out, line) { + var i = 0, tab = 0; + + while (i < line.length) { + var org = i; + + while (i < line.length && line[i] != '\t') { + i++; tab++; + } + + if (i > org) out.s += line.slice(org, i); + + if (i >= line.length) break; + + do { + out.s += ' '; + tab++; + } while (tab % 4); + + i++; + } + } + + /** + Render markdown code to HTML. + + @param {string} source Markdown code. + @returns {string} HTML code. + */ + function render(source) { + var text = new Buffer(); + var beg = 0, end; + this.refs = {}; + + while (beg < source.length) { /* iterating over lines */ + if (end = is_ref(source, beg, source.length, this)) + beg = end; + else { /* skipping to the next line */ + end = beg; + while (end < source.length && source[end] != '\n' && source[end] != '\r') end++; + + /* adding the line body if present */ + if (end > beg) expand_tabs(text, source.slice(beg, end)); + + while (end < source.length && (source[end] == '\n' || source[end] == '\r')) { + /* add one \n per newline */ + if (source[end] == '\n' || (end + 1 < source.length && source[end + 1] != '\n')) + text.s += '\n'; + end++; + } + + beg = end; + } + } + + var out = new Buffer(); + + /* second pass: actual rendering */ + if (this.callbacks.doc_header) + this.callbacks.doc_header(out, this.context); + + if (text.s.length) { + /* adding a final newline if not already present */ + if (text.s[text.s.length - 1] != '\n' && text.s[text.s.length - 1] != '\r') + text.s += '\n'; + parse_block(out, this, text.s); + } + + if (this.callbacks.doc_footer) + this.callbacks.doc_footer(out, this.context); + + return out.s; + } + Markdown.prototype['render'] = render; + + /** + Create a parser object using the given configuration parameters. + + To get a Reddit equivelent configuration, pass no arguments. + + @param {?Renderer=} renderer A renderer object. + @param {?Number=} extensions A series of OR'd extension flags. (Extension flags start with MKDEXT_) + @param {?Number=} nestingLimit The maximum depth to which inline elements can be nested. + @return {Markdown} A configured markdown object. + */ + exports.getParser = function getParser(renderer, extensions, nestingLimit, columnLimit) { + var md = new Markdown(); + if (renderer) md.callbacks = renderer.callbacks; + if (nestingLimit) md.nestingLimit = nestingLimit; + if (nestingLimit) md.maxTableCols = columnLimit; + if (renderer) md.context = renderer.context; + if (extensions != undefined && extensions != null) md.extensions = extensions; + + // if (!(nestingLimit > 0 && columnLimit > 0 && callbacks)) throw new Error; + var cb = md.callbacks; + if (cb.emphasis || cb.double_emphasis || cb.triple_emphasis) { + md.activeChars['*'] = MD_CHAR_EMPHASIS; + md.activeChars['_'] = MD_CHAR_EMPHASIS; + if (md.extensions & MKDEXT_STRIKETHROUGH) md.activeChars['~'] = MD_CHAR_EMPHASIS; + } + + if (cb.codespan) md.activeChars['`'] = MD_CHAR_CODESPAN; + if (cb.linebreak) md.activeChars['\n'] = MD_CHAR_LINEBREAK; + if (cb.image || cb.link) md.activeChars['['] = MD_CHAR_LINK; + + md.activeChars['<'] = MD_CHAR_LANGLE; + md.activeChars['\\'] = MD_CHAR_ESCAPE; + md.activeChars['&'] = MD_CHAR_ENTITITY; + + if (md.extensions & MKDEXT_AUTOLINK) { + if (!(md.extensions & MKDEXT_NO_EMAIL_AUTOLINK)) { + md.activeChars['@'] = MD_CHAR_AUTOLINK_EMAIL; + } + md.activeChars[':'] = MD_CHAR_AUTOLINK_URL; + md.activeChars['w'] = MD_CHAR_AUTOLINK_WWW; + md.activeChars['/'] = MD_CHAR_AUTOLINK_SUBREDDIT_OR_USERNAME; + } + if (md.extensions & MKDEXT_SUPERSCRIPT) md.activeChars['^'] = MD_CHAR_SUPERSCRIPT; + + return md; + } + + var DEFAULT_BODY_FLAGS = HTML_SKIP_HTML | HTML_SKIP_IMAGES | HTML_SAFELINK | HTML_ESCAPE | HTML_USE_XHTML; + var DEFAULT_WIKI_FLAGS = HTML_SKIP_HTML | HTML_SAFELINK | HTML_ALLOW_ELEMENT_WHITELIST | HTML_ESCAPE | HTML_USE_XHTML; + var DEFAULT_HTML_ATTR_WHITELIST = ['colspan', 'rowspan', 'cellspacing', 'cellpadding', 'scope']; + var DEFAULT_HTML_ELEMENT_WHITELIST = ['tr', 'th', 'td', 'table', 'tbody', 'thead', 'tfoot', 'caption']; + exports.DEFAULT_HTML_ELEMENT_WHITELIST = DEFAULT_HTML_ELEMENT_WHITELIST; + exports.DEFAULT_HTML_ATTR_WHITELIST = DEFAULT_HTML_ATTR_WHITELIST; + exports.DEFAULT_BODY_FLAGS = DEFAULT_BODY_FLAGS; + exports.DEFAULT_WIKI_FLAGS = DEFAULT_WIKI_FLAGS; + + exports.HTML_SKIP_HTML = HTML_SKIP_HTML; + exports.HTML_SKIP_STYLE = HTML_SKIP_STYLE; + exports.HTML_SKIP_IMAGES = HTML_SKIP_IMAGES; + exports.HTML_SKIP_LINKS = HTML_SKIP_LINKS; + exports.HTML_EXPAND_TABS = HTML_EXPAND_TABS; + exports.HTML_SAFELINK = HTML_SAFELINK; + exports.HTML_TOC = HTML_TOC; + exports.HTML_HARD_WRAP = HTML_HARD_WRAP; + exports.HTML_USE_XHTML = HTML_USE_XHTML; + exports.HTML_ESCAPE = HTML_ESCAPE; + exports.HTML_ALLOW_ELEMENT_WHITELIST = HTML_ALLOW_ELEMENT_WHITELIST; + + exports.MKDEXT_NO_INTRA_EMPHASIS = MKDEXT_NO_INTRA_EMPHASIS; + exports.MKDEXT_TABLES = MKDEXT_TABLES; + exports.MKDEXT_FENCED_CODE = MKDEXT_FENCED_CODE; + exports.MKDEXT_AUTOLINK = MKDEXT_AUTOLINK; + exports.MKDEXT_STRIKETHROUGH = MKDEXT_STRIKETHROUGH; + exports.MKDEXT_SPACE_HEADERS = MKDEXT_SPACE_HEADERS; + exports.MKDEXT_SUPERSCRIPT = MKDEXT_SUPERSCRIPT; + exports.MKDEXT_LAX_SPACING = MKDEXT_LAX_SPACING; + exports.MKDEXT_NO_EMAIL_AUTOLINK = MKDEXT_NO_EMAIL_AUTOLINK; + + exports['SD_AUTOLINK_SHORT_DOMAINS'] = SD_AUTOLINK_SHORT_DOMAINS; + + exports.MKDA_NOT_AUTOLINK = MKDA_NOT_AUTOLINK; + exports.MKDA_NORMAL = MKDA_NORMAL; + exports.MKDA_EMAIL = MKDA_EMAIL; + + if (typeof define === 'function') { + define('snuownd', [], exports); + } +})(typeof(exports)!=='undefined'?exports:typeof(window)!=='undefined'?window.SnuOwnd={}:{}); + diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..c5abb3f --- /dev/null +++ b/static/style.css @@ -0,0 +1,225 @@ +body { + background-color: #262626; + margin: 0; + font-family: verdana, arial, helvetica, sans-serif; +} + +p { + margin: 5px 0; + line-height: 20px; +} + +#thread-selftext p:first-child { + margin-top: 0px; +} + +#thread-selftext p:last-child { + margin-bottom: 0px; +} + +a { + cursor: auto; + color: #8cb3d9; +} + +a:link, a:visited, a:hover, a:active { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + +.user-text { + color: #ddd; + font-size: 14px; +} + +.user-link { + color: #6a98af; + +} +/* +.user-link:hover { + text-decoration: underline; +}*/ + + +#header { + background-color: #181818; + padding: 20px 10px 10px; +} + +#header a { + color: #ddd; +} + +#header a:hover { + color: #fff; + text-decoration: none; +} + +#header-title { + margin: 0; + display: inline; +} + +#main { + margin: 15px; +} + +#thread { + display: flex; +} + + +.thumbnail { + background-color: black; + float: left; + width: 70px; + height: 70px; +} + +#thread-score-box { + margin: 0 7px; +} + +.vote { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC0AAAAcCAMAAADC4sagAAABFFBMVEUjJCQiIiIjIyMkIiIkJCQlJSUlJicmJiYnIyEnJycnKCsoKCgpLC8rIyErKyssMDUvLy8wJCAwMDAxMTEyMjI0NDQ2NjY3JR83Nzc5OTk7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NGRkZISEhKSkpKV2tLS0tMTExOTk5PT09QYHdSUlJTU1NVVVVWVlZXV1dXaINYWFhaWlpabYlbW1tecpBgYGBhYWFid5dkZGRle5xlfJ1pgKRrhKhshKlshapuLhZwirFyjbV0j7h4lL97mcZ9MBR/nsyCo9OOMxGcNQ+eNg+kNw6sOA2vOAy0OQy7Ogq/OwrBOwrDPAnLPQjPPQfTPgfbPwXjQQTrQgPzQwLKxGgxAAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEwAACxMBAJqcGAAAAVlJREFUOMu9lF1TwjAQRTcMvmeQOi1QPmxFBQTxW1ERUUFEUVRU/P//w9yGmBTFyZPnpXNmbju76W6IJM8Uw49rLa6Tz4mpq/mCqY3Gvqnj6XQ61lp0mGd8favCwx2tow8wUlpwGWOZrNJKjXO+UVc6fJcMZzV7IswSOU/qej0l0rwyq33wphhAM1kWkcy70GB7mUfUNqG9V02PyPMTMs2Wig5RaTctwzxVXyPqvJh0VkJBIKJ4huncCRAvHOEZyGKegO4a6UDrIdLGCT4C6/QDsE7fA+v0HdAadWqk0SH9D3sgXkm4uJIDYKTFnwwXd3kMrNOnwDp9BmzS5QuTshN1iDRwfkxV9VJTJXLzSWYM+NzEEjWvFE2oGvCE7/2yDeL8riWzM8xmorRazLlNI2rdgNb3ZYLF1Es/t8VE7X6/39Yqlt4tLLwhiLq3XVNLfumP24fo3Opm+wLGCVa252Y8tQAAAABJRU5ErkJggg==); + background-repeat: no-repeat; + margin-left: auto; + margin-right: auto; + width: 15px; + height: 14px; + cursor: pointer; +} + +.upvote { + background-position: -15px 0px; + margin-top: 2px; +} + +.downvote { + background-position: -15px -14px; + margin-top: 18px; +} + +#thread-score { + color: #646464; + float: left; + font-size: 13px; + font-weight: bold; + text-align: center; + +} + +#thread-content { + flex: 1; + float: left; + margin-left: 3px; + max-width: 840px; +} + +.link-flair { + color: #c8c8c8; + background-color: #404040; + margin-right: 5px; + border: 1px solid #4d4d4d; + border-radius: 2px; + padding: 0 2px; + font-size: 10px; +} + +#thread-title { + color: #a6a6a6; + margin-right: 5px; +} + +#thread-title:hover { + text-decoration: none; +} + +#domain { + color: #888; + font-size: 10px; +} + +#thread-info { + color: #828282; + font-size: 10px; + margin-top: 2px; +} + +#thread-selftext { + border: 1px solid #666; + border-radius: 7px; + margin: 5px 0; + padding: 5px 10px; +} + +#comment-info { + color: red; + margin-top: 10px; + padding-bottom: 5px; + border-bottom: 1px dotted #808080; + font-size: 16px; +} + +#comment-sort { + color: #808080; + font-size: 12px; + margin: 5px 0 10px; +} + +.comment { + margin: 0 8px 8px 0; + padding: 5px 8px 5px /*14px*/ 30px; + border: 1px solid #333; + border-radius: 3px; + color: #ddd; + font-size: 14px; +} + +.comment-head { + font-size: 10px; +} + +.comment-author { + font-weight: bold; +} + +.comment-score { + color: #b4b4b4; + font-weight: bold; +} + +.comment-time { + color: #828282; +} + +.comment-body { + line-height: 20px; +} + +.comment-links { + margin-bottom: 6px; +} + +.comment-links a { + color: #828282; + font-weight: bold; + font-size: 10px; +} + +.comment-removed { + background-color: #633636; +} + +.comment-odd { + background-color: #121212; +} + +.comment-even { + background-color: #161616; +} diff --git a/templates/footer.html b/templates/footer.html new file mode 100644 index 0000000..f82581f --- /dev/null +++ b/templates/footer.html @@ -0,0 +1,6 @@ +
    + + + + + diff --git a/templates/frontpage.html b/templates/frontpage.html new file mode 100644 index 0000000..cc1d496 --- /dev/null +++ b/templates/frontpage.html @@ -0,0 +1,7 @@ +{% extends "main.html" %} +{% block body %} + +frontpage + +{% endblock %} + diff --git a/templates/header.html b/templates/header.html new file mode 100644 index 0000000..cba62d6 --- /dev/null +++ b/templates/header.html @@ -0,0 +1,15 @@ + + + + + + + + + + + + +
    diff --git a/templates/main.html b/templates/main.html new file mode 100644 index 0000000..0a1a98b --- /dev/null +++ b/templates/main.html @@ -0,0 +1,4 @@ +{% include "header.html" %} +{% block body%} +{% endblock %} +{% include "footer.html" %} diff --git a/templates/thread.html b/templates/thread.html new file mode 100644 index 0000000..df3b410 --- /dev/null +++ b/templates/thread.html @@ -0,0 +1,16 @@ +{% extends "main.html" %} +{% block body %} +
    + + + + + + +{% endblock %} +