tandem 0.2.0.rc → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/lib/tandem/version.rb +1 -1
  2. data/vendor/assets/javascripts/tandem/jquery-fileupload/index.js +2 -0
  3. data/vendor/assets/javascripts/tandem/jquery-fileupload/jquery.fileupload.js +852 -0
  4. data/vendor/assets/javascripts/tandem/jquery-fileupload/jquery.iframe-transport.js +165 -0
  5. data/vendor/assets/javascripts/tandem/jquery.colorbox.js +888 -0
  6. data/vendor/assets/javascripts/tandem/jquery.ui.widget.js +282 -0
  7. data/vendor/assets/javascripts/tandem/modernizr-2.5.3.min.js +4 -0
  8. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-article.png +0 -0
  9. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-aside.png +0 -0
  10. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-blockquote.png +0 -0
  11. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-command.png +0 -0
  12. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-details.png +0 -0
  13. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-figcaption.png +0 -0
  14. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-figure.png +0 -0
  15. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-footer.png +0 -0
  16. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-h1.png +0 -0
  17. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-h2.png +0 -0
  18. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-h3.png +0 -0
  19. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-h4.png +0 -0
  20. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-h5.png +0 -0
  21. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-h6.png +0 -0
  22. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-header.png +0 -0
  23. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-hgroup.png +0 -0
  24. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-mark.png +0 -0
  25. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-meter.png +0 -0
  26. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-nav.png +0 -0
  27. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-p.png +0 -0
  28. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-pre.png +0 -0
  29. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-progress.png +0 -0
  30. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-rp.png +0 -0
  31. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-rt.png +0 -0
  32. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-ruby.png +0 -0
  33. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-section.png +0 -0
  34. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-summary.png +0 -0
  35. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-time.png +0 -0
  36. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/readme.md +1 -0
  37. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/wymiframe.css +98 -0
  38. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/wymiframe.css.scss +98 -0
  39. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/wymiframe.html +26 -0
  40. data/vendor/assets/javascripts/tandem/wymeditor/jquery.wymeditor.min.js +34 -0
  41. data/vendor/assets/javascripts/tandem/wymeditor/lang/bg.js +45 -0
  42. data/vendor/assets/javascripts/tandem/wymeditor/lang/ca.js +45 -0
  43. data/vendor/assets/javascripts/tandem/wymeditor/lang/cs.js +45 -0
  44. data/vendor/assets/javascripts/tandem/wymeditor/lang/cy.js +45 -0
  45. data/vendor/assets/javascripts/tandem/wymeditor/lang/de.js +45 -0
  46. data/vendor/assets/javascripts/tandem/wymeditor/lang/en.js +45 -0
  47. data/vendor/assets/javascripts/tandem/wymeditor/lang/es.js +45 -0
  48. data/vendor/assets/javascripts/tandem/wymeditor/lang/fa.js +46 -0
  49. data/vendor/assets/javascripts/tandem/wymeditor/lang/fi.js +44 -0
  50. data/vendor/assets/javascripts/tandem/wymeditor/lang/fr.js +45 -0
  51. data/vendor/assets/javascripts/tandem/wymeditor/lang/gl.js +45 -0
  52. data/vendor/assets/javascripts/tandem/wymeditor/lang/he.js +45 -0
  53. data/vendor/assets/javascripts/tandem/wymeditor/lang/hr.js +45 -0
  54. data/vendor/assets/javascripts/tandem/wymeditor/lang/hu.js +45 -0
  55. data/vendor/assets/javascripts/tandem/wymeditor/lang/it.js +45 -0
  56. data/vendor/assets/javascripts/tandem/wymeditor/lang/ja.js +44 -0
  57. data/vendor/assets/javascripts/tandem/wymeditor/lang/lt.js +45 -0
  58. data/vendor/assets/javascripts/tandem/wymeditor/lang/nb.js +45 -0
  59. data/vendor/assets/javascripts/tandem/wymeditor/lang/nl.js +45 -0
  60. data/vendor/assets/javascripts/tandem/wymeditor/lang/nn.js +45 -0
  61. data/vendor/assets/javascripts/tandem/wymeditor/lang/pl.js +45 -0
  62. data/vendor/assets/javascripts/tandem/wymeditor/lang/pt-br.js +45 -0
  63. data/vendor/assets/javascripts/tandem/wymeditor/lang/pt.js +45 -0
  64. data/vendor/assets/javascripts/tandem/wymeditor/lang/ru.js +45 -0
  65. data/vendor/assets/javascripts/tandem/wymeditor/lang/sv.js +46 -0
  66. data/vendor/assets/javascripts/tandem/wymeditor/lang/tr.js +45 -0
  67. data/vendor/assets/javascripts/tandem/wymeditor/lang/zh_cn.js +47 -0
  68. data/vendor/assets/javascripts/tandem/wymeditor/plugins/embed/jquery.wymeditor.embed.js +82 -0
  69. data/vendor/assets/javascripts/tandem/wymeditor/plugins/fullscreen/icon_fullscreen.gif +0 -0
  70. data/vendor/assets/javascripts/tandem/wymeditor/plugins/fullscreen/jquery.wymeditor.fullscreen.js +124 -0
  71. data/vendor/assets/javascripts/tandem/wymeditor/plugins/hovertools/jquery.wymeditor.hovertools.js +49 -0
  72. data/vendor/assets/javascripts/tandem/wymeditor/plugins/list/jquery.wymeditor.list.js +60 -0
  73. data/vendor/assets/javascripts/tandem/wymeditor/plugins/rdfa/jquery.wymeditor.rdfa.js +186 -0
  74. data/vendor/assets/javascripts/tandem/wymeditor/plugins/resizable/jquery.wymeditor.resizable.js +77 -0
  75. data/vendor/assets/javascripts/tandem/wymeditor/plugins/resizable/readme.txt +124 -0
  76. data/vendor/assets/javascripts/tandem/wymeditor/plugins/table/jquery.wymeditor.table.js +721 -0
  77. data/vendor/assets/javascripts/tandem/wymeditor/plugins/table/table_delete_column.png +0 -0
  78. data/vendor/assets/javascripts/tandem/wymeditor/plugins/table/table_delete_row.png +0 -0
  79. data/vendor/assets/javascripts/tandem/wymeditor/plugins/table/table_insert_column.png +0 -0
  80. data/vendor/assets/javascripts/tandem/wymeditor/plugins/table/table_insert_row.png +0 -0
  81. data/vendor/assets/javascripts/tandem/wymeditor/plugins/table/table_join_row.png +0 -0
  82. data/vendor/assets/javascripts/tandem/wymeditor/plugins/tidy/README +19 -0
  83. data/vendor/assets/javascripts/tandem/wymeditor/plugins/tidy/jquery.wymeditor.tidy.js +78 -0
  84. data/vendor/assets/javascripts/tandem/wymeditor/plugins/tidy/tidy.php +58 -0
  85. data/vendor/assets/javascripts/tandem/wymeditor/plugins/tidy/wand.png +0 -0
  86. data/vendor/assets/javascripts/tandem/wymeditor/skins/compact/icons.png +0 -0
  87. data/vendor/assets/javascripts/tandem/wymeditor/skins/compact/skin.css +134 -0
  88. data/vendor/assets/javascripts/tandem/wymeditor/skins/compact/skin.js +37 -0
  89. data/vendor/assets/javascripts/tandem/wymeditor/skins/default/icons.png +0 -0
  90. data/vendor/assets/javascripts/tandem/wymeditor/skins/default/skin.css +136 -0
  91. data/vendor/assets/javascripts/tandem/wymeditor/skins/default/skin.js +40 -0
  92. data/vendor/assets/javascripts/tandem/wymeditor/skins/minimal/images/bg.header.gif +0 -0
  93. data/vendor/assets/javascripts/tandem/wymeditor/skins/minimal/images/bg.selector.silver.gif +0 -0
  94. data/vendor/assets/javascripts/tandem/wymeditor/skins/minimal/images/bg.wymeditor.png +0 -0
  95. data/vendor/assets/javascripts/tandem/wymeditor/skins/minimal/images/icons.silver.gif +0 -0
  96. data/vendor/assets/javascripts/tandem/wymeditor/skins/minimal/skin.css +131 -0
  97. data/vendor/assets/javascripts/tandem/wymeditor/skins/minimal/skin.js +30 -0
  98. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/COPYING +674 -0
  99. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/README +27 -0
  100. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/images/bg.header.gif +0 -0
  101. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/images/bg.selector.silver.gif +0 -0
  102. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/images/bg.wymeditor.png +0 -0
  103. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/images/icons.silver.gif +0 -0
  104. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/skin.css +297 -0
  105. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/skin.js +61 -0
  106. data/vendor/assets/javascripts/tandem/wymeditor/skins/twopanels/icons.png +0 -0
  107. data/vendor/assets/javascripts/tandem/wymeditor/skins/twopanels/skin.css +134 -0
  108. data/vendor/assets/javascripts/tandem/wymeditor/skins/twopanels/skin.js +39 -0
  109. data/vendor/assets/javascripts/tandem/wymeditor/skins/wymeditor_icon.png +0 -0
  110. metadata +112 -4
@@ -0,0 +1,124 @@
1
+
2
+
3
+ resizable plugin for WYMeditor
4
+ ##############################
5
+
6
+ The ``resizable`` plugin for WYMeditor_ enables vertical resizing of the
7
+ editor area. The plugin is based on the jQuery UI library.
8
+
9
+ Requirements
10
+ ============
11
+ The following packages are required for using the WYMeditor ``resizable``
12
+ plugin:
13
+
14
+ * jQuery (tested with jQuery ``jquery-1.2.4a.js`` from ``jquery.ui`` package)
15
+ * WYMeditor SVN trunk (Revision: 482)
16
+ * jQuery-UI (tested with ``jquery.ui-1.5b2``)
17
+
18
+ It should be possible to use this plugin with ``WYMeditor-0.4`` but I have not
19
+ tried.
20
+
21
+ Download
22
+ ========
23
+ You can download the WYMeditor ``resizable`` plugin here:
24
+
25
+ * wymeditor-resizable-plugin-0.2.tgz_
26
+ * wymeditor-resizable-plugin-0.1.tgz_
27
+
28
+ See the Changelog_ for more infos about the releases.
29
+
30
+ .. _wymeditor-resizable-plugin-0.2.tgz: http://pyjax.net/download/wymeditor-resizable-plugin-0.2.tgz
31
+ .. _wymeditor-resizable-plugin-0.1.tgz: http://pyjax.net/download/wymeditor-resizable-plugin-0.1.tgz
32
+
33
+ Installation
34
+ ============
35
+ Just extract the downloaded archive into your WYMeditor's ``plugin``
36
+ directory.
37
+
38
+ Usage
39
+ =====
40
+ For general instructions on WYMeditor plugins please refer to the `WYMeditor
41
+ plugin page`_.
42
+
43
+ To use the ``resizable`` plugin simply include the plugin's JavaScript file in
44
+ your code. You **do not** need to include the jQuery UI files - this is done
45
+ automatically by the plugin (see `Internals`_)::
46
+
47
+ <script type="text/javascript"
48
+ src="/js/wymeditor/plugins/resizable/jquery.wymeditor.resizable.js">
49
+ </script>
50
+
51
+ Make sure to adjust the ``src`` attribute to your needs, then initialize the
52
+ plugin in WYMeditor's ``postInit`` function::
53
+
54
+ wymeditor({postInit: function(wym) {
55
+ wym.hovertools(); // other plugins...
56
+ wym.resizable({handles: "s,e",
57
+ maxHeight: 600});
58
+ }
59
+ })
60
+
61
+ The ``resizable`` plugin takes exactly one parameter, which is an object literal
62
+ containing the options of the plugin. The WYMeditor ``resizable`` plugin
63
+ supports all options of the jQuery UI ``resizable`` plugin. These are the
64
+ default values used by the plugin::
65
+
66
+ handles: "s,e,se",
67
+ minHeight: 250,
68
+ maxHeight: 600
69
+
70
+ See the `jQuery UI resizable plugin docs`_ for a list of all options.
71
+
72
+ That's it! You are now able to resize the WYMeditor vertically, horizontally or
73
+ both, depending on your options.
74
+
75
+ .. _jQuery UI resizable plugin docs: http://docs.jquery.com/UI/Resizables
76
+
77
+ Internals
78
+ =========
79
+ The plugin takes care of loading the necessary jQuery UI files (``base`` and
80
+ ``resizable``) from the same path the jQuery library was loaded. Here's how
81
+ it's done::
82
+
83
+ // Get the jQuery path from the editor, stripping away the jQuery file.
84
+ // see http://www.oreilly.com/catalog/regex/chapter/ch04.html
85
+ // The match result array contains the path and the filename.
86
+ var jQueryPath = wym.computeJqueryPath().match(/^(.*)\/(.*)$/)[1];
87
+
88
+ // Make an array of the external JavaScript files required by the plugin.
89
+ var jQueryPlugins = [jQueryPath + '/ui.base.js',
90
+ jQueryPath + '/ui.resizable.js'];
91
+
92
+ // First get the jQuery UI base file
93
+ $.getScript(jQueryPlugins[0]);
94
+
95
+ // Get the jQuery UI resizeable plugin and then init the wymeditor resizable
96
+ // plugin. It is import to do the initialisation after loading the
97
+ // necessary jQuery UI files has finished, otherwise the "resizable" method
98
+ // would not be available.
99
+ $.getScript(jQueryPlugins[1], function() {
100
+ jQuery(wym._box).resizable(final_options);
101
+ });
102
+
103
+ An alternative approach would be to use an AJAX queue when getting the script
104
+ files to ensure that all jQuery files are loaded before the initialisation code
105
+ of the plugin is executed. There is an `jQuery AJAX queue plugin`_ which does
106
+ that.
107
+
108
+ .. _jQuery AJAX queue plugin: http://plugins.jquery.com/project/ajaxqueue
109
+
110
+ Changelog
111
+ =========
112
+
113
+ 0.2
114
+ ---
115
+ - Added full support for all jQuery UI resizable plugin options.
116
+ - Refactored and documented code.
117
+ - Now contains a packed version (775 bytes).
118
+
119
+ 0.1
120
+ ---
121
+ - Initial release.
122
+
123
+ .. _WYMeditor: http://www.wymeditor.org/
124
+ .. _WYMeditor plugin page: http://trac.wymeditor.org/trac/wiki/0.4/Plugins
@@ -0,0 +1,721 @@
1
+ /**
2
+ * Copyright (c) 2011 PolicyStat LLC.
3
+ * MIT licensed (MIT-license.txt)
4
+ *
5
+ * @author Wes Winham (winhamwr@gmail.com)
6
+ */
7
+
8
+ // Fugue icons by Yusuke Kamiyamane http://p.yusukekamiyamane.com/
9
+ // and licensed under Creative Commons Attribution
10
+
11
+ /**
12
+ * A Table editing plugin that gives the user ability to add and remove
13
+ * rows and columns as well as merge rows and columns.
14
+ *
15
+ * @param options A configuration object.
16
+ * @param wym The WYMeditor instance to which the TableEditor should attach.
17
+ * @class
18
+ */
19
+ function TableEditor(options, wym) {
20
+ options = jQuery.extend({
21
+ sMergeRowButtonHtml: String() +
22
+ '<li class="wym_tools_merge_row">' +
23
+ '<a name="merge_row" href="#" title="Merge Cells" ' +
24
+ 'style="background-image: ' +
25
+ "url('" + wym._options.basePath +
26
+ "plugins/table/table_join_row.png')" + '">' +
27
+ 'Merge Table Row' +
28
+ '</a>' +
29
+ '</li>',
30
+
31
+ sMergeRowButtonSelector: "li.wym_tools_merge_row a",
32
+
33
+ sAddRowButtonHtml: String() +
34
+ "<li class='wym_tools_add_row'>" +
35
+ "<a name='add_row' href='#' " +
36
+ "title='Add Row' " +
37
+ "style='background-image:" +
38
+ " url(" + wym._options.basePath +
39
+ "plugins/table/table_insert_row.png)'>" +
40
+ "Add Table Row" +
41
+ "</a>" +
42
+ "</li>",
43
+ sAddRowButtonSelector: "li.wym_tools_add_row a",
44
+
45
+ sRemoveRowButtonHtml: String() +
46
+ "<li class='wym_tools_remove_row'>" +
47
+ "<a name='remove_row' href='#' " +
48
+ "title='Remove Row' " +
49
+ "style='background-image: " +
50
+ "url(" + wym._options.basePath +
51
+ "plugins/table/table_delete_row.png)'>" +
52
+ "Remove Table Row" +
53
+ "</a>" +
54
+ "</li>",
55
+ sRemoveRowButtonSelector: "li.wym_tools_remove_row a",
56
+
57
+ sAddColumnButtonHtml: String() +
58
+ "<li class='wym_tools_add_column'>" +
59
+ "<a name='add_column' href='#' " +
60
+ "title='Add Column' " +
61
+ "style='background-image: " +
62
+ "url(" + wym._options.basePath +
63
+ "plugins/table/table_insert_column.png)'>" +
64
+ "Add Table Column" +
65
+ "</a>" +
66
+ "</li>",
67
+ sAddColumnButtonSelector: "li.wym_tools_add_column a",
68
+
69
+ sRemoveColumnButtonHtml: String() +
70
+ "<li class='wym_tools_remove_column'>" +
71
+ "<a name='remove_column' href='#' " +
72
+ "title='Remove Column' " +
73
+ "style='background-image: " +
74
+ "url(" + wym._options.basePath +
75
+ "plugins/table/table_delete_column.png)'>" +
76
+ "Remove Table Column" +
77
+ "</a>" +
78
+ "</li>",
79
+ sRemoveColumnButtonSelector: "li.wym_tools_remove_column a",
80
+
81
+ enableCellTabbing: true
82
+
83
+ }, options);
84
+
85
+ this._options = options;
86
+ this._wym = wym;
87
+
88
+ this.init();
89
+ }
90
+
91
+ /**
92
+ * Construct and return a table objects using the given options object.
93
+ *
94
+ * @param options The configuration object.
95
+ */
96
+ WYMeditor.editor.prototype.table = function (options) {
97
+ var tableEditor = new TableEditor(options, this);
98
+ this.tableEditor = tableEditor;
99
+
100
+ return tableEditor;
101
+ };
102
+
103
+ /**
104
+ * Initialize the TableEditor object by adding appropriate toolbar buttons and
105
+ * binding any required event listeners.
106
+ */
107
+ TableEditor.prototype.init = function () {
108
+ var wym = this._wym,
109
+ tableEditor = this,
110
+ // Add the tool panel buttons
111
+ tools = $(wym._box).find(
112
+ wym._options.toolsSelector + wym._options.toolsListSelector
113
+ );
114
+
115
+ tools.append(tableEditor._options.sMergeRowButtonHtml);
116
+ tools.append(tableEditor._options.sAddRowButtonHtml);
117
+ tools.append(tableEditor._options.sRemoveRowButtonHtml);
118
+ tools.append(tableEditor._options.sAddColumnButtonHtml);
119
+ tools.append(tableEditor._options.sRemoveColumnButtonHtml);
120
+
121
+ tableEditor.bindEvents();
122
+ rangy.init();
123
+ };
124
+
125
+ /**
126
+ * Bind all required event listeners, including button listeners and support for
127
+ * tabbing through table cells if enableCellTabbing is true.
128
+ */
129
+ TableEditor.prototype.bindEvents = function () {
130
+ var wym = this._wym,
131
+ tableEditor = this;
132
+
133
+ // Handle tool button click
134
+ $(wym._box).find(tableEditor._options.sMergeRowButtonSelector).click(function () {
135
+ var sel = rangy.getIframeSelection(wym._iframe);
136
+ tableEditor.mergeRow(sel);
137
+ return false;
138
+ });
139
+ $(wym._box).find(tableEditor._options.sAddRowButtonSelector).click(function () {
140
+ return tableEditor.addRow(wym.selected());
141
+ });
142
+ $(wym._box).find(tableEditor._options.sRemoveRowButtonSelector).click(function () {
143
+ return tableEditor.removeRow(wym.selected());
144
+ });
145
+ $(wym._box).find(tableEditor._options.sAddColumnButtonSelector).click(function () {
146
+ return tableEditor.addColumn(wym.selected());
147
+ });
148
+ $(wym._box).find(tableEditor._options.sRemoveColumnButtonSelector).click(function () {
149
+ return tableEditor.removeColumn(wym.selected());
150
+ });
151
+
152
+ // Handle tab clicks
153
+ if (tableEditor._options.enableCellTabbing) {
154
+ $(wym._doc).bind('keydown', tableEditor.keyDown);
155
+ }
156
+ };
157
+
158
+ /**
159
+ * Get the number of columns in a given tr element, accounting for colspan and
160
+ * rowspan. This function assumes that the table structure is valid, and will
161
+ * return incorrect results for uneven tables.
162
+ *
163
+ * @param tr The <tr> node whose number of columns we need to count.
164
+ *
165
+ * @returns {Number} The number of columns in the given tr, accounting for
166
+ * colspan and rowspan.
167
+ */
168
+ TableEditor.prototype.getNumColumns = function (tr) {
169
+ var wym = this._wym,
170
+ numColumns = 0,
171
+ table,
172
+ firstTr;
173
+
174
+ table = wym.findUp(tr, 'table');
175
+ firstTr = $(table).find('tr:eq(0)');
176
+
177
+ // Count the tds and ths in the FIRST ROW of this table, accounting for
178
+ // colspan. We count the first td because it won't have any rowspan's before
179
+ // it to complicate things
180
+ $(firstTr).children('td,th').each(function (index, elmnt) {
181
+ numColumns += TableEditor.GET_COLSPAN_PROP(elmnt);
182
+ });
183
+
184
+ return numColumns;
185
+ };
186
+
187
+ /**
188
+ TableEditor.GET_COLSPAN_PROP
189
+ ============================
190
+
191
+ Get the integer value of the inferred colspan property on the given cell in
192
+ a cross-browser compatible way that's also compatible across jquery versions.
193
+
194
+ jquery 1.6 changed the way .attr works, which affected certain browsers
195
+ differently with regard to colspan and rowspan for cells that didn't explcility
196
+ have that attribue set.
197
+ */
198
+ TableEditor.GET_COLSPAN_PROP = function (cell) {
199
+ var colspan = $(cell).attr('colspan');
200
+ if (typeof colspan === 'undefined') {
201
+ colspan = 1;
202
+ }
203
+ return parseInt(colspan, 10);
204
+ };
205
+
206
+ /**
207
+ TableEditor.GET_ROWSPAN_PROP
208
+ ============================
209
+
210
+ Get the integer value of the inferred rowspan property on the given cell in
211
+ a cross-browser compatible way that's also compatible across jquery versions.
212
+
213
+ See GET_COLSPAN_PROP for details
214
+ */
215
+ TableEditor.GET_ROWSPAN_PROP = function (cell) {
216
+ var rowspan = $(cell).attr('rowspan');
217
+ if (typeof rowspan === 'undefined') {
218
+ rowspan = 1;
219
+ }
220
+ return parseInt(rowspan, 10);
221
+ };
222
+ /**
223
+ * Get the X grid index of the given td or th table cell (0-indexed). This takes
224
+ * in to account all colspans and rowspans.
225
+ *
226
+ * @param cell The td or th node whose X index we're returning.
227
+ */
228
+ TableEditor.prototype.getCellXIndex = function (cell) {
229
+ var tableEditor = this,
230
+ i,
231
+ parentTr,
232
+ baseRowColumns,
233
+ rowColCount,
234
+ missingCells,
235
+ rowspanIndexes,
236
+ checkTr,
237
+ rowOffset,
238
+ trChildren,
239
+ elmnt,
240
+ colspan,
241
+ indexCounter,
242
+ cellIndex;
243
+ parentTr = $(cell).parent('tr')[0];
244
+
245
+ baseRowColumns = this.getNumColumns(parentTr);
246
+
247
+ // Figure out how many explicit cells are missing which is how many rowspans
248
+ // we're affected by
249
+ rowColCount = 0;
250
+ $(parentTr).children('td,th').each(function (index, elmnt) {
251
+ rowColCount += TableEditor.GET_COLSPAN_PROP(elmnt);
252
+ });
253
+
254
+ missingCells = baseRowColumns - rowColCount;
255
+ rowspanIndexes = [];
256
+ checkTr = parentTr;
257
+ rowOffset = 1;
258
+
259
+ // If this cell is affected by a rowspan from farther up the table,
260
+ // we need to take in to account any possible colspan attributes on that
261
+ // cell. Store the real X index of the cells to the left of our cell to use
262
+ // in the colspan calculation.
263
+ while (missingCells > 0) {
264
+ checkTr = $(checkTr).prev('tr');
265
+ rowOffset += 1;
266
+ trChildren = $(checkTr).children('td,th');
267
+ for (i = 0; i < trChildren.length; i++) {
268
+ elmnt = trChildren[i];
269
+ if (TableEditor.GET_ROWSPAN_PROP(elmnt) >= rowOffset) {
270
+ // Actually affects our source row
271
+ missingCells -= 1;
272
+ colspan = TableEditor.GET_COLSPAN_PROP(elmnt);
273
+ rowspanIndexes[tableEditor.getCellXIndex(elmnt)] = colspan;
274
+ }
275
+ }
276
+ }
277
+
278
+ indexCounter = 0;
279
+ cellIndex = null;
280
+ // Taking in to account the real X indexes of all of the columns to the left
281
+ // of this cell, determine the real X index.
282
+ $(parentTr).children('td,th').each(function (index, elmnt) {
283
+ if (cellIndex !== null) {
284
+ // We've already iterated to the cell we're checking
285
+ return;
286
+ }
287
+ // Account for an inferred colspan created by a rowspan from above
288
+ while (typeof rowspanIndexes[indexCounter] !== 'undefined') {
289
+ indexCounter += parseInt(rowspanIndexes[indexCounter], 10);
290
+ }
291
+ if (elmnt === cell) {
292
+ // We're at our cell, no need to keep moving to the right.
293
+ // Signal this by setting the cellIndex
294
+ cellIndex = indexCounter;
295
+ return;
296
+ }
297
+ // Account for an explicit colspan on this cell
298
+ indexCounter += TableEditor.GET_COLSPAN_PROP(elmnt);
299
+ });
300
+
301
+ if (cellIndex === null) {
302
+ // Somehow, we never found the cell when iterating over its row.
303
+ throw "Cell index not found";
304
+ }
305
+ return cellIndex;
306
+ };
307
+
308
+ /**
309
+ * Get the number of columns represented by the given array of contiguous cell
310
+ * (td/th) nodes.
311
+ * Accounts for colspan and rowspan attributes.
312
+ *
313
+ * @param cells An array of td/th nodes whose total column span we're checking.
314
+ *
315
+ * @return {Number} The number of columns represented by the "cells"
316
+ */
317
+ TableEditor.prototype.getTotalColumns = function (cells) {
318
+ var tableEditor = this,
319
+ rootTr = this.getCommonParentTr(cells),
320
+ baseRowColumns,
321
+ colspanCount,
322
+ rowColCount;
323
+
324
+ if (rootTr === null) {
325
+ // Non-contiguous columns
326
+ throw "getTotalColumns only allowed for contiguous cells";
327
+ }
328
+
329
+ baseRowColumns = this.getNumColumns(rootTr);
330
+
331
+ // Count the number of simple columns, not accounting for rowspans
332
+ colspanCount = 0;
333
+ $(cells).each(function (index, elmnt) {
334
+ colspanCount += TableEditor.GET_COLSPAN_PROP(elmnt);
335
+ });
336
+
337
+ // Determine if we're affected by rowspans. If the number of simple columns
338
+ // in the row equals the number of columns in the first row, we don't have
339
+ // any rowspans
340
+ rowColCount = 0;
341
+ $(rootTr).children('td,th').each(function (index, elmnt) {
342
+ rowColCount += TableEditor.GET_COLSPAN_PROP(elmnt);
343
+ });
344
+
345
+ if (rowColCount === baseRowColumns) {
346
+ // Easy case. No rowspans to deal with
347
+ return colspanCount;
348
+ } else {
349
+ if (cells.length === 1) {
350
+ // Easy. Just the colspan
351
+ return TableEditor.GET_COLSPAN_PROP(cells[0]);
352
+ } else {
353
+ var lastCell = $(cells).eq(cells.length - 1)[0],
354
+ firstCell = $(cells).eq(0)[0];
355
+ // On jQuery 1.4 upgrade, $(cells).eq(-1)
356
+ return 1 + tableEditor.getCellXIndex(lastCell) -
357
+ tableEditor.getCellXIndex(firstCell);
358
+ }
359
+ }
360
+ };
361
+
362
+ /**
363
+ * Merge the table cells in the given selection using a colspan.
364
+ *
365
+ * @param sel A rangy selection object across which to row merge.
366
+ *
367
+ * @return {Boolean} true if changes are made, false otherwise
368
+ */
369
+ TableEditor.prototype.mergeRow = function (sel) {
370
+ var wym = this._wym,
371
+ tableEditor = this,
372
+ i,
373
+ // Get all of the affected nodes in the range
374
+ nodes = [],
375
+ range = null,
376
+ cells,
377
+ rootTr,
378
+ mergeCell,
379
+ $elmnt,
380
+ rowspanProp;
381
+
382
+ for (i = 0; i < sel.rangeCount; i++) {
383
+ range = sel.getRangeAt(i);
384
+ nodes = nodes.concat(range.getNodes(false));
385
+ }
386
+
387
+ // Just use the td and th nodes
388
+ cells = $(nodes).filter('td,th');
389
+ if (cells.length === 0) {
390
+ return false;
391
+ }
392
+
393
+ // If the selection is across multiple tables, don't merge
394
+ rootTr = tableEditor.getCommonParentTr(cells);
395
+ if (rootTr === null) {
396
+ return false;
397
+ }
398
+
399
+ mergeCell = cells[0];
400
+ // If any of the cells have a rowspan, create the inferred cells
401
+ $(cells).each(function (i, elmnt) {
402
+ $elmnt = $(elmnt);
403
+ rowspanProp = TableEditor.GET_ROWSPAN_PROP(elmnt);
404
+ if (rowspanProp <= 1) {
405
+ // We don't care about cells without a rowspan
406
+ return;
407
+ }
408
+
409
+ // This cell has an actual rowspan, we need to account for it
410
+ // Figure out the x index for this cell in the table grid
411
+ var prevCells = $elmnt.prevAll('td,th'),
412
+ index = tableEditor.getCellXIndex(elmnt),
413
+ // Create the previously-inferred cell in the appropriate index
414
+ // with one less rowspan
415
+ newRowspan = rowspanProp - 1,
416
+ newTd;
417
+ if (newRowspan === 1) {
418
+ newTd = '<td>' + $elmnt.html() + '</td>';
419
+ } else {
420
+ newTd = String() +
421
+ '<td rowspan="' + newRowspan + '">' +
422
+ $elmnt.html() +
423
+ '</td>';
424
+ }
425
+ if (index === 0) {
426
+ $elmnt.parent('tr')
427
+ .next('tr')
428
+ .prepend(newTd);
429
+ } else {
430
+ // TODO: account for colspan/rowspan with insertion
431
+ // Account for colspan/rowspan by walking from right to left looking
432
+ // for the cell closest to the desired index to APPEND to
433
+ var insertionIndex = index - 1,
434
+ insertionCells = $elmnt.parent('tr').next('tr')
435
+ .find('td,th'),
436
+ cellInserted = false;
437
+ for (i = insertionCells.length - 1; i >= 0; i--) {
438
+ var xIndex = tableEditor.getCellXIndex(insertionCells[i]);
439
+ if (xIndex <= insertionIndex) {
440
+ $(insertionCells[i]).append(newTd);
441
+ cellInserted = true;
442
+ break;
443
+ }
444
+ }
445
+ if (!cellInserted) {
446
+ // Bail out now before we clear HTML and break things
447
+ throw "Cell rowspan invalid";
448
+ }
449
+ }
450
+
451
+ // Clear the cell's html, since we just moved it down
452
+ $elmnt.html('');
453
+ });
454
+
455
+ // Remove any rowspan from the mergecell now that we've shifted rowspans
456
+ // down
457
+ // ie fails when we try to remove a rowspan for some reason
458
+ try {
459
+ $(mergeCell).removeAttr('rowspan');
460
+ } catch (err) {
461
+ $(mergeCell).attr('rowspan', 1);
462
+ }
463
+
464
+ // Build the content of the new combined cell from all of the included cells
465
+ var newContent = '';
466
+ $(cells).each(function (index, elmnt) {
467
+ newContent += $(elmnt).html();
468
+ });
469
+
470
+ // Add a colspan to the farthest-left cell
471
+ var combinedColspan = this.getTotalColumns(cells);
472
+ if ($.browser.msie) {
473
+ // jQuery.attr doesn't work for colspan in ie
474
+ mergeCell.colSpan = combinedColspan;
475
+ } else {
476
+ $(mergeCell).attr('colspan', combinedColspan);
477
+ }
478
+
479
+ // Delete the rest of the cells
480
+ $(cells).each(function (index, elmnt) {
481
+ if (index !== 0) {
482
+ $(elmnt).remove();
483
+ }
484
+ });
485
+
486
+ // Change the content in our newly-merged cell
487
+ $(mergeCell).html(newContent);
488
+
489
+ tableEditor.selectElement(mergeCell);
490
+
491
+ return true;
492
+ };
493
+
494
+ /**
495
+ * Add a row to the given elmnt (representing a <tr> or a child of a <tr>).
496
+ *
497
+ * @param The node which will have a row appended after its parent row.
498
+ */
499
+ TableEditor.prototype.addRow = function (elmnt) {
500
+ var wym = this._wym,
501
+ tr = this._wym.findUp(elmnt, 'tr'),
502
+ numColumns,
503
+ tdHtml,
504
+ i;
505
+
506
+ if (tr === null) {
507
+ return false;
508
+ }
509
+
510
+ numColumns = this.getNumColumns(tr);
511
+
512
+ tdHtml = '';
513
+ for (i = 0; i < numColumns; i++) {
514
+ tdHtml += '<td>&nbsp;</td>';
515
+ }
516
+ $(tr).after('<tr>' + tdHtml + '</tr>');
517
+
518
+ return false;
519
+ };
520
+
521
+ /**
522
+ * Remove the given table if it doesn't have any rows/columns.
523
+ *
524
+ * @param table The table to delete if it is empty.
525
+ */
526
+ TableEditor.prototype.removeEmptyTable = function (table) {
527
+ var cells = $(table).find('td,th');
528
+ if (cells.length === 0) {
529
+ $(table).remove();
530
+ }
531
+ };
532
+
533
+ /**
534
+ * Remove the row for the given element (representing a <tr> or a child
535
+ * of a <tr>).
536
+ *
537
+ * @param elmnt The node whose parent tr will be removed.
538
+ */
539
+ TableEditor.prototype.removeRow = function (elmnt) {
540
+ var wym = this._wym,
541
+ tr = this._wym.findUp(elmnt, 'tr'),
542
+ table;
543
+
544
+ if (tr === null) {
545
+ return false;
546
+ }
547
+ table = wym.findUp(elmnt, 'table');
548
+ $(tr).remove();
549
+ this.removeEmptyTable(table);
550
+
551
+ return false;
552
+ };
553
+
554
+ /**
555
+ * Add a column to the given elmnt (representing a <td> or a child of a <td>).
556
+ *
557
+ * @param elmnt The node which will have a column appended afterward.
558
+ */
559
+ TableEditor.prototype.addColumn = function (elmnt) {
560
+ var wym = this._wym,
561
+ td = this._wym.findUp(elmnt, ['td', 'th']),
562
+ prevTds,
563
+ tdIndex,
564
+ tr,
565
+ newTd = '<td>&nbsp;</td>',
566
+ newTh = '<th>&nbsp;</th>',
567
+ insertionElement;
568
+
569
+ if (td === null) {
570
+ return false;
571
+ }
572
+ prevTds = $(td).prevAll();
573
+ tdIndex = prevTds.length;
574
+
575
+ tr = wym.findUp(td, 'tr');
576
+ $(tr).siblings('tr').andSelf().each(function (index, element) {
577
+ insertionElement = newTd;
578
+ if ($(element).find('th').length > 0) {
579
+ // The row has a TH, so insert a th
580
+ insertionElement = newTh;
581
+ }
582
+
583
+ $(element).find('td,th').eq(tdIndex).after(insertionElement);
584
+ });
585
+
586
+ return false;
587
+ };
588
+
589
+ /**
590
+ * Remove the column to the right of the given elmnt (representing a <td> or a
591
+ * child of a <td>).
592
+ */
593
+ TableEditor.prototype.removeColumn = function (elmnt) {
594
+ var wym = this._wym,
595
+ td = this._wym.findUp(elmnt, ['td', 'th']),
596
+ table,
597
+ prevTds,
598
+ tdIndex,
599
+ tr;
600
+ if (td === null) {
601
+ return false;
602
+ }
603
+ table = wym.findUp(elmnt, 'table');
604
+ prevTds = $(td).prevAll();
605
+ tdIndex = prevTds.length;
606
+
607
+ tr = wym.findUp(td, 'tr');
608
+ $(tr).siblings('tr').each(function (index, element) {
609
+ $(element).find('td,th').eq(tdIndex).remove();
610
+ });
611
+ $(td).remove();
612
+ this.removeEmptyTable(table);
613
+
614
+ return false;
615
+ };
616
+
617
+ /**
618
+ * keyDown event handler used for consistent tab key cell movement.
619
+ */
620
+ TableEditor.prototype.keyDown = function (evt) {
621
+ //'this' is the doc
622
+ var wym = WYMeditor.INSTANCES[this.title],
623
+ tableEditor = wym.tableEditor;
624
+
625
+ if (evt.keyCode === WYMeditor.KEY.TAB) {
626
+ return tableEditor.selectNextCell(wym.selected());
627
+ }
628
+
629
+ return null;
630
+ };
631
+
632
+ /**
633
+ * Move the focus to the next cell.
634
+ */
635
+ TableEditor.prototype.selectNextCell = function (elmnt) {
636
+ var wym = this._wym,
637
+ tableEditor = this,
638
+ cell = wym.findUp(elmnt, ['td', 'th']),
639
+ nextCells,
640
+ tr,
641
+ nextRows;
642
+
643
+ if (cell === null) {
644
+ return null;
645
+ }
646
+
647
+ // Try moving to the next cell to the right
648
+ nextCells = $(cell).next('td,th');
649
+ if (nextCells.length > 0) {
650
+ tableEditor.selectElement(nextCells[0]);
651
+ return false;
652
+ }
653
+
654
+ // There was no cell to the right, use the first cell in the next row
655
+ tr = wym.findUp(cell, 'tr');
656
+ nextRows = $(tr).next('tr');
657
+ if (nextRows.length !== 0) {
658
+ nextCells = $(nextRows).children('td,th');
659
+ if (nextCells.length > 0) {
660
+ tableEditor.selectElement(nextCells[0]);
661
+ return false;
662
+ }
663
+ }
664
+
665
+ // There is no next row. Do a normal tab
666
+ return null;
667
+ };
668
+
669
+ /**
670
+ * Select the given element using rangy selectors.
671
+ */
672
+ TableEditor.prototype.selectElement = function (elmnt) {
673
+ var sel = rangy.getIframeSelection(this._wym._iframe),
674
+ range = rangy.createRange(this._wym._doc);
675
+
676
+ range.setStart(elmnt, 0);
677
+ range.setEnd(elmnt, 0);
678
+ range.collapse(false);
679
+
680
+ try {
681
+ sel.setSingleRange(range);
682
+ } catch (err) {
683
+ // ie8 can raise an "unkown runtime error" trying to empty the range
684
+ }
685
+ // IE selection hack
686
+ if ($.browser.msie) {
687
+ this._wym.saveCaret();
688
+ }
689
+ };
690
+
691
+ /**
692
+ * Get the common parent tr for the given table cell nodes. If the closest parent
693
+ * tr for each cell isn't the same, returns null.
694
+ */
695
+ TableEditor.prototype.getCommonParentTr = function (cells) {
696
+ var firstCell,
697
+ parentTrList,
698
+ rootTr;
699
+
700
+ cells = $(cells).filter('td,th');
701
+ if (cells.length === 0) {
702
+ return null;
703
+ }
704
+ firstCell = cells[0];
705
+ parentTrList = $(firstCell).parent('tr');
706
+
707
+ if (parentTrList.length === 0) {
708
+ return null;
709
+ }
710
+ rootTr = parentTrList[0];
711
+
712
+ // Ensure that all of the cells have the same parent tr
713
+ $(cells).each(function (index, elmnt) {
714
+ parentTrList = $(elmnt).parent('tr');
715
+ if (parentTrList.length === 0 || parentTrList[0] !== rootTr) {
716
+ return null;
717
+ }
718
+ });
719
+
720
+ return rootTr;
721
+ };