From 65a1f637391920c91a42cfd36695e55b84a1eb95 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Wed, 17 Sep 2008 13:23:30 +0200 Subject: [PATCH] Added table of contents. --- lib/librarian/html.py | 57 +++-- wolnelektury/media/css/master.book.css | 104 +++++++++- wolnelektury/media/img/arrow-down.png | Bin 0 -> 687 bytes wolnelektury/media/img/arrow-up.png | Bin 0 -> 761 bytes wolnelektury/media/js/book.js | 22 ++ wolnelektury/media/js/jquery.scrollto.js | 194 ++++++++++++++++++ wolnelektury/settings.py | 10 +- wolnelektury/templates/base.html | 1 + .../templates/catalogue/book_text.html | 14 +- 9 files changed, 374 insertions(+), 28 deletions(-) create mode 100644 wolnelektury/media/img/arrow-down.png create mode 100644 wolnelektury/media/img/arrow-up.png create mode 100644 wolnelektury/media/js/book.js create mode 100644 wolnelektury/media/js/jquery.scrollto.js diff --git a/lib/librarian/html.py b/lib/librarian/html.py index 13cef6520..8d256ee95 100644 --- a/lib/librarian/html.py +++ b/lib/librarian/html.py @@ -162,17 +162,26 @@ def extract_fragments(input_filename): return closed_fragments, open_fragments -def add_anchor(element, number): - anchor = etree.Element('a', href='#f%d' % number) - anchor.set('class', 'anchor') - anchor.text = str(number) - if element.text: - anchor.tail = element.text - element.text = u'' - element.insert(0, anchor) +def add_anchor(element, prefix, with_link=True, with_target=True, link_text=None): + if with_link: + if link_text is None: + link_text = prefix + anchor = etree.Element('a', href='#%s' % prefix) + anchor.set('class', 'anchor') + anchor.text = unicode(link_text) + if element.text: + anchor.tail = element.text + element.text = u'' + element.insert(0, anchor) - anchor_target = etree.Element('a', name='f%d' % number) - element.insert(0, anchor_target) + if with_target: + anchor_target = etree.Element('a', name='%s' % prefix) + anchor_target.set('class', 'target') + anchor_target.text = u' ' + if element.text: + anchor_target.tail = element.text + element.text = u'' + element.insert(0, anchor_target) def any_ancestor(element, test): @@ -191,39 +200,43 @@ def add_anchors(root): if element.tag == 'p' and 'verse' in element.get('class', ''): if counter == 1 or counter % 5 == 0: - add_anchor(element, counter) + add_anchor(element, "f%d" % counter, link_text=counter) counter += 1 elif 'paragraph' in element.get('class', ''): - add_anchor(element, counter) + add_anchor(element, "f%d" % counter, link_text=counter) counter += 1 def add_table_of_contents(root): sections = [] - + counter = 1 for element in root.iterdescendants(): if element.tag in ('h2', 'h3'): - if any_ancestor(element, lambda e: e.get('id') in ('footnotes')): + if any_ancestor(element, lambda e: e.get('id') in ('footnotes',)): continue - if element.tag == 'h3' and len(sections) and sections[-1][0] == 'h2': - sections[-1][2].append((element.tag, ''.join(element.xpath('descendant-or-self::text()')), [])) + if element.tag == 'h3' and len(sections) and sections[-1][1] == 'h2': + sections[-1][3].append((counter, element.tag, ''.join(element.xpath('text()')), [])) else: - sections.append((element.tag, ''.join(element.xpath('descendant-or-self::text()')), [])) - + sections.append((counter, element.tag, ''.join(element.xpath('text()')), [])) + add_anchor(element, "s%d" % counter, with_link=False) + counter += 1 + toc = etree.Element('div') toc.set('id', 'toc') toc_header = etree.SubElement(toc, 'h2') toc_header.text = u'Spis treści' toc_list = etree.SubElement(toc, 'ol') - for section, text, subsections in sections: + for n, section, text, subsections in sections: section_element = etree.SubElement(toc_list, 'li') - section_element.text = text + add_anchor(section_element, "s%d" % n, with_target=False, link_text=text) + if len(subsections): subsection_list = etree.SubElement(section_element, 'ol') - for subsection, text, _ in subsections: + for n, subsection, text, _ in subsections: subsection_element = etree.SubElement(subsection_list, 'li') - subsection_element.text = text + add_anchor(subsection_element, "s%d" % n, with_target=False, link_text=text) + root.insert(0, toc) diff --git a/wolnelektury/media/css/master.book.css b/wolnelektury/media/css/master.book.css index 98e142b9f..b789bcd0e 100644 --- a/wolnelektury/media/css/master.book.css +++ b/wolnelektury/media/css/master.book.css @@ -2,8 +2,7 @@ body { font-size: 16px; font: Georgia, "Times New Roman", serif; line-height: 1.5em; - margin: 3em; - max-width: 36em; + margin: 0; } a { @@ -11,6 +10,104 @@ a { text-decoration: none; } +#book-text { + margin: 3em; + max-width: 36em; +} + +/* ================================== */ +/* = Header with logo and menu = */ +/* ================================== */ +#header { + margin: 3.4em 0 0 1.4em; +} + +img { + border: none; +} + + +#menu { + position: fixed; + left: 0em; + top: 0em; + width: 100%; + height: 1.5em; + background: #333; + color: #FFF; +} + +#menu ul { + list-style: none; + padding: 0; + margin: 0; +} + +#menu li a { + display: block; + float: left; + width: 7.5em; + height: 1.5em; + margin-left: 0.5em; + text-align: center; + color: #FFF; +} + +#menu li a:hover, #menu li a:active { + color: #000; + background: #FFF url(/media/img/arrow-down.png) no-repeat center right; +} + +#menu li a.selected { + color: #000; + background: #FFF url(/media/img/arrow-up.png) no-repeat center right; +} + +#toc { + position: fixed; + left: 0em; + top: 1.5em; + width: 37em; + padding: 1.5em; + background: #FFF; + border-bottom: 0.25em solid #DDD; + border-right: 0.25em solid #DDD; + display: none; + height: 16em; + overflow-x: hidden; + overflow-y: auto; +} + +#toc ol { + list-style: none; + padding: 0; + margin: 0; +} + +#toc ol li { + font-weight: bold; +} + +#toc ol ol { + padding: 0 0 1.5em 1.5em; + margin: 0; +} + +#toc ol ol li { + font-weight: normal; +} + +#toc h2 { + display: none; +} + +#toc .anchor { + float: none; + margin: 0; + color: blue; + font-size: 16px; +} + /* =================================================== */ /* = Common elements: headings, paragraphs and lines = */ /* =================================================== */ @@ -98,12 +195,11 @@ p { padding: 0.2em 0.5em; } -.anchor:hover, .anchor:active { +.anchor:hover, #book-text .anchor:active { color: #FFF; background-color: #CCC; } - /* =================== */ /* = Custom elements = */ /* =================== */ diff --git a/wolnelektury/media/img/arrow-down.png b/wolnelektury/media/img/arrow-down.png new file mode 100644 index 0000000000000000000000000000000000000000..0e32315c09fe4728ae4c5995836beaa2eea23c38 GIT binary patch literal 687 zcmV;g0#N;lP);vm%-9)BYQR{6n;@WWrkKeV>tGL)l!P--c?W=UfRd=4TmEVQL+BC? zFyrxB(Vv9JUN|5|D1dPSmmxt20$+o3Y}Fnw$i@6W;oMHiDNt3=b_uM7BAf%`3~ds^ z5t@eitO4f+h0WoNDGlHV*-=#vlUi_&Suj*1j|w;oUQkpBvWy3`eUGICR@7C!{JO|+ zbJVXd<$(7VrC!0qQBX}5_j3Sss03on2$;{OWFY&R4o@|l#1Y2;W|a(kAGvh-PvY?l zj5jv8c*gMZ&0C(li#&b)g7c?;-o7$*7-25u>58}i=+@2ax_AGP_9UtOs>cr>=*r(W z^sl6%Vf2N|Tx$1LJbu>xhu5l4p4F#L3bSvzc;TF0f0}AHNU7vOVYjohv-87#ZWq03 zmL&@6trNs<&UAmD*oplna_z6HY*Yp>Vaz};_l`-^MI1}yvM9Ix@?NKpkA&OStKF%j zC@K6$5K=$=Y@r_gpwU3rM2;QX=I))_Jo{?+gd%_7Q1!@Z^C4|L$OPtTBYY+Zl5FOk>2D84I`?G?@ zlMQW$_ZI6dAs6b&WHA?%Q7IIG7Pbb0I2y&l;!!TZlr?_JQE%&oefSpxPM69$* zYn4hYweml*36lT7Eo2qNkVbYbLL{uo-S=Z=o?|iZ&EBiKu}JGF<^!HNbLPyMF-c;E z9Lzx0#z7fyvL{j(!3&r%dL%?x;6SAxde*Bo&~2#*?4Z`xdx2oICPcavnZ^+d2`rLg zTR4@Bfc0ukE_s(~pE%AN*10H8j*0_?U_wB4n5IkwLRyfE14b9q9by#y0bQCQ%%AjQJb{a-lv={Hy;7STy+k#xWmAjbXUXa_}-t4bOqA9OlpRk@T; z^u?{u^v?Sq=+P+2CFMz3(vatQ$-7kAQyg$gEtD#-z`6?r#Qk6I@yCNddFzeWIr!lK z0i=%p$;dLolgAqf>0-rs+Q?NkN-ADe`V%Q{^wEbmb$EEFDWu& zh8HSMT}#0*RZ1zu&bjyVkF2g-5w8U*Xo5-}3HzH@KP~@%zy+pWpt52M>>UIn6h&Z9Fa@-sTu(Voa3N6YhL} zmrXIMj8rE2F=MOCeEi9$ys&B+tz~4njO@O=f92R5g3TX_H5DouLWI!%vj7Z3p<6Yg zgj2>-((=WfAnc_ALZH1@jOy+ETMU>Pb>2Kr1M5}50C%Xhl9sDyd)%=7YQo-J^L*>( rcA-73In#Lo@$)16cgg?6&xyYQ>gUgHCboWa00000NkvXXu0mjf83bxD literal 0 HcmV?d00001 diff --git a/wolnelektury/media/js/book.js b/wolnelektury/media/js/book.js new file mode 100644 index 000000000..cfb9ab088 --- /dev/null +++ b/wolnelektury/media/js/book.js @@ -0,0 +1,22 @@ +$(function() { + $('#toc').hide(); + + if ($('#toc li').length == 0) { + $('#menu li a[href="#toc"]').remove(); + } + + $('#toc a').click(function(event) { + event.preventDefault(); + $('#menu li a.selected[href="#toc"]').click(); + $.scrollTo('a[name="' + $(this).attr('href').slice(1) + '"]', {offset: {top: -50, left: 0}}); + }); + + $('#menu li a').toggle(function() { + $('#menu li a.selected').click(); + $(this).addClass('selected'); + $($(this).attr('href')).slideDown('fast'); + }, function() { + $(this).removeClass('selected'); + $($(this).attr('href')).slideUp('fast'); + }); +}); diff --git a/wolnelektury/media/js/jquery.scrollto.js b/wolnelektury/media/js/jquery.scrollto.js new file mode 100644 index 000000000..7f4248922 --- /dev/null +++ b/wolnelektury/media/js/jquery.scrollto.js @@ -0,0 +1,194 @@ +/** + * jQuery.ScrollTo + * Copyright (c) 2007-2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com + * Dual licensed under MIT and GPL. + * Date: 9/11/2008 + * + * @projectDescription Easy element scrolling using jQuery. + * http://flesler.blogspot.com/2007/10/jqueryscrollto.html + * Tested with jQuery 1.2.6. On FF 2/3, IE 6/7, Opera 9.2/5 and Safari 3. on Windows. + * + * @author Ariel Flesler + * @version 1.4 + * + * @id jQuery.scrollTo + * @id jQuery.fn.scrollTo + * @param {String, Number, DOMElement, jQuery, Object} target Where to scroll the matched elements. + * The different options for target are: + * - A number position (will be applied to all axes). + * - A string position ('44', '100px', '+=90', etc ) will be applied to all axes + * - A jQuery/DOM element ( logically, child of the element to scroll ) + * - A string selector, that will be relative to the element to scroll ( 'li:eq(2)', etc ) + * - A hash { top:x, left:y }, x and y can be any kind of number/string like above. + * @param {Number} duration The OVERALL length of the animation, this argument can be the settings object instead. + * @param {Object,Function} settings Optional set of settings or the onAfter callback. + * @option {String} axis Which axis must be scrolled, use 'x', 'y', 'xy' or 'yx'. + * @option {Number} duration The OVERALL length of the animation. + * @option {String} easing The easing method for the animation. + * @option {Boolean} margin If true, the margin of the target element will be deducted from the final position. + * @option {Object, Number} offset Add/deduct from the end position. One number for both axes or { top:x, left:y }. + * @option {Object, Number} over Add/deduct the height/width multiplied by 'over', can be { top:x, left:y } when using both axes. + * @option {Boolean} queue If true, and both axis are given, the 2nd axis will only be animated after the first one ends. + * @option {Function} onAfter Function to be called after the scrolling ends. + * @option {Function} onAfterFirst If queuing is activated, this function will be called after the first scrolling ends. + * @return {jQuery} Returns the same jQuery object, for chaining. + * + * @desc Scroll to a fixed position + * @example $('div').scrollTo( 340 ); + * + * @desc Scroll relatively to the actual position + * @example $('div').scrollTo( '+=340px', { axis:'y' } ); + * + * @dec Scroll using a selector (relative to the scrolled element) + * @example $('div').scrollTo( 'p.paragraph:eq(2)', 500, { easing:'swing', queue:true, axis:'xy' } ); + * + * @ Scroll to a DOM element (same for jQuery object) + * @example var second_child = document.getElementById('container').firstChild.nextSibling; + * $('#container').scrollTo( second_child, { duration:500, axis:'x', onAfter:function(){ + * alert('scrolled!!'); + * }}); + * + * @desc Scroll on both axes, to different values + * @example $('div').scrollTo( { top: 300, left:'+=200' }, { axis:'xy', offset:-20 } ); + */ +;(function( $ ){ + + var $scrollTo = $.scrollTo = function( target, duration, settings ){ + $(window).scrollTo( target, duration, settings ); + }; + + $scrollTo.defaults = { + axis:'y', + duration:1 + }; + + // Returns the element that needs to be animated to scroll the window. + // Kept for backwards compatibility (specially for localScroll & serialScroll) + $scrollTo.window = function( scope ){ + return $(window).scrollable(); + }; + + // Hack, hack, hack... stay away! + // Returns the real elements to scroll (supports window/iframes, documents and regular nodes) + $.fn.scrollable = function(){ + return this.map(function(){ + // Just store it, we might need it + var win = this.parentWindow || this.defaultView, + // If it's a document, get its iframe or the window if it's THE document + elem = this.nodeName == '#document' ? win.frameElement || win : this, + // Get the corresponding document + doc = elem.contentDocument || (elem.contentWindow || elem).document, + isWin = elem.setInterval; + + return elem.nodeName == 'IFRAME' || isWin && $.browser.safari ? doc.body + : isWin ? doc.documentElement + : this; + }); + }; + + $.fn.scrollTo = function( target, duration, settings ){ + if( typeof duration == 'object' ){ + settings = duration; + duration = 0; + } + if( typeof settings == 'function' ) + settings = { onAfter:settings }; + + settings = $.extend( {}, $scrollTo.defaults, settings ); + // Speed is still recognized for backwards compatibility + duration = duration || settings.speed || settings.duration; + // Make sure the settings are given right + settings.queue = settings.queue && settings.axis.length > 1; + + if( settings.queue ) + // Let's keep the overall duration + duration /= 2; + settings.offset = both( settings.offset ); + settings.over = both( settings.over ); + + return this.scrollable().each(function(){ + var elem = this, + $elem = $(elem), + targ = target, toff, attr = {}, + win = $elem.is('html,body'); + + switch( typeof targ ){ + // A number will pass the regex + case 'number': + case 'string': + if( /^([+-]=)?\d+(px)?$/.test(targ) ){ + targ = both( targ ); + // We are done + break; + } + // Relative selector, no break! + targ = $(targ,this); + case 'object': + // DOMElement / jQuery + if( targ.is || targ.style ) + // Get the real position of the target + toff = (targ = $(targ)).offset(); + } + $.each( settings.axis.split(''), function( i, axis ){ + var Pos = axis == 'x' ? 'Left' : 'Top', + pos = Pos.toLowerCase(), + key = 'scroll' + Pos, + old = elem[key], + Dim = axis == 'x' ? 'Width' : 'Height', + dim = Dim.toLowerCase(); + + if( toff ){// jQuery / DOMElement + attr[key] = toff[pos] + ( win ? 0 : old - $elem.offset()[pos] ); + + // If it's a dom element, reduce the margin + if( settings.margin ){ + attr[key] -= parseInt(targ.css('margin'+Pos)) || 0; + attr[key] -= parseInt(targ.css('border'+Pos+'Width')) || 0; + } + + attr[key] += settings.offset[pos] || 0; + + if( settings.over[pos] ) + // Scroll to a fraction of its width/height + attr[key] += targ[dim]() * settings.over[pos]; + }else + attr[key] = targ[pos]; + + // Number or 'number' + if( /^\d+$/.test(attr[key]) ) + // Check the limits + attr[key] = attr[key] <= 0 ? 0 : Math.min( attr[key], max(Dim) ); + + // Queueing axes + if( !i && settings.queue ){ + // Don't waste time animating, if there's no need. + if( old != attr[key] ) + // Intermediate animation + animate( settings.onAfterFirst ); + // Don't animate this axis again in the next iteration. + delete attr[key]; + } + }); + animate( settings.onAfter ); + + function animate( callback ){ + $elem.animate( attr, duration, settings.easing, callback && function(){ + callback.call(this, target, settings); + }); + }; + function max( Dim ){ + var attr ='scroll'+Dim, + doc = elem.ownerDocument; + + return win + ? Math.max( doc.documentElement[attr], doc.body[attr] ) + : elem[attr]; + }; + }).end(); + }; + + function both( val ){ + return typeof val == 'object' ? val : { top:val, left:val }; + }; + +})( jQuery ); \ No newline at end of file diff --git a/wolnelektury/settings.py b/wolnelektury/settings.py index 3135bc244..a978759f9 100644 --- a/wolnelektury/settings.py +++ b/wolnelektury/settings.py @@ -99,10 +99,18 @@ COMPRESS_CSS = { } COMPRESS_JS = { + 'jquery': { + 'source_filenames': ('js/jquery.js',), + 'output_filename': 'js/jquery.min.js', + }, 'all': { - 'source_filenames': ('js/jquery.js', 'js/jquery.autocomplete.js', 'js/jquery.form.js', + 'source_filenames': ('js/jquery.autocomplete.js', 'js/jquery.form.js', 'js/jquery.jqmodal.js', 'js/jquery.labelify.js', 'js/catalogue.js',), 'output_filename': 'js/all.min.js', + }, + 'book': { + 'source_filenames': ('js/jquery.scrollto.js', 'js/book.js',), + 'output_filename': 'js/book.min.js', } } diff --git a/wolnelektury/templates/base.html b/wolnelektury/templates/base.html index 434796ea5..f481c9767 100644 --- a/wolnelektury/templates/base.html +++ b/wolnelektury/templates/base.html @@ -7,6 +7,7 @@ {% block title %}WolneLektury.pl{% endblock %} {% compressed_css "all" %} + {% compressed_js "jquery" %} {% compressed_js "all" %} {% block extrahead %} {% endblock %} diff --git a/wolnelektury/templates/catalogue/book_text.html b/wolnelektury/templates/catalogue/book_text.html index a957ecf7b..d733555a6 100644 --- a/wolnelektury/templates/catalogue/book_text.html +++ b/wolnelektury/templates/catalogue/book_text.html @@ -7,9 +7,21 @@ {% block title %}WolneLektury.pl{% endblock %} {% compressed_css "book" %} - {% compressed_js "all" %} + {% compressed_js "jquery" %} + {% compressed_js "book" %} + + {{ book.html_file.read|safe }} -- 2.20.1