Creating external links to local paths

I want to link to source code files on my drive. Since usually the path prefix is fixed (where I cloned the code), I wanted a macro so I wouldn’t need to repeatedly type it (well a [source[]] syntax would have been nicer…). I tried to do it with the code below, but TW doesn’t recognize this as an external link and so it is not rendered properly. Note that I want to use ‘source code’ when a label is not provided.

\define source(path, text)
	<$set name='text' filter=" $text$" value="$text$" emptyValue="source code">
  <$set name="full" value="file:///home/ittay/workspace/$path$">
<$link to=<<full>> ><<text>></$link>
</$set>
	</$set>
\end

From the first line of the documentation for the Link Widget:

The link widget generates links to tiddlers. (Use the HTML <a> element to generate external links).

Try this:

\define source(path, text)
<a href={{{ [[file:///home/ittay/workspace/]addsuffix<__path__>] }}}><$text={{{ [<__text__>!is[blank]else[source code]] }}}/></a>
\end
1 Like

Thanks, I did try to use <a> initially, but then the link was without the tiddlywiki classes, so was not styled. And I couldn’t figure out how to make the $link widget use the values I wanted. It’s not very clear when variables can be used and when filtered transclusion is necessary.

I ended up creating a parser rule (need to set the module-type field to wikirule)

/*\
title: $:/core/modules/parsers/wikiparser/rules/prettylcllink.js
type: application/javascript
module-type: wikirule

Wiki text inline rule for local links. For example:

[lcl[foo/bar/MyClass.java]]
[lcl[Tooltip|foo/bar/MyClass.java]]

For relative paths, they are converted to absolute (by appending) with the value in $:/config/LocalLinks/root 
\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";

exports.name = "prettylcllink";
exports.types = {inline: true};

exports.init = function(parser) {
	this.parser = parser;
};

exports.findNextMatch = function(startPos) {
	// Find the next tag
	this.nextLink = this.findNextLink(this.parser.source,startPos);
	return this.nextLink ? this.nextLink.start : undefined;
};

exports.parse = function() {
	// Move past the match
	this.parser.pos = this.nextLink.end;
	return [this.nextLink];
};

/*
Find the next link from the current position
*/
exports.findNextLink = function(source,pos) {
	// A regexp for finding candidate links
	var reLookahead = /(\[lcl\[)/g;
	// Find the next candidate
	reLookahead.lastIndex = pos;
	var match = reLookahead.exec(source);
	while(match) {
		// Try to parse the candidate as a link
		var link = this.parseLink(source,match.index);
		// Return success
		if(link) {
			return link;
		}
		// Look for the next match
		reLookahead.lastIndex = match.index + 1;
		match = reLookahead.exec(source);
	}
	// Failed
	return null;
};

/*
Look for an link at the specified position. Returns null if not found, otherwise returns {type: "element", tag: "a", attributes: [], isSelfClosing:, start:, end:,}
*/
exports.parseLink = function(source,pos) {
	var token,
		textNode = {
			type: "text"
		},
		node = {
			type: "element",
			tag: "a",
			start: pos,
			attributes: {
				"class": {type: "string", value: "tc-tiddlylink-local"},
			},
			children: [textNode]
		};
	// Skip whitespace
	pos = $tw.utils.skipWhiteSpace(source,pos);
	// Look for the `[lcl[`
	token = $tw.utils.parseTokenString(source,pos,"[lcl[");
	if(!token) {
		return null;
	}
	pos = token.end;
	// Look ahead for the terminating `]]`
	var closePos = source.indexOf("]]",pos);
	if(closePos === -1) {
		return null;
	}
	// Look for a `|` separating the tooltip
	var splitPos = source.indexOf("|",pos);
	if(splitPos === -1 || splitPos > closePos) {
		splitPos = null;
	}
	// Pull out the tooltip and URL
	var tooltip, URL;
	if(splitPos) {
		URL = source.substring(splitPos + 1,closePos).trim();
		textNode.text = source.substring(pos,splitPos).trim();
	} else {
		URL = source.substring(pos,closePos).trim();
		let tposs = URL.lastIndexOf('/')
		let tpose = URL.length
		if (tposs == URL.length - 1) {
			tposs = URL.lastIndexOf('/', URL.length - 2)
			tpose = URL.length - 1
		}
		tposs++
		textNode.text = URL.substring(tposs, tpose).trim();
	}
	
	if (URL.charAt(0) != '/') {
		URL = "file://" + this.parser.wiki.getTiddlerText("$:/config/LocalLinks/root", "/") + URL
	} else {
		URL = "file://" + URL;
	}
	
	node.attributes.href = {type: "string", value: URL};
	node.attributes.target = {type: "string", value: "_blank"};
	node.attributes.rel = {type: "string", value: "noopener noreferrer"};
	// Update the end position
	node.end = closePos + 2;
	return node;
};

})();

I just want to let you know I have done a lot with formatting file and http paths in all manner of ways and would be happy to share, but in fact this is a large subject area.

  • First and foremost no custom Javascript needed

One way I have delt with this is to break down a path into its parts so you can follow a link to its constituents paths.

For example if I plugin your path file:///home/ittay/workspace/$path$ into my split-path macro I get this;
Snag_5b60652

  • Each piece is a link and clicking will open the folder in a browser tab, the icon to the right adds the suffix “/wikibackups” so I can open the backups folder it it is a wiki.
  • Using this method I simple capture the full path and don’t bother storing the prefix

Have a look at the macro still under development to see how I construct a html element to facilitate this.

path-tools.json (2.0 KB)

I would be happy to build exactly what you want but please spell it out with a little more details, otherwise I have to, in part reverse, engineer your requirements from your unworking code, and it takes more of my time.

  • After looking at my example above, perhaps your requirements may change?
1 Like