diff options
Diffstat (limited to 'public/javascripts')
| -rw-r--r-- | public/javascripts/admin_interface.js | 253 | ||||
| -rw-r--r-- | public/javascripts/admin_search.js | 393 | ||||
| -rw-r--r-- | public/javascripts/cacycle_diff.js | 1112 | ||||
| -rw-r--r-- | public/javascripts/related_assets.js | 80 |
4 files changed, 481 insertions, 1357 deletions
diff --git a/public/javascripts/admin_interface.js b/public/javascripts/admin_interface.js index 450438f..e3d4ff1 100644 --- a/public/javascripts/admin_interface.js +++ b/public/javascripts/admin_interface.js | |||
| @@ -12,7 +12,7 @@ function hide_all() { | |||
| 12 | 12 | ||
| 13 | $(document).ready(function () { | 13 | $(document).ready(function () { |
| 14 | admin_search.initialize(); | 14 | admin_search.initialize(); |
| 15 | 15 | ||
| 16 | tinymce.init({ | 16 | tinymce.init({ |
| 17 | selector: 'textarea.with_editor', | 17 | selector: 'textarea.with_editor', |
| 18 | license_key: 'gpl', | 18 | license_key: 'gpl', |
| @@ -29,40 +29,56 @@ $(document).ready(function () { | |||
| 29 | cccms.setup_autosave(); | 29 | cccms.setup_autosave(); |
| 30 | }); | 30 | }); |
| 31 | } | 31 | } |
| 32 | }); | 32 | }); |
| 33 | |||
| 34 | if ($("#dashboard_search_term").length != 0) { | ||
| 35 | dashboard_search.initialize(); | ||
| 36 | } | ||
| 33 | 37 | ||
| 34 | if ($("#menu_search_term").length != 0) { | 38 | if ($("#menu_search_term").length != 0) { |
| 35 | menu_items.initialize_search(); | 39 | menu_items.initialize_search(); |
| 36 | } | 40 | } |
| 37 | 41 | ||
| 38 | if ($("#menu_item_list").length != 0) { | 42 | if ($("#menu_item_list").length != 0) { |
| 39 | menu_item_sorter.initialize(); | 43 | menu_item_sorter.initialize(); |
| 40 | } | 44 | } |
| 41 | 45 | ||
| 42 | if ($("#metadata").length != 0) { | 46 | if ($("#metadata").length != 0) { |
| 43 | meta_data.initialize(); | 47 | meta_data.initialize(); |
| 44 | } | 48 | } |
| 45 | 49 | ||
| 46 | if ($("#parent_search_term").length != 0) { | 50 | if ($("#parent_search_term").length != 0) { |
| 47 | parent_search.initialize_search(); | 51 | parent_search.initialize_search(); |
| 48 | } | 52 | } |
| 49 | 53 | ||
| 50 | if ($("#move_to_search_term").length != 0) { | 54 | if ($("#move_to_search_term").length != 0) { |
| 51 | move_to_search.initialize_search(); | 55 | move_to_search.initialize_search(); |
| 52 | } | 56 | } |
| 53 | 57 | ||
| 58 | if ($("#event_node_search_term").length != 0) { | ||
| 59 | event_search.initialize_search(); | ||
| 60 | } | ||
| 61 | |||
| 62 | if ($("#related_asset_search_term").length != 0) { | ||
| 63 | related_assets.initialize(); | ||
| 64 | } | ||
| 65 | |||
| 66 | if ($("#rrule_builder").length != 0) { | ||
| 67 | rrule_builder.initialize(); | ||
| 68 | } | ||
| 69 | |||
| 54 | if ($('#recent_changes_toggle').length != 0) { | 70 | if ($('#recent_changes_toggle').length != 0) { |
| 55 | hide_all(); | 71 | hide_all(); |
| 56 | $('#recent_changes_toggle').attr("class", "selected"); | 72 | $('#recent_changes_toggle').attr("class", "selected"); |
| 57 | $('#recent_changes_table').show(); | 73 | $('#recent_changes_table').show(); |
| 58 | 74 | ||
| 59 | $('#recent_changes_toggle').bind("click", function(){ | 75 | $('#recent_changes_toggle').bind("click", function(){ |
| 60 | hide_all(); | 76 | hide_all(); |
| 61 | $('#recent_changes_toggle').attr("class", "selected"); | 77 | $('#recent_changes_toggle').attr("class", "selected"); |
| 62 | $('#recent_changes_table').show(); | 78 | $('#recent_changes_table').show(); |
| 63 | return false; | 79 | return false; |
| 64 | }); | 80 | }); |
| 65 | 81 | ||
| 66 | $('#my_work_toggle').bind("click", function(){ | 82 | $('#my_work_toggle').bind("click", function(){ |
| 67 | hide_all(); | 83 | hide_all(); |
| 68 | $('#my_work_toggle').attr("class", "selected"); | 84 | $('#my_work_toggle').attr("class", "selected"); |
| @@ -76,7 +92,7 @@ $(document).ready(function () { | |||
| 76 | $('#my_work_table').show(); | 92 | $('#my_work_table').show(); |
| 77 | return false; | 93 | return false; |
| 78 | }); | 94 | }); |
| 79 | 95 | ||
| 80 | $('#current_drafts_toggle').bind("click", function(){ | 96 | $('#current_drafts_toggle').bind("click", function(){ |
| 81 | hide_all(); | 97 | hide_all(); |
| 82 | $('#current_drafts_toggle').attr("class", "selected"); | 98 | $('#current_drafts_toggle').attr("class", "selected"); |
| @@ -98,91 +114,108 @@ $(document).ready(function () { | |||
| 98 | return false; | 114 | return false; |
| 99 | }); | 115 | }); |
| 100 | } | 116 | } |
| 101 | 117 | ||
| 102 | jQuery.ajaxSetup({ | 118 | jQuery.ajaxSetup({ |
| 103 | 'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript");} | 119 | 'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript");} |
| 104 | }); | 120 | }); |
| 105 | 121 | ||
| 106 | $(document).ajaxSend(function(event, request, settings) { | 122 | $(document).ajaxSend(function(event, request, settings) { |
| 107 | if (typeof(AUTH_TOKEN) == "undefined") return; | 123 | if (typeof(AUTH_TOKEN) == "undefined") return; |
| 108 | // settings.data is a serialized string like "foo=bar&baz=boink" (or null) | 124 | // settings.data is a serialized string like "foo=bar&baz=boink" (or null) |
| 109 | settings.data = settings.data || ""; | 125 | settings.data = settings.data || ""; |
| 110 | settings.data += (settings.data ? "&" : "") + "authenticity_token=" + encodeURIComponent(AUTH_TOKEN); | 126 | settings.data += (settings.data ? "&" : "") + "authenticity_token=" + encodeURIComponent(AUTH_TOKEN); |
| 111 | }); | 127 | }); |
| 112 | 128 | ||
| 113 | }); | 129 | }); |
| 114 | 130 | ||
| 115 | 131 | ||
| 116 | meta_data = { | 132 | meta_data = { |
| 117 | initialize : function() { | 133 | initialize : function() { |
| 118 | $("#metadata").hide(); | 134 | document.getElementById("metadata_details").addEventListener("toggle", function() { |
| 119 | 135 | if (this.open) image_interface.initialize(); | |
| 120 | $("#button").click(function () { | ||
| 121 | |||
| 122 | $("#metadata").slideToggle(1200); | ||
| 123 | image_interface.initialize(); | ||
| 124 | |||
| 125 | if ($("#button").attr("class") == "unselected") { | ||
| 126 | $("#button").attr("class", "selected"); | ||
| 127 | |||
| 128 | } | ||
| 129 | else { | ||
| 130 | $("#button").attr("class", "unselected"); | ||
| 131 | $("#image_browser").hide(); | ||
| 132 | $("#image_browser_toggle").attr("class", "unselected"); | ||
| 133 | } | ||
| 134 | |||
| 135 | return false; | ||
| 136 | }); | 136 | }); |
| 137 | } | 137 | } |
| 138 | }; | 138 | }; |
| 139 | 139 | ||
| 140 | cccms = { | 140 | cccms = { |
| 141 | setup_autosave : function() { | 141 | setup_autosave : function() { |
| 142 | 142 | ||
| 143 | var elements = { | 143 | var elements = { |
| 144 | title : $('#page_title'), | 144 | title : $('#page_title'), |
| 145 | abstract : $('#page_abstract'), | 145 | abstract : $('#page_abstract'), |
| 146 | body : $('#page_body_ifr').contents().find('#tinymce'), | 146 | body : $('#page_body_ifr').contents().find('#tinymce'), |
| 147 | } | 147 | } |
| 148 | 148 | ||
| 149 | var page = { | 149 | var page = { |
| 150 | cached_title : elements.title.val(), | 150 | cached_title : elements.title.val(), |
| 151 | cached_abstract : elements.abstract.val(), | 151 | cached_abstract : elements.abstract.val(), |
| 152 | cached_body : elements.body.html(), | 152 | cached_body : elements.body.html(), |
| 153 | 153 | ||
| 154 | title_has_changed : function() { | 154 | title_has_changed : function() { |
| 155 | return (elements.title.val() != this.cached_title) | 155 | return (elements.title.val() != this.cached_title) |
| 156 | }, | 156 | }, |
| 157 | 157 | ||
| 158 | abstract_has_changed : function() { | 158 | abstract_has_changed : function() { |
| 159 | return (elements.abstract.val() != this.cached_abstract) | 159 | return (elements.abstract.val() != this.cached_abstract) |
| 160 | }, | 160 | }, |
| 161 | 161 | ||
| 162 | body_has_changed : function() { | 162 | body_has_changed : function() { |
| 163 | return elements.body.html() != this.cached_body | 163 | return elements.body.html() != this.cached_body |
| 164 | } | 164 | } |
| 165 | } | 165 | } |
| 166 | 166 | ||
| 167 | jQuery.fn.submitWithAjax = function(options) { | 167 | jQuery.fn.submitWithAjax = function(options) { |
| 168 | if (page.title_has_changed() || page.abstract_has_changed() || page.body_has_changed()) { | 168 | if (page.title_has_changed() || page.abstract_has_changed() || page.body_has_changed()) { |
| 169 | 169 | ||
| 170 | tinymce.triggerSave(); | ||
| 170 | page.cached_title = elements.title.val(); | 171 | page.cached_title = elements.title.val(); |
| 171 | page.cached_abstract = elements.abstract.val(); | 172 | page.cached_abstract = elements.abstract.val(); |
| 172 | page.cached_body = elements.body.html(); | 173 | page.cached_body = elements.body.html(); |
| 173 | 174 | ||
| 174 | $("#flash").append("<img src='/images/ajax-loader.gif' alt='' />"); | 175 | var data = this.serializeArray().filter(function(field) { |
| 175 | $.post(this.attr("action"), $(this).serialize(), null, "script"); | 176 | return field.name !== "_method"; |
| 176 | 177 | }); | |
| 178 | data.push({ name: "_method", value: "put" }); | ||
| 179 | |||
| 180 | $.ajax({ | ||
| 181 | type: "POST", | ||
| 182 | url: this.attr("data-autosave-url"), | ||
| 183 | data: data, | ||
| 184 | error: function(xhr) { | ||
| 185 | if (xhr.status === 423) { | ||
| 186 | clearInterval(cccms.autosave_timer); | ||
| 187 | cccms.report_lock_lost($("#page_editor > form").attr("data-show-url")); | ||
| 188 | } | ||
| 189 | // any other failure: quietly retried on the next tick | ||
| 190 | } | ||
| 191 | }); | ||
| 192 | |||
| 177 | } | 193 | } |
| 178 | }; | 194 | }; |
| 179 | 195 | ||
| 180 | setInterval('$("#page_editor > form").submitWithAjax()', 7000); | 196 | cccms.autosave_timer = setInterval(function() { |
| 197 | $("#page_editor > form").submitWithAjax(); | ||
| 198 | }, 7000); | ||
| 199 | }, | ||
| 200 | |||
| 201 | report_lock_lost : function(show_url) { | ||
| 202 | var $flash = $("#flash"); | ||
| 203 | if ($flash.length === 0) { | ||
| 204 | $flash = $("<div>", { id: "flash" }).insertAfter(".admin_content_spacer"); | ||
| 205 | } | ||
| 206 | |||
| 207 | var $banner = $("<span>", { "class": "warning" }).text( | ||
| 208 | "This page is now locked by someone else — your changes here can no longer be saved. Copy anything you need, then " | ||
| 209 | ); | ||
| 210 | $banner.append($("<a>", { href: show_url }).text("return to the published page")); | ||
| 211 | $banner.append("."); | ||
| 212 | |||
| 213 | $flash.html($banner); | ||
| 181 | } | 214 | } |
| 182 | } | 215 | } |
| 183 | 216 | ||
| 184 | menu_item_sorter = { | 217 | menu_item_sorter = { |
| 185 | 218 | ||
| 186 | initialize : function() { | 219 | initialize : function() { |
| 187 | $("#menu_item_list").sortable({ | 220 | $("#menu_item_list").sortable({ |
| 188 | axis: 'y', | 221 | axis: 'y', |
| @@ -205,24 +238,24 @@ menu_item_sorter = { | |||
| 205 | } | 238 | } |
| 206 | }); | 239 | }); |
| 207 | }, | 240 | }, |
| 208 | 241 | ||
| 209 | placeholder_helper : function(e,ui) { | 242 | placeholder_helper : function(e,ui) { |
| 210 | $(".ui-state-highlight").html("<td colspan='100%'></td>"); | 243 | $(".ui-state-highlight").html("<td colspan='100%'></td>"); |
| 211 | } | 244 | } |
| 212 | } | 245 | } |
| 213 | 246 | ||
| 214 | image_interface = { | 247 | image_interface = { |
| 215 | 248 | ||
| 216 | initialize : function() { | 249 | initialize : function() { |
| 217 | 250 | ||
| 218 | $("#image_browser").hide(); | 251 | $("#image_browser").hide(); |
| 219 | image_interface.initialize_sortable_image_box(); | 252 | image_interface.initialize_sortable_image_box(); |
| 220 | image_interface.connect_browser_and_box(); | 253 | image_interface.connect_browser_and_box(); |
| 221 | image_interface.set_droppable_behavior(); | 254 | image_interface.set_droppable_behavior(); |
| 222 | image_interface.bind_image_browser_toggle(); | 255 | image_interface.bind_image_browser_toggle(); |
| 223 | }, | 256 | }, |
| 224 | 257 | ||
| 225 | 258 | ||
| 226 | set_droppable_behavior : function() { | 259 | set_droppable_behavior : function() { |
| 227 | $("ul#image_box").droppable({ | 260 | $("ul#image_box").droppable({ |
| 228 | out : function(event, ui) { | 261 | out : function(event, ui) { |
| @@ -238,7 +271,7 @@ image_interface = { | |||
| 238 | } | 271 | } |
| 239 | }); | 272 | }); |
| 240 | }, | 273 | }, |
| 241 | 274 | ||
| 242 | connect_browser_and_box : function() { | 275 | connect_browser_and_box : function() { |
| 243 | $("#image_browser ul li").draggable({ | 276 | $("#image_browser ul li").draggable({ |
| 244 | connectToSortable : 'ul#image_box', | 277 | connectToSortable : 'ul#image_box', |
| @@ -246,9 +279,9 @@ image_interface = { | |||
| 246 | revert : 'invalid' | 279 | revert : 'invalid' |
| 247 | }); | 280 | }); |
| 248 | }, | 281 | }, |
| 249 | 282 | ||
| 250 | initialize_sortable_image_box : function() { | 283 | initialize_sortable_image_box : function() { |
| 251 | 284 | ||
| 252 | $("ul#image_box").sortable({ | 285 | $("ul#image_box").sortable({ |
| 253 | revert : true, | 286 | revert : true, |
| 254 | update : function(event, ui) { | 287 | update : function(event, ui) { |
| @@ -263,9 +296,9 @@ image_interface = { | |||
| 263 | } | 296 | } |
| 264 | }); | 297 | }); |
| 265 | } | 298 | } |
| 266 | }); | 299 | }); |
| 267 | }, | 300 | }, |
| 268 | 301 | ||
| 269 | bind_image_browser_toggle : function() { | 302 | bind_image_browser_toggle : function() { |
| 270 | $("#image_browser_toggle").bind("click", function(){ | 303 | $("#image_browser_toggle").bind("click", function(){ |
| 271 | if ($("#image_browser_toggle").attr("class") == "unselected") { | 304 | if ($("#image_browser_toggle").attr("class") == "unselected") { |
| @@ -276,10 +309,110 @@ image_interface = { | |||
| 276 | $("#image_browser_toggle").attr("class", "unselected"); | 309 | $("#image_browser_toggle").attr("class", "unselected"); |
| 277 | $("#image_browser").hide(); | 310 | $("#image_browser").hide(); |
| 278 | } | 311 | } |
| 279 | 312 | ||
| 280 | return false; | 313 | return false; |
| 281 | }); | 314 | }); |
| 282 | } | 315 | } |
| 283 | } | 316 | } |
| 284 | |||
| 285 | 317 | ||
| 318 | rrule_builder = { | ||
| 319 | initialize : function() { | ||
| 320 | rrule_builder.try_populate_from_rrule($("#event_rrule").val()); | ||
| 321 | |||
| 322 | $("input[name='rrule_freq']").bind("change", function() { | ||
| 323 | if ($(this).val() === "weekly") { rrule_builder.show_weekly_options(); } | ||
| 324 | else { rrule_builder.show_monthly_options(); } | ||
| 325 | rrule_builder.sync(); | ||
| 326 | }); | ||
| 327 | $("#rrule_monthly_ordinal").bind("change", function() { | ||
| 328 | $("#rrule_ordinal_fields").toggle($(this).is(":checked")); | ||
| 329 | rrule_builder.sync(); | ||
| 330 | }); | ||
| 331 | $("#rrule_exclude_month").bind("change", function() { | ||
| 332 | $("#rrule_excluded_month").toggle($(this).is(":checked")); | ||
| 333 | rrule_builder.sync(); | ||
| 334 | }); | ||
| 335 | $("#rrule_builder input, #rrule_builder select").bind("change", rrule_builder.sync); | ||
| 336 | }, | ||
| 337 | |||
| 338 | show_weekly_options : function() { | ||
| 339 | $("#rrule_weekly_options").show(); | ||
| 340 | $("#rrule_monthly_options").hide(); | ||
| 341 | }, | ||
| 342 | show_monthly_options : function() { | ||
| 343 | $("#rrule_weekly_options").hide(); | ||
| 344 | $("#rrule_monthly_options").show(); | ||
| 345 | }, | ||
| 346 | |||
| 347 | sync : function() { | ||
| 348 | var freq = $("input[name='rrule_freq']:checked").val(); | ||
| 349 | var parts = []; | ||
| 350 | |||
| 351 | if (freq === "weekly") { | ||
| 352 | parts.push("FREQ=WEEKLY"); | ||
| 353 | if ($("#rrule_biweekly").is(":checked")) parts.push("INTERVAL=2"); | ||
| 354 | var days = []; | ||
| 355 | $("input[id^='rrule_byday_']:checked").each(function() { | ||
| 356 | days.push($(this).attr("id").replace("rrule_byday_", "")); | ||
| 357 | }); | ||
| 358 | if (days.length > 0) parts.push("BYDAY=" + days.join(",")); | ||
| 359 | } else { | ||
| 360 | parts.push("FREQ=MONTHLY"); | ||
| 361 | if ($("#rrule_monthly_ordinal").is(":checked")) { | ||
| 362 | parts.push("BYDAY=" + $("#rrule_ordinal").val() + $("#rrule_ordinal_day").val()); | ||
| 363 | } | ||
| 364 | } | ||
| 365 | |||
| 366 | if ($("#rrule_exclude_month").is(":checked")) { | ||
| 367 | var excluded = parseInt($("#rrule_excluded_month").val(), 10); | ||
| 368 | var months = []; | ||
| 369 | for (var m = 1; m <= 12; m++) { if (m !== excluded) months.push(m); } | ||
| 370 | parts.push("BYMONTH=" + months.join(",")); | ||
| 371 | } | ||
| 372 | |||
| 373 | $("#event_rrule").val(parts.join(";")); | ||
| 374 | }, | ||
| 375 | |||
| 376 | try_populate_from_rrule : function(rrule) { | ||
| 377 | if (!rrule) return; | ||
| 378 | var parts = {}; | ||
| 379 | rrule.split(";").forEach(function(p) { | ||
| 380 | var kv = p.split("="); | ||
| 381 | parts[kv[0]] = kv[1]; | ||
| 382 | }); | ||
| 383 | if (parts.COUNT || parts.UNTIL) return; | ||
| 384 | |||
| 385 | if (parts.FREQ === "WEEKLY") { | ||
| 386 | $("input[name='rrule_freq'][value='weekly']").prop("checked", true); | ||
| 387 | rrule_builder.show_weekly_options(); | ||
| 388 | if (parts.INTERVAL === "2") $("#rrule_biweekly").prop("checked", true); | ||
| 389 | if (parts.BYDAY) { | ||
| 390 | parts.BYDAY.split(",").forEach(function(code) { | ||
| 391 | $("#rrule_byday_" + code).prop("checked", true); | ||
| 392 | }); | ||
| 393 | } | ||
| 394 | } else if (parts.FREQ === "MONTHLY") { | ||
| 395 | $("input[name='rrule_freq'][value='monthly']").prop("checked", true); | ||
| 396 | rrule_builder.show_monthly_options(); | ||
| 397 | var match = parts.BYDAY && parts.BYDAY.match(/^(-?\d+)([A-Z]{2})$/); | ||
| 398 | if (match) { | ||
| 399 | $("#rrule_monthly_ordinal").prop("checked", true); | ||
| 400 | $("#rrule_ordinal_fields").show(); | ||
| 401 | $("#rrule_ordinal").val(match[1]); | ||
| 402 | $("#rrule_ordinal_day").val(match[2]); | ||
| 403 | } | ||
| 404 | } else { | ||
| 405 | return; | ||
| 406 | } | ||
| 407 | |||
| 408 | if (parts.BYMONTH) { | ||
| 409 | var included = parts.BYMONTH.split(",").map(function(s) { return parseInt(s, 10); }); | ||
| 410 | var missing = []; | ||
| 411 | for (var m = 1; m <= 12; m++) { if (included.indexOf(m) === -1) missing.push(m); } | ||
| 412 | if (missing.length === 1) { | ||
| 413 | $("#rrule_exclude_month").prop("checked", true); | ||
| 414 | $("#rrule_excluded_month").val(missing[0]).show(); | ||
| 415 | } | ||
| 416 | } | ||
| 417 | } | ||
| 418 | }; | ||
diff --git a/public/javascripts/admin_search.js b/public/javascripts/admin_search.js index 9bf878b..7604bb7 100644 --- a/public/javascripts/admin_search.js +++ b/public/javascripts/admin_search.js | |||
| @@ -1,5 +1,4 @@ | |||
| 1 | admin_search = { | 1 | admin_search = { |
| 2 | |||
| 3 | initialize : function() { | 2 | initialize : function() { |
| 4 | $(document).bind("keydown", 'Alt+f', function(){ | 3 | $(document).bind("keydown", 'Alt+f', function(){ |
| 5 | admin_search.display_toggle(); | 4 | admin_search.display_toggle(); |
| @@ -20,25 +19,12 @@ admin_search = { | |||
| 20 | } | 19 | } |
| 21 | }); | 20 | }); |
| 22 | 21 | ||
| 23 | $("#search_term").bind("input", function() { | 22 | initSearchPicker({ |
| 24 | if (!$('#search_widget').is(':visible')) return; | 23 | inputSelector: "#search_term", |
| 25 | if ($(this).val()) { | 24 | resultsSelector: "#menu_search_results", |
| 26 | $.ajax({ | 25 | url: ADMIN_SEARCH_URL, |
| 27 | type: "GET", | 26 | isActive: function() { return $('#search_widget').is(':visible'); }, |
| 28 | url: ADMIN_SEARCH_URL, | 27 | resultsHeaderHtml: "<p class='search_more'>Press Enter to see all results ⏎</p>" |
| 29 | data: "search_term=" + $(this).val(), | ||
| 30 | dataType: "json", | ||
| 31 | success: function(results) { | ||
| 32 | admin_search.show_results(results); | ||
| 33 | }, | ||
| 34 | error: function(xhr, status, error) { | ||
| 35 | console.log("Ajax error:", status, error, xhr.status, xhr.responseText); | ||
| 36 | } | ||
| 37 | }); | ||
| 38 | } else { | ||
| 39 | $('#search_results').slideUp(); | ||
| 40 | $('#search_results').empty(); | ||
| 41 | } | ||
| 42 | }); | 28 | }); |
| 43 | }, | 29 | }, |
| 44 | 30 | ||
| @@ -49,75 +35,155 @@ admin_search = { | |||
| 49 | $('#search_widget').fadeIn(); | 35 | $('#search_widget').fadeIn(); |
| 50 | $('#search_term').focus(); | 36 | $('#search_term').focus(); |
| 51 | } | 37 | } |
| 52 | }, | ||
| 53 | |||
| 54 | show_results : function(results) { | ||
| 55 | $('#search_results').empty(); | ||
| 56 | if (results.length) { | ||
| 57 | $('#search_results').append( | ||
| 58 | "<p class='search_more'>Press Enter to see all results ⏎</p>" | ||
| 59 | ); | ||
| 60 | } | ||
| 61 | for (result in results) { | ||
| 62 | $('#search_results').append( | ||
| 63 | "<p><a href='" + results[result].node_path + "'>" + | ||
| 64 | results[result].title + | ||
| 65 | "<span class='result_path'>" + results[result].unique_name + "</span>" + | ||
| 66 | "</a></p>" | ||
| 67 | ); | ||
| 68 | } | ||
| 69 | $('#search_results').slideDown(); | ||
| 70 | } | 38 | } |
| 71 | }; | 39 | }; |
| 72 | 40 | ||
| 73 | menu_items = { | 41 | function initSearchPicker(options) { |
| 42 | var inputSelector = options.inputSelector; | ||
| 43 | var resultsSelector = options.resultsSelector; | ||
| 44 | var url = options.url || ADMIN_MENU_SEARCH_URL; | ||
| 45 | var onSelect = options.onSelect; | ||
| 46 | var isActive = options.isActive; | ||
| 47 | var resultsHeaderHtml = options.resultsHeaderHtml; | ||
| 48 | var renderResults = options.renderResults; | ||
| 49 | var loadOnFocus = options.loadOnFocus; // optional, fires an initial search on focus with no term typed yet | ||
| 50 | var requestId = 0; | ||
| 51 | var timeout; | ||
| 52 | var results = $(resultsSelector); | ||
| 74 | 53 | ||
| 75 | initialize_search : function() { | 54 | function runSearch(term) { |
| 76 | $("#menu_search_term").bind("keyup", function() { | 55 | var thisRequest = ++requestId; |
| 77 | if ($(this).attr("value")) { | 56 | $.ajax({ |
| 78 | $.ajax({ | 57 | type: "GET", |
| 79 | type: "GET", | 58 | url: url, |
| 80 | url: ADMIN_MENU_SEARCH_URL, | 59 | data: "search_term=" + term, |
| 81 | data: "search_term=" + $(this).attr("value"), | 60 | dataType: "json", |
| 82 | dataType: "json", | 61 | success: function(data) { |
| 83 | success : function(results) { | 62 | if (thisRequest !== requestId) return; |
| 84 | menu_items.show_results(results); | 63 | results.empty(); |
| 64 | |||
| 65 | var found; | ||
| 66 | if (renderResults) { | ||
| 67 | found = renderResults(data, results, resultsHeaderHtml); | ||
| 68 | } else { | ||
| 69 | if (data.length && resultsHeaderHtml) { | ||
| 70 | results.append(resultsHeaderHtml); | ||
| 85 | } | 71 | } |
| 86 | }); | 72 | found = false; |
| 87 | } | 73 | for (var i = 0; i < data.length; i++) { |
| 88 | else { | 74 | (function(node) { |
| 89 | $('#search_results').slideUp(); | 75 | var link; |
| 90 | $('#search_results').empty(); | 76 | if (onSelect) { |
| 77 | link = $( | ||
| 78 | "<p><a href='#'>" + node.title + | ||
| 79 | "<span class='result_path'>" + node.unique_name + "</span></a></p>" | ||
| 80 | ); | ||
| 81 | link.find("a").bind("click", function() { | ||
| 82 | onSelect(node); | ||
| 83 | results.slideUp(); | ||
| 84 | results.empty(); | ||
| 85 | return false; | ||
| 86 | }); | ||
| 87 | } else { | ||
| 88 | link = $( | ||
| 89 | "<p><a href='" + node.node_path + "'>" + node.title + | ||
| 90 | "<span class='result_path'>" + node.unique_name + "</span></a></p>" | ||
| 91 | ); | ||
| 92 | } | ||
| 93 | results.append(link); | ||
| 94 | found = true; | ||
| 95 | })(data[i]); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | if (found) { | ||
| 100 | results.slideDown(); | ||
| 101 | } else { | ||
| 102 | results.slideUp(); | ||
| 103 | } | ||
| 104 | }, | ||
| 105 | error: function(xhr, status, error) { | ||
| 106 | console.log("Ajax error:", status, error, xhr.status, xhr.responseText); | ||
| 91 | } | 107 | } |
| 92 | }); | 108 | }); |
| 93 | }, | 109 | } |
| 94 | 110 | ||
| 95 | show_results : function(results) { | 111 | $(inputSelector).bind("input", function() { |
| 96 | $("#search_results").empty(); | 112 | if (isActive && !isActive()) return; |
| 97 | for (result in results) { | ||
| 98 | var link = $(("<a href='#'>"+ results[result].title + "</a>")); | ||
| 99 | $(link).bind("click", menu_items.link_closure(results[result])); | ||
| 100 | 113 | ||
| 114 | var term = $(this).val(); | ||
| 115 | clearTimeout(timeout); | ||
| 101 | 116 | ||
| 102 | // Sometimes I don't get jquery; wrap() didn't work *sigh* | 117 | if (!term) { |
| 103 | // Guess I'll need a book someday or another framework | 118 | results.slideUp(); |
| 104 | var wrapper = $("<div></div>"); | 119 | results.empty(); |
| 105 | $(wrapper).append(link) | 120 | return; |
| 121 | } | ||
| 106 | 122 | ||
| 107 | $("#search_results").append(wrapper); | 123 | timeout = setTimeout(function() { runSearch(term); }, 250); |
| 124 | }); | ||
| 108 | 125 | ||
| 109 | } | 126 | if (loadOnFocus) { |
| 110 | }, | 127 | $(inputSelector).bind("focus", function() { |
| 128 | if (isActive && !isActive()) return; | ||
| 129 | if ($(this).val()) return; // the input handler above already covers a real term | ||
| 130 | runSearch(""); | ||
| 131 | }); | ||
| 132 | } | ||
| 133 | } | ||
| 111 | 134 | ||
| 112 | link_closure : function(node) { | 135 | dashboard_search = { |
| 113 | var barf = function(){ | 136 | initialize : function() { |
| 114 | $("#menu_item_node_id").val(node.node_id); | 137 | initSearchPicker({ |
| 115 | $("#menu_item_path").val("/" + node.unique_name); | 138 | inputSelector: "#dashboard_search_term", |
| 116 | $("#menu_item_title").val(node.title); | 139 | resultsSelector: "#dashboard_search_results", |
| 117 | return false; | 140 | url: DASHBOARD_SEARCH_URL, |
| 118 | } | 141 | resultsHeaderHtml: "<p class='search_more'>Press Enter to see all results ⏎</p>", |
| 142 | renderResults: function(data, results, resultsHeaderHtml) { | ||
| 143 | var found = false; | ||
| 144 | |||
| 145 | if ((data.tags.length || data.nodes.length) && resultsHeaderHtml) { | ||
| 146 | results.append(resultsHeaderHtml); | ||
| 147 | } | ||
| 148 | |||
| 149 | if (data.tags.length) { | ||
| 150 | results.append("<p class='search_group_label'>Tags</p>"); | ||
| 151 | var tag_row = $("<div class='search_tag_row'></div>"); | ||
| 152 | data.tags.forEach(function(tag) { | ||
| 153 | tag_row.append("<a class='search_tag_pill' href='" + tag.tag_path + "'>" + tag.name + "</a>"); | ||
| 154 | found = true; | ||
| 155 | }); | ||
| 156 | results.append(tag_row); | ||
| 157 | } | ||
| 158 | |||
| 159 | if (data.nodes.length) { | ||
| 160 | results.append("<p class='search_group_label'>Pages</p>"); | ||
| 161 | data.nodes.forEach(function(node) { | ||
| 162 | results.append( | ||
| 163 | "<p><a href='" + node.node_path + "'>" + node.title + | ||
| 164 | "<span class='result_path'>" + node.unique_name + "</span></a></p>" | ||
| 165 | ); | ||
| 166 | found = true; | ||
| 167 | }); | ||
| 168 | } | ||
| 169 | |||
| 170 | return found; | ||
| 171 | } | ||
| 172 | }); | ||
| 173 | } | ||
| 174 | }; | ||
| 119 | 175 | ||
| 120 | return barf; | 176 | menu_items = { |
| 177 | initialize_search : function() { | ||
| 178 | initSearchPicker({ | ||
| 179 | inputSelector: "#menu_search_term", | ||
| 180 | resultsSelector: "#menu_item_search_results", | ||
| 181 | onSelect: function(node) { | ||
| 182 | $("#menu_item_node_id").val(node.node_id); | ||
| 183 | $("#menu_item_path").val("/" + node.unique_name); | ||
| 184 | $("#menu_item_title").val(node.title); | ||
| 185 | } | ||
| 186 | }); | ||
| 121 | } | 187 | } |
| 122 | }; | 188 | }; |
| 123 | 189 | ||
| @@ -125,136 +191,93 @@ parent_search = { | |||
| 125 | initialize_search : function() { | 191 | initialize_search : function() { |
| 126 | parent_search.initialize_radio_buttons(); | 192 | parent_search.initialize_radio_buttons(); |
| 127 | 193 | ||
| 128 | $("#parent_search_term").bind("keyup", function() { | 194 | initSearchPicker({ |
| 129 | if ($(this).attr("value")) { | 195 | inputSelector: "#parent_search_term", |
| 130 | $.ajax({ | 196 | resultsSelector: "#parent_search_results", |
| 131 | type: "GET", | 197 | onSelect: function(node) { |
| 132 | url: ADMIN_MENU_SEARCH_URL, | 198 | $("#parent_search_term").val(node.title); |
| 133 | data: "search_term=" + $(this).attr("value"), | 199 | $("#parent_id").val(node.node_id).attr("data-unique-name", node.unique_name); |
| 134 | dataType: "json", | 200 | parent_search.update_resulting_path(); |
| 135 | success : function(results) { | ||
| 136 | parent_search.show_results(results); | ||
| 137 | } | ||
| 138 | }); | ||
| 139 | } | ||
| 140 | else { | ||
| 141 | $('#search_results').slideUp(); | ||
| 142 | $('#search_results').empty(); | ||
| 143 | } | 201 | } |
| 144 | }); | 202 | }); |
| 145 | }, | ||
| 146 | 203 | ||
| 147 | show_results : function(results) { | 204 | $("#title").bind("input", function() { |
| 148 | $("#search_results").empty(); | 205 | parent_search.update_resulting_path(); |
| 149 | var found = false; | 206 | }); |
| 150 | for (result in results) { | ||
| 151 | var link = $(("<a href='#'>"+ results[result].title + "</a>")); | ||
| 152 | $(link).bind("click", parent_search.link_closure(results[result])); | ||
| 153 | 207 | ||
| 208 | $("#copy_resulting_path").bind("click", function() { | ||
| 209 | var path = $("#resulting_path").text(); | ||
| 210 | if (path === "—" || !navigator.clipboard) return; | ||
| 211 | navigator.clipboard.writeText(path); | ||
| 212 | }); | ||
| 213 | }, | ||
| 154 | 214 | ||
| 155 | // Sometimes I don't get jquery; wrap() didn't work *sigh* | 215 | update_resulting_path : function() { |
| 156 | // Guess I'll need a book someday or another framework | 216 | var kind = $("input[name='kind']:checked"); |
| 157 | var wrapper = $("<div></div>"); | 217 | var title = $("#title").val(); |
| 158 | $(wrapper).append(link); | ||
| 159 | 218 | ||
| 160 | $("#search_results").append(wrapper); | 219 | if (title === "") { |
| 161 | found = true; | 220 | $("#resulting_path").text("—"); |
| 221 | return; | ||
| 162 | } | 222 | } |
| 163 | if (found) | ||
| 164 | $('#search_results').slideDown(); | ||
| 165 | }, | ||
| 166 | 223 | ||
| 167 | link_closure : function(node) { | 224 | var prefix = kind.val() === "generic" |
| 168 | var barf = function(){ | 225 | ? ($("#parent_id").attr("data-unique-name") || "") |
| 169 | $("#parent_search_term").attr("value", node.title); | 226 | : (kind.attr("data-path-prefix") || ""); |
| 170 | $("#parent_id").attr("value", node.node_id); | ||
| 171 | $('#search_results').slideUp(); | ||
| 172 | $('#search_results').empty(); | ||
| 173 | return false; | ||
| 174 | } | ||
| 175 | 227 | ||
| 176 | return barf; | 228 | clearTimeout(parent_search.path_timeout); |
| 229 | parent_search.path_timeout = setTimeout(function() { | ||
| 230 | $.get(PARAMETERIZE_PREVIEW_URL, { title: title }, function(slug) { | ||
| 231 | $("#resulting_path").text(window.location.origin + "/" + (prefix ? prefix + "/" : "") + slug); | ||
| 232 | }); | ||
| 233 | }, 300); | ||
| 177 | }, | 234 | }, |
| 178 | 235 | ||
| 179 | initialize_radio_buttons : function() { | 236 | initialize_radio_buttons : function() { |
| 180 | $("#kind_top_level").bind("change", function(){ | 237 | parent_search.sync_parent_field(); |
| 181 | $("#parent_search_field").hide(); | 238 | $("input[name='kind']").bind("change", function() { |
| 182 | }); | 239 | parent_search.sync_parent_field(); |
| 183 | 240 | parent_search.update_resulting_path(); | |
| 184 | $("#kind_update").bind("change", function(){ | ||
| 185 | $("#parent_search_field").hide(); | ||
| 186 | }); | 241 | }); |
| 242 | }, | ||
| 187 | 243 | ||
| 188 | $("#kind_press_release").bind("change", function(){ | 244 | sync_parent_field : function() { |
| 245 | var kind = $("input[name='kind']:checked").val(); | ||
| 246 | if (kind === "generic") { | ||
| 247 | $("#parent_search_field").show(); | ||
| 248 | } else { | ||
| 189 | $("#parent_search_field").hide(); | 249 | $("#parent_search_field").hide(); |
| 190 | }); | 250 | } |
| 251 | } | ||
| 252 | }; | ||
| 191 | 253 | ||
| 192 | $("#kind_generic").bind("change", function(){ | 254 | move_to_search = { |
| 193 | $("#parent_search_field").show(); | 255 | initialize_search : function() { |
| 256 | initSearchPicker({ | ||
| 257 | inputSelector: "#move_to_search_term", | ||
| 258 | resultsSelector: "#move_to_search_results", | ||
| 259 | onSelect: function(node) { | ||
| 260 | $("#move_to_search_term").val(node.title); | ||
| 261 | $("#node_staged_parent_id").val(node.node_id); | ||
| 262 | } | ||
| 194 | }); | 263 | }); |
| 195 | |||
| 196 | } | 264 | } |
| 197 | } | 265 | }; |
| 198 | 266 | ||
| 199 | move_to_search = { | 267 | event_search = { |
| 200 | initialize_search : function() { | 268 | initialize_search : function() { |
| 201 | $("#move_to_search_term").bind("keyup", function() { move_to_search.do_search($(this))}); | 269 | initSearchPicker({ |
| 202 | $("#move_to_search_term").bind("keydown", function() { move_to_search.do_search($(this))}); | 270 | inputSelector: "#event_node_search_term", |
| 203 | $("#move_to_search_term").bind("keypress", function() { move_to_search.do_search($(this))}); | 271 | resultsSelector: "#event_search_results", |
| 204 | $("#move_to_search_term").bind("paste", function() { move_to_search.do_search($(this))}); | 272 | onSelect: function(node) { |
| 205 | $("#move_to_search_term").bind("cut", function() { move_to_search.do_search($(this))}); | 273 | $("#event_node_search_term").val(node.title); |
| 206 | }, | 274 | $("#event_node_id").val(node.node_id); |
| 207 | 275 | ||
| 208 | do_search : function(_this) { | 276 | var title_field = $("#event_title"); |
| 209 | if (_this.attr("value")) { | 277 | if (title_field.val() === "") { |
| 210 | $.ajax({ | 278 | $("#event_title_hint").text("Using \"" + node.title + "\" from the associated node."); |
| 211 | type: "GET", | ||
| 212 | url: ADMIN_MENU_SEARCH_URL, | ||
| 213 | data: "search_term=" + _this.attr("value"), | ||
| 214 | dataType: "json", | ||
| 215 | success : function(results) { | ||
| 216 | move_to_search.show_results(results); | ||
| 217 | } | 279 | } |
| 218 | }); | 280 | } |
| 219 | } | 281 | }); |
| 220 | else { | ||
| 221 | $('#search_results').slideUp(); | ||
| 222 | $('#search_results').empty(); | ||
| 223 | } | ||
| 224 | }, | ||
| 225 | |||
| 226 | show_results : function(results) { | ||
| 227 | $("#search_results").empty(); | ||
| 228 | var found = false; | ||
| 229 | for (result in results) { | ||
| 230 | var link = $(("<a href='#'>"+ results[result].title + "</a>")); | ||
| 231 | $(link).bind("click", move_to_search.link_closure(results[result])); | ||
| 232 | |||
| 233 | |||
| 234 | // Sometimes I don't get jquery; wrap() didn't work *sigh* | ||
| 235 | // Guess I'll need a book someday or another framework | ||
| 236 | var wrapper = $("<div></div>"); | ||
| 237 | $(wrapper).append(link) | ||
| 238 | |||
| 239 | $("#search_results").append(wrapper); | ||
| 240 | found = true; | ||
| 241 | } | ||
| 242 | if (found) | ||
| 243 | $('#search_results').slideDown(); | ||
| 244 | else | ||
| 245 | $('#search_results').slideUp(); | ||
| 246 | |||
| 247 | }, | ||
| 248 | |||
| 249 | link_closure : function(node) { | ||
| 250 | var barf = function(){ | ||
| 251 | $("#move_to_search_term").attr("value", node.title); | ||
| 252 | $("#node_staged_parent_id").attr("value", node.node_id); | ||
| 253 | $('#search_results').slideUp(); | ||
| 254 | $('#search_results').empty(); | ||
| 255 | return false; | ||
| 256 | } | ||
| 257 | |||
| 258 | return barf; | ||
| 259 | } | 282 | } |
| 260 | } | 283 | }; |
diff --git a/public/javascripts/cacycle_diff.js b/public/javascripts/cacycle_diff.js deleted file mode 100644 index 24f9d0b..0000000 --- a/public/javascripts/cacycle_diff.js +++ /dev/null | |||
| @@ -1,1112 +0,0 @@ | |||
| 1 | // <pre><nowiki> | ||
| 2 | |||
| 3 | /* | ||
| 4 | |||
| 5 | Name: diff.js | ||
| 6 | Version: 0.9.5a (April 6, 2008) | ||
| 7 | Info: http://en.wikipedia.org/wiki/User:Cacycle/diff | ||
| 8 | Code: http://en.wikipedia.org/wiki/User:Cacycle/diff.js | ||
| 9 | |||
| 10 | JavaScript diff algorithm by [[en:User:Cacycle]] (http://en.wikipedia.org/wiki/User_talk:Cacycle). | ||
| 11 | Outputs html/css-formatted new text with highlighted deletions, inserts, and block moves. | ||
| 12 | |||
| 13 | The program uses cross-browser code and should work with all modern browsers. It has been tested with: | ||
| 14 | * Mozilla Firefox 1.5.0.1 | ||
| 15 | * Mozilla SeaMonkey 1.0 | ||
| 16 | * Opera 8.53 | ||
| 17 | * Internet Explorer 6.0.2900.2180 | ||
| 18 | * Internet Explorer 7.0.5730.11 | ||
| 19 | This program is also compatibel with Greasemonkey | ||
| 20 | |||
| 21 | An implementation of the word-based algorithm from: | ||
| 22 | |||
| 23 | Communications of the ACM 21(4):264 (1978) | ||
| 24 | http://doi.acm.org/10.1145/359460.359467 | ||
| 25 | |||
| 26 | With the following additional feature: | ||
| 27 | |||
| 28 | * Word types have been optimized for MediaWiki source texts | ||
| 29 | * Additional post-pass 5 code for resolving islands caused by adding | ||
| 30 | two common words at the end of sequences of common words | ||
| 31 | * Additional detection of block borders and color coding of moved blocks and their original position | ||
| 32 | * Optional "intelligent" omission of unchanged parts from the output | ||
| 33 | |||
| 34 | This code is used by the MediaWiki in-browser text editors [[en:User:Cacycle/editor]] and [[en:User:Cacycle/wikEd]] | ||
| 35 | and the enhanced diff view tool wikEdDiff [[en:User:Cacycle/wikEd]]. | ||
| 36 | |||
| 37 | Usage: var htmlText = WDiffString(oldText, newText); | ||
| 38 | |||
| 39 | This code has been released into the public domain. | ||
| 40 | |||
| 41 | Datastructures: | ||
| 42 | |||
| 43 | text: an object that holds all text related datastructures | ||
| 44 | .newWords: consecutive words of the new text (N) | ||
| 45 | .oldWords: consecutive words of the old text (O) | ||
| 46 | .newToOld: array of corresponding word number in old text (NA) | ||
| 47 | .oldToNew: array of corresponding word number in new text (OA) | ||
| 48 | .message: output message for testing purposes | ||
| 49 | |||
| 50 | symbol['word']: symbol table for passes 1 - 3, holds words as a hash | ||
| 51 | .newCtr: new word occurences counter (NC) | ||
| 52 | .oldCtr: old word occurences counter (OC) | ||
| 53 | .toNew: table last old word number | ||
| 54 | .toOld: last new word number (OLNA) | ||
| 55 | |||
| 56 | block: an object that holds block move information | ||
| 57 | blocks indexed after new text: | ||
| 58 | .newStart: new text word number of start of this block | ||
| 59 | .newLength: element number of this block including non-words | ||
| 60 | .newWords: true word number of this block | ||
| 61 | .newNumber: corresponding block index in old text | ||
| 62 | .newBlock: moved-block-number of a block that has been moved here | ||
| 63 | .newLeft: moved-block-number of a block that has been moved from this border leftwards | ||
| 64 | .newRight: moved-block-number of a block that has been moved from this border rightwards | ||
| 65 | .newLeftIndex: index number of a block that has been moved from this border leftwards | ||
| 66 | .newRightIndex: index number of a block that has been moved from this border rightwards | ||
| 67 | blocks indexed after old text: | ||
| 68 | .oldStart: word number of start of this block | ||
| 69 | .oldToNew: corresponding new text word number of start | ||
| 70 | .oldLength: element number of this block including non-words | ||
| 71 | .oldWords: true word number of this block | ||
| 72 | |||
| 73 | */ | ||
| 74 | |||
| 75 | |||
| 76 | // css for change indicators | ||
| 77 | if (typeof(wDiffStyleDelete) == 'undefined') { window.wDiffStyleDelete = 'font-weight: normal; text-decoration: none; color: #fff; background-color: #990033;'; } | ||
| 78 | if (typeof(wDiffStyleInsert) == 'undefined') { window.wDiffStyleInsert = 'font-weight: normal; text-decoration: none; color: #fff; background-color: #009933;'; } | ||
| 79 | if (typeof(wDiffStyleMoved) == 'undefined') { window.wDiffStyleMoved = 'font-weight: bold; color: #000; vertical-align: text-bottom; font-size: xx-small; padding: 0; border: solid 1px;'; } | ||
| 80 | if (typeof(wDiffStyleBlock) == 'undefined') { window.wDiffStyleBlock = [ | ||
| 81 | 'color: #000; background-color: #ffff80;', | ||
| 82 | 'color: #000; background-color: #c0ffff;', | ||
| 83 | 'color: #000; background-color: #ffd0f0;', | ||
| 84 | 'color: #000; background-color: #ffe080;', | ||
| 85 | 'color: #000; background-color: #aaddff;', | ||
| 86 | 'color: #000; background-color: #ddaaff;', | ||
| 87 | 'color: #000; background-color: #ffbbbb;', | ||
| 88 | 'color: #000; background-color: #d8ffa0;', | ||
| 89 | 'color: #000; background-color: #d0d0d0;' | ||
| 90 | ]; } | ||
| 91 | |||
| 92 | // html for change indicators, {number} is replaced by the block number | ||
| 93 | // {block} is replaced by the block style, class and html comments are important for shortening the output | ||
| 94 | if (typeof(wDiffHtmlMovedRight) == 'undefined') { window.wDiffHtmlMovedRight = '<input class="wDiffHtmlMovedRight" type="button" value=">" style="' + wDiffStyleMoved + ' {block}"><!--wDiffHtmlMovedRight-->'; } | ||
| 95 | if (typeof(wDiffHtmlMovedLeft) == 'undefined') { window.wDiffHtmlMovedLeft = '<input class="wDiffHtmlMovedLeft" type="button" value="<" style="' + wDiffStyleMoved + ' {block}"><!--wDiffHtmlMovedLeft-->'; } | ||
| 96 | |||
| 97 | if (typeof(wDiffHtmlBlockStart) == 'undefined') { window.wDiffHtmlBlockStart = '<span class="wDiffHtmlBlock" style="{block}">'; } | ||
| 98 | if (typeof(wDiffHtmlBlockEnd) == 'undefined') { window.wDiffHtmlBlockEnd = '</span><!--wDiffHtmlBlock-->'; } | ||
| 99 | |||
| 100 | if (typeof(wDiffHtmlDeleteStart) == 'undefined') { window.wDiffHtmlDeleteStart = '<span class="wDiffHtmlDelete" style="' + wDiffStyleDelete + '">'; } | ||
| 101 | if (typeof(wDiffHtmlDeleteEnd) == 'undefined') { window.wDiffHtmlDeleteEnd = '</span><!--wDiffHtmlDelete-->'; } | ||
| 102 | |||
| 103 | if (typeof(wDiffHtmlInsertStart) == 'undefined') { window.wDiffHtmlInsertStart = '<span class="wDiffHtmlInsert" style="' + wDiffStyleInsert + '">'; } | ||
| 104 | if (typeof(wDiffHtmlInsertEnd) == 'undefined') { window.wDiffHtmlInsertEnd = '</span><!--wDiffHtmlInsert-->'; } | ||
| 105 | |||
| 106 | // minimal number of real words for a moved block (0 for always displaying block move indicators) | ||
| 107 | if (typeof(wDiffBlockMinLength) == 'undefined') { window.wDiffBlockMinLength = 3; } | ||
| 108 | |||
| 109 | // exclude identical sequence starts and endings from change marking | ||
| 110 | if (typeof(wDiffWordDiff) == 'undefined') { window.wDiffWordDiff = true; } | ||
| 111 | |||
| 112 | // enable recursive diff to resolve problematic sequences | ||
| 113 | if (typeof(wDiffRecursiveDiff) == 'undefined') { window.wDiffRecursiveDiff = true; } | ||
| 114 | |||
| 115 | // enable block move display | ||
| 116 | if (typeof(wDiffShowBlockMoves) == 'undefined') { window.wDiffShowBlockMoves = true; } | ||
| 117 | |||
| 118 | // remove unchanged parts from final output | ||
| 119 | |||
| 120 | // characters before diff tag to search for previous heading, paragraph, line break, cut characters | ||
| 121 | if (typeof(wDiffHeadingBefore) == 'undefined') { window.wDiffHeadingBefore = 1500; } | ||
| 122 | if (typeof(wDiffParagraphBefore) == 'undefined') { window.wDiffParagraphBefore = 1500; } | ||
| 123 | if (typeof(wDiffLineBeforeMax) == 'undefined') { window.wDiffLineBeforeMax = 1000; } | ||
| 124 | if (typeof(wDiffLineBeforeMin) == 'undefined') { window.wDiffLineBeforeMin = 500; } | ||
| 125 | if (typeof(wDiffBlankBeforeMax) == 'undefined') { window.wDiffBlankBeforeMax = 1000; } | ||
| 126 | if (typeof(wDiffBlankBeforeMin) == 'undefined') { window.wDiffBlankBeforeMin = 500; } | ||
| 127 | if (typeof(wDiffCharsBefore) == 'undefined') { window.wDiffCharsBefore = 500; } | ||
| 128 | |||
| 129 | // characters after diff tag to search for next heading, paragraph, line break, or characters | ||
| 130 | if (typeof(wDiffHeadingAfter) == 'undefined') { window.wDiffHeadingAfter = 1500; } | ||
| 131 | if (typeof(wDiffParagraphAfter) == 'undefined') { window.wDiffParagraphAfter = 1500; } | ||
| 132 | if (typeof(wDiffLineAfterMax) == 'undefined') { window.wDiffLineAfterMax = 1000; } | ||
| 133 | if (typeof(wDiffLineAfterMin) == 'undefined') { window.wDiffLineAfterMin = 500; } | ||
| 134 | if (typeof(wDiffBlankAfterMax) == 'undefined') { window.wDiffBlankAfterMax = 1000; } | ||
| 135 | if (typeof(wDiffBlankAfterMin) == 'undefined') { window.wDiffBlankAfterMin = 500; } | ||
| 136 | if (typeof(wDiffCharsAfter) == 'undefined') { window.wDiffCharsAfter = 500; } | ||
| 137 | |||
| 138 | // maximal fragment distance to join close fragments | ||
| 139 | if (typeof(wDiffFragmentJoin) == 'undefined') { window.wDiffFragmentJoin = 1000; } | ||
| 140 | if (typeof(wDiffOmittedChars) == 'undefined') { window.wDiffOmittedChars = '…'; } | ||
| 141 | if (typeof(wDiffOmittedLines) == 'undefined') { window.wDiffOmittedLines = '<hr style="height: 2px; margin: 1em 10%;">'; } | ||
| 142 | if (typeof(wDiffNoChange) == 'undefined') { window.wDiffNoChange = '<hr style="height: 2px; margin: 1em 20%;">'; } | ||
| 143 | |||
| 144 | // compatibility fix for old name of main function | ||
| 145 | window.StringDiff = window.WDiffString; | ||
| 146 | |||
| 147 | |||
| 148 | // WDiffString: main program | ||
| 149 | // input: oldText, newText, strings containing the texts | ||
| 150 | // returns: html diff | ||
| 151 | |||
| 152 | window.WDiffString = function(oldText, newText) { | ||
| 153 | |||
| 154 | // IE / Mac fix | ||
| 155 | oldText = oldText.replace(/(\r\n)/g, '\n'); | ||
| 156 | newText = newText.replace(/(\r\n)/g, '\n'); | ||
| 157 | |||
| 158 | var text = {}; | ||
| 159 | text.newWords = []; | ||
| 160 | text.oldWords = []; | ||
| 161 | text.newToOld = []; | ||
| 162 | text.oldToNew = []; | ||
| 163 | text.message = ''; | ||
| 164 | var block = {}; | ||
| 165 | var outText = ''; | ||
| 166 | |||
| 167 | // trap trivial changes: no change | ||
| 168 | if (oldText == newText) { | ||
| 169 | outText = newText; | ||
| 170 | outText = WDiffEscape(outText); | ||
| 171 | outText = WDiffHtmlFormat(outText); | ||
| 172 | return(outText); | ||
| 173 | } | ||
| 174 | |||
| 175 | // trap trivial changes: old text deleted | ||
| 176 | if ( (oldText == null) || (oldText.length == 0) ) { | ||
| 177 | outText = newText; | ||
| 178 | outText = WDiffEscape(outText); | ||
| 179 | outText = WDiffHtmlFormat(outText); | ||
| 180 | outText = wDiffHtmlInsertStart + outText + wDiffHtmlInsertEnd; | ||
| 181 | return(outText); | ||
| 182 | } | ||
| 183 | |||
| 184 | // trap trivial changes: new text deleted | ||
| 185 | if ( (newText == null) || (newText.length == 0) ) { | ||
| 186 | outText = oldText; | ||
| 187 | outText = WDiffEscape(outText); | ||
| 188 | outText = WDiffHtmlFormat(outText); | ||
| 189 | outText = wDiffHtmlDeleteStart + outText + wDiffHtmlDeleteEnd; | ||
| 190 | return(outText); | ||
| 191 | } | ||
| 192 | |||
| 193 | // split new and old text into words | ||
| 194 | WDiffSplitText(oldText, newText, text); | ||
| 195 | |||
| 196 | // calculate diff information | ||
| 197 | WDiffText(text); | ||
| 198 | |||
| 199 | //detect block borders and moved blocks | ||
| 200 | WDiffDetectBlocks(text, block); | ||
| 201 | |||
| 202 | // process diff data into formatted html text | ||
| 203 | outText = WDiffToHtml(text, block); | ||
| 204 | |||
| 205 | // IE fix | ||
| 206 | outText = outText.replace(/> ( *)</g, '> $1<'); | ||
| 207 | |||
| 208 | return(outText); | ||
| 209 | } | ||
| 210 | |||
| 211 | |||
| 212 | // WDiffSplitText: split new and old text into words | ||
| 213 | // input: oldText, newText, strings containing the texts | ||
| 214 | // changes: text.newWords and text.oldWords, arrays containing the texts in arrays of words | ||
| 215 | |||
| 216 | window.WDiffSplitText = function(oldText, newText, text) { | ||
| 217 | |||
| 218 | // convert strange spaces | ||
| 219 | oldText = oldText.replace(/[\t\u000b\u00a0\u2028\u2029]+/g, ' '); | ||
| 220 | newText = newText.replace(/[\t\u000b\u00a0\u2028\u2029]+/g, ' '); | ||
| 221 | |||
| 222 | // split old text into words | ||
| 223 | |||
| 224 | // / | | | | | | | | | | | | | | / | ||
| 225 | var pattern = /[\w]+|\[\[|\]\]|\{\{|\}\}|\n+| +|&\w+;|'''|''|=+|\{\||\|\}|\|\-|./g; | ||
| 226 | var result; | ||
| 227 | do { | ||
| 228 | result = pattern.exec(oldText); | ||
| 229 | if (result != null) { | ||
| 230 | text.oldWords.push(result[0]); | ||
| 231 | } | ||
| 232 | } while (result != null); | ||
| 233 | |||
| 234 | // split new text into words | ||
| 235 | do { | ||
| 236 | result = pattern.exec(newText); | ||
| 237 | if (result != null) { | ||
| 238 | text.newWords.push(result[0]); | ||
| 239 | } | ||
| 240 | } while (result != null); | ||
| 241 | |||
| 242 | return; | ||
| 243 | } | ||
| 244 | |||
| 245 | |||
| 246 | // WDiffText: calculate diff information | ||
| 247 | // input: text.newWords and text.oldWords, arrays containing the texts in arrays of words | ||
| 248 | // optionally for recursive calls: newStart, newEnd, oldStart, oldEnd, recursionLevel | ||
| 249 | // changes: text.newToOld and text.oldToNew, containing the line numbers in the other version | ||
| 250 | |||
| 251 | window.WDiffText = function(text, newStart, newEnd, oldStart, oldEnd, recursionLevel) { | ||
| 252 | |||
| 253 | symbol = new Object(); | ||
| 254 | symbol.newCtr = []; | ||
| 255 | symbol.oldCtr = []; | ||
| 256 | symbol.toNew = []; | ||
| 257 | symbol.toOld = []; | ||
| 258 | |||
| 259 | // set defaults | ||
| 260 | newStart = newStart || 0; | ||
| 261 | newEnd = newEnd || text.newWords.length; | ||
| 262 | oldStart = oldStart || 0; | ||
| 263 | oldEnd = oldEnd || text.oldWords.length; | ||
| 264 | recursionLevel = recursionLevel || 0; | ||
| 265 | |||
| 266 | // limit recursion depth | ||
| 267 | if (recursionLevel > 10) { | ||
| 268 | return; | ||
| 269 | } | ||
| 270 | |||
| 271 | // pass 1: parse new text into symbol table s | ||
| 272 | |||
| 273 | var word; | ||
| 274 | for (var i = newStart; i < newEnd; i ++) { | ||
| 275 | word = text.newWords[i]; | ||
| 276 | |||
| 277 | // add new entry to symbol table | ||
| 278 | if ( symbol[word] == null) { | ||
| 279 | symbol[word] = { newCtr: 0, oldCtr: 0, toNew: null, toOld: null }; | ||
| 280 | } | ||
| 281 | |||
| 282 | // increment symbol table word counter for new text | ||
| 283 | symbol[word].newCtr ++; | ||
| 284 | |||
| 285 | // add last word number in new text | ||
| 286 | symbol[word].toNew = i; | ||
| 287 | } | ||
| 288 | |||
| 289 | // pass 2: parse old text into symbol table | ||
| 290 | |||
| 291 | for (var j = oldStart; j < oldEnd; j ++) { | ||
| 292 | word = text.oldWords[j]; | ||
| 293 | |||
| 294 | // add new entry to symbol table | ||
| 295 | if ( symbol[word] == null) { | ||
| 296 | symbol[word] = { newCtr: 0, oldCtr: 0, toNew: null, toOld: null }; | ||
| 297 | } | ||
| 298 | |||
| 299 | // increment symbol table word counter for old text | ||
| 300 | symbol[word].oldCtr ++; | ||
| 301 | |||
| 302 | // add last word number in old text | ||
| 303 | symbol[word].toOld = j; | ||
| 304 | } | ||
| 305 | |||
| 306 | // pass 3: connect unique words | ||
| 307 | |||
| 308 | for (var i in symbol) { | ||
| 309 | |||
| 310 | // find words in the symbol table that occur only once in both versions | ||
| 311 | if ( (symbol[i].newCtr == 1) && (symbol[i].oldCtr == 1) ) { | ||
| 312 | var toNew = symbol[i].toNew; | ||
| 313 | var toOld = symbol[i].toOld; | ||
| 314 | |||
| 315 | // do not use spaces as unique markers | ||
| 316 | if ( ! /\s/.test( text.newWords[toNew] ) ) { | ||
| 317 | |||
| 318 | // connect from new to old and from old to new | ||
| 319 | text.newToOld[toNew] = toOld; | ||
| 320 | text.oldToNew[toOld] = toNew; | ||
| 321 | } | ||
| 322 | } | ||
| 323 | } | ||
| 324 | |||
| 325 | // pass 4: connect adjacent identical words downwards | ||
| 326 | |||
| 327 | for (var i = newStart; i < newEnd - 1; i ++) { | ||
| 328 | |||
| 329 | // find already connected pairs | ||
| 330 | if (text.newToOld[i] != null) { | ||
| 331 | j = text.newToOld[i]; | ||
| 332 | |||
| 333 | // check if the following words are not yet connected | ||
| 334 | if ( (text.newToOld[i + 1] == null) && (text.oldToNew[j + 1] == null) ) { | ||
| 335 | |||
| 336 | // if the following words are the same connect them | ||
| 337 | if ( text.newWords[i + 1] == text.oldWords[j + 1] ) { | ||
| 338 | text.newToOld[i + 1] = j + 1; | ||
| 339 | text.oldToNew[j + 1] = i + 1; | ||
| 340 | } | ||
| 341 | } | ||
| 342 | } | ||
| 343 | } | ||
| 344 | |||
| 345 | // pass 5: connect adjacent identical words upwards | ||
| 346 | |||
| 347 | for (var i = newEnd - 1; i > newStart; i --) { | ||
| 348 | |||
| 349 | // find already connected pairs | ||
| 350 | if (text.newToOld[i] != null) { | ||
| 351 | j = text.newToOld[i]; | ||
| 352 | |||
| 353 | // check if the preceeding words are not yet connected | ||
| 354 | if ( (text.newToOld[i - 1] == null) && (text.oldToNew[j - 1] == null) ) { | ||
| 355 | |||
| 356 | // if the preceeding words are the same connect them | ||
| 357 | if ( text.newWords[i - 1] == text.oldWords[j - 1] ) { | ||
| 358 | text.newToOld[i - 1] = j - 1; | ||
| 359 | text.oldToNew[j - 1] = i - 1; | ||
| 360 | } | ||
| 361 | } | ||
| 362 | } | ||
| 363 | } | ||
| 364 | |||
| 365 | // recursively diff still unresolved regions downwards | ||
| 366 | |||
| 367 | if (wDiffRecursiveDiff) { | ||
| 368 | i = newStart; | ||
| 369 | j = oldStart; | ||
| 370 | while (i < newEnd) { | ||
| 371 | if (text.newToOld[i - 1] != null) { | ||
| 372 | j = text.newToOld[i - 1] + 1; | ||
| 373 | } | ||
| 374 | |||
| 375 | // check for the start of an unresolved sequence | ||
| 376 | if ( (text.newToOld[i] == null) && (text.oldToNew[j] == null) ) { | ||
| 377 | |||
| 378 | // determine the ends of the sequences | ||
| 379 | var iStart = i; | ||
| 380 | var iEnd = i; | ||
| 381 | while ( (text.newToOld[iEnd] == null) && (iEnd < newEnd) ) { | ||
| 382 | iEnd ++; | ||
| 383 | } | ||
| 384 | var iLength = iEnd - iStart; | ||
| 385 | |||
| 386 | var jStart = j; | ||
| 387 | var jEnd = j; | ||
| 388 | while ( (text.oldToNew[jEnd] == null) && (jEnd < oldEnd) ) { | ||
| 389 | jEnd ++; | ||
| 390 | } | ||
| 391 | var jLength = jEnd - jStart; | ||
| 392 | |||
| 393 | // recursively diff the unresolved sequence | ||
| 394 | if ( (iLength > 0) && (jLength > 0) ) { | ||
| 395 | if ( (iLength > 1) || (jLength > 1) ) { | ||
| 396 | if ( (iStart != newStart) || (iEnd != newEnd) || (jStart != oldStart) || (jEnd != oldEnd) ) { | ||
| 397 | WDiffText(text, iStart, iEnd, jStart, jEnd, recursionLevel + 1); | ||
| 398 | } | ||
| 399 | } | ||
| 400 | } | ||
| 401 | i = iEnd; | ||
| 402 | } | ||
| 403 | else { | ||
| 404 | i ++; | ||
| 405 | } | ||
| 406 | } | ||
| 407 | } | ||
| 408 | |||
| 409 | // recursively diff still unresolved regions upwards | ||
| 410 | |||
| 411 | if (wDiffRecursiveDiff) { | ||
| 412 | i = newEnd - 1; | ||
| 413 | j = oldEnd - 1; | ||
| 414 | while (i >= newStart) { | ||
| 415 | if (text.newToOld[i + 1] != null) { | ||
| 416 | j = text.newToOld[i + 1] - 1; | ||
| 417 | } | ||
| 418 | |||
| 419 | // check for the start of an unresolved sequence | ||
| 420 | if ( (text.newToOld[i] == null) && (text.oldToNew[j] == null) ) { | ||
| 421 | |||
| 422 | // determine the ends of the sequences | ||
| 423 | var iStart = i; | ||
| 424 | var iEnd = i + 1; | ||
| 425 | while ( (text.newToOld[iStart - 1] == null) && (iStart >= newStart) ) { | ||
| 426 | iStart --; | ||
| 427 | } | ||
| 428 | var iLength = iEnd - iStart; | ||
| 429 | |||
| 430 | var jStart = j; | ||
| 431 | var jEnd = j + 1; | ||
| 432 | while ( (text.oldToNew[jStart - 1] == null) && (jStart >= oldStart) ) { | ||
| 433 | jStart --; | ||
| 434 | } | ||
| 435 | var jLength = jEnd - jStart; | ||
| 436 | |||
| 437 | // recursively diff the unresolved sequence | ||
| 438 | if ( (iLength > 0) && (jLength > 0) ) { | ||
| 439 | if ( (iLength > 1) || (jLength > 1) ) { | ||
| 440 | if ( (iStart != newStart) || (iEnd != newEnd) || (jStart != oldStart) || (jEnd != oldEnd) ) { | ||
| 441 | WDiffText(text, iStart, iEnd, jStart, jEnd, recursionLevel + 1); | ||
| 442 | } | ||
| 443 | } | ||
| 444 | } | ||
| 445 | i = iStart - 1; | ||
| 446 | } | ||
| 447 | else { | ||
| 448 | i --; | ||
| 449 | } | ||
| 450 | } | ||
| 451 | } | ||
| 452 | return; | ||
| 453 | } | ||
| 454 | |||
| 455 | |||
| 456 | // WDiffToHtml: process diff data into formatted html text | ||
| 457 | // input: text.newWords and text.oldWords, arrays containing the texts in arrays of words | ||
| 458 | // text.newToOld and text.oldToNew, containing the line numbers in the other version | ||
| 459 | // block data structure | ||
| 460 | // returns: outText, a html string | ||
| 461 | |||
| 462 | window.WDiffToHtml = function(text, block) { | ||
| 463 | |||
| 464 | var outText = text.message; | ||
| 465 | |||
| 466 | var blockNumber = 0; | ||
| 467 | var i = 0; | ||
| 468 | var j = 0; | ||
| 469 | var movedAsInsertion; | ||
| 470 | |||
| 471 | // cycle through the new text | ||
| 472 | do { | ||
| 473 | var movedIndex = []; | ||
| 474 | var movedBlock = []; | ||
| 475 | var movedLeft = []; | ||
| 476 | var blockText = ''; | ||
| 477 | var identText = ''; | ||
| 478 | var delText = ''; | ||
| 479 | var insText = ''; | ||
| 480 | var identStart = ''; | ||
| 481 | |||
| 482 | // check if a block ends here and finish previous block | ||
| 483 | if (movedAsInsertion != null) { | ||
| 484 | if (movedAsInsertion == false) { | ||
| 485 | identStart += wDiffHtmlBlockEnd; | ||
| 486 | } | ||
| 487 | else { | ||
| 488 | identStart += wDiffHtmlInsertEnd; | ||
| 489 | } | ||
| 490 | movedAsInsertion = null; | ||
| 491 | } | ||
| 492 | |||
| 493 | // detect block boundary | ||
| 494 | if ( (text.newToOld[i] != j) || (blockNumber == 0 ) ) { | ||
| 495 | if ( ( (text.newToOld[i] != null) || (i >= text.newWords.length) ) && ( (text.oldToNew[j] != null) || (j >= text.oldWords.length) ) ) { | ||
| 496 | |||
| 497 | // block moved right | ||
| 498 | var moved = block.newRight[blockNumber]; | ||
| 499 | if (moved > 0) { | ||
| 500 | var index = block.newRightIndex[blockNumber]; | ||
| 501 | movedIndex.push(index); | ||
| 502 | movedBlock.push(moved); | ||
| 503 | movedLeft.push(false); | ||
| 504 | } | ||
| 505 | |||
| 506 | // block moved left | ||
| 507 | moved = block.newLeft[blockNumber]; | ||
| 508 | if (moved > 0) { | ||
| 509 | var index = block.newLeftIndex[blockNumber]; | ||
| 510 | movedIndex.push(index); | ||
| 511 | movedBlock.push(moved); | ||
| 512 | movedLeft.push(true); | ||
| 513 | } | ||
| 514 | |||
| 515 | // check if a block starts here | ||
| 516 | moved = block.newBlock[blockNumber]; | ||
| 517 | if (moved > 0) { | ||
| 518 | |||
| 519 | // mark block as inserted text | ||
| 520 | if (block.newWords[blockNumber] < wDiffBlockMinLength) { | ||
| 521 | identStart += wDiffHtmlInsertStart; | ||
| 522 | movedAsInsertion = true; | ||
| 523 | } | ||
| 524 | |||
| 525 | // mark block by color | ||
| 526 | else { | ||
| 527 | if (moved > wDiffStyleBlock.length) { | ||
| 528 | moved = wDiffStyleBlock.length; | ||
| 529 | } | ||
| 530 | identStart += WDiffHtmlCustomize(wDiffHtmlBlockStart, moved - 1); | ||
| 531 | movedAsInsertion = false; | ||
| 532 | } | ||
| 533 | } | ||
| 534 | |||
| 535 | if (i >= text.newWords.length) { | ||
| 536 | i ++; | ||
| 537 | } | ||
| 538 | else { | ||
| 539 | j = text.newToOld[i]; | ||
| 540 | blockNumber ++; | ||
| 541 | } | ||
| 542 | } | ||
| 543 | } | ||
| 544 | |||
| 545 | // get the correct order if moved to the left as well as to the right from here | ||
| 546 | if (movedIndex.length == 2) { | ||
| 547 | if (movedIndex[0] > movedIndex[1]) { | ||
| 548 | movedIndex.reverse(); | ||
| 549 | movedBlock.reverse(); | ||
| 550 | movedLeft.reverse(); | ||
| 551 | } | ||
| 552 | } | ||
| 553 | |||
| 554 | // handle left and right block moves from this position | ||
| 555 | for (var m = 0; m < movedIndex.length; m ++) { | ||
| 556 | |||
| 557 | // insert the block as deleted text | ||
| 558 | if (block.newWords[ movedIndex[m] ] < wDiffBlockMinLength) { | ||
| 559 | var movedStart = block.newStart[ movedIndex[m] ]; | ||
| 560 | var movedLength = block.newLength[ movedIndex[m] ]; | ||
| 561 | var str = ''; | ||
| 562 | for (var n = movedStart; n < movedStart + movedLength; n ++) { | ||
| 563 | str += text.newWords[n]; | ||
| 564 | } | ||
| 565 | str = WDiffEscape(str); | ||
| 566 | str = str.replace(/\n/g, '¶<br>'); | ||
| 567 | blockText += wDiffHtmlDeleteStart + str + wDiffHtmlDeleteEnd; | ||
| 568 | } | ||
| 569 | |||
| 570 | // add a placeholder / move direction indicator | ||
| 571 | else { | ||
| 572 | if (movedBlock[m] > wDiffStyleBlock.length) { | ||
| 573 | movedBlock[m] = wDiffStyleBlock.length; | ||
| 574 | } | ||
| 575 | if (movedLeft[m]) { | ||
| 576 | blockText += WDiffHtmlCustomize(wDiffHtmlMovedLeft, movedBlock[m] - 1); | ||
| 577 | } | ||
| 578 | else { | ||
| 579 | blockText += WDiffHtmlCustomize(wDiffHtmlMovedRight, movedBlock[m] - 1); | ||
| 580 | } | ||
| 581 | } | ||
| 582 | } | ||
| 583 | |||
| 584 | // collect consecutive identical text | ||
| 585 | while ( (i < text.newWords.length) && (j < text.oldWords.length) ) { | ||
| 586 | if ( (text.newToOld[i] == null) || (text.oldToNew[j] == null) ) { | ||
| 587 | break; | ||
| 588 | } | ||
| 589 | if (text.newToOld[i] != j) { | ||
| 590 | break; | ||
| 591 | } | ||
| 592 | identText += text.newWords[i]; | ||
| 593 | i ++; | ||
| 594 | j ++; | ||
| 595 | } | ||
| 596 | |||
| 597 | // collect consecutive deletions | ||
| 598 | while ( (text.oldToNew[j] == null) && (j < text.oldWords.length) ) { | ||
| 599 | delText += text.oldWords[j]; | ||
| 600 | j ++; | ||
| 601 | } | ||
| 602 | |||
| 603 | // collect consecutive inserts | ||
| 604 | while ( (text.newToOld[i] == null) && (i < text.newWords.length) ) { | ||
| 605 | insText += text.newWords[i]; | ||
| 606 | i ++; | ||
| 607 | } | ||
| 608 | |||
| 609 | // remove leading and trailing similarities betweein delText and ins from highlighting | ||
| 610 | var preText = ''; | ||
| 611 | var postText = ''; | ||
| 612 | if (wDiffWordDiff) { | ||
| 613 | if ( (delText != '') && (insText != '') ) { | ||
| 614 | |||
| 615 | // remove leading similarities | ||
| 616 | while ( delText.charAt(0) == insText.charAt(0) && (delText != '') && (insText != '') ) { | ||
| 617 | preText = preText + delText.charAt(0); | ||
| 618 | delText = delText.substr(1); | ||
| 619 | insText = insText.substr(1); | ||
| 620 | } | ||
| 621 | |||
| 622 | // remove trailing similarities | ||
| 623 | while ( delText.charAt(delText.length - 1) == insText.charAt(insText.length - 1) && (delText != '') && (insText != '') ) { | ||
| 624 | postText = delText.charAt(delText.length - 1) + postText; | ||
| 625 | delText = delText.substr(0, delText.length - 1); | ||
| 626 | insText = insText.substr(0, insText.length - 1); | ||
| 627 | } | ||
| 628 | } | ||
| 629 | } | ||
| 630 | |||
| 631 | // output the identical text, deletions and inserts | ||
| 632 | |||
| 633 | // moved from here indicator | ||
| 634 | if (blockText != '') { | ||
| 635 | outText += blockText; | ||
| 636 | } | ||
| 637 | |||
| 638 | // identical text | ||
| 639 | if (identText != '') { | ||
| 640 | outText += identStart + WDiffEscape(identText); | ||
| 641 | } | ||
| 642 | outText += preText; | ||
| 643 | |||
| 644 | // deleted text | ||
| 645 | if (delText != '') { | ||
| 646 | delText = wDiffHtmlDeleteStart + WDiffEscape(delText) + wDiffHtmlDeleteEnd; | ||
| 647 | delText = delText.replace(/\n/g, '¶<br>'); | ||
| 648 | outText += delText; | ||
| 649 | } | ||
| 650 | |||
| 651 | // inserted text | ||
| 652 | if (insText != '') { | ||
| 653 | insText = wDiffHtmlInsertStart + WDiffEscape(insText) + wDiffHtmlInsertEnd; | ||
| 654 | insText = insText.replace(/\n/g, '¶<br>'); | ||
| 655 | outText += insText; | ||
| 656 | } | ||
| 657 | outText += postText; | ||
| 658 | } while (i <= text.newWords.length); | ||
| 659 | |||
| 660 | outText += '\n'; | ||
| 661 | outText = WDiffHtmlFormat(outText); | ||
| 662 | |||
| 663 | return(outText); | ||
| 664 | } | ||
| 665 | |||
| 666 | |||
| 667 | // WDiffEscape: replaces html-sensitive characters in output text with character entities | ||
| 668 | |||
| 669 | window.WDiffEscape = function(text) { | ||
| 670 | |||
| 671 | text = text.replace(/&/g, '&'); | ||
| 672 | text = text.replace(/</g, '<'); | ||
| 673 | text = text.replace(/>/g, '>'); | ||
| 674 | text = text.replace(/\"/g, '"'); | ||
| 675 | |||
| 676 | return(text); | ||
| 677 | } | ||
| 678 | |||
| 679 | |||
| 680 | // HtmlCustomize: customize indicator html: replace {number} with the block number, {block} with the block style | ||
| 681 | |||
| 682 | window.WDiffHtmlCustomize = function(text, block) { | ||
| 683 | |||
| 684 | text = text.replace(/\{number\}/, block); | ||
| 685 | text = text.replace(/\{block\}/, wDiffStyleBlock[block]); | ||
| 686 | |||
| 687 | return(text); | ||
| 688 | } | ||
| 689 | |||
| 690 | |||
| 691 | // HtmlFormat: replaces newlines and multiple spaces in text with html code | ||
| 692 | |||
| 693 | window.WDiffHtmlFormat = function(text) { | ||
| 694 | |||
| 695 | text = text.replace(/ /g, ' '); | ||
| 696 | text = text.replace(/\n/g, '<br>'); | ||
| 697 | |||
| 698 | return(text); | ||
| 699 | } | ||
| 700 | |||
| 701 | |||
| 702 | // WDiffDetectBlocks: detect block borders and moved blocks | ||
| 703 | // input: text object, block object | ||
| 704 | |||
| 705 | window.WDiffDetectBlocks = function(text, block) { | ||
| 706 | |||
| 707 | block.oldStart = []; | ||
| 708 | block.oldToNew = []; | ||
| 709 | block.oldLength = []; | ||
| 710 | block.oldWords = []; | ||
| 711 | block.newStart = []; | ||
| 712 | block.newLength = []; | ||
| 713 | block.newWords = []; | ||
| 714 | block.newNumber = []; | ||
| 715 | block.newBlock = []; | ||
| 716 | block.newLeft = []; | ||
| 717 | block.newRight = []; | ||
| 718 | block.newLeftIndex = []; | ||
| 719 | block.newRightIndex = []; | ||
| 720 | |||
| 721 | var blockNumber = 0; | ||
| 722 | var wordCounter = 0; | ||
| 723 | var realWordCounter = 0; | ||
| 724 | |||
| 725 | // get old text block order | ||
| 726 | if (wDiffShowBlockMoves) { | ||
| 727 | var j = 0; | ||
| 728 | var i = 0; | ||
| 729 | do { | ||
| 730 | |||
| 731 | // detect block boundaries on old text | ||
| 732 | if ( (text.oldToNew[j] != i) || (blockNumber == 0 ) ) { | ||
| 733 | if ( ( (text.oldToNew[j] != null) || (j >= text.oldWords.length) ) && ( (text.newToOld[i] != null) || (i >= text.newWords.length) ) ) { | ||
| 734 | if (blockNumber > 0) { | ||
| 735 | block.oldLength[blockNumber - 1] = wordCounter; | ||
| 736 | block.oldWords[blockNumber - 1] = realWordCounter; | ||
| 737 | wordCounter = 0; | ||
| 738 | realWordCounter = 0; | ||
| 739 | } | ||
| 740 | |||
| 741 | if (j >= text.oldWords.length) { | ||
| 742 | j ++; | ||
| 743 | } | ||
| 744 | else { | ||
| 745 | i = text.oldToNew[j]; | ||
| 746 | block.oldStart[blockNumber] = j; | ||
| 747 | block.oldToNew[blockNumber] = text.oldToNew[j]; | ||
| 748 | blockNumber ++; | ||
| 749 | } | ||
| 750 | } | ||
| 751 | } | ||
| 752 | |||
| 753 | // jump over identical pairs | ||
| 754 | while ( (i < text.newWords.length) && (j < text.oldWords.length) ) { | ||
| 755 | if ( (text.newToOld[i] == null) || (text.oldToNew[j] == null) ) { | ||
| 756 | break; | ||
| 757 | } | ||
| 758 | if (text.oldToNew[j] != i) { | ||
| 759 | break; | ||
| 760 | } | ||
| 761 | i ++; | ||
| 762 | j ++; | ||
| 763 | wordCounter ++; | ||
| 764 | if ( /\w/.test( text.newWords[i] ) ) { | ||
| 765 | realWordCounter ++; | ||
| 766 | } | ||
| 767 | } | ||
| 768 | |||
| 769 | // jump over consecutive deletions | ||
| 770 | while ( (text.oldToNew[j] == null) && (j < text.oldWords.length) ) { | ||
| 771 | j ++; | ||
| 772 | } | ||
| 773 | |||
| 774 | // jump over consecutive inserts | ||
| 775 | while ( (text.newToOld[i] == null) && (i < text.newWords.length) ) { | ||
| 776 | i ++; | ||
| 777 | } | ||
| 778 | } while (j <= text.oldWords.length); | ||
| 779 | |||
| 780 | // get the block order in the new text | ||
| 781 | var lastMin; | ||
| 782 | var currMinIndex; | ||
| 783 | lastMin = null; | ||
| 784 | |||
| 785 | // sort the data by increasing start numbers into new text block info | ||
| 786 | for (var i = 0; i < blockNumber; i ++) { | ||
| 787 | currMin = null; | ||
| 788 | for (var j = 0; j < blockNumber; j ++) { | ||
| 789 | curr = block.oldToNew[j]; | ||
| 790 | if ( (curr > lastMin) || (lastMin == null) ) { | ||
| 791 | if ( (curr < currMin) || (currMin == null) ) { | ||
| 792 | currMin = curr; | ||
| 793 | currMinIndex = j; | ||
| 794 | } | ||
| 795 | } | ||
| 796 | } | ||
| 797 | block.newStart[i] = block.oldToNew[currMinIndex]; | ||
| 798 | block.newLength[i] = block.oldLength[currMinIndex]; | ||
| 799 | block.newWords[i] = block.oldWords[currMinIndex]; | ||
| 800 | block.newNumber[i] = currMinIndex; | ||
| 801 | lastMin = currMin; | ||
| 802 | } | ||
| 803 | |||
| 804 | // detect not moved blocks | ||
| 805 | for (var i = 0; i < blockNumber; i ++) { | ||
| 806 | if (block.newBlock[i] == null) { | ||
| 807 | if (block.newNumber[i] == i) { | ||
| 808 | block.newBlock[i] = 0; | ||
| 809 | } | ||
| 810 | } | ||
| 811 | } | ||
| 812 | |||
| 813 | // detect switches of neighbouring blocks | ||
| 814 | for (var i = 0; i < blockNumber - 1; i ++) { | ||
| 815 | if ( (block.newBlock[i] == null) && (block.newBlock[i + 1] == null) ) { | ||
| 816 | if (block.newNumber[i] - block.newNumber[i + 1] == 1) { | ||
| 817 | if ( (block.newNumber[i + 1] - block.newNumber[i + 2] != 1) || (i + 2 >= blockNumber) ) { | ||
| 818 | |||
| 819 | // the shorter one is declared the moved one | ||
| 820 | if (block.newLength[i] < block.newLength[i + 1]) { | ||
| 821 | block.newBlock[i] = 1; | ||
| 822 | block.newBlock[i + 1] = 0; | ||
| 823 | } | ||
| 824 | else { | ||
| 825 | block.newBlock[i] = 0; | ||
| 826 | block.newBlock[i + 1] = 1; | ||
| 827 | } | ||
| 828 | } | ||
| 829 | } | ||
| 830 | } | ||
| 831 | } | ||
| 832 | |||
| 833 | // mark all others as moved and number the moved blocks | ||
| 834 | j = 1; | ||
| 835 | for (var i = 0; i < blockNumber; i ++) { | ||
| 836 | if ( (block.newBlock[i] == null) || (block.newBlock[i] == 1) ) { | ||
| 837 | block.newBlock[i] = j++; | ||
| 838 | } | ||
| 839 | } | ||
| 840 | |||
| 841 | // check if a block has been moved from this block border | ||
| 842 | for (var i = 0; i < blockNumber; i ++) { | ||
| 843 | for (var j = 0; j < blockNumber; j ++) { | ||
| 844 | |||
| 845 | if (block.newNumber[j] == i) { | ||
| 846 | if (block.newBlock[j] > 0) { | ||
| 847 | |||
| 848 | // block moved right | ||
| 849 | if (block.newNumber[j] < j) { | ||
| 850 | block.newRight[i] = block.newBlock[j]; | ||
| 851 | block.newRightIndex[i] = j; | ||
| 852 | } | ||
| 853 | |||
| 854 | // block moved left | ||
| 855 | else { | ||
| 856 | block.newLeft[i + 1] = block.newBlock[j]; | ||
| 857 | block.newLeftIndex[i + 1] = j; | ||
| 858 | } | ||
| 859 | } | ||
| 860 | } | ||
| 861 | } | ||
| 862 | } | ||
| 863 | } | ||
| 864 | return; | ||
| 865 | } | ||
| 866 | |||
| 867 | |||
| 868 | // WDiffShortenOutput: remove unchanged parts from final output | ||
| 869 | // input: the output of WDiffString | ||
| 870 | // returns: the text with removed unchanged passages indicated by (...) | ||
| 871 | |||
| 872 | window.WDiffShortenOutput = function(diffText) { | ||
| 873 | |||
| 874 | // html <br/> to newlines | ||
| 875 | diffText = diffText.replace(/<br[^>]*>/g, '\n'); | ||
| 876 | |||
| 877 | // scan for diff html tags | ||
| 878 | var regExpDiff = new RegExp('<\\w+ class=\\"(\\w+)\\"[^>]*>(.|\\n)*?<!--\\1-->', 'g'); | ||
| 879 | var tagStart = []; | ||
| 880 | var tagEnd = []; | ||
| 881 | var i = 0; | ||
| 882 | var found; | ||
| 883 | while ( (found = regExpDiff.exec(diffText)) != null ) { | ||
| 884 | |||
| 885 | // combine consecutive diff tags | ||
| 886 | if ( (i > 0) && (tagEnd[i - 1] == found.index) ) { | ||
| 887 | tagEnd[i - 1] = found.index + found[0].length; | ||
| 888 | } | ||
| 889 | else { | ||
| 890 | tagStart[i] = found.index; | ||
| 891 | tagEnd[i] = found.index + found[0].length; | ||
| 892 | i ++; | ||
| 893 | } | ||
| 894 | } | ||
| 895 | |||
| 896 | // no diff tags detected | ||
| 897 | if (tagStart.length == 0) { | ||
| 898 | return(wDiffNoChange); | ||
| 899 | } | ||
| 900 | |||
| 901 | // define regexps | ||
| 902 | var regExpHeading = new RegExp('\\n=+.+?=+ *\\n|\\n\\{\\||\\n\\|\\}', 'g'); | ||
| 903 | var regExpParagraph = new RegExp('\\n\\n+', 'g'); | ||
| 904 | var regExpLine = new RegExp('\\n+', 'g'); | ||
| 905 | var regExpBlank = new RegExp('(<[^>]+>)*\\s+', 'g'); | ||
| 906 | |||
| 907 | // determine fragment border positions around diff tags | ||
| 908 | var rangeStart = []; | ||
| 909 | var rangeEnd = []; | ||
| 910 | var rangeStartType = []; | ||
| 911 | var rangeEndType = []; | ||
| 912 | for (var i = 0; i < tagStart.length; i ++) { | ||
| 913 | var found; | ||
| 914 | |||
| 915 | // find last heading before diff tag | ||
| 916 | var lastPos = tagStart[i] - wDiffHeadingBefore; | ||
| 917 | if (lastPos < 0) { | ||
| 918 | lastPos = 0; | ||
| 919 | } | ||
| 920 | regExpHeading.lastIndex = lastPos; | ||
| 921 | while ( (found = regExpHeading.exec(diffText)) != null ) { | ||
| 922 | if (found.index > tagStart[i]) { | ||
| 923 | break; | ||
| 924 | } | ||
| 925 | rangeStart[i] = found.index; | ||
| 926 | rangeStartType[i] = 'heading'; | ||
| 927 | } | ||
| 928 | |||
| 929 | // find last paragraph before diff tag | ||
| 930 | if (rangeStart[i] == null) { | ||
| 931 | lastPos = tagStart[i] - wDiffParagraphBefore; | ||
| 932 | if (lastPos < 0) { | ||
| 933 | lastPos = 0; | ||
| 934 | } | ||
| 935 | regExpParagraph.lastIndex = lastPos; | ||
| 936 | while ( (found = regExpParagraph.exec(diffText)) != null ) { | ||
| 937 | if (found.index > tagStart[i]) { | ||
| 938 | break; | ||
| 939 | } | ||
| 940 | rangeStart[i] = found.index; | ||
| 941 | rangeStartType[i] = 'paragraph'; | ||
| 942 | } | ||
| 943 | } | ||
| 944 | |||
| 945 | // find line break before diff tag | ||
| 946 | if (rangeStart[i] == null) { | ||
| 947 | lastPos = tagStart[i] - wDiffLineBeforeMax; | ||
| 948 | if (lastPos < 0) { | ||
| 949 | lastPos = 0; | ||
| 950 | } | ||
| 951 | regExpLine.lastIndex = lastPos; | ||
| 952 | while ( (found = regExpLine.exec(diffText)) != null ) { | ||
| 953 | if (found.index > tagStart[i] - wDiffLineBeforeMin) { | ||
| 954 | break; | ||
| 955 | } | ||
| 956 | rangeStart[i] = found.index; | ||
| 957 | rangeStartType[i] = 'line'; | ||
| 958 | } | ||
| 959 | } | ||
| 960 | |||
| 961 | // find blank before diff tag | ||
| 962 | if (rangeStart[i] == null) { | ||
| 963 | lastPos = tagStart[i] - wDiffBlankBeforeMax; | ||
| 964 | if (lastPos < 0) { | ||
| 965 | lastPos = 0; | ||
| 966 | } | ||
| 967 | regExpBlank.lastIndex = lastPos; | ||
| 968 | while ( (found = regExpBlank.exec(diffText)) != null ) { | ||
| 969 | if (found.index > tagStart[i] - wDiffBlankBeforeMin) { | ||
| 970 | break; | ||
| 971 | } | ||
| 972 | rangeStart[i] = found.index; | ||
| 973 | rangeStartType[i] = 'blank'; | ||
| 974 | } | ||
| 975 | } | ||
| 976 | |||
| 977 | // fixed number of chars before diff tag | ||
| 978 | if (rangeStart[i] == null) { | ||
| 979 | rangeStart[i] = tagStart[i] - wDiffCharsBefore; | ||
| 980 | rangeStartType[i] = 'chars'; | ||
| 981 | if (rangeStart[i] < 0) { | ||
| 982 | rangeStart[i] = 0; | ||
| 983 | } | ||
| 984 | } | ||
| 985 | |||
| 986 | // find first heading after diff tag | ||
| 987 | regExpHeading.lastIndex = tagEnd[i]; | ||
| 988 | if ( (found = regExpHeading.exec(diffText)) != null ) { | ||
| 989 | if (found.index < tagEnd[i] + wDiffHeadingAfter) { | ||
| 990 | rangeEnd[i] = found.index + found[0].length; | ||
| 991 | rangeEndType[i] = 'heading'; | ||
| 992 | } | ||
| 993 | } | ||
| 994 | |||
| 995 | // find first paragraph after diff tag | ||
| 996 | if (rangeEnd[i] == null) { | ||
| 997 | regExpParagraph.lastIndex = tagEnd[i]; | ||
| 998 | if ( (found = regExpParagraph.exec(diffText)) != null ) { | ||
| 999 | if (found.index < tagEnd[i] + wDiffParagraphAfter) { | ||
| 1000 | rangeEnd[i] = found.index; | ||
| 1001 | rangeEndType[i] = 'paragraph'; | ||
| 1002 | } | ||
| 1003 | } | ||
| 1004 | } | ||
| 1005 | |||
| 1006 | // find first line break after diff tag | ||
| 1007 | if (rangeEnd[i] == null) { | ||
| 1008 | regExpLine.lastIndex = tagEnd[i] + wDiffLineAfterMin; | ||
| 1009 | if ( (found = regExpLine.exec(diffText)) != null ) { | ||
| 1010 | if (found.index < tagEnd[i] + wDiffLineAfterMax) { | ||
| 1011 | rangeEnd[i] = found.index; | ||
| 1012 | rangeEndType[i] = 'break'; | ||
| 1013 | } | ||
| 1014 | } | ||
| 1015 | } | ||
| 1016 | |||
| 1017 | // find blank after diff tag | ||
| 1018 | if (rangeEnd[i] == null) { | ||
| 1019 | regExpBlank.lastIndex = tagEnd[i] + wDiffBlankAfterMin; | ||
| 1020 | if ( (found = regExpBlank.exec(diffText)) != null ) { | ||
| 1021 | if (found.index < tagEnd[i] + wDiffBlankAfterMax) { | ||
| 1022 | rangeEnd[i] = found.index; | ||
| 1023 | rangeEndType[i] = 'blank'; | ||
| 1024 | } | ||
| 1025 | } | ||
| 1026 | } | ||
| 1027 | |||
| 1028 | // fixed number of chars after diff tag | ||
| 1029 | if (rangeEnd[i] == null) { | ||
| 1030 | rangeEnd[i] = tagEnd[i] + wDiffCharsAfter; | ||
| 1031 | if (rangeEnd[i] > diffText.length) { | ||
| 1032 | rangeEnd[i] = diffText.length; | ||
| 1033 | rangeEndType[i] = 'chars'; | ||
| 1034 | } | ||
| 1035 | } | ||
| 1036 | } | ||
| 1037 | |||
| 1038 | // remove overlaps, join close fragments | ||
| 1039 | var fragmentStart = []; | ||
| 1040 | var fragmentEnd = []; | ||
| 1041 | var fragmentStartType = []; | ||
| 1042 | var fragmentEndType = []; | ||
| 1043 | fragmentStart[0] = rangeStart[0]; | ||
| 1044 | fragmentEnd[0] = rangeEnd[0]; | ||
| 1045 | fragmentStartType[0] = rangeStartType[0]; | ||
| 1046 | fragmentEndType[0] = rangeEndType[0]; | ||
| 1047 | var j = 1; | ||
| 1048 | for (var i = 1; i < rangeStart.length; i ++) { | ||
| 1049 | if (rangeStart[i] > fragmentEnd[j - 1] + wDiffFragmentJoin) { | ||
| 1050 | fragmentStart[j] = rangeStart[i]; | ||
| 1051 | fragmentEnd[j] = rangeEnd[i]; | ||
| 1052 | fragmentStartType[j] = rangeStartType[i]; | ||
| 1053 | fragmentEndType[j] = rangeEndType[i]; | ||
| 1054 | j ++; | ||
| 1055 | } | ||
| 1056 | else { | ||
| 1057 | fragmentEnd[j - 1] = rangeEnd[i]; | ||
| 1058 | fragmentEndType[j - 1] = rangeEndType[i]; | ||
| 1059 | } | ||
| 1060 | } | ||
| 1061 | |||
| 1062 | // assemble the fragments | ||
| 1063 | var outText = ''; | ||
| 1064 | for (var i = 0; i < fragmentStart.length; i ++) { | ||
| 1065 | |||
| 1066 | // get text fragment | ||
| 1067 | var fragment = diffText.substring(fragmentStart[i], fragmentEnd[i]); | ||
| 1068 | var fragment = fragment.replace(/^\n+|\n+$/g, ''); | ||
| 1069 | |||
| 1070 | // add inline marks for omitted chars and words | ||
| 1071 | if (fragmentStart[i] > 0) { | ||
| 1072 | if (fragmentStartType[i] == 'chars') { | ||
| 1073 | fragment = wDiffOmittedChars + fragment; | ||
| 1074 | } | ||
| 1075 | else if (fragmentStartType[i] == 'blank') { | ||
| 1076 | fragment = wDiffOmittedChars + ' ' + fragment; | ||
| 1077 | } | ||
| 1078 | } | ||
| 1079 | if (fragmentEnd[i] < diffText.length) { | ||
| 1080 | if (fragmentStartType[i] == 'chars') { | ||
| 1081 | fragment = fragment + wDiffOmittedChars; | ||
| 1082 | } | ||
| 1083 | else if (fragmentStartType[i] == 'blank') { | ||
| 1084 | fragment = fragment + ' ' + wDiffOmittedChars; | ||
| 1085 | } | ||
| 1086 | } | ||
| 1087 | |||
| 1088 | // add omitted line separator | ||
| 1089 | if (fragmentStart[i] > 0) { | ||
| 1090 | outText += wDiffOmittedLines; | ||
| 1091 | } | ||
| 1092 | |||
| 1093 | // encapsulate span errors | ||
| 1094 | outText += '<div>' + fragment + '</div>'; | ||
| 1095 | } | ||
| 1096 | |||
| 1097 | // add trailing omitted line separator | ||
| 1098 | if (fragmentEnd[i - 1] < diffText.length) { | ||
| 1099 | outText = outText + wDiffOmittedLines; | ||
| 1100 | } | ||
| 1101 | |||
| 1102 | // remove leading and trailing empty lines | ||
| 1103 | outText = outText.replace(/^(<div>)\n+|\n+(<\/div>)$/g, '$1$2'); | ||
| 1104 | |||
| 1105 | // convert to html linebreaks | ||
| 1106 | outText = outText.replace(/\n/g, '<br />'); | ||
| 1107 | |||
| 1108 | return(outText); | ||
| 1109 | } | ||
| 1110 | |||
| 1111 | |||
| 1112 | // <pre><nowiki> \ No newline at end of file | ||
diff --git a/public/javascripts/related_assets.js b/public/javascripts/related_assets.js new file mode 100644 index 0000000..2955cbf --- /dev/null +++ b/public/javascripts/related_assets.js | |||
| @@ -0,0 +1,80 @@ | |||
| 1 | related_assets = { | ||
| 2 | initialize: function() { | ||
| 3 | var container = $("#related_assets"); | ||
| 4 | var searchUrl = container.data("search-url"); | ||
| 5 | var createUrl = container.data("create-url"); | ||
| 6 | var list = $("#related_asset_list"); | ||
| 7 | |||
| 8 | initSearchPicker({ | ||
| 9 | inputSelector: "#related_asset_search_term", | ||
| 10 | resultsSelector: "#related_asset_search_results", | ||
| 11 | url: searchUrl, | ||
| 12 | loadOnFocus: true, | ||
| 13 | renderResults: function(data, results) { | ||
| 14 | var found = false; | ||
| 15 | data.forEach(function(asset) { | ||
| 16 | var item = $( | ||
| 17 | "<a href='#' class='related_asset_result'>" + | ||
| 18 | "<img src='" + asset.thumb_url + "' alt=''>" + | ||
| 19 | "<span>" + asset.name + "</span>" + | ||
| 20 | "</a>" | ||
| 21 | ); | ||
| 22 | item.bind("click", function() { | ||
| 23 | related_assets.attach(asset.id, createUrl, list); | ||
| 24 | return false; | ||
| 25 | }); | ||
| 26 | results.append(item); | ||
| 27 | found = true; | ||
| 28 | }); | ||
| 29 | return found; | ||
| 30 | } | ||
| 31 | }); | ||
| 32 | |||
| 33 | $(document).on("click", function(e) { | ||
| 34 | if (!$(e.target).closest("#related_assets").length) { | ||
| 35 | $("#related_asset_search_results").slideUp().empty(); | ||
| 36 | } | ||
| 37 | }); | ||
| 38 | |||
| 39 | list.sortable({ | ||
| 40 | handle: ".related_asset_handle", | ||
| 41 | update: function(event, ui) { | ||
| 42 | var newPosition = ui.item.index() + 1; // acts_as_list is 1-based; jQuery UI's index() is 0-based | ||
| 43 | $.ajax({ | ||
| 44 | type: "PATCH", | ||
| 45 | url: ui.item.data("url"), | ||
| 46 | data: "position=" + newPosition | ||
| 47 | }); | ||
| 48 | } | ||
| 49 | }); | ||
| 50 | |||
| 51 | list.on("click", ".related_asset_remove", function() { | ||
| 52 | var item = $(this).closest("li"); | ||
| 53 | $.ajax({ | ||
| 54 | type: "DELETE", | ||
| 55 | url: item.data("url"), | ||
| 56 | success: function() { | ||
| 57 | item.remove(); | ||
| 58 | } | ||
| 59 | }); | ||
| 60 | }); | ||
| 61 | }, | ||
| 62 | |||
| 63 | attach: function(assetId, createUrl, list) { | ||
| 64 | $.ajax({ | ||
| 65 | type: "POST", | ||
| 66 | url: createUrl, | ||
| 67 | data: "asset_id=" + assetId, | ||
| 68 | dataType: "json", | ||
| 69 | success: function(related) { | ||
| 70 | var item = $($("#related_asset_template").html().trim()); | ||
| 71 | item.attr("data-url", related.url); | ||
| 72 | item.find("img").attr("src", related.thumb_url); | ||
| 73 | list.append(item); | ||
| 74 | list.sortable("refresh"); | ||
| 75 | $("#related_asset_search_term").val(""); | ||
| 76 | $("#related_asset_search_results").slideUp().empty(); | ||
| 77 | } | ||
| 78 | }); | ||
| 79 | } | ||
| 80 | }; | ||
