Commit | Line | Data |
---|---|---|
8e198a08 MB |
1 | ;;;# ParenScript Tutorial |
2 | ||
3 | ;;; This chapter is a short introductory tutorial to ParenScript. It | |
4 | ;;; hopefully will give you an idea how ParenScript can be used in a | |
5 | ;;; Lisp web application. | |
6 | ||
7 | ;;;# Setting up the ParenScript environment | |
8 | ||
9 | ;;; In this tutorial, we will use the Portable Allegroserve webserver | |
10 | ;;; to serve the tutorial web application. We use the ASDF system to | |
11 | ;;; load both Allegroserve and ParenScript. I assume you have | |
12 | ;;; installed and downloaded Allegroserve and Parenscript, and know | |
13 | ;;; how to setup the central registry for ASDF. | |
14 | ||
15 | (asdf:oos 'asdf:load-op :aserve) | |
16 | ||
17 | ; ... lots of compiler output ... | |
18 | ||
19 | (asdf:oos 'asdf:load-op :parenscript) | |
20 | ||
21 | ; ... lots of compiler output ... | |
22 | ||
23 | ;;; The tutorial will be placed in its own package, which we first | |
24 | ;;; have to define. | |
25 | ||
26 | (defpackage :js-tutorial | |
d5ede101 | 27 | (:use :common-lisp :net.aserve :net.html.generator :parenscript)) |
8e198a08 MB |
28 | |
29 | (in-package :js-tutorial) | |
30 | ||
d5ede101 VS |
31 | ;;; Since we will not be using ParenScript's package system for this |
32 | ;;; tutorial, disable it for now. | |
33 | ||
34 | (setf *enable-package-system* nil) | |
35 | ||
8e198a08 MB |
36 | ;;; The next command starts the webserver on the port 8000. |
37 | ||
38 | (start :port 8000) | |
39 | ||
40 | ;;; We are now ready to generate the first JavaScript-enabled webpages | |
41 | ;;; using ParenScript. | |
42 | ||
43 | ;;;# A simple embedded example | |
44 | ||
45 | ;;; The first document we will generate is a simple HTML document, | |
46 | ;;; which features a single hyperlink. When clicking the hyperlink, a | |
47 | ;;; JavaScript handler opens a popup alert window with the string | |
48 | ;;; "Hello world". To facilitate the development, we will factor out | |
49 | ;;; the HTML generation to a separate function, and setup a handler | |
50 | ;;; for the url "/tutorial1", which will generate HTTP headers and | |
51 | ;;; call the function `TUTORIAL1'. At first, our function does nothing. | |
52 | ||
53 | (defun tutorial1 (req ent) | |
54 | (declare (ignore req ent)) | |
55 | nil) | |
56 | ||
57 | (publish :path "/tutorial1" | |
58 | :content-type "text/html; charset=ISO-8859-1" | |
59 | :function #'(lambda (req ent) | |
60 | (with-http-response (req ent) | |
61 | (with-http-body (req ent) | |
62 | (tutorial1 req ent))))) | |
63 | ||
64 | ;;; Browsing "http://localhost:8000/tutorial1" should return an empty | |
65 | ;;; HTML page. It's now time to fill this rather page with | |
66 | ;;; content. ParenScript features a macro that generates a string that | |
94a05cdf | 67 | ;;; can be used as an attribute value of HTML nodes. |
8e198a08 MB |
68 | |
69 | (defun tutorial1 (req ent) | |
70 | (declare (ignore req ent)) | |
71 | (html | |
72 | (:html | |
73 | (:head (:title "ParenScript tutorial: 1st example")) | |
74 | (:body (:h1 "ParenScript tutorial: 1st example") | |
75 | (:p "Please click the link below." :br | |
76 | ((:a :href "#" :onclick (js-inline | |
77 | (alert "Hello World"))) | |
78 | "Hello World")))))) | |
79 | ||
80 | ;;; Browsing "http://localhost:8000/tutorial1" should return the | |
81 | ;;; following HTML: | |
82 | ||
8e198a08 MB |
83 | <html><head><title>ParenScript tutorial: 1st example</title> |
84 | </head> | |
85 | <body><h1>ParenScript tutorial: 1st example</h1> | |
86 | <p>Please click the link below.<br/> | |
87 | <a href="#" | |
88 | onclick="javascript:alert("Hello World");">Hello World</a> | |
89 | </p> | |
90 | </body> | |
91 | </html> | |
92 | ||
93 | ;;;# Adding an inline ParenScript | |
94 | ||
95 | ;;; Suppose we now want to have a general greeting function. One way | |
96 | ;;; to do this is to add the javascript in a `SCRIPT' element at the | |
97 | ;;; top of the HTML page. This is done using the `JS-SCRIPT' macro | |
98 | ;;; which will generate the necessary XML and comment tricks to | |
99 | ;;; cleanly embed JavaScript. We will redefine our `TUTORIAL1' | |
100 | ;;; function and add a few links: | |
101 | ||
102 | (defun tutorial1 (req ent) | |
103 | (declare (ignore req ent)) | |
104 | (html | |
105 | (:html | |
106 | (:head | |
107 | (:title "ParenScript tutorial: 2nd example") | |
108 | (js-script | |
109 | (defun greeting-callback () | |
110 | (alert "Hello World")))) | |
111 | (:body | |
112 | (:h1 "ParenScript tutorial: 2nd example") | |
113 | (:p "Please click the link below." :br | |
114 | ((:a :href "#" :onclick (js-inline (greeting-callback))) | |
115 | "Hello World") | |
116 | :br "And maybe this link too." :br | |
117 | ((:a :href "#" :onclick (js-inline (greeting-callback))) | |
118 | "Knock knock") | |
119 | :br "And finally a third link." :br | |
120 | ((:a :href "#" :onclick (js-inline (greeting-callback))) | |
121 | "Hello there")))))) | |
122 | ||
123 | ;;; This will generate the following HTML page, with the embedded | |
124 | ;;; JavaScript nicely sitting on top. Take note how | |
125 | ;;; `GREETING-CALLBACK' was converted to camelcase, and how the lispy | |
126 | ;;; `DEFUN' was converted to a JavaScript function declaration. | |
127 | ||
8e198a08 MB |
128 | <html><head><title>ParenScript tutorial: 2nd example</title> |
129 | <script type="text/javascript"> | |
130 | // <![CDATA[ | |
131 | function greetingCallback() { | |
132 | alert("Hello World"); | |
133 | } | |
134 | // ]]> | |
135 | </script> | |
136 | </head> | |
137 | <body><h1>ParenScript tutorial: 2nd example</h1> | |
138 | <p>Please click the link below.<br/> | |
139 | <a href="#" | |
140 | onclick="javascript:greetingCallback();">Hello World</a> | |
141 | <br/> | |
142 | And maybe this link too.<br/> | |
143 | <a href="#" | |
144 | onclick="javascript:greetingCallback();">Knock knock</a> | |
145 | <br/> | |
146 | ||
147 | And finally a third link.<br/> | |
148 | <a href="#" | |
149 | onclick="javascript:greetingCallback();">Hello there</a> | |
150 | </p> | |
151 | </body> | |
152 | </html> | |
153 | ||
154 | ;;;# Generating a JavaScript file | |
155 | ||
156 | ;;; The best way to integrate ParenScript into a Lisp application is | |
157 | ;;; to generate a JavaScript file from ParenScript code. This file can | |
158 | ;;; be cached by intermediate proxies, and webbrowsers won't have to | |
159 | ;;; reload the javascript code on each pageview. A standalone | |
160 | ;;; JavaScript can be generated using the macro `JS-FILE'. We will | |
161 | ;;; publish the tutorial JavaScript under "/tutorial.js". | |
162 | ||
163 | (defun tutorial1-file (req ent) | |
164 | (declare (ignore req ent)) | |
165 | (js-file | |
166 | (defun greeting-callback () | |
167 | (alert "Hello World")))) | |
168 | ||
169 | (publish :path "/tutorial1.js" | |
170 | :content-type "text/javascript; charset=ISO-8859-1" | |
171 | :function #'(lambda (req ent) | |
172 | (with-http-response (req ent) | |
173 | (with-http-body (req ent) | |
174 | (tutorial1-file req ent))))) | |
175 | ||
176 | (defun tutorial1 (req ent) | |
177 | (declare (ignore req ent)) | |
178 | (html | |
179 | (:html | |
180 | (:head | |
181 | (:title "ParenScript tutorial: 3rd example") | |
182 | ((:script :language "JavaScript" :src "/tutorial1.js"))) | |
183 | (:body | |
184 | (:h1 "ParenScript tutorial: 3rd example") | |
185 | (:p "Please click the link below." :br | |
186 | ((:a :href "#" :onclick (js-inline (greeting-callback))) | |
187 | "Hello World") | |
188 | :br "And maybe this link too." :br | |
189 | ((:a :href "#" :onclick (js-inline (greeting-callback))) | |
190 | "Knock knock") | |
191 | :br "And finally a third link." :br | |
192 | ((:a :href "#" :onclick (js-inline (greeting-callback))) | |
193 | "Hello there")))))) | |
194 | ||
195 | ;;; This will generate the following JavaScript code under | |
196 | ;;; "/tutorial1.js": | |
197 | ||
198 | function greetingCallback() { | |
199 | alert("Hello World"); | |
200 | } | |
201 | ||
202 | ;;; and the following HTML code: | |
203 | ||
204 | <html><head><title>ParenScript tutorial: 3rd example</title> | |
205 | <script language="JavaScript" src="/tutorial1.js"></script> | |
206 | </head> | |
207 | <body><h1>ParenScript tutorial: 3rd example</h1> | |
208 | <p>Please click the link below.<br/> | |
209 | <a href="#" onclick="javascript:greetingCallback();">Hello World</a> | |
210 | <br/> | |
211 | And maybe this link too.<br/> | |
212 | <a href="#" onclick="javascript:greetingCallback();">Knock knock</a> | |
213 | <br/> | |
214 | ||
215 | And finally a third link.<br/> | |
216 | <a href="#" onclick="javascript:greetingCallback();">Hello there</a> | |
217 | </p> | |
218 | </body> | |
219 | </html> | |
220 | ||
221 | ;;;# A ParenScript slideshow | |
222 | ||
223 | ;;; While developing ParenScript, I used JavaScript programs from the | |
224 | ;;; web and rewrote them using ParenScript. This is a nice slideshow | |
225 | ;;; example from | |
226 | ||
227 | http://www.dynamicdrive.com/dynamicindex14/dhtmlslide.htm | |
228 | ||
229 | ;;; The slideshow will be accessible under "/slideshow", and will | |
230 | ;;; slide through the images "photo1.png", "photo2.png" and | |
231 | ;;; "photo3.png". The first ParenScript version will be very similar | |
232 | ;;; to the original JavaScript code. The second version will then show | |
233 | ;;; how to integrate data from the Lisp environment into the | |
234 | ;;; ParenScript code, allowing us to customize the slideshow | |
235 | ;;; application by supplying a list of image names. We first setup the | |
236 | ;;; slideshow path. | |
237 | ||
238 | (publish :path "/slideshow" | |
239 | :content-type "text/html" | |
240 | :function #'(lambda (req ent) | |
241 | (with-http-response (req ent) | |
242 | (with-http-body (req ent) | |
243 | (slideshow req ent))))) | |
244 | ||
245 | (publish :path "/slideshow.js" | |
246 | :content-type "text/html" | |
247 | :function #'(lambda (req ent) | |
248 | (with-http-response (req ent) | |
249 | (with-http-body (req ent) | |
250 | (js-slideshow req ent))))) | |
251 | ||
252 | ;;; The images are just random images I found on my harddrive. We will | |
253 | ;;; publish them by hand for now. | |
254 | ||
255 | (publish-file :path "/photo1.png" | |
256 | :file "/home/manuel/bknr-sputnik.png") | |
257 | (publish-file :path "/photo2.png" | |
258 | :file "/home/manuel/bknrlogo_red648.png") | |
259 | (publish-file :path "/photo3.png" | |
260 | :file "/home/manuel/bknr-sputnik.png") | |
261 | ||
262 | ;;; The function `SLIDESHOW' generates the HTML code for the main | |
263 | ;;; slideshow page. It also features little bits of ParenScript. These | |
264 | ;;; are the callbacks on the links for the slideshow application. In | |
265 | ;;; this special case, the javascript generates the links itself by | |
266 | ;;; using `document.write' in a "SCRIPT" element. Users that don't | |
267 | ;;; have JavaScript enabled won't see anything at all. | |
268 | ;;; | |
269 | ;;; `SLIDESHOW' also generates a static array called `PHOTOS' which | |
270 | ;;; holds the links to the photos of the slideshow. This array is | |
271 | ;;; handled by the ParenScript code in "slideshow.js". Note how the | |
272 | ;;; HTML code issued by the JavaScript is generated using the `HTML' | |
273 | ;;; construct. In fact, we have two different HTML generators in the | |
274 | ;;; example below, one is the standard Lisp HTML generator, and the | |
275 | ;;; other is the JavaScript HTML generator, which generates a | |
276 | ;;; JavaScript expression. | |
277 | ||
278 | (defun slideshow (req ent) | |
279 | (declare (ignore req ent)) | |
280 | (html | |
281 | (:html | |
282 | (:head (:title "ParenScript slideshow") | |
283 | ((:script :language "JavaScript" | |
284 | :src "/slideshow.js")) | |
285 | (js-script | |
286 | (defvar *linkornot* 0) | |
287 | (defvar photos (array "photo1.png" | |
288 | "photo2.png" | |
289 | "photo3.png")))) | |
290 | (:body (:h1 "ParenScript slideshow") | |
291 | (:body (:h2 "Hello") | |
292 | ((:table :border 0 | |
293 | :cellspacing 0 | |
294 | :cellpadding 0) | |
295 | (:tr ((:td :width "100%" :colspan 2 :height 22) | |
296 | (:center | |
297 | (js-script | |
298 | (let ((img | |
299 | (html | |
300 | ((:img :src (aref photos 0) | |
301 | :name "photoslider" | |
302 | :style ( + "filter:" | |
303 | (js (reveal-trans | |
304 | (setf duration 2) | |
305 | (setf transition 23)))) | |
306 | :border 0))))) | |
307 | (document.write | |
308 | (if (= *linkornot* 1) | |
309 | (html ((:a :href "#" | |
310 | :onclick (js-inline (transport))) | |
311 | img)) | |
312 | img))))))) | |
313 | (:tr ((:td :width "50%" :height "21") | |
314 | ((:p :align "left") | |
315 | ((:a :href "#" | |
316 | :onclick (js-inline (backward) | |
317 | (return false))) | |
318 | "Previous Slide"))) | |
319 | ((:td :width "50%" :height "21") | |
320 | ((:p :align "right") | |
321 | ((:a :href "#" | |
322 | :onclick (js-inline (forward) | |
323 | (return false))) | |
324 | "Next Slide")))))))))) | |
325 | ||
326 | ;;; `SLIDESHOW' generates the following HTML code (long lines have | |
327 | ;;; been broken down): | |
328 | ||
329 | <html><head><title>ParenScript slideshow</title> | |
330 | <script language="JavaScript" src="/slideshow.js"></script> | |
331 | <script type="text/javascript"> | |
332 | // <![CDATA[ | |
333 | var LINKORNOT = 0; | |
334 | var photos = [ "photo1.png", "photo2.png", "photo3.png" ]; | |
335 | // ]]> | |
336 | </script> | |
337 | </head> | |
338 | <body><h1>ParenScript slideshow</h1> | |
339 | <body><h2>Hello</h2> | |
340 | <table border="0" cellspacing="0" cellpadding="0"> | |
341 | <tr><td width="100%" colspan="2" height="22"> | |
342 | <center><script type="text/javascript"> | |
343 | // <![CDATA[ | |
344 | var img = | |
345 | "<img src=\"" + photos[0] | |
346 | + "\" name=\"photoslider\" | |
347 | style=\"filter:revealTrans(duration=2,transition=23)\" | |
348 | border=\"0\"></img>"; | |
349 | document.write(LINKORNOT == 1 ? | |
350 | "<a href=\"#\" | |
351 | onclick=\"javascript:transport()\">" | |
352 | + img + "</a>" | |
353 | : img); | |
354 | // ]]> | |
355 | </script> | |
356 | </center> | |
357 | </td> | |
358 | </tr> | |
359 | <tr><td width="50%" height="21"><p align="left"> | |
360 | <a href="#" | |
361 | onclick="javascript:backward(); return false;">Previous Slide</a> | |
362 | ||
363 | </p> | |
364 | </td> | |
365 | <td width="50%" height="21"><p align="right"> | |
366 | <a href="#" | |
367 | onclick="javascript:forward(); return false;">Next Slide</a> | |
368 | </p> | |
369 | </td> | |
370 | </tr> | |
371 | </table> | |
372 | </body> | |
373 | </body> | |
374 | </html> | |
375 | ||
8e198a08 MB |
376 | ;;; The actual slideshow application is generated by the function |
377 | ;;; `JS-SLIDESHOW', which generates a ParenScript file. The code is | |
378 | ;;; pretty straightforward for a lisp savy person. Symbols are | |
379 | ;;; converted to JavaScript variables, but the dot "." is left as | |
380 | ;;; is. This enables us to access object "slots" without using the | |
381 | ;;; `SLOT-VALUE' function all the time. However, when the object we | |
382 | ;;; are referring to is not a variable, but for example an element of | |
383 | ;;; an array, we have to revert to `SLOT-VALUE'. | |
384 | ||
385 | (defun js-slideshow (req ent) | |
386 | (declare (ignore req ent)) | |
387 | (js-file | |
388 | (defvar *preloaded-images* (make-array)) | |
389 | (defun preload-images (photos) | |
390 | (dotimes (i photos.length) | |
391 | (setf (aref *preloaded-images* i) (new *Image) | |
392 | (slot-value (aref *preloaded-images* i) 'src) | |
393 | (aref photos i)))) | |
551080b7 | 394 | |
8e198a08 MB |
395 | (defun apply-effect () |
396 | (when (and document.all photoslider.filters) | |
397 | (let ((trans photoslider.filters.reveal-trans)) | |
398 | (setf (slot-value trans '*Transition) | |
399 | (floor (* (random) 23))) | |
400 | (trans.stop) | |
401 | (trans.apply)))) | |
551080b7 | 402 | |
8e198a08 MB |
403 | (defun play-effect () |
404 | (when (and document.all photoslider.filters) | |
405 | (photoslider.filters.reveal-trans.play))) | |
406 | ||
407 | (defvar *which* 0) | |
551080b7 | 408 | |
8e198a08 MB |
409 | (defun keep-track () |
410 | (setf window.status | |
411 | (+ "Image " (1+ *which*) " of " photos.length))) | |
551080b7 | 412 | |
8e198a08 MB |
413 | (defun backward () |
414 | (when (> *which* 0) | |
415 | (decf *which*) | |
416 | (apply-effect) | |
417 | (setf document.images.photoslider.src | |
418 | (aref photos *which*)) | |
419 | (play-effect) | |
420 | (keep-track))) | |
551080b7 | 421 | |
8e198a08 MB |
422 | (defun forward () |
423 | (when (< *which* (1- photos.length)) | |
424 | (incf *which*) | |
425 | (apply-effect) | |
426 | (setf document.images.photoslider.src | |
427 | (aref photos *which*)) | |
428 | (play-effect) | |
429 | (keep-track))) | |
551080b7 | 430 | |
8e198a08 MB |
431 | (defun transport () |
432 | (setf window.location (aref photoslink *which*))))) | |
433 | ||
434 | ;;; `JS-SLIDESHOW' generates the following JavaScript code: | |
435 | ||
436 | var PRELOADEDIMAGES = new Array(); | |
437 | function preloadImages(photos) { | |
438 | for (var i = 0; i != photos.length; i = i++) { | |
439 | PRELOADEDIMAGES[i] = new Image; | |
440 | PRELOADEDIMAGES[i].src = photos[i]; | |
441 | } | |
442 | } | |
443 | function applyEffect() { | |
444 | if (document.all && photoslider.filters) { | |
445 | var trans = photoslider.filters.revealTrans; | |
446 | trans.Transition = Math.floor(Math.random() * 23); | |
447 | trans.stop(); | |
448 | trans.apply(); | |
449 | } | |
450 | } | |
451 | function playEffect() { | |
452 | if (document.all && photoslider.filters) { | |
453 | photoslider.filters.revealTrans.play(); | |
454 | } | |
455 | } | |
456 | var WHICH = 0; | |
457 | function keepTrack() { | |
458 | window.status = "Image " + (WHICH + 1) + " of " + | |
459 | photos.length; | |
460 | } | |
461 | function backward() { | |
462 | if (WHICH > 0) { | |
463 | --WHICH; | |
464 | applyEffect(); | |
465 | document.images.photoslider.src = photos[WHICH]; | |
466 | playEffect(); | |
467 | keepTrack(); | |
468 | } | |
469 | } | |
470 | function forward() { | |
471 | if (WHICH < photos.length - 1) { | |
472 | ++WHICH; | |
473 | applyEffect(); | |
474 | document.images.photoslider.src = photos[WHICH]; | |
475 | playEffect(); | |
476 | keepTrack(); | |
477 | } | |
478 | } | |
479 | function transport() { | |
480 | window.location = photoslink[WHICH]; | |
481 | } | |
482 | ||
483 | ;;;# Customizing the slideshow | |
484 | ||
485 | ;;; For now, the slideshow has the path to all the slideshow images | |
486 | ;;; hardcoded in the HTML code, as well as in the publish | |
487 | ;;; statements. We now want to customize this by publishing a | |
488 | ;;; slideshow under a certain path, and giving it a list of image urls | |
489 | ;;; and pathnames where those images can be found. For this, we will | |
490 | ;;; create a function `PUBLISH-SLIDESHOW' which takes a prefix as | |
491 | ;;; argument, as well as a list of image pathnames to be published. | |
492 | ||
493 | (defun publish-slideshow (prefix images) | |
494 | (let* ((js-url (format nil "~Aslideshow.js" prefix)) | |
495 | (html-url (format nil "~Aslideshow" prefix)) | |
496 | (image-urls | |
497 | (mapcar #'(lambda (image) | |
498 | (format nil "~A~A.~A" prefix | |
499 | (pathname-name image) | |
500 | (pathname-type image))) | |
501 | images))) | |
502 | (publish :path html-url | |
503 | :content-type "text/html" | |
504 | :function #'(lambda (req ent) | |
505 | (with-http-response (req ent) | |
506 | (with-http-body (req ent) | |
507 | (slideshow2 req ent image-urls))))) | |
508 | (publish :path js-url | |
509 | :content-type "text/html" | |
510 | :function #'(lambda (req ent) | |
511 | (with-http-response (req ent) | |
512 | (with-http-body (req ent) | |
513 | (js-slideshow req ent))))) | |
514 | (map nil #'(lambda (image url) | |
515 | (publish-file :path url | |
516 | :file image)) | |
517 | images image-urls))) | |
518 | ||
519 | (defun slideshow2 (req ent image-urls) | |
520 | (declare (ignore req ent)) | |
521 | (html | |
522 | (:html | |
523 | (:head (:title "ParenScript slideshow") | |
524 | ((:script :language "JavaScript" | |
525 | :src "/slideshow.js")) | |
526 | ((:script :type "text/javascript") | |
527 | (:princ (format nil "~%// <![CDATA[~%")) | |
528 | (:princ (js (defvar *linkornot* 0))) | |
529 | (:princ (js-to-string `(defvar photos | |
530 | (array ,@image-urls)))) | |
531 | (:princ (format nil "~%// ]]>~%")))) | |
532 | (:body (:h1 "ParenScript slideshow") | |
533 | (:body (:h2 "Hello") | |
534 | ((:table :border 0 | |
535 | :cellspacing 0 | |
536 | :cellpadding 0) | |
537 | (:tr ((:td :width "100%" :colspan 2 :height 22) | |
538 | (:center | |
539 | (js-script | |
540 | (let ((img | |
541 | (html | |
542 | ((:img :src (aref photos 0) | |
543 | :name "photoslider" | |
544 | :style ( + "filter:" | |
545 | (js (reveal-trans | |
546 | (setf duration 2) | |
547 | (setf transition 23)))) | |
548 | :border 0))))) | |
549 | (document.write | |
550 | (if (= *linkornot* 1) | |
551 | (html ((:a :href "#" | |
552 | :onclick (js-inline (transport))) | |
553 | img)) | |
554 | img))))))) | |
555 | (:tr ((:td :width "50%" :height "21") | |
556 | ((:p :align "left") | |
557 | ((:a :href "#" | |
558 | :onclick (js-inline (backward) | |
559 | (return false))) | |
560 | "Previous Slide"))) | |
561 | ((:td :width "50%" :height "21") | |
562 | ((:p :align "right") | |
563 | ((:a :href "#" | |
564 | :onclick (js-inline (forward) | |
565 | (return false))) | |
566 | "Next Slide")))))))))) | |
567 | ||
568 | ;;; We can now publish the same slideshow as before, under the | |
569 | ;;; "/bknr/" prefix: | |
570 | ||
94a05cdf | 571 | (publish-slideshow "/bknr/" |
8e198a08 MB |
572 | `("/home/manuel/bknr-sputnik.png" |
573 | "/home/manuel/bknrlogo_red648.png" | |
574 | "/home/manuel/screenshots/screenshot-14.03.2005-11.54.33.png")) | |
575 | ||
576 | ;;; That's it, we can now access our customized slideshow under | |
577 | ||
578 | http://localhost:8000/bknr/slideshow | |
579 |