guardian / scribe

DEPRECATED: A rich text editor framework for the web platform
http://guardian.github.io/scribe/
Apache License 2.0
3.51k stars 245 forks source link

Creating plugin: What’s the cleanest way to detect if the current selection is already wrapped in a whitelisted element? #419

Open brandondurham opened 9 years ago

brandondurham commented 9 years ago

I'm working on a plugin for font sizes that just wraps the selected text in a <span> and applies style="font-size: [n]px;" to the span, where [n] gets replaced by the value passed in. My code thus far:

setFontSizeCommand.execute = function(value) {
    scribe.transactionManager.run(function () {
        var selection = new scribe.api.Selection();
        var range = selection.range;

        var selectedHtmlDocumentFragment = range.extractContents();
        var span = document.createElement('span');
        span.appendChild(selectedHtmlDocumentFragment);
        range.insertNode(span);
        range.selectNode(span);
        span.style.fontSize = value + 'px';

        // Re-apply the range
        selection.selection.removeAllRanges();
        selection.selection.addRange(range);
    });
};

I’d like to be able to just reuse a span (or any element, for that matter) if the currently-selected text is wrapped in one. With the above code, if you select the characters “Basil Elton” in the sentence “I am Basil Elton” and apply a font size, then apply another to the same selection you will get the following output:

I am <span style="font-size: 36px;"><span style="font-size: 42px;">Basil Elton</span></span>

What is the best way to see if the currently-selected text is already wrapped in an element that can be used in this instance?

JeSuis commented 9 years ago

parentElement.nodeName should do it, but there might be more to what you want to do (and I'm totally for it) Considering you need to merge the attributes into one tag. Is this in a single plugin or does it span multiple ones?

brandondurham commented 9 years ago

This comparison function will actually span multiple plugins that I'll be sharing in the not-too-distant future.

brandondurham commented 9 years ago

parentElement.nodeName won't necessarily tell me if the selected text is already wrapped in a <span>, though, right? It will just give me the nodeName of the parent.

rrees commented 9 years ago

@brandondurham If I understand you correctly then extractContents returns a DocumentFragment which you can check the first element of.

jsiebern commented 8 years ago

Thanks for your code @brandondurham , I wanted to achieve the very same thing and based it off of your code here. These are my results (these are not "production-ready" or tested thoroughly!!):

fontSizeCommand.execute = function(size) {
    scribe.transactionManager.run(function () {
        let selection = new scribe.api.Selection();
        let range = selection.range;

        // Get Range Text & Current Node Text
        let selectedHtmlDocumentFragment = range.extractContents();
        let tDiv = document.createElement('div');
        tDiv.appendChild(selectedHtmlDocumentFragment.cloneNode(true));
        let rangeText = tDiv.innerText;
        let nodeText = selection.selection.focusNode.textContent;

        // Determine if we need a new node
        let isNewNode = true;
        if (nodeText === rangeText) {
            isNewNode = (selection.selection.focusNode.parentElement.nodeName === 'SPAN') ? false : true;
        }

        // Create / Get SPAN
        let span = (!isNewNode) ? selection.selection.focusNode.parentElement : document.createElement('span');
        span.appendChild(selectedHtmlDocumentFragment);
        if (isNewNode) {
            range.insertNode(span);
            range.selectNode(span);
        }
        // Clear Setting for children
        for (let i in span.childNodes) {
            let child = span.childNodes[i];
            if (child.nodeName === 'SPAN') {
                child.style.fontSize = '';
                if (!child.getAttribute('style')) {
                    scribe.node.unwrap(span,child);
                }
            }
        }
        // Apply new Font-Size
        span.style.fontSize = size;

        // Re-apply the range
        selection.selection.removeAllRanges();
        selection.selection.addRange(range);
    });
};

I'd very much like to hear your thoughts / experiences since your last post.


Edit: Ok, it's not perfect yet, I'm integrating it with more options like color and font-family and it seems that some child nodes do not get removed properly yet, that should be solveable with a simple recursive lookup in the child nodes though.


Edit2: I actually threw this into its own repo, so if anyone is interested I will be happy about feedback / ideas! https://github.com/jsiebern/scribe-plugin-span-style

jsiebern commented 8 years ago

Hey guys, thought I'd update this thread as I've made some significant progress to get this working more reliably and even mostly cross-browser. Just check out the latest source here:

https://github.com/jsiebern/scribe-plugin-span-style

brandondurham commented 8 years ago

Very nice, @jsiebern! Taking a look at it now. Thanks for sharing.

jsiebern commented 8 years ago

My pleasure, feel free to let me know if you find any improvements or have other suggestions.