alongslide 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,504 @@
1
+ #
2
+ # alongslide.coffee: Re-format HTML into horizontally-scrolling elements
3
+ # which scroll at different rates.
4
+ #
5
+ # Use CSS Regions polyfill for text flowing, and skrollr for scroll positioning.
6
+ #
7
+ # Copyright 2013 Canopy Canopy Canopy, Inc.
8
+ # Authors Adam Florin & Anthony Tran
9
+ #
10
+ class Alongslide::Layout
11
+
12
+ # For parsing pinned panel directives.
13
+ #
14
+ HORIZONTAL_EDGES: ["left", "right"]
15
+ VERTICAL_EDGES: ["top", "bottom"]
16
+ EDGES: @::HORIZONTAL_EDGES.concat @::VERTICAL_EDGES
17
+ SIZES: ["one-third", "half", "two-thirds"]
18
+ ALIGNMENTS: @::EDGES.concat 'fullscreen'
19
+
20
+ # Keys to ALS position data attributes
21
+ IN_POINT_KEY: 'als-in-position'
22
+ OUT_POINT_KEY: 'als-out-position'
23
+
24
+ # Pixel width of each frame == screen width.
25
+ #
26
+ frameWidth: $(window).width()
27
+
28
+ # Switch to true for verbose debugging. Plus constants for indent level.
29
+ #
30
+ debug: false
31
+ SUPER_FRAME_LEVEL: 1
32
+ FRAME_LEVEL: 2
33
+ SUB_FRAME_LEVEL: 3
34
+
35
+ #
36
+ #
37
+ constructor: (options = {}) ->
38
+ {@frames, @flowNames, @backgrounds, @panels, @regionCls, @sourceLength} = options
39
+
40
+ # Main entrypoint for asynchronous chain of render calls.
41
+ #
42
+ # @param postRenderCallback - to be called when layout is 100% complete
43
+ #
44
+ render: (@postRenderCallback) ->
45
+ @reset()
46
+ @writeBackgrounds()
47
+ @layout()
48
+
49
+ # Flow text into columns, using regionFlow as a CSS regions polyfill.
50
+ #
51
+ # Loop through each flow (= section), then each frame, then each column.
52
+ #
53
+ # This is all done asynchronously so that DOM can update itself periodically
54
+ # (namely for layout progress updates).
55
+ #
56
+ layout: =>
57
+ @startTime = new Date
58
+ @log "Beginning layout"
59
+
60
+ @currentFlowIndex = 0
61
+
62
+ # kick off asynchronous render
63
+ @renderSection()
64
+
65
+ # Render one section (a.k.a. one "flow" in CSSRegions parlance),
66
+ # one frame at a time, asynchronously.
67
+ #
68
+ renderSection: ->
69
+ flowName = @flowNames[@currentFlowIndex]
70
+
71
+ @log "Laying out section \"#{flowName}\"", @SUPER_FRAME_LEVEL
72
+ background = @findBackground(flowName)
73
+ @setPositionOf background, to: @nextFramePosition() if background.length
74
+ background.addClass('unstaged')
75
+
76
+ @renderFrame(flowName)
77
+
78
+ # Render one frame and its containing columns.
79
+ #
80
+ # When frame is done, trigger (asynchronously) either next frame or next section.
81
+ #
82
+ # Note that normally we check _second-to-last_ column for directives,
83
+ # as last column contains overflow. Once flow is complete, though,
84
+ # check the last column--and remove it if it contains nothing but directives.
85
+ #
86
+ renderFrame: (flowName, frame, lastColumn) ->
87
+ frame = @findOrBuildNextFlowFrame frame
88
+
89
+ # for each column in frame
90
+ while frame.find('.'+@regionCls).length < @numFrameColumns(frame)
91
+ column = @buildRegion frame, flowName
92
+
93
+ # Move three-columns class from .section to .frame
94
+ hasThreeColumns = (column.children('.three-columns').length)
95
+ if hasThreeColumns
96
+ column.find('.section').removeClass('three-columns')
97
+ column.parent().addClass('three-columns')
98
+
99
+ # Process N-1 column (as current column still total text overflow of
100
+ # the entire section).
101
+ if lastColumn?
102
+ @checkForOrphans lastColumn
103
+ @updateProgress lastColumn
104
+ @checkForDirectives lastColumn
105
+
106
+ # Section is complete. Current column is the last column of the
107
+ # section (no longer the overflow column).
108
+ if @flowComplete(flowName)
109
+ @updateProgress column
110
+ @checkForDirectives column, true
111
+ @checkForEmpties column
112
+
113
+ # render next section, or complete render.
114
+ @currentFlowIndex++
115
+ unless @currentFlowIndex is @flowNames.length
116
+ background = @findBackground(flowName)
117
+ @setPositionOf background, until: @lastFramePosition() if background.length
118
+ setTimeout((=> @renderSection()), 1)
119
+ else
120
+ @log "Layout complete"
121
+ @reorder()
122
+ @index()
123
+ @frames.children('.flow').find('.frame').addClass('unstaged')
124
+ @postRenderCallback(@lastFramePosition())
125
+ return
126
+
127
+ lastColumn = column
128
+
129
+ # unstage earlier frames
130
+ frame.prevAll().addClass('unstaged')
131
+
132
+ # render next frame
133
+ setTimeout((=> @renderFrame(flowName, frame, lastColumn)), 1)
134
+
135
+ # Check the last "fit" column for any special directives (CSS classes).
136
+ #
137
+ # The last "fit" column is the last one that doesn't also contain all the
138
+ # overflow to be laid out in other columns--typically the second-to-last column.
139
+ #
140
+ # Then, having found a directive, parse the classes and act accordingly.
141
+ #
142
+ # NOTE that this method also takes responsibility for creating the appropriate
143
+ # next frame for text to flow in. It doesn't need to return it, however,
144
+ # as the caller will just use `findOrBuildNextFlowFrame` to check for it.
145
+ #
146
+ # @param column - jQuery element to scan for directives
147
+ # @param layoutComplete - true if this flow has been completely laid out
148
+ # (and therefore no new flowing regions should be created)
149
+ #
150
+ checkForDirectives: (column, layoutComplete) ->
151
+ # for each directive
152
+ (column.find ".alongslide").each (index, directiveElement) =>
153
+ directive = $(directiveElement).detach()
154
+ id = directive.data('alongslide-id')
155
+
156
+ # the column's frame may have changed (if specified by previous directive)
157
+ flowFrame = column.parent('.frame')
158
+
159
+ switch
160
+ # new panel
161
+ when directive.hasClass "show"
162
+
163
+ # build next flow frame (if there is one)
164
+ unless layoutComplete
165
+ nextFlowFrame = @findOrBuildNextFlowFrame flowFrame
166
+ nextFlowFramePosition = if nextFlowFrame
167
+ @getPositionOf(nextFlowFrame)
168
+ else
169
+ @nextFramePosition()
170
+
171
+ # build panel
172
+ panelPosition = if directive.hasClass("now")
173
+ @getPositionOf(flowFrame)
174
+ else
175
+ nextFlowFramePosition
176
+ panelFrame = @buildPanel id, panelPosition
177
+
178
+ @updateProgress(panelFrame)
179
+
180
+ switch
181
+
182
+ # pinned panel layout
183
+ when directive.hasClass "pin"
184
+ # display forever (until unpinned)
185
+ @setPositionOf panelFrame, until: -1
186
+
187
+ # which frames need to have classes set--next and/or current?
188
+ framesWithPinnedPanels = _.compact [
189
+ flowFrame if directive.hasClass("now"),
190
+ nextFlowFrame]
191
+
192
+ # set frame classes--pushing columns into subsequent frames if necessary
193
+ _.each framesWithPinnedPanels, (frame) =>
194
+ @log "Applying with-pinned-panel styles to flow frame at " +
195
+ "#{@getPositionOf frame}", @SUB_FRAME_LEVEL
196
+ frame.addClass @withPinnedClass(directive)
197
+ frame.addClass @withSizedClass(directive)
198
+ while frame.find('.'+@regionCls).length > @numFrameColumns frame
199
+ pushToFrame = @findOrBuildNextFlowFrame frame
200
+ @log "Pushing last column of flow frame at #{@getPositionOf frame} " +
201
+ "to flow frame at #{@getPositionOf pushToFrame}", @SUB_FRAME_LEVEL
202
+ frame.find('.'+@regionCls).last().detach().prependTo(pushToFrame)
203
+
204
+ # If we changed this frame's layout, re-flow this whole section's
205
+ # regions.
206
+ if directive.hasClass("now")
207
+ sectionId = flowFrame.data('als-section-id')
208
+ @frames.children('.flow').find(".frame[data-als-section-id=#{sectionId}]").removeClass('unstaged')
209
+ document.namedFlows.get(sectionId).reFlow()
210
+
211
+ # fullscreen panel layout
212
+ when directive.hasClass "fullscreen"
213
+ if directive.hasClass "now"
214
+ @setPositionOf flowFrame, to: nextFlowFramePosition
215
+
216
+ if nextFlowFrame?
217
+ @setPositionOf nextFlowFrame, to: nextFlowFramePosition + 1
218
+
219
+ # unpin pinned panel
220
+ when directive.hasClass "unpin"
221
+ panelFrame = @findPanel(id)
222
+
223
+ unless panelFrame.length == 0
224
+ @setPositionOf panelFrame, until:
225
+ if layoutComplete
226
+ @nextFramePosition() - 1
227
+ else
228
+ Math.max @getPositionOf(flowFrame), @getPositionOf(panelFrame)
229
+
230
+ # unset frame classes for first flow frame after panel (may be next-next frame)
231
+ unless layoutComplete
232
+ postPanelFlowFrame = @findOrBuildNextFlowFrame flowFrame
233
+ if @getPositionOf(postPanelFlowFrame) is @getPositionOf(panelFrame)
234
+ postPanelFlowFrame = @findOrBuildNextFlowFrame postPanelFlowFrame
235
+ postPanelFlowFrame.removeClass @withPinnedClass(panelFrame)
236
+ postPanelFlowFrame.removeClass @withSizedClass(panelFrame)
237
+
238
+ # If column contains nothing other than directives, remove it.
239
+ #
240
+ # Called only when a section has been fully laid out.
241
+ #
242
+ # Do the test on a clone, so we don't strip directives from actual column,
243
+ # which will still be checked by checkForDirectives.
244
+ #
245
+ # If column's frame is empty, remove that, too.
246
+ #
247
+ checkForEmpties: (column) ->
248
+ columnClone = column.clone()
249
+ columnClone.find(".alongslide").detach()
250
+ if @isEmpty columnClone
251
+ columnFrame = column.parent('.frame')
252
+ @log "Removing empty column from flow frame at " +
253
+ "#{@getPositionOf columnFrame}", @SUB_FRAME_LEVEL
254
+ column.detach()
255
+
256
+ # Reset entire section layout, as regionFlow has kept a record of this column
257
+ document.namedFlows.get(columnFrame.data('als-section-id')).resetRegions()
258
+
259
+ # destroy column frame if it's empty, too
260
+ @destroyFlowFrame columnFrame if @isEmpty columnFrame
261
+
262
+ # while we're at it, check if any empty frames were created (probably at the end)
263
+ @frames.children('.flow').find('.frame:empty').each (index, frame) =>
264
+ @destroyFlowFrame $(frame)
265
+
266
+ # Check for orphaned content. This can take many forms, so this method will
267
+ # grow and evolve as cases emerge.
268
+ #
269
+ checkForOrphans: (column) ->
270
+ # If column ends with a header, push it to the overflow column.
271
+ column.find(':last:header').detach().prependTo($('.'+@regionCls+':last'))
272
+
273
+ # Given a flow frame, find the next in line after it--or create one if none exists.
274
+ #
275
+ findOrBuildNextFlowFrame: (lastFrame) ->
276
+ nextFlowFrame = if lastFrame?.length then lastFrame.next('.frame')
277
+ unless nextFlowFrame?.length
278
+ nextFlowFrame = @buildFlowFrame lastFrame
279
+ return nextFlowFrame
280
+
281
+ # Build one frame to hold columns of flowing text.
282
+ #
283
+ # Only build new frame if there are none. Otherwise, clone the last one.
284
+ #
285
+ buildFlowFrame: (lastFrame) ->
286
+ position = if lastFrame?.length
287
+ @getPositionOf(lastFrame) + 1
288
+ else
289
+ @nextFramePosition()
290
+ @log "Building flow frame at #{position}", @FRAME_LEVEL
291
+ frame = if lastFrame?.length
292
+ lastFrame?.clone().empty()
293
+ else
294
+ $('<div class="frame"/>')
295
+ frame.appendTo @frames.children('.flow')
296
+ @setPositionOf frame, to: position
297
+
298
+ # Destroy frame, shifting any subsequent panels up by one.
299
+ #
300
+ destroyFlowFrame: (frame) ->
301
+ @log "Destroying flow frame at #{@getPositionOf frame}", @FRAME_LEVEL
302
+ frame.detach()
303
+ @frames.children('.panels').find('.panel.frame').each (index, panel) =>
304
+ panelPosition = $(panel).data(@IN_POINT_KEY)
305
+ if panelPosition > frame.data(@IN_POINT_KEY)
306
+ @log "Moving panel \"#{$(panel).data('alongslide-id')}\" at " +
307
+ "#{panelPosition} to #{panelPosition-1}", @FRAME_LEVEL
308
+ $(panel).data(@IN_POINT_KEY, panelPosition-1)
309
+
310
+ # Create region to receive flowing text (column). Return jQuery object.
311
+ #
312
+ buildRegion: (frame, flowName) ->
313
+ @log "Building column in flow frame at #{@getPositionOf frame}", @SUB_FRAME_LEVEL
314
+ region = $('<div/>').addClass(@regionCls)
315
+ frame.attr('data-als-section-id', flowName)
316
+ region.appendTo frame
317
+ document.namedFlows.get(flowName).addRegion(region.get(0))
318
+ return region
319
+
320
+ # Pull panel element out of @panels storage, apply its transition, and
321
+ # append to DOM!
322
+ #
323
+ # @param id - Alongslide panel ID
324
+ #
325
+ buildPanel: (id, position) ->
326
+ panel = @panels[id].clone().addClass('unstaged').show()
327
+ alignment = _.filter @ALIGNMENTS, (alignment) -> panel.hasClass(alignment)
328
+ @log "Building #{alignment} panel frame \"#{id}\" at position #{position}", @FRAME_LEVEL
329
+ panel.addClass('frame')
330
+ panel.appendTo @frames.children('.panels')
331
+ @setPositionOf panel, to: position
332
+ return panel
333
+
334
+ # Destroy all previously laid out content.
335
+ #
336
+ reset: ->
337
+ @laidOutLength = 0
338
+
339
+ @frames.find('.backgrounds').empty()
340
+ @frames.find('.flow').empty()
341
+ @frames.find('.panels').empty()
342
+
343
+ # remove regionFlows' internal record of regions we just destroyed
344
+ _.each document.namedFlows.namedFlows, (flow) -> flow.resetRegions()
345
+
346
+ # Write the given array of backgrounds to the DOM.
347
+ #
348
+ writeBackgrounds: ->
349
+ for background in @backgrounds
350
+ @frames.find('.backgrounds').append(background.clone())
351
+
352
+ # Set frame start/end position.
353
+ #
354
+ # @param options
355
+ # to: in point (= start position)
356
+ # until: out point (= end position)
357
+ #
358
+ setPositionOf: (frame, options={}) ->
359
+ frameType = frame.parent().get(0).className
360
+ if options.to?
361
+ if (currentFramePosition = @getPositionOf frame)?
362
+ @log "Moving #{frameType} frame at #{currentFramePosition} to " +
363
+ "#{options.to}", @SUB_FRAME_LEVEL
364
+ frame.data @IN_POINT_KEY, options.to
365
+ if options.until?
366
+ @log "Dismissing #{frameType} frame \"#{frame.data('alongslide-id')}\" " +
367
+ "at #{options.until}", @SUB_FRAME_LEVEL
368
+ frame.data @OUT_POINT_KEY, options.until
369
+ return frame
370
+
371
+ # Return start position.
372
+ #
373
+ getPositionOf: (frame) ->
374
+ frame.data(@IN_POINT_KEY)
375
+
376
+ # What position the _next_ (as yet unrendered) frame should be at.
377
+ #
378
+ nextFramePosition: ->
379
+ framePosition = @lastFramePosition()
380
+ if framePosition? then framePosition + 1 else 0
381
+
382
+ # Return the largest frame number of all frames.
383
+ #
384
+ lastFramePosition: ->
385
+ flowsAndPanels = @frames.children('.flow, .panels').find('.frame')
386
+ allFramePositions = _(flowsAndPanels).map (frame) => @getPositionOf $(frame)
387
+ return if allFramePositions.length then Math.max(allFramePositions...)
388
+
389
+ # Certain frame CSS classes limit the number of columns allowed
390
+ # per flow frame.
391
+ #
392
+ numFrameColumns: (frame) ->
393
+ numColumns = if frame.hasClass('three-columns') then 3 else 2
394
+ numColumns -= 1 if @isWithHorizontalPanel(frame)
395
+ numColumns -= 1 if frame.hasClass('three-columns') and frame.hasClass("with-panel-sized-two-thirds")
396
+ return numColumns
397
+
398
+ # Once render is done, build relevant indices for later lookup.
399
+ #
400
+ index: ->
401
+ @panelIndex = {}
402
+ @frames.children('.panels').find('.panel.frame').each (index, panel) =>
403
+ outPosition = $(panel).data(@OUT_POINT_KEY) || $(panel).data(@IN_POINT_KEY)
404
+ outPosition = @lastFramePosition() if outPosition is -1
405
+ for position in [$(panel).data(@IN_POINT_KEY)..outPosition]
406
+ @panelIndex[position] ?= []
407
+ @panelIndex[position].push panel
408
+
409
+ # Re-order elements in DOM if specified.
410
+ #
411
+ # This is a more reliable method of forcing a higher z-index for certain panels.
412
+ #
413
+ reorder: ->
414
+ frontPanels = @frames.children('.panels').find('.panel.front').detach()
415
+ @frames.children('.panels').append(frontPanels)
416
+
417
+ # DOM utility.
418
+ #
419
+ # Find background for a given section (flow).
420
+ #
421
+ findBackground: (flowName) ->
422
+ @frames.children('.backgrounds').find(".background.frame[data-alongslide-id=#{flowName}]")
423
+
424
+ # DOM utility.
425
+ #
426
+ findPanel: (id) ->
427
+ @frames.children('.panels').find(".panel.frame[data-alongslide-id=#{id}]")
428
+
429
+ # Check panel index for panel at given position, and check if it's horizontal.
430
+ #
431
+ horizontalPanelAt: (position, edge) ->
432
+ edges = if edge? then [edge] else @HORIZONTAL_EDGES
433
+ _(@panelIndex[position] || []).any (panel) ->
434
+ _(edges).any (edge) -> $(panel).hasClass(edge)
435
+
436
+ # Check if flow frame shares space with horizontally-pinned panel.
437
+ #
438
+ isWithHorizontalPanel: (frame) ->
439
+ for cssClass in _.map(@HORIZONTAL_EDGES, (edge) -> "with-panel-pinned-#{edge}")
440
+ return true if frame.hasClass(cssClass)
441
+
442
+ # Given a directive for a pinned panel, retun the class to be applied to
443
+ # flow frames.
444
+ #
445
+ withPinnedClass: (directive) ->
446
+ edge = _.first _.filter @EDGES, (edge) -> directive.hasClass(edge)
447
+ "with-panel-pinned-#{edge}"
448
+
449
+ # Given a directive for a sized panel, retun the class to be applied to
450
+ # flow frames.
451
+ # TODO: maybe combine withPinnedClass and withSizedClass
452
+ withSizedClass: (directive) ->
453
+ size = _.first _.filter @SIZES, (size) -> directive.hasClass(size)
454
+ size ?= "half"
455
+ "with-panel-sized-#{size}"
456
+
457
+ # If panel is partial width (i.e. is with left/right pinned panel),
458
+ # then return its scrolling scale (0.0-1.0).
459
+ #
460
+ # Called by Scrolling to determine scoll distance.
461
+ #
462
+ # @return partialWidth - a percentage width (0.0-1.0), or undefined.
463
+ # Note: This number doesn't have to be exact--just has to "feel" right for
464
+ # pinned panels of different widths.
465
+ #
466
+ framePartialWidth: (frame) ->
467
+ if @isWithHorizontalPanel(frame)
468
+ $column = frame.find('.'+@regionCls)
469
+ return ($column.width() + $column.position().left) * @numFrameColumns(frame) / frame.width()
470
+
471
+ # Return panel alignment.
472
+ #
473
+ panelAlignment: (directive) ->
474
+ _.first _.filter @ALIGNMENTS, (alignment) -> directive.hasClass(alignment)
475
+
476
+ # Returns true when all flowing text has been laid out (i.e. last column
477
+ # region no longer contains overflow.)
478
+ #
479
+ flowComplete: (flowName) ->
480
+ not document.namedFlows.get(flowName).updateOverset()
481
+
482
+ # Utility: Is column empty after we remove directives from it?
483
+ #
484
+ isEmpty: (el) ->
485
+ $.trim(el.children().html()) == ''
486
+
487
+ # Compute length of total laid out content so far and broadcast it to our
488
+ # listeners.
489
+ #
490
+ # To listen:
491
+ #
492
+ # $(document).on 'alongslide.progress', (e, progress) ->
493
+ # #
494
+ #
495
+ updateProgress: (newElement) ->
496
+ @laidOutLength += newElement.text().length
497
+ $(document).triggerHandler 'alongslide.progress', (@laidOutLength / @sourceLength)
498
+
499
+ # Write debug log to console if available/desired.
500
+ #
501
+ log: (message, indentLevel = 0) ->
502
+ indent = (_(indentLevel).times -> ". ").join('')
503
+ if console? and @debug
504
+ console.log "#{indent}#{message} (elapsed: #{(new Date - @startTime).valueOf()}ms)"
@@ -0,0 +1,125 @@
1
+ #
2
+ # parser.coffee: parse raw HTML into Alongslide types: sections, panels, etc.
3
+ #
4
+ # Copyright 2013 Canopy Canopy Canopy, Inc.
5
+ # Author Adam Florin
6
+
7
+ class Alongslide::Parser
8
+
9
+ # Store names of flows here as we create them.
10
+ #
11
+ # sections: []
12
+ backgrounds: []
13
+ flowNames: []
14
+
15
+ constructor: (options) ->
16
+ {@source} = options
17
+ @preprocessSource()
18
+
19
+ #
20
+ #
21
+ preprocessSource: ->
22
+ # Put dummy content inside empty directives as CSSRegions trims any empty
23
+ # elements found near the boundaries of a region.
24
+ (@source.find ".alongslide:empty").text("[ALS]")
25
+
26
+ # Parser entrypoint.
27
+ #
28
+ # Build sections and store them directly as CSSRegions named flows.
29
+ #
30
+ # Retun panels and footnotes, which will be needed by other components.
31
+ #
32
+ # Note! Parse order matters! Sections should go last, once all non-section
33
+ # material has been scraped out of @source.
34
+ #
35
+ parse: ->
36
+ @sourceLength = 0
37
+
38
+ panels = @collectPanels()
39
+ footnotes = @collectFootnotes()
40
+ @collectSections()
41
+
42
+ flowNames: @flowNames
43
+ backgrounds: @backgrounds
44
+ panels: panels
45
+ footnotes: footnotes
46
+ sourceLength: @sourceLength
47
+
48
+ #
49
+ #
50
+ collectPanels: ->
51
+ rawPanels = @source.find('.alongslide.show.panel')
52
+
53
+ _.object _.map rawPanels, (el) ->
54
+ $el = $(el)
55
+ panelId = $el.data('alongslide-id')
56
+ panelEl = $el.clone().removeClass('show')
57
+
58
+ # Cleanup
59
+ $el.empty().removeClass('panel')
60
+
61
+ return [ panelId, panelEl ]
62
+
63
+ # Sift through passed-in sections, delineating them based on `enter` and `exit`
64
+ # directives, then assigning each to a flow.
65
+ #
66
+ collectSections: ->
67
+ @source.find('.alongslide.enter.section').each (index, directiveElement) =>
68
+ directive = $(directiveElement)
69
+ id = directive.data('alongslide-id')
70
+ exitSelector = '.alongslide.exit.section[data-alongslide-id='+id+']'
71
+
72
+ # build section for content BEFORE section enter
73
+ lastSectionContents = directive.prevAll().detach().get().reverse()
74
+ @buildSection(lastSectionContents, directive) if lastSectionContents.length
75
+
76
+ # build section for content AFTER section enter
77
+ sectionContents = directive.nextUntil(exitSelector).detach()
78
+ @buildSection(sectionContents, directive, id)
79
+
80
+ # cleanup section build process
81
+ directive.remove()
82
+ (@source.find exitSelector).remove()
83
+
84
+ @buildSection @source.children() unless @source.is(':empty')
85
+
86
+ # Build section, given content.
87
+ #
88
+ # Create new NamedFlow for it, and log the name.
89
+ #
90
+ # Create section background.
91
+ #
92
+ # @param content - jQuery object of section contents
93
+ # @param directive (optional) - directive which specified the section
94
+ # @param id (optional) - Alongslide section ID
95
+ #
96
+ buildSection: (content, directive, id) ->
97
+ flowName = id || "sectionFlow#{@flowNames.length}"
98
+
99
+ idElement = directive?.removeClass('alongslide').empty().removeClass('enter') || $("<div/>")
100
+ idElement.attr('data-alongslide-id', flowName)
101
+
102
+ # create section
103
+ section = idElement.clone().append(content)
104
+
105
+ # tally up length
106
+ @sourceLength += section.text().length
107
+
108
+ # create NamedFlow
109
+ document.namedFlows.get(flowName).addContent(section.get(0))
110
+ @flowNames.push flowName
111
+
112
+ # create background if ID is specified
113
+ if id?
114
+ background = idElement.clone().addClass('background frame').html("&nbsp;")
115
+ @backgrounds.push(background)
116
+
117
+ # Search for footntes as formatted by Redcarpet.
118
+ #
119
+ # Each has an ID of the form `fn1`, which corresponds to the links in the
120
+ # footnote references.
121
+ #
122
+ # Returns a jQuery list of li elements and removes the generated footnotes from DOM
123
+ collectFootnotes: ->
124
+ @source.find('.footnotes:last-child')
125
+ .remove()