├── .gitignore
├── LICENSE
├── README.md
├── dist
└── felixhayashi
│ └── topstoryview
│ ├── config.js
│ ├── files
│ ├── layout.css
│ └── tiddlywiki.files
│ ├── plugin.info
│ ├── tiddlers
│ ├── config.tid
│ ├── license.tid
│ └── readme.tid
│ └── top_story_view.js
└── src
├── gulpfile.js
├── node_modules
└── plugins
└── felixhayashi
└── topstoryview
├── config.js
├── files
├── layout.scss
└── tiddlywiki.files
├── plugin.info
├── tiddlers
├── config.tid
├── license.tid
└── readme.tid
└── top_story_view.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # ignore all files with a leading dot
2 | .*
3 | !.gitignore
4 | !.git
5 |
6 | # ignore file in these paths
7 | src/note_modules/
8 | src/jsdoc/templates/docstrap/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014, Felix Küppers
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 |
8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 |
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TopStoryView
2 |
3 | TopStoryView is a TiddlyWiki plugin that adds another story view to tiddlywiki -- the *top* view.
4 |
5 | ## What it does
6 |
7 | It behaves like TiddlyWiki's *classic* story view but with a different scrolling strategy:
8 |
9 | * When opening or navigating to a tiddler, the site is scrolled until the title of the tiddler reaches the very top of the browser window.
10 | * Even the last tiddler is scrolled till it reaches the top. This works because the plugin adds an invisible spacer at the bottom so the tiddler can be scrolled to the top.
11 |
12 | ### What happens if a tiddler at the bottom of the story-river is removed?
13 |
14 | In this case the plugin will put the tiddler above into focus (unless the story river is empty).
15 |
16 | ## How it looks like
17 |
18 | The [demo site](http://tiddlymap.org) of TiddlyMap uses this story view.
19 |
20 | ## How to install and configure it?
21 |
22 | You can use this plugin by importing it (e.g. from the [TiddlyMap demo site](http://tiddlymap.org)) and switching your wiki's story view to *top* story view.
23 |
24 | 
25 |
26 | You can configure the plugin by opening TiddlyWiki's configuration and selecting TopStoryView in the plugin section.
27 |
28 | 
29 |
30 | ## Motivation
31 |
32 | See this discussion https://github.com/Jermolene/TiddlyWiki5/issues/1290
--------------------------------------------------------------------------------
/dist/felixhayashi/topstoryview/config.js:
--------------------------------------------------------------------------------
1 | /*\
2 |
3 | title: $:/plugins/felixhayashi/topstoryview/config.js
4 | type: application/javascript
5 | module-type: library
6 |
7 | @preserve
8 |
9 | \*/
10 | (function(){"use strict";exports.config={classNames:{storyRiver:"tc-story-river",backDrop:"story-backdrop",tiddlerFrame:"tc-tiddler-frame",tiddlerTitle:"tc-title"},references:{userConfig:"$:/config/topStoryView",focussedTiddlerStore:"$:/temp/focussedTiddler",refreshTrigger:"$:/temp/focussedTiddler/refresh"},checkbackTime:$tw.utils.getAnimationDuration()}})();
--------------------------------------------------------------------------------
/dist/felixhayashi/topstoryview/files/layout.css:
--------------------------------------------------------------------------------
1 | html .tc-story-river:after {
2 | content: "";
3 | display: block; }
4 |
--------------------------------------------------------------------------------
/dist/felixhayashi/topstoryview/files/tiddlywiki.files:
--------------------------------------------------------------------------------
1 | {
2 | "tiddlers": [
3 | {
4 | "file": "layout.css", "fields": {
5 | "title": "$:/plugins/felixhayashi/topstoryview/layout",
6 | "type": "text/vnd.tiddlywiki",
7 | "tags": [ "$:/tags/Stylesheet" ]
8 | }}
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/dist/felixhayashi/topstoryview/plugin.info:
--------------------------------------------------------------------------------
1 | {
2 | "title": "$:/plugins/felixhayashi/topstoryview",
3 | "description": "TopStoryView – Always scroll navigated tiddlers to the top",
4 | "author": "Felix Küppers",
5 | "version": "0.2.0",
6 | "released": "",
7 | "core-version": ">=5.1.5",
8 | "source": "https://github.com/felixhayashi/TW5-TopStoryView",
9 | "type": "application/json",
10 | "plugin-type": "plugin",
11 | "list": "Configuration License Readme",
12 | "dependents": ""
13 | }
--------------------------------------------------------------------------------
/dist/felixhayashi/topstoryview/tiddlers/config.tid:
--------------------------------------------------------------------------------
1 | title: $:/plugins/felixhayashi/topstoryview/Configuration
2 |
3 | Please see the [[GitHub page|https://github.com/felixhayashi/TW5-TopStoryView]] for more information on the options.
4 |
5 | Save and reload the wiki to activate changes.
6 |
7 |
8 |
9 | Scroll offset: |
10 | <$edit-text tiddler="$:/config/topStoryView" field="scroll-offset" tag="input" default="150px" /> |
11 |
12 |
--------------------------------------------------------------------------------
/dist/felixhayashi/topstoryview/tiddlers/license.tid:
--------------------------------------------------------------------------------
1 | title: $:/plugins/felixhayashi/topstoryview/License
2 |
3 | This code is released under the BSD license. For the exact terms visit:
4 |
5 | https://github.com/felixhayashi/TW5-TopStoryView/blob/master/LICENSE
--------------------------------------------------------------------------------
/dist/felixhayashi/topstoryview/tiddlers/readme.tid:
--------------------------------------------------------------------------------
1 | title: $:/plugins/felixhayashi/topstoryview/Readme
2 |
3 | Please visit the [[GitHub page|https://github.com/felixhayashi/TW5-TopStoryView]] for more information.
--------------------------------------------------------------------------------
/dist/felixhayashi/topstoryview/top_story_view.js:
--------------------------------------------------------------------------------
1 | /*\
2 | title: $:/plugins/felixhayashi/topstoryview/top.js
3 | type: application/javascript
4 | module-type: storyview
5 |
6 | Views the story as a linear sequence
7 |
8 | @preserve
9 |
10 | \*/
11 | (function(){"use strict";var t=require("$:/plugins/felixhayashi/topstoryview/config.js").config;var e="cubic-bezier(0.645, 0.045, 0.355, 1)";var i=function(e){this.listWidget=e;this.pageScroller=new $tw.utils.PageScroller;this.pageScroller.scrollIntoView=this.scrollIntoView;this.pageScroller.storyRiverDomNode=document.getElementsByClassName(t.classNames.storyRiver)[0];var i=$tw.wiki.getTiddler(t.references.userConfig);var o=i?i.fields:{};$tw.hooks.addHook("th-opening-default-tiddlers-list",this.hookOpenDefaultTiddlers);var r=parseInt(o["scroll-offset"]);this.pageScroller.scrollOffset=isNaN(r)?71:r;this.recalculateBottomSpace()};i.prototype.refreshStart=function(t,e){};i.prototype.refreshEnd=function(t,e){};i.prototype.hookOpenDefaultTiddlers=function(t){return t};i.prototype.navigateTo=function(t){var e=this.listWidget.findListItem(0,t.title);if(e===undefined)return;var i=this.listWidget.children[e];var o=i.findFirstDomNode();if(!(o instanceof Element))return;this.pageScroller.scrollIntoView(o)};i.prototype.insert=function(t){if(!t)return;var e=t.findFirstDomNode();if(!(e instanceof Element))return;this.startInsertAnimation(e,function(){this.recalculateBottomSpace()}.bind(this))};i.prototype.remove=function(t){if(!t)return;var e=t.findFirstDomNode();if(!(e instanceof Element)){t.removeChildDomNodes();return}var i=this.getLastFrame()===e;this.startRemoveAnimation(t,e,function(){t.removeChildDomNodes();this.recalculateBottomSpace();if(i){this.pageScroller.scrollIntoView(this.getLastFrame())}}.bind(this))};i.prototype.getLastFrame=function(){var t=this.listWidget.children[this.listWidget.children.length-1];return t?t.findFirstDomNode():null};i.prototype.recalculateBottomSpace=function(){var t=this.pageScroller.storyRiverDomNode;if(this.getLastFrame()){var e=this.getLastFrame().getBoundingClientRect();var i=window.innerHeight;if(e.height=1){l.cancelScroll();t=1}t=$tw.utils.slowInSlowOut(t);window.scrollTo(i.x+(s-i.x)*t,i.y+(a-i.y)*t);if(t<1){l.idRequestFrame=l.requestAnimationFrame.call(window,c)}};c()}};i.prototype.startInsertAnimation=function(t,i){var o=$tw.utils.getAnimationDuration();var r=window.getComputedStyle(t),n=parseInt(r.marginBottom,10),s=parseInt(r.marginTop,10),a=t.offsetHeight+s;setTimeout(function(){$tw.utils.setStyle(t,[{transition:"none"},{marginBottom:""}]);i()},o);$tw.utils.setStyle(t,[{transition:"none"},{marginBottom:-a+"px"},{opacity:"0.0"}]);$tw.utils.forceLayout(t);$tw.utils.setStyle(t,[{transition:"opacity "+o+"ms "+e+", "+"margin-bottom "+o+"ms "+e},{marginBottom:n+"px"},{opacity:"1.0"}])};i.prototype.startRemoveAnimation=function(t,i,o){var r=$tw.utils.getAnimationDuration();var n=i.offsetWidth,s=window.getComputedStyle(i),a=parseInt(s.marginBottom,10),l=parseInt(s.marginTop,10),c=i.offsetHeight+l;setTimeout(o,r);$tw.utils.setStyle(i,[{transition:"none"},{transform:"translateX(0px)"},{marginBottom:a+"px"},{opacity:"1.0"}]);$tw.utils.forceLayout(i);$tw.utils.setStyle(i,[{transition:$tw.utils.roundTripPropertyName("transform")+" "+r+"ms "+e+", "+"opacity "+r+"ms "+e+", "+"margin-bottom "+r+"ms "+e},{transform:"translateX(-"+n+"px)"},{marginBottom:-c+"px"},{opacity:"0.0"}])};exports.top=i})();
--------------------------------------------------------------------------------
/src/gulpfile.js:
--------------------------------------------------------------------------------
1 |
2 | /********************************************************************
3 | * Imports
4 | *******************************************************************/
5 |
6 | var gulp = require("gulp");
7 | var gutil = require("gulp-util");
8 | var sass = require("gulp-sass");
9 | var addsrc = require("gulp-add-src"); // https://github.com/gulpjs/gulp/issues/396
10 | var uglify = require("gulp-uglify");
11 | var esprima = require('gulp-esprima');
12 | var debug = require('gulp-debug');
13 | var gulpif = require('gulp-if');
14 | var argv = require('yargs').argv;
15 | var del = require("del"); // rm -rf
16 | //~ var jsdoc = require("gulp-jsdoc"); // not maintained!
17 |
18 | /********************************************************************
19 | * Tasks
20 | *******************************************************************/
21 |
22 | // clean will not be run twice, even though it is called as a dependency twice.
23 | // @see https://github.com/gulpjs/gulp/blob/master/docs/recipes/running-tasks-in-series.md
24 | gulp.task("clean", function (cb) {
25 |
26 | var options = { force: true };
27 | del([ "../dist/"/*, "../docs/"*/ ], options, cb);
28 |
29 | });
30 |
31 | gulp.task("compile", ["clean"], function () {
32 |
33 | // copy everything that doesn't need further processing
34 | gulp.src(["plugins/**", "!plugins/**/*.scss", "!plugins/**/*.js"])
35 | .pipe(gulp.dest("../dist/"));
36 |
37 | // compile styles
38 | gulp.src("plugins/**/*.scss")
39 | .pipe(sass({ style: "expanded" }))
40 | .pipe(gulp.dest("../dist/"));
41 |
42 | // uglify js
43 | gulp.src("plugins/**/*.js")
44 | .pipe(
45 | gulpif(argv.production,
46 | uglify({ compress: false, preserveComments: "some" })))
47 | .pipe(gulp.dest("../dist/"));
48 |
49 | });
50 |
51 | gulp.task("create docs", ["clean"], function () {
52 |
53 | // ...
54 |
55 | });
56 |
57 | gulp.task("default", ["compile", "create docs"]);
--------------------------------------------------------------------------------
/src/node_modules:
--------------------------------------------------------------------------------
1 | /usr/local/lib/node_modules
--------------------------------------------------------------------------------
/src/plugins/felixhayashi/topstoryview/config.js:
--------------------------------------------------------------------------------
1 | /*\
2 |
3 | title: $:/plugins/felixhayashi/topstoryview/config.js
4 | type: application/javascript
5 | module-type: library
6 |
7 | @preserve
8 |
9 | \*/
10 |
11 | (function(){
12 |
13 | /*jslint node: true, browser: true */
14 | /*global $tw: false */
15 | "use strict";
16 |
17 | /***************************** CODE ******************************/
18 |
19 | exports.config = {
20 |
21 | // Essential tiddlywiki classes that we depend on
22 | classNames: {
23 |
24 | // contains all tiddlers and some other stuff
25 | storyRiver: "tc-story-river",
26 |
27 | // back drop element of the sr
28 | backDrop: "story-backdrop",
29 |
30 | // wraps a tiddler and contains the title
31 | tiddlerFrame: "tc-tiddler-frame",
32 |
33 | // elements with this class contain the title
34 | tiddlerTitle: "tc-title"
35 |
36 | },
37 |
38 | references: {
39 |
40 | // User configuration
41 | userConfig: "$:/config/topStoryView",
42 |
43 | // This tiddler holds a reference to the currently focussed
44 | // tiddler. A tiddler is focussed if it was scrolled to
45 | // reach the top offset.
46 | focussedTiddlerStore: "$:/temp/focussedTiddler",
47 |
48 | // back drop element of the sr
49 | refreshTrigger: "$:/temp/focussedTiddler/refresh"
50 |
51 | },
52 |
53 | // Time after a scroll event that has to elapse before we
54 | // check which tiddler is actually focussed. This is necessary
55 | // to avoid updates that only result from scroll animations.
56 | checkbackTime: $tw.utils.getAnimationDuration()
57 |
58 | };
59 |
60 | })();
61 |
--------------------------------------------------------------------------------
/src/plugins/felixhayashi/topstoryview/files/layout.scss:
--------------------------------------------------------------------------------
1 | html .tc-story-river:after {
2 | content: "";
3 | display: block;
4 | }
--------------------------------------------------------------------------------
/src/plugins/felixhayashi/topstoryview/files/tiddlywiki.files:
--------------------------------------------------------------------------------
1 | {
2 | "tiddlers": [
3 | {
4 | "file": "layout.css", "fields": {
5 | "title": "$:/plugins/felixhayashi/topstoryview/layout",
6 | "type": "text/vnd.tiddlywiki",
7 | "tags": [ "$:/tags/Stylesheet" ]
8 | }}
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/src/plugins/felixhayashi/topstoryview/plugin.info:
--------------------------------------------------------------------------------
1 | {
2 | "title": "$:/plugins/felixhayashi/topstoryview",
3 | "description": "TopStoryView – Always scroll navigated tiddlers to the top",
4 | "author": "Felix Küppers",
5 | "version": "0.2.0",
6 | "released": "",
7 | "core-version": ">=5.1.5",
8 | "source": "https://github.com/felixhayashi/TW5-TopStoryView",
9 | "type": "application/json",
10 | "plugin-type": "plugin",
11 | "list": "Configuration License Readme",
12 | "dependents": ""
13 | }
--------------------------------------------------------------------------------
/src/plugins/felixhayashi/topstoryview/tiddlers/config.tid:
--------------------------------------------------------------------------------
1 | title: $:/plugins/felixhayashi/topstoryview/Configuration
2 |
3 | Please see the [[GitHub page|https://github.com/felixhayashi/TW5-TopStoryView]] for more information on the options.
4 |
5 | Save and reload the wiki to activate changes.
6 |
7 |
8 |
9 | Scroll offset: |
10 | <$edit-text tiddler="$:/config/topStoryView" field="scroll-offset" tag="input" default="150px" /> |
11 |
12 |
--------------------------------------------------------------------------------
/src/plugins/felixhayashi/topstoryview/tiddlers/license.tid:
--------------------------------------------------------------------------------
1 | title: $:/plugins/felixhayashi/topstoryview/License
2 |
3 | This code is released under the BSD license. For the exact terms visit:
4 |
5 | https://github.com/felixhayashi/TW5-TopStoryView/blob/master/LICENSE
--------------------------------------------------------------------------------
/src/plugins/felixhayashi/topstoryview/tiddlers/readme.tid:
--------------------------------------------------------------------------------
1 | title: $:/plugins/felixhayashi/topstoryview/Readme
2 |
3 | Please visit the [[GitHub page|https://github.com/felixhayashi/TW5-TopStoryView]] for more information.
--------------------------------------------------------------------------------
/src/plugins/felixhayashi/topstoryview/top_story_view.js:
--------------------------------------------------------------------------------
1 | /*\
2 | title: $:/plugins/felixhayashi/topstoryview/top.js
3 | type: application/javascript
4 | module-type: storyview
5 |
6 | Views the story as a linear sequence
7 |
8 | @preserve
9 |
10 | \*/
11 | (function(){
12 |
13 | /*jslint node: true, browser: true */
14 | /*global $tw: false */
15 | "use strict";
16 |
17 | /**************************** IMPORTS ****************************/
18 |
19 | var config = require("$:/plugins/felixhayashi/topstoryview/config.js").config;
20 |
21 | /***************************** CODE ******************************/
22 |
23 | var easing = "cubic-bezier(0.645, 0.045, 0.355, 1)"; // From http://easings.net/#easeInOutCubic
24 |
25 |
26 | var TopStoryView = function(listWidget) {
27 |
28 | this.listWidget = listWidget;
29 |
30 | this.pageScroller = new $tw.utils.PageScroller();
31 | this.pageScroller.scrollIntoView = this.scrollIntoView;
32 | this.pageScroller.storyRiverDomNode = document.getElementsByClassName(config.classNames.storyRiver)[0];
33 |
34 | // load user config
35 | var confRef = $tw.wiki.getTiddler(config.references.userConfig);
36 | var userConf = (confRef ? confRef.fields : {});
37 |
38 | // add hooks
39 | $tw.hooks.addHook("th-opening-default-tiddlers-list",
40 | this.hookOpenDefaultTiddlers);
41 |
42 | var scrollOffset = parseInt(userConf["scroll-offset"]);
43 | this.pageScroller.scrollOffset = (isNaN(scrollOffset) ? 71 : scrollOffset); // px
44 | //~ console.log("tsv:", "scrollOffset", this.pageScroller.scrollOffset);
45 |
46 | this.recalculateBottomSpace();
47 |
48 | };
49 |
50 | /**
51 | * This function is called by the list widget that is associated with
52 | * the wiki's story view on every refresh before it started to refresh itself
53 | */
54 | TopStoryView.prototype.refreshStart = function(changedTiddlers,changedAttributes) {
55 | // -
56 | };
57 |
58 | /**
59 | * This function is called by the list widget that is associated with
60 | * the wiki's story view on every refresh after it refreshed itself
61 | */
62 | TopStoryView.prototype.refreshEnd = function(changedTiddlers,changedAttributes) {
63 | // -
64 | };
65 |
66 | /**
67 | * Hook invoked by TW5 before tm-home is executed.
68 | */
69 | TopStoryView.prototype.hookOpenDefaultTiddlers = function(storyList) {
70 |
71 | return storyList;
72 |
73 | };
74 |
75 | /**
76 | * This function is called by the list widget that is associated with
77 | * the wiki's story view everytime the history changes. In other words,
78 | * if the history list tiddler (i.e usually $:/HistoryList) is contained
79 | * in the changedTiddlers hashmap, navigateTo will be called.
80 | *
81 | * If a list item has been newly inserted into the list, the `insert()`
82 | * method is called first by the widget and `navigateTo()` will be called
83 | * subsequently.
84 | */
85 | TopStoryView.prototype.navigateTo = function(historyInfo) {
86 |
87 | var listElIndex = this.listWidget.findListItem(0, historyInfo.title);
88 |
89 | if(listElIndex === undefined) return;
90 |
91 | var listItemWidget = this.listWidget.children[listElIndex];
92 | var targetElement = listItemWidget.findFirstDomNode();
93 |
94 | // when should this ever happen? Anyhow...
95 | if(!(targetElement instanceof Element)) return;
96 |
97 | this.pageScroller.scrollIntoView(targetElement);
98 |
99 | };
100 |
101 | /**
102 | * Function is called when a list item is inserted into the list
103 | * widget that is associated with the story river.
104 | *
105 | * The insert function does not call `scrollIntoView()` – this
106 | * is done later when TW invokes `navigateTo()`!
107 | */
108 | TopStoryView.prototype.insert = function(listItemWidget) {
109 |
110 | if(!listItemWidget) return;
111 |
112 | var targetElement = listItemWidget.findFirstDomNode();
113 | if(!(targetElement instanceof Element)) return;
114 |
115 | this.startInsertAnimation(targetElement, function() {
116 | // already recalculate (even if not visible yet) so navigateTo will be possible
117 | this.recalculateBottomSpace();
118 | }.bind(this));
119 |
120 | };
121 |
122 | /**
123 | * Function is called when an item is removed from the list widget
124 | * associated with the story river. At the time `remove()` is called,
125 | * the listItemWidget passed as parameter is still contained in
126 | * `this.storyList.children`. This means that `this.storyList.children.length`
127 | * will always (!) be greater 0.
128 | */
129 | TopStoryView.prototype.remove = function(listItemWidget) {
130 |
131 | if(!listItemWidget) return;
132 |
133 | var targetElement = listItemWidget.findFirstDomNode();
134 | if(!(targetElement instanceof Element)) {
135 | // when would this happen? anyhow...
136 | listItemWidget.removeChildDomNodes();
137 | return;
138 | }
139 |
140 | // needs to be calculated before remove animation
141 | var isLast = (this.getLastFrame() === targetElement);
142 |
143 | this.startRemoveAnimation(listItemWidget, targetElement, function() {
144 |
145 | // since it is not visible anymore, we can already remove the frame
146 | listItemWidget.removeChildDomNodes();
147 |
148 | // update state
149 | this.recalculateBottomSpace();
150 |
151 | if(isLast) {
152 | // focus new last frame
153 | //~ console.log("tsv:", "lastframe", this.getLastFrame());
154 | this.pageScroller.scrollIntoView(this.getLastFrame());
155 | }
156 |
157 | }.bind(this));
158 |
159 | };
160 |
161 | /**
162 | * Returns the last tiddler frame or null if no tiddler is contained
163 | * in the river.
164 | */
165 | TopStoryView.prototype.getLastFrame = function() {
166 |
167 | var lastItem = this.listWidget.children[this.listWidget.children.length-1];
168 | return (lastItem ? lastItem.findFirstDomNode() : null);
169 |
170 | };
171 |
172 | /**
173 | * Called after insert- or remove-animations finished when the last
174 | * tiddler changed. Recalculates the bottom space: In order to be
175 | * able to scroll the last frame to the top of the window,
176 | * we need to add a padding below.
177 | */
178 | TopStoryView.prototype.recalculateBottomSpace = function() {
179 |
180 | var sr = this.pageScroller.storyRiverDomNode;
181 |
182 | if(this.getLastFrame()) {
183 |
184 | var rect = this.getLastFrame().getBoundingClientRect();
185 | var windowHeight = window.innerHeight;
186 |
187 | if(rect.height < windowHeight) {
188 |
189 | // recalculate style
190 | sr.style["paddingBottom"] = (windowHeight - rect.height) + "px";
191 |
192 | return;
193 | }
194 |
195 | }
196 |
197 | // in any other case
198 | sr.style["paddingBottom"] = "";
199 |
200 | };
201 |
202 | /**
203 | * Starts an animated scroll to bring the specified element to the top
204 | * of the window's viewport.
205 | */
206 | TopStoryView.prototype.scrollIntoView = function(element) {
207 |
208 | if(this.preventNextScrollAttempt) {
209 | this.preventNextScrollAttempt = false;
210 | }
211 |
212 | if(!element) return;
213 |
214 | var duration = $tw.utils.getAnimationDuration();
215 | // Now get ready to scroll the body
216 | this.cancelScroll();
217 | this.startTime = Date.now();
218 | var scrollPosition = $tw.utils.getScrollPosition();
219 |
220 | // Get the client bounds of the element and adjust by the scroll position
221 | var clientBounds = element.getBoundingClientRect(),
222 | bounds = {
223 | left: clientBounds.left + scrollPosition.x,
224 | top: clientBounds.top + scrollPosition.y,
225 | width: clientBounds.width,
226 | height: clientBounds.height
227 | };
228 | // We'll consider the horizontal and vertical scroll directions separately via this function
229 | var getEndPos = function(targetPos,targetSize,currentPos,currentSize) {
230 | // If the target is above/left of the current view, then scroll to it's top/left
231 | if(targetPos <= currentPos) {
232 | return targetPos;
233 | // If the target is smaller than the window and the scroll position is too far up, then scroll till the target is at the bottom of the window
234 | } else if(targetSize < currentSize && currentPos < (targetPos + targetSize - currentSize)) {
235 | return targetPos + targetSize - currentSize;
236 | // If the target is big, then just scroll to the top
237 | } else if(currentPos < targetPos) {
238 | return targetPos;
239 | // Otherwise, stay where we are
240 | } else {
241 | return currentPos;
242 | }
243 | },
244 | endX = getEndPos(bounds.left,bounds.width,scrollPosition.x,window.innerWidth),
245 | endY = bounds.top - this.scrollOffset;
246 | // Only scroll if necessary
247 | if(endX !== scrollPosition.x || endY !== scrollPosition.y) {
248 | var self = this,
249 | drawFrame;
250 | drawFrame = function () {
251 | var t;
252 | if(duration <= 0) {
253 | t = 1;
254 | } else {
255 | t = ((Date.now()) - self.startTime) / duration;
256 | }
257 | if(t >= 1) {
258 | self.cancelScroll();
259 | t = 1;
260 | }
261 | t = $tw.utils.slowInSlowOut(t);
262 | window.scrollTo(scrollPosition.x + (endX - scrollPosition.x) * t,
263 | scrollPosition.y + (endY - scrollPosition.y) * t);
264 |
265 | if(t < 1) {
266 | self.idRequestFrame = self.requestAnimationFrame.call(window,drawFrame);
267 | }
268 | };
269 | drawFrame();
270 | }
271 | };
272 |
273 | /**
274 | * Animation for when a tiddler is inserted
275 | */
276 | TopStoryView.prototype.startInsertAnimation = function(targetElement, callback) {
277 |
278 | var duration = $tw.utils.getAnimationDuration();
279 |
280 | // Get the current height of the tiddler
281 | var computedStyle = window.getComputedStyle(targetElement),
282 | currMarginBottom = parseInt(computedStyle.marginBottom,10),
283 | currMarginTop = parseInt(computedStyle.marginTop,10),
284 | currHeight = targetElement.offsetHeight + currMarginTop;
285 | // Reset the margin once the transition is over
286 | setTimeout(function() {
287 | $tw.utils.setStyle(targetElement,[
288 | {transition: "none"},
289 | {marginBottom: ""}
290 | ]);
291 | callback();
292 | },duration);
293 | // Set up the initial position of the element
294 | $tw.utils.setStyle(targetElement,[
295 | {transition: "none"},
296 | {marginBottom: (-currHeight) + "px"},
297 | {opacity: "0.0"}
298 | ]);
299 | $tw.utils.forceLayout(targetElement);
300 | // Transition to the final position
301 | $tw.utils.setStyle(targetElement,[
302 | {transition: "opacity " + duration + "ms " + easing + ", " +
303 | "margin-bottom " + duration + "ms " + easing},
304 | {marginBottom: currMarginBottom + "px"},
305 | {opacity: "1.0"}
306 | ]);
307 | };
308 |
309 | /**
310 | * Animation for when a tiddler is removed
311 | */
312 | TopStoryView.prototype.startRemoveAnimation = function(listItemWidget, targetElement, callback) {
313 |
314 | var duration = $tw.utils.getAnimationDuration();
315 |
316 | // Get the current height of the tiddler
317 | var currWidth = targetElement.offsetWidth,
318 | computedStyle = window.getComputedStyle(targetElement),
319 | currMarginBottom = parseInt(computedStyle.marginBottom,10),
320 | currMarginTop = parseInt(computedStyle.marginTop,10),
321 | currHeight = targetElement.offsetHeight + currMarginTop;
322 | // Remove the dom nodes of the listItemWidget at the end of the transition
323 | setTimeout(callback,duration);
324 | // Animate the closure
325 | $tw.utils.setStyle(targetElement,[
326 | {transition: "none"},
327 | {transform: "translateX(0px)"},
328 | {marginBottom: currMarginBottom + "px"},
329 | {opacity: "1.0"}
330 | ]);
331 | $tw.utils.forceLayout(targetElement);
332 | $tw.utils.setStyle(targetElement,[
333 | {transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms " + easing + ", " +
334 | "opacity " + duration + "ms " + easing + ", " +
335 | "margin-bottom " + duration + "ms " + easing},
336 | {transform: "translateX(-" + currWidth + "px)"},
337 | {marginBottom: (-currHeight) + "px"},
338 | {opacity: "0.0"}
339 | ]);
340 |
341 | };
342 |
343 | exports.top = TopStoryView;
344 |
345 | })();
--------------------------------------------------------------------------------