Is your feature request related to a problem? Please describe.
Currently, the only way to navigate the story river, to edit a tiddler etc., is using the mouse. We could make this more keyboard friendly
Describe the solution you'd like
A currently viewed tiddler should be stored in a state tiddler and emphasized visually so that the user knows which one it is. Then there should be some keyboard shortcuts that allow
Thanks @BurningTreeC I was thinking it might be good to try to tackle this for v5.1.24, do you think that's something you'd like to try?
A currently viewed tiddler should be stored in a state tiddler and emphasized visually so that the user knows which one it is.
I wondered if we might reuse the tiddler at the top of the list in $:/HistoryList; the implication would be that pressing cursor down to reach a new tiddler would actually navigate to it.
I wondered if we might reuse the tiddler at the top of the list in $:/HistoryList; the implication would be that pressing cursor down to reach a new tiddler would actually navigate to it.
@Jermolene - Is that the tiddler stored in the current-tiddler field of $:/HistoryList?
@Jermolene - Is that the tiddler stored in the
current-tiddlerfield of$:/HistoryList?
Yes; the subtlety is that that field is actually a convenient cache for the topmost item in the history list:
https://github.com/Jermolene/TiddlyWiki5/blob/master/core/modules/story.js#L100
So, one must update the list and the current-tiddler field consistently.
So, one must update the list and the current-tiddler field consistently.
@Jermolene - using the action-navigate widget this would happen automatically, right?
Hi @BurningTreeC
using the action-navigate widget this would happen automatically, right?
Yes indeed.
@Jermolene - here's a prototype Demo using wikitext only
Excellent stuff @BurningTreeC. Some thoughts:
Despite all of those provisos, what you've got is very usable and surprisingly discrete and so I do wonder if it might be worth releasing it in its present form as a plugin while we chew over getting it into the core.
A few quick thoughts:
I agree that making this available as a plugin would be helpful in making sure we get the UX correct and consistent.
+1 for a plugin first approach
@BurningTreeC
Great stuff. A minor suggestion
Is it possible to have alt+Home to go to first tiddler and alt+End to go to the last tiddler in the story river? This is similar in Excel to go to last cell or first cell in a long column!
@kookma
Is it possible to have alt+Home to go to first tiddler and alt+End to go to the last tiddler in the story river? This is similar in Excel to go to last cell or first cell in a long column!
Yes that's possible
@Jermolene
I guess you used alt-up/down so that it's possible to navigate to a different tiddler even if the focus is in the search box? I wonder if we could use plain up/down by inducing the search box to pass along keypresses that it doesn't need (I suppose that would mean introducing an action-keyboardevent widget)
Yes that's right. I have no idea how to implement such an achtion-keyboardevent widget though
I tried putting two adjacent tiddlers into edit mode and seeing if I could use the keyboard to navigate between editing their respective text fields. The problem is that pressing tab isn't relative to the current tiddler. I wonder if when navigating we should also set the browser focus to the newly navigated tiddler
I could do so using the tm-focus-selector message
Which raises a deeper question: whether we need to integrate with the browser focus mechanism in general, so that we use the browsers own highlighting for the item with focus. That would of course in turn raise our old bugbear about needing to maintain the currently highlighted item over a refresh cycle. I think that's overdue, and we've had quite a long time to think about it!
I still have a hard time imagining what the browser focus is and how to gain advantage from it
Despite all of those provisos, what you've got is very usable and surprisingly discrete and so I do wonder if it might be worth releasing it in its present form as a plugin while we chew over getting it into the core.
Thanks! Should it be a tiddlywiki core plugin?
Thanks! Should it be a tiddlywiki core plugin?
I don't think so. We can't remove it anymore.
I don't think so. We can't remove it anymore.
Ok then, I'll release this as an unofficial plugin
@Jermolene - this Browser - focus thing, how should that be implemented?
A problem that arises when we use the Up/Down arrow keys for tiddler-navigation is, that the normal scrolling using those keys wouldn't work anymore
A problem that arises when we use the Up/Down arrow keys for tiddler-navigation is, that the normal scrolling using those keys wouldn't work anymore
Good point. Perhaps along with focus preservation we also need to add scroll position preservation, and then handle keyboard scrolling ourselves. That does seem overkill though.
I'm now wondering if we shouldn't just merge @BurningTreeC's current solution, let people try it out, and iterate from there.
Good point. Perhaps along with focus preservation we also need to add scroll position preservation, and then handle keyboard scrolling ourselves. That does seem overkill though.
@Jermolene - how would focus preservation look like? I'm not able to focus a tiddler...
@Jermolene I strongly recommend we let people try this as a plugin first.
I can already forsee usability issues. Many users, including myself, use rich interfaces in the view template for interacting with and even editing their wikis. However, with the current implementation under discussion here, this is a disconnect between the tiddler one might be working in, and the highlighted one.
Example: Three tidders in a story: A. B and C in that order. If A is the last opened (and thus highlighted) but I have scrolled down to B and am working in that tiddler, I would reasonably expect further navigation to start from B but that will not be the case. If however, clicking on a tiddler in the story set that as the highlighted/focused one, this would not be an issue.
Furthermore it might be reasonable to expect that as a tiddler scrolls out of view, the highlight moves to the tiddler before or after it depending on the direction of scrolling.
I would also like for any such implementation to be adaptable for a multi-story setup as well.
So if were to merge this as is, at the very least there should be a simple config option to disable it entirely as in many cases it wont make sense.
@BurningTreeC the tiddler div's in the story would need a tabindex attribute in order to receive focus. However I think what is needed in TW is to remember the exact element that has focus and restore that after a refresh. The element with focus can be retrieved via document.activeElement
Just for reference and completeness, there is an older implementation of similar features here, though with similar caveats:
the tiddler div's in the story would need a tabindex attribute in order to receive focus. However I think what is needed in TW is to remember the exact element that has focus and restore that after a refresh. The element with focus can be retrieved via document.activeElement
I see @saqimtiaz , thanks!
I think "focused" and "currentTiddler" in HistoryList are completely different things. .. I want to change focus of a tiddler independently. Eg: I want to scroll up and down and set some "threshold pixels" when a tiddler becomes focused as shown in the viedeo here, where the TOC updates focus in relation to the story.
So if a tiddler reaches eg: the upper 3rd of the screen it should get the focus. This behaviour needs to be true if I use the up/down keys.
The problem with most of this behaviour is, that the storyview.js is responsible for story animation and _not_ the navigator.js. storyview also the only place that knows, which tiddler is shown. .. Have a closer look to the "stacked" storyview in the https://tiddlywiki.com/prerelease/ ...
So changes as this one need to be plugins first. I think it's far away from being generically useful.
@pmario agreed, and this reminds of me something similar that has been done in one of the dynaview plugins demos, highlighting a tiddler title in the open tab in response to scroll position. Could be something to build on there.
I am all for being able to navigate the story river via keyboard, but it needs to be consistent with other navigation methods such as scrolling and take into account which element has focus as that can change independent of scrolling or keyboard navigation.
So, I'd like to gather a list of things here that we need to come closer to a real keyboard navigatable story river
focus EventListener on each Tiddler that sets a "$:/state/focused-tiddler" to the currentTiddler variablecurrentTiddler variableI think the key aspect of the integration between keyboard navigation and browser focus is that when I click within a focusable element within a tiddler (whether it is in edit or view mode), the "tiddler focus" should move to that tiddler so that subsequent keyboard shortcuts will apply to it.
@pmario raises the idea that the tiddler focus should automatically be adjusted as the story river is scrolled. I can understand the attraction of the approach, but I'm not sure that it would be universally useful. For example, I often use a little "trick" whereby while I'm editing a tiddler and have the focus within it, I'll scroll down the story river to consult another tiddler. Then I can just start typing in order to automatically scroll back to the tiddler that I'm editing. My feeling is that the focussed tiddler should be under direct user control.
I think the key aspect of the integration between keyboard navigation and browser focus is that when I click within a focusable element within a tiddler (whether it is in edit or view mode), the "tiddler focus" should move to that tiddler so that subsequent keyboard shortcuts will apply to it.
@Jermolene we are on the same page here. I think this is the critical part missing from the current implementation. It is also worth considering whether we should introduce a new "focused-tiddler" state that is maintained, instead of re-using the current-tiddler from the historylist.
@Jermolene @saqimtiaz
The different approach like you mentioned before would be to set the tabindex for tiddlers so that they are focusable
Then we can query for the focused tiddler with .tc-tiddler-frame:focus-within or .tc-tiddler-frame:focus
A new action-widget could probably do that... That action-widget could then set the focus to the next/previous tiddler, accept actions on the focused tiddler and so on... without the need for a state tiddler that saves the focused tiddler
@BurningTreeC a CSS based solution is appealing, however I think also saving the value to a state tiddler will allow more flexibility in terms of interactions with plugins.
@saqimtiaz I guess saving the value would be no problem
@Jermolene we are on the same page here. I think this is the critical part missing from the current implementation. It is also worth considering whether we should introduce a new "focused-tiddler" state that is maintained, instead of re-using the current-tiddler from the historylist.
I would be very much in favour of a new focused-tiddler field in the $:/HIstoryList ...
@Jermolene .. I think this "trick" also works with the existing demo from BRC see: http://tiddlywiki-keyboard.tiddlyspot.com/
I wouldn't want to change that behaviour. .. The "edit-focus" imo won't be changed by the focused-tiddler field.
@BurningTreeC is proposing to piggy-back on the browser focus mechanism: when we need to know which tiddler has the focus we look for the currently focussed element in the DOM and then we navigate up through the DOM to find the containing tiddler. When we set the need to set the focus to a tiddler we instruct the browser to set the focus to the tiddler representation in the DOM.
@saqimtiaz and @pmario raise that it would be attractive to maintain the title of the focused tiddler in a state tiddler; the implication would presumably be that modifying that tiddler would be the means to set the focus to a different tiddler. There still seem to be inconsistencies here: would moving the tiddler focus also move the browser focus and vice versa?
I feel that @BurningTreeC's focus-based solution works more cleanly with existing browser behaviour, but I accept that using tm-focus-selector isn't as convenient as setting a state tiddler to a particular title.
The thing with both proposals that is perhaps missing is the new mechanism we have discussed to maintain the browser focus over refresh cycles -- @BurningTreeC I think we had a ticket for that?
The thing with both proposals that is perhaps missing is the new mechanism we have discussed to maintain the browser focus over refresh cycles -- @BurningTreeC I think we had a ticket for that?
I don't remember if we have a ticket for that @Jermolene
I tried adding a focus eventListener on each tiddler through the classic storyview, which sets a $:/state/focused-tiddler to the focused tiddler-title, which works well. The missing part is adding such an EventListener to the Startup tiddlers, too and I don't know how to do that.
The second part of this mechanism is that the editor engines also set that $:/state/focused-tiddler to the current storyTiddler. Also the buttons on click should do so.
The question now is, how navigation should work. It should allow to navigate between two stories, too, like @saqimtiaz mentioned. Therefor an action-keyboardevent widget could query for all tiddlers, find the focused one and focus the next/previous one, depending on the given parameters. Would you @saqimtiaz have a simple two-story prototype to play with?
The next question is, how cancelling/deleting/saving a tiddler is handled by the focus mechanism. When a tiddler is cancelled and the tiddler with the draft-title exists, that tiddler should get focus. If that tiddler doesn't exist, a next tiddler should get focus (or a previous one if there's no next tiddler). For deleting and saving it's a similar question.
These are just some thoughts, maybe the whole mechanism should be totally different
@BurningTreeC ... I think your concept to complicated. IMO there is no need to add a new event handler. We already have the default one, that is activated as soon as someone modifies the $:/HistoryList tiddler. ... So it can be used to fire "multi story" events.
The $:/StoryList tiddler contains the list with all tiddlers that are active. So it can be used for moving the focus from 1 tiddler to the next one.
So if $:/HistoryList has a field named: focused-tiddler and you change it, the existing event mechanism takes over.
Since there can be several $:/HistoryLists there is no need to add additional code to manage $:/state/focused-tiddler handling. It is done automatically.
I'll publish a proof of concept ASAP.
The edition in the video I did mention already has most of the stuff needed. .. We need to modify navigator.js a little bit. So that the link-events add the "active story" info. We need to modify linkcatcher, so we can use a lightwight widget instead of the ultra heavy <$navigator ... If we want to.
And all the storyview's may need some modification in a second iteration. The code changed imo is very little and it should give us a generic way to implement multi-column stories, where ever we want. eg: A story in a tiddler, a story in the sidebar, 2 stories in the main area. ... IMO it should be simple.
@pmario thanks for answering. I'm curious about your proof of concept :) :+1:
@pmario - What I don't get is how to modify the $:/HistoryList e.g. with a keyboard shortcut, so that the event listener catches in... And where to handle that navigation
@pmario - What I don't get is how to modify the $:/HistoryList e.g. with a keyboard shortcut, so that the event listener catches in... And where to handle that navigation
The POC won't handle the keyboard shortcuts. ... but it should lay the background to make it easier. ... I didn't have a look at the keyboard widget yet
The POC won't handle the keyboard shortcuts. ... but it should lay the background to make it easier. ... I didn't have a look at the keyboard widget yet
I thought modifying the $:/HistoryList using keyboard shortcuts would have any effect
I thought modifying the $:/HistoryList using keyboard shortcuts would have any effect
Modifying the $:/HistoryList has an effect. Always. ... It THE tiddler that starts the story animation mechanism.
@pmario
Modifying the $:/HistoryList has an effect. Always. ... It THE tiddler that starts the story animation mechanism.
In my experiments only modifying its text field has an effect. Modifying its current-tiddler field has no effect
You are right. It's the tm-navigate message which triggers the process. -> It is handled by the navigator -> which sets StoryList and HistoryList. -> Changing the HistoryList triggers a tiddler "change" event. -> This "special" change event is handled by the list-widget -> list widget has a handleHistoryChanges() function, which calls -> storyview.navigateTo() function. .. The storyview does the animation using tragetElement.scrollIntoView()
That's why it's so important to do it right. ... In the example above is 1 important element missing. The linkcatcher-widget, which can also modify and influence the whole chain in different places.
linkcatcher is a lightweight widget, which allows us to modify any click-event. Otherwise we would need to use the navigator-widget, which is heavy weight.
thanks for the explanation @pmario
I'm curious how you would implement this
I just had a look at you demo wiki. I think the navigation can be done much easier. IMO you may not need a navigator instance. Setting / using the right tv-story-list and tv-history-list should do the job. ... At least with the mods I have in mind. ... Using variables will also make it multi-story aware.
@BurningTreeC Where is the keyboard handler for "global" shortcuts? Is it part of the root-widget?
@pmario
Where is the keyboard handler for "global" shortcuts? Is it part of the root-widget?
It's part of $:/core/modules/keyboard.js
Or do you mean where the eventListener is added?
Ahh, OK it's the root widget. So that's the reason why we have to use the navigator. ... atm. ... But it will need a state-tiddler which story is active at the moment. So we may need a "switch story" keyboard shortcut too. ... This can be a single tiddler, because only 1 story can have focus at the time.
Ahh, OK it's the root widget. So that's the reason why we have to use the navigator. ... atm
yes, that's the reason why
But it will need a state-tiddler which story is active at the moment. So we may need a "switch story" keyboard shortcut too. ... This can be a single tiddler, because only 1 story can have focus at the time.
What I have in mind is a different approach:
tc-tiddler-frameoffsetTop propertyIt's complicated, yes, but it has its advantages
What I have in mind is a different approach:
IMO that's not possible, since the offsetTop is only valid for classic ... storyviews. If you have look at the stacked storyview in the prerelease, you'll see that these hardcoded calculations break down very easily.
That's also a problem I had with the "scroll into view" detection. We will need to have a function in the different storyview files, that will allow us to query some info about "visible-current-tiddler" and may be some other stats.
IMO that's not possible, since the offsetTop is only valid for classic ... storyviews
Hmm... yes you're right
We will need to have a function in the different storyview files, that will allow us to query some info about "visible-current-tiddler" and may be some other stats.
My storyview adds an eventListener to the tiddler that gets inserted and when that tiddler gets focus (tiddlers have tabindex 1 in my templates) it sets the $:/state/focused-tiddler to the focused tiddlers' title
To make navigation multi-story aware maybe we could set a $:/config/StoryList tiddler to the title of the current storyList, set a $:/config/HistoryList tiddler to the title of the current historyList and use those config tiddlers in the corresponding keyboard shortcuts within the navigator widget like:
<$navigator story={{$:/config/StoryList}} history={{$:/config/HistoryList}}>
...
</$navigator>
we could use keyboard shortcuts to switch between different stories
The navigator widget sets tv-story-list and tv-history-list variables. They are scoped and should be used by the linkcatcher and the navigator to know what to do. ... That's part of my POC code.
As I wrote. The <$navigator widget should only (needs to be) be instantiated once.
back to: https://github.com/Jermolene/TiddlyWiki5/blob/bbdd12cffdeeaffbcc9cc84dd5d95d9c0346e164/core/modules/startup/startup.js#L112-L118
That's a problem. First you need to create global shortcuts, that have to use the <$navigator widget to work. This behavior causes support and slows down devs, because we also forget about it (may be BTC is an exception here ;) .. And it moves the control of the functionality from "advanced users" to "core developers" ...
Jeremy mentioned this here on gh already.
see: #3967 "Implement more of TiddlyWiki's black box logic as customisable action strings"
So may be this is a chance to fix it.
In my imagination there is only 1 <$navigator widget in the PageTemplate. This widget implements the "heavy weight" functionality. ... As it does already ;)
<$linkcatcher can "redefine" tv-history-list and tv-story-list which is added to the tm-navigate event. <$navigator needs to deal with this info and use the right "storys". ... that's part of my POC code too. .. It modifies "just" a view lines of existing code.
@pmario - where can we see your POC code?
There is 1 point which Jeremy didn't like in the first run. Some "save config tiddlers" eg:
title: $:/core/save/all
[is[tiddler]] -[prefix[$:/state/popup/]] -[[$:/HistoryList]]
needs to be
[is[tiddler]] -[prefix[$:/state/popup/]] -[prefix[$:/HistoryList/]]
See the prefix ... because we need to deal with many different HistoryLists, which shouldn't be part of the saved file.
It should be possible to use the $(publishFilter)$ for this. .. once V5.1.23 is active, where this variable should work. see: TW prerelease - save all tiddler
Edit:
That's not part of my POC yet, since I had this idea as I did write this response ;)
@pmario - where can we see your POC code?
I'm preparing a PR atm. which contains several commits, so devs can see, what's going on. ... I needed to merge prerelease master first, which caused some merge conflicts.
I'm preparing a PR atm. which contains several commits, so devs can see, what's going on. ... I needed to merge prerelease master first, which caused some merge conflicts.
Ah I see. A proof-of-concept Demo page would be great to play with if you find the time @pmario
Ah I see. A proof-of-concept Demo page would be great to play with if you find the time @pmario
;) .. Yea, Today I woke up early, since I had so many new ideas, how everything could work together nicely. ... using "out of the box" elements only. Without any hacks.
... BUT on the way debugging the "event flow" I found out, that my add-blocker adds code to a file TW, which shouldn't be there at all :/
... This stuff really slows me down :/
;) .. Yea, Today I woke up early, since I had so many new ideas, how everything could work together nicely. ... using "out of the box" elements only. Without any hacks.
@pmario I'm very curious about your ideas!
... BUT on the way debugging the "event flow" I found out, that my add-blocker adds code to a file TW, which shouldn't be there at all :/
... This stuff really slows me down :/
://
@pmario any progress on this from your side? :o)
Hi @BurningTreeC :) kind of. ... I did change so much code, that I'm almost sure it will never get merged.
The "curx" is generic multi-story handling, which is part of the core concept _but_ isn't part of the code yet.
The general "internals" of a story are like this:
From PageTemplate
<$navigator story="$:/StoryList" history="$:/HistoryList" openLinkFromInsideRiver={{$:/config/Navigation/openLinkFromInsideRiver}} openLinkFromOutsideRiver={{$:/config/Navigation/openLinkFromOutsideRiver}} relinkOnRename={{$:/config/RelinkOnRename}}>
As you can see, there are 5 parameters, which define 1 story context. story, history, openLinkFromInsideRiver, openLinkFromOutsideRiver, relinkOnRename. All those parameters need/can be defined on a "per story" basis.
I do write all of them to tv-xxxx variables now. I call those 4 parameters story context. The <$link widget includes this info into the tm-navigate event (now). So the navigator can see it. .. It's possible to send story and history context on a "per link" basis.
To be able to handle several links the same way we can use the <$linkcatcher widget, which now also lets us (re)define the story context
Linkcatcher is lightweight and used to be able to create hacks like in the TOC macro Tabbed external / internal, which would need to use <navigator widget otherwise. ... (Which imo would have many advantages!!!)
Linkcatcher can now be properly "nested" ...
The problem is, that the story context object needs to be passed around from link-widget-event -> navigator.js event handling -> story.js story handling. ... (That's simple, but it shows, that there is a conceptual problem)
BUT
all the other tm-xxx messages (in navigator.js) don't know about the story context. So it's not possible atm to handle eg: tm-close-tiddler message properly, if you want to do it in the <linkcatcher context. ... You must use <$navigator widget to get it right. ... Similar to "global keyboard" shortcuts.
Since navigator.js and story.js contain quite some "duplicated code" and don't have a proper "separation of concerns", I'm rewriting them atm.
Which should lead to less code in navigator.js and will lead to a little bit more code in story.js.
Once the "separation of concerns" is done, we need to move global keyboard handling out of the core startup (root widget) into the PageTemplate - navigator scope. So it will be easy for users to define multiple "per story" keyboard shortcuts.
... I'm sorry .. It's more work as I thought, if I want to do it right. ... I did create a test environment already, that will allow me to test for regressions with the new code. ...
@pmario - maybe you could post a Demo?
@saqimtiaz .... raise that it would be attractive to maintain the title of the focused tiddler in a state tiddler; the implication would presumably be that modifying that tiddler would be the means to set the focus to a different tiddler. There still seem to be inconsistencies here: would moving the tiddler focus also move the browser focus and vice versa?
I feel that @BurningTreeC's focus-based solution works more cleanly with existing browser behaviour, but I accept that using
tm-focus-selectorisn't as convenient as setting a state tiddler to a particular title.
I think using tm-focus-selector to set the focused tiddler is OK.
The desire for saving that title in a state tiddler is to me more about being able to query it from filters, rather than set the focus via the state tiddler. For example, there might be UI controls that target the focused tiddler. Alternatives could be a global variable or a JavaScript macro that returns the focused tiddler title.
The desire for saving that title in a state tiddler is to me more about being able to query it from filters, rather than set the focus via the state tiddler.
@saqimtiaz - that's exactly the usecase I have in mind
Alternatives could be a global variable or a JavaScript macro that returns the focused tiddler title.
Any alternative is welcome :smile:
Most helpful comment
@Jermolene - here's a prototype Demo using wikitext only