alongslide 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +76 -4
- data/alongslide.gemspec +1 -1
- data/app/assets/javascripts/alongslide.coffee +4 -0
- data/app/assets/javascripts/fixedAspect.coffee +145 -0
- data/app/assets/javascripts/styles.coffee +57 -0
- metadata +3 -1
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Alongslide
|
2
2
|
|
3
|
-
Alongslide is a layout engine extending Markdown syntax
|
3
|
+
Alongslide is a layout engine extending Markdown syntax for use in Rails-based
|
4
|
+
web applications
|
4
5
|
|
5
6
|
It was developed by [Triple Canopy](http://canopycanopycanopy.com/) as a
|
6
7
|
text-editable yet sophisticated platform for producing long-form reading
|
@@ -8,6 +9,8 @@ content on the web.
|
|
8
9
|
|
9
10
|
Try the demo at [alongslide.com](http://alongslide.com) or [read some theory](http://canopycanopycanopy.com/contents/announcing_alongslide) underlying the project.
|
10
11
|
|
12
|
+
*Copyright 2013-2014 Canopy Canopy Canopy, Inc.*
|
13
|
+
|
11
14
|
## Dependencies
|
12
15
|
|
13
16
|
The Ruby backend uses Redcarpet to extract layout directives from the
|
@@ -15,10 +18,79 @@ Markdown, Treetop to parse their grammar, and HAML to render them into
|
|
15
18
|
templates.
|
16
19
|
|
17
20
|
The CoffeeScript/SASS frontend then scan the resulting HTML for layout
|
18
|
-
cues, using
|
19
|
-
skrollr to build transition animations and respond to user scrolling
|
21
|
+
cues, using a custom CSS Regions polyfill to flow the body text,
|
22
|
+
skrollr to build transition animations and respond to user scrolling,
|
23
|
+
plus jQuery and underscore.js as utilities.
|
24
|
+
|
25
|
+
## Installing
|
26
|
+
|
27
|
+
```bash
|
28
|
+
gem install alongslide
|
29
|
+
```
|
30
|
+
|
31
|
+
The gem includes both the Ruby and frontend components.
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
To use Alongslide, begin by setting up your view:
|
36
|
+
|
37
|
+
```erb
|
38
|
+
<article id="frames">
|
39
|
+
<div class="backgrounds"></div>
|
40
|
+
<div class="flow"></div>
|
41
|
+
<div class="panels"></div>
|
42
|
+
</article>
|
43
|
+
<div id="content" style="display: none">
|
44
|
+
<%= Alongslide::render(your_markdown_string) %>
|
45
|
+
</div>
|
46
|
+
```
|
47
|
+
|
48
|
+
Where `your_markdown_string` is a string of Alongslide-flavored Markdown.
|
49
|
+
|
50
|
+
Note that the Alongslide HTML is rendered into a hidden DOM element. The empty
|
51
|
+
elements above are there for Alongslide's JS to write into.
|
52
|
+
|
53
|
+
Then, once the page is loaded and ready, you can kick off the Alongslide render:
|
54
|
+
|
55
|
+
```javascript
|
56
|
+
MIN_WINDOW_WIDTH = 980
|
57
|
+
window.alongslide = new Alongslide({
|
58
|
+
source: '#content',
|
59
|
+
to: '#frames'
|
60
|
+
})
|
61
|
+
frameAspect = FixedAspect.prototype.fitFrame(MIN_WINDOW_WIDTH)
|
62
|
+
window.alongslide.render(frameAspect, function({
|
63
|
+
FixedAspect.prototype.fitPanels(frameAspect)
|
64
|
+
}))
|
65
|
+
```
|
66
|
+
|
67
|
+
For RegionFlow (our CSS Regions polyfill) to work properly, it is critical that
|
68
|
+
_all_ styles and webfonts be loaded in browser memory before the Alongslide
|
69
|
+
render begins. For that reason, it is recommend that you use the included
|
70
|
+
`Styles.prototype.doLoad()` utility to force CSS to load (by loading it via
|
71
|
+
Ajax and writing it to the DOM) and/or a tool such as
|
72
|
+
[webfontloader](https://github.com/typekit/webfontloader).
|
73
|
+
|
74
|
+
After the inital render, you can use `window.alongslide.refresh(frameAspect)`
|
75
|
+
to update just the scroll positioning (after a window resize for example) without
|
76
|
+
incurring the full cost of doing the layout over again.
|
77
|
+
|
78
|
+
### Creating custom templates
|
79
|
+
|
80
|
+
You may specify a directory in your application where you can specify your own
|
81
|
+
custom Alongslide templates. To do so, create an `alongslide.rb` in
|
82
|
+
`config/initializers` with the contents:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
Alongslide.configure do |config|
|
86
|
+
config.user_template_dir = Rails.root.join("app/views/alongslide")
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
Then you may create views in that directory using the YAML frontmatter format
|
91
|
+
described below.
|
20
92
|
|
21
|
-
|
93
|
+
If these template have JS/CSS components, those may go wherever you like.
|
22
94
|
|
23
95
|
## Syntax
|
24
96
|
|
data/alongslide.gemspec
CHANGED
@@ -0,0 +1,145 @@
|
|
1
|
+
#
|
2
|
+
# fixedAspect.coffee: Maintain fixed aspect ratio for given element.
|
3
|
+
#
|
4
|
+
# Copyright 2013 Canopy Canopy Canopy, Inc.
|
5
|
+
#
|
6
|
+
|
7
|
+
class window.FixedAspect
|
8
|
+
|
9
|
+
# Fit the content elements so that they maintain a consistent aspect ratio
|
10
|
+
# no matter the aspect ratio of the browser window.
|
11
|
+
#
|
12
|
+
# @param minWidthPx - minimum width in pixels to allow. Don't shrink smaller!
|
13
|
+
# @param marginTopPx - margin top in pixels to leave open.
|
14
|
+
#
|
15
|
+
# @return frame - bounding rect of content area, expressed as percentages
|
16
|
+
# relative to the browser window (0.0-1.0).
|
17
|
+
#
|
18
|
+
fitFrame: (minWidthPx, marginTopPx = 0) ->
|
19
|
+
FRAME_ASPECT = 1.6
|
20
|
+
FRAME_SELECTOR = '#frames > .flow > .frame'
|
21
|
+
|
22
|
+
frameAspect =
|
23
|
+
width: 1.0
|
24
|
+
height: 1.0
|
25
|
+
left: 0
|
26
|
+
top: 0
|
27
|
+
|
28
|
+
# aspect ratio calculations
|
29
|
+
window_aspect = $(window).width() / $(window).height()
|
30
|
+
if window_aspect > FRAME_ASPECT
|
31
|
+
frameAspect.width = FRAME_ASPECT / window_aspect
|
32
|
+
frameAspect.left = (1.0 - frameAspect.width) / 2
|
33
|
+
else
|
34
|
+
frameAspect.height = window_aspect / FRAME_ASPECT
|
35
|
+
frameAspect.top = (1.0 - frameAspect.height) / 2
|
36
|
+
|
37
|
+
# enforce margins, squeezing content down if necessary
|
38
|
+
marginTop = marginTopPx / $(window).height()
|
39
|
+
if frameAspect.top < marginTop
|
40
|
+
frameAspect.top = marginTop
|
41
|
+
scaleDown = (1.0 - frameAspect.top) / frameAspect.height
|
42
|
+
frameAspect.height *= scaleDown
|
43
|
+
frameAspect.width *= scaleDown
|
44
|
+
frameAspect.left = (1.0 - frameAspect.width) / 2
|
45
|
+
|
46
|
+
# enforce minWidthPx
|
47
|
+
frameWidthPx = frameAspect.width * $(window).width()
|
48
|
+
scaleUp = minWidthPx / frameWidthPx
|
49
|
+
if scaleUp > 1.0
|
50
|
+
frameAspect.height *= scaleUp
|
51
|
+
frameAspect.width *= scaleUp
|
52
|
+
|
53
|
+
# write to DOM, except for offset value for ALS
|
54
|
+
Styles::write 'debug-grid-aspect', Styles::formatPercentages('#als-debug-grid', frameAspect)
|
55
|
+
Styles::write 'frame-aspect', Styles::formatPercentages(FRAME_SELECTOR, _(frameAspect).omit('left'))
|
56
|
+
|
57
|
+
# write font-size / line-height
|
58
|
+
scalar = Math.max(1.0, (1.0 / scaleUp))
|
59
|
+
textStyles =
|
60
|
+
'font-size': parseInt($('#content-display').css('font-size')) * scalar
|
61
|
+
'line-height': parseInt($('#content-display').css('line-height')) * scalar
|
62
|
+
Styles::write 'frame-text', Styles::formatPixels('#frames', textStyles)
|
63
|
+
|
64
|
+
return frameAspect
|
65
|
+
|
66
|
+
# Go through all panels and manually turn absolute pixel-based values into
|
67
|
+
# percentage-based values.
|
68
|
+
#
|
69
|
+
# Do this after render, as once the operation is done, it can't be undone.
|
70
|
+
#
|
71
|
+
# Because of the complex interrelation between the CSS values (specified in a
|
72
|
+
# long, structural SASS doc) and the frame aspect value (computed on the fly),
|
73
|
+
# there's just no other way to do this but to get dirty and effectively write
|
74
|
+
# part of a CSS engine.
|
75
|
+
#
|
76
|
+
# Every `.panel` contains a `.contents`. The latter class has top/left/width/height
|
77
|
+
# specified--relative to the `.panel`. We re-derive the percentages from these
|
78
|
+
# relative values, because JS can't easily access CSS percentage values. And this
|
79
|
+
# method of dynamic re-percentifying seemed more sensible than putting large amounts
|
80
|
+
# of style data into JSON.
|
81
|
+
#
|
82
|
+
fitPanels: (frameAspect) ->
|
83
|
+
$('#frames > .panels > .panel').each (index, panel) =>
|
84
|
+
$panel = $(panel)
|
85
|
+
$contents = $panel.find('> .contents')
|
86
|
+
|
87
|
+
alignment = Alongslide::Layout::panelAlignment($panel)
|
88
|
+
|
89
|
+
innerFrame = $contents.data('innerFrame')
|
90
|
+
|
91
|
+
# If innerFrame isn't already stored, compute it now and store it.
|
92
|
+
unless innerFrame?
|
93
|
+
innerFrame = @innerFrame($panel)
|
94
|
+
$contents.data('innerFrame', innerFrame)
|
95
|
+
|
96
|
+
# .panel
|
97
|
+
#
|
98
|
+
panelFrame =
|
99
|
+
width: 1.0
|
100
|
+
height: 1.0
|
101
|
+
|
102
|
+
switch alignment
|
103
|
+
when "left"
|
104
|
+
panelFrame.width = frameAspect.left + (innerFrame.left + innerFrame.width) * frameAspect.width
|
105
|
+
when "right"
|
106
|
+
panelFrame.width = frameAspect.left + (1.0 - innerFrame.left) * frameAspect.width
|
107
|
+
when "top"
|
108
|
+
panelFrame.height = frameAspect.top + (innerFrame.top + innerFrame.height) * frameAspect.height
|
109
|
+
when "bottom"
|
110
|
+
# can't assume that frameAspect is vertically centered,
|
111
|
+
# due to `marginTop` (above). re-compute bottom.
|
112
|
+
frameAspectBottom = 1.0 - frameAspect.top - frameAspect.height
|
113
|
+
panelFrame.height = frameAspectBottom + (1.0 - innerFrame.top) * frameAspect.height
|
114
|
+
|
115
|
+
# .panel .contents
|
116
|
+
#
|
117
|
+
contentsFrame =
|
118
|
+
left: (frameAspect.left + innerFrame.left * frameAspect.width) / panelFrame.width
|
119
|
+
top: (frameAspect.top + innerFrame.top * frameAspect.height) / panelFrame.height
|
120
|
+
width: (innerFrame.width * frameAspect.width) / panelFrame.width
|
121
|
+
height: (innerFrame.height * frameAspect.height) / panelFrame.height
|
122
|
+
|
123
|
+
switch alignment
|
124
|
+
when "right"
|
125
|
+
# must use margin-left as skrollr uses left (so right-aligning won't work)
|
126
|
+
panelFrame['margin-left'] = (frameAspect.left + innerFrame.left * frameAspect.width)
|
127
|
+
contentsFrame.left = 0
|
128
|
+
when "bottom"
|
129
|
+
panelFrame.bottom = 0
|
130
|
+
contentsFrame.top = 0
|
131
|
+
|
132
|
+
# burn styles into CSS
|
133
|
+
#
|
134
|
+
$panel.css Styles::formatPercentageValues panelFrame
|
135
|
+
$contents.css Styles::formatPercentageValues contentsFrame
|
136
|
+
|
137
|
+
# Given a panel, determine its innerFrame.
|
138
|
+
#
|
139
|
+
innerFrame: ($panel) ->
|
140
|
+
$contents = $panel.find('> .contents')
|
141
|
+
|
142
|
+
left: parseInt($contents.css('left')) / $panel.width()
|
143
|
+
top: parseInt($contents.css('top')) / $panel.height()
|
144
|
+
width: $contents.width() / $panel.width()
|
145
|
+
height: $contents.height() / $panel.height()
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#
|
2
|
+
# styles.coffee: Load CSS styles manually to be sure when they're loaded.
|
3
|
+
#
|
4
|
+
# Copyright 2013 Canopy Canopy Canopy, Inc.
|
5
|
+
#
|
6
|
+
|
7
|
+
class window.Styles
|
8
|
+
urls: []
|
9
|
+
|
10
|
+
# Break open HTML snippet and store href attributes of contained <link>s.
|
11
|
+
#
|
12
|
+
loadLater: (snippet) ->
|
13
|
+
$(snippet).filter('link').each (index, link) =>
|
14
|
+
@urls.push $(link).attr('href')
|
15
|
+
|
16
|
+
# Load all CSS files, then execute callback.
|
17
|
+
#
|
18
|
+
# For now, load synchronously, to make sure CSS is loaded in the correct
|
19
|
+
# order. Async loading is a possible optimization, but would have to do
|
20
|
+
# more legwork to keep track of all requests.
|
21
|
+
#
|
22
|
+
doLoad: (callback) ->
|
23
|
+
styles = $('<style type="text/css"/>').appendTo('body')
|
24
|
+
$.each @urls, (index, url) =>
|
25
|
+
$.ajax url,
|
26
|
+
async: false
|
27
|
+
success: (data, textStatus, jqXHR) =>
|
28
|
+
styles.append data
|
29
|
+
callback()
|
30
|
+
|
31
|
+
# Util: CSS formatter
|
32
|
+
#
|
33
|
+
formatPercentages: (selector, values) ->
|
34
|
+
declarations = (_.map values, (value, key) => "#{key}: #{@formatPercentageValue(value)}").join(';')
|
35
|
+
return "#{selector} { #{declarations} }"
|
36
|
+
|
37
|
+
#
|
38
|
+
#
|
39
|
+
formatPercentageValues: (values) ->
|
40
|
+
_.object _(values).map (percent, key) => [key, @formatPercentageValue percent]
|
41
|
+
|
42
|
+
# Util.
|
43
|
+
#
|
44
|
+
formatPercentageValue: (value) ->
|
45
|
+
"#{value * 100}%"
|
46
|
+
|
47
|
+
# Util: CSS formatter
|
48
|
+
#
|
49
|
+
formatPixels: (selector, values) ->
|
50
|
+
declarations = (_.map values, (value, key) -> "#{key}: #{value}px").join(';')
|
51
|
+
return "#{selector} { #{declarations} }"
|
52
|
+
|
53
|
+
# Util: set CSS by writing <style> to the DOM.
|
54
|
+
#
|
55
|
+
write: (id, styles) ->
|
56
|
+
$("style##{id}").remove()
|
57
|
+
$('<style type="text/css" id="'+id+'"/>').append(styles).appendTo('body')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: alongslide
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -92,6 +92,8 @@ files:
|
|
92
92
|
- app/assets/javascripts/alongslide/layout.coffee
|
93
93
|
- app/assets/javascripts/alongslide/parser.coffee
|
94
94
|
- app/assets/javascripts/alongslide/scrolling.coffee
|
95
|
+
- app/assets/javascripts/fixedAspect.coffee
|
96
|
+
- app/assets/javascripts/styles.coffee
|
95
97
|
- app/assets/stylesheets/alongslide.sass
|
96
98
|
- app/views/panel/panel.haml
|
97
99
|
- app/views/panel/unpin.haml
|