diff --git a/boot/boot.js b/boot/boot.js index 06d4628c0..c6cd983a6 100644 --- a/boot/boot.js +++ b/boot/boot.js @@ -287,6 +287,56 @@ $tw.utils.decodeURIComponentSafe = function(s) { return v; }; + +/* +Convert a URI List Component encoded string (with `+` as an allowed replacement +for `+`) to a string +*/ +$tw.utils.decodeTWURIList = function(s) { + return $tw.utils.decodeURIComponentSafe( + s.split('&') + .map(s => s.replaceAll('+', ' ')) + .map(s => s.includes(' ') ? '[[' + s + ']]' : s) + .join( ' ') + ) +}; + +/* +Convert a URI Target Component encoded string (with `+` as an allowed replacement +for `+`) to a string +*/ +$tw.utils.decodeTWURITarget = function(s) { + return $tw.utils.decodeURIComponentSafe( + s.replaceAll('+', ' ') + ) +}; + +/* +Convert a URIComponent encoded string (with `+` as an allowed replacement for `+`) to a string +*/ +$tw.utils.encodeTWURIComponent = function(s) { + return s.split(/\[\[|\]\]\s?/) + .filter(Boolean) + .map(function(s) {return s.trim()}) + .map(function(s) {return encodeURIComponent(s)}) + .map(function(s) {return s.replaceAll('%20', '+')}) + .join('&') +}; + +/* +const encode = (input) => { + const sub = (s) => s.replace(/\[\[|\]\]/g, '') + const url = new URL(input) + const fragment = url.hash && decodeURIComponent(url.hash.slice(1)) + if (fragment) { + const [focus, list = ''] = fragment.split(':') + const parts = list.split(/\[\[|\]\]\s?/).filter(Boolean).map(s=>s.trim()).map((s) => s.includes(' ') ? `[[${s}]]` : s).filter(Boolean) + const encoded = sub(focus) + (parts.length ? (':' + parts.map(sub).join('&')) : '') + url.hash = encoded + } + return url.toString().replaceAll('%20', ' ') +} +*/ /* Convert a URI encoded string to a string safely */ diff --git a/core/modules/startup/story.js b/core/modules/startup/story.js index 734f6ae76..21b40d7ae 100644 --- a/core/modules/startup/story.js +++ b/core/modules/startup/story.js @@ -122,10 +122,10 @@ function openStartupTiddlers(options) { var hash = $tw.locationHash.substr(1), split = hash.indexOf(":"); if(split === -1) { - target = $tw.utils.decodeURIComponentSafe(hash.trim()); + target = $tw.utils.decodeTWURITarget(hash.trim()); } else { - target = $tw.utils.decodeURIComponentSafe(hash.substr(0,split).trim()); - storyFilter = $tw.utils.decodeURIComponentSafe(hash.substr(split + 1).trim()); + target = $tw.utils.decodeTWURITarget(hash.substr(0,split).trim()); + storyFilter = $tw.utils.decodeTWURIList(hash.substr(split + 1).trim()); } } // If the story wasn't specified use the current tiddlers or a blank story @@ -198,19 +198,19 @@ function updateLocationHash(options) { // Assemble the location hash switch(options.updateAddressBar) { case "permalink": - $tw.locationHash = "#" + encodeURIComponent(targetTiddler); + $tw.locationHash = "#" + $tw.utils.encodeTWURIComponent(targetTiddler); break; case "permaview": - $tw.locationHash = "#" + encodeURIComponent(targetTiddler) + ":" + encodeURIComponent($tw.utils.stringifyList(storyList)); + $tw.locationHash = "#" + $tw.utils.encodeTWURIComponent(targetTiddler) + ":" + $tw.utils.encodeTWURIComponent($tw.utils.stringifyList(storyList)); break; } // Copy URL to the clipboard switch(options.copyToClipboard) { case "permalink": - $tw.utils.copyToClipboard($tw.utils.getLocationPath() + "#" + encodeURIComponent(targetTiddler)); + $tw.utils.copyToClipboard($tw.utils.getLocationPath() + "#" + $tw.utils.encodeTWURIComponent(targetTiddler)); break; case "permaview": - $tw.utils.copyToClipboard($tw.utils.getLocationPath() + "#" + encodeURIComponent(targetTiddler) + ":" + encodeURIComponent($tw.utils.stringifyList(storyList))); + $tw.utils.copyToClipboard($tw.utils.getLocationPath() + "#" + $tw.utils.encodeTWURIComponent(targetTiddler) + ":" + $tw.utils.encodeTWURIComponent($tw.utils.stringifyList(storyList))); break; } // Only change the location hash if we must, thus avoiding unnecessary onhashchange events