diff src/bbcode/Bbcode.luan @ 44:96f0c3d65698

add /bbcode
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 10 Nov 2022 23:18:58 -0700
parents src/lib/Bbcode.luan@298c71e0c854
children 2d4f00755092
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bbcode/Bbcode.luan	Thu Nov 10 23:18:58 2022 -0700
@@ -0,0 +1,338 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local type = Luan.type or error()
+local ipairs = Luan.ipairs or error()
+local pairs = Luan.pairs or error()
+local stringify = Luan.stringify or error()
+local Io = require "luan:Io.luan"
+local output_of = Io.output_of or error()
+local Parsers = require "luan:Parsers.luan"
+local bbcode_parse = Parsers.bbcode_parse or error()
+local Html = require "luan:Html.luan"
+local html_encode = Html.encode or error()
+local html_parse = Html.parse or error()
+local Table = require "luan:Table.luan"
+local is_list = Table.is_list or error()
+local concat = Table.concat or error()
+local String = require "luan:String.luan"
+local regex = String.regex or error()
+local ends_with = String.ends_with or error()
+local User = require "site:/lib/User.luan"
+local Shared = require "site:/lib/Shared.luan"
+local list_to_set = Shared.list_to_set or error()
+local to_list = Shared.to_list or error()
+local Logging = require "luan:logging/Logging.luan"
+local logger = Logging.logger "Bbcode"
+
+
+local Bbcode = {}
+
+local starting_cr_regex = regex[[^\n]]
+
+local to_html
+local html = {}
+
+function html.b(bbcode,options)
+	%><b><% to_html(bbcode.contents,options) %></b><%
+end
+
+function html.i(bbcode,options)
+	%><i><% to_html(bbcode.contents,options) %></i><%
+end
+
+function html.u(bbcode,options)
+	%><u><% to_html(bbcode.contents,options) %></u><%
+end
+
+function html.s(bbcode,options)
+	%><s><% to_html(bbcode.contents,options) %></s><%
+end
+
+function html.sup(bbcode,options)
+	%><sup><% to_html(bbcode.contents,options) %></sup><%
+end
+
+function html.brackets(bbcode,options)
+	%>[<% to_html(bbcode.contents,options) %>]<%
+end
+
+function html.url(bbcode,options)
+	local url = bbcode.param
+	if url == nil then
+		url = html_encode(bbcode.contents)
+		%><a href="<%=url%>"><%=url%></a><%
+	else
+		url = html_encode(url)
+		%><a href="<%=url%>"><% to_html(bbcode.contents,options) %></a><%
+	end
+end
+
+function html.code(bbcode,options)
+	local s = starting_cr_regex.gsub(bbcode.contents,"")
+	%><code><%= html_encode(s) %></code><%
+	options.strip_newline = true
+end
+
+function html.img(bbcode,options)
+	%><img src="<%= html_encode(bbcode.contents) %>"><%
+end
+
+function html.color(bbcode,options)
+	%><span style="color:<%=bbcode.param%>"><% to_html(bbcode.contents,options) %></span><%
+end
+
+function html.size(bbcode,options)
+	%><span style="font-size:<%=bbcode.param%>"><% to_html(bbcode.contents,options) %></span><%
+end
+
+function html.quote(bbcode,options)
+	%><blockquote><%
+	local user_name = bbcode.param
+	if user_name ~= nil then
+		local user = User.get_by_name(user_name)
+		if user == nil then
+			%><%= user_name %> wrote:<%
+		else
+			%><a href="/user_something"><%= user_name %></a> wrote:<%
+		end
+	else
+		options.strip_newline = true
+	end
+	to_html(bbcode.contents,options)
+	%></blockquote><%
+	options.strip_newline = true
+end
+
+local function video_iframe(url)
+	%><iframe width="560" height="315" frameborder="0" allowfullscreen src="<%=url%>"></iframe><%
+end
+
+local video_handlers = {}
+do
+	local ptn1 = regex[[^\Qhttps://youtu.be/\E([a-zA-Z0-9_-]+)(?:\?t=([0-9]+))?]]
+	local ptn2 = regex[[^\Qhttps://www.youtube.com/watch?v=\E([a-zA-Z0-9_-]+)(?:&t=([0-9]+)s)?]]
+	function video_handlers.youtube(url)
+		local id, start = ptn1.match(url)
+		if id == nil then
+			id, start = ptn2.match(url)
+		end
+		if id == nil then
+			return false
+		end
+		url = "https://www.youtube.com/embed/"..id
+		if start ~= nil then
+			url = url.."?start="..start
+		end
+		video_iframe(url)
+		return true
+	end
+end
+do
+	local ptn = regex[[^\Qhttps://rumble.com/embed/\E[a-z0-9]+/\?pub=[a-z0-9]+]]
+	function video_handlers.rumble(url)
+		if not ptn.matches(url) then
+			return false
+		end
+		video_iframe(url)
+		return true
+	end
+end
+do
+	local ptn = regex[[^\Qhttps://www.bitchute.com/video/\E([a-zA-Z0-9]+)/]]
+	function video_handlers.bitchute(url)
+		local id = ptn.match(url)
+		if id == nil then
+			return false
+		end
+		url = "https://www.bitchute.com/embed/"..id.."/"
+		video_iframe(url)
+		return true
+	end
+end
+do
+	local ptn = regex[[^\Qhttps://vimeo.com/\E([0-9]+)]]
+	function video_handlers.vimeo(url)
+		local id = ptn.match(url)
+		if id == nil then
+			return false
+		end
+		url = "https://player.vimeo.com/video/"..id
+		video_iframe(url)
+		return true
+	end
+end
+do
+	local ptn = regex[[^\Qhttps://dai.ly/\E([a-z0-9]+)]]
+	function video_handlers.dailymotion(url)
+		local id = ptn.match(url)
+		if id == nil then
+			return false
+		end
+		url = "https://www.dailymotion.com/embed/video/"..id
+		video_iframe(url)
+		return true
+	end
+end
+do
+	local ptn = regex[[^\Qhttps://www.tiktok.com/\E[^/]+/video/([0-9]+)]]
+	function video_handlers.tiktok(url)
+		local id = ptn.match(url)
+		if id == nil then
+			return false
+		end
+		%><blockquote class="tiktok-embed" data-video-id="<%=id%>" style="max-width: 560px; margin-left: 0;"><section></section></blockquote><%
+		%><script async src="https://www.tiktok.com/embed.js"></script><%
+		return true
+	end
+end
+do
+	local ptn = regex[[\.[a-zA-Z0-9]+$]]
+	function video_handlers.file(url)
+		if not ptn.matches(url) then
+			return false
+		end
+		%><video controls width="560"><source src="<%=html_encode(url)%>"></video><%
+		return true
+	end
+end
+
+function html.video(bbcode,options)
+	local url = bbcode.contents
+	for _, handle in pairs(video_handlers) do
+		if handle(url) then return end
+	end
+	url = html_encode(url)
+	%><a href="<%=url%>"><%=url%></a><%
+end
+
+local function list_to_html(bbcode,options)
+	local list = to_list(bbcode.contents)
+	for _, item in ipairs(list) do
+		if type(item) == "table" and item.name == "li" then
+			%><li><% to_html(item.contents,options) %></li><%
+		end
+	end
+	options.strip_newline = true
+end
+
+function html.ul(bbcode,options)
+	%><ul><%
+	list_to_html(bbcode,options)
+	%></ul><%
+end
+
+function html.ol(bbcode,options)
+	%><ol><%
+	list_to_html(bbcode,options)
+	%></ol><%
+end
+
+function to_html(bbcode,options)
+	if options.strip_newline then
+		if type(bbcode) == "string" then
+			bbcode = starting_cr_regex.gsub(bbcode,"")
+		end
+		options.strip_newline = false
+	end
+	if type(bbcode) == "string" then
+		%><%= html_encode(bbcode) %><%
+	else
+		type(bbcode) == "table" or error()
+		if is_list(bbcode) then
+			for _, v in ipairs(bbcode) do
+				to_html(v,options)
+			end
+		else
+			local fn = html[bbcode.name] or error(bbcode.name.." not handled")
+			fn(bbcode,options)
+		end
+	end
+end
+
+function Bbcode.to_html(bbcode)
+	bbcode = bbcode_parse(bbcode)
+	%><div message><%
+	to_html(bbcode,{strip_newline=false})
+	%></div><%
+end
+
+
+local doesnt_nest = list_to_set{
+	"url"
+	"code"
+	"img"
+	"video"
+}
+
+local url_regex = regex[[(^|\s)(https?://\S+)]]
+
+local function preprocess(bbcode)
+	if type(bbcode) == "string" then
+		bbcode = url_regex.gsub( bbcode, "$1[url]$2[/url]" )
+		%><%= bbcode %><%
+	else
+		type(bbcode) == "table" or error()
+		if is_list(bbcode) then
+			for _, v in ipairs(bbcode) do
+				preprocess(v)
+			end
+		else
+			local name = bbcode.name
+			local param = bbcode.param
+			%>[<%=name%><%
+			if param ~= nil then
+				%>=<%=param%><%
+			end
+			%>]<%
+			if doesnt_nest[name] then
+				%><%=bbcode.contents%><%
+			else
+				preprocess(bbcode.contents)
+			end
+			if name == "code" and param ~= nil then
+				%>[/<%=name%>=<%=param%>]<%
+			else
+				%>[/<%=name%>]<%
+			end
+		end
+	end
+end
+
+-- url handling
+function Bbcode.preprocess(bbcode)
+	bbcode = bbcode_parse(bbcode)
+	return output_of(function()
+		preprocess(bbcode)
+	end)
+end
+
+function Bbcode.remove_html(text)
+	local ends_with_newline = ends_with(text,"\n")
+	local t = {}
+	local html = html_parse(text)
+	local is_first = true
+	for _, v in ipairs(html) do
+		if type(v) == "string" then
+			t[#t+1] = v
+		else
+			local name = v.name
+			if name == "div" then
+				if not is_first then
+					t[#t+1] = "\n"
+				end
+			elseif name == "/div" or name == "br" then
+				-- ignore
+			else
+				error("unexpected tag: "..name)
+			end
+		end
+		is_first = false
+	end
+	if not ends_with_newline then
+		t[#t+1] = "\n"
+	end
+	return concat(t)
+end
+
+
+return Bbcode