diff src/lib/Shared.luan @ 0:8f4df159f06b

start public repo
author Franklin Schmidt <fschmidt@gmail.com>
date Fri, 11 Jul 2025 20:57:49 -0600
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/Shared.luan	Fri Jul 11 20:57:49 2025 -0600
@@ -0,0 +1,358 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local ipairs = Luan.ipairs or error()
+local pairs = Luan.pairs or error()
+local range = Luan.range or error()
+local Table = require "luan:Table.luan"
+local is_list = Table.is_list or error()
+local is_empty = Table.is_empty or error()
+local concat = Table.concat or error()
+local Io = require "luan:Io.luan"
+local uri = Io.uri or error()
+local Html = require "luan:Html.luan"
+local html_encode = Html.encode or error()
+local url_encode = Html.url_encode or error()
+local Parsers = require "luan:Parsers.luan"
+local json_parse = Parsers.json_parse or error()
+local json_string = Parsers.json_string or error()
+local Thread = require "luan:Thread.luan"
+local thread_run = Thread.run or error()
+local sleep = Thread.sleep or error()
+local Time = require "luan:Time.luan"
+local Http = require "luan:http/Http.luan"
+local Mail = require "luan:mail/Mail.luan"
+local Config = require "site:/private/Config.luan"
+local uploadcare_public_key = Config.uploadcare.public_key or error()
+local Ab_test = require "site:/lib/Ab_test.luan"
+local ab_test_head = Ab_test.head or error()
+local User = require "site:/lib/User.luan"
+local current_user = User.current or error()
+local Icon = require "site:/lib/Icon.luan"
+local get_user_icons = Icon.get_user_icons or error()
+local Utils = require "site:/lib/Utils.luan"
+local is_production = not not Utils.is_production
+local base_url = Utils.base_url or error()
+local to_list = Utils.to_list or error()
+local Logging = require "luan:logging/Logging.luan"
+local logger = Logging.logger "Shared"
+
+
+local Shared = {}
+
+Shared.has_facebook = false;
+
+local started = Time.now()
+
+Shared.is_production = is_production
+local is_local = Http.domain == nil
+local has_reporting = true
+
+Shared.compressed = Utils.compressed
+
+local mp_id = is_production and "404d4c479de9c3070252e692375e82ca" or "bd2099a22e4118350a46b5b360d8c4da"
+Shared.mp_id = mp_id
+local mp_url = "https://api.mixpanel.com/track"
+
+local function call_mixpanel_raw(url,data)
+	local options = {
+		method = "POST"
+		parameters = {
+			data = json_string(data)
+			verbose = "1"
+		}
+	}
+	-- logger.info(options.parameters.data)
+	local result = uri(url,options).read_text()
+	result = json_parse(result)
+	result.error and logger.error("mixpanel: "..result.error)
+end
+Shared.call_mixpanel_raw = call_mixpanel_raw
+
+function Shared.call_mixpanel(data)
+	if is_list(data) then
+		for _, event in ipairs(data) do
+			event.properties.token = mp_id
+		end
+	else
+		data.properties.token = mp_id
+	end
+	call_mixpanel_raw(mp_url,data)
+end
+
+
+local function remove_from_path(name)
+	local path = Http.request.path
+	if path == "/index.html" then
+		path = "/"
+	end
+	local params = Http.request.parameters
+	params[name] = nil
+	if not is_empty(params) then
+		local t = {}
+		for name, value in pairs(params) do
+			t[#t+1] = url_encode(name).."="..url_encode(value)
+		end
+		path = path.."?"..concat(t,"&")
+	end
+	return path
+end
+
+local function min_head(user)
+%>
+		<meta name="viewport" content="width=device-width, initial-scale=1">
+		<link rel="icon" href="/images/favicon.png" type="image/png">
+		<style>
+			@import "https://fonts.googleapis.com/css?family=Bitter";
+			@import "/site.css?s=<%=started%>";
+		</style>
+		<script src="/site.js?s=<%=started%>"></script>
+<%
+	local theme_date = user and user.theme_date
+	if theme_date ~= nil then
+%>
+		<style> @import "/theme.css?user=<%=user.id%>&date=<%=theme_date%>&s=<%=started%>"; </style>
+<%
+	end
+end
+
+function Shared.head()
+	local user = current_user()
+	min_head(user)
+	local source = Http.request.parameters.source
+	if source ~= nil then
+		local path = remove_from_path('source')
+%>
+		<script>
+			history.replaceState(null,null,'<%=path%>');
+		</script>
+<%
+	end
+	if user == nil and Http.request.cookies.source == nil then
+		source = source or ""
+%>
+		<script>
+			mixpanel.ours.identify();
+			mixpanel.ours.people.set({'source':'<%=source%>'});
+		</script>
+<%
+		Http.response.set_persistent_cookie( "source", source )
+	end
+%>
+		<style>
+			@import "/uploadcare/croppr.css";
+			@import "/uploadcare/uploadcare.css?s=<%=started%>";
+			@import "/dad.css";
+			@import "/admin.css?s=<%=started%>";
+		</style>
+		<script src="/uploadcare/croppr.js"></script>
+		<script src="/uploadcare/uploadcare.js?s=<%=started%>"></script>
+		<script> window.uploadcarePubKey = '<%=uploadcare_public_key%>'; </script>
+		<script src="/dad.js?v1"></script>
+<%	if user ~= nil then %>
+		<script>
+			window.UserEmail = '<%= user.email or error() %>';
+			window.UserName = '<%= user.name or error() %>';
+		</script>
+<%	end %>
+		<script src="/admin.js?s=<%=started%>"></script>
+<%
+	ab_test_head()
+end
+
+function Shared.pub_head(user)
+%>
+		<script>
+			window.Owner = '<%= user.name %>';
+<%	if user.mp_id ~= nil then %>
+			window.OwnerMpId = <%= json_string(user.mp_id) %>;
+<%	end %>
+		</script>
+<%
+	min_head(user)
+	if has_reporting then
+%>
+		<script async src="/reporting.js?s=<%=started%>"></script>
+<%
+	end
+end
+
+function Shared.page_header()
+%>
+			<a header href="/">
+				<img logo=big src="/images/logo.png">
+				<img logo=small src="/images/small_logo.png">
+			</a>
+<%
+end
+
+function Shared.body_header()
+	local user = current_user()
+%>
+		<div header>
+			<a left href="/">
+				<img logo=big src="/images/logo.png">
+				<img logo=small src="/images/small_logo.png">
+			</a>
+<%	if user == nil then %>
+			<span right login>
+				<a button login href="/login.html">Log in</a>
+				<a button register href="/register.html">Sign up</a>
+			</span>
+<%	else %>
+			<span right pulldown>
+				<img src="<%= user.get_pic_url() or "/images/user.png" %>" onclick="clickMenu(this)">
+				<div pulldown_menu>
+					<a href="javascript:copyLink()">
+						<span>linkmy.style/<%=user.name%></span>
+						<span copy>Copy</span>
+					</a>
+					<a href="/<%=user.name%>">My page</a>
+					<a href="/account.html">My account</a>
+					<a href="/links.html">Links</a>
+					<a href="/pics.html">Photos</a>
+					<a href="/theme.html">My theme</a>
+					<a href="/qr_code.html">QR code</a>
+					<a href="/analytics.html">Analytics</a>
+					<a href="javascript:logout()">Log out</a>
+				</div>
+			</span>
+			<input clipboard value="<%=base_url()%>/<%=user.name%>">
+<%	end %>
+		</div>
+<%
+end
+
+function Shared.footer()
+%>
+		<div footer>
+			<span>
+				<a href="/help.html">Help</a>
+				<br>support@linkmy.style
+			</span>
+			<span>
+				<a href="https://apps.apple.com/us/app/linkmystyle/id6475133762"><img ios src="/images/ios.svg"></a>
+				<a href="https://www.instagram.com/linkmy.style/"><img src="/images/icons/instagram.svg"></a>
+			</span>
+		</div>
+<%
+end
+
+function Shared.show_editable_link(link)
+%>
+			<div link="<%=link.id%>">
+				<div>
+					<a link pub_content href="<%=html_encode(link.url)%>" draggable=false><%=html_encode(link.title)%></a>
+					<img src="/images/drag_indicator.svg">
+				</div>
+				<button type=button small onclick="editLink('<%=link.id%>')">Edit</button>
+				<button type=button small onclick="deleteLink1(this,'<%=link.id%>')">Delete</button>
+			</div>
+<%
+end
+
+function Shared.show_saved(close_url)
+%>
+		<div saved>
+			<p>Your changes have been saved.</p>
+			<p><a href="/theme.html">Edit Theme</a></p>
+			<a close href="<%=close_url%>"><img src="/images/close.svg"></a>
+		</div>
+<%
+end
+
+function Shared.show_user_icons(user)
+	local icons = get_user_icons(user.id)
+%>
+				<div list>
+<%
+	for _, icon in ipairs(icons) do
+%>
+					<span icon="<%=icon.id%>">
+						<img src="/images/drag_indicator.svg">
+						<a <%=icon.title_attr()%> href="<%=html_encode(icon.url)%>" draggable=false>
+							<img src="/images/icons/<%=icon.name%>.svg" draggable=false>
+						</a>
+					</span>
+<%
+	end
+%>
+				</div>
+				<button big onclick="ajax('/edit_icons.js')"><%= #icons==0 and "Add" or "Edit" %></button>
+<%
+end
+
+function Shared.js_error(field,message)
+%>
+	showError( context.form, '<%=field%>', <%=json_string(message)%> );
+<%
+end
+
+local send_mail = Mail.sender(Config.mail_server).send
+
+function Shared.send_mail_async(mail)
+	thread_run( function()
+		for i in range(1,5) do
+			try
+				send_mail(mail)
+				return
+			catch e
+				logger.error("send_mail_async fail "..i..":\n"..e)
+				sleep(10000)
+			end
+		end
+		error "send_mail_async failed"
+	end )
+end
+
+Shared.theme_fields = {
+	background_color = "#c0d1d6"
+	link_background_color = "#325762"
+	link_hover_background_color = "#243f47"
+	link_text_color = "#ffffff"
+	title_color = "#000000"
+	bio_color = "#000000"
+	icon_color = ""
+	link_border_radius = "12px / 50%"
+	link_border_color = ""
+	link_shadow = ""
+	link_shadow_color = ""
+	font = "Bitter"
+	font_url = ""
+	background_img_uuid = ""
+	background_img_filename = ""
+}
+
+function Shared.password_input(value)
+	value = value or ""
+%>
+		<div password>
+			<input type=password required name=password value="<%=html_encode(value)%>" placeholder="Password">
+			<img show src="/images/visibility.svg" onclick="showPassword(parentNode)">
+			<img hide src="/images/visibility_off.svg" onclick="hidePassword(parentNode)">
+		</div>
+<%
+end
+
+function Shared.get_hashtags_list(pics)
+	local hashtags_set = {}
+	for _, pic in ipairs(pics) do
+		local hashtags = pic.hashtags
+		if hashtags ~= nil then
+			hashtags = to_list(hashtags)
+			for _, hashtag in ipairs(hashtags) do
+				hashtags_set[hashtag] = true
+			end
+			local classes = concat( hashtags, " " )
+			pic.class = [[class="]]..classes..[["]]
+		end
+	end
+	local list = {}
+	for hashtag, v in pairs(hashtags_set) do
+		if v == true then
+			list[#list+1] = hashtag
+		end
+	end
+	return list, hashtags_set
+end
+
+return Shared