Mercurial Hosting > linkmystyle
comparison 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 |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:8f4df159f06b |
|---|---|
| 1 local Luan = require "luan:Luan.luan" | |
| 2 local error = Luan.error | |
| 3 local ipairs = Luan.ipairs or error() | |
| 4 local pairs = Luan.pairs or error() | |
| 5 local range = Luan.range or error() | |
| 6 local Table = require "luan:Table.luan" | |
| 7 local is_list = Table.is_list or error() | |
| 8 local is_empty = Table.is_empty or error() | |
| 9 local concat = Table.concat or error() | |
| 10 local Io = require "luan:Io.luan" | |
| 11 local uri = Io.uri or error() | |
| 12 local Html = require "luan:Html.luan" | |
| 13 local html_encode = Html.encode or error() | |
| 14 local url_encode = Html.url_encode or error() | |
| 15 local Parsers = require "luan:Parsers.luan" | |
| 16 local json_parse = Parsers.json_parse or error() | |
| 17 local json_string = Parsers.json_string or error() | |
| 18 local Thread = require "luan:Thread.luan" | |
| 19 local thread_run = Thread.run or error() | |
| 20 local sleep = Thread.sleep or error() | |
| 21 local Time = require "luan:Time.luan" | |
| 22 local Http = require "luan:http/Http.luan" | |
| 23 local Mail = require "luan:mail/Mail.luan" | |
| 24 local Config = require "site:/private/Config.luan" | |
| 25 local uploadcare_public_key = Config.uploadcare.public_key or error() | |
| 26 local Ab_test = require "site:/lib/Ab_test.luan" | |
| 27 local ab_test_head = Ab_test.head or error() | |
| 28 local User = require "site:/lib/User.luan" | |
| 29 local current_user = User.current or error() | |
| 30 local Icon = require "site:/lib/Icon.luan" | |
| 31 local get_user_icons = Icon.get_user_icons or error() | |
| 32 local Utils = require "site:/lib/Utils.luan" | |
| 33 local is_production = not not Utils.is_production | |
| 34 local base_url = Utils.base_url or error() | |
| 35 local to_list = Utils.to_list or error() | |
| 36 local Logging = require "luan:logging/Logging.luan" | |
| 37 local logger = Logging.logger "Shared" | |
| 38 | |
| 39 | |
| 40 local Shared = {} | |
| 41 | |
| 42 Shared.has_facebook = false; | |
| 43 | |
| 44 local started = Time.now() | |
| 45 | |
| 46 Shared.is_production = is_production | |
| 47 local is_local = Http.domain == nil | |
| 48 local has_reporting = true | |
| 49 | |
| 50 Shared.compressed = Utils.compressed | |
| 51 | |
| 52 local mp_id = is_production and "404d4c479de9c3070252e692375e82ca" or "bd2099a22e4118350a46b5b360d8c4da" | |
| 53 Shared.mp_id = mp_id | |
| 54 local mp_url = "https://api.mixpanel.com/track" | |
| 55 | |
| 56 local function call_mixpanel_raw(url,data) | |
| 57 local options = { | |
| 58 method = "POST" | |
| 59 parameters = { | |
| 60 data = json_string(data) | |
| 61 verbose = "1" | |
| 62 } | |
| 63 } | |
| 64 -- logger.info(options.parameters.data) | |
| 65 local result = uri(url,options).read_text() | |
| 66 result = json_parse(result) | |
| 67 result.error and logger.error("mixpanel: "..result.error) | |
| 68 end | |
| 69 Shared.call_mixpanel_raw = call_mixpanel_raw | |
| 70 | |
| 71 function Shared.call_mixpanel(data) | |
| 72 if is_list(data) then | |
| 73 for _, event in ipairs(data) do | |
| 74 event.properties.token = mp_id | |
| 75 end | |
| 76 else | |
| 77 data.properties.token = mp_id | |
| 78 end | |
| 79 call_mixpanel_raw(mp_url,data) | |
| 80 end | |
| 81 | |
| 82 | |
| 83 local function remove_from_path(name) | |
| 84 local path = Http.request.path | |
| 85 if path == "/index.html" then | |
| 86 path = "/" | |
| 87 end | |
| 88 local params = Http.request.parameters | |
| 89 params[name] = nil | |
| 90 if not is_empty(params) then | |
| 91 local t = {} | |
| 92 for name, value in pairs(params) do | |
| 93 t[#t+1] = url_encode(name).."="..url_encode(value) | |
| 94 end | |
| 95 path = path.."?"..concat(t,"&") | |
| 96 end | |
| 97 return path | |
| 98 end | |
| 99 | |
| 100 local function min_head(user) | |
| 101 %> | |
| 102 <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 103 <link rel="icon" href="/images/favicon.png" type="image/png"> | |
| 104 <style> | |
| 105 @import "https://fonts.googleapis.com/css?family=Bitter"; | |
| 106 @import "/site.css?s=<%=started%>"; | |
| 107 </style> | |
| 108 <script src="/site.js?s=<%=started%>"></script> | |
| 109 <% | |
| 110 local theme_date = user and user.theme_date | |
| 111 if theme_date ~= nil then | |
| 112 %> | |
| 113 <style> @import "/theme.css?user=<%=user.id%>&date=<%=theme_date%>&s=<%=started%>"; </style> | |
| 114 <% | |
| 115 end | |
| 116 end | |
| 117 | |
| 118 function Shared.head() | |
| 119 local user = current_user() | |
| 120 min_head(user) | |
| 121 local source = Http.request.parameters.source | |
| 122 if source ~= nil then | |
| 123 local path = remove_from_path('source') | |
| 124 %> | |
| 125 <script> | |
| 126 history.replaceState(null,null,'<%=path%>'); | |
| 127 </script> | |
| 128 <% | |
| 129 end | |
| 130 if user == nil and Http.request.cookies.source == nil then | |
| 131 source = source or "" | |
| 132 %> | |
| 133 <script> | |
| 134 mixpanel.ours.identify(); | |
| 135 mixpanel.ours.people.set({'source':'<%=source%>'}); | |
| 136 </script> | |
| 137 <% | |
| 138 Http.response.set_persistent_cookie( "source", source ) | |
| 139 end | |
| 140 %> | |
| 141 <style> | |
| 142 @import "/uploadcare/croppr.css"; | |
| 143 @import "/uploadcare/uploadcare.css?s=<%=started%>"; | |
| 144 @import "/dad.css"; | |
| 145 @import "/admin.css?s=<%=started%>"; | |
| 146 </style> | |
| 147 <script src="/uploadcare/croppr.js"></script> | |
| 148 <script src="/uploadcare/uploadcare.js?s=<%=started%>"></script> | |
| 149 <script> window.uploadcarePubKey = '<%=uploadcare_public_key%>'; </script> | |
| 150 <script src="/dad.js?v1"></script> | |
| 151 <% if user ~= nil then %> | |
| 152 <script> | |
| 153 window.UserEmail = '<%= user.email or error() %>'; | |
| 154 window.UserName = '<%= user.name or error() %>'; | |
| 155 </script> | |
| 156 <% end %> | |
| 157 <script src="/admin.js?s=<%=started%>"></script> | |
| 158 <% | |
| 159 ab_test_head() | |
| 160 end | |
| 161 | |
| 162 function Shared.pub_head(user) | |
| 163 %> | |
| 164 <script> | |
| 165 window.Owner = '<%= user.name %>'; | |
| 166 <% if user.mp_id ~= nil then %> | |
| 167 window.OwnerMpId = <%= json_string(user.mp_id) %>; | |
| 168 <% end %> | |
| 169 </script> | |
| 170 <% | |
| 171 min_head(user) | |
| 172 if has_reporting then | |
| 173 %> | |
| 174 <script async src="/reporting.js?s=<%=started%>"></script> | |
| 175 <% | |
| 176 end | |
| 177 end | |
| 178 | |
| 179 function Shared.page_header() | |
| 180 %> | |
| 181 <a header href="/"> | |
| 182 <img logo=big src="/images/logo.png"> | |
| 183 <img logo=small src="/images/small_logo.png"> | |
| 184 </a> | |
| 185 <% | |
| 186 end | |
| 187 | |
| 188 function Shared.body_header() | |
| 189 local user = current_user() | |
| 190 %> | |
| 191 <div header> | |
| 192 <a left href="/"> | |
| 193 <img logo=big src="/images/logo.png"> | |
| 194 <img logo=small src="/images/small_logo.png"> | |
| 195 </a> | |
| 196 <% if user == nil then %> | |
| 197 <span right login> | |
| 198 <a button login href="/login.html">Log in</a> | |
| 199 <a button register href="/register.html">Sign up</a> | |
| 200 </span> | |
| 201 <% else %> | |
| 202 <span right pulldown> | |
| 203 <img src="<%= user.get_pic_url() or "/images/user.png" %>" onclick="clickMenu(this)"> | |
| 204 <div pulldown_menu> | |
| 205 <a href="javascript:copyLink()"> | |
| 206 <span>linkmy.style/<%=user.name%></span> | |
| 207 <span copy>Copy</span> | |
| 208 </a> | |
| 209 <a href="/<%=user.name%>">My page</a> | |
| 210 <a href="/account.html">My account</a> | |
| 211 <a href="/links.html">Links</a> | |
| 212 <a href="/pics.html">Photos</a> | |
| 213 <a href="/theme.html">My theme</a> | |
| 214 <a href="/qr_code.html">QR code</a> | |
| 215 <a href="/analytics.html">Analytics</a> | |
| 216 <a href="javascript:logout()">Log out</a> | |
| 217 </div> | |
| 218 </span> | |
| 219 <input clipboard value="<%=base_url()%>/<%=user.name%>"> | |
| 220 <% end %> | |
| 221 </div> | |
| 222 <% | |
| 223 end | |
| 224 | |
| 225 function Shared.footer() | |
| 226 %> | |
| 227 <div footer> | |
| 228 <span> | |
| 229 <a href="/help.html">Help</a> | |
| 230 <br>support@linkmy.style | |
| 231 </span> | |
| 232 <span> | |
| 233 <a href="https://apps.apple.com/us/app/linkmystyle/id6475133762"><img ios src="/images/ios.svg"></a> | |
| 234 <a href="https://www.instagram.com/linkmy.style/"><img src="/images/icons/instagram.svg"></a> | |
| 235 </span> | |
| 236 </div> | |
| 237 <% | |
| 238 end | |
| 239 | |
| 240 function Shared.show_editable_link(link) | |
| 241 %> | |
| 242 <div link="<%=link.id%>"> | |
| 243 <div> | |
| 244 <a link pub_content href="<%=html_encode(link.url)%>" draggable=false><%=html_encode(link.title)%></a> | |
| 245 <img src="/images/drag_indicator.svg"> | |
| 246 </div> | |
| 247 <button type=button small onclick="editLink('<%=link.id%>')">Edit</button> | |
| 248 <button type=button small onclick="deleteLink1(this,'<%=link.id%>')">Delete</button> | |
| 249 </div> | |
| 250 <% | |
| 251 end | |
| 252 | |
| 253 function Shared.show_saved(close_url) | |
| 254 %> | |
| 255 <div saved> | |
| 256 <p>Your changes have been saved.</p> | |
| 257 <p><a href="/theme.html">Edit Theme</a></p> | |
| 258 <a close href="<%=close_url%>"><img src="/images/close.svg"></a> | |
| 259 </div> | |
| 260 <% | |
| 261 end | |
| 262 | |
| 263 function Shared.show_user_icons(user) | |
| 264 local icons = get_user_icons(user.id) | |
| 265 %> | |
| 266 <div list> | |
| 267 <% | |
| 268 for _, icon in ipairs(icons) do | |
| 269 %> | |
| 270 <span icon="<%=icon.id%>"> | |
| 271 <img src="/images/drag_indicator.svg"> | |
| 272 <a <%=icon.title_attr()%> href="<%=html_encode(icon.url)%>" draggable=false> | |
| 273 <img src="/images/icons/<%=icon.name%>.svg" draggable=false> | |
| 274 </a> | |
| 275 </span> | |
| 276 <% | |
| 277 end | |
| 278 %> | |
| 279 </div> | |
| 280 <button big onclick="ajax('/edit_icons.js')"><%= #icons==0 and "Add" or "Edit" %></button> | |
| 281 <% | |
| 282 end | |
| 283 | |
| 284 function Shared.js_error(field,message) | |
| 285 %> | |
| 286 showError( context.form, '<%=field%>', <%=json_string(message)%> ); | |
| 287 <% | |
| 288 end | |
| 289 | |
| 290 local send_mail = Mail.sender(Config.mail_server).send | |
| 291 | |
| 292 function Shared.send_mail_async(mail) | |
| 293 thread_run( function() | |
| 294 for i in range(1,5) do | |
| 295 try | |
| 296 send_mail(mail) | |
| 297 return | |
| 298 catch e | |
| 299 logger.error("send_mail_async fail "..i..":\n"..e) | |
| 300 sleep(10000) | |
| 301 end | |
| 302 end | |
| 303 error "send_mail_async failed" | |
| 304 end ) | |
| 305 end | |
| 306 | |
| 307 Shared.theme_fields = { | |
| 308 background_color = "#c0d1d6" | |
| 309 link_background_color = "#325762" | |
| 310 link_hover_background_color = "#243f47" | |
| 311 link_text_color = "#ffffff" | |
| 312 title_color = "#000000" | |
| 313 bio_color = "#000000" | |
| 314 icon_color = "" | |
| 315 link_border_radius = "12px / 50%" | |
| 316 link_border_color = "" | |
| 317 link_shadow = "" | |
| 318 link_shadow_color = "" | |
| 319 font = "Bitter" | |
| 320 font_url = "" | |
| 321 background_img_uuid = "" | |
| 322 background_img_filename = "" | |
| 323 } | |
| 324 | |
| 325 function Shared.password_input(value) | |
| 326 value = value or "" | |
| 327 %> | |
| 328 <div password> | |
| 329 <input type=password required name=password value="<%=html_encode(value)%>" placeholder="Password"> | |
| 330 <img show src="/images/visibility.svg" onclick="showPassword(parentNode)"> | |
| 331 <img hide src="/images/visibility_off.svg" onclick="hidePassword(parentNode)"> | |
| 332 </div> | |
| 333 <% | |
| 334 end | |
| 335 | |
| 336 function Shared.get_hashtags_list(pics) | |
| 337 local hashtags_set = {} | |
| 338 for _, pic in ipairs(pics) do | |
| 339 local hashtags = pic.hashtags | |
| 340 if hashtags ~= nil then | |
| 341 hashtags = to_list(hashtags) | |
| 342 for _, hashtag in ipairs(hashtags) do | |
| 343 hashtags_set[hashtag] = true | |
| 344 end | |
| 345 local classes = concat( hashtags, " " ) | |
| 346 pic.class = [[class="]]..classes..[["]] | |
| 347 end | |
| 348 end | |
| 349 local list = {} | |
| 350 for hashtag, v in pairs(hashtags_set) do | |
| 351 if v == true then | |
| 352 list[#list+1] = hashtag | |
| 353 end | |
| 354 end | |
| 355 return list, hashtags_set | |
| 356 end | |
| 357 | |
| 358 return Shared |
