ether / etherpad-lite

Etherpad: A modern really-real-time collaborative document editor.
http://docs.etherpad.org/
Apache License 2.0
16.02k stars 2.79k forks source link

Ideas for table support #402

Closed gedion closed 11 years ago

gedion commented 12 years ago

Hello. My partner and I have come up with the following idea for table representation. However we are having difficulties with concurrent modifications(for special table properties like hitting tab key,row/cell highlighting) and are seeking your opinions.

The table is represented using a json string like below.

var myJSONString = '{"payload":[["Company","Contact","Country"],["Alfreds Futterkiste","Maria Anders","Germany"],["Berglunds snabbkop","Christina Berglund","Sweden"],["Centro comercial Moctezuma","Francisco Chang","Mexico"],["Ernst Handel","Roland Mendel","Austria"],["Island Trading","Helen Bennett","UK"],["Koniglich Essen","Philip Cramer","Germany"],["Laughing Bacchus Winecellars","Yoshi Tannamuri","Canada"]],"tblId":1,"tblClass":"data-tables","trClass":"alst","tdClass":"hide-el","importTbl":"importTbl","cellAttr":[],"tblProperties":{"cellAttrs":[],"width":"","rowAttrs":{},"colAttrs":[]}}';

in domline.js/domlilne_client.js this string object is processed and rendered as shown below.

eplite Table

We then use css to hide the unnecessary columns.

Most of our additions in ace2_inner.js and domline.js/domline_client.js are straightforward and harmless.

Here are a minimal change one can do to make this work.

/*in linestylefilter.js/linestylefilter_client.js, the function attribsToClasses, needs to avoid the creation of span within tables when multiple users are modifying 
*/
var spanSize;
        if(txt.indexOf('data-tables')!=-1){
            spanSize = leftInAuthor = txt.length;        
        }
        else{
            spanSize = txt.length;
        }
gedion commented 12 years ago

continued.... I accidentally hit submit...

Here are the rest of the changes. Remember to use the firefox browser when testing. In Chrome, the caret jumps around when it is within the table. Please send us ideas and feedback

/*
In contentcollector.js, in function isBlockElement(), we need to make sure that br tags within tables do not induce a new line
*/
  function isBlockElement(n)
  {     
   var divNode = dom.nodeTagName(n) || "";
   if(divNode == 'DIV'){
     var tAttr = dom.nodeAttr(n,'value')
     if(tAttr==null||tAttr!='tblBreak'){ 
        return !!_blockElems[(dom.nodeTagName(n) || "").toLowerCase()];
    }
   }
   return false;
  }
/* similarly in  cc.collectContent , the br condition needs an update */
   if (tname == "br")
      {
       tAttr = dom.nodeAttr(node,'value');
       if(tAttr == null || tAttr!='tblBreak'){
        cc.startNewLine(state);
        }
      }
/*
*  in domline.js and domline_clinet.js, we add the below function to render the json string as html table
*/

   /*
     this code snippet needs to be added to result.appendSpan(txt,cls)       
   */   
   else if (txt)
    {

      if(txt.indexOf("data-tables")!=-1){       
        try{
            tblJSONObj = JSON.parse(txt);   
            var htmlTbl = buildText(tblJSONObj,cls);
            html.push('<span class="', cls || '', '">',htmlTbl, perTextNodeProcess(""), '</span>');
        }catch(error){          
            var tempErrorMessage = "hmm...something went wrong. ctrl-z is the best thing a man could have. Reporting it to WLOS developers is the 2nd best thing!";         
            top.$('#error_message_cont').html('Error: buildText ' + error + '<br>' + tempErrorMessage);
            top.$('#error_message_cont').fadeIn().delay(5000).fadeOut('slow');          
        }
      }
      else{
          if (href)
          {
            if(!~href.indexOf("http")) // if the url doesn't include http or https etc prefix it.
            {
              href = "http://"+href;
            }
            extraOpenTags = extraOpenTags + '<a href="' + domline.escapeHTML(href) + '">';
            extraCloseTags = '</a>' + extraCloseTags;
          }   
          if (simpleTags)
          {
            simpleTags.sort();
            extraOpenTags = extraOpenTags + '<' + simpleTags.join('><') + '>';
            simpleTags.reverse();
            extraCloseTags = '</' + simpleTags.join('></') + '>' + extraCloseTags;
          }
          html.push('<span class="', cls || '', '">', extraOpenTags, perTextNodeProcess(domline.escapeHTML(txt)), extraCloseTags, '</span>');
      }
    }

  function buildText(tblJSONObj, cls) {
      var htmlTbl = "";
      var tblId = tblJSONObj.tblId;
      var tblClass = tblJSONObj.tblClass;
      var tdClass = tblJSONObj.tdClass;
      var trClass = tblJSONObj.trClass;
      var payload = tblJSONObj.payload;
      var tblProperties = tblJSONObj.tblProperties;
      var rowAttrs = tblProperties.rowAttrs;
      var cellAttrs = tblProperties.cellAttrs;
      var colAttrs = tblProperties.colAttrs;

      var tblWidth = typeof (tblProperties) == 'undefined' || tblProperties == null ? "6" : tblProperties.width || "6";
      tblWidth = getAttrInInch(tblWidth);
      var tblHeight = typeof (tblProperties) == 'undefined' || tblProperties == null ? "" : tblProperties.height || "";
      tblHeight = getAttrInInch(tblHeight);
      var tblBorderWidth = typeof (tblProperties) == 'undefined' || tblProperties == null ? 2 : tblProperties.borderWidth || 2;
      var tblBorderColor = typeof (tblProperties) == 'undefined' || tblProperties == null ? "" : tblProperties.borderColor || "";
      var currRow = tblProperties.currRowAuthorIdx;
      var currCell = tblProperties.currCellAuthorIdx;
      var authors = tblProperties.authors;
      var htmlTbl = "<table  id='" + tblId + "' class='" + tblClass + "' style='width:" + tblWidth + "px;height:" + tblHeight + "px;'><tbody>";

      var rows = tblJSONObj.payload;
      var evenRowBgColor = typeof (rowAttrs) == 'undefined' || rowAttrs == null ? "" : rowAttrs.evenBgColor || "";
      var oddRowBgColor = typeof (rowAttrs) == 'undefined' || rowAttrs == null ? "" : rowAttrs.oddBgColor || "";
      for (var j = 0, rl = rows.length; j < rl; j++) {
          var tds = rows[j];
          trClassName = "";
          var rowBgColor = oddRowBgColor;
          if (j % 2 == 0) {
              trClassName = "alt";
              rowBgColor = evenRowBgColor;
          }
          htmlTbl += "<tr style='background-color:" + rowBgColor + ";' class='" + trClass + " " + trClassName + "'>";
          var preHeader = "";
          if (j == 0) {
              preHeader = "{\"payload\":["
          }
          htmlTbl += "<td   class='" + tdClass + " overhead'>" + preHeader + "[\"" + "</td>";
          for (var i = 0, tl = tds.length; i < tl; i++) {
              var cellAttr = typeof (cellAttrs[j]) == 'undefined' || cellAttrs[j] == null ? null : cellAttrs[j][i];

              var cellStyles = getCellAttrs(cellAttr, colAttrs[i], authors, i, j)

              var quoteAndComma = ",\"";
              var bracketAndcomma = "";
              if (i == tl - 1) {
                  quoteAndComma = "";
                  bracketAndcomma = "],";
                  if (j == rl - 1) {
                      bracketAndcomma = "]],\"tblId\":\"" + tblId + "\",\"tblClass\":\"" + tblClass + "\",\"trClass\":\"" + trClass + "\",\"tdClass\":\"" + tdClass + "\",\"tblProperties\":" + JSON.stringify(tblProperties) + "}";
                  }
              }
              if (tds[i].indexOf('/r/n') != -1) {
                  cellsWithBr = "";
                  var tdText = tds[i].split('/r/n');
                  for (var k = 0; k < tdText.length; k++) {
                      if (k < tdText.length - 1) {
                          cellsWithBr += domline.escapeHTML(tdText[k]) + "<div value='tblBreak' class='hide-el'>/r/n</div><br value='tblBreak'>";
                      } else cellsWithBr += domline.escapeHTML(tdText[k]);
                  }
                  htmlTbl += "<td style='" + cellStyles + "border:" + tblBorderWidth + "px solid " + tblBorderColor + ";' >" + cellsWithBr + "<br value='tblBreak'></td><td  class='" + tdClass + " overhead'>\"" + quoteAndComma + bracketAndcomma + "</td>";
              } else {
                  htmlTbl += "<td style='" + cellStyles + "border:" + tblBorderWidth + "px solid " + tblBorderColor + ";' >" + domline.escapeHTML(tds[i] + "") + "<br value='tblBreak'></td><td class=' " + tdClass + " overhead'>\"" + quoteAndComma + bracketAndcomma + "</td>";
              }
          }
          htmlTbl += "</tr>";
      }
      htmlTbl += "</tbody></table>";
      return htmlTbl;
  };

  function getCellAttrs(cellAttr, colAttr, authors, cell, row) {
      var attrsJSO = {};

      var colWidth = typeof (colAttr) == 'undefined' || colAttr == null ? "1" : colAttr.width || "1";
      attrsJSO['width'] = getAttrInInch(colWidth) + 'px';

      var cellBgColor = typeof (cellAttr) == 'undefined' || cellAttr == null ? "" : cellAttr.bgColor || "";

      if (typeof (authors) != 'undefined' && authors != null) {

          for (var authorId in authors) {
              author = authors[authorId];
              if (typeof (author) != 'undefined' && author != null && author.cell == cell && author.row == row) {
                  cellBgColor = author.colorId;
              }
          }
      }

      attrsJSO['background-color'] = cellBgColor;

      var cellHeight = typeof (cellAttr) == 'undefined' || cellAttr == null ? "" : cellAttr.height || "";
      attrsJSO['height'] = getAttrInInch(cellHeight) + 'px';

      var cellPadding = typeof (cellAttr) == 'undefined' || cellAttr == null ? "" : cellAttr.padding || "";
      attrsJSO['padding-top'] = attrsJSO['padding-bottom'] = attrsJSO['padding-left'] = attrsJSO['padding-right'] = getAttrInInch(cellPadding) + 'px';

      var cellVAlign = typeof (cellAttr) == 'undefined' || cellAttr == null ? "" : cellAttr.vAlign || "";
      attrsJSO['vertical-align'] = cellVAlign;

      var cellFontSize = typeof (cellAttr) == 'undefined' || cellAttr == null ? "" : cellAttr.fontSize || "";
      attrsJSO['font-size'] = cellFontSize + 'px';

      var cellFontWeight = typeof (cellAttr) == 'undefined' || cellAttr == null ? "" : cellAttr.fontWeight || "";
      attrsJSO['font-weight'] = cellFontWeight;

      var cellFontStyle = typeof (cellAttr) == 'undefined' || cellAttr == null ? "" : cellAttr.fontStyle || "";
      attrsJSO['font-style'] = cellFontStyle;

      var cellTextDecoration = typeof (cellAttr) == 'undefined' || cellAttr == null ? "" : cellAttr.textDecoration || "";
      attrsJSO['text-decoration'] = cellTextDecoration;

      var attrsString = "";
      for (var attrName in attrsJSO) {
          if (attrName) attrsString += attrName + ":" + attrsJSO[attrName] + ";";
      }
      return attrsString;
  }

  function getAttrInInch(attrValue) {
      var intAttrValue = 0;
      intAttrValue = parseInt(attrValue);
      attrValue = isNaN(intAttrValue) ? parseFloat(attrValue) : intAttrValue;
      return 96 * attrValue - 1;
  }

css to hide the unneeded columns. hide-el:{display:none;}

JohnMcLear commented 12 years ago

.

gedion commented 12 years ago

Hey guys. Today I had a breakthrough. All the table properties are working really nicely. I tested with a group of 5 people for about an hour. We were able to make entries, change table properties like table width/height/bolding ect. They helped me identify errors that I am now working on.

This might actually be usable guys.

On Sun, Feb 5, 2012 at 2:41 PM, John McLear < reply@reply.github.com

wrote:

.


Reply to this email directly or view it on GitHub: https://github.com/Pita/etherpad-lite/issues/402#issuecomment-3820568

JohnMcLear commented 12 years ago

Wow, that is awesome! :) Can we see anywhere?

-----Original Message----- From: gedion [mailto:reply@reply.github.com] Sent: 08 February 2012 17:29 To: John McLear Subject: Re: [etherpad-lite] Ideas for table support (#402)

Hey guys. Today I had a breakthrough. All the table properties are working really nicely. I tested with a group of 5 people for about an hour. We were able to make entries, change table properties like table width/height/bolding ect. They helped me identify errors that I am now working on.

This might actually be usable guys.

On Sun, Feb 5, 2012 at 2:41 PM, John McLear < reply@reply.github.com

wrote:

.


Reply to this email directly or view it on GitHub: https://github.com/Pita/etherpad-lite/issues/402#issuecomment-3820568


Reply to this email directly or view it on GitHub: https://github.com/Pita/etherpad-lite/issues/402#issuecomment-3871782 This email and its attachments may be confidential and are intended solely for the use of the individual to whom it is addressed. Any views or opinions expressed are solely those of the author and do not necessarily represent those of the organisation from which this email originated. If you are not the intended recipient of this email and its attachments, you must take no action based upon them, nor must you copy or show them to anyone. Please contact the sender if you believe you have received this email in error. This email was sent by School Email - Safe Webmail and Hosted Email for Schools

gedion commented 12 years ago

I am working on providing a public link right now.

zeigerpuppy commented 12 years ago

Fantastic to hear this is taking shape, tables would be a major step forward!

gedion commented 12 years ago

I think what I need to do to take this to the next level is be able to sanitize/validate the table representation before a changeset is accepted.What I'd like to be able to do is reject a user request if that request breaks the json object.

For example if a user requests to add a row and the caret position is at an improper location, I'd like to verify it and then reject the changeset. In ace2_inner.js I am using the function performDocumentReplaceRange to add rows/columns. I need help figuring out where/when to do a validation.

marcelklehr commented 12 years ago

Hey, what's the status on this?

teian commented 12 years ago

Yeah any update on the Progress for this? I would love to have table support!

JohnMcLear commented 12 years ago

Any update yet @gedion ? Thanks :)

gedion commented 12 years ago

Hello. Sorry for the delay. We've been quiet busy. We are almost ready for beta testing with larger audience. The table appears to be very stable. We also have a working oracle database support.

Will share our work by tomorrow night Central Time Zone.

teian commented 12 years ago

thats seriously nice to hear!

mgoldboi commented 12 years ago

video looks amazing, lots of people are anxious to try it... any chance of an update, or some piece of the code? :)

gedion commented 12 years ago

We have shared our latest codes with Pita and John last night. Hoping that they'll be able to make it available for the public.

JohnMcLear commented 12 years ago

I got the email. I assume you haven't used git?

The Oracle and Table support stuff is all together so picking out different bits is going to be a pain.

Also it isn't built as a plugin so will need re-writing as a plugin, I guess there is at least a full days work if not 2 days work just reading through this and trying to pluginify it.

Neither me or Pita have the time available to do that so if someone wants table support please let Gedion know and you will have to go through his code and pick out each line that is relevant and add it to a plugin...

We have developer guidelines to try to avoid this exact problem, we also use Git to avoid this problem. Developers, use git or your code will never see the light of day.

Gedion, would you be willing to make this a plugin? Without it being pluginified there is no way we can include it in the project.

-----Original Message----- From: gedion [mailto:reply@reply.github.com] Sent: 10 May 2012 03:38 To: John McLear Subject: Re: [etherpad-lite] Ideas for table support (#402)

We have shared our latest codes with Pita and John last night. Hoping that they'll be able to make it available for the public.


Reply to this email directly or view it on GitHub: https://github.com/Pita/etherpad-lite/issues/402#issuecomment-5616913 This email and its attachments may be confidential and are intended solely for the use of the individual to whom it is addressed. Any views or opinions expressed are solely those of the author and do not necessarily represent those of the organisation from which this email originated. If you are not the intended recipient of this email and its attachments, you must take no action based upon them, nor must you copy or show them to anyone. Please contact the sender if you believe you have received this email in error. This email was sent by School Email - Safe Webmail and Hosted Email for Schools

gedion commented 12 years ago

Ok. I will give it a try.

teian commented 12 years ago

@gedion any update on this?

gedion commented 12 years ago

Tak0r, not much improvement. Making it plug-able is going to take me a while. Right now our priority is to have it ready for testing with the way we have shared it with John/Pita. On my own time, I've started looking at existing plugins. I can only afford to put about 4 hours a week on it.

mgoldboi commented 12 years ago

would you mind sending a gz of your code or just the diffs, maybe some other people can work on it on parallel and push it forward. it's just a shame such a great feature will be lost due to time restrictions.

marcelklehr commented 12 years ago

@morangold It would also make sense to set up a public fork for this (wtih its own feature branch), so people can file issues and more than three people can work on it...

gedion commented 12 years ago

Please hold on. So much to do so little time. To be quiet frank, the arrival of Diablo3 is also consuming some of my allotted time for this(but i'm already getting bored as I'm finding it that it is nothing like my beloved D2). I'll set up something in the coming week. Will also try and see if I can figure out a public demo for it as well.

Thank you for your patience.

JohnMcLear commented 12 years ago

Hehe. I thought d3 sucked too.. Played it for 3 days then uninstalled it and got back to being productive. Diablo is the Lord of Productivity Destruction

gedion commented 12 years ago

Heh yea. Maybe I should count it as a blessing that it's not as intriguing as d2.

Anyway. I've decided to share some of the feedback that we receive from users(I should point out that our users are actually European). Hope this will entice you to provide our work as a plug in because I don't think I'll have the necessary time and knowledge needed to refactor it and make it plug-able. I am still interested in making plug-able and will continue to do so.

eplite Table

feedback1

feedback1

mgoldboi commented 12 years ago

gedion, you need to stop teasing ;)

gedion commented 11 years ago

No more teasing : ). Here is a plugable code.

1) Clone/fork my version of eplite which has the plugins and some exposure of ace functions to editorInfo object.(https://github.com/gedion/etherpad-lite.git) 2) run the ep lite, access your admins/plugin page. You should see ep_datatables in the list of plugins. 3) install it and then rerun your ether pad. You should see a button that says table.

teian commented 11 years ago

Yeah very nice!

teian commented 11 years ago

okay not so nice since http://registry.npmjs.org/ doesen't respont :( so i can't test it ...

gedion commented 11 years ago

Try following my steps.

teian commented 11 years ago

i did as i said.... the npmjs repo isn't avaiable so the generation of the plugin list is stuck!

gedion commented 11 years ago

Hmm I just did this and it worked.

Using your terminal

1) git clone https://github.com/gedion/etherpad-lite.git 2) (update settings.json to set up admin plugin)cd into your bin and type bash run.sh 3) http://localhost:9001/admin/plugins 4) You should see ep_datatables in the list. Install it and then restart you ep lite.

teian commented 11 years ago

again, nothing wrong with your code.

http://registry.npmjs.org/ wasn't reachable for some time it's working now!

marcelklehr commented 11 years ago

Better git clone https://github.com/pita/etherpad-lite.git and then git pull https://github.com/gedion/etherpad-lite.git.

This applies the latest bugfixes before merging in gedions changes...

JohnMcLear commented 11 years ago

Some issues:

a) Try to install ep_speechtotext plugin then enable ep_datatables, minor styling issue. b)

Uncaught TypeError: Object # has no method 'ace_doReturnKey'

I got latest master then pulled gedions.

b FIXED: Yo dawg, clear your cache. a is still a problem.

JohnMcLear commented 11 years ago

RE npmjs not being available I spoke to E about this earlier and we're going to setup an npm mirror on npm.etherpad.org, can anyone provide hosting and admin of this please? We can sort DNS out.

JohnMcLear commented 11 years ago

c) Removing focus doesn't close the table dialogue

JohnMcLear commented 11 years ago

d) How do I close table properties? Oh the close thing popped up eventually, pressing escape should close this.

JohnMcLear commented 11 years ago

I should of said, this is great work Gedion :) I just showed a few people and they are super happy! Looking forward to seeing the polished work. Put a pull request in for your changes and I will test/merge over the next few days.

gedion commented 11 years ago

Hey John. Thanks for the feedback.

I have made a pull request. But Matthias Bartelmeß and others are making suggestions. I will work on their suggestions and make a new pull request on develop branch.

Regarding error/bugs, could you please enter them here? https://github.com/gedion/ep_datatables. They're already starting to rack up and having to juggle between emails, chats and message boards is no fun.

mgoldboi commented 11 years ago

Gedion, Job well done, lots of people were expecting it. and sorry for the teasing :)

gedion commented 11 years ago

Thanks morangold. But hey I thought I was the one doing the teasing : )

gedion commented 11 years ago

Alright. here are the feedbacks that I have received and that I'll be working on.

feedbac