From e195a00d1b8cc306acad381561799e693bfc2fe9 Mon Sep 17 00:00:00 2001 From: Lars Eggert <lars@eggert.org> Date: Thu, 18 Nov 2021 15:18:48 +0000 Subject: [PATCH] And more agenda fixes. - Legacy-Id: 19682 --- ietf/meeting/views.py | 6 +- ietf/static/js/agenda_filter.js | 163 ++++--- ietf/static/js/agenda_personalize.js | 41 ++ ietf/static/js/agenda_timezone.js | 20 +- ietf/static/js/timezone.js | 42 +- ietf/templates/meeting/agenda.html | 121 ++++-- .../templates/meeting/agenda_personalize.html | 408 ------------------ .../agenda_personalize_buttonlist.html | 24 +- ietf/templates/meeting/meeting_heading.html | 15 +- package.json | 1 + 10 files changed, 271 insertions(+), 570 deletions(-) create mode 100644 ietf/static/js/agenda_personalize.js delete mode 100644 ietf/templates/meeting/agenda_personalize.html diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 72161a16b..58499a7d2 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -1530,6 +1530,7 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc="" is_current_meeting = (num is None) or (num == get_current_ietf_meeting_num()) rendered_page = render(request, "meeting/"+base+ext, { + "personalize": False, "schedule": schedule, "filtered_assignments": filtered_assignments, "updated": updated, @@ -1701,8 +1702,9 @@ def agenda_personalize(request, num): return render( request, - "meeting/agenda_personalize.html", + "meeting/agenda.html", { + 'personalize': True, 'schedule': meeting.schedule, 'updated': meeting.updated(), 'filtered_assignments': filtered_assignments, @@ -4161,4 +4163,4 @@ def approve_proposed_slides(request, slidesubmission_id, num): 'session_number': session_number, 'existing_doc' : existing_doc, 'form': form, - }) + }) \ No newline at end of file diff --git a/ietf/static/js/agenda_filter.js b/ietf/static/js/agenda_filter.js index 92da3f600..b84877960 100644 --- a/ietf/static/js/agenda_filter.js +++ b/ietf/static/js/agenda_filter.js @@ -3,7 +3,7 @@ window.agenda_filter_for_testing; // methods to be accessed for automated testin // closure to create private scope (function () { - 'use strict' + 'use strict'; /* n.b., const refers to the opts object itself, not its contents. * Use camelCase for easy translation into element.dataset keys, @@ -15,10 +15,10 @@ window.agenda_filter_for_testing; // methods to be accessed for automated testin }; /* Remove from list, if present */ - function remove_list_item (list, item) { + function remove_list_item(list, item) { var item_index = list.indexOf(item); if (item_index !== -1) { - list.splice(item_index, 1) + list.splice(item_index, 1); } } @@ -26,63 +26,68 @@ window.agenda_filter_for_testing; // methods to be accessed for automated testin * * Returns true if added to the list, otherwise false. */ - function toggle_list_item (list, item) { + function toggle_list_item(list, item) { var item_index = list.indexOf(item); if (item_index === -1) { - list.push(item) + list.push(item); return true; } else { - list.splice(item_index, 1) + list.splice(item_index, 1); return false; } } - function parse_query_params (qs) { - var params = {} - qs = decodeURI(qs).replace(/^\?/, '').toLowerCase() + function parse_query_params(qs) { + var params = {}; + qs = decodeURI(qs) + .replace(/^\?/, '') + .toLowerCase(); if (qs) { - var param_strs = qs.split('&') + var param_strs = qs.split('&'); for (var ii = 0; ii < param_strs.length; ii++) { - var toks = param_strs[ii].split('=', 2) - params[toks[0]] = toks[1] || true + var toks = param_strs[ii].split('=', 2); + params[toks[0]] = toks[1] || true; } } - return params + return params; } /* filt = 'show' or 'hide' */ - function get_filter_from_qparams (qparams, filt) { + function get_filter_from_qparams(qparams, filt) { if (!qparams[filt] || (qparams[filt] === true)) { return []; } var result = []; var qp = qparams[filt].split(','); - + for (var ii = 0; ii < qp.length; ii++) { result.push(qp[ii].trim()); } return result; } - function get_filter_params (qparams) { + function get_filter_params(qparams) { var enabled = opts.alwaysShow || qparams.show || qparams.hide; return { enabled: enabled, show: get_filter_from_qparams(qparams, 'show'), hide: get_filter_from_qparams(qparams, 'hide') - } + }; } function get_keywords(elt) { - var keywords = $(elt).attr('data-filter-keywords'); + var keywords = $(elt) + .attr('data-filter-keywords'); if (keywords) { - return keywords.toLowerCase().split(','); + return keywords.toLowerCase() + .split(','); } return []; } function get_item(elt) { - return $(elt).attr('data-filter-item'); + return $(elt) + .attr('data-filter-item'); } // utility method - is there a match between two lists of keywords? @@ -94,25 +99,27 @@ window.agenda_filter_for_testing; // methods to be accessed for automated testin } return false; } - + // Find the items corresponding to a keyword - function get_items_with_keyword (keyword) { + function get_items_with_keyword(keyword) { var items = []; - $('.view button.pickview').filter(function(index, elt) { - return keyword_match(get_keywords(elt), [keyword]); - }).each(function (index, elt) { - items.push(get_item($(elt))); - }); + $('.view button.pickview') + .filter(function (index, elt) { + return keyword_match(get_keywords(elt), [keyword]); + }) + .each(function (index, elt) { + items.push(get_item($(elt))); + }); return items; } - function filtering_is_enabled (filter_params) { + function filtering_is_enabled(filter_params) { return filter_params.enabled; } // Update the filter / customization UI to match the current filter parameters - function update_filter_ui (filter_params) { + function update_filter_ui(filter_params) { var buttons = $('.pickview'); if (!filtering_is_enabled(filter_params)) { @@ -121,12 +128,12 @@ window.agenda_filter_for_testing; // methods to be accessed for automated testin return; } - update_href_querystrings(filter_params_as_querystring(filter_params)) + update_href_querystrings(filter_params_as_querystring(filter_params)); // show the customizer - it will stay visible even if filtering is disabled const customizer = $('#customize'); if (customizer.hasClass('collapse')) { - customizer.collapse('show') + customizer.collapse('show'); } // Update button state to match visibility @@ -135,7 +142,7 @@ window.agenda_filter_for_testing; // methods to be accessed for automated testin var keywords = get_keywords(elt); keywords.push(get_item(elt)); // treat item as one of its keywords var hidden = keyword_match(filter_params.hide, keywords); - var shown = keyword_match(filter_params.show, keywords); + var shown = keyword_match(filter_params.show, keywords); if (shown && !hidden) { elt.addClass('active'); } else { @@ -149,11 +156,11 @@ window.agenda_filter_for_testing; // methods to be accessed for automated testin * Calling the individual update_* functions outside of this method will likely cause * various parts of the page to get out of sync. */ - function update_view () { - var filter_params = get_filter_params(parse_query_params(window.location.search)) - update_filter_ui(filter_params) + function update_view() { + var filter_params = get_filter_params(parse_query_params(window.location.search)); + update_filter_ui(filter_params); if (opts.updateCallback) { - opts.updateCallback(filter_params) + opts.updateCallback(filter_params); } } @@ -162,19 +169,19 @@ window.agenda_filter_for_testing; // methods to be accessed for automated testin * Updates the URL to match filter_params, then updates the history / display to match * (if supported) or loads the new URL. */ - function update_filters (filter_params) { + function update_filters(filter_params) { var new_url = replace_querystring( - window.location.href, - filter_params_as_querystring(filter_params) - ) - update_href_querystrings(filter_params_as_querystring(filter_params)) + window.location.href, + filter_params_as_querystring(filter_params) + ); + update_href_querystrings(filter_params_as_querystring(filter_params)); if (window.history && window.history.replaceState) { // Keep current origin, replace search string, no page reload - history.replaceState({}, document.title, new_url) - update_view() + history.replaceState({}, document.title, new_url); + update_view(); } else { // No window.history.replaceState support, page reload required - window.location = new_url + window.location = new_url; } } @@ -183,34 +190,35 @@ window.agenda_filter_for_testing; // methods to be accessed for automated testin */ function update_href_querystrings(querystring) { Array.from( - document.getElementsByClassName('agenda-link filterable') - ).forEach( - (elt) => elt.href = replace_querystring(elt.href, querystring) - ) + document.getElementsByClassName('agenda-link filterable') + ) + .forEach( + (elt) => elt.href = replace_querystring(elt.href, querystring) + ); } function filter_params_as_querystring(filter_params) { - var qparams = [] + var qparams = []; if (filter_params.show.length > 0) { - qparams.push('show=' + filter_params.show.join()) + qparams.push('show=' + filter_params.show.join()); } if (filter_params.hide.length > 0) { - qparams.push('hide=' + filter_params.hide.join()) + qparams.push('hide=' + filter_params.hide.join()); } if (qparams.length > 0) { - return '?' + qparams.join('&') + return '?' + qparams.join('&'); } - return '' + return ''; } function replace_querystring(url, new_querystring) { - return url.replace(/(\?.*)?(#.*)?$/, new_querystring + window.location.hash) + return url.replace(/(\?.*)?(#.*)?$/, new_querystring + window.location.hash); } /* Helper for pick group/type button handlers - toggles the appropriate parameter entry * elt - the jquery element that was clicked */ - function handle_pick_button (elt) { + function handle_pick_button(elt) { var fp = get_filter_params(parse_query_params(window.location.search)); var item = get_item(elt); @@ -232,17 +240,17 @@ window.agenda_filter_for_testing; // methods to be accessed for automated testin * an area will enable all items in the row as one would expect. */ if (just_showed_item) { var children = get_items_with_keyword(item); - $.each(children, function(index, child) { + $.each(children, function (index, child) { remove_list_item(fp.show, child); remove_list_item(fp.hide, child); }); } - + // If the show list is empty, clear the hide list because there is nothing to hide if (fp.show.length === 0) { fp.hide = []; } - + return fp; } @@ -251,23 +259,13 @@ window.agenda_filter_for_testing; // methods to be accessed for automated testin } function register_handlers() { - $('.pickview').on("click", function () { - if (is_disabled($(this))) { return; } - var fp = handle_pick_button($(this)); - update_filters(fp); - }); - } - - /** - * Read options from the template - */ - function read_template_options() { - const opts_elt = document.getElementById('agenda-filter-options'); - opts.keys().forEach((opt) => { - if (opt in opts_elt.dataset) { - opts[opt] = opts_elt.dataset[opt]; - } - }); + $('.pickview') + .on("click", function () { + console.log("pickview"); + if (is_disabled($(this))) { return; } + var fp = handle_pick_button($(this)); + update_filters(fp); + }); } /* Entry point to filtering code when page loads @@ -275,17 +273,18 @@ window.agenda_filter_for_testing; // methods to be accessed for automated testin * This must be called if you are using the HTML template to provide a customization * button UI. Do not call if you only want to use the parameter parsing routines. */ - function enable () { + function enable() { // ready handler fires immediately if document is already "ready" - $(document).ready(function () { - register_handlers(); - update_view(); - }) + $(document) + .ready(function () { + register_handlers(); + update_view(); + }) } // utility method - filter a jquery set to those matching a keyword function rows_matching_filter_keyword(rows, kw) { - return rows.filter(function(index, element) { + return rows.filter(function (index, element) { var row_kws = get_keywords(element); return keyword_match(row_kws, [kw.toLowerCase()]); }); @@ -305,6 +304,6 @@ window.agenda_filter_for_testing; // methods to be accessed for automated testin keyword_match: keyword_match, parse_query_params: parse_query_params, rows_matching_filter_keyword: rows_matching_filter_keyword, - set_update_callback: function (cb) {opts.updateCallback = cb} + set_update_callback: function (cb) { opts.updateCallback = cb } }; })(); \ No newline at end of file diff --git a/ietf/static/js/agenda_personalize.js b/ietf/static/js/agenda_personalize.js new file mode 100644 index 000000000..edb2787f3 --- /dev/null +++ b/ietf/static/js/agenda_personalize.js @@ -0,0 +1,41 @@ +// Copyright The IETF Trust 2021, All Rights Reserved + +/** + * Agenda personalization JS methods + * + * Requires agenda_timezone.js and timezone.js be included. + */ +'use strict'; + +/** + * Update the checkbox state to match the filter parameters + */ +function updateAgendaCheckboxes(filter_params) { + var selection_inputs = document.getElementsByName('selected-sessions'); + selection_inputs.forEach((inp) => { + const item_keywords = inp.dataset.filterKeywords.toLowerCase() + .split(','); + if ( + agenda_filter.keyword_match(item_keywords, filter_params.show) && + !agenda_filter.keyword_match(item_keywords, filter_params.hide) + ) { + inp.checked = true; + } else { + inp.checked = false; + } + }); +} + +window.handleFilterParamUpdate = function (filter_params) { + updateAgendaCheckboxes(filter_params); +}; + +window.handleTableClick = function (event) { + if (event.target.name === 'selected-sessions') { + // hide the tooltip after clicking on a checkbox + const jqElt = jQuery(event.target); + if (jqElt.tooltip) { + jqElt.tooltip('hide'); + } + } +}; \ No newline at end of file diff --git a/ietf/static/js/agenda_timezone.js b/ietf/static/js/agenda_timezone.js index bef58d6cb..5b8907c83 100644 --- a/ietf/static/js/agenda_timezone.js +++ b/ietf/static/js/agenda_timezone.js @@ -13,12 +13,12 @@ var local_timezone = moment.tz.guess(); // get_current_tz_cb must be overwritten using set_current_tz_cb function get_current_tz_cb() { - throw new Error('Tried to get current timezone before callback registered. Use set_current_tz_cb().') + throw new Error('Tried to get current timezone before callback registered. Use set_current_tz_cb().'); }; // Initialize moments window.initialize_moments = function () { - var times = $('div.time') + var times = $('div.time'); $.each(times, function (i, item) { item.start_ts = moment.unix(this.getAttribute("data-start-time")) .utc(); @@ -33,7 +33,7 @@ window.initialize_moments = function () { item.format = +this.getAttribute("format"); } }); - var times = $('[data-slot-start-ts]') + times = $('[data-slot-start-ts]'); $.each(times, function (i, item) { item.slot_start_ts = moment.unix(this.getAttribute("data-slot-start-ts")) .utc(); @@ -205,7 +205,7 @@ window.update_times = function (newtz) { }); update_tooltips_all(); update_clock(); -} +}; // Highlight ongoing based on the current time window.highlight_ongoing = function () { @@ -213,7 +213,7 @@ window.highlight_ongoing = function () { .remove("#now"); $('.table-warning') .removeClass("table-warning"); - var agenda_rows = $('[data-slot-start-ts]') + var agenda_rows = $('[data-slot-start-ts]'); agenda_rows = agenda_rows.filter(function () { return moment() .isBetween(this.slot_start_ts, this.slot_end_ts); @@ -245,13 +245,13 @@ window.update_tooltips_all = function () { $(this) .html(format_tooltip_table(this.start_ts, this.end_ts)); }); -} +}; // Update clock window.update_clock = function () { $('#current-time') .html(format_time(moment(), get_current_tz_cb(), 0)); -} +}; $.urlParam = function (name) { var results = new RegExp('[\?&]' + name + '=([^&#]*)') @@ -261,7 +261,7 @@ $.urlParam = function (name) { } else { return results[1] || 0; } -} +}; window.init_timers = function () { var fast_timer = 60000 / (speedup > 600 ? 600 : speedup); @@ -271,9 +271,9 @@ window.init_timers = function () { setInterval(function () { highlight_ongoing(); }, fast_timer); setInterval(function () { update_tooltips(); }, fast_timer); setInterval(function () { update_tooltips_all(); }, 3600000 / speedup); -} +}; // set method used to find current time zone window.set_current_tz_cb = function (fn) { get_current_tz_cb = fn; -} \ No newline at end of file +}; \ No newline at end of file diff --git a/ietf/static/js/timezone.js b/ietf/static/js/timezone.js index fda6f7f47..5f77dccf3 100644 --- a/ietf/static/js/timezone.js +++ b/ietf/static/js/timezone.js @@ -17,18 +17,19 @@ window.ietf_timezone; // public interface var current_timezone; // Select timezone to use. Arg is name of a timezone or 'local' to guess local tz. - function use_timezone (newtz) { + function use_timezone(newtz) { // Guess local timezone if necessary if (newtz.toLowerCase() === 'local') { - newtz = moment.tz.guess() + newtz = moment.tz.guess(); } if (current_timezone !== newtz) { - current_timezone = newtz + current_timezone = newtz; // Update values of tz-select inputs but do not trigger change event - $('select.tz-select').val(newtz) + $('select.tz-select') + .val(newtz); if (timezone_change_callback) { - timezone_change_callback(newtz) + timezone_change_callback(newtz); } } } @@ -38,37 +39,40 @@ window.ietf_timezone; // public interface * This will set the timezone to the value of 'current'. Set up the tz_change callback * before initializing. */ - function timezone_init (current) { - var tz_names = moment.tz.names() - var select = $('select.tz-select') + function timezone_init(current) { + var tz_names = moment.tz.names(); + var select = $('select.tz-select'); - select.empty() + select.empty(); $.each(tz_names, function (i, item) { if (current === item) { select.append($('<option/>', { - selected: 'selected', html: item, value: item - })) + selected: 'selected', + html: item, + value: item + })); } else { select.append($('<option/>', { - html: item, value: item - })) + html: item, + value: item + })); } - }) - select.on("change", function () { use_timezone(this.value)}); + }); + select.on("change", function () { use_timezone(this.value); }); /* When navigating back/forward, the browser may change the select input's * value after the window load event. It does not fire the change event on * the input when it does this. The pageshow event occurs after such an update, * so trigger the change event ourselves to be sure the UI stays consistent * with the timezone select input. */ - window.addEventListener('pageshow', function(){select.trigger("change"); }) + window.addEventListener('pageshow', function () { select.trigger("change"); }); use_timezone(current); } // Expose public interface ietf_timezone = { - get_current_tz: function() {return current_timezone}, + get_current_tz: function () { return current_timezone }, initialize: timezone_init, - set_tz_change_callback: function(cb) {timezone_change_callback=cb}, + set_tz_change_callback: function (cb) { timezone_change_callback = cb; }, use: use_timezone - } + }; })(); \ No newline at end of file diff --git a/ietf/templates/meeting/agenda.html b/ietf/templates/meeting/agenda.html index d8459183b..824b22d54 100644 --- a/ietf/templates/meeting/agenda.html +++ b/ietf/templates/meeting/agenda.html @@ -4,13 +4,16 @@ {% load static %} {% load ietf_filters %} {% load textfilters %} -{% load htmlfilters agenda_custom_tags%} +{% load htmlfilters agenda_custom_tags %} {% block title %} IETF {{ schedule.meeting.number }} Meeting Agenda {% if "-utc" in request.path %} (UTC) {% endif %} + {% if personalize %} + Personalization + {% endif %} {% endblock %} {% block morecss %} @@ -24,9 +27,10 @@ <div class="row"> <div class="col-md-10"> - {% if "-utc" in request.path %} {% include "meeting/meeting_heading.html" with meeting=schedule.meeting updated=updated selected="agenda-utc" title_extra="(UTC)" %} + {% elif personalize %} + {% include "meeting/meeting_heading.html" with meeting=schedule.meeting updated=updated selected="select-sessions" title_extra="" %} {% else %} {% include "meeting/meeting_heading.html" with meeting=schedule.meeting updated=updated selected="agenda" title_extra="" %} {% endif %} @@ -34,9 +38,16 @@ {# cache this part -- it takes 3-6 seconds to generate #} {% load cache %} {% cache cache_time ietf_meeting_agenda_utc schedule.meeting.number request.path %} + <div class="row"> <div class="col-5"> - <h2>Agenda</h2> + <h2> + {% if personalize %} + Session Selection + {% else %} + Agenda + {% endif %} + </h2> </div> <div class="col float-end tz-display"> <div class="input-group input-group-sm"> @@ -68,28 +79,22 @@ {% endif %} <p> - {% include "meeting/agenda_filter.html" with filter_categories=filter_categories customize_button_text="Customize the agenda view..." %} + {% include "meeting/agenda_filter.html" with filter_categories=filter_categories customize_button_text="Personalize the agenda view..." always_show=personalize%} </p> - <p> - <div class="d-inline mb-3">Download agenda as .ics:</div> + {% include "meeting/agenda_personalize_buttonlist.html" with meeting=schedule.meeting only %} - <div class="d-inline"> - <a id="ical-link" class="visually-hidden btn btn-sm btn-primary agenda-link filterable" href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}">Customized schedule above</a> - </div> - - <div class="input-group input-group-sm mb-3 d-inline"> - <button class="btn btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">By area</button><ul class="dropdown-menu"> - {% for fc in filter_categories %} - {% if not forloop.last %} {# skip the last group, it's the office hours/misc #} - {% for p in fc|dictsort:"label" %} - <li><a class="dropdown-item" href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}?show={{p.keyword}}">{{p.label}}</a></li> - {% endfor %} - {% endif %} - {% endfor %} - </ul><a class="btn btn-outline-primary" href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}?show={{ non_area_keywords|join:',' }}">Non-area events</a> - </div> - </p> + <div class="input-group input-group-sm mb-3"> + <button class="btn btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Download area agenda</button><ul class="dropdown-menu"> + {% for fc in filter_categories %} + {% if not forloop.last %} {# skip the last group, it's the office hours/misc #} + {% for p in fc|dictsort:"label" %} + <li><a class="dropdown-item" href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}?show={{p.keyword}}">{{p.label}}</a></li> + {% endfor %} + {% endif %} + {% endfor %} + </ul><a class="btn btn-outline-primary" href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}?show={{ non_area_keywords|join:',' }}">Download non-area events</a> + </div> <div id="weekview" class="visually-hidden mt-3"> <h2> @@ -103,14 +108,22 @@ <iframe class="w-100 overflow-hidden border border-dark" scrolling="no"></iframe> </div> - <h2>Detailed Agenda</h2> + <h2 class="mt-3"> + {% if personalize %} + Personalize + {% endif %} + Detailed Agenda + </h2> + {% if personalize %} + <p>Check boxes below to select individual sessions.</p> + {% endif %} - <table class="table table-sm"> + <table id="agenda-table" class="table table-sm"> {% for item in filtered_assignments %} {% ifchanged item.timeslot.time|date:"Y-m-d" %} <tr class="table-primary"> - <th colspan="5"> + <th colspan="6"> {# The anchor here needs to be in a div, not in the th, in order for the anchor-target margin hack to work #} <div class="anchor-target" id="slot-{{item.timeslot.time|slugify}}"></div> <div class="h6 mt-2">{{ item.timeslot.time|date:"l, F j, Y" }}</div> @@ -122,7 +135,20 @@ <tr id="row-{{ item.slug }}" data-filter-keywords="{{ item.filter_keywords|join:',' }}" data-slot-start-ts="{{item.start_timestamp}}" data-slot-end-ts="{{item.end_timestamp}}"> - <td class="text-nowrap text-right"> + <td class="text-center"> + {% if item.session_keyword %} + <input + type="checkbox" + class="pickview form-check-input" + title="Select session" + name="selected-sessions" + value="{{ item.session_keyword }}" + data-filter-keywords="{{ item.filter_keywords|join:',' }}" + data-filter-item="{{ item.session_keyword }}"> + {% endif %} + </td> + + <td class="text-nowrap text-end"> {% include "meeting/timeslot_start_end.html" %} </td> <td colspan="3"> @@ -171,7 +197,10 @@ <tr class="table-secondary session-label-row" data-slot-start-ts="{{item.start_timestamp}}" data-slot-end-ts="{{item.end_timestamp}}"> - <th class="text-nowrap text-right"> + <td class="text-center"> + </td> + + <th class="text-nowrap text-end"> {% include "meeting/timeslot_start_end.html" %} </th> <th colspan="4"> @@ -188,8 +217,22 @@ data-filter-keywords="{{ item.filter_keywords|join:',' }}" data-slot-start-ts="{{item.start_timestamp}}" data-slot-end-ts="{{item.end_timestamp}}"> + + <td class="text-center"> + {% if item.session_keyword %} + <input + type="checkbox" + class="pickview form-check-input" + title="Select session" + name="selected-sessions" + value="{{ item.session_keyword }}" + data-filter-keywords="{{ item.filter_keywords|join:',' }}" + data-filter-item="{{ item.session_keyword }}"> + {% endif %} + </td> + {% if item.slot_type.slug == 'plenary' %} - <th class="text-nowrap text-right"> + <th class="text-nowrap text-end"> {% include "meeting/timeslot_start_end.html" %} </th> <td colspan="3"> @@ -270,6 +313,8 @@ {% endif %} {% endfor %} </table> +{% include "meeting/agenda_personalize_buttonlist.html" with meeting=schedule.meeting only %} + </div> <div class="col-md-2 d-print-none" id="affix"> @@ -341,8 +386,7 @@ } function update_ical_links(filter_params) { - var ical_link = $("#ical-link"); - ical_link.toggleClass("visually-hidden", !agenda_filter.filtering_is_enabled(filter_params)); + $(".ical-link").toggleClass("visually-hidden", !agenda_filter.filtering_is_enabled(filter_params)); } function update_weekview(filter_params) { @@ -389,6 +433,10 @@ <script src="{% static 'ietf/js/timezone.js' %}"></script> <script src="{% static 'ietf/js/agenda_materials.js' %}"></script> <script src="{% static 'ietf/js/agenda_timezone.js' %}"></script> + {% if personalize %} + <script src="{% static 'ietf/js/agenda_personalize.js' %}"></script> + {% endif %} + <script> {% if settings.DEBUG and settings.DEBUG_AGENDA %} speedup = +$.urlParam('speedup'); @@ -441,7 +489,18 @@ init_timers(); // Finally, set up the agenda filter UI. This does not depend on the timezone. - agenda_filter.set_update_callback(update_view); + {% if personalize %} + agenda_filter.set_update_callback(function (e) { + handleFilterParamUpdate(e); + update_view(e); + }); + + document.getElementById('agenda-table') + .addEventListener('click', handleTableClick); + {% else %} + agenda_filter.set_update_callback(update_view); + {% endif %} + agenda_filter.enable(); } ); diff --git a/ietf/templates/meeting/agenda_personalize.html b/ietf/templates/meeting/agenda_personalize.html deleted file mode 100644 index f208ef8ec..000000000 --- a/ietf/templates/meeting/agenda_personalize.html +++ /dev/null @@ -1,408 +0,0 @@ -{% extends "base.html" %} -{# Copyright The IETF Trust 2021, All Rights Reserved #} -{% load origin %} -{% load static %} -{% load ietf_filters %} -{% load textfilters %} -{% load htmlfilters %} - -{% block title %} - IETF {{ schedule.meeting.number }} meeting agenda personalization -{% endblock %} - -{% block morecss %} - tr:not(:first-child) th.gap { - height: 3em !important; - background-color: inherit !important; - border: none !important; - } - tr:first-child th.gap { - height: 0 !important; - background-color: inherit !important; - border: none !important; - } - div.tz-display { - margin-bottom: 0.5em; - margin-top: 1em; - text-align: right; - } - .tz-display a { - cursor: pointer; - } - .tz-display label { - font-weight: normal; - } - .tz-display select { - min-width: 15em; - } - #affix .nav li.tz-display { - padding: 4px 20px; - } - #affix .nav li.tz-display a { - display: inline; - padding: 0; - } -{% endblock %} - -{% block bodyAttrs %}data-bs-spy="scroll" data-bs-target="#affix"{% endblock %} - -{% block content %} - {% origin %} - - <div class="row"> - <div class="col-md-12"> - {% include "meeting/meeting_heading.html" with meeting=schedule.meeting updated=updated selected="select-sessions" title_extra="" %} - </div> - </div> - <div class="row"> - <div class="col-md-10"> - {# cache this part -- it takes 3-6 seconds to generate #} - {% load cache %} - {% cache cache_time ietf_meeting_agenda_personalize schedule.meeting.number request.path %} - <div class="row"> - <div class="col-xs-6"><h1>Session Selection</h1></div> - <div class="col-xs-6"> - <div class="tz-display"> - <div><small> - <label for="timezone-select">Time zone:</label> - <a id="meeting-timezone" onclick="ietf_timezone.use('{{ timezone }}')">Meeting</a> | - <a id="local-timezone" onclick="ietf_timezone.use('local')">Local</a> | - <a id="utc-timezone" onclick="ietf_timezone.use('UTC')">UTC</a> - </small></div> - <select id="timezone-select" class="tz-select"> - {# Avoid blank while loading. JavaScript replaces the option list after init. #} - <option selected>{{ timezone }}</option> - </select> - </div> - </div> - </div> - {% if is_current_meeting %} - <p class="alert alert-info"> - <b>Note:</b> IETF agendas are subject to change, up to and during a meeting. - </p> - {% endif %} - - {% include "meeting/agenda_filter.html" with filter_categories=filter_categories always_show=True %} - - {% include "meeting/agenda_personalize_buttonlist.html" with meeting=schedule.meeting only %} - - <h2> - Individual Sessions - </h2> - <p> - Check boxes below to select individual sessions. - </p> - - <table id="agenda-table" class="table table-sm table-striped"> - {% for item in filtered_assignments %} - - {% ifchanged item.timeslot.time|date:"Y-m-d" %} - <tr> - <th class="gap" colspan="7"></th> - </tr> - <tr class="table-warning"> - <th colspan="7"> - {# The anchor here needs to be in a div, not in the th, in order for the anchor-target margin hack to work #} - <div class="anchor-target" id="slot-{{ item.timeslot.time|slugify }}"></div> - {{ item.timeslot.time|date:"l, F j, Y" }} - </th> - </tr> - {% endifchanged %} - - {% if item.timeslot.type_id == 'regular' %} - {% ifchanged %} - <tr class="table-info session-label-row" - data-slot-start-ts="{{ item.start_timestamp }}" - data-slot-end-ts="{{ item.end_timestamp }}"> - <td class="leftmarker"></td> - <th class="text-nowrap text-right"> - <span class="d-none d-sm-block"> - {% include "meeting/timeslot_start_end.html" %} - </span> - </th> - <th colspan="4"> - <span class="d-none d-md-block d-lg-block d-xl-block d-xxl-block"> - {% include "meeting/timeslot_start_end.html" %} - </span> - {{ item.timeslot.time|date:"l" }} - {{ item.timeslot.name|capfirst_allcaps }} - </th> - <td class="rightmarker"></td> - </tr> - {% endifchanged %} - {% endif %} - - - {% if item.timeslot.type.slug == 'break' or item.timeslot.type.slug == 'reg' or item.timeslot.type.slug == 'other' %} - <tr id="row-{{ item.slug }}" - data-slot-start-ts="{{ item.start_timestamp }}" - data-slot-end-ts="{{ item.end_timestamp }}"> - <td class="leftmarker"> - {% if item.session_keyword %} - <input - type="checkbox" - class="pickview" - title="Select session" - name="selected-sessions" - value="{{ item.session_keyword }}" - data-filter-keywords="{{ item.filter_keywords|join:',' }}" - data-filter-item="{{ item.session_keyword }}"> - {% endif %} - </td> - <td class="text-nowrap text-right"> - <span class="d-none d-sm-block"> - {% include "meeting/timeslot_start_end.html" %} - </span> - </td> - <td colspan="3"> - <span class="d-none d-md-block d-lg-block d-xl-block d-xxl-block"> - {% include "meeting/timeslot_start_end.html" %} - </span> - {% if item.timeslot.show_location and item.timeslot.get_html_location %} - {% if schedule.meeting.number|add:"0" < 96 %} - {% comment %}<a href="https://tools.ietf.org/agenda/{{ schedule.meeting.number }}/venue/?room={{ item.timeslot.get_html_location|xslugify }}">{% endcomment $} - {{ item.timeslot.get_html_location }} - {% comment %}</a>{% endcomment %} - {% elif item.timeslot.location.floorplan %} - <a - href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a> - {% else %} - {{ item.timeslot.get_html_location }} - {% endif %} - {% with item.timeslot.location.floorplan as floor %} - {% if item.timeslot.location.floorplan %} - <span class="d-none d-sm-block"> - <a - href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}#{{ floor.name|xslugify }}" - class="float-end" title="{{ floor.name }}"><span - class="badge bg-blank label-wide">{{ floor.short }}</span></a> - </span> - {% endif %} - {% endwith %} - {% endif %} - </td> - <td> - {% if item.session.agenda %} - <a href="{{ item.session.agenda.get_href }}"> - {{ item.timeslot.name }} - </a> - {% else %} - {{ item.timeslot.name }} - {% endif %} - - {% if item.session.current_status == 'canceled' %} - <span class="badge bg-danger float-end">CANCELLED</span> - {% endif %} - </td> - <td class="rightmarker"></td> - </tr> - {% endif %} - - {% if item.timeslot.type_id == 'regular' or item.timeslot.type.slug == 'plenary' %} - {% if item.session.historic_group %} - <tr id="row-{{ item.slug }}" - {% if item.timeslot.type.slug == 'plenary' %}class="{{ item.timeslot.type.slug }}danger"{% endif %} - data-slot-start-ts="{{ item.start_timestamp }}" - data-slot-end-ts="{{ item.end_timestamp }}"> - <td class="leftmarker"> - {% if item.session_keyword %} - <input - type="checkbox" - class="pickview" - title="Select session" - name="selected-sessions" - value="{{ item.session_keyword }}" - data-filter-keywords="{{ item.filter_keywords|join:',' }}" - data-filter-item="{{ item.session_keyword }}"> - {% endif %} - </td> - {% if item.timeslot.type.slug == 'plenary' %} - <th class="text-nowrap text-right"> - <span class="d-none d-sm-block"> - {% include "meeting/timeslot_start_end.html" %} - </span> - </th> - <td colspan="3"> - <span class="d-none d-md-block d-lg-block d-xl-block d-xxl-block"> - {% include "meeting/timeslot_start_end.html" %} - </span> - {% if item.timeslot.show_location and item.timeslot.get_html_location %} - {% if schedule.meeting.number|add:"0" < 96 %} - {% comment %}<a href="https://tools.ietf.org/agenda/{{ schedule.meeting.number }}/venue/?room={{ item.timeslot.get_html_location|xslugify }}">{% endcomment %} - {{ item.timeslot.get_html_location }} - {% comment %}</a>{% endcomment %} - {% elif item.timeslot.location.floorplan %} - <a - href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a> - {% else %} - {{ item.timeslot.get_html_location }} - {% endif %} - {% endif %} - </td> - - {% else %} - <td> - {% with item.timeslot.location.floorplan as floor %} - {% if item.timeslot.location.floorplan %} - <span class="d-none d-sm-block"> - <a - href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}#{{ floor.name|xslugify }}" - class="float-end" title="{{ floor.name }}"><span - class="badge bg-blank">{{ floor.short }}</span></a> - </span> - {% endif %} - {% endwith %} - </td> - <td> - {% if item.timeslot.show_location and item.timeslot.get_html_location %} - {% if schedule.meeting.number|add:"0" < 96 %} - {% comment %}<a href="https://tools.ietf.org/agenda/{{ schedule.meeting.number }}/venue/?room={{ item.timeslot.get_html_location|xslugify }}">{% endcomment %} - {{ item.timeslot.get_html_location }} - {% comment %}</a>{% endcomment %} - {% elif item.timeslot.location.floorplan %} - <a - href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a> - {% else %} - {{ item.timeslot.get_html_location }} - {% endif %} - {% endif %} - </td> - - <td><span class="d-none d-sm-block">{{ item.session.historic_group.historic_parent.acronym }}</span></td> - - <td> - {% if item.session.historic_group %} - <a - href="{% url 'ietf.group.views.group_about' acronym=item.session.historic_group.acronym %}">{{ item.session.historic_group.acronym }}</a> - {% else %} - {{ item.session.historic_group.acronym }} - {% endif %} - </td> - {% endif %} - - <td> - {% if item.session.agenda %} - <a href="{{ item.session.agenda.get_href }}"> - {% endif %} - {% if item.timeslot.type.slug == 'plenary' %} - {{ item.timeslot.name }} - {% else %} - {{ item.session.historic_group.name }} - {% endif %} - {% if item.session.agenda %} - </a> - {% endif %} - - {% if item.session.current_status == 'canceled' %} - <span class="badge bg-danger float-end">CANCELLED</span> - {% endif %} - - {% if item.session.historic_group.state_id == "bof" %} - <span class="badge bg-success float-end">BOF</span> - {% endif %} - - {% if item.session.current_status == 'resched' %} - <span class="badge bg-danger float-end"> - RESCHEDULED - {% if item.session.rescheduled_to %} - TO - <span class="timetooltip reschedtimetooltip"><span class="time" - data-start-time="{{ item.session.rescheduled_to.utc_start_time|date:"U" }}" - data-end-time="{{ item.session.rescheduled_to.utc_end_time|date:"U" }}" - {% if item.timeslot.time|date:"l" != item.session.rescheduled_to.time|date:"l" %} - weekday="1"{% endif %}> - {% if "-utc" in request.path %} - {{ item.session.rescheduled_to.utc_start_time|date:"l G:i"|upper }}- - {{ item.session.rescheduled_to.utc_end_time|date:"G:i" }} - {% else %} - {{ item.session.rescheduled_to.time|date:"l G:i"|upper }}- - {{ item.session.rescheduled_to.end_time|date:"G:i" }} - {% endif %} - </span></span> - {% endif %} - </span> - {% endif %} - - {% if item.session.agenda_note|first_url|conference_url %} - <br> - <a href={{ item.session.agenda_note|first_url }}>{{ item.session.agenda_note|slice:":23" }}</a> - {% elif item.session.agenda_note %} - <br><span class="text-danger">{{ item.session.agenda_note }}</span> - {% endif %} - </td> - <td class="rightmarker"></td> - </tr> - {% endif %} - {% endif %} - {% endfor %} - </table> - - {% include "meeting/agenda_personalize_buttonlist.html" with meeting=schedule.meeting only %} - - </div> - <div class="col-md-2 d-print-none" id="affix"> - <ul class="nav nav-pills nav-stacked small" data-bs-spy="affix"> - <li><a href="#now">Now</a></li> - {% for item in filtered_assignments %} - {% ifchanged item.timeslot.time|date:"Y-m-d" %} - <li><a href="#slot-{{ item.timeslot.time|slugify }}">{{ item.timeslot.time|date:"l, F j, Y" }}</a></li> - {% endifchanged %} - {% endfor %} - <li> - <hr/> - </li> - <li class="tz-display">Showing <span class="current-tz">{{ timezone }}</span> time</li> - <li class="tz-display"><span> {# span avoids applying nav link styling to these shortcuts #} - <a onclick="ietf_timezone.use('{{ timezone }}')">Meeting time</a> | - <a onclick="ietf_timezone.use('local')">Local time</a> | - <a onclick="ietf_timezone.use('UTC')">UTC</a></span> - </li> - {% if settings.DEBUG and settings.DEBUG_AGENDA %} - <li> - <hr/> - </li> - <li><span id="current-time"></span></li> - {% endif %} - </ul> - </div> - </div> - - {% endcache %} - - {# make the timezone available to JS #} - <span id="initial-data" hidden data-timezone="{{ timezone }}"></span> -{% endblock %} - -{% block js %} - <script src="{% static 'ietf/js/moment.js' %}"></script> - <script src="{% static 'ietf/js/moment-timezone-with-data-10-year-range.js' %}"></script> - <script src="{% static 'ietf/js/timezone.js' %}"></script> - <script src="{% static 'ietf/js/agenda_timezone.js' %}"></script> - <script src="{% static 'ietf/js/agenda_filter.js' %}"></script> - <script src="{% static 'ietf/js/agenda/agenda_personalize.js' %}"></script> - <script> - - {% if settings.DEBUG and settings.DEBUG_AGENDA %} - speedup = +$.urlParam('speedup') - if (speedup < 1) { - speedup = 1 - } - start_time = moment().utc() - if ($.urlParam('date')) { - offset_time = moment.tz(decodeURIComponent($.urlParam('date')), 'UTC') - } else { - offset_time = start_time - } - if (speedup > 1 || offset_time != start_time) { - moment.now = function () { - return (+new Date() - start_time) * speedup + offset_time - } - } - {% else %} - speedup = 1 - {% endif %} - - /* pull this from the agenda_personalize js module to make available to agenda_timezone */ - meeting_timezone = agenda_personalize.meeting_timezone; - </script> -{% endblock %} \ No newline at end of file diff --git a/ietf/templates/meeting/agenda_personalize_buttonlist.html b/ietf/templates/meeting/agenda_personalize_buttonlist.html index fcad3a41e..99aee38a8 100644 --- a/ietf/templates/meeting/agenda_personalize_buttonlist.html +++ b/ietf/templates/meeting/agenda_personalize_buttonlist.html @@ -4,17 +4,17 @@ Buttons for the agenda_personalize.html template Required parameter: meeting - meeting being displayed {% endcomment %} {% load agenda_custom_tags %} -<div class="buttonlist"> - <a class="btn btn-primary agenda-link filterable" - href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}"> - View customized agenda - </a> - <a class="btn btn-primary agenda-link filterable" - href="{% url 'ietf.meeting.views.agenda_ical' num=meeting.number %}"> - Download as .ics - </a> - <a class="btn btn-primary agenda-link filterable" + +<div class="mb-3"> + <a class="btn btn-sm btn-outline-primary visually-hidden ical-link agenda-link filterable" href="{% webcal_url 'ietf.meeting.views.agenda_ical' num=meeting.number %}"> - Subscribe with webcal + Subscribe to personal agenda </a> -</div> + + <a class="visually-hidden btn btn-sm btn-outline-primary ical-link agenda-link filterable" href="{% url "ietf.meeting.views.agenda_ical" num=meeting.number %}">Download .ics of personal agenda</a> + + <a class="btn btn-sm btn-outline-primary visually-hidden ical-link agenda-link filterable" + href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}"> + View personal agenda + </a> +</div> \ No newline at end of file diff --git a/ietf/templates/meeting/meeting_heading.html b/ietf/templates/meeting/meeting_heading.html index 54603b7e9..c53a2f91b 100644 --- a/ietf/templates/meeting/meeting_heading.html +++ b/ietf/templates/meeting/meeting_heading.html @@ -13,6 +13,9 @@ <h1> IETF {{ meeting.number }} Meeting Agenda {{ title_extra }} + {% if personalize %} + Personalization + {% endif %} </h1> <h4> {{ meeting.city|default:"Location TBD" }}, {{ meeting.date|date:"F j" }} - @@ -42,6 +45,12 @@ UTC Agenda </a> </li> + <li class="nav-item"> + <a class="nav-link agenda-link filterable {% if selected == "select-sessions" %}active{% endif %}" href="{% url 'ietf.meeting.views.agenda_personalize' num=meeting.number %}" + > + Personalize Agenda + </a> + </li> {% if user|has_role:"Secretariat,Area Director,IAB" %} {% if schedule != meeting.schedule %} <li class="nav-item"> @@ -72,11 +81,5 @@ Plaintext </a> </li> - <li class="nav-item"> - <a class="nav-link agenda-link filterable {% if selected == "select-sessions" %}active{% endif %}" href="{% url 'ietf.meeting.views.agenda_personalize' num=meeting.number %}" - > - Select Sessions - </a> - </li> </ul> </p> \ No newline at end of file diff --git a/package.json b/package.json index 250cd4249..23c3d902e 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "ietf/static/js/agenda_filter.js", "ietf/static/js/agenda_materials.js", "ietf/static/js/agenda_timezone.js", + "ietf/static/js/agenda_personalize.js", "ietf/static/js/timezone.js", "ietf/static/js/room_params.js", "ietf/static/js/week-view.js",