New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Blaze] contenteditable reactivity? #1964

Closed
serviewcare opened this Issue Mar 26, 2014 · 41 comments

Comments

Projects
None yet
@ghost

ghost commented Mar 26, 2014

With Handlebars I could do some reactive programming using contenteditable.

With blaze it seems to do really strange things, like duplicating text, and removing lines, when rendering content injected into it.

A small example illustrating this behavior can be found at:
http://contenteditable-broken.meteor.com/

It takes a little bit to trigger the odd behavior sometimes, clear all of the text out, and type in a couple lines, like: "TestTest"

Then click out of the box to trigger the save, and reactive update.

The source is available here:
https://github.com/serviewcare/contenteditable

Any suggestions or help regarding a workaround or a more proper way to use blaze to marshal this content would be greatly appreciated.

@mattmo10587

This comment has been minimized.

mattmo10587 commented Apr 11, 2014

Seems to be working correctly for me on the page you stood up. But I have experienced similar issues with contenteditable. Right now I have an issue where if I type for the first time in an empty contenteditable field, remove focus from the field, and then update the DB through Meteor.methods triggered on blur, the field gets the text that I just typed concatenated onto itself. This doesn't happen with subsequent updates to the field.

@lukejagodzinski

This comment has been minimized.

lukejagodzinski commented Apr 18, 2014

Yes, there is definitely problem with contenteditable in the Blaze. At the beginning I thought that there was some problem with your code but I investigated it in details and I faced the same issue.

I found out that the problem is in adding dynamic content to the element created with Blaze. When DOM element is created with Blaze it has its representation as a proper UI component (I don't know if I named it right). However whenever DOM's element content has changed it should be recreated using Blaze not just by simply replacing element's content HTML. Blaze automatically handles changing value of attributes, innerText etc. but does not handle chaning innerHTML because any HTML element should be just Blaze compatible. Maybe there is some workaround, maybe you could pass this HTML code of contenteditable element to Blaze to have it Blaze ready :).

@estark37

This comment has been minimized.

Contributor

estark37 commented Jun 6, 2014

Hi @serviewcare. We've confirmed this bug; thanks for the reproduction. As a work-around for now, you can just set the contents of contenteditable fields yourself in a Deps.autorun. For example, if your repro, you could remove the content helper and instead do:

Template.contenteditable.rendered = function () {
  this.autorun(function () {
    var content = Content.findOne();
    if (content) this.find(".cr-note p").innerHTML = content.node;
  });
};
@Swavek

This comment has been minimized.

Swavek commented Jun 24, 2014

Hi,
As I need to apply this dynamically to many elements on a page, I simplified @estark37 workaround. Here is the modified blur event handler that I used instead of Deps.autorun:

  Template.contenteditable.events({
    'blur .cr-note p':
        function ( evt) {       
            var note_html = $('.cr-note p').html();
            evt.target.innerHTML = note_html;   
            Content.update({_id: Content.findOne()._id}, {$set: {note: note_html}});
        }
  } );

It works well in my scenario, I'm wondering if there are any potential problems with this 'shortcut' version.

@lukejagodzinski

This comment has been minimized.

lukejagodzinski commented Jun 25, 2014

@Swavek your solution works as well however the solution by @estark37 is more elegant. I was curious why is that bug happening. It appears that when content.note is rendered reactively, its HTML code is parsed by Blaze and any parsed DOM element has $ui (DomRange) object stored inside. And when any change happens to note it doesn't remove old elements but just adds new one without DomRange.

However there is an error with this in the @estark37 's example. Should be:

Template.contenteditable.rendered = function () {
    var t = this;
    this.contentAutorun = Deps.autorun(function () {
        var content = Content.findOne();
        if (content) {
            t.find(".cr-note p").innerHTML = content.note;
        }
    });
};
@sferoze

This comment has been minimized.

sferoze commented Jul 20, 2014

Ah ha I just ran into the same issue! Trying to update a contenteditable div and when saving the content lines get repeated. I tried a solution similar to @Swavek and it seems to work well. I am confused on how to implement the Deps.autorun solution.

Here is what I was trying to do: Have a user update a contenteditable div, and save the contents. The error was that the last line would get repeated twice if I edited or added new lines to the end.

The solution was to just set the innerHTML of the div to the current div innerHTML and it works. Look out for the line where I commented, # THIS IS THE LINE THAT FIXED THE ISSUE!

It makes no sense to me but here is my code:

'click #editNote': (e, t) ->
    note = t.find '.note-container'    
    classie.toggle t.find('.note'), 'focus'
    if t.$('.note').hasClass 'focus'
      $(e.target).text 'Save'
      t.editor.activate()
    else
      t.editor.deactivate()
      $(e.target).attr 'disabled'
      currentNoteId = @_id
      innerText = getInnerText(note)
      noteHTML = note.innerHTML
      note.innerHTML = noteHTML  # THIS IS THE LINE THAT FIXED THE ISSUE! 
      Notes.update currentNoteId,
        $set: 
          html: noteHTML
          text: innerText
      , (error) ->
        if error
          console.log error
          Errors.throw('could not save this note, please refresh browser & contact support', false)
          t.editor.activate()
          classie.toggle t.find('.note'), 'focus'
        else
          t.editor.deactivate()
          $(e.target).text 'Edit'
          $(e.target).removeAttr 'disabled'
@sferoze

This comment has been minimized.

sferoze commented Jul 20, 2014

Actually I updated my code to use the recommended way that @jagi & @estark37 recommended

  Deps.autorun () ->
    content = Notes.findOne(self.data._id)
    if content?.html?
      self.find('.note-container').innerHTML = content.html

Is this only needed temporarily? Is this going to be fixed in a future release and then when I update I can remove this code?

@lukejagodzinski

This comment has been minimized.

lukejagodzinski commented Jul 21, 2014

@estark37 wrote that they know about the bug so I think they gonna make it work soon. It's no the feature that is often used so I wouldn't expect it to be fixed like in month.

@lukejagodzinski

This comment has been minimized.

lukejagodzinski commented Aug 1, 2014

@sferoze create reproduction so we will be able to say what's going on.

@sferoze

This comment has been minimized.

sferoze commented Aug 7, 2014

An issue with the current work around

Deps.autorun () ->
    content = Notes.findOne(self.data._id)
    if content?.html?
      self.find('.note-container')?.innerHTML = content.html

Is that is completely screws up the caret position when the div is re-rendered. Without the deps.autorun code above, the caret position is correctly preserved when the div is re-rendered.

I think I know more specifically what the issue is.

Everything works OK, when the contenteditable div is set to true and you update the database,and the template is re-rendered.

BUT i have a button on my site that sets the contenteditable div to false and at the same time saves the content of the div to the database. Then when meteor goes to update the div, this is where the error occurs, which causes the same text to be repeated within the div.

@queso

This comment has been minimized.

Contributor

queso commented Aug 28, 2014

Is there a fix forthcoming for this?

@sferoze

This comment has been minimized.

sferoze commented Aug 29, 2014

I am very confused. In one part of my app the caret position is preserved but in another it is not preserved. I am not sure if Meteor currently preserves the caret position in contenteditable divs. Although I know for sure it is still having the issue of repeating lines and such.

@bstocks

This comment has been minimized.

bstocks commented Sep 11, 2014

I'm also interested in a status update for this.

I've got workaround in place that preserves the caret position/selection, deals with the reactive update (using a similar method to what @estark37 posted above), and then restores the caret position/selection. It works ok, but i'm looking forward to dropping all this extra code and having it just work as expected.

@eluck

This comment has been minimized.

eluck commented Sep 15, 2014

Hey guys,

Check out this commit (eluck/contenteditable@b406d83). It shows a workaround for the problem for the current Meteor version 0.9.2.

This is kind of hack and it was very different for Meteor 0.8. So if you use it, you should be ready to keep updating it until Blaze becomes more stable.

@bstocks

This comment has been minimized.

bstocks commented Sep 15, 2014

thanks @eluck I definitely learned a thing or two from your code. Unfortunately this doesn't simplify my caret placement issue. I'm doing "save as you type" on the contenteditable similar to google docs, so when the user pauses in their entry, it updates the collection. And then when the changes come back to the client the caret position isn't preserved. As I mentioned above, I'm dealing with that by capturing and restoring the position, but there is a tiny ui flicker in certain circumstances and it's just extra code to do something that I would hope was built in. I'm sure I can tweak my workaround to behave exactly how I want, I'm just looking forward to the day when I don't need it.

@eluck

This comment has been minimized.

eluck commented Sep 16, 2014

Hey Ben,

Your use case is really complicated. And the fact that the same collection entry at the same time may be modified by multiple users doesn't make it simpler.

What I'd like to highlight is that all previous solutions don't preserve reactivity or don't work well with the current version and the solution from my post does both. This may be interesting not only for you, but for other thread participants too.

@bstocks

This comment has been minimized.

bstocks commented Sep 16, 2014

Got it. I do like your method.

I've put multi-user realtime collaborative element on the backburner because it's not critical right now for my project, the main need was for the autosaving feature for a single user, which I've gotten to work. I'm moving on for now and just keeping an eye on this thread.

@queso

This comment has been minimized.

Contributor

queso commented Sep 23, 2014

@glasser or @estark37 any update on this? Kind of a frustrating problem.

@adamgins

This comment has been minimized.

adamgins commented Oct 2, 2014

@eluck hi folks, thanks for this.
It's been a while since I have looks at this is issue and I've been using the following workaround for some time:

Deps.autorun () ->
    content = Notes.findOne(self.data._id)
    if content?.html?
      self.find('.note-container')?.innerHTML = content.html

I see eluck/contenteditable@b406d83 option above, so just confirming if I am runnig Meteor 0.9.3 can I replace my current version with this new approach?

thanks

@eluck

This comment has been minimized.

eluck commented Oct 2, 2014

Hey @adamgins,

Yes, it works for Meteor 0.9.3. Just checked: eluck/contenteditable@91e77fb

Cheers

@adamgins

This comment has been minimized.

adamgins commented Oct 2, 2014

Mmm. I must have something wrong. I am now getting the doubled up text.. ie I enter some text eg "something" and the text field changes to "something something". Not this only happens sometimes.

My code inside the blurred event looks like:

  var blurredItem = e.currentTarget;
        var blurredResourceID = Blaze.getData(blurredItem)._id;
        console.log("Blurred Item ID::" + blurredResourceID);

        var item = $(blurredItem);
        var fieldName = item.data('fieldname');

        //var currentResourceID = Session.get('currentResourceID')
        //updateResourceField (blurredResourceID, item.data('fieldname'), item.html());
        //updateResourceField (blurredResourceID, 'updated', new Date().getTime());
        item.removeClass('resourceUpdated');
        var obj = {};
        obj[fieldName] = item.html();
        obj['updated'] =  new Date().getTime();


        Resources.update(
            {_id:blurredResourceID},
            {$set:obj},
            function() {
                $(e.target).contents().filter(function(){return this.nodeType != 3 && !this.$blaze_range}).remove();
            });
@adamgins

This comment has been minimized.

adamgins commented Oct 3, 2014

BTW, one of the reasons for me looking for the new way to do this is I want to show/hide a contenteditable field, enabled by the user clicking a checkbox The issue is with the old workaround:

Deps.autorun () ->
    content = Notes.findOne(self.data._id)
    if content?.html?
      self.find('.note-container')?.innerHTML = content.html

Is when i reshow the field, it does not show the data. Ie there's no {{field name}} so it does not re-render. DO you have any suggested approaches. Ie when i re-show it i could query the DB and manually put the value in there. Alternative, I was wondering if I should be using Blaze.render or something like that.

Any suggestions appreciated. (including any bugs I had in the post above that prevented that workaround from solving the problem - i.e. show/hide then works fine).

thanks

@prasadKodeInCloud

This comment has been minimized.

prasadKodeInCloud commented Oct 3, 2014

We are facing the same issue and in our case contenteditable is iterating in a list. So the Deps.autorun fix is not applicable since at initial list loading all the contenteditable components set with the same text.

As a workaround removed the current text of contenteditable before updating the text content. The issue with this fix is sometimes text update is visible at a text change (remove and insert) for newly created contenteditable components.

'blur .ti-input':function (evt, tmpl) {
          evt.target.innerHTML = '';  
          Content.update({_id: Content.findOne()._id}, {$set: {note: temp_storedText_at_KeyUp}});
    },

I think a permanent solution is needed for contenteditable to behave like other html components. When replacing contenteditable with a input text, this issue is not reflected.

@eluck

This comment has been minimized.

eluck commented Oct 3, 2014

@adamgins Maybe this will work?

$(e.currentTarget).contents().filter(function(){return this.nodeType != 3 && !this.$blaze_range}).remove();

You're referencing the contenteditable item as e.currentTarget instead of e.target, try doing the same in the after update callback. This case is a bit more complicated than in the simplest example.

I have couple complex contenteditable usecases in the application I work on and each of them requires a slightly different workaround.

hellogerard added a commit to meteor-blog/meteor-blog that referenced this issue Oct 3, 2014

Reactivity shmeactivity
- Can't use reactive template vars (e.g. {{body}}) for contenteditable divs, so
  use old-fashioned jquery instead :-(
- meteor/meteor#1964

hellogerard added a commit to meteor-blog/meteor-blog that referenced this issue Oct 3, 2014

Reactivity shmeactivity
- Can't use reactive template vars (e.g. {{body}}) for contenteditable divs, so
  use old-fashioned jquery instead :-(
- meteor/meteor#1964
@hellogerard

This comment has been minimized.

hellogerard commented Oct 3, 2014

FWIW, as referenced by the above commits, I couldn't get any of the workarounds on this thread to work satisfactorily for me, so I took out any reactive-type code and implemented my feature the old-fashioned way, with a lot of jquery.

@queso

This comment has been minimized.

Contributor

queso commented Oct 3, 2014

Really is a sad state of affairs that we are marching towards a big release and no one from MDG is chiming into this thread now.

@glasser or @dgreensp? @avital?

@bstocks

This comment has been minimized.

bstocks commented Oct 3, 2014

Couldn't agree more @queso

@Swavek

This comment has been minimized.

Swavek commented Oct 3, 2014

Hi,

I created a temporarary solution that caches the target element nodes when the element gets focus. After element loses focus the list of cached nodes is used to remove the nodes that were added while the content was edited.

The solution is contained in 'editor.js' file. There are 2 methods that you can use in your code:

  • Edit - can be called from 'focus' event handler, caches initial list of nodes, sets contentEditable=true
  • Save - should be called from 'blur' event handler, sets contentEditable='inherited', updates content collection document and removes (prunes) the nodes that were added.

The example usage:

Template.contenteditable.events( {

    'focus div.cr-note': function( evt) {
         evt.stopPropagation();
         Editor.Edit( this, evt.target);
    },

 'blur div.cr-note':  function( evt) {
         evt.stopPropagation(); 
         Editor.Save( this, evt.target);
    }

 } );

The solution is tested on Chrome v37 running on Ubuntu 14.04. Please, give it a try in other environments.

The content editable problem root cause

Blaze has no functionality that would link DOM node added 'manually' with corresponding DOMRange object. I'm looking for a way to create such connection rather than 'prune' nodes.
If the added node is linked to DOMRange, Blaze would treat them in the same way as nodes generated by template.

@dgreensp

This comment has been minimized.

Contributor

dgreensp commented Oct 3, 2014

Blaze is pretty much locked down for 1.0 at this point. Looking into the future, though, the developer experience of making form fields definitely needs work! It's not just contentEditable; there are many patterns using good old <input> fields that currently require some careful hackery to achieve, when it comes to saving as you type or on blur, and handling the interaction between updates from the server and input typed by the user.

As for moving forward on this issue, a lot depends on how it is interpreted. The supposedly "ideal" solution to having a reactive contentEditable element -- where you just write <div contenteditable="true">{{{html}}}</div> and it magically works collaboratively, like EtherPad -- is not possible or ideal. The HTML produced by contentEditable is just too bizarre and browser-specific. If you want collaboratively editable rich text, you need an editor library. Now, what I would like to do is integrate Quill (http://quilljs.com/), not necessarily as part of Blaze but as a Meteor package that allows the editor to be easily embedded and wired up reactively.

There are more down-to-earth use cases here, however, like the save-as-you-type editor. There's probably something easy that Blaze could do to make the use cases her less of a hassle, but I'm not sure exactly what it is.

The reason the "doubling up" happens is that <div>{{{html}}}</div> does not mean "replace the contents of the div when html changes," it means "create a div and put html inside it, and then reactively update those nodes in place." The DOM changes that the browser makes when contentEditable is used are considered "foreign" edits, like edits made via jQuery, the DOM API, or third-party widgets. Blaze aims to accommodate foreign edits to a point -- and does a better job than many other front-end libraries -- but when we tried to come up with fancier rules for handling foreign nodes and edits in a way that might make more cases "just work," it led to too much complexity. Since Blaze is necessarily doing some fancy stuff with the DOM, it should be simple and predictable when possible. DOM modifications that come from contentEditable are even harder to deal with than arbitrary modifications via jQuery, because they are themselves unpredictable! Basically, when you invoke contentEditable, the browser does whatever it wants to the DOM, and Blaze just can't keep up.

That doesn't mean there isn't anything we can do here. Maybe <div>{{{html}}}</div> should be compiled into something that replaces the entire top level of the div when html changes. In the save-as-you-type case, maybe it would help to shield the helper from reactive updates somehow while the field is focused.

If I had more time right now, I would go through and catalogue the use cases and examine the solutions provided, but as it is I'm really happy to see people sharing code and ideas here!

@Swavek

This comment has been minimized.

Swavek commented Oct 5, 2014

I found the solution which is much simpler than manipulating the DOMRange.members collection. Here the editable content is isolated from Blaze generated DOM elements:

  • simplified template where entire editable element with its content is provided by single helper:
<template name="contenteditable">
  <h1>Content Editable Test</h1>
  <h3 tabindex=0>Click in the box below, type some multi-lined content, and then click out of it.</h3>
  {{#with content}}
    {{{editable}}}
  {{/with}}
</template>
  • new "editable" helper wraps the source note with contententEditable div element:
Template.contenteditable.editable = function () {
  return '<div class="cr-note" contenteditable="true" tabindex=0>' + this.note + '</div>';
};

This way DOMrange does not reference the content but the div.cr-note element. Content can be saved and is reactively updated when data source is changed.

Check the full code and additional comments.

@dgreensp

This comment has been minimized.

Contributor

dgreensp commented Oct 6, 2014

Swavek, that's a great idea.

On Sunday, October 5, 2014, Swavek Kublin notifications@github.com wrote:

I found the solution which is much simpler than manipulating the
DOMrange.Members collection. Here the editable content is isolated from
Blaze generated DOM elements:

  • simplified template where entire editable element with its content
    is provided by single helper.

Content Editable Test

Click in the box below, type some multi-lined content, and then click out of it.

{{#with content}} {{{editable}}} {{/with}} - new "editable" helper wraps the source note with contententEditable div element:

Template.contenteditable.editable = function () {
return '

' + this.note + '
';
};

This way DOMrange does not reference the content but the div.cr-note
element. Content can be saved and is reactively updated when data source is
changed.


Reply to this email directly or view it on GitHub
#1964 (comment).

@prasadKodeInCloud

This comment has been minimized.

prasadKodeInCloud commented Oct 6, 2014

Even with @Swavek's fix Identified that blur event is firing randomly when keep typing in content editable.

@mitar

This comment has been minimized.

Collaborator

mitar commented Oct 18, 2014

So maybe there is still a need for constant helper like we had it before?

@citizenkevin

This comment has been minimized.

citizenkevin commented Oct 18, 2014

SIMPLE SOLUTION IF YOU ONLY NEED ONE USER EDITING
Won't work for everyone situation, but for anyone that is coming to this thread that only needs one user to be able to edit the content at a time, TURN OFF REACTIVITY for your content query (add "reactive: false" option to the query) if the user is editing, and you will sidestep Blaze scrambling your contentEditable. The editing user doesn't need realtime updates on the state of the content....they already have one on their screen........

@adamgins

This comment has been minimized.

adamgins commented Dec 2, 2014

Hi folks, one additional solution that seems to work well.
BTW, I have implemented the above solutions and they work. I have used them with Ckeditor and various other editors.

in my travels I came across http://summernote.github.io/summernote/ and it seems that as a side-effect of how Summernote handles it rich text editor it actually solves the issue, as far as I can tell. I think this is cause Summernote actually creates a separate contenteditable outside the original one you created, so it's outside of Meteor's scope.

So all you need to do is:

    <div class='summernote'>{{{someField}}} </div> Note tripple braces for raw HTML

And then call

 $('.summernote').summernote();

I expect Summernote must be doing something similar to the ".innerHTML" solution above... but that's just a guess.

@adamgins

This comment has been minimized.

adamgins commented Dec 3, 2014

Phew I wish Meteor included an awesome rich text editor :-) Anyway, on my quest of trying a bunch of editors, I was trying out Redactor and hit this issue - not sure if I need to revert to the autorun/html()/innerHTML approach for this type of 3rd party editor : http://stackoverflow.com/questions/27265717/redactor-getting-failed-to-execute-insertbefore-on-node-error-with-meteor

@joegoldbeck

This comment has been minimized.

joegoldbeck commented Jan 6, 2015

I found @Swavek's solution quite helpful. Thanks!

@avital

This comment has been minimized.

Contributor

avital commented Feb 4, 2015

We don't plan to support the general use-case of dynamically generated contenteditable DIVs for the reasons described in this thread. @Swavek's workaround looks appropriate. Thanks to everyone for chiming in on their workarounds.

We will in a future version of Blaze add an explicit compile-time error in case users try to create dynamic contenteditable DIVs so that they don't get bitten by this problem. #3635

@avital avital closed this Feb 4, 2015

@christianvoigt

This comment has been minimized.

christianvoigt commented Feb 4, 2015

I would like to add a small improvement to the workaround originally proposed by @estark37 as this might help someone else as well:

I use the workaround with Blaze.toHTMLWithData(template, data). This way we can get rid of Blaze's reactivity in contenteditable elements and still use all templating features for generating the editable content (in my case all rich-text formatting is produced by Meteor templates). Because we loose Blaze's fine-grained reactivity this will make the updates after user input much less performant, so I think it's regrettable that Meteor won't support this use-case in a more efficient way.

@ajenkins-kyr

This comment has been minimized.

ajenkins-kyr commented Feb 9, 2015

I also ran into this issue, but I came up with a different solution. Basically, if the value in the contenteditable has changed from its previous value, I clear out its contents and let Blaze fill it back in with the new value. Here's the code I'm using:

<!-- poll.html -->

<template name="poll">
  <h1 class="title" contenteditable="true">{{title}}</h1>
</template>
# poll.coffee

Template.poll.helpers
  title: ->
    @title

Template.poll.events
  'keydown .title': (event) ->
    if event.keyCode is 13
      event.target.blur()
      false
  'blur .title': (event) ->
    text = event.target.innerHTML
    if text isnt @title
      event.target.innerHTML = ''
    Polls.update @_id, $set: title: text
    false
@rhythnic

This comment has been minimized.

rhythnic commented Mar 29, 2015

I tried @Swavek's workaround, but my element was blurred after each keypress. I ended up pairing the contenteditable element with a hidden input.

<template name="contentEditable">
  <input type="hidden" value="{{value}}">
  <span class="content-editable" contenteditable></span>
</template>

Insert the value on rendered.

Template.contentEditable.rendered = function () {
  var elem = this.$('.content-editable')
    .html(this.data.value);
};

pass all changes to input value, then listen for input change in the parent template.

'keyup .content-editable': function (e, t) {
    var text = t.$(e.currentTarget).html();
    t.$('input').val(text).trigger('input');
  }

and finally, use Template.currentData() to change the content if it changes in the data source.

Template.contentEditable.created = function () {
  var tmpl = this;
  this.autorun(function () {
    if (tmpl.lastNode) {
      tmpl.$('.content-editable').html(Template.currentData().value);
    }
  });
};

It seems to work well.

imiric added a commit to imiric/tiq-web that referenced this issue Apr 4, 2015

A bunch of client-side fixes
Can't be bothered to split this commit. Here's what's in it:

- Fix duplicate text when switching tiq views.
  This is an issue with Meteor and contenteditable elements.
  See workaround here:
  meteor/meteor#1964 (comment).

- Properly disable contenteditable on elements other than the current
  editing tag. This is so that the regular anchor functionality can kick
  in.

- Properly reset placeholders.

There's a lot of spaghetti logic here. I'll clean it up eventually.

imiric added a commit to imiric/tiq-web that referenced this issue Apr 5, 2015

A bunch of client-side fixes
Can't be bothered to split this commit. Here's what's in it:

- Fix duplicate text when switching tiq views.
  This is an issue with Meteor and contenteditable elements.
  See workaround here:
  meteor/meteor#1964 (comment).

- Properly disable contenteditable on elements other than the current
  editing tag. This is so that the regular anchor functionality can kick
  in.

- Properly reset placeholders.

There's a lot of spaghetti logic here. I'll clean it up eventually.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment