TiddlyWiki / TiddlyWiki5

A self-contained JavaScript wiki for the browser, Node.js, AWS Lambda etc.
https://tiddlywiki.com/
Other
8.07k stars 1.19k forks source link

Tech specifications for proposal: "JavaScript anchors" to replace missing HTML anchor functionality. #2271

Open BlakeTNC opened 8 years ago

BlakeTNC commented 8 years ago

This proposal is an offshoot of the issue https://github.com/Jermolene/TiddlyWiki5/issues/1783 . However, I thought the proposal was different enough from the normal clamor for "Tiddler internal HTML anchors", that it deserved its own issue page. I also wanted to separate technical discussion from philosophy conversations. What I'm proposing is a replacement for the HTML anchor functionality, implemented with JavaScript. The full details and design specs are pasted below.

Original proposal message:

Hello all, I may have a plausible technical solution for internal links. Here is the problem defined by Jermolene.

"@Jermolene wrote: Hi @rcrath the problem isn't that I have a philosophical dislike for internal links. I also can't see a plausible implementation. As you've discovered, naive implementations clash with TiddlyWiki's existing permalink handling."

I researched and found a way to "re-implement" internal anchors without using the normal anchor mechanism (Without using the pound symbol #, and without changing the current browser URL.)

A normal HTML anchor allows you to click on a text link, and the browser window will adjust the scrollbar until a text target is at the top of the browser window. There is a JavaScript method to accomplish this same task, called scrollIntoView(). I made a small HTML file to demonstrate how this method can be employed to re-implement the "anchor" functionality. The example is pasted below. To see how it works, copy the HTML text into a text file, save it on your computer with the extension HTML, and open that file in your browser. From my research, the used JavaScript method is implemented by all major browsers.

----------  Here is an HTML file which demonstrates a working JavaScript based "anchor". ---------- 
<html><head><script type="text/javascript">
function ScrollRed (alignToTop) { var redText = document.getElementById ("redText"); redText.scrollIntoView (alignToTop); }
</script></head><body><br/><br/><br/>
<a href="javascript:void(0);" onClick="ScrollRed (true);" >Scroll the red text into the top of visible area!</a><br/><br/><br/>
Or the same thing with a button:<br/><button onclick="ScrollRed (true);">Scroll the red text into the top of visible area!</button>
<BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/>
<BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/>
<BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/>
<span id="redText" style="color:red">Red text for scroll test.</span>
<BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/>
<BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/>
<BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/><BR/>
<BR/><BR/><BR/></body></html>
----------------------------------------------------------------------------------------------------

Here is one design for how this feature could be implemented into the TiddlyWiki core.

Limitations: Such JavaScript anchors could not be used to create "anchored bookmarks", because they're not part of the page URL. The anchor links would only work from within TW.

Internal Table of Contents: Once internal anchors are implemented, it would be possible to also implement an "internal table of contents" macro. This macro would automatically insert a table of contents at the top of a Tiddler, based on any headers that exist in the same Tiddler. Clicking on any item in the table of contents would scroll to that header in the Tiddler.

Warm regards BlakeTNC

Algorithm for generating the necessary header and anchor target identifiers:

I've been thinking about the implementation details of the proposal, and I want to address a particular design issue. (Original proposal here: https://github.com/Jermolene/TiddlyWiki5/issues/1783#issuecomment-179561486 )

That the below text will not make much sense unless you have read the original proposal. (It still may not make much sense unless you are a programmer!)

The biggest issue I see in implementing "JavaScript anchors" is how to choose the ID strings for the headers (or manually inserted anchor targets). The ID strings will need to meet various specifications and restrictions. These include:

Considering only the restrictions discovered so far, the actual restrictions on ID strings for this usage appear to be few (from the current research), but I would say it would be common programmer sense to avoid things like new lines, angle brackets, quotation marks, and just to be conservative, probably to avoid special characters in general. It's also worth noting that although "unique" IDs are requested by HTML 5 and JavaScript, they are not required. If getElementById() is called on a non-unique ID, it will just find the first one on the page. Therefore, I think the burden of uniqueness should be placed on the user... It is their job to make sure their header text or manual anchor target names are reasonably unique. Although it is the programmers job to make sure that anchor id tags do not conflict with unrelated id tags used by TW.

Based on all those requirements, I would suggest the following deterministic algorithm for creating ID strings from header strings: (Java pseudocode is used to describe the algorithm.)

You'll need a JavaScript hashcode function to implement the below algorithm. A good, short one is found at this link. http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/

//// A deterministic anchor ID tag generation algorithm.

// Start with the original header text, or the original anchor-target macro text. 
// (Manual anchor targets are described in the original proposal.)
String identifier = "1. Original header string as entered by the user!"; 
// Trim leading and trailing whitespace.
identifier = identifier.trim();
// Truncate the header text to 100 characters.
identifier = identifier.substring(0,100); 
// Convert the identifier to lowercase so that anchor targets will be case-insensitive for the users.
identifier = identifier.toLowerCase(); 
// Generate a hash code from the string. 
// This hash will be used to preserve the extra string uniqueness provided by any special characters that we will be removing in a moment.
String hashcode = identifier.hashcode().toString();
// Strip all characters except letters, numbers, and digits.
identifier = RemoveAllCharactersExceptazAZ09(identifier); 
// Prepend a "anchortarget" string so that: 
// 1) All identifiers start with a letter instead of a digit. 
// 2) No identifier will conflict with any ID tags that are rendered elsewhere by TW. 
// 3) This will also ensure that the end result of the algorithm is never an empty string.
// Also add the hash to preserve the uniqueness of previously removed characters.
identifier = "anchortarget" + identifier + hashcode; 
// Done.

Unless I have missed something, the above algorithm will:

Considerations for user generated anchor links: If the user wants to create a manual anchor link to a specific anchor target (By using the syntax [anchor-link[Pretty Title|Heading 1]] from the original proposal), they will not need to know the generated ID tag. They will only need to know the text of the header or anchor target that they wish to link to. To put that another way, users never need to know the output of this algorithm. They only need to know the input of the algorithm. TW would convert their input text into the matching anchor ID tag whenever it parses the "anchor-link" syntax element.

PS, My JavaScript skills are poor, or I would have presented the algorithm in JavaScript, or just implemented this anchor proposal myself. Since JavaScript is not my native programming language, I thought I would be of greater use by providing a very detailed implementation plan, so that the required effort on any developer who wishes to implement these anchors will be as minimal as I can make it.

Now that the issue of what to use for ID tags has a workable solution, I don't forsee any technical blockers to implementing these anchors. Please let me know if you discover any design issues that I have not anticipated.

Warm regards, BlakeTNC

rcrath commented 8 years ago

@BlakeTNC thank you for taking the time to sketch this out. I can't speak to the code, but the design you describe would certainly work for the feature I sketched out in #1783. I am wondering if it would need to be implemented as TW5 core code or if it could be written as a plugin? This implementation would meet my needs fine. One thing I noticed is that in the JavaScript implementation in the dummy html file, there seems to be no way back to the TOC without creating another link back to TOC from the target. muscle memory wants to hit the back button to do it, but it doesn't leave a trace in browser history, obviously. automatically generating a link back to TOC for any header or other item picked up in the process of generating the TOC links would seem to be one possibility.