summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/models/concerns/rrule_humanizer.rb34
-rw-r--r--app/views/events/edit.html.erb8
-rw-r--r--public/javascripts/admin_interface.js46
-rw-r--r--test/models/concerns/rrule_humanizer_test.rb18
4 files changed, 98 insertions, 8 deletions
diff --git a/app/models/concerns/rrule_humanizer.rb b/app/models/concerns/rrule_humanizer.rb
index 8231de8..ca29648 100644
--- a/app/models/concerns/rrule_humanizer.rb
+++ b/app/models/concerns/rrule_humanizer.rb
@@ -15,8 +15,8 @@ module RruleHumanizer
15 }.freeze 15 }.freeze
16 16
17 ORDINAL_NAMES = { 17 ORDINAL_NAMES = {
18 de: { 1=>"ersten", 2=>"zweiten", 3=>"dritten", 4=>"vierten", -1=>"letzten", -2=>"vorletzten" }, 18 de: { 1=>"ersten", 2=>"zweiten", 3=>"dritten", 4=>"vierten", 5=>"fünften", -1=>"letzten", -2=>"vorletzten" },
19 en: { 1=>"first", 2=>"second", 3=>"third", 4=>"fourth", -1=>"last", -2=>"second-to-last" } 19 en: { 1=>"first", 2=>"second", 3=>"third", 4=>"fourth", 5=>"fifth", -1=>"last", -2=>"second-to-last" }
20 }.freeze 20 }.freeze
21 21
22 MONTH_NAMES = { 22 MONTH_NAMES = {
@@ -35,7 +35,8 @@ module RruleHumanizer
35 ordinals = ORDINAL_NAMES[loc] || ORDINAL_NAMES[:en] 35 ordinals = ORDINAL_NAMES[loc] || ORDINAL_NAMES[:en]
36 months = MONTH_NAMES[loc] || MONTH_NAMES[:en] 36 months = MONTH_NAMES[loc] || MONTH_NAMES[:en]
37 37
38 days = byday&.split(",")&.map do |d| 38 byday_values = byday&.split(",")
39 days = byday_values&.map do |d|
39 if d =~ /^(-?\d+)([A-Z]{2})$/ 40 if d =~ /^(-?\d+)([A-Z]{2})$/
40 "#{ordinals[$1.to_i]} #{weekdays[$2]}" 41 "#{ordinals[$1.to_i]} #{weekdays[$2]}"
41 else 42 else
@@ -43,6 +44,21 @@ module RruleHumanizer
43 end 44 end
44 end 45 end
45 46
47 excluded_monthly_ordinal = nil
48 excluded_monthly_weekday = nil
49 if freq == "MONTHLY" && byday_values.present?
50 ordinal_days = byday_values.map { |d| d.match(/^([1-5])([A-Z]{2})$/) }
51 if ordinal_days.all?
52 positions = ordinal_days.map { |match| match[1].to_i }.uniq.sort
53 weekdays_in_rule = ordinal_days.map { |match| match[2] }.uniq
54 missing_positions = (1..5).to_a - positions
55 if positions.size == 4 && weekdays_in_rule.size == 1 && missing_positions.size == 1
56 excluded_monthly_ordinal = missing_positions.first
57 excluded_monthly_weekday = weekdays_in_rule.first
58 end
59 end
60 end
61
46 base = 62 base =
47 case loc 63 case loc
48 when :de 64 when :de
@@ -59,14 +75,22 @@ module RruleHumanizer
59 interval == 2 ? "Alle zwei Wochen" : "Wöchentlich" 75 interval == 2 ? "Alle zwei Wochen" : "Wöchentlich"
60 end 76 end
61 when "MONTHLY" 77 when "MONTHLY"
62 days ? "Jeden #{days.join(' und ')} im Monat" : "Monatlich" 78 if excluded_monthly_ordinal
79 "Jeden #{weekdays[excluded_monthly_weekday]} im Monat, außer dem #{ordinals[excluded_monthly_ordinal]} #{weekdays[excluded_monthly_weekday]}"
80 else
81 days ? "Jeden #{days.join(' und ')} im Monat" : "Monatlich"
82 end
63 end 83 end
64 else 84 else
65 case freq 85 case freq
66 when "WEEKLY" 86 when "WEEKLY"
67 days ? "#{interval == 2 ? 'Every other' : 'Every'} #{days.join(' and ')}" : (interval == 2 ? "Every other week" : "Weekly") 87 days ? "#{interval == 2 ? 'Every other' : 'Every'} #{days.join(' and ')}" : (interval == 2 ? "Every other week" : "Weekly")
68 when "MONTHLY" 88 when "MONTHLY"
69 days ? "Every #{days.join(' and ')} of the month" : "Monthly" 89 if excluded_monthly_ordinal
90 "Every #{weekdays[excluded_monthly_weekday]} of the month, except the #{ordinals[excluded_monthly_ordinal]} #{weekdays[excluded_monthly_weekday]}"
91 else
92 days ? "Every #{days.join(' and ')} of the month" : "Monthly"
93 end
70 end 94 end
71 end 95 end
72 return nil unless base 96 return nil unless base
diff --git a/app/views/events/edit.html.erb b/app/views/events/edit.html.erb
index b6564a4..bf8cb35 100644
--- a/app/views/events/edit.html.erb
+++ b/app/views/events/edit.html.erb
@@ -56,9 +56,15 @@
56 <div id="rrule_monthly_options" style="display: none;"> 56 <div id="rrule_monthly_options" style="display: none;">
57 <p><label><%= check_box_tag "rrule_monthly_ordinal" %> On a specific weekday each month</label></p> 57 <p><label><%= check_box_tag "rrule_monthly_ordinal" %> On a specific weekday each month</label></p>
58 <p id="rrule_ordinal_fields" style="display: none;"> 58 <p id="rrule_ordinal_fields" style="display: none;">
59 <%= select_tag "rrule_ordinal", options_for_select([["1.", 1], ["2.", 2], ["3.", 3], ["4.", 4], ["letzter", -1], ["vorletzter", -2]]) %> 59 <%= select_tag "rrule_ordinal", options_for_select([["1.", 1], ["2.", 2], ["3.", 3], ["4.", 4], ["letzter", -1], ["vorletzter", -2], ["Selected weeks", "custom"]]) %>
60 <%= select_tag "rrule_ordinal_day", options_for_select(%w[MO TU WE TH FR SA SU].map { |c| [RruleHumanizer::WEEKDAY_NAMES[:de][c], c] }) %> 60 <%= select_tag "rrule_ordinal_day", options_for_select(%w[MO TU WE TH FR SA SU].map { |c| [RruleHumanizer::WEEKDAY_NAMES[:de][c], c] }) %>
61 </p> 61 </p>
62 <p id="rrule_custom_ordinal_fields" style="display: none;">
63 Weeks of the month:
64 <% (1..5).each do |ordinal| %>
65 <label><%= check_box_tag "rrule_custom_ordinal_#{ordinal}" %> <%= ordinal %>.</label>
66 <% end %>
67 </p>
62 </div> 68 </div>
63 <p> 69 <p>
64 <label><%= check_box_tag "rrule_exclude_month" %> Except in one month</label> 70 <label><%= check_box_tag "rrule_exclude_month" %> Except in one month</label>
diff --git a/public/javascripts/admin_interface.js b/public/javascripts/admin_interface.js
index 9ce0b9e..3988016 100644
--- a/public/javascripts/admin_interface.js
+++ b/public/javascripts/admin_interface.js
@@ -415,8 +415,10 @@ rrule_builder = {
415 }); 415 });
416 $("#rrule_monthly_ordinal").bind("change", function() { 416 $("#rrule_monthly_ordinal").bind("change", function() {
417 $("#rrule_ordinal_fields").toggle($(this).is(":checked")); 417 $("#rrule_ordinal_fields").toggle($(this).is(":checked"));
418 rrule_builder.toggle_custom_ordinal_fields();
418 rrule_builder.sync(); 419 rrule_builder.sync();
419 }); 420 });
421 $("#rrule_ordinal").bind("change", rrule_builder.toggle_custom_ordinal_fields);
420 $("#rrule_exclude_month").bind("change", function() { 422 $("#rrule_exclude_month").bind("change", function() {
421 $("#rrule_excluded_month").toggle($(this).is(":checked")); 423 $("#rrule_excluded_month").toggle($(this).is(":checked"));
422 rrule_builder.sync(); 424 rrule_builder.sync();
@@ -433,6 +435,12 @@ rrule_builder = {
433 $("#rrule_monthly_options").show(); 435 $("#rrule_monthly_options").show();
434 }, 436 },
435 437
438 toggle_custom_ordinal_fields : function() {
439 $("#rrule_custom_ordinal_fields").toggle(
440 $("#rrule_monthly_ordinal").is(":checked") && $("#rrule_ordinal").val() === "custom"
441 );
442 },
443
436 sync : function() { 444 sync : function() {
437 var freq = $("input[name='rrule_freq']:checked").val(); 445 var freq = $("input[name='rrule_freq']:checked").val();
438 var parts = []; 446 var parts = [];
@@ -448,7 +456,16 @@ rrule_builder = {
448 } else { 456 } else {
449 parts.push("FREQ=MONTHLY"); 457 parts.push("FREQ=MONTHLY");
450 if ($("#rrule_monthly_ordinal").is(":checked")) { 458 if ($("#rrule_monthly_ordinal").is(":checked")) {
451 parts.push("BYDAY=" + $("#rrule_ordinal").val() + $("#rrule_ordinal_day").val()); 459 var ordinal = $("#rrule_ordinal").val();
460 if (ordinal === "custom") {
461 var customDays = [];
462 $("input[id^='rrule_custom_ordinal_']:checked").each(function() {
463 customDays.push($(this).attr("id").replace("rrule_custom_ordinal_", "") + $("#rrule_ordinal_day").val());
464 });
465 if (customDays.length > 0) parts.push("BYDAY=" + customDays.join(","));
466 } else {
467 parts.push("BYDAY=" + ordinal + $("#rrule_ordinal_day").val());
468 }
452 } 469 }
453 } 470 }
454 471
@@ -483,8 +500,33 @@ rrule_builder = {
483 } else if (parts.FREQ === "MONTHLY") { 500 } else if (parts.FREQ === "MONTHLY") {
484 $("input[name='rrule_freq'][value='monthly']").prop("checked", true); 501 $("input[name='rrule_freq'][value='monthly']").prop("checked", true);
485 rrule_builder.show_monthly_options(); 502 rrule_builder.show_monthly_options();
503 var ordinalDays = parts.BYDAY && parts.BYDAY.split(",");
504 var customOrdinalDay = null;
505 var customOrdinals = [];
506 var customOrdinalsValid = ordinalDays && ordinalDays.length > 0;
507 if (customOrdinalsValid) {
508 ordinalDays.forEach(function(value) {
509 var customMatch = value.match(/^([1-5])([A-Z]{2})$/);
510 if (!customMatch || (customOrdinalDay && customOrdinalDay !== customMatch[2])) {
511 customOrdinalsValid = false;
512 return;
513 }
514 customOrdinalDay = customMatch[2];
515 customOrdinals.push(customMatch[1]);
516 });
517 }
518
486 var match = parts.BYDAY && parts.BYDAY.match(/^(-?\d+)([A-Z]{2})$/); 519 var match = parts.BYDAY && parts.BYDAY.match(/^(-?\d+)([A-Z]{2})$/);
487 if (match) { 520 if (customOrdinalsValid && (customOrdinals.length > 1 || customOrdinals[0] === "5")) {
521 $("#rrule_monthly_ordinal").prop("checked", true);
522 $("#rrule_ordinal_fields").show();
523 $("#rrule_ordinal").val("custom");
524 $("#rrule_ordinal_day").val(customOrdinalDay);
525 customOrdinals.forEach(function(ordinal) {
526 $("#rrule_custom_ordinal_" + ordinal).prop("checked", true);
527 });
528 rrule_builder.toggle_custom_ordinal_fields();
529 } else if (match) {
488 $("#rrule_monthly_ordinal").prop("checked", true); 530 $("#rrule_monthly_ordinal").prop("checked", true);
489 $("#rrule_ordinal_fields").show(); 531 $("#rrule_ordinal_fields").show();
490 $("#rrule_ordinal").val(match[1]); 532 $("#rrule_ordinal").val(match[1]);
diff --git a/test/models/concerns/rrule_humanizer_test.rb b/test/models/concerns/rrule_humanizer_test.rb
index 279ff73..c4e78d4 100644
--- a/test/models/concerns/rrule_humanizer_test.rb
+++ b/test/models/concerns/rrule_humanizer_test.rb
@@ -47,6 +47,24 @@ class RruleHumanizerTest < ActiveSupport::TestCase
47 assert_equal "Every second-to-last Thursday of the month", humanize("FREQ=MONTHLY;BYDAY=-2TH", :en) 47 assert_equal "Every second-to-last Thursday of the month", humanize("FREQ=MONTHLY;BYDAY=-2TH", :en)
48 end 48 end
49 49
50 test "monthly selected weeks" do
51 assert_equal "Jeden ersten Dienstag und dritten Dienstag im Monat",
52 humanize("FREQ=MONTHLY;BYDAY=1TU,3TU")
53 assert_equal "Every first Tuesday and third Tuesday of the month",
54 humanize("FREQ=MONTHLY;BYDAY=1TU,3TU", :en)
55 end
56
57 test "monthly fifth weekday" do
58 assert_equal "Jeden fünften Dienstag im Monat", humanize("FREQ=MONTHLY;BYDAY=5TU")
59 assert_equal "Every fifth Tuesday of the month", humanize("FREQ=MONTHLY;BYDAY=5TU", :en)
60 end
61
62 test "monthly weekday excluding one ordinal" do
63 rrule = "FREQ=MONTHLY;BYDAY=1TU,3TU,4TU,5TU"
64 assert_equal "Jeden Dienstag im Monat, außer dem zweiten Dienstag", humanize(rrule)
65 assert_equal "Every Tuesday of the month, except the second Tuesday", humanize(rrule, :en)
66 end
67
50 test "monthly no byday" do 68 test "monthly no byday" do
51 assert_equal "Monatlich", humanize("FREQ=MONTHLY") 69 assert_equal "Monatlich", humanize("FREQ=MONTHLY")
52 assert_equal "Monthly", humanize("FREQ=MONTHLY", :en) 70 assert_equal "Monthly", humanize("FREQ=MONTHLY", :en)