Mercurial Hosting > linkmystyle
comparison src/links.html.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 Html = require "luan:Html.luan" | |
5 local html_encode = Html.encode or error() | |
6 local Table = require "luan:Table.luan" | |
7 local concat = Table.concat or error() | |
8 local Io = require "luan:Io.luan" | |
9 local Http = require "luan:http/Http.luan" | |
10 local Shared = require "site:/lib/Shared.luan" | |
11 local head = Shared.head or error() | |
12 local body_header = Shared.body_header or error() | |
13 local footer = Shared.footer or error() | |
14 local show_editable_link = Shared.show_editable_link or error() | |
15 local User = require "site:/lib/User.luan" | |
16 local Link = require "site:/lib/Link.luan" | |
17 local get_owner_links = Link.get_owner_links or error() | |
18 local Pic = require "site:/lib/Pic.luan" | |
19 local Utils = require "site:/lib/Utils.luan" | |
20 local to_list = Utils.to_list or error() | |
21 local Logging = require "luan:logging/Logging.luan" | |
22 local logger = Logging.logger "links.html" | |
23 | |
24 | |
25 local function hashtags(pic) | |
26 local hashtags = to_list(pic.hashtags) | |
27 for i, hashtag in ipairs(hashtags) do | |
28 hashtags[i] = "#"..hashtag | |
29 end | |
30 return concat(hashtags," ") | |
31 end | |
32 | |
33 local function div_links(links,pic_id) | |
34 %> | |
35 <div links> | |
36 <form add onsubmit="ajaxForm('add_link.js',this)" action="javascript:"> | |
37 <% if pic_id ~= nil then %> | |
38 <input type=hidden name=pic value="<%=pic_id%>"> | |
39 <% end %> | |
40 <input type=text required name=title placeholder="Title"> | |
41 <input type=url required name=url placeholder="URL"> | |
42 <button type=submit big>Add link</button> | |
43 </form> | |
44 <div start></div> | |
45 <% for _, link in ipairs(links) do | |
46 show_editable_link(link) | |
47 end %> | |
48 </div> | |
49 <% | |
50 end | |
51 | |
52 return function() | |
53 local user = User.current_required() | |
54 if user==nil then return end | |
55 local owner = user | |
56 local pic = Http.request.parameters.pic | |
57 if pic ~= nil then | |
58 pic = Pic.get_by_id(pic) | |
59 if pic == nil then | |
60 logger.warn("pic not found\n"..Http.request.raw_head) | |
61 Http.response.send_error(404) | |
62 return | |
63 end | |
64 pic.user_id == user.id or error() | |
65 owner = pic | |
66 end | |
67 local links = get_owner_links(owner.id) | |
68 local pic_id = pic and pic.id | |
69 Io.stdout = Http.response.text_writer() | |
70 %> | |
71 <!doctype html> | |
72 <html lang="en"> | |
73 <head> | |
74 <% head() %> | |
75 <title>Link My Style</title> | |
76 <style> | |
77 form[add] { | |
78 margin-bottom: 40px; | |
79 } | |
80 input { | |
81 margin-bottom: 5px; | |
82 } | |
83 input[type="url"], | |
84 input[type="text"] { | |
85 display: block; | |
86 } | |
87 div[link] { | |
88 margin-bottom: 20px; | |
89 } | |
90 button[small] { | |
91 font-size: 12px; | |
92 } | |
93 div[delete2] p { | |
94 margin-bottom: 5px; | |
95 } | |
96 | |
97 div[link] > div:first-of-type { | |
98 border-radius: 12px / 50%; | |
99 margin-bottom: 5px; | |
100 overflow: hidden; | |
101 position: relative; | |
102 border: 1px solid #ebebeb; | |
103 } | |
104 div[link] a { | |
105 border-radius: initial; | |
106 margin-bottom: initial; | |
107 } | |
108 div[link] a:hover { | |
109 background-color: #243F47; | |
110 } | |
111 div[link] img { | |
112 position: absolute; | |
113 top: 0; | |
114 height: 100%; | |
115 background-color: white; | |
116 opacity: 0.3; | |
117 padding: 4px; | |
118 touch-action: none; | |
119 } | |
120 | |
121 <% if pic == nil then %> | |
122 div[links] { | |
123 margin-top: 20px; | |
124 } | |
125 <% else %> | |
126 div[msg] { | |
127 margin-top: 20px; | |
128 margin-left: 5%; | |
129 color: darkgreen; | |
130 <% if Http.request.parameters.saved == nil then %> | |
131 display: none; | |
132 <% end %> | |
133 } | |
134 div[body] { | |
135 margin-top: 40px; | |
136 margin-bottom: 20px; | |
137 } | |
138 div[pic] img { | |
139 width: 100%; | |
140 display: block; | |
141 margin-bottom: 5px; | |
142 } | |
143 div[pic] form { | |
144 margin-top: 20px; | |
145 } | |
146 div[field] { | |
147 margin-top: 1px; | |
148 margin-bottom: 10px; | |
149 } | |
150 div[hashtags] { | |
151 margin-top: 20px; | |
152 } | |
153 @media (min-width: 888px) { | |
154 div[body] { | |
155 display: flex; | |
156 } | |
157 div[pic] { | |
158 width: 45%; | |
159 margin-left: 5%; | |
160 } | |
161 div[outer_links] { | |
162 width: 55%; | |
163 margin-left: 5%; | |
164 margin-right: 5%; | |
165 } | |
166 } | |
167 @media (max-width: 887px) { | |
168 div[pic] { | |
169 display: block; | |
170 width: 90%; | |
171 margin-left: auto; | |
172 margin-right: auto; | |
173 margin-bottom: 40px; | |
174 } | |
175 } | |
176 <% end %> | |
177 </style> | |
178 <script> | |
179 'use strict'; | |
180 | |
181 function clearAddForm() { | |
182 let form = document.querySelector('form[add]'); | |
183 form.querySelector('[name="title"]').value = ''; | |
184 form.querySelector('[name="url"]').value = ''; | |
185 } | |
186 | |
187 function deleteLink1(button,linkId) { | |
188 let div = button.parentNode; | |
189 if( div.querySelector('div[delete2]') ) | |
190 return; | |
191 let html = ` | |
192 <div delete2> | |
193 <p>Are you sure that you want to delete this?</p> | |
194 <button delete2 small onclick="deleteLink2('${linkId}')">Yes</button> | |
195 <button small onclick="undelete1(this)">No</button> | |
196 </div> | |
197 ` ; | |
198 div.insertAdjacentHTML( 'beforeEnd', html ); | |
199 div.scrollIntoViewIfNeeded(false); | |
200 } | |
201 function undelete1(button) { | |
202 button.parentNode.outerHTML = ''; | |
203 } | |
204 function deleteLink2(linkId) { | |
205 ajax( '/delete_link.js?link='+linkId ); | |
206 } | |
207 function deletePic1(button) { | |
208 let div = button.parentNode; | |
209 if( div.querySelector('div[delete2]') ) | |
210 return; | |
211 let html = ` | |
212 <div delete2> | |
213 <p>Are you sure that you want to delete this?</p> | |
214 <button delete2 small onclick="deletePic2('<%=pic_id%>')">Yes</button> | |
215 <button small onclick="undelete1(this)">No</button> | |
216 </div> | |
217 ` ; | |
218 div.insertAdjacentHTML( 'beforeEnd', html ); | |
219 div.scrollIntoViewIfNeeded(false); | |
220 } | |
221 function deletePic2() { | |
222 ajax( '/delete_pic.js?pic=<%=pic_id%>' ); | |
223 } | |
224 | |
225 function editLink(linkId) { | |
226 ajax( '/edit_link.js?link='+linkId ); | |
227 } | |
228 | |
229 function cancel(linkId) { | |
230 ajax( '/cancel_edit_link.js?link='+linkId ); | |
231 } | |
232 | |
233 dad.onDropped = function(event) { | |
234 let dragging = event.original; | |
235 if( iDragging === indexOf(dragging.parentNode.querySelectorAll(dropSelector),dragging) ) | |
236 return; | |
237 let linkId = dragging.getAttribute('link'); | |
238 let prev = dragging.previousElementSibling; | |
239 let prevId = prev && prev.getAttribute('link'); | |
240 ajax( '/move_link.js?link='+linkId+'&prev='+prevId ); | |
241 }; | |
242 | |
243 dad.whatToDrag = function(draggable) { | |
244 return draggable.parentNode.parentNode; | |
245 }; | |
246 | |
247 function dragInit() { | |
248 dropSelector = 'div[link]'; | |
249 let items = document.querySelectorAll('div[link] img'); | |
250 for( let i=0; i<items.length; i++ ) { | |
251 let item = items[i]; | |
252 dad.setDraggable(item); | |
253 dad.setDropzone(item.parentNode.parentNode); | |
254 } | |
255 } | |
256 | |
257 <% if pic ~= nil then %> | |
258 function changePic(uuid,filename) { | |
259 ajax( '/change_pic.js', 'pic=<%=pic.id%>&uuid=' + uuid + '&filename=' + encodeURIComponent(filename) ); | |
260 } | |
261 function startChangePic() { | |
262 uploadcare.cropprOptions = {}; | |
263 uploadcare.upload(changePic); | |
264 } | |
265 <% end %> | |
266 </script> | |
267 </head> | |
268 <body> | |
269 <div full> | |
270 <% | |
271 body_header() | |
272 if pic == nil then | |
273 %> | |
274 <p top>Enter your title and a URL to create a link. Once saved, you can drag-and-drop the icon on the left to change the order. The order on this page will also appear on your page.</p> | |
275 <% | |
276 div_links(links,pic_id) | |
277 else | |
278 %> | |
279 <div back> | |
280 <a href="/pics.html#p-<%=pic.id%>"><img src="/images/keyboard_backspace.svg"></a> | |
281 </div> | |
282 <div msg> | |
283 Your image has been saved. | |
284 </div> | |
285 <div body> | |
286 <div pic> | |
287 <img <%=pic.title_attr()%> src="<%=pic.get_url()%>"> | |
288 <div> | |
289 <button type=button small onclick="startChangePic()">Change</button> | |
290 <button type=button small onclick="deletePic1(this)">Delete</button> | |
291 </div> | |
292 <div hashtags><%=pic.hashtags_html("pics.html")%></div> | |
293 <form onsubmit="ajaxForm('/save_pic_title.js',this)" action="javascript:"> | |
294 <input type=hidden name=pic value="<%=pic_id%>"> | |
295 <label>Photo title</label> | |
296 <div field> | |
297 <input type=text required name=title placeholder="Title" value="<%= html_encode(pic.title or "") %>"> | |
298 </div> | |
299 <label>Hashtags</label> | |
300 <div field> | |
301 <textarea name=hashtags placeholder="#hashtag1 #hashtag2"><%= hashtags(pic) %></textarea> | |
302 <div error=hashtags></div> | |
303 </div> | |
304 <div field> | |
305 <label clickable><input type=checkbox name=visible <%=pic.is_hidden and "" or "checked"%>> Visible</label> | |
306 </div> | |
307 <button type=submit small>Save</button> | |
308 <div error=success></div> | |
309 </form> | |
310 </div> | |
311 <div outer_links> | |
312 <% div_links(links,pic_id) %> | |
313 </div> | |
314 </div> | |
315 <% | |
316 end | |
317 %> | |
318 <% footer() %> | |
319 </div> | |
320 <script> dragInit(); </script> | |
321 </body> | |
322 </html> | |
323 <% | |
324 end |