Creating Boxes that can be Drag and Drop Imported as Tiddlers

I was trying to set up a tampermonkey script that allowed me to drag and drop quotes from GoodReads straight to my TiddlyWiki.

I wrote this this (incomplete) js snippet to test.

quotes = Array.from(document.getElementsByClassName('quote'));

quotes.forEach((quote) => {

    const data = {
        created:20230112194959769,
        tags: "Quotes",
        author: quote.querySelector(".authorOrTitle"),
        modified:20230112211530897,
        title: "Quote",
        source: ""
    };
        
    var a = quote.querySelector(".action");
    const p = document.createElement("div");

    p.setAttribute('draggable', true);

    p.addEventListener('dragstart', (event) => {
        event.dataTransfer.setData('application/json', data);
    });

    var text = document.createTextNode("Drag to TW");
    p.appendChild(text);
    
    a.appendChild(p);
})

As far as I can tell, TW just eats it and nothing happens.

If I change the event listener to:

    p.addEventListener('dragstart', (event) => {
        event.dataTransfer.setData('text/plain', JSON.stringify(data));
    });

TW does see the import but treats it as plain text.

I don’t see any docs for the import system, so if anyone knows how to get this to work that would be great.

Ok, after some more digging I found the proper docs: https://tiddlywiki.com/dev/#TiddlyWiki%20Drag%20and%20Drop%20Interoperability

I am confident this can all be done in wiki text and would be better.

This is run in tampermonkey on a different website. It’s kind of hard to get wikitext working on other websites.

(It adds the “Drag to TW” box)
image

1 Like

While this isn’t the best code, I’ll leave this here for any who want to do the same / want an example to learn from.

Edit: Here’s a link where you can test it after installing

// ==UserScript==
// @name         GoodReads Quote TW Dragger
// @license      MIT
// @version      1.0
// @description  Adds a draggable block to drag GoodReads quotes to TW
// @author       Gamedungeon
// @match     http*://www.goodreads.com/author/quotes/*
// ==/UserScript==

(function() {
    'use strict';

    var quotes = Array.from(document.getElementsByClassName('quote'));

    quotes.forEach((quote) => {

        const fancy_regex = /[“”]+/g;
        const comma_regex = /[,]+/g;

        const data = {
            tags: "Quotes",
            author: quote.querySelector(".authorOrTitle").innerText.replace(comma_regex, '').trim(),
            title: quote.querySelector(".quoteText").firstChild.nodeValue.replace(fancy_regex, '').trim(),
            source: quote.querySelectorAll(".authorOrTitle")[1]?.innerText.trim()
        };

        var a = quote.querySelector(".action");
        const p = document.createElement("div");

        var custom_style = {
            "border-radius": "3px",
            border: "1px solid #D6D0C4",
            appearance: "none",
            cursor: "pointer",
            display: "inline-block",
            "text-decoration": "none",
            color: "#333333",
            "background-color": "#F4F1EA",
            padding: "4px 12px",
            "margin-top": "10px",
            "font-size": "11px"
        }

        Object.assign(p.style,custom_style);


        p.setAttribute('draggable', true);

        p.addEventListener('dragstart', (event) => {
            event.dataTransfer.setData("URL","data:text/vnd.tiddler," + encodeURIComponent(JSON.stringify(data)));
            event.stopPropagation();
            return false;
        });

        var text = document.createTextNode("Drag to TW");
        p.appendChild(text);

        a.appendChild(p);
    })

})();
3 Likes

Thanks for sharing, Could you give some instructions on how to use it in TiddlyWiki?, assume no understanding and more people will benefit.

  • Perhaps include a finished tiddler they can install?

The code above is a tamper monkey script. TamperMonkey is a browser extension that allows you to run custom js on websites. That code creates a draggable box that can be dragged into TiddlyWiki as an import for a tiddler. Due to how TiddlyWiki is already set up, nothing needs to be done to enable you to drag a tiddler in.

You need to create the box to be dragged, and importantly set it to be draggable. In this case, I add some text to it.

var parent = quote.querySelector(".test");
const box = document.createElement("div");

box.setAttribute('draggable', true);

var text = document.createTextNode("Drag to TW");
box.appendChild(text);

You then need to create the data to be imported into TW. This is a list of fields (title, tags, and text being fields.) If you’re using this as part of a tamper monkey script, then these will likely need to be filled out dynamically with js.

var data = {
    title: "Hogfather",
    tags: "Books",
    author: "Terry Pratchett",
    published: "1996",
    text: "I really like this book"
}

Here is the important part. This adds the data to the dragged box. This is modified a bit from here, where they use a list of tiddlers instead of a single one.

box.addEventListener('dragstart', (event) => {
    event.dataTransfer.setData("URL","data:text/vnd.tiddler," + encodeURIComponent(JSON.stringify(data)));
    event.stopPropagation();
    return false;
});

You then need to actually add the box to the website. After this, you should be done.

parent.appendChild(box);

Here is a link where you can play with and test the code.

2 Likes

Very interesting @GameDungeon but outside my current needs or capabilities but a great contribution never the less.

I played around with this; quite neat and opens the door to a number of workflow enhancements into TW. Here’s a URL that will work with the pattern the script is looking for (matches the URL filter, it took me a while to find a URL that works and see the script in action): Amor Towles Quotes (Author of A Gentleman in Moscow)

I personally prefer the quote text to be in the text element, rather than the the title of the tiddler, but that was easy to change in the script.

Each webpage would requires its own script to extract page elements (here based on a js query selector document.getElementsByClassName('quote')), but that doesn’t seem too hard if you inspect the page and find a pattern of interest.

@GameDungeon : thanks for sharing; quite powerful. Do you think it may be possible to have the Drag to TW button show up whenever the user selects text on a webpage, generically on any website?

I think I could have the text show up, but what data exactly would go to tiddilywiki?

Maybe something like the user text selection becomes the text of the tiddler, a source field was added with either the webpage’s title, and a URL field was added well, with the URL of the webpage.

For the title of the imported tiddler, I am open minded. It could be generic (static). The user has the ability to edit the title during the import process. Or it could be the website’s title also.

This javascript bookmarklet (grabbed on this forum somewhere), which copies some of these element to the copy buffer, provides perhaps useful bits:

javascript: (function() {
    function copyToClipboard(text) {
        if (window.clipboardData && window.clipboardData.setData) { /*IE specific code path to prevent textarea being shown while dialog is visible.*/
            return clipboardData.setData("Text", text);
        } else if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
            var textarea = document.createElement("textarea");
            textarea.textContent = text;
            textarea.style.position = "fixed"; /* Prevent scrolling to bottom of page in MS Edge.*/
            document.body.appendChild(textarea);
            textarea.select();
            try {
                return document.execCommand("copy"); /* Security exception may be thrown by some browsers.*/
            } catch (ex) {
                console.warn("Copy to clipboard failed.", ex);
                return false;
            } finally {
                document.body.removeChild(textarea);
            }
        }
    }
    var markdown = '[ext[' + document.title.replace(/\|/g, "-") + '|' + window.location.href + ']]';
    copyToClipboard(markdown);
    if(history.replaceState){history.replaceState({},null,location.href);};
})();

Later today when I have some more time I could throw that together, as that seems quite useful.

Which do you prefer, the drag to TW box appearing, or a right-click menu “copy to tiddler” option?

Right click option would be great. Is there a way to drag from context menu? Otherwise how does one paste complex content into TW? Just ctrl+v?

Ctrl+v works just fine, it just isn’t really needed most times as you rarely have a tiddler in your clipboard.

Due to some quite annoying features of chrome, it turns out I cannot save a tiddler to the clipboard. What do you think is the best way for me to do the drag and drop? Having something show up every time you highlight text would be annoying, but it also shouldn’t be hard to use.

I could make the right-click menu make a drag-and-drop I guess?

Some unstructured notes

For some reason you cant drop a tiddler from a title in the current wiki, I think this can be changed.

You can copy to the clipboard but there is no predefined format for a tiddler in the clipboard. You can copy the title then use that title on drop from within the same wiki. You could copy the text field to the clipboard then that does not include the title.

Using the tm-copy-to-clipboard message to copy anything to the clipboard including a set of JSON tiddlers. The most common example is dragging a tag tiddler which carries everything it tags.

Some how we need a copy to make it look like a file was copied, not only a title.

Copied files in web browsers save a data type called a MIME type with the copied data. For some reason, in chrome, the clipboard only accepts 3 of these, none of which are of the required type. To make this work, TW would need to support these, which is actually not the hardest thing to do: Web custom formats for the Async Clipboard API - Chrome Developers

If someone would mind explaining the relevant internals to me, I might be able to add support for this, this weekend.

This bookmarklet paste a text and TW create a Tiddler of type text/plain!

What do you think if a deserializer is added to TiddlyWiki?

I would also recommend seeing the Chris Aldrich bookmark solution!
Chris Aldrich — the wiki-based commonplace book of a Modern Day Cyberneticist (boffosocko.com)

Original discussion

Clip web page to TW? (google.com)

So good news, bad news. I got it to work…

inside an unmerged pr I created.

For the possible future where that PR gets merged, here is the working code. I think I will do a new thread to release it when it is actually supported. This doesn’t do much, but it should be a clear start.

// ==UserScript==
// @name         Copy to Tiddler
// @version      0.1
// @description  Copies text to a tiddler format.
// @author       You
// @match        *://*/*
// @run-at   context-menu
// ==/UserScript==

function getSelectionText() {
    var text = "";
    if (window.getSelection) {
        text = window.getSelection().toString();
    } else if (document.selection && document.selection.type != "Control") {
        text = document.selection.createRange().text;
    }
    return text;
}

(function() {
    'use strict';

     const data = {
         url: window.location.href,
         text: getSelectionText(),
         title: document.title,
     };

    /*
    This is the way you are supposed to do it. TiddlyWiki does not support Web Custom Formats, so it does not work.
    The way I do it is depricated.

    https://developer.chrome.com/blog/web-custom-formats-for-the-async-clipboard-api/

    navigator.clipboard.write([
      new window.ClipboardItem({
        ["web URL"]: "data:text/vnd.tiddler," + encodeURIComponent(JSON.stringify(data)),
      }),
    ]);
    */

    function listener(e) {
        e.clipboardData.setData("URL", "data:text/vnd.tiddler," + encodeURIComponent(JSON.stringify(data)));
        e.preventDefault();
    }

    document.addEventListener("copy", listener);
    document.execCommand("copy");
    document.removeEventListener("copy", listener);
})();