From 00291117d85144fe62c394afa1ebc634298ec7f4 Mon Sep 17 00:00:00 2001
From: Rene Schallner
Date: Sun, 7 May 2023 04:01:05 +0200
Subject: [PATCH] websockets + example
---
README.md | 2 +
build.zig | 1 +
build.zig.zon | 7 +-
examples/websockets/frontend/index.html | 209 +++++++++++++++++++
examples/websockets/frontend/index.js | 69 ++++++
examples/websockets/frontend/showdown.min.js | 3 +
examples/websockets/websockets.zig | 180 ++++++++++++++++
src/fio.zig | 39 ++++
src/websockets.zig | 207 ++++++++++++++++++
src/zap.zig | 70 ++++++-
10 files changed, 775 insertions(+), 12 deletions(-)
create mode 100644 examples/websockets/frontend/index.html
create mode 100644 examples/websockets/frontend/index.js
create mode 100644 examples/websockets/frontend/showdown.min.js
create mode 100644 examples/websockets/websockets.zig
create mode 100644 src/websockets.zig
diff --git a/README.md b/README.md
index 4bf6840..992c9b6 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,8 @@ Here's what works:
itself query parameters of all supported types.
- **[cookies](examples/cookies/cookies.zig)**: a simple example sending
itself a cookie and responding with a session cookie.
+- **[websockets](examples/websockets/websockets.zig)**: a simple websockets chat
+for the browser.
I'll continue wrapping more of facil.io's functionality and adding stuff to zap
diff --git a/build.zig b/build.zig
index 0c7b4db..7ae9697 100644
--- a/build.zig
+++ b/build.zig
@@ -52,6 +52,7 @@ pub fn build(b: *std.build.Builder) !void {
.{ .name = "endpoint_auth", .src = "examples/endpoint_auth/endpoint_auth.zig" },
.{ .name = "http_params", .src = "examples/http_params/http_params.zig" },
.{ .name = "cookies", .src = "examples/cookies/cookies.zig" },
+ .{ .name = "websockets", .src = "examples/websockets/websockets.zig" },
}) |excfg| {
const ex_name = excfg.name;
const ex_src = excfg.src;
diff --git a/build.zig.zon b/build.zig.zon
index 841ee35..66b6566 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -1,12 +1,11 @@
.{
.name = "zap",
- .version = "0.0.15",
+ .version = "0.0.16",
.dependencies = .{
.@"facil.io" = .{
- .url = "https://github.com/zigzap/facil.io/archive/refs/tags/zap-0.0.7.tar.gz",
- .hash = "1220d03e0579bbb726efb8224ea289b26227bc421158b45c1b16a60b31bfa400ab33",
-
+ .url = "https://github.com/zigzap/facil.io/archive/refs/tags/zap-0.0.8.tar.gz",
+ .hash = "122071fcc675e114941331726291ca1f0c0c33751d992782c6abf1f0f2ddddc5734d",
}
}
}
diff --git a/examples/websockets/frontend/index.html b/examples/websockets/frontend/index.html
new file mode 100644
index 0000000..9a15c86
--- /dev/null
+++ b/examples/websockets/frontend/index.html
@@ -0,0 +1,209 @@
+
+
+
+
+
+
+
+ ZAP-Chat
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Send
+
+
+
+
+
+
+
+
diff --git a/examples/websockets/frontend/index.js b/examples/websockets/frontend/index.js
new file mode 100644
index 0000000..4f2130b
--- /dev/null
+++ b/examples/websockets/frontend/index.js
@@ -0,0 +1,69 @@
+// import { show_welcome } from "./screens/welcome_screen.js?version=0";
+
+var chatContainer = document.getElementById("chat-container");
+var promptInput = document.getElementById("prompt-input");
+var sendButton = document.getElementById("send-button");
+
+// var ws;
+
+function addBotMessage(message) {
+ var msg = document.createElement("DIV");
+ msg.classList.add("message");
+ msg.classList.add("bot");
+
+ let converter = new showdown.Converter();
+ msg.innerHTML = converter.makeHtml(message);
+ chatContainer.appendChild(msg);
+ chatContainer.scrollTop = chatContainer.scrollHeight;
+}
+
+function addUserMessage(message) {
+ var msg = document.createElement("DIV");
+ msg.classList.add("message");
+ msg.classList.add("user");
+
+ let converter = new showdown.Converter();
+ msg.innerHTML = converter.makeHtml(message);
+ chatContainer.appendChild(msg);
+ chatContainer.scrollTop = chatContainer.scrollHeight;
+}
+
+function onSend() {
+ let prompt = promptInput.value;
+ console.log(prompt);
+ // addUserMessage(prompt);
+ promptInput.value = "";
+ ws.send(prompt);
+ promptInput.focus();
+}
+
+function show_markdown_body(container, task, body) {
+ let converter = new showdown.Converter();
+ let m = document.createElement("DIV");
+ m.innerHTML = converter.makeHtml(body);
+ m.classList.add("message");
+ container.appendChild(m);
+}
+
+function init() {
+ sendButton.onclick = onSend;
+ promptInput.addEventListener("keypress", function(event) {
+ if(event.key === "Enter") {
+ event.preventDefault();
+ sendButton.click();
+ }
+ });
+ promptInput.focus();
+}
+
+init();
+
+
+var ws = new WebSocket("ws://localhost:3010/chat");
+ws.onmessage = function(e) { addBotMessage(e.data); return false;};
+ws.onclose = function(e) { console.log("closed"); };
+ws.onopen = function(e) { /*e.target.send("Yo!");*/ return false;};
+
+
+
+
diff --git a/examples/websockets/frontend/showdown.min.js b/examples/websockets/frontend/showdown.min.js
new file mode 100644
index 0000000..fddb5a4
--- /dev/null
+++ b/examples/websockets/frontend/showdown.min.js
@@ -0,0 +1,3 @@
+/*! showdown v 2.1.0 - 21-04-2022 */
+!function(){function a(e){"use strict";var r={omitExtraWLInCodeBlocks:{defaultValue:!1,describe:"Omit the default extra whiteline added to code blocks",type:"boolean"},noHeaderId:{defaultValue:!1,describe:"Turn on/off generated header id",type:"boolean"},prefixHeaderId:{defaultValue:!1,describe:"Add a prefix to the generated header ids. Passing a string will prefix that string to the header id. Setting to true will add a generic 'section-' prefix",type:"string"},rawPrefixHeaderId:{defaultValue:!1,describe:'Setting this option to true will prevent showdown from modifying the prefix. This might result in malformed IDs (if, for instance, the " char is used in the prefix)',type:"boolean"},ghCompatibleHeaderId:{defaultValue:!1,describe:"Generate header ids compatible with github style (spaces are replaced with dashes, a bunch of non alphanumeric chars are removed)",type:"boolean"},rawHeaderId:{defaultValue:!1,describe:"Remove only spaces, ' and \" from generated header ids (including prefixes), replacing them with dashes (-). WARNING: This might result in malformed ids",type:"boolean"},headerLevelStart:{defaultValue:!1,describe:"The header blocks level start",type:"integer"},parseImgDimensions:{defaultValue:!1,describe:"Turn on/off image dimension parsing",type:"boolean"},simplifiedAutoLink:{defaultValue:!1,describe:"Turn on/off GFM autolink style",type:"boolean"},excludeTrailingPunctuationFromURLs:{defaultValue:!1,describe:"Excludes trailing punctuation from links generated with autoLinking",type:"boolean"},literalMidWordUnderscores:{defaultValue:!1,describe:"Parse midword underscores as literal underscores",type:"boolean"},literalMidWordAsterisks:{defaultValue:!1,describe:"Parse midword asterisks as literal asterisks",type:"boolean"},strikethrough:{defaultValue:!1,describe:"Turn on/off strikethrough support",type:"boolean"},tables:{defaultValue:!1,describe:"Turn on/off tables support",type:"boolean"},tablesHeaderId:{defaultValue:!1,describe:"Add an id to table headers",type:"boolean"},ghCodeBlocks:{defaultValue:!0,describe:"Turn on/off GFM fenced code blocks support",type:"boolean"},tasklists:{defaultValue:!1,describe:"Turn on/off GFM tasklist support",type:"boolean"},smoothLivePreview:{defaultValue:!1,describe:"Prevents weird effects in live previews due to incomplete input",type:"boolean"},smartIndentationFix:{defaultValue:!1,describe:"Tries to smartly fix indentation in es6 strings",type:"boolean"},disableForced4SpacesIndentedSublists:{defaultValue:!1,describe:"Disables the requirement of indenting nested sublists by 4 spaces",type:"boolean"},simpleLineBreaks:{defaultValue:!1,describe:"Parses simple line breaks as (GFM Style)",type:"boolean"},requireSpaceBeforeHeadingText:{defaultValue:!1,describe:"Makes adding a space between `#` and the header text mandatory (GFM Style)",type:"boolean"},ghMentions:{defaultValue:!1,describe:"Enables github @mentions",type:"boolean"},ghMentionsLink:{defaultValue:"https://github.com/{u}",describe:"Changes the link generated by @mentions. Only applies if ghMentions option is enabled.",type:"string"},encodeEmails:{defaultValue:!0,describe:"Encode e-mail addresses through the use of Character Entities, transforming ASCII e-mail addresses into its equivalent decimal entities",type:"boolean"},openLinksInNewWindow:{defaultValue:!1,describe:"Open all links in new windows",type:"boolean"},backslashEscapesHTMLTags:{defaultValue:!1,describe:"Support for HTML Tag escaping. ex: foo
",type:"boolean"},emoji:{defaultValue:!1,describe:"Enable emoji support. Ex: `this is a :smile: emoji`",type:"boolean"},underline:{defaultValue:!1,describe:"Enable support for underline. Syntax is double or triple underscores: `__underline word__`. With this option enabled, underscores no longer parses into `` and ``",type:"boolean"},ellipsis:{defaultValue:!0,describe:"Replaces three dots with the ellipsis unicode character",type:"boolean"},completeHTMLDocument:{defaultValue:!1,describe:"Outputs a complete html document, including ``, `` and `` tags",type:"boolean"},metadata:{defaultValue:!1,describe:"Enable support for document metadata (defined at the top of the document between `«««` and `»»»` or between `---` and `---`).",type:"boolean"},splitAdjacentBlockquotes:{defaultValue:!1,describe:"Split adjacent blockquote blocks",type:"boolean"}};if(!1===e)return JSON.parse(JSON.stringify(r));var t,a={};for(t in r)r.hasOwnProperty(t)&&(a[t]=r[t].defaultValue);return a}var x={},t={},d={},p=a(!0),h="vanilla",_={github:{omitExtraWLInCodeBlocks:!0,simplifiedAutoLink:!0,excludeTrailingPunctuationFromURLs:!0,literalMidWordUnderscores:!0,strikethrough:!0,tables:!0,tablesHeaderId:!0,ghCodeBlocks:!0,tasklists:!0,disableForced4SpacesIndentedSublists:!0,simpleLineBreaks:!0,requireSpaceBeforeHeadingText:!0,ghCompatibleHeaderId:!0,ghMentions:!0,backslashEscapesHTMLTags:!0,emoji:!0,splitAdjacentBlockquotes:!0},original:{noHeaderId:!0,ghCodeBlocks:!1},ghost:{omitExtraWLInCodeBlocks:!0,parseImgDimensions:!0,simplifiedAutoLink:!0,excludeTrailingPunctuationFromURLs:!0,literalMidWordUnderscores:!0,strikethrough:!0,tables:!0,tablesHeaderId:!0,ghCodeBlocks:!0,tasklists:!0,smoothLivePreview:!0,simpleLineBreaks:!0,requireSpaceBeforeHeadingText:!0,ghMentions:!1,encodeEmails:!0},vanilla:a(!0),allOn:function(){"use strict";var e,r=a(!0),t={};for(e in r)r.hasOwnProperty(e)&&(t[e]=!0);return t}()};function g(e,r){"use strict";var t=r?"Error in "+r+" extension->":"Error in unnamed extension",a={valid:!0,error:""};x.helper.isArray(e)||(e=[e]);for(var n=0;n").replace(/&/g,"&")};function u(e,r,t,a){"use strict";var n,s,o,i=-1<(a=a||"").indexOf("g"),l=new RegExp(r+"|"+t,"g"+a.replace(/g/g,"")),c=new RegExp(r,a.replace(/g/g,"")),u=[];do{for(n=0;p=l.exec(e);)if(c.test(p[0]))n++||(o=(s=l.lastIndex)-p[0].length);else if(n&&!--n){var d=p.index+p[0].length,p={left:{start:o,end:s},match:{start:s,end:p.index},right:{start:p.index,end:d},wholeMatch:{start:o,end:d}};if(u.push(p),!i)return u}}while(n&&(l.lastIndex=s));return u}function s(u){"use strict";return function(e,r,t,a,n,s,o){var i=t=t.replace(x.helper.regexes.asteriskDashAndColon,x.helper.escapeCharactersCallback),l="",c="",r=r||"",o=o||"";return/^www\./i.test(t)&&(t=t.replace(/^www\./i,"http://www.")),u.excludeTrailingPunctuationFromURLs&&s&&(l=s),r+'"+i+" "+l+o}}function o(n,s){"use strict";return function(e,r,t){var a="mailto:";return r=r||"",t=x.subParser("unescapeSpecialChars")(t,n,s),n.encodeEmails?(a=x.helper.encodeEmailAddress(a+t),t=x.helper.encodeEmailAddress(t)):a+=t,r+''+t+" "}}x.helper.matchRecursiveRegExp=function(e,r,t,a){"use strict";for(var n=u(e,r,t,a),s=[],o=0;o>=0,t=String(t||" "),e.length>r?String(e):((r-=e.length)>t.length&&(t+=t.repeat(r/t.length)),String(e)+t.slice(0,r))},"undefined"==typeof console&&(console={warn:function(e){"use strict";alert(e)},log:function(e){"use strict";alert(e)},error:function(e){"use strict";throw e}}),x.helper.regexes={asteriskDashAndColon:/([*_:~])/g},x.helper.emojis={"+1":"👍","-1":"👎",100:"💯",1234:"🔢","1st_place_medal":"🥇","2nd_place_medal":"🥈","3rd_place_medal":"🥉","8ball":"🎱",a:"🅰️",ab:"🆎",abc:"🔤",abcd:"🔡",accept:"🉑",aerial_tramway:"🚡",airplane:"✈️",alarm_clock:"⏰",alembic:"⚗️",alien:"👽",ambulance:"🚑",amphora:"🏺",anchor:"⚓️",angel:"👼",anger:"💢",angry:"😠",anguished:"😧",ant:"🐜",apple:"🍎",aquarius:"♒️",aries:"♈️",arrow_backward:"◀️",arrow_double_down:"⏬",arrow_double_up:"⏫",arrow_down:"⬇️",arrow_down_small:"🔽",arrow_forward:"▶️",arrow_heading_down:"⤵️",arrow_heading_up:"⤴️",arrow_left:"⬅️",arrow_lower_left:"↙️",arrow_lower_right:"↘️",arrow_right:"➡️",arrow_right_hook:"↪️",arrow_up:"⬆️",arrow_up_down:"↕️",arrow_up_small:"🔼",arrow_upper_left:"↖️",arrow_upper_right:"↗️",arrows_clockwise:"🔃",arrows_counterclockwise:"🔄",art:"🎨",articulated_lorry:"🚛",artificial_satellite:"🛰",astonished:"😲",athletic_shoe:"👟",atm:"🏧",atom_symbol:"⚛️",avocado:"🥑",b:"🅱️",baby:"👶",baby_bottle:"🍼",baby_chick:"🐤",baby_symbol:"🚼",back:"🔙",bacon:"🥓",badminton:"🏸",baggage_claim:"🛄",baguette_bread:"🥖",balance_scale:"⚖️",balloon:"🎈",ballot_box:"🗳",ballot_box_with_check:"☑️",bamboo:"🎍",banana:"🍌",bangbang:"‼️",bank:"🏦",bar_chart:"📊",barber:"💈",baseball:"⚾️",basketball:"🏀",basketball_man:"⛹️",basketball_woman:"⛹️♀️",bat:"🦇",bath:"🛀",bathtub:"🛁",battery:"🔋",beach_umbrella:"🏖",bear:"🐻",bed:"🛏",bee:"🐝",beer:"🍺",beers:"🍻",beetle:"🐞",beginner:"🔰",bell:"🔔",bellhop_bell:"🛎",bento:"🍱",biking_man:"🚴",bike:"🚲",biking_woman:"🚴♀️",bikini:"👙",biohazard:"☣️",bird:"🐦",birthday:"🎂",black_circle:"⚫️",black_flag:"🏴",black_heart:"🖤",black_joker:"🃏",black_large_square:"⬛️",black_medium_small_square:"◾️",black_medium_square:"◼️",black_nib:"✒️",black_small_square:"▪️",black_square_button:"🔲",blonde_man:"👱",blonde_woman:"👱♀️",blossom:"🌼",blowfish:"🐡",blue_book:"📘",blue_car:"🚙",blue_heart:"💙",blush:"😊",boar:"🐗",boat:"⛵️",bomb:"💣",book:"📖",bookmark:"🔖",bookmark_tabs:"📑",books:"📚",boom:"💥",boot:"👢",bouquet:"💐",bowing_man:"🙇",bow_and_arrow:"🏹",bowing_woman:"🙇♀️",bowling:"🎳",boxing_glove:"🥊",boy:"👦",bread:"🍞",bride_with_veil:"👰",bridge_at_night:"🌉",briefcase:"💼",broken_heart:"💔",bug:"🐛",building_construction:"🏗",bulb:"💡",bullettrain_front:"🚅",bullettrain_side:"🚄",burrito:"🌯",bus:"🚌",business_suit_levitating:"🕴",busstop:"🚏",bust_in_silhouette:"👤",busts_in_silhouette:"👥",butterfly:"🦋",cactus:"🌵",cake:"🍰",calendar:"📆",call_me_hand:"🤙",calling:"📲",camel:"🐫",camera:"📷",camera_flash:"📸",camping:"🏕",cancer:"♋️",candle:"🕯",candy:"🍬",canoe:"🛶",capital_abcd:"🔠",capricorn:"♑️",car:"🚗",card_file_box:"🗃",card_index:"📇",card_index_dividers:"🗂",carousel_horse:"🎠",carrot:"🥕",cat:"🐱",cat2:"🐈",cd:"💿",chains:"⛓",champagne:"🍾",chart:"💹",chart_with_downwards_trend:"📉",chart_with_upwards_trend:"📈",checkered_flag:"🏁",cheese:"🧀",cherries:"🍒",cherry_blossom:"🌸",chestnut:"🌰",chicken:"🐔",children_crossing:"🚸",chipmunk:"🐿",chocolate_bar:"🍫",christmas_tree:"🎄",church:"⛪️",cinema:"🎦",circus_tent:"🎪",city_sunrise:"🌇",city_sunset:"🌆",cityscape:"🏙",cl:"🆑",clamp:"🗜",clap:"👏",clapper:"🎬",classical_building:"🏛",clinking_glasses:"🥂",clipboard:"📋",clock1:"🕐",clock10:"🕙",clock1030:"🕥",clock11:"🕚",clock1130:"🕦",clock12:"🕛",clock1230:"🕧",clock130:"🕜",clock2:"🕑",clock230:"🕝",clock3:"🕒",clock330:"🕞",clock4:"🕓",clock430:"🕟",clock5:"🕔",clock530:"🕠",clock6:"🕕",clock630:"🕡",clock7:"🕖",clock730:"🕢",clock8:"🕗",clock830:"🕣",clock9:"🕘",clock930:"🕤",closed_book:"📕",closed_lock_with_key:"🔐",closed_umbrella:"🌂",cloud:"☁️",cloud_with_lightning:"🌩",cloud_with_lightning_and_rain:"⛈",cloud_with_rain:"🌧",cloud_with_snow:"🌨",clown_face:"🤡",clubs:"♣️",cocktail:"🍸",coffee:"☕️",coffin:"⚰️",cold_sweat:"😰",comet:"☄️",computer:"💻",computer_mouse:"🖱",confetti_ball:"🎊",confounded:"😖",confused:"😕",congratulations:"㊗️",construction:"🚧",construction_worker_man:"👷",construction_worker_woman:"👷♀️",control_knobs:"🎛",convenience_store:"🏪",cookie:"🍪",cool:"🆒",policeman:"👮",copyright:"©️",corn:"🌽",couch_and_lamp:"🛋",couple:"👫",couple_with_heart_woman_man:"💑",couple_with_heart_man_man:"👨❤️👨",couple_with_heart_woman_woman:"👩❤️👩",couplekiss_man_man:"👨❤️💋👨",couplekiss_man_woman:"💏",couplekiss_woman_woman:"👩❤️💋👩",cow:"🐮",cow2:"🐄",cowboy_hat_face:"🤠",crab:"🦀",crayon:"🖍",credit_card:"💳",crescent_moon:"🌙",cricket:"🏏",crocodile:"🐊",croissant:"🥐",crossed_fingers:"🤞",crossed_flags:"🎌",crossed_swords:"⚔️",crown:"👑",cry:"😢",crying_cat_face:"😿",crystal_ball:"🔮",cucumber:"🥒",cupid:"💘",curly_loop:"➰",currency_exchange:"💱",curry:"🍛",custard:"🍮",customs:"🛃",cyclone:"🌀",dagger:"🗡",dancer:"💃",dancing_women:"👯",dancing_men:"👯♂️",dango:"🍡",dark_sunglasses:"🕶",dart:"🎯",dash:"💨",date:"📅",deciduous_tree:"🌳",deer:"🦌",department_store:"🏬",derelict_house:"🏚",desert:"🏜",desert_island:"🏝",desktop_computer:"🖥",male_detective:"🕵️",diamond_shape_with_a_dot_inside:"💠",diamonds:"♦️",disappointed:"😞",disappointed_relieved:"😥",dizzy:"💫",dizzy_face:"😵",do_not_litter:"🚯",dog:"🐶",dog2:"🐕",dollar:"💵",dolls:"🎎",dolphin:"🐬",door:"🚪",doughnut:"🍩",dove:"🕊",dragon:"🐉",dragon_face:"🐲",dress:"👗",dromedary_camel:"🐪",drooling_face:"🤤",droplet:"💧",drum:"🥁",duck:"🦆",dvd:"📀","e-mail":"📧",eagle:"🦅",ear:"👂",ear_of_rice:"🌾",earth_africa:"🌍",earth_americas:"🌎",earth_asia:"🌏",egg:"🥚",eggplant:"🍆",eight_pointed_black_star:"✴️",eight_spoked_asterisk:"✳️",electric_plug:"🔌",elephant:"🐘",email:"✉️",end:"🔚",envelope_with_arrow:"📩",euro:"💶",european_castle:"🏰",european_post_office:"🏤",evergreen_tree:"🌲",exclamation:"❗️",expressionless:"😑",eye:"👁",eye_speech_bubble:"👁🗨",eyeglasses:"👓",eyes:"👀",face_with_head_bandage:"🤕",face_with_thermometer:"🤒",fist_oncoming:"👊",factory:"🏭",fallen_leaf:"🍂",family_man_woman_boy:"👪",family_man_boy:"👨👦",family_man_boy_boy:"👨👦👦",family_man_girl:"👨👧",family_man_girl_boy:"👨👧👦",family_man_girl_girl:"👨👧👧",family_man_man_boy:"👨👨👦",family_man_man_boy_boy:"👨👨👦👦",family_man_man_girl:"👨👨👧",family_man_man_girl_boy:"👨👨👧👦",family_man_man_girl_girl:"👨👨👧👧",family_man_woman_boy_boy:"👨👩👦👦",family_man_woman_girl:"👨👩👧",family_man_woman_girl_boy:"👨👩👧👦",family_man_woman_girl_girl:"👨👩👧👧",family_woman_boy:"👩👦",family_woman_boy_boy:"👩👦👦",family_woman_girl:"👩👧",family_woman_girl_boy:"👩👧👦",family_woman_girl_girl:"👩👧👧",family_woman_woman_boy:"👩👩👦",family_woman_woman_boy_boy:"👩👩👦👦",family_woman_woman_girl:"👩👩👧",family_woman_woman_girl_boy:"👩👩👧👦",family_woman_woman_girl_girl:"👩👩👧👧",fast_forward:"⏩",fax:"📠",fearful:"😨",feet:"🐾",female_detective:"🕵️♀️",ferris_wheel:"🎡",ferry:"⛴",field_hockey:"🏑",file_cabinet:"🗄",file_folder:"📁",film_projector:"📽",film_strip:"🎞",fire:"🔥",fire_engine:"🚒",fireworks:"🎆",first_quarter_moon:"🌓",first_quarter_moon_with_face:"🌛",fish:"🐟",fish_cake:"🍥",fishing_pole_and_fish:"🎣",fist_raised:"✊",fist_left:"🤛",fist_right:"🤜",flags:"🎏",flashlight:"🔦",fleur_de_lis:"⚜️",flight_arrival:"🛬",flight_departure:"🛫",floppy_disk:"💾",flower_playing_cards:"🎴",flushed:"😳",fog:"🌫",foggy:"🌁",football:"🏈",footprints:"👣",fork_and_knife:"🍴",fountain:"⛲️",fountain_pen:"🖋",four_leaf_clover:"🍀",fox_face:"🦊",framed_picture:"🖼",free:"🆓",fried_egg:"🍳",fried_shrimp:"🍤",fries:"🍟",frog:"🐸",frowning:"😦",frowning_face:"☹️",frowning_man:"🙍♂️",frowning_woman:"🙍",middle_finger:"🖕",fuelpump:"⛽️",full_moon:"🌕",full_moon_with_face:"🌝",funeral_urn:"⚱️",game_die:"🎲",gear:"⚙️",gem:"💎",gemini:"♊️",ghost:"👻",gift:"🎁",gift_heart:"💝",girl:"👧",globe_with_meridians:"🌐",goal_net:"🥅",goat:"🐐",golf:"⛳️",golfing_man:"🏌️",golfing_woman:"🏌️♀️",gorilla:"🦍",grapes:"🍇",green_apple:"🍏",green_book:"📗",green_heart:"💚",green_salad:"🥗",grey_exclamation:"❕",grey_question:"❔",grimacing:"😬",grin:"😁",grinning:"😀",guardsman:"💂",guardswoman:"💂♀️",guitar:"🎸",gun:"🔫",haircut_woman:"💇",haircut_man:"💇♂️",hamburger:"🍔",hammer:"🔨",hammer_and_pick:"⚒",hammer_and_wrench:"🛠",hamster:"🐹",hand:"✋",handbag:"👜",handshake:"🤝",hankey:"💩",hatched_chick:"🐥",hatching_chick:"🐣",headphones:"🎧",hear_no_evil:"🙉",heart:"❤️",heart_decoration:"💟",heart_eyes:"😍",heart_eyes_cat:"😻",heartbeat:"💓",heartpulse:"💗",hearts:"♥️",heavy_check_mark:"✔️",heavy_division_sign:"➗",heavy_dollar_sign:"💲",heavy_heart_exclamation:"❣️",heavy_minus_sign:"➖",heavy_multiplication_x:"✖️",heavy_plus_sign:"➕",helicopter:"🚁",herb:"🌿",hibiscus:"🌺",high_brightness:"🔆",high_heel:"👠",hocho:"🔪",hole:"🕳",honey_pot:"🍯",horse:"🐴",horse_racing:"🏇",hospital:"🏥",hot_pepper:"🌶",hotdog:"🌭",hotel:"🏨",hotsprings:"♨️",hourglass:"⌛️",hourglass_flowing_sand:"⏳",house:"🏠",house_with_garden:"🏡",houses:"🏘",hugs:"🤗",hushed:"😯",ice_cream:"🍨",ice_hockey:"🏒",ice_skate:"⛸",icecream:"🍦",id:"🆔",ideograph_advantage:"🉐",imp:"👿",inbox_tray:"📥",incoming_envelope:"📨",tipping_hand_woman:"💁",information_source:"ℹ️",innocent:"😇",interrobang:"⁉️",iphone:"📱",izakaya_lantern:"🏮",jack_o_lantern:"🎃",japan:"🗾",japanese_castle:"🏯",japanese_goblin:"👺",japanese_ogre:"👹",jeans:"👖",joy:"😂",joy_cat:"😹",joystick:"🕹",kaaba:"🕋",key:"🔑",keyboard:"⌨️",keycap_ten:"🔟",kick_scooter:"🛴",kimono:"👘",kiss:"💋",kissing:"😗",kissing_cat:"😽",kissing_closed_eyes:"😚",kissing_heart:"😘",kissing_smiling_eyes:"😙",kiwi_fruit:"🥝",koala:"🐨",koko:"🈁",label:"🏷",large_blue_circle:"🔵",large_blue_diamond:"🔷",large_orange_diamond:"🔶",last_quarter_moon:"🌗",last_quarter_moon_with_face:"🌜",latin_cross:"✝️",laughing:"😆",leaves:"🍃",ledger:"📒",left_luggage:"🛅",left_right_arrow:"↔️",leftwards_arrow_with_hook:"↩️",lemon:"🍋",leo:"♌️",leopard:"🐆",level_slider:"🎚",libra:"♎️",light_rail:"🚈",link:"🔗",lion:"🦁",lips:"👄",lipstick:"💄",lizard:"🦎",lock:"🔒",lock_with_ink_pen:"🔏",lollipop:"🍭",loop:"➿",loud_sound:"🔊",loudspeaker:"📢",love_hotel:"🏩",love_letter:"💌",low_brightness:"🔅",lying_face:"🤥",m:"Ⓜ️",mag:"🔍",mag_right:"🔎",mahjong:"🀄️",mailbox:"📫",mailbox_closed:"📪",mailbox_with_mail:"📬",mailbox_with_no_mail:"📭",man:"👨",man_artist:"👨🎨",man_astronaut:"👨🚀",man_cartwheeling:"🤸♂️",man_cook:"👨🍳",man_dancing:"🕺",man_facepalming:"🤦♂️",man_factory_worker:"👨🏭",man_farmer:"👨🌾",man_firefighter:"👨🚒",man_health_worker:"👨⚕️",man_in_tuxedo:"🤵",man_judge:"👨⚖️",man_juggling:"🤹♂️",man_mechanic:"👨🔧",man_office_worker:"👨💼",man_pilot:"👨✈️",man_playing_handball:"🤾♂️",man_playing_water_polo:"🤽♂️",man_scientist:"👨🔬",man_shrugging:"🤷♂️",man_singer:"👨🎤",man_student:"👨🎓",man_teacher:"👨🏫",man_technologist:"👨💻",man_with_gua_pi_mao:"👲",man_with_turban:"👳",tangerine:"🍊",mans_shoe:"👞",mantelpiece_clock:"🕰",maple_leaf:"🍁",martial_arts_uniform:"🥋",mask:"😷",massage_woman:"💆",massage_man:"💆♂️",meat_on_bone:"🍖",medal_military:"🎖",medal_sports:"🏅",mega:"📣",melon:"🍈",memo:"📝",men_wrestling:"🤼♂️",menorah:"🕎",mens:"🚹",metal:"🤘",metro:"🚇",microphone:"🎤",microscope:"🔬",milk_glass:"🥛",milky_way:"🌌",minibus:"🚐",minidisc:"💽",mobile_phone_off:"📴",money_mouth_face:"🤑",money_with_wings:"💸",moneybag:"💰",monkey:"🐒",monkey_face:"🐵",monorail:"🚝",moon:"🌔",mortar_board:"🎓",mosque:"🕌",motor_boat:"🛥",motor_scooter:"🛵",motorcycle:"🏍",motorway:"🛣",mount_fuji:"🗻",mountain:"⛰",mountain_biking_man:"🚵",mountain_biking_woman:"🚵♀️",mountain_cableway:"🚠",mountain_railway:"🚞",mountain_snow:"🏔",mouse:"🐭",mouse2:"🐁",movie_camera:"🎥",moyai:"🗿",mrs_claus:"🤶",muscle:"💪",mushroom:"🍄",musical_keyboard:"🎹",musical_note:"🎵",musical_score:"🎼",mute:"🔇",nail_care:"💅",name_badge:"📛",national_park:"🏞",nauseated_face:"🤢",necktie:"👔",negative_squared_cross_mark:"❎",nerd_face:"🤓",neutral_face:"😐",new:"🆕",new_moon:"🌑",new_moon_with_face:"🌚",newspaper:"📰",newspaper_roll:"🗞",next_track_button:"⏭",ng:"🆖",no_good_man:"🙅♂️",no_good_woman:"🙅",night_with_stars:"🌃",no_bell:"🔕",no_bicycles:"🚳",no_entry:"⛔️",no_entry_sign:"🚫",no_mobile_phones:"📵",no_mouth:"😶",no_pedestrians:"🚷",no_smoking:"🚭","non-potable_water":"🚱",nose:"👃",notebook:"📓",notebook_with_decorative_cover:"📔",notes:"🎶",nut_and_bolt:"🔩",o:"⭕️",o2:"🅾️",ocean:"🌊",octopus:"🐙",oden:"🍢",office:"🏢",oil_drum:"🛢",ok:"🆗",ok_hand:"👌",ok_man:"🙆♂️",ok_woman:"🙆",old_key:"🗝",older_man:"👴",older_woman:"👵",om:"🕉",on:"🔛",oncoming_automobile:"🚘",oncoming_bus:"🚍",oncoming_police_car:"🚔",oncoming_taxi:"🚖",open_file_folder:"📂",open_hands:"👐",open_mouth:"😮",open_umbrella:"☂️",ophiuchus:"⛎",orange_book:"📙",orthodox_cross:"☦️",outbox_tray:"📤",owl:"🦉",ox:"🐂",package:"📦",page_facing_up:"📄",page_with_curl:"📃",pager:"📟",paintbrush:"🖌",palm_tree:"🌴",pancakes:"🥞",panda_face:"🐼",paperclip:"📎",paperclips:"🖇",parasol_on_ground:"⛱",parking:"🅿️",part_alternation_mark:"〽️",partly_sunny:"⛅️",passenger_ship:"🛳",passport_control:"🛂",pause_button:"⏸",peace_symbol:"☮️",peach:"🍑",peanuts:"🥜",pear:"🍐",pen:"🖊",pencil2:"✏️",penguin:"🐧",pensive:"😔",performing_arts:"🎭",persevere:"😣",person_fencing:"🤺",pouting_woman:"🙎",phone:"☎️",pick:"⛏",pig:"🐷",pig2:"🐖",pig_nose:"🐽",pill:"💊",pineapple:"🍍",ping_pong:"🏓",pisces:"♓️",pizza:"🍕",place_of_worship:"🛐",plate_with_cutlery:"🍽",play_or_pause_button:"⏯",point_down:"👇",point_left:"👈",point_right:"👉",point_up:"☝️",point_up_2:"👆",police_car:"🚓",policewoman:"👮♀️",poodle:"🐩",popcorn:"🍿",post_office:"🏣",postal_horn:"📯",postbox:"📮",potable_water:"🚰",potato:"🥔",pouch:"👝",poultry_leg:"🍗",pound:"💷",rage:"😡",pouting_cat:"😾",pouting_man:"🙎♂️",pray:"🙏",prayer_beads:"📿",pregnant_woman:"🤰",previous_track_button:"⏮",prince:"🤴",princess:"👸",printer:"🖨",purple_heart:"💜",purse:"👛",pushpin:"📌",put_litter_in_its_place:"🚮",question:"❓",rabbit:"🐰",rabbit2:"🐇",racehorse:"🐎",racing_car:"🏎",radio:"📻",radio_button:"🔘",radioactive:"☢️",railway_car:"🚃",railway_track:"🛤",rainbow:"🌈",rainbow_flag:"🏳️🌈",raised_back_of_hand:"🤚",raised_hand_with_fingers_splayed:"🖐",raised_hands:"🙌",raising_hand_woman:"🙋",raising_hand_man:"🙋♂️",ram:"🐏",ramen:"🍜",rat:"🐀",record_button:"⏺",recycle:"♻️",red_circle:"🔴",registered:"®️",relaxed:"☺️",relieved:"😌",reminder_ribbon:"🎗",repeat:"🔁",repeat_one:"🔂",rescue_worker_helmet:"⛑",restroom:"🚻",revolving_hearts:"💞",rewind:"⏪",rhinoceros:"🦏",ribbon:"🎀",rice:"🍚",rice_ball:"🍙",rice_cracker:"🍘",rice_scene:"🎑",right_anger_bubble:"🗯",ring:"💍",robot:"🤖",rocket:"🚀",rofl:"🤣",roll_eyes:"🙄",roller_coaster:"🎢",rooster:"🐓",rose:"🌹",rosette:"🏵",rotating_light:"🚨",round_pushpin:"📍",rowing_man:"🚣",rowing_woman:"🚣♀️",rugby_football:"🏉",running_man:"🏃",running_shirt_with_sash:"🎽",running_woman:"🏃♀️",sa:"🈂️",sagittarius:"♐️",sake:"🍶",sandal:"👡",santa:"🎅",satellite:"📡",saxophone:"🎷",school:"🏫",school_satchel:"🎒",scissors:"✂️",scorpion:"🦂",scorpius:"♏️",scream:"😱",scream_cat:"🙀",scroll:"📜",seat:"💺",secret:"㊙️",see_no_evil:"🙈",seedling:"🌱",selfie:"🤳",shallow_pan_of_food:"🥘",shamrock:"☘️",shark:"🦈",shaved_ice:"🍧",sheep:"🐑",shell:"🐚",shield:"🛡",shinto_shrine:"⛩",ship:"🚢",shirt:"👕",shopping:"🛍",shopping_cart:"🛒",shower:"🚿",shrimp:"🦐",signal_strength:"📶",six_pointed_star:"🔯",ski:"🎿",skier:"⛷",skull:"💀",skull_and_crossbones:"☠️",sleeping:"😴",sleeping_bed:"🛌",sleepy:"😪",slightly_frowning_face:"🙁",slightly_smiling_face:"🙂",slot_machine:"🎰",small_airplane:"🛩",small_blue_diamond:"🔹",small_orange_diamond:"🔸",small_red_triangle:"🔺",small_red_triangle_down:"🔻",smile:"😄",smile_cat:"😸",smiley:"😃",smiley_cat:"😺",smiling_imp:"😈",smirk:"😏",smirk_cat:"😼",smoking:"🚬",snail:"🐌",snake:"🐍",sneezing_face:"🤧",snowboarder:"🏂",snowflake:"❄️",snowman:"⛄️",snowman_with_snow:"☃️",sob:"😭",soccer:"⚽️",soon:"🔜",sos:"🆘",sound:"🔉",space_invader:"👾",spades:"♠️",spaghetti:"🍝",sparkle:"❇️",sparkler:"🎇",sparkles:"✨",sparkling_heart:"💖",speak_no_evil:"🙊",speaker:"🔈",speaking_head:"🗣",speech_balloon:"💬",speedboat:"🚤",spider:"🕷",spider_web:"🕸",spiral_calendar:"🗓",spiral_notepad:"🗒",spoon:"🥄",squid:"🦑",stadium:"🏟",star:"⭐️",star2:"🌟",star_and_crescent:"☪️",star_of_david:"✡️",stars:"🌠",station:"🚉",statue_of_liberty:"🗽",steam_locomotive:"🚂",stew:"🍲",stop_button:"⏹",stop_sign:"🛑",stopwatch:"⏱",straight_ruler:"📏",strawberry:"🍓",stuck_out_tongue:"😛",stuck_out_tongue_closed_eyes:"😝",stuck_out_tongue_winking_eye:"😜",studio_microphone:"🎙",stuffed_flatbread:"🥙",sun_behind_large_cloud:"🌥",sun_behind_rain_cloud:"🌦",sun_behind_small_cloud:"🌤",sun_with_face:"🌞",sunflower:"🌻",sunglasses:"😎",sunny:"☀️",sunrise:"🌅",sunrise_over_mountains:"🌄",surfing_man:"🏄",surfing_woman:"🏄♀️",sushi:"🍣",suspension_railway:"🚟",sweat:"😓",sweat_drops:"💦",sweat_smile:"😅",sweet_potato:"🍠",swimming_man:"🏊",swimming_woman:"🏊♀️",symbols:"🔣",synagogue:"🕍",syringe:"💉",taco:"🌮",tada:"🎉",tanabata_tree:"🎋",taurus:"♉️",taxi:"🚕",tea:"🍵",telephone_receiver:"📞",telescope:"🔭",tennis:"🎾",tent:"⛺️",thermometer:"🌡",thinking:"🤔",thought_balloon:"💭",ticket:"🎫",tickets:"🎟",tiger:"🐯",tiger2:"🐅",timer_clock:"⏲",tipping_hand_man:"💁♂️",tired_face:"😫",tm:"™️",toilet:"🚽",tokyo_tower:"🗼",tomato:"🍅",tongue:"👅",top:"🔝",tophat:"🎩",tornado:"🌪",trackball:"🖲",tractor:"🚜",traffic_light:"🚥",train:"🚋",train2:"🚆",tram:"🚊",triangular_flag_on_post:"🚩",triangular_ruler:"📐",trident:"🔱",triumph:"😤",trolleybus:"🚎",trophy:"🏆",tropical_drink:"🍹",tropical_fish:"🐠",truck:"🚚",trumpet:"🎺",tulip:"🌷",tumbler_glass:"🥃",turkey:"🦃",turtle:"🐢",tv:"📺",twisted_rightwards_arrows:"🔀",two_hearts:"💕",two_men_holding_hands:"👬",two_women_holding_hands:"👭",u5272:"🈹",u5408:"🈴",u55b6:"🈺",u6307:"🈯️",u6708:"🈷️",u6709:"🈶",u6e80:"🈵",u7121:"🈚️",u7533:"🈸",u7981:"🈲",u7a7a:"🈳",umbrella:"☔️",unamused:"😒",underage:"🔞",unicorn:"🦄",unlock:"🔓",up:"🆙",upside_down_face:"🙃",v:"✌️",vertical_traffic_light:"🚦",vhs:"📼",vibration_mode:"📳",video_camera:"📹",video_game:"🎮",violin:"🎻",virgo:"♍️",volcano:"🌋",volleyball:"🏐",vs:"🆚",vulcan_salute:"🖖",walking_man:"🚶",walking_woman:"🚶♀️",waning_crescent_moon:"🌘",waning_gibbous_moon:"🌖",warning:"⚠️",wastebasket:"🗑",watch:"⌚️",water_buffalo:"🐃",watermelon:"🍉",wave:"👋",wavy_dash:"〰️",waxing_crescent_moon:"🌒",wc:"🚾",weary:"😩",wedding:"💒",weight_lifting_man:"🏋️",weight_lifting_woman:"🏋️♀️",whale:"🐳",whale2:"🐋",wheel_of_dharma:"☸️",wheelchair:"♿️",white_check_mark:"✅",white_circle:"⚪️",white_flag:"🏳️",white_flower:"💮",white_large_square:"⬜️",white_medium_small_square:"◽️",white_medium_square:"◻️",white_small_square:"▫️",white_square_button:"🔳",wilted_flower:"🥀",wind_chime:"🎐",wind_face:"🌬",wine_glass:"🍷",wink:"😉",wolf:"🐺",woman:"👩",woman_artist:"👩🎨",woman_astronaut:"👩🚀",woman_cartwheeling:"🤸♀️",woman_cook:"👩🍳",woman_facepalming:"🤦♀️",woman_factory_worker:"👩🏭",woman_farmer:"👩🌾",woman_firefighter:"👩🚒",woman_health_worker:"👩⚕️",woman_judge:"👩⚖️",woman_juggling:"🤹♀️",woman_mechanic:"👩🔧",woman_office_worker:"👩💼",woman_pilot:"👩✈️",woman_playing_handball:"🤾♀️",woman_playing_water_polo:"🤽♀️",woman_scientist:"👩🔬",woman_shrugging:"🤷♀️",woman_singer:"👩🎤",woman_student:"👩🎓",woman_teacher:"👩🏫",woman_technologist:"👩💻",woman_with_turban:"👳♀️",womans_clothes:"👚",womans_hat:"👒",women_wrestling:"🤼♀️",womens:"🚺",world_map:"🗺",worried:"😟",wrench:"🔧",writing_hand:"✍️",x:"❌",yellow_heart:"💛",yen:"💴",yin_yang:"☯️",yum:"😋",zap:"⚡️",zipper_mouth_face:"🤐",zzz:"💤",octocat:' ',showdown:"S "},x.Converter=function(e){"use strict";var r,t,n={},i=[],l=[],o={},a=h,s={parsed:{},raw:"",format:""};for(r in e=e||{},p)p.hasOwnProperty(r)&&(n[r]=p[r]);if("object"!=typeof e)throw Error("Converter expects the passed parameter to be an object, but "+typeof e+" was passed instead.");for(t in e)e.hasOwnProperty(t)&&(n[t]=e[t]);function c(e,r){if(r=r||null,x.helper.isString(e)){if(r=e=x.helper.stdExtName(e),x.extensions[e]){console.warn("DEPRECATION WARNING: "+e+" is an old extension that uses a deprecated loading method.Please inform the developer that the extension should be updated!");var t=x.extensions[e],a=e;if("function"==typeof t&&(t=t(new x.Converter)),x.helper.isArray(t)||(t=[t]),!(a=g(t,a)).valid)throw Error(a.error);for(var n=0;n [ \t]+,">¨NBSP;<"),!r){if(!window||!window.document)throw new Error("HTMLParser is undefined. If in a webworker or nodejs environment, you need to provide a WHATWG DOM and HTML such as JSDOM");r=window.document}for(var r=r.createElement("div"),t=(r.innerHTML=e,{preList:function(e){for(var r=e.querySelectorAll("pre"),t=[],a=0;a'}else t.push(r[a].innerHTML),r[a].innerHTML="",r[a].setAttribute("prenum",a.toString());return t}(r)}),a=(!function e(r){for(var t=0;t? ?(['"].*['"])?\)$/m))a="";else if(!a){if(a="#"+(t=t||r.toLowerCase().replace(/ ?\n/g," ")),x.helper.isUndefined(l.gUrls[t]))return e;a=l.gUrls[t],x.helper.isUndefined(l.gTitles[t])||(o=l.gTitles[t])}return e='"+r+" "}return e=(e=(e=(e=(e=l.converter._dispatch("anchors.before",e,i,l)).replace(/\[((?:\[[^\]]*]|[^\[\]])*)] ?(?:\n *)?\[(.*?)]()()()()/g,r)).replace(/\[((?:\[[^\]]*]|[^\[\]])*)]()[ \t]*\([ \t]?<([^>]*)>(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g,r)).replace(/\[((?:\[[^\]]*]|[^\[\]])*)]()[ \t]*\([ \t]?([\S]+?(?:\([\S]*?\)[\S]*?)?)>?(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g,r)).replace(/\[([^\[\]]+)]()()()()()/g,r),i.ghMentions&&(e=e.replace(/(^|\s)(\\)?(@([a-z\d]+(?:[a-z\d.-]+?[a-z\d]+)*))/gim,function(e,r,t,a,n){if("\\"===t)return r+a;if(!x.helper.isString(i.ghMentionsLink))throw new Error("ghMentionsLink option must be a string");t="";return r+'"+a+" "})),e=l.converter._dispatch("anchors.after",e,i,l)});var i=/([*~_]+|\b)(((https?|ftp|dict):\/\/|www\.)[^'">\s]+?\.[^'">\s]+?)()(\1)?(?=\s|$)(?!["<>])/gi,l=/([*~_]+|\b)(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+?)([.!?,()\[\]])?(\1)?(?=\s|$)(?!["<>])/gi,c=/()<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)()>()/gi,m=/(^|\s)(?:mailto:)?([A-Za-z0-9!#$%&'*+-/=?^_`{|}~.]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?=$|\s)/gim,f=/<()(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi;x.subParser("autoLinks",function(e,r,t){"use strict";return e=(e=(e=t.converter._dispatch("autoLinks.before",e,r,t)).replace(c,s(r))).replace(f,o(r,t)),e=t.converter._dispatch("autoLinks.after",e,r,t)}),x.subParser("simplifiedAutoLinks",function(e,r,t){"use strict";return r.simplifiedAutoLink?(e=t.converter._dispatch("simplifiedAutoLinks.before",e,r,t),e=(e=r.excludeTrailingPunctuationFromURLs?e.replace(l,s(r)):e.replace(i,s(r))).replace(m,o(r,t)),t.converter._dispatch("simplifiedAutoLinks.after",e,r,t)):e}),x.subParser("blockGamut",function(e,r,t){"use strict";return e=t.converter._dispatch("blockGamut.before",e,r,t),e=x.subParser("blockQuotes")(e,r,t),e=x.subParser("headers")(e,r,t),e=x.subParser("horizontalRule")(e,r,t),e=x.subParser("lists")(e,r,t),e=x.subParser("codeBlocks")(e,r,t),e=x.subParser("tables")(e,r,t),e=x.subParser("hashHTMLBlocks")(e,r,t),e=x.subParser("paragraphs")(e,r,t),e=t.converter._dispatch("blockGamut.after",e,r,t)}),x.subParser("blockQuotes",function(e,r,t){"use strict";e=t.converter._dispatch("blockQuotes.before",e,r,t);var a=/(^ {0,3}>[ \t]?.+\n(.+\n)*\n*)+/gm;return r.splitAdjacentBlockquotes&&(a=/^ {0,3}>[\s\S]*?(?:\n\n)/gm),e=(e+="\n\n").replace(a,function(e){return e=(e=(e=e.replace(/^[ \t]*>[ \t]?/gm,"")).replace(/¨0/g,"")).replace(/^[ \t]+$/gm,""),e=x.subParser("githubCodeBlocks")(e,r,t),e=(e=(e=x.subParser("blockGamut")(e,r,t)).replace(/(^|\n)/g,"$1 ")).replace(/(\s*[^\r]+?<\/pre>)/gm,function(e,r){return r.replace(/^ /gm,"¨0").replace(/¨0/g,"")}),x.subParser("hashBlock")("\n"+e+"\n ",r,t)}),e=t.converter._dispatch("blockQuotes.after",e,r,t)}),x.subParser("codeBlocks",function(e,n,s){"use strict";e=s.converter._dispatch("codeBlocks.before",e,n,s);return e=(e=(e+="¨0").replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=¨0))/g,function(e,r,t){var a="\n",r=x.subParser("outdent")(r,n,s);return r=x.subParser("encodeCode")(r,n,s),r=""+(r=(r=(r=x.subParser("detab")(r,n,s)).replace(/^\n+/g,"")).replace(/\n+$/g,""))+(a=n.omitExtraWLInCodeBlocks?"":a)+"
",x.subParser("hashBlock")(r,n,s)+t})).replace(/¨0/,""),e=s.converter._dispatch("codeBlocks.after",e,n,s)}),x.subParser("codeSpans",function(e,n,s){"use strict";return e=(e=void 0===(e=s.converter._dispatch("codeSpans.before",e,n,s))?"":e).replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(e,r,t,a){return a=(a=a.replace(/^([ \t]*)/g,"")).replace(/[ \t]*$/g,""),a=r+""+(a=x.subParser("encodeCode")(a,n,s))+"
",a=x.subParser("hashHTMLSpans")(a,n,s)}),e=s.converter._dispatch("codeSpans.after",e,n,s)}),x.subParser("completeHTMLDocument",function(e,r,t){"use strict";if(!r.completeHTMLDocument)return e;e=t.converter._dispatch("completeHTMLDocument.before",e,r,t);var a,n="html",s="\n",o="",i=' \n',l="",c="";for(a in void 0!==t.metadata.parsed.doctype&&(s="\n","html"!==(n=t.metadata.parsed.doctype.toString().toLowerCase())&&"html5"!==n||(i=' ')),t.metadata.parsed)if(t.metadata.parsed.hasOwnProperty(a))switch(a.toLowerCase()){case"doctype":break;case"title":o=""+t.metadata.parsed.title+" \n";break;case"charset":i="html"===n||"html5"===n?' \n':' \n';break;case"language":case"lang":l=' lang="'+t.metadata.parsed[a]+'"',c+=' \n';break;default:c+=' \n'}return e=s+"\n\n"+o+i+c+"\n\n"+e.trim()+"\n\n",e=t.converter._dispatch("completeHTMLDocument.after",e,r,t)}),x.subParser("detab",function(e,r,t){"use strict";return e=(e=(e=(e=(e=(e=t.converter._dispatch("detab.before",e,r,t)).replace(/\t(?=\t)/g," ")).replace(/\t/g,"¨A¨B")).replace(/¨B(.+?)¨A/g,function(e,r){for(var t=r,a=4-t.length%4,n=0;n/g,">"),e=t.converter._dispatch("encodeAmpsAndAngles.after",e,r,t)}),x.subParser("encodeBackslashEscapes",function(e,r,t){"use strict";return e=(e=(e=t.converter._dispatch("encodeBackslashEscapes.before",e,r,t)).replace(/\\(\\)/g,x.helper.escapeCharactersCallback)).replace(/\\([`*_{}\[\]()>#+.!~=|:-])/g,x.helper.escapeCharactersCallback),e=t.converter._dispatch("encodeBackslashEscapes.after",e,r,t)}),x.subParser("encodeCode",function(e,r,t){"use strict";return e=(e=t.converter._dispatch("encodeCode.before",e,r,t)).replace(/&/g,"&").replace(//g,">").replace(/([*_{}\[\]\\=~-])/g,x.helper.escapeCharactersCallback),e=t.converter._dispatch("encodeCode.after",e,r,t)}),x.subParser("escapeSpecialCharsWithinTagAttributes",function(e,r,t){"use strict";return e=(e=(e=t.converter._dispatch("escapeSpecialCharsWithinTagAttributes.before",e,r,t)).replace(/<\/?[a-z\d_:-]+(?:[\s]+[\s\S]+?)?>/gi,function(e){return e.replace(/(.)<\/?code>(?=.)/g,"$1`").replace(/([\\`*_~=|])/g,x.helper.escapeCharactersCallback)})).replace(/-]|-[^>])(?:[^-]|-[^-])*)--)>/gi,function(e){return e.replace(/([\\`*_~=|])/g,x.helper.escapeCharactersCallback)}),e=t.converter._dispatch("escapeSpecialCharsWithinTagAttributes.after",e,r,t)}),x.subParser("githubCodeBlocks",function(e,s,o){"use strict";return s.ghCodeBlocks?(e=o.converter._dispatch("githubCodeBlocks.before",e,s,o),e=(e=(e+="¨0").replace(/(?:^|\n)(?: {0,3})(```+|~~~+)(?: *)([^\s`~]*)\n([\s\S]*?)\n(?: {0,3})\1/g,function(e,r,t,a){var n=s.omitExtraWLInCodeBlocks?"":"\n";return a=x.subParser("encodeCode")(a,s,o),a=""+(a=(a=(a=x.subParser("detab")(a,s,o)).replace(/^\n+/g,"")).replace(/\n+$/g,""))+n+"
",a=x.subParser("hashBlock")(a,s,o),"\n\n¨G"+(o.ghCodeBlocks.push({text:e,codeblock:a})-1)+"G\n\n"})).replace(/¨0/,""),o.converter._dispatch("githubCodeBlocks.after",e,s,o)):e}),x.subParser("hashBlock",function(e,r,t){"use strict";return e=(e=t.converter._dispatch("hashBlock.before",e,r,t)).replace(/(^\n+|\n+$)/g,""),e="\n\n¨K"+(t.gHtmlBlocks.push(e)-1)+"K\n\n",e=t.converter._dispatch("hashBlock.after",e,r,t)}),x.subParser("hashCodeTags",function(e,n,s){"use strict";e=s.converter._dispatch("hashCodeTags.before",e,n,s);return e=x.helper.replaceRecursiveRegExp(e,function(e,r,t,a){t=t+x.subParser("encodeCode")(r,n,s)+a;return"¨C"+(s.gHtmlSpans.push(t)-1)+"C"},"]*>","
","gim"),e=s.converter._dispatch("hashCodeTags.after",e,n,s)}),x.subParser("hashElement",function(e,r,t){"use strict";return function(e,r){return r=(r=(r=r.replace(/\n\n/g,"\n")).replace(/^\n/,"")).replace(/\n+$/g,""),r="\n\n¨K"+(t.gHtmlBlocks.push(r)-1)+"K\n\n"}}),x.subParser("hashHTMLBlocks",function(e,r,n){"use strict";e=n.converter._dispatch("hashHTMLBlocks.before",e,r,n);function t(e,r,t,a){return-1!==t.search(/\bmarkdown\b/)&&(e=t+n.converter.makeHtml(r)+a),"\n\n¨K"+(n.gHtmlBlocks.push(e)-1)+"K\n\n"}var a=["pre","div","h1","h2","h3","h4","h5","h6","blockquote","table","dl","ol","ul","script","noscript","form","fieldset","iframe","math","style","section","header","footer","nav","article","aside","address","audio","canvas","figure","hgroup","output","video","p"];r.backslashEscapesHTMLTags&&(e=e.replace(/\\<(\/?[^>]+?)>/g,function(e,r){return"<"+r+">"}));for(var s=0;s ]*>)","im"),i="<"+a[s]+"\\b[^>]*>",l=""+a[s]+">";-1!==(c=x.helper.regexIndexOf(e,o));){var c=x.helper.splitAtIndex(e,c),u=x.helper.replaceRecursiveRegExp(c[1],t,i,l,"im");if(u===c[1])break;e=c[0].concat(u)}return e=e.replace(/(\n {0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,x.subParser("hashElement")(e,r,n)),e=(e=x.helper.replaceRecursiveRegExp(e,function(e){return"\n\n¨K"+(n.gHtmlBlocks.push(e)-1)+"K\n\n"},"^ {0,3}\x3c!--","--\x3e","gm")).replace(/(?:\n\n)( {0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,x.subParser("hashElement")(e,r,n)),e=n.converter._dispatch("hashHTMLBlocks.after",e,r,n)}),x.subParser("hashHTMLSpans",function(e,r,t){"use strict";function a(e){return"¨C"+(t.gHtmlSpans.push(e)-1)+"C"}return e=(e=(e=(e=(e=t.converter._dispatch("hashHTMLSpans.before",e,r,t)).replace(/<[^>]+?\/>/gi,a)).replace(/<([^>]+?)>[\s\S]*?<\/\1>/g,a)).replace(/<([^>]+?)\s[^>]+?>[\s\S]*?<\/\1>/g,a)).replace(/<[^>]+?>/gi,a),e=t.converter._dispatch("hashHTMLSpans.after",e,r,t)}),x.subParser("unhashHTMLSpans",function(e,r,t){"use strict";e=t.converter._dispatch("unhashHTMLSpans.before",e,r,t);for(var a=0;a]*>\\s*]*>","^ {0,3}
\\s* ","gim"),e=s.converter._dispatch("hashPreCodeTags.after",e,n,s)}),x.subParser("headers",function(e,n,s){"use strict";e=s.converter._dispatch("headers.before",e,n,s);var o=isNaN(parseInt(n.headerLevelStart))?1:parseInt(n.headerLevelStart),r=n.smoothLivePreview?/^(.+)[ \t]*\n={2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n=+[ \t]*\n+/gm,t=n.smoothLivePreview?/^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n-+[ \t]*\n+/gm,r=(e=(e=e.replace(r,function(e,r){var t=x.subParser("spanGamut")(r,n,s),r=n.noHeaderId?"":' id="'+i(r)+'"',r=""+t+" ";return x.subParser("hashBlock")(r,n,s)})).replace(t,function(e,r){var t=x.subParser("spanGamut")(r,n,s),r=n.noHeaderId?"":' id="'+i(r)+'"',a=o+1,r=""+t+" ";return x.subParser("hashBlock")(r,n,s)}),n.requireSpaceBeforeHeadingText?/^(#{1,6})[ \t]+(.+?)[ \t]*#*\n+/gm:/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm);function i(e){var r=e=n.customizedHeaderId&&(r=e.match(/\{([^{]+?)}\s*$/))&&r[1]?r[1]:e,e=x.helper.isString(n.prefixHeaderId)?n.prefixHeaderId:!0===n.prefixHeaderId?"section-":"";return n.rawPrefixHeaderId||(r=e+r),r=(n.ghCompatibleHeaderId?r.replace(/ /g,"-").replace(/&/g,"").replace(/¨T/g,"").replace(/¨D/g,"").replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g,""):n.rawHeaderId?r.replace(/ /g,"-").replace(/&/g,"&").replace(/¨T/g,"¨").replace(/¨D/g,"$").replace(/["']/g,"-"):r.replace(/[^\w]/g,"")).toLowerCase(),n.rawPrefixHeaderId&&(r=e+r),s.hashLinkCounts[r]?r=r+"-"+s.hashLinkCounts[r]++:s.hashLinkCounts[r]=1,r}return e=e.replace(r,function(e,r,t){var a=t,a=(n.customizedHeaderId&&(a=t.replace(/\s?\{([^{]+?)}\s*$/,"")),x.subParser("spanGamut")(a,n,s)),t=n.noHeaderId?"":' id="'+i(t)+'"',r=o-1+r.length,t=""+a+" ";return x.subParser("hashBlock")(t,n,s)}),e=s.converter._dispatch("headers.after",e,n,s)}),x.subParser("horizontalRule",function(e,r,t){"use strict";e=t.converter._dispatch("horizontalRule.before",e,r,t);var a=x.subParser("hashBlock")(" ",r,t);return e=(e=(e=e.replace(/^ {0,2}( ?-){3,}[ \t]*$/gm,a)).replace(/^ {0,2}( ?\*){3,}[ \t]*$/gm,a)).replace(/^ {0,2}( ?_){3,}[ \t]*$/gm,a),e=t.converter._dispatch("horizontalRule.after",e,r,t)}),x.subParser("images",function(e,r,d){"use strict";function l(e,r,t,a,n,s,o,i){var l=d.gUrls,c=d.gTitles,u=d.gDimensions;if(t=t.toLowerCase(),i=i||"",-1? ?(['"].*['"])?\)$/m))a="";else if(""===a||null===a){if(a="#"+(t=""!==t&&null!==t?t:r.toLowerCase().replace(/ ?\n/g," ")),x.helper.isUndefined(l[t]))return e;a=l[t],x.helper.isUndefined(c[t])||(i=c[t]),x.helper.isUndefined(u[t])||(n=u[t].width,s=u[t].height)}r=r.replace(/"/g,""").replace(x.helper.regexes.asteriskDashAndColon,x.helper.escapeCharactersCallback);e=' "}return e=(e=(e=(e=(e=(e=d.converter._dispatch("images.before",e,r,d)).replace(/!\[([^\]]*?)] ?(?:\n *)?\[([\s\S]*?)]()()()()()/g,l)).replace(/!\[([^\]]*?)][ \t]*()\([ \t]?(data:.+?\/.+?;base64,[A-Za-z0-9+/=\n]+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,function(e,r,t,a,n,s,o,i){return l(e,r,t,a=a.replace(/\s/g,""),n,s,0,i)})).replace(/!\[([^\]]*?)][ \t]*()\([ \t]?<([^>]*)>(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(?:(["'])([^"]*?)\6))?[ \t]?\)/g,l)).replace(/!\[([^\]]*?)][ \t]*()\([ \t]?([\S]+?(?:\([\S]*?\)[\S]*?)?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,l)).replace(/!\[([^\[\]]+)]()()()()()/g,l),e=d.converter._dispatch("images.after",e,r,d)}),x.subParser("italicsAndBold",function(e,r,t){"use strict";return e=t.converter._dispatch("italicsAndBold.before",e,r,t),e=r.literalMidWordUnderscores?(e=(e=e.replace(/\b___(\S[\s\S]*?)___\b/g,function(e,r){return""+r+" "})).replace(/\b__(\S[\s\S]*?)__\b/g,function(e,r){return""+r+" "})).replace(/\b_(\S[\s\S]*?)_\b/g,function(e,r){return""+r+" "}):(e=(e=e.replace(/___(\S[\s\S]*?)___/g,function(e,r){return/\S$/.test(r)?""+r+" ":e})).replace(/__(\S[\s\S]*?)__/g,function(e,r){return/\S$/.test(r)?""+r+" ":e})).replace(/_([^\s_][\s\S]*?)_/g,function(e,r){return/\S$/.test(r)?""+r+" ":e}),e=r.literalMidWordAsterisks?(e=(e=e.replace(/([^*]|^)\B\*\*\*(\S[\s\S]*?)\*\*\*\B(?!\*)/g,function(e,r,t){return r+""+t+" "})).replace(/([^*]|^)\B\*\*(\S[\s\S]*?)\*\*\B(?!\*)/g,function(e,r,t){return r+""+t+" "})).replace(/([^*]|^)\B\*(\S[\s\S]*?)\*\B(?!\*)/g,function(e,r,t){return r+""+t+" "}):(e=(e=e.replace(/\*\*\*(\S[\s\S]*?)\*\*\*/g,function(e,r){return/\S$/.test(r)?""+r+" ":e})).replace(/\*\*(\S[\s\S]*?)\*\*/g,function(e,r){return/\S$/.test(r)?""+r+" ":e})).replace(/\*([^\s*][\s\S]*?)\*/g,function(e,r){return/\S$/.test(r)?""+r+" ":e}),e=t.converter._dispatch("italicsAndBold.after",e,r,t)}),x.subParser("lists",function(e,d,c){"use strict";function p(e,r){c.gListLevel++,e=e.replace(/\n{2,}$/,"\n");var t=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0| {0,3}([*+-]|\d+[.])[ \t]+))/gm,l=/\n[ \t]*\n(?!¨0)/.test(e+="¨0");return d.disableForced4SpacesIndentedSublists&&(t=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0|\2([*+-]|\d+[.])[ \t]+))/gm),e=(e=e.replace(t,function(e,r,t,a,n,s,o){o=o&&""!==o.trim();var n=x.subParser("outdent")(n,d,c),i="";return s&&d.tasklists&&(i=' class="task-list-item" style="list-style-type: none;"',n=n.replace(/^[ \t]*\[(x|X| )?]/m,function(){var e=' "})),n=n.replace(/^([-*+]|\d\.)[ \t]+[\S\n ]*/g,function(e){return"¨A"+e}),n=""+(n=(n=r||-1\n"})).replace(/¨0/g,""),c.gListLevel--,e=r?e.replace(/\s+$/,""):e}function h(e,r){if("ol"===r){r=e.match(/^ *(\d+)\./);if(r&&"1"!==r[1])return' start="'+r[1]+'"'}return""}function n(n,s,o){var e,i=d.disableForced4SpacesIndentedSublists?/^ ?\d+\.[ \t]/gm:/^ {0,3}\d+\.[ \t]/gm,l=d.disableForced4SpacesIndentedSublists?/^ ?[*+-][ \t]/gm:/^ {0,3}[*+-][ \t]/gm,c="ul"===s?i:l,u="";return-1!==n.search(c)?function e(r){var t=r.search(c),a=h(n,s);-1!==t?(u+="\n\n<"+s+a+">\n"+p(r.slice(0,t),!!o)+""+s+">\n",c="ul"===(s="ul"===s?"ol":"ul")?i:l,e(r.slice(t))):u+="\n\n<"+s+a+">\n"+p(r,!!o)+""+s+">\n"}(n):(e=h(n,s),u="\n\n<"+s+e+">\n"+p(n,!!o)+""+s+">\n"),u}return e=c.converter._dispatch("lists.before",e,d,c),e+="¨0",e=(e=c.gListLevel?e.replace(/^(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,function(e,r,t){return n(r,-1"),i+="
",n.push(i))}for(s=n.length,o=0;o]*>\s*]*>/.test(c)&&(u=!0)}n[o]=c}return e=(e=(e=n.join("\n")).replace(/^\n+/g,"")).replace(/\n+$/g,""),t.converter._dispatch("paragraphs.after",e,r,t)}),x.subParser("runExtension",function(e,r,t,a){"use strict";return e.filter?r=e.filter(r,a.converter,t):e.regex&&((a=e.regex)instanceof RegExp||(a=new RegExp(a,"g")),r=r.replace(a,e.replace)),r}),x.subParser("spanGamut",function(e,r,t){"use strict";return e=t.converter._dispatch("spanGamut.before",e,r,t),e=x.subParser("codeSpans")(e,r,t),e=x.subParser("escapeSpecialCharsWithinTagAttributes")(e,r,t),e=x.subParser("encodeBackslashEscapes")(e,r,t),e=x.subParser("images")(e,r,t),e=x.subParser("anchors")(e,r,t),e=x.subParser("autoLinks")(e,r,t),e=x.subParser("simplifiedAutoLinks")(e,r,t),e=x.subParser("emoji")(e,r,t),e=x.subParser("underline")(e,r,t),e=x.subParser("italicsAndBold")(e,r,t),e=x.subParser("strikethrough")(e,r,t),e=x.subParser("ellipsis")(e,r,t),e=x.subParser("hashHTMLSpans")(e,r,t),e=x.subParser("encodeAmpsAndAngles")(e,r,t),r.simpleLineBreaks?/\n\n¨K/.test(e)||(e=e.replace(/\n+/g," \n")):e=e.replace(/ +\n/g," \n"),e=t.converter._dispatch("spanGamut.after",e,r,t)}),x.subParser("strikethrough",function(e,t,a){"use strict";return t.strikethrough&&(e=(e=a.converter._dispatch("strikethrough.before",e,t,a)).replace(/(?:~){2}([\s\S]+?)(?:~){2}/g,function(e,r){return r=r,""+(r=t.simplifiedAutoLink?x.subParser("simplifiedAutoLinks")(r,t,a):r)+""}),e=a.converter._dispatch("strikethrough.after",e,t,a)),e}),x.subParser("stripLinkDefinitions",function(i,l,c){"use strict";function e(e,r,t,a,n,s,o){return r=r.toLowerCase(),i.toLowerCase().split(r).length-1<2?e:(t.match(/^data:.+?\/.+?;base64,/)?c.gUrls[r]=t.replace(/\s/g,""):c.gUrls[r]=x.subParser("encodeAmpsAndAngles")(t,l,c),s?s+o:(o&&(c.gTitles[r]=o.replace(/"|'/g,""")),l.parseImgDimensions&&a&&n&&(c.gDimensions[r]={width:a,height:n}),""))}return i=(i=(i=(i+="¨0").replace(/^ {0,3}\[([^\]]+)]:[ \t]*\n?[ \t]*(data:.+?\/.+?;base64,[A-Za-z0-9+/=\n]+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n\n|(?=¨0)|(?=\n\[))/gm,e)).replace(/^ {0,3}\[([^\]]+)]:[ \t]*\n?[ \t]*([^>\s]+)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=¨0))/gm,e)).replace(/¨0/,"")}),x.subParser("tables",function(e,y,P){"use strict";if(!y.tables)return e;function r(e){for(var r=e.split("\n"),t=0;t"+(n=x.subParser("spanGamut")(n,y,P))+"\n"));for(t=0;t"+x.subParser("spanGamut")(i,y,P)+"\n"));h.push(_)}for(var m=d,f=h,b="\n\n\n",w=m.length,k=0;k\n \n\n",k=0;k\n";for(var v=0;v\n"}return b+=" \n
\n"}return e=(e=(e=(e=P.converter._dispatch("tables.before",e,y,P)).replace(/\\(\|)/g,x.helper.escapeCharactersCallback)).replace(/^ {0,3}\|?.+\|.+\n {0,3}\|?[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:[-=]){2,}[\s\S]+?(?:\n\n|¨0)/gm,r)).replace(/^ {0,3}\|.+\|[ \t]*\n {0,3}\|[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*\n( {0,3}\|.+\|[ \t]*\n)*(?:\n|¨0)/gm,r),e=P.converter._dispatch("tables.after",e,y,P)}),x.subParser("underline",function(e,r,t){"use strict";return r.underline?(e=t.converter._dispatch("underline.before",e,r,t),e=(e=r.literalMidWordUnderscores?(e=e.replace(/\b___(\S[\s\S]*?)___\b/g,function(e,r){return""+r+" "})).replace(/\b__(\S[\s\S]*?)__\b/g,function(e,r){return""+r+" "}):(e=e.replace(/___(\S[\s\S]*?)___/g,function(e,r){return/\S$/.test(r)?""+r+" ":e})).replace(/__(\S[\s\S]*?)__/g,function(e,r){return/\S$/.test(r)?""+r+" ":e})).replace(/(_)/g,x.helper.escapeCharactersCallback),t.converter._dispatch("underline.after",e,r,t)):e}),x.subParser("unescapeSpecialChars",function(e,r,t){"use strict";return e=(e=t.converter._dispatch("unescapeSpecialChars.before",e,r,t)).replace(/¨E(\d+)E/g,function(e,r){r=parseInt(r);return String.fromCharCode(r)}),e=t.converter._dispatch("unescapeSpecialChars.after",e,r,t)}),x.subParser("makeMarkdown.blockquote",function(e,r){"use strict";var t="";if(e.hasChildNodes())for(var a=e.childNodes,n=a.length,s=0;s ")}),x.subParser("makeMarkdown.codeBlock",function(e,r){"use strict";var t=e.getAttribute("language"),e=e.getAttribute("precodenum");return"```"+t+"\n"+r.preList[e]+"\n```"}),x.subParser("makeMarkdown.codeSpan",function(e){"use strict";return"`"+e.innerHTML+"`"}),x.subParser("makeMarkdown.emphasis",function(e,r){"use strict";var t="";if(e.hasChildNodes()){t+="*";for(var a=e.childNodes,n=a.length,s=0;s",e.hasAttribute("width")&&e.hasAttribute("height")&&(r+=" ="+e.getAttribute("width")+"x"+e.getAttribute("height")),e.hasAttribute("title")&&(r+=' "'+e.getAttribute("title")+'"'),r+=")"),r}),x.subParser("makeMarkdown.links",function(e,r){"use strict";var t="";if(e.hasChildNodes()&&e.hasAttribute("href")){for(var a=e.childNodes,n=a.length,t="[",s=0;s"),e.hasAttribute("title")&&(t+=' "'+e.getAttribute("title")+'"'),t+=")"}return t}),x.subParser("makeMarkdown.list",function(e,r,t){"use strict";var a="";if(!e.hasChildNodes())return"";for(var n=e.childNodes,s=n.length,o=e.getAttribute("start")||1,i=0;i"+r.preList[e]+""}),x.subParser("makeMarkdown.strikethrough",function(e,r){"use strict";var t="";if(e.hasChildNodes()){t+="~~";for(var a=e.childNodes,n=a.length,s=0;str>th"),s=e.querySelectorAll("tbody>tr"),o=0;o/g,"\\$1>")).replace(/^#/gm,"\\#")).replace(/^(\s*)([-=]{3,})(\s*)$/,"$1\\$2$3")).replace(/^( {0,3}\d+)\./gm,"$1\\.")).replace(/^( {0,3})([+-])/gm,"$1\\$2")).replace(/]([\s]*)\(/g,"\\]$1\\(")).replace(/^ {0,3}\[([\S \t]*?)]:/gm,"\\[$1]:")});"function"==typeof define&&define.amd?define(function(){"use strict";return x}):"undefined"!=typeof module&&module.exports?module.exports=x:this.showdown=x}.call(this);
+//# sourceMappingURL=showdown.min.js.map
diff --git a/examples/websockets/websockets.zig b/examples/websockets/websockets.zig
new file mode 100644
index 0000000..0e5f66b
--- /dev/null
+++ b/examples/websockets/websockets.zig
@@ -0,0 +1,180 @@
+const std = @import("std");
+const zap = @import("zap");
+const WebSockets = zap.WebSockets;
+
+const Context = struct {
+ userName: []const u8,
+ channel: []const u8,
+ // we need to hold on to them and just re-use them for every incoming connection
+ subscribeArgs: WebsocketHandler.SubscribeArgs,
+ settings: WebsocketHandler.WebSocketSettings,
+};
+
+const ContextList = std.ArrayList(*Context);
+
+const ContextManager = struct {
+ allocator: std.mem.Allocator,
+ channel: []const u8,
+ usernamePrefix: []const u8,
+ lock: std.Thread.Mutex = .{},
+ contexts: ContextList = undefined,
+
+ const Self = @This();
+
+ pub fn init(allocator: std.mem.Allocator, channelName: []const u8, usernamePrefix: []const u8) Self {
+ return .{
+ .allocator = allocator,
+ .channel = channelName,
+ .usernamePrefix = usernamePrefix,
+ .contexts = ContextList.init(allocator),
+ };
+ }
+
+ pub fn deinit(self: *Self) void {
+ for (self.contexts.items) |ctx| {
+ self.allocator.free(ctx.userName);
+ }
+ self.contexts.deinit();
+ }
+
+ pub fn newContext(self: *Self) !*Context {
+ self.lock.lock();
+ defer self.lock.unlock();
+
+ var ctx = try self.allocator.create(Context);
+ var userName = try std.fmt.allocPrint(self.allocator, "{s}{d}", .{ self.usernamePrefix, self.contexts.items.len });
+ ctx.* = .{
+ .userName = userName,
+ .channel = self.channel,
+ // used in subscribe()
+ .subscribeArgs = .{
+ .channel = self.channel,
+ .force_text = true,
+ .context = ctx,
+ },
+ // used in upgrade()
+ .settings = .{
+ .on_open = on_open_websocket,
+ .on_close = on_close_websocket,
+ .on_message = handle_websocket_message,
+ .context = ctx,
+ },
+ };
+ try self.contexts.append(ctx);
+ return ctx;
+ }
+};
+
+//
+// Websocket Callbacks
+//
+fn on_open_websocket(context: ?*Context, handle: WebSockets.WsHandle) void {
+ if (context) |ctx| {
+ _ = WebsocketHandler.subscribe(handle, &ctx.subscribeArgs) catch |err| {
+ std.log.err("Error opening websocket: {any}", .{err});
+ return;
+ };
+
+ // say hello
+ var buf: [128]u8 = undefined;
+ const message = std.fmt.bufPrint(&buf, "{s} joined the chat.", .{ctx.userName}) catch unreachable;
+
+ // send notification to all others
+ WebsocketHandler.publish(.{ .channel = ctx.channel, .message = message });
+ std.log.info("new websocket opened: {s}", .{message});
+ }
+}
+
+fn on_close_websocket(context: ?*Context, uuid: isize) void {
+ _ = uuid;
+ if (context) |ctx| {
+ // say goodbye
+ var buf: [128]u8 = undefined;
+ const message = std.fmt.bufPrint(&buf, "{s} left the chat.", .{ctx.userName}) catch unreachable;
+
+ // send notification to all others
+ WebsocketHandler.publish(.{ .channel = ctx.channel, .message = message });
+ std.log.info("websocket closed: {s}", .{message});
+ }
+}
+fn handle_websocket_message(context: ?*Context, handle: WebSockets.WsHandle, message: []const u8, is_text: bool) void {
+ _ = is_text;
+ _ = handle;
+ if (context) |ctx| {
+ // say goodbye
+ var buf: [128]u8 = undefined;
+ const chat_message = std.fmt.bufPrint(&buf, "{s}: {s}", .{ ctx.userName, message }) catch unreachable;
+
+ // send notification to all others
+ WebsocketHandler.publish(.{ .channel = ctx.channel, .message = chat_message });
+ std.log.info("{s}", .{chat_message});
+ }
+}
+
+//
+// HTTP stuff
+//
+fn on_request(r: zap.SimpleRequest) void {
+ r.setHeader("Server", "zap.example") catch unreachable;
+ r.sendBody("This is a simple Websocket chatroom example ") catch return;
+}
+
+fn on_upgrade(r: zap.SimpleRequest, target_protocol: []const u8) void {
+ // make sure we're talking the right protocol
+ if (!std.mem.eql(u8, target_protocol, "websocket")) {
+ std.log.warn("received illegal target protocol: {s}", .{target_protocol});
+ r.setStatus(.bad_request);
+ r.sendBody("400 - BAD REQUEST") catch unreachable;
+ return;
+ }
+ var context = GlobalContextManager.newContext() catch |err| {
+ std.log.err("Error creating context: {any}", .{err});
+ return;
+ };
+
+ WebsocketHandler.upgrade(r.h, &context.settings) catch |err| {
+ std.log.err("Error in websocketUpgrade(): {any}", .{err});
+ return;
+ };
+ std.log.info("connection upgrade OK", .{});
+}
+
+// global variables, yeah!
+var GlobalContextManager: ContextManager = undefined;
+
+const WebsocketHandler = WebSockets.Handler(Context);
+var handler_instance: WebsocketHandler = .{};
+
+// here we go
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{
+ .thread_safe = true,
+ }){};
+ var allocator = gpa.allocator();
+
+ GlobalContextManager = ContextManager.init(allocator, "chatroom", "user-");
+ defer GlobalContextManager.deinit();
+
+ // setup listener
+ var listener = zap.SimpleHttpListener.init(
+ .{
+ .port = 3010,
+ .on_request = on_request,
+ .on_upgrade = on_upgrade,
+ .max_clients = 1000,
+ .max_body_size = 1 * 1024,
+ .public_folder = "examples/websockets/frontend",
+ .log = true,
+ },
+ );
+ try listener.listen();
+ std.log.info("", .{});
+ std.log.info("Connect with browser to http://localhost:3010.", .{});
+ std.log.info("Connect to websocket on ws://localhost:3010.", .{});
+ std.log.info("Terminate with CTRL+C", .{});
+
+ zap.start(.{
+ .threads = 1,
+ .workers = 1,
+ });
+}
diff --git a/src/fio.zig b/src/fio.zig
index 6346c85..22abfa3 100644
--- a/src/fio.zig
+++ b/src/fio.zig
@@ -362,6 +362,32 @@ pub const websocket_settings_s = extern struct {
on_close: ?*const fn (isize, ?*anyopaque) callconv(.C) void,
udata: ?*anyopaque,
};
+
+// struct websocket_subscribe_s_zigcompat {
+// ws_s *ws;
+// fio_str_info_s channel;
+// void (*on_message)(ws_s *ws, fio_str_info_s channel, fio_str_info_s msg, void *udata);
+// void (*on_unsubscribe)(void *udata);
+// void *udata;
+// fio_match_fn match;
+// unsigned char force_binary;
+// unsigned char force_text;
+// };
+
+pub const websocket_subscribe_s_zigcompat = extern struct {
+ ws: ?*ws_s,
+ channel: fio_str_info_s,
+ on_message: ?*const fn (?*ws_s, fio_str_info_s, fio_str_info_s, ?*anyopaque) callconv(.C) void,
+ on_unsubscribe: ?*const fn (?*anyopaque) callconv(.C) void,
+ udata: ?*anyopaque,
+ match: fio_match_fn,
+ force_binary: u8,
+ force_text: u8,
+};
+
+/// 0 on failure
+pub extern fn websocket_subscribe_zigcompat(websocket_subscribe_s_zigcompat) callconv(.C) usize;
+
pub extern fn http_upgrade2ws(http: [*c]http_s, websocket_settings_s) c_int;
pub extern fn websocket_connect(url: [*c]const u8, settings: websocket_settings_s) c_int;
pub extern fn websocket_attach(uuid: isize, http_settings: [*c]http_settings_s, args: [*c]websocket_settings_s, data: ?*anyopaque, length: usize) void;
@@ -375,6 +401,19 @@ pub const struct_websocket_subscribe_s = opaque {};
pub extern fn websocket_subscribe(args: struct_websocket_subscribe_s) usize;
pub extern fn websocket_unsubscribe(ws: ?*ws_s, subscription_id: usize) void;
pub extern fn websocket_optimize4broadcasts(@"type": isize, enable: c_int) void;
+
+pub extern fn fio_publish(args: fio_publish_args_s) void;
+pub const fio_publish_args_s = struct_fio_publish_args_s;
+pub const struct_fio_publish_args_s = extern struct {
+ engine: ?*anyopaque = null,
+ // we don't support engines other than default
+ // engine: [*c]const fio_pubsub_engine_s,
+ filter: i32 = 0,
+ channel: fio_str_info_s,
+ message: fio_str_info_s,
+ is_json: u8,
+};
+
pub const http_sse_s = struct_http_sse_s;
pub const struct_http_sse_s = extern struct {
on_open: ?*const fn ([*c]http_sse_s) callconv(.C) void,
diff --git a/src/websockets.zig b/src/websockets.zig
new file mode 100644
index 0000000..515132d
--- /dev/null
+++ b/src/websockets.zig
@@ -0,0 +1,207 @@
+const std = @import("std");
+const zap = @import("zap.zig");
+const fio = @import("fio.zig");
+const util = @import("util.zig");
+
+pub const WsHandle = ?*fio.ws_s;
+pub fn Handler(comptime ContextType: type) type {
+ return struct {
+ /// OnMessage Callback on a websocket
+ pub const WsOnMessageFn = *const fn (
+ /// user-provided context, passed in from websocketHttpUpgrade()
+ context: ?*ContextType,
+ /// websocket handle, used to identify the websocket internally
+ handle: WsHandle,
+ /// the received message
+ message: []const u8,
+ /// indicator if message is text or binary
+ is_text: bool,
+ ) void;
+
+ /// Callback when websocket is closed. uuid is a connection identifier,
+ /// it is -1 if a connection could not be established
+ pub const WsOnCloseFn = *const fn (context: ?*ContextType, uuid: isize) void;
+
+ /// A websocket callback function. provides the context passed in at
+ /// websocketHttpUpgrade().
+ pub const WsFn = *const fn (context: ?*ContextType, handle: WsHandle) void;
+
+ pub const WebSocketSettings = struct {
+ /// on_message(context, handle, message, is_text)
+ on_message: ?WsOnMessageFn = null,
+ /// on_open(context)
+ on_open: ?WsFn = null,
+ /// on_ready(context)
+ on_ready: ?WsFn = null,
+ /// on_shutdown(context, uuid)
+ on_shutdown: ?WsFn = null,
+ /// on_close(context)
+ on_close: ?WsOnCloseFn = null,
+ /// passed-in user-defined context
+ context: ?*ContextType = null,
+ };
+
+ /// This function will end the HTTP stage of the connection and attempt to "upgrade" to a WebSockets connection.
+ pub fn upgrade(h: [*c]fio.http_s, settings: *WebSocketSettings) WebSocketError!void {
+ var fio_settings: fio.websocket_settings_s = .{
+ .on_message = internal_on_message,
+ .on_open = internal_on_open,
+ .on_ready = internal_on_ready,
+ .on_shutdown = internal_on_shutdown,
+ .on_close = internal_on_close,
+ .udata = settings,
+ };
+ if (fio.http_upgrade2ws(h, fio_settings) != 0) {
+ return error.UpgradeError;
+ }
+ }
+
+ fn internal_on_message(handle: WsHandle, msg: fio.fio_str_info_s, is_text: u8) callconv(.C) void {
+ var user_provided_settings: ?*WebSocketSettings = @ptrCast(?*WebSocketSettings, @alignCast(@alignOf(?*WebSocketSettings), fio.websocket_udata_get(handle)));
+ var message = msg.data[0..msg.len];
+ if (user_provided_settings) |settings| {
+ if (settings.on_message) |on_message| {
+ on_message(settings.context, handle, message, is_text == 1);
+ }
+ }
+ }
+
+ fn internal_on_open(handle: WsHandle) callconv(.C) void {
+ var user_provided_settings: ?*WebSocketSettings = @ptrCast(?*WebSocketSettings, @alignCast(@alignOf(?*WebSocketSettings), fio.websocket_udata_get(handle)));
+ if (user_provided_settings) |settings| {
+ if (settings.on_open) |on_open| {
+ on_open(settings.context, handle);
+ }
+ }
+ }
+
+ fn internal_on_ready(handle: WsHandle) callconv(.C) void {
+ var user_provided_settings: ?*WebSocketSettings = @ptrCast(?*WebSocketSettings, @alignCast(@alignOf(?*WebSocketSettings), fio.websocket_udata_get(handle)));
+ if (user_provided_settings) |settings| {
+ if (settings.on_ready) |on_ready| {
+ on_ready(settings.context, handle);
+ }
+ }
+ }
+
+ fn internal_on_shutdown(handle: WsHandle) callconv(.C) void {
+ var user_provided_settings: ?*WebSocketSettings = @ptrCast(?*WebSocketSettings, @alignCast(@alignOf(?*WebSocketSettings), fio.websocket_udata_get(handle)));
+ if (user_provided_settings) |settings| {
+ if (settings.on_shutdown) |on_shutdown| {
+ on_shutdown(settings.context, handle);
+ }
+ }
+ }
+
+ fn internal_on_close(uuid: isize, udata: ?*anyopaque) callconv(.C) void {
+ var user_provided_settings: ?*WebSocketSettings = @ptrCast(?*WebSocketSettings, @alignCast(@alignOf(?*WebSocketSettings), udata));
+ if (user_provided_settings) |settings| {
+ if (settings.on_close) |on_close| {
+ on_close(settings.context, uuid);
+ }
+ }
+ }
+
+ const WebSocketError = error{
+ WriteError,
+ UpgradeError,
+ SubscribeError,
+ };
+
+ pub inline fn write(handle: WsHandle, message: []const u8, is_text: bool) WebSocketError!void {
+ if (fio.websocket_write(
+ handle,
+ fio.str2fio(message),
+ if (is_text) 1 else 0,
+ ) != 0) {
+ return error.WriteError;
+ }
+ }
+
+ pub fn udataToContext(udata: *anyopaque) *ContextType {
+ return @ptrCast(*ContextType, @alignCast(@alignOf(*ContextType), udata));
+ }
+
+ pub inline fn close(handle: WsHandle) void {
+ fio.websocket_close(handle);
+ }
+
+ const PublishArgs = struct {
+ channel: []const u8,
+ message: []const u8,
+ is_json: bool = false,
+ };
+
+ /// publish a message in a channel
+ pub inline fn publish(args: PublishArgs) void {
+ fio.fio_publish(.{
+ .channel = util.str2fio(args.channel),
+ .message = util.str2fio(args.message),
+ .is_json = if (args.is_json) 1 else 0,
+ });
+ }
+
+ pub const SubscriptionOnMessageFn = *const fn (context: ?*ContextType, handle: WsHandle, channel: []const u8, message: []const u8) void;
+ pub const SubscriptionOnUnsubscribeFn = *const fn (context: ?*ContextType) void;
+
+ pub const SubscribeArgs = struct {
+ channel: []const u8,
+ on_message: ?SubscriptionOnMessageFn = null,
+ on_unsubscribe: ?SubscriptionOnUnsubscribeFn = null,
+ /// this is not wrapped nicely yet
+ match: fio.fio_match_fn = null,
+ /// When using direct message forwarding (no on_message callback), this indicates if
+ /// messages should be sent to the client as binary blobs, which is the safest approach.
+ /// By default, facil.io will test for UTF-8 data validity and send the data as text if
+ /// it's a valid UTF-8. Messages above ~32Kb might be assumed to be binary rather than
+ /// tested.
+ force_binary: bool = false,
+ /// When using direct message forwarding (no on_message callback), this indicates if
+ /// messages should be sent to the client as UTF-8 text. By default, facil.io will test
+ /// for UTF-8 data validity and send the data as text if it's a valid UTF-8. Messages
+ /// above ~32Kb might be assumed to be binary rather than tested. force_binary has
+ /// precedence over force_text.
+ force_text: bool = false,
+ context: ?*ContextType = null,
+ };
+
+ /// Returns a subscription ID on success and 0 on failure.
+ /// we copy the pointer so make sure the struct stays valid.
+ /// we need it to look up the ziggified callbacks.
+ pub inline fn subscribe(handle: WsHandle, args: *SubscribeArgs) WebSocketError!usize {
+ if (handle == null) return error.SubscribeError;
+ var fio_args: fio.websocket_subscribe_s_zigcompat = .{
+ .ws = handle.?,
+ .channel = util.str2fio(args.channel),
+ .on_message = if (args.on_message) |_| internal_subscription_on_message else null,
+ .on_unsubscribe = if (args.on_unsubscribe) |_| internal_subscription_on_unsubscribe else null,
+ .match = args.match,
+ .force_binary = if (args.force_binary) 1 else 0,
+ .force_text = if (args.force_text) 1 else 0,
+ .udata = args,
+ };
+ const ret = fio.websocket_subscribe_zigcompat(fio_args);
+ if (ret == 0) {
+ return error.SubscribeError;
+ }
+ return ret;
+ }
+
+ pub fn internal_subscription_on_message(handle: WsHandle, channel: fio.fio_str_info_s, message: fio.fio_str_info_s, udata: ?*anyopaque) callconv(.C) void {
+ if (udata) |p| {
+ const args = @ptrCast(*SubscribeArgs, @alignCast(@alignOf(*SubscribeArgs), p));
+ if (args.on_message) |on_message| {
+ on_message(args.context, handle, channel.data[0..channel.len], message.data[0..message.len]);
+ }
+ }
+ }
+ pub fn internal_subscription_on_unsubscribe(udata: ?*anyopaque) callconv(.C) void {
+ if (udata) |p| {
+ const args = @ptrCast(*SubscribeArgs, @alignCast(@alignOf(*SubscribeArgs), p));
+ if (args.on_unsubscribe) |on_unsubscribe| {
+ on_unsubscribe(args.context);
+ }
+ }
+ }
+ };
+}
diff --git a/src/zap.zig b/src/zap.zig
index 5afcff1..06b8ac6 100644
--- a/src/zap.zig
+++ b/src/zap.zig
@@ -10,6 +10,7 @@ pub usingnamespace @import("util.zig");
pub usingnamespace @import("http.zig");
pub usingnamespace @import("mustache.zig");
pub usingnamespace @import("http_auth.zig");
+pub const WebSockets = @import("websockets.zig");
pub const Log = @import("log.zig");
@@ -496,16 +497,32 @@ pub const CookieArgs = struct {
pub const HttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void;
pub const SimpleHttpRequestFn = *const fn (SimpleRequest) void;
+/// websocket connection upgrade
+/// fn(request, targetstring)
+pub const SimpleHttpUpgradeFn = *const fn (r: SimpleRequest, target_protocol: []const u8) void;
+
+/// http finish, called when zap finishes. You get your udata back in the
+/// struct.
+pub const SimpleHttpFinishSettings = [*c]fio.struct_http_settings_s;
+pub const SimpleHttpFinishFn = *const fn (SimpleHttpFinishSettings) void;
+
pub const SimpleHttpListenerSettings = struct {
port: usize,
interface: [*c]const u8 = null,
on_request: ?SimpleHttpRequestFn,
- on_response: ?*const fn ([*c]fio.http_s) callconv(.C) void = null,
+ on_response: ?SimpleHttpRequestFn = null,
+ on_upgrade: ?SimpleHttpUpgradeFn = null,
+ on_finish: ?SimpleHttpFinishFn = null,
+ // provide any pointer in there for "user data". it will be passed pack in
+ // on_finish()'s copy of the struct_http_settings_s
+ udata: ?*anyopaque = null,
public_folder: ?[]const u8 = null,
max_clients: ?isize = null,
max_body_size: ?usize = null,
timeout: ?u8 = null,
log: bool = false,
+ ws_timeout: u8 = 40,
+ ws_max_msg_size: usize = 262144,
};
pub const SimpleHttpListener = struct {
@@ -520,6 +537,9 @@ pub const SimpleHttpListener = struct {
};
}
+ // on_upgrade: ?*const fn ([*c]fio.http_s, [*c]u8, usize) callconv(.C) void = null,
+ // on_finish: ?*const fn ([*c]fio.struct_http_settings_s) callconv(.C) void = null,
+
// we could make it dynamic by passing a SimpleHttpListener via udata
pub fn theOneAndOnlyRequestCallBack(r: [*c]fio.http_s) callconv(.C) void {
if (the_one_and_only_listener) |l| {
@@ -534,6 +554,39 @@ pub const SimpleHttpListener = struct {
}
}
+ pub fn theOneAndOnlyResponseCallBack(r: [*c]fio.http_s) callconv(.C) void {
+ if (the_one_and_only_listener) |l| {
+ var req: SimpleRequest = .{
+ .path = util.fio2str(r.*.path),
+ .query = util.fio2str(r.*.query),
+ .body = util.fio2str(r.*.body),
+ .method = util.fio2str(r.*.method),
+ .h = r,
+ };
+ l.settings.on_response.?(req);
+ }
+ }
+
+ pub fn theOneAndOnlyUpgradeCallBack(r: [*c]fio.http_s, target: [*c]u8, target_len: usize) callconv(.C) void {
+ if (the_one_and_only_listener) |l| {
+ var req: SimpleRequest = .{
+ .path = util.fio2str(r.*.path),
+ .query = util.fio2str(r.*.query),
+ .body = util.fio2str(r.*.body),
+ .method = util.fio2str(r.*.method),
+ .h = r,
+ };
+ var zigtarget: []u8 = target[0..target_len];
+ l.settings.on_upgrade.?(req, zigtarget);
+ }
+ }
+
+ pub fn theOneAndOnlyFinishCallBack(s: [*c]fio.struct_http_settings_s) callconv(.C) void {
+ if (the_one_and_only_listener) |l| {
+ l.settings.on_finish.?(s);
+ }
+ }
+
pub fn listen(self: *Self) !void {
var pfolder: [*c]const u8 = null;
var pfolder_len: usize = 0;
@@ -546,22 +599,23 @@ pub const SimpleHttpListener = struct {
var x: fio.http_settings_s = .{
.on_request = if (self.settings.on_request) |_| Self.theOneAndOnlyRequestCallBack else null,
- .on_upgrade = null,
- .on_response = self.settings.on_response,
- .on_finish = null,
+ .on_upgrade = if (self.settings.on_upgrade) |_| Self.theOneAndOnlyUpgradeCallBack else null,
+ .on_response = if (self.settings.on_response) |_| Self.theOneAndOnlyResponseCallBack else null,
+ .on_finish = if (self.settings.on_finish) |_| Self.theOneAndOnlyFinishCallBack else null,
.udata = null,
.public_folder = pfolder,
.public_folder_length = pfolder_len,
.max_header_size = 32 * 1024,
.max_body_size = self.settings.max_body_size orelse 50 * 1024 * 1024,
- .max_clients = self.settings.max_clients orelse 100,
+ // fio provides good default:
+ .max_clients = self.settings.max_clients orelse 0,
.tls = null,
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
.ws_max_msg_size = 0,
.timeout = self.settings.timeout orelse 5,
- .ws_timeout = 0,
+ .ws_timeout = self.settings.ws_timeout,
.log = if (self.settings.log) 1 else 0,
.is_client = 0,
};
@@ -624,7 +678,7 @@ pub fn listen(port: [*c]const u8, interface: [*c]const u8, settings: ListenSetti
var x: fio.http_settings_s = .{
.on_request = settings.on_request,
.on_upgrade = settings.on_upgrade,
- .on_response = settings.on_response orelse null,
+ .on_response = settings.on_response,
.on_finish = settings.on_finish,
.udata = null,
.public_folder = pfolder,
@@ -636,7 +690,7 @@ pub fn listen(port: [*c]const u8, interface: [*c]const u8, settings: ListenSetti
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
- .ws_max_msg_size = 0,
+ .ws_max_msg_size = settings.ws_max_msg_size,
.timeout = settings.keepalive_timeout_s,
.ws_timeout = 0,
.log = if (settings.log) 1 else 0,