Skip to content
154 changes: 131 additions & 23 deletions in-note-text-tagging/in-note-text-tagging.qml
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,120 @@ import QOwnNotesTypes 1.0

/**
* This script handles tagging in a note for tags in the note text like:
* @tag1 @tag2 @tag3
* #tag1 #tag2 #tag3 #tag4
* @tag_one would tag the note with "tag one" tag.
* One or both markers can be enabled independently via the settings.
* - The two checkboxes are independent — you can enable # alone, @ alone, both, or freely change the characters
* - If both are disabled, no tags are detected (clean empty return)
* - maxTagLength = 0 disables the limit → * quantifier in the regex
* - maxTagLength = 32 → {0,31} quantifier (1 mandatory letter + 31 more = 32 total)
* - The tagBodyQuantifier() function computes the correct quantifier on each call

*/

Script {
property bool putToBeginning
property variant settingsVariables: [
{
"identifier": "useMarker1",
"name": "Enable primary tag marker",
"description": "Recognize words starting with the primary marker as tags",
"type": "boolean",
"default": "true"
},
{
"identifier": "tagMarker",
"name": "Tag word marker",
"description": "A word that starts with this characters is recognized as tag",
"name": "Primary tag marker character",
"description": "Character used as primary tag prefix (default: #)",
"type": "string",
"default": "#"
},
{
"identifier": "useMarker2",
"name": "Enable secondary tag marker",
"description": "Recognize words starting with the secondary marker as tags",
"type": "boolean",
"default": "false"
},
{
"identifier": "tagMarker2",
"name": "Secondary tag marker character",
"description": "Character used as secondary tag prefix (default: @)",
"type": "string",
"default": "@"
},
{
"identifier": "maxTagLength",
"name": "Maximum tag length",
"description": "Maximum number of characters allowed in a tag (0 = no limit, default: 32)",
"type": "integer",
"default": "32"
},
{
"identifier": "putToBeginning",
"name": "Put tags to beginning of note rather than to end",
"description": "If enabled tags, added by UI, will be put to the first line of note or right after top headline",
"description": "If enabled, tags added via UI will be put to the first line of note or right after top headline",
"type": "boolean",
"default": "false"
},
]
property bool useMarker1
property string tagMarker
property bool useMarker2
property string tagMarker2
property int maxTagLength

// Returns an array of currently active tag markers
function allMarkers() {
var markers = [];
if (useMarker1 && tagMarker) markers.push(tagMarker);
if (useMarker2 && tagMarker2) markers.push(tagMarker2);
return markers;
}

// Returns a regex alternation string matching any active marker, or null if none active
function markerPattern() {
var markers = allMarkers();
if (markers.length === 0) return null;
return "(?:" + markers.map(escapeRegExp).join("|") + ")";
}

// Returns the quantifier for tag body chars based on maxTagLength setting.
// First letter is always required; this governs the *remaining* characters.
function tagBodyQuantifier() {
if (maxTagLength > 0) return "{0," + (maxTagLength - 1) + "}";
return "*";
}

/**
* Hook to feed the autocompletion with tags if the current word starts with the tag marker
* Hook to feed the autocompletion with tags if the current word starts with any active marker
*/
function autocompletionHook() {
var pattern = markerPattern();
if (!pattern) return [];

// get the current word plus non-word-characters before the word to also get the tag marker
var word = script.noteTextEditCurrentWord(true);

if (!word.startsWith(tagMarker)) {
var matchedMarker = "";
var markers = allMarkers();
for (var i = 0; i < markers.length; i++) {
if (word.startsWith(markers[i])) {
matchedMarker = markers[i];
break;
}
}

if (!matchedMarker) {
return [];
}

// cut the tag marker off of the string and do a substring search for tags
var tags = script.searchTagsByName(word.substr(tagMarker.length));
var tags = script.searchTagsByName(word.substr(matchedMarker.length));

// convert tag names with spaces to in-text tags with "_", "tag one" to @tag_one
for (var i = 0; i < tags.length; i++) {
tags[i] = tags[i].replace(/ /g, "_");
for (var j = 0; j < tags.length; j++) {
tags[j] = tags[j].replace(/ /g, "_");
}

return tags;
Expand All @@ -54,6 +127,18 @@ Script {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

// If the line already starts with a tag marker, appends the tag to it.
// Otherwise inserts a new tag line before it, prefixed by prepend.
function appendTag(text, tag, prepend) {
var markers = allMarkers();
for (var mi = 0; mi < markers.length; mi++) {
var m = markers[mi];
if (text.substring(0, m.length) == m || text.substring(1, m.length + 1) == m)
return text + " " + tag;
}
return prepend + tag + "\n" + text;
}

/**
* Handles note tagging for a note
*
Expand All @@ -67,8 +152,13 @@ Script {
* @return string or string-list (if action = "list")
*/
function noteTaggingHook(note, action, tagName, newTagName) {
var pattern = markerPattern();
if (!pattern) return action === "list" ? [] : "";

var noteText = note.noteText;
var tagRegExp = RegExp("\\B%1(?=($|\\s|\\b)) ?".arg(escapeRegExp(tagMarker + tagName).replace(/ /g, "_")));
// Match a specific known tag with any active marker.
// Group 1 captures the leading space/newline so it is preserved on replace.
var tagRegExp = RegExp("(^|\\s)%1%2(?=($|\\s)) ?".arg(pattern).arg(escapeRegExp(tagName).replace(/ /g, "_")), "m");

switch (action) {
// adds the tag "tagName" to the note
Expand All @@ -80,7 +170,7 @@ Script {
return "";
}

const tag = tagMarker + tagName.replace(/ /g, "_");
var tag = tagMarker + tagName.replace(/ /g, "_");

// add the tag to the beginning or to the end of the note
if (putToBeginning) {
Expand All @@ -103,15 +193,6 @@ Script {

textLines.push(noteText.substring(lineStart));

// if line after headline is a line for tags add tag there,
// or make a new line for tags after headline
function appendTag(text, tag, prepend) {
if (text.substring(0, tagMarker.length) == tagMarker || text.substring(1, tagMarker.length + 1) == tagMarker)
return text + " " + tag;
else
return prepend + tag + "\n" + text;
}

// use different tag line number depending on a headline type
if (textLines[0].substring(0, 1) == "#")
textLines[1] = appendTag(textLines[1], tag, "\n");
Expand All @@ -130,17 +211,44 @@ Script {
// the new note text has to be returned so that the note can be updated
// returning an empty string indicates that nothing has to be changed
case "remove":
return noteText.replace(tagRegExp, "");
return noteText.replace(tagRegExp, "$1");

// renames the tag "tagName" in the note to "newTagName"
// the new note text has to be returned so that the note can be updated
// returning an empty string indicates that nothing has to be changed
case "rename":
return noteText.replace(tagRegExp, tagMarker + newTagName.replace(/ /g, "_"));
return noteText.replace(tagRegExp, "$1" + tagMarker + newTagName.replace(/ /g, "_"));

// returns a list of all tag names of the note
case "list":
var re = new RegExp("\\B%1([^\\s,;%1]+)".arg(escapeRegExp(tagMarker)), "gi"), result, tagNameList = [];
// Exclude all marker characters from tag content.
// Requires a space/newline (or start of line) before the marker,
// a letter (including accented/Unicode) as first char.
// Max length is controlled by tagBodyQuantifier().
var excludedChars = allMarkers().map(escapeRegExp).join("");
var re = new RegExp(
"(?:^|\\s)" + pattern +
"([a-zA-Z" +
"\\u00C0-\\u024F" + // Latin Extended A+B
"\\u0250-\\u02AF" + // IPA Extensions
"\\u0370-\\u03FF" + // Greek and Coptic
"\\u0400-\\u052F" + // Cyrillic + Supplement
"\\u0530-\\u058F" + // Armenian
"\\u05D0-\\u05FF" + // Hebrew
"\\u0600-\\u06FF" + // Arabic
"\\u0900-\\u0D7F" + // Indic (Devanagari, Bengali, Gurmukhi, Gujarati, Oriya, Tamil, Telugu, Kannada, Malayalam)
"\\u0E00-\\u0EFF" + // Thai and Lao
"\\u10A0-\\u10FF" + // Georgian
"\\u1200-\\u137F" + // Ethiopic
"\\u1E00-\\u1FFF" + // Latin Extended Additional + Greek Extended
"\\u3040-\\u30FF" + // Hiragana + Katakana
"\\u3400-\\u4DBF" + // CJK Extension A
"\\u4E00-\\u9FFF" + // CJK Unified Ideographs
"\\uAC00-\\uD7AF" + // Hangul Syllables
"]" +
"[^\\s,;" + excludedChars + "]" + tagBodyQuantifier() + ")",
"gim"
), result, tagNameList = [];

while ((result = re.exec(noteText)) !== null) {
tagName = result[1].replace(/_/g, " ");
Expand Down
2 changes: 1 addition & 1 deletion in-note-text-tagging/info.json
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"script": "in-note-text-tagging.qml",
"authors": ["@Maboroshy"],
"platforms": ["linux", "macos", "windows"],
"version": "0.2.0",
"version": "0.2.1",
"minAppVersion": "20.6.0",
"description": "With this script you can <b>store your tags in your note-text</b>. Use tags like <i>@tag</i> or <i>@tag_one</i> for 'tag one' inside your note-text to tag your notes. You can change '@' to something else in script settings.\n\nYou also are able to use the functionality of the QOwnNotes user-interface to tag with this tags inside your note texts, like for adding a tag to the current note as well as bulk operations for adding and removing tags to your note. If you rename a tag inside QOwnNotes the text-tags in your notes are also updated.\n\nIf you start writing a tag you can also use the autocompleter to get a list of already existing tags.\n\nYou can also use this script as template for implementing your own, unique tagging mechanism.\n\n<b>If you install this script you will loose all links between notes and tags and instead your in-note tags will be used!\n\nThis functionality is still experimental!</b>\nPlease report your experiences."
}
8 changes: 8 additions & 0 deletions txt2tags-it/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# txt2tags-it

This script for QOwnNotes is based on https://github.com/qownnotes/scripts/tree/main/markdown-it

It allows the use of the txt2tags syntax, in addition to the markdown one, in both the editor and in the preview windows of QOwnNotes.

Made with the help of some LLM.

15 changes: 15 additions & 0 deletions txt2tags-it/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "txt2tags-it",
"identifier": "txt2tags-it",
"script": "txt2tags-it.qml",
"resources": [
"markdown-it.js",
"markdown-it-deflist.js",
"markdown-it-katex.js",
"markdown-it-txt2tags.js"
],
"authors": ["@luginf"],
"version": "0.1",
"minAppVersion": "26.4.11",
"description": "This script, based on markdown-it, replaces the default markdown renderer with markdown-it AND also with the txt2tags syntax. It also allows for optional LaTeX rendering support with the Markdown-It KaTeX plugin. (NOTE: LaTeX defaults to rendering with MathML ONLY). \n\n<b>Dependencies</b>\n<a href=\"https://github.com/markdown-it/markdown-it\">markdown-it.js</a> (v8.4.2 bundled with the script)\n<a href=\"https://github.com/mdit-plugins/mdit-plugins/tree/main/packages/katex\">Markdown-It KaTeX plugin</a> (v0.18.0 bundled with the script)\n\n<b>Usage</b>\nFor the possible configuration options check <a href=\"https://github.com/markdown-it/markdown-it/tree/main/lib/presets\">here</a>.\n\n<b>Important</b>\nThis script currently only works with <a href=\"https://github.com/qownnotes/scripts/issues/77\"><b>legacy media links</b></a>. You can turn them on in the <i>General Settings</i>.\n\nImportant note: You need to use legacy image linking with this script, otherwise there will be no images shown in the preview!"
}
Loading
Loading