Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into MPMSPII-4_mail_noti…
Browse files Browse the repository at this point in the history
…fications
  • Loading branch information
gbastien committed Aug 27, 2021
2 parents 8b89932 + 7e28a24 commit ef5167b
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 29 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ Changelog
- Fixed display of `Application parameters` fieldset when adding a new organization
in an overlay when on `Own organization`, CSS was hidding it wrongly.
[gbastien]
- When going back to meeting from item, go to the correct faceted page and
scroll to item position. Same scrolling mechanism is now used when an item is
decided on a meeting, instead just refreshing the faceted, the faceted is
refreshed and the screen scrolls to the modified item.
[gbastien]
- Added 3 new types of events related to items that will trigger a mail being sent:

- Item state changed, history aware : Notify by mail one specific user (if possible)
Expand Down
3 changes: 2 additions & 1 deletion src/Products/PloneMeeting/MeetingItem.py
Original file line number Diff line number Diff line change
Expand Up @@ -2167,7 +2167,8 @@ def validate_optionalAdvisers(self, values):
# when removing an advice asked to a userid
return translate(
'can_not_unselect_already_given_advice',
mapping={'removedAdviser':
mapping={
'removedAdviser':
vocab.getTermByToken(orig_removedAdviser).sortable_title},
domain='PloneMeeting',
context=self.REQUEST)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
lastItemNumberDisplay python: view.display_number(lastItemNumber);">

<tal:comment replace="nothing">Go to the meeting</tal:comment>
<a href="#" tal:attributes="href string:${meeting/absolute_url}">
<a href="#"
tal:attributes="href string:${context_url}/@@object_goto?itemNumber=0&way=meeting;
onclick string:localStorage.setItem('dashboardRowId', 'row_${context/UID}');">
<img title="pm_goto_meeting" i18n:attributes="title" style="cursor:pointer"
tal:attributes="src string:${view/portal_url}/gotoMeeting.png;"/>
</a>
Expand Down
43 changes: 33 additions & 10 deletions src/Products/PloneMeeting/browser/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,18 +302,41 @@ def __call__(self, itemNumber, way='previous'):
p_itemNumber is the number of the item we want to go to. This item
is in the same meeting than self.context.
"""
catalog = api.portal.get_tool('portal_catalog')
meeting = self.context.getMeeting()
itemNumber = _itemNumber_to_storedItemNumber(itemNumber)
brains = catalog(meeting_uid=meeting.UID(), getItemNumber=itemNumber)
if not brains:
self.context.plone_utils.addPortalMessage(
translate(msgid='item_number_not_accessible',
domain='PloneMeeting',
context=self.request),
type='warning')
return self.request.RESPONSE.redirect(self.context.absolute_url())
# got to meeting view on relevant item?
if way == 'meeting':
item_uids = [brain.UID for brain
in meeting.get_items(ordered=True, the_objects=False)]
# find on which page item will be displayed
# when displayed by 20, item number 10 is on page 1
# item number 20 is on page 1, item number 22 is on page 2
# but page 1 b_start is 0...
tool = api.portal.get_tool('portal_plonemeeting')
cfg = tool.getMeetingConfig(self.context)
context_uid = self.context.UID()
# use index position so element 20 index is 19 and is < 20
item_pos = tuple(item_uids).index(context_uid)
items_by_page = cfg.getMaxShownMeetingItems()
page_num = float(item_pos) / items_by_page
# round 0.85 to 0 or 1.05 to 1
int_page_num = int(page_num)
url = "{0}#b_start={1}".format(
meeting.absolute_url(), int_page_num*items_by_page)
return self.request.RESPONSE.redirect(url)

# navigate thru items
else:
catalog = api.portal.get_tool('portal_catalog')
itemNumber = _itemNumber_to_storedItemNumber(itemNumber)
brains = catalog(meeting_uid=meeting.UID(), getItemNumber=itemNumber)
if not brains:
self.context.plone_utils.addPortalMessage(
translate(msgid='item_number_not_accessible',
domain='PloneMeeting',
context=self.request),
type='warning')
return self.request.RESPONSE.redirect(self.context.absolute_url())

obj = brains[0].getObject()
# check if obj isPrivacyViewable, if not, find the previous/next viewable item
next_obj = None
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
112 changes: 97 additions & 15 deletions src/Products/PloneMeeting/skins/plonemeeting_styles/plonemeeting.js
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,8 @@ function moveItem(baseUrl, moveType, tag) {
success: function(data) {
// reload the faceted page
Faceted.URLHandler.hash_changed();
// set dashboardRowId if on meeting view
// start_meeting_scroll_to_item_observer(tag);
},
error: function(jqXHR, textStatus, errorThrown) {
/*console.log(textStatus);*/
Expand All @@ -566,20 +568,34 @@ function moveItem(baseUrl, moveType, tag) {
});
}

function isMeeting() {
if ($("body.template-meeting_view").length) {
return true;
}
return false;
}

// event subscriber when a transition is triggered
$(document).on('ap_transition_triggered', synchronizeMeetingFaceteds);

// synchronize faceted displayed on the meeting_view, available items and presented items
function synchronizeMeetingFaceteds(infos) {
// refresh iframe 'available items' while removing an item
if ((infos.transition === 'backToValidated') && ((window.frames[0]) && (window.frames[0] != window))) {
window.frames[0].Faceted.URLHandler.hash_changed();
updateNumberOfItems();
}
// refresh main frame while presenting an item
if ((infos.transition === 'present') && (window != parent)) {
parent.Faceted.URLHandler.hash_changed();
updateNumberOfItems();

if (isMeeting) {

// refresh iframe 'available items' while removing an item
if ((infos.transition === 'backToValidated') &&
((window.frames[0]) && (window.frames[0] != window))) {
window.frames[0].Faceted.URLHandler.hash_changed();
updateNumberOfItems();
} else if ((infos.transition === 'present') && (window != parent)) {
// refresh main frame while presenting an item
parent.Faceted.URLHandler.hash_changed();
updateNumberOfItems();
} else {
// set dashboardRowId if on meeting view
start_meeting_scroll_to_item_observer(infos.tag);
}
}
}

Expand Down Expand Up @@ -733,30 +749,31 @@ $(document).ready(function () {
}
$(Faceted.Events).bind(Faceted.Events.AJAX_QUERY_SUCCESS, function() {
updatePortletTodo();

});
});

/* Disable caching of AJAX responses when using IE,
otherwise, the double ajax call on the meeting (available and presented items)
will display the same result... */
if (/msie/.test(navigator.userAgent.toLowerCase())) {
$.ajaxSetup ({
cache: false });
$.ajaxSetup ({
cache: false });
}

/* make sure not_selectable inputs in MeetingItem.optionalAdvisers are not selectable ! */
$(document).ready(function () {
$("input[value^='not_selectable_value_'").each(function() {
this.disabled = true;
});
});
});

/* make sure first line of MeetingConfig.itemWFValidationLevels can not be edited */
$(document).ready(function () {
$("input[id$='_itemWFValidationLevels_1'").each(function() {
this.readOnly = true;
});

});

function update_search_term(tag){
Expand Down Expand Up @@ -791,6 +808,7 @@ $(document).ready(function () {
function initializeItemsDND(){
$('table.faceted-table-results').tableDnD({
onDrop: function(table, row) {
row_id = row.id;
row_index = row.rowIndex;
// id is like row_200
row_item_number = parseInt(table.rows[row.rowIndex].cells[2].dataset.item_number);
Expand All @@ -803,7 +821,7 @@ $('table.faceted-table-results').tableDnD({
move_type = 'down';
}
} else {move_type = 'down';}

// now that we know the move, we can determinate number to use
if (move_type == 'down') {
new_value = parseInt(table.rows[row.rowIndex - 1].cells[2].dataset.item_number);
Expand All @@ -819,6 +837,7 @@ $('table.faceted-table-results').tableDnD({
cache: false,
success: function(data) {
Faceted.URLHandler.hash_changed();
start_meeting_scroll_to_item_observer(tag=null, row_id=row_id);
},
error: function(jqXHR, textStatus, errorThrown) {
/*console.log(textStatus);*/
Expand Down Expand Up @@ -878,7 +897,7 @@ function initReadmore() {

var $el, $up;

/* first check if need to use readmorable or not, only if content > set CSS max-height + 50px */
/* first check if need to use readmorable or not, only if content > set CSS max-height + 100px */
$("div.readmorable").each(function() {
$el = $(this);
/* get max-height defined in CSS for div.readmorable {} */
Expand Down Expand Up @@ -918,3 +937,66 @@ $("div.readmorable p.readless").click(function() {
return false;
});
}

// utility method that will scroll to a position with an offset
function _scrollTo(el, yOffset = 0){
const y = el.getBoundingClientRect().top + window.pageYOffset + yOffset;
window.scrollTo({top: y, behavior: 'smooth'});
}

// function that scroll to a row in a dashboard
function scrollToRow(row) {
// goto row
header_height = $("#portal-header").height();
_scrollTo(row, - 200 -header_height);
// highlight row
tds = $('td', row);
tds.each(function(){
$(this).effect('highlight', {}, 5000);
}
);
}

// mutation observer necessary to scroll to a position in a dashboard
// because when document is loaded, the faceted results are loaded by an ajax query
// and scrollToRow need the element to be visible
const observer = new MutationObserver((mutations, obs) => {
var row_id = null;
if (localStorage.getItem("dashboardRowId", null)) {
row_id = localStorage.getItem("dashboardRowId", null);
}
if (row_id) {
row_id_tag = document.getElementById(row_id);
if (row_id_tag) {
scrollToRow(row_id_tag);
obs.disconnect();
localStorage.removeItem("dashboardRowId");
return;
}
}
else {
obs.disconnect();
return;
}

});

// start meeting observer when on meeting view
// it is used when needed to scroll to an item position when faceted is refreshed
function start_meeting_scroll_to_item_observer(tag=null, row_id=null) {
if (isMeeting()) {
if (tag) {
localStorage.setItem("dashboardRowId", $(tag).parents("tr[id^='row_']")[0].id);
} else if (row_id) {
localStorage.setItem("dashboardRowId", row_id);
}
observer.observe(document, {
childList: true,
subtree: true
});
}
}

$(document).ready(function () {
start_meeting_scroll_to_item_observer();
});
5 changes: 3 additions & 2 deletions src/Products/PloneMeeting/tests/testMeetingItem.py
Original file line number Diff line number Diff line change
Expand Up @@ -6542,8 +6542,9 @@ def test_pm_GetLinkCachingInvalidatedWhenOnAvailableItemsOfMeetingView(self):
self.assertFalse(item.wfConditions().isLateFor(meeting))
late_icon_html = u"<img title='Late' src='http://nohost/plone/late.png' " \
"style=\"width: 16px; height: 16px;\" />"
val_deadline_icon_html = u"<img title=\'icon_help_validation_deadline_ko\' " \
"src=\'http://nohost/plone/deadlineKo.png\' style=\"width: 16px; height: 16px;\" />"
val_deadline_icon_html = u"<img title=\'This item was validated after " \
u"the validation deadline defined on this meeting\' " \
u"src=\'http://nohost/plone/deadlineKo.png\' style=\"width: 16px; height: 16px;\" />"
self.assertFalse(late_icon_html in IPrettyLink(item).getLink())
self.assertFalse(val_deadline_icon_html in IPrettyLink(item).getLink())
# right now change current URL so displaying_available_items is True
Expand Down
37 changes: 37 additions & 0 deletions src/Products/PloneMeeting/tests/testViews.py
Original file line number Diff line number Diff line change
Expand Up @@ -2128,6 +2128,43 @@ def test_pm_goto_object(self):
self.assertEqual(view('1', 'first'), secretItem1.absolute_url())
self.assertEqual(view('5', 'last'), secretItem3.absolute_url())

def test_pm_goto_object_meeting(self):
"""Test the item navigation widget when way='meeting'."""
cfg = self.meetingConfig
cfg.setMaxShownMeetingItems(2)
self._removeConfigObjectsFor(cfg)
cfg.setInsertingMethodsOnAddItem(({'insertingMethod': 'at_the_end',
'reverse': '0'},))
# create a meeting with 3 items and display
# items presented on meeting by batch of 2
self.changeUser('pmManager')
item1 = self.create('MeetingItem')
item2 = self.create('MeetingItem')
item3 = self.create('MeetingItem')
meeting = self.create('Meeting')
meeting_url = meeting.absolute_url()
self.presentItem(item1)
self.presentItem(item2)
self.presentItem(item3)
# from item1, page 1 of meeting
self.assertEqual(item1.getItemNumber(), 100)
view = item1.restrictedTraverse('@@object_goto')
view(itemNumber=None, way='meeting')
self.assertEqual(self.request.response.getHeader('location'),
"{0}#b_start=0".format(meeting_url))
# from item2, page 1 of meeting
self.assertEqual(item2.getItemNumber(), 200)
view = item2.restrictedTraverse('@@object_goto')
view(itemNumber=None, way='meeting')
self.assertEqual(self.request.response.getHeader('location'),
"{0}#b_start=0".format(meeting_url))
# from item3, page 2 of meeting
self.assertEqual(item3.getItemNumber(), 300)
view = item3.restrictedTraverse('@@object_goto')
view(itemNumber=None, way='meeting')
self.assertEqual(self.request.response.getHeader('location'),
"{0}#b_start=2".format(meeting_url))

def test_pm_ETags(self):
"""Test that correct ETags are used for :
- dashboard (Folder);
Expand Down

0 comments on commit ef5167b

Please sign in to comment.