[RFC] Providing Hash tag support in TiddlyWiki's

Do you mean like a form of autocomplete, as you type?

The feature to add [[#tags]] entered in the text to become titles, is not unlike what is available through the tags pill. Although I personally would like a form of tag that does not fill the tags field on tiddlers, but can mostly be used in similar ways, like testing if they are in the text, listing them including for selection, and listing the tiddlers they tag.

  • I am the designer of reimagin tags which extends the tag drop down a lot, So I thought of extending hashtags further, but there is something simple and deterministic about the current title driven process.
  • We could always create a user interface element to add any hashtag as a tag on the tiddler automagicaly or manually, or via its insertion via an editorToolbar button, but for now at least I don’t think it necessary.
  • You may also find another organising method flags of interest. In effect no-touch tags.

I don’t think we should be concerned with more options and features for ā€œmarkingā€ or organising tiddlers. We will just use what we need when we need it. As I am writing a book this idea of hashtags or content tags appeals because it can be used to mark my content, in tiddler, its more specific.

We do start with plugins, but if it was ever in the core it could require a config item be set, to appear or even just using [[#tagsname]] and it becomes visible.

Yes, I actually use Ctrl+l a lot (though bound to Ctrl+k to match other software). I find it indispensable.

I have used CodeMirror 6 as well though mostly for testing purposes and provided feedback to BTC.

It’s not the auto-completion that matters so much as the convenience of adding a tag without breaking out of a writing flow.

I think Scott and I were describing a workflow along the lines of:

  1. I type #hashtag [SPACE]
  2. #hashtag is appended to the tags field
  3. #hashtag is deleted from the text input. My cursor stays where it was.
    OR
    My text input is replaced with <<tag #hashtag>>.

That’s the point. tags are already ā€œspecialā€ in every way. Technically they are not a tiddler field. JS internally, they are an array. The same is true for the list field.

If tags are listed, they use the list-field of a tag-tiddler to provide a custom sort order. There is no other field, that can do that.

Every other field that contains a Title list internally is a string, with special formatting to allow abc [[title with spaces]]. So internally there is a fundamental difference, between tags, list and any other field.

For every tag in the system, the core manages indexes. So if we want to know, which tiddlers are tagged eg: HelloThere, the access is very fast, since the index knows that tiddler list already.

Let’s assume we have 3 tiddlers a, b and c, which contain different ā€œtag-likeā€ titles in my-field

title: a
my-field: a [[a a]]

+
title: b
my-field: b

+
title: c
my-field: a

A) The filter to list tiddlers that contain ā€œaā€ in my-field would need to look as follows.

\function tag.like(field, tagLike)
[has<field>!is[draft]] :filter[get<field>enlist-input[]match<tagLike>] +[join[, ]]
\end

Tiddlers that contain: `a` -> <<tag.like "my-field" "a">>

test-tag-like-filter.json (877 Bytes)

B) A similar function to get the [tag[HelloThere]] would look like this:

\function f.tag(tag) [tag<tag>!is[draft]] +[join[, ]]

tag contains HelloThere -> <<f.tag HelloThere>>

In example A) we need to

  • List all tiddlers that have the ā€œtag-likeā€ field … has<field>
    • This functionality needs to read every tiddler in the wiki
    • This can become a problem if we have 10000++ tiddlers
  • Make sure we do not list draft tiddlers … !is[draft]
  • Filter the results :filter[...]
  • In the filter read my-field … get<field>,
    • which returns the whole title-list string
  • Separate the title-list string into a real list we can iterate over … enlist-input[]
  • Match the title list against the ā€œsearch stringā€ … match<tagLike>
  • Return the tiddler title only if it matches
  • join the found elements with a coma +[join[, ]]

Example B)

  • Get tiddlers with tag … tag<tag>
    • This function directly reads form the index, which is automatically updated, whenever a tiddler changes
  • Make sure we do not list draft tiddlers … !is[draft]
  • join the found elements with a coma +[join[, ]]

I think it is clear that there is a huge performance benefit. …


Conclusion Tag-likes

So to make ā€œtag-likeā€ titles fast, if they are in fields, we would need a more generalised indexing system.

  • It would need to allow us to speed up the field:my-field[] operator in a way, that is works like a tag.
  • May be field:my-field:tag-like<tagLike>

Disadvantage: Every tag-like field would need quite some memory, to store the index.


That’s a completely different part of the core code. links[] and backlinks[] are also very expensive.

  • To find links and backlinks the whole tiddler text needs to be parsed.
    • That’s the most expensive functionality we have in TW
    • So the parse-tree is cached independently from filters
  • 2 indices need to be created one for links, that other for backlinks
  • They are created ā€œon demandā€ when they are needed the first time
    • Once created, they are cached
    • But these caches is completely destroyed frequently, so it still can be work intensive

Conclusion links, backlinks

IMO these two functionalities have room for improvement.

  • If you use [[#tag]] this mechanisms are automatically active.
  • They are not active for tag-like values stored in eg: my-field
    • I would not use the existing link, backlink mechanisms here
    • I would use a trie like index, similar to the one, I am using for my ā€œaliasesā€ and ā€œbackaliasesā€ handling in my uni-link plugin.
      • My trie-structure is similar to the Aho-Corasick functionality that is used now for the freelink-plugin. (Wich is optimised to handle ā€œnon Latinā€ character sets)

Those 2 trie-indexes could probably be used to create a more generic mechanism that could also be used for very performant tag-like field link and backlink handling.

It would probably also be able to handle the existing tag indexes.

I am not sure if it could replace the existing links and backlinks indexes. A lot of experimenting would be needed.

Just some thoughts.
Mario

2 Likes

I just stumbled upon the listed-operator, which uses a global-cache. So it is cached … But – The main problem here is that the global cache is destroyed with every UI interaction.

I’ll update my post accordingly soon. Need to dig a bit deeper.

1 Like

all,

Please take note I reopened this thread to implement hashtags which I could also call content tags as they are placed within the text field. My proposal is to simply use titles as links [[#tagname]] and make use of existing mechanisms. I have demonstrated a number of custom functions to make handling the # and other title prefixes easier to use.

This is quite simple yet functional and does not involve turning them into regular or alternative field tags. yet it’s quite easy to locate tiddlers containing them, if the current tiddler has one or more, or test if it contains a particular one. you can open any content tag and see where it is mentioned.

  • as Mario points out this leverages existing optimisations

So I would appreciate some feedback on this approach specificaly before going too far in another direction and if you are give a reason by comparing with what I proposed.

post script

there is no real need to turn my proposed content tags into regular tags, I am not sure what that would add, just reduce their specificity.

I think this was the primary motivation behind @pmario’s musings (and I’d have the same concerns)…

This may be of less concern if the wiki(s) where you’re using your links/backlinks-based approach aren’t particularly large or text-heavy, of course.

Since 2001! :smile: : Wikipedia page on UseModWiki

@etardiff tanks for your feedback

Mario’s comment is a technical performance review, specifically in the context of other fields, that is something we may need to contend with, however out of the box we are not trying to use fields beyond the text field, @pmario as responding to using alternative fields, I am explicitly excluding them in my current solution.

  • When dealing with [[#hashtags]] I quickly reduce the results of links[] or backlinks[] with prefix[#]
  • I believe the majority of activity relating to hashtags may only be on the current tiddler, as it is focused on ā€œcontent tagsā€.

Something else that is often forgotten is many uses of links and tags for that matter are ā€œon demandā€ only. My example hashtags are just titles, ie links and leverage the caching and optimisations in the core, and only need to be iterated, when, for example one is looking for all instances of that hashtag. In a similar way when testing for hashtags within the current tiddler you are only referencing the current tiddler.

  • In many ways this solution benefits from the nature, structure and history of performance improvements in TiddlyWiki.
  • If someone can demonstrate a performance hit using this approach, we can look at that when it happens. However I would be surprised.

Other approaches I did not follow

I was looking at the parsing, and $link widget, to code features for hashtags, and may do so in the future, however I realised I can satisfy 90% of the requirements without doing so. I could also take the macro route such as <<# hashtag>> but then I need to search for these throughout the wiki.

  • The macro model could permit setting anchors for navigation, turning off display (for printing) and other dynamic features.

Finally this could also be implemented through template transclusion like {{#hashtag||_userTag}} where user tag provides a link with more button features.

  • Note: I am looking at adopting the prefix _ for transclusion-template tiddlers

That’s too late. To list all links every tiddler has to be parsed and the parse-tree has to be searched for a link-widget call. Performance here is OKish.

Backlinks are the culprit as we know from many discussions here in the forum. The algorithm to find backlinks needs to search every tag in every tiddler, if it is linked there.

As I wrote, there is room for improvement. The different indexers have been created organically, when we experienced performance problems. So they are implemented according to the distinct issue that needed to be solved. So they are all slightly different.

IMO we should create a more generic functionality now, that we know what actually causes performance issues. – But that’s a lot of work.

But it is true that this performance requirement is only when trying to find all occurrences, and this typically needs a particular tiddler to be visible. ie there is no background performance issue unless say you leave a sidebar tab open that iterated a hashtag.

As @pmario said, this is true of links[] but not of backlinks[], which you were proposing to use…

I’m not sure how you could avoid this issue unless you sacrifice this functionality… and IMO, hashtags don’t offer much benefit if you can’t find all occurrences of a given hashtag in a given set of tiddlers. which (in your proposed solution) would require backlinks, search:text, or similar operators. So the performance issue seems inescapable without separate improvements to backlinks indexing. Thus…

I did understand your intent. What I was implying (and should perhaps have stated more directly) in my previous post was that others are proposing field-based alternatives because they see the performance issues currently inherent to backlinks[] as a substantial barrier to adopting your approach.

Edit: Unless you’re saying that functions like your find.userTag would only incur performance costs when viewing a tiddler (or template) where they were used? That’s technically true, but strikes me as a strong incentive not to use them…

This is a common use pattern, keeping the need to do something in the context in which is is needed and not globalising it.

Why does it?

strike YOU as a strong incentive not to use them

I don’t follow :thinking:

Perhaps I misread this, @pmario can help us here. I believe his reply was in the context of links and backlinks within additional fields, as I suggested giving links and backlinks a fieldname as a parameter. In turn I was responding to other users raising that. I am not wedded or interested in that now.

No I was not saying that, find user tag is when you are searching for the occurrence of the use of that tag and all.userTags when trying to find all of them. This is when you are trying to collate them, I don’t envision this to be a common need. But I would put it on the hashtag tiddler, like on a tag tiddler, automatically if needed, if title has prefix.

userTags.here is similar to links, and the tags[] operator but only returns those links[] within the currentTiddler tiddler body that have the nominated prefix eg #. We could use this in a viewTemplate to find, and perhaps test for the presence of a particular content tag, then if it exists do something. I am not suggesting we need to, only that we can, just as we can with tags.

The key value I see is allowing a form of annotation within a tiddler body with view and edit modes, something you care about mostly when viewing/or editing that specific tiddler. For example an author I may want to use [[#expand]] to indicate I need to add more here, or [[#reword]]. I expect once done I delete them.

Additional tips,

  • if these hashtags are in content to be posted to X or something you can use ~ or hide links within the output template.
  • You can hide the visibility using prettylinks such as [[some in place text|#tag]] but still find the tiddler and list the #tag. [[some text to reword|#reword]] ctrl-L is really good for applying this.

Mario can clarify, but I understood his comments re: links and backlinks to be reflective of their current status (and potential performance impact) — i.e., backlinks[] used to retrieve links from text fields (only).

If nothing else, his technical overview aligns very closely with my personal experiences using backlinks in large wikis (actually for a very similar purpose to the hashtag approach you’ve outlined here). I concluded that, while link syntax is certainly easy to type, collating backlinks from even a subset of a large, highly linked wiki was so slow as to be virtually unusable. I ultimately switched to a system much like this one…

… where all text-field internal [[links]] that matched a certain filter pattern would be automatically converted to list-items in a nominated field whenever I saved the tiddler.

I realize this is exactly what you didn’t want to hear, but it does work and it’s reasonably performant for all the filtering I’d want to do (equivalents of userTags.here, find.userTag, and so on) whereas the links-based filtering really was not.

If it’s mostly maintaining a separate field that you object to, I suppose you could, if you liked, also hack the $link widget to include an inline x button — something like [[#hashtag]] → #hashtag [x], which you could click in view or preview mode to automatically remove the tag from the field.

To a few of your other points…

Really? This is pretty much the primary (only?) function of hashtags as I’ve seen them used: to make it easy to collate and browse posts on a particular topic.

This isn’t a use that would ever have occurred to me, but I can see the utility of having your annotations pre-styled in an eye-catching format. But surely you can do this already, without any supporting functions or ViewTemplates, just by typing hashtags as missing links?

I suppose I’d gathered from the example functions you gave earlier that you were mostly interested in using in-text hashtags to collate content to be presented elsewhere — either on the same tiddler or on a separate hashtag tiddler, backlinks-style. If this isn’t your end-goal, then you’re right: you wouldn’t have to worry about backlinks performance.

On the other hand, if hashtag collation is part of the intended package (and IMO, most people would expect this sort of feature)…

Because, as discussed above, it will be very slow. :stuck_out_tongue:

1 Like

I hear you Emily, although as I have only just built it, I have not seen performance problems yet.

  • Given a [[#hashtag]] is a title, as above we can use;
\function all.userTags(prefix:"#") [all[shadows+tiddlers+missing]prefix<prefix>sort[]]
  • This is tapping into the cached list of titles not backlinks.

In the first wiki in which I want to use it, there are not many tags on tiddlers, other than those that add it to the contents. So If I used the above approach to add them to the tags field would be fine.

The solution I was building had another objective, to avoid overuse of tags by introducing another method, as my flags (no-touch tags) solution also does. In my case I expect a few hashtags to be used, they will be added to content needing work, then when the work is done removed, So I dont think the number will accrue too much.

Mario details the issue with non-tags fields but suggests the expensive list and backlinks is already optimised against the text field, although has its limitations. He mentions;

So we have room to innovate.

[Edited]
Perhaps there is value using the very limitations we fact to define different functions, such as local #hashtags (just links) for listing and consumption in a tiddlers text.

List these #thashtags found using links[]prefix[#] on the current tiddler in the story. Whilst we would need backlinks to find these, we don’t. Instead allow hashtags to be globalised with a click, by adding it to the tags. It thus becomes visible as tags[]prefix[#] ie we list only hash tags that are also tags.