diff --git a/client/resources/css/darkmode.css b/client/resources/css/darkmode.css index d9fa61800..d5f7e8b4a 100644 --- a/client/resources/css/darkmode.css +++ b/client/resources/css/darkmode.css @@ -1405,6 +1405,14 @@ body.dark-mode .x-form-textarea.stickynote_dialog_pink { background-color: #6843 body.dark-mode .x-form-textarea.stickynote_dialog_yellow { background-color: #4e4d00 !important; color: var(--dm-fg) !important; } body.dark-mode .x-form-textarea.stickynote_dialog_white { background-color: #5c5c5c !important; color: var(--dm-fg) !important; } +/* Notes linked to the previewed mail, same palette as the note dialog above */ +body.dark-mode .preview-header-linkednotes .preview-header-linkednote { color: var(--dm-fg) !important; } +body.dark-mode .preview-header-linkednotes .preview-header-linkednote.note_blue { background-color: #00233b !important; } +body.dark-mode .preview-header-linkednotes .preview-header-linkednote.note_green { background-color: #113a00 !important; } +body.dark-mode .preview-header-linkednotes .preview-header-linkednote.note_pink { background-color: #684363 !important; } +body.dark-mode .preview-header-linkednotes .preview-header-linkednote.note_yellow { background-color: #4e4d00 !important; } +body.dark-mode .preview-header-linkednotes .preview-header-linkednote.note_white { background-color: #5c5c5c !important; border-color: #6e6e6e !important; } + /*============================================================================= Settings =============================================================================*/ diff --git a/client/resources/css/grommunio.css b/client/resources/css/grommunio.css index fafaed101..59ea97102 100644 --- a/client/resources/css/grommunio.css +++ b/client/resources/css/grommunio.css @@ -2238,11 +2238,46 @@ input.x-form-text.k-searchfolder-field.x-form-focus { display: block; padding-bottom: 6px; } -.preview-header-recipients, .preview-header-meeting, .preview-header-attachments { +.preview-header-recipients, .preview-header-meeting, .preview-header-attachments, .preview-header-linkednotes { display: block; margin-top: 6px; margin-left: 10px; } +/* Notes linked to the previewed mail */ +.preview-header-linkednotes .preview-header-linkednotes-list { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: 6px; } + +.preview-header-linkednotes .preview-header-linkednote { + display: block; + max-width: 260px; + /* Long notes stay readable: the box grows to about ten lines and scrolls beyond + that, rather than cutting the text off with no way to reach the rest. */ + max-height: 12em; + padding: 4px 8px; + border-radius: 2px; + cursor: pointer; + overflow-x: hidden; + overflow-y: auto; } + +.preview-header-linkednotes .preview-header-linkednote.note_yellow { + background: #FF9; } + +.preview-header-linkednotes .preview-header-linkednote.note_blue { + background: #DEF1FF; } + +.preview-header-linkednotes .preview-header-linkednote.note_green { + background: #CBF0BB; } + +.preview-header-linkednotes .preview-header-linkednote.note_pink { + background: #FFDBFB; } + +.preview-header-linkednotes .preview-header-linkednote.note_white { + background: #FFF; + border: 1px solid #DDD; } + .preview-header-recipients .preview-recipient-title, .preview-header-attachments .preview-attachment-title { float: left; } @@ -4305,6 +4340,23 @@ div.zarafa-contact-cardview-selected { border-color: #d5e8f7; } /* New note background color */ +/* Names the mail a note is attached to, at the top of the note dialog */ +.stickynote_linkedmail { + padding: 4px 6px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } + +.stickynote_linkedmail .stickynote_linkedmail_link { + cursor: pointer; + text-decoration: underline; } + +/* Notes made before the mail entryid was recorded can be named but not opened */ +.stickynote_linkedmail.stickynote_linkedmail_unopenable .stickynote_linkedmail_link { + cursor: default; + text-decoration: none; + opacity: 0.7; } + .x-form-textarea.stickynote_dialog_yellow { background: #FF9 none repeat scroll 0 0; } diff --git a/client/resources/css/icon-masks.css b/client/resources/css/icon-masks.css index 9e514c90a..ea5233967 100644 --- a/client/resources/css/icon-masks.css +++ b/client/resources/css/icon-masks.css @@ -1777,8 +1777,8 @@ td.sort-asc .x-grid3-sort-icon { background-image: none !important; } .icon_new_note { - -webkit-mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJNMTIgNi41YTUuNSA1LjUgMCAxIDAtMTEgMCA1LjUgNS41IDAgMCAwIDExIDBNNyA3bC4wMDEgMi41MDRhLjUuNSAwIDAgMS0xIDBWN0gzLjQ5NmEuNS41IDAgMCAxIDAtMUg2VjMuNWEuNS41IDAgMCAxIDEgMFY2aDIuNTAzYS41LjUgMCAwIDEgMCAxSDcuMDAxWiIvPjxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0iTTE3Ljc1IDQuNWgtNS4wNjNhNi41IDYuNSAwIDAgMC0uNzA5LTEuNWg1Ljc3MkEzLjI1IDMuMjUgMCAwIDEgMjEgNi4yNXY2Ljg3OWEyLjI1IDIuMjUgMCAwIDEtLjY1OSAxLjU5bC01LjYyMSA1LjYyMmEyLjI1IDIuMjUgMCAwIDEtMS41OTEuNjU5SDYuMjVBMy4yNSAzLjI1IDAgMCAxIDMgMTcuNzV2LTUuNzcyYy40NjMuMjk3Ljk2Ny41MzYgMS41LjcwOXY1LjA2M2MwIC45NjYuNzg0IDEuNzUgMS43NSAxLjc1SDEzdi0zLjI1YTMuMjUgMy4yNSAwIDAgMSAzLjA2Ni0zLjI0NUwxNi4yNSAxM2gzLjI1VjYuMjVhMS43NSAxLjc1IDAgMCAwLTEuNzUtMS43NW0uNjg5IDEwSDE2LjI1YTEuNzUgMS43NSAwIDAgMC0xLjc0NCAxLjYwN2wtLjAwNi4xNDN2Mi4xODl6Ii8+PC9zdmc+); - mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJNMTIgNi41YTUuNSA1LjUgMCAxIDAtMTEgMCA1LjUgNS41IDAgMCAwIDExIDBNNyA3bC4wMDEgMi41MDRhLjUuNSAwIDAgMS0xIDBWN0gzLjQ5NmEuNS41IDAgMCAxIDAtMUg2VjMuNWEuNS41IDAgMCAxIDEgMFY2aDIuNTAzYS41LjUgMCAwIDEgMCAxSDcuMDAxWiIvPjxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0iTTE3Ljc1IDQuNWgtNS4wNjNhNi41IDYuNSAwIDAgMC0uNzA5LTEuNWg1Ljc3MkEzLjI1IDMuMjUgMCAwIDEgMjEgNi4yNXY2Ljg3OWEyLjI1IDIuMjUgMCAwIDEtLjY1OSAxLjU5bC01LjYyMSA1LjYyMmEyLjI1IDIuMjUgMCAwIDEtMS41OTEuNjU5SDYuMjVBMy4yNSAzLjI1IDAgMCAxIDMgMTcuNzV2LTUuNzcyYy40NjMuMjk3Ljk2Ny41MzYgMS41LjcwOXY1LjA2M2MwIC45NjYuNzg0IDEuNzUgMS43NSAxLjc1SDEzdi0zLjI1YTMuMjUgMy4yNSAwIDAgMSAzLjA2Ni0zLjI0NUwxNi4yNSAxM2gzLjI1VjYuMjVhMS43NSAxLjc1IDAgMCAwLTEuNzUtMS43NW0uNjg5IDEwSDE2LjI1YTEuNzUgMS43NSAwIDAgMC0xLjc0NCAxLjYwN2wtLjAwNi4xNDN2Mi4xODl6Ii8+PC9zdmc+); + -webkit-mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+PG1hc2sgaWQ9ImEiPjxyZWN0IHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0iI2ZmZiIvPjxjaXJjbGUgY3g9IjE3LjUiIGN5PSIxNy41IiByPSI2LjUiIGZpbGw9IiMwMDAiLz48L21hc2s+PGcgbWFzaz0idXJsKCNhKSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLjE1IDIwLjU1KSBzY2FsZSguODUgLS44NSkiPjxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0iTTE3Ljc1IDNINi4yNUEzLjI1IDMuMjUgMCAwIDAgMyA2LjI1djExLjVBMy4yNSAzLjI1IDAgMCAwIDYuMjUgMjFoNi44NzlhMi4yNSAyLjI1IDAgMCAwIDEuNTkxLS42NTlsNS42MjEtNS42MjJBMi4yNSAyLjI1IDAgMCAwIDIxIDEzLjEyOVY2LjI1QTMuMjUgMy4yNSAwIDAgMCAxNy43NSAzbTAgMS41Yy45NjYgMCAxLjc1Ljc4NCAxLjc1IDEuNzVWMTNoLTMuMjVhMy4yNSAzLjI1IDAgMCAwLTMuMjUgMy4yNXYzLjI1SDYuMjVhMS43NSAxLjc1IDAgMCAxLTEuNzUtMS43NVY2LjI1YzAtLjk2Ni43ODQtMS43NSAxLjc1LTEuNzV6bTEuNzUgOC41aC0zLjI1YTEuNzUgMS43NSAwIDAgMC0xLjc1IDEuNzV2My4yNXoiLz48L2c+PC9nPjxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0iTTIzIDE3LjVhNS41IDUuNSAwIDEgMC0xMSAwIDUuNSA1LjUgMCAwIDAgMTEgMG0tNSAuNS4wMDEgMi41MDNhLjUuNSAwIDEgMS0xIDBWMThoLTIuNTA1YS41LjUgMCAwIDEgMC0xSDE3di0yLjVhLjUuNSAwIDEgMSAxIDBWMTdoMi40OTdhLjUuNSAwIDAgMSAwIDF6Ii8+PC9zdmc+); + mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSI+PG1hc2sgaWQ9ImEiPjxyZWN0IHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0iI2ZmZiIvPjxjaXJjbGUgY3g9IjE3LjUiIGN5PSIxNy41IiByPSI2LjUiIGZpbGw9IiMwMDAiLz48L21hc2s+PGcgbWFzaz0idXJsKCNhKSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLjE1IDIwLjU1KSBzY2FsZSguODUgLS44NSkiPjxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0iTTE3Ljc1IDNINi4yNUEzLjI1IDMuMjUgMCAwIDAgMyA2LjI1djExLjVBMy4yNSAzLjI1IDAgMCAwIDYuMjUgMjFoNi44NzlhMi4yNSAyLjI1IDAgMCAwIDEuNTkxLS42NTlsNS42MjEtNS42MjJBMi4yNSAyLjI1IDAgMCAwIDIxIDEzLjEyOVY2LjI1QTMuMjUgMy4yNSAwIDAgMCAxNy43NSAzbTAgMS41Yy45NjYgMCAxLjc1Ljc4NCAxLjc1IDEuNzVWMTNoLTMuMjVhMy4yNSAzLjI1IDAgMCAwLTMuMjUgMy4yNXYzLjI1SDYuMjVhMS43NSAxLjc1IDAgMCAxLTEuNzUtMS43NVY2LjI1YzAtLjk2Ni43ODQtMS43NSAxLjc1LTEuNzV6bTEuNzUgOC41aC0zLjI1YTEuNzUgMS43NSAwIDAgMC0xLjc1IDEuNzV2My4yNXoiLz48L2c+PC9nPjxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0iTTIzIDE3LjVhNS41IDUuNSAwIDEgMC0xMSAwIDUuNSA1LjUgMCAwIDAgMTEgMG0tNSAuNS4wMDEgMi41MDNhLjUuNSAwIDEgMS0xIDBWMThoLTIuNTA1YS41LjUgMCAwIDEgMC0xSDE3di0yLjVhLjUuNSAwIDEgMSAxIDBWMTdoMi40OTdhLjUuNSAwIDAgMSAwIDF6Ii8+PC9zdmc+); -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-position: center center; diff --git a/client/resources/iconsets/breeze/breeze-icons.css b/client/resources/iconsets/breeze/breeze-icons.css index 8939a815c..ae874d163 100644 --- a/client/resources/iconsets/breeze/breeze-icons.css +++ b/client/resources/iconsets/breeze/breeze-icons.css @@ -136,7 +136,7 @@ .icon_new_distlist {background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PHBhdGggZmlsbD0nIzAwMCcgZD0nTTE3LjUgMTJhNS41IDUuNSAwIDEgMSAwIDExIDUuNSA1LjUgMCAwIDEgMC0xMU00IDEyLjk5OWw4LjgxLjAwMWE2LjUgNi41IDAgMCAwLTEuMDc4IDEuNUw0IDE0LjQ5OWwtLjEuMDFhLjUuNSAwIDAgMC0uMjU0LjEzNi41LjUgMCAwIDAtLjEzNi4yNTNsLS4wMS4xMDFWMTYuNWMwIDEuMDA5LjQ1IDEuNzIyIDEuNDE3IDIuMjQyLjgyNi40NDUgMi4wMDMuNzE0IDMuMjY2Ljc1M2wuMzE3LjAwNS4zMTctLjAwNWMuODY0LS4wMjYgMS42ODctLjE2IDIuMzg1LS4zODUuMTI0LjQ5Mi4zMDYuOTYzLjUzNyAxLjQwMy0xLjA1LjM1LTIuMjA4LjQ4Ny0zLjIzOS40ODctMi43MjIgMC02LjMzNS0uOTU2LTYuNDk1LTQuMjdMMiAxNi41di0xLjUwMWEyIDIgMCAwIDEgMi0ybTEzLjUgMS4wMDMtLjA5LjAwOGEuNS41IDAgMCAwLS40MDIuNDAybC0uMDA4LjA5VjE3aC0yLjVsLS4wOS4wMDhhLjUuNSAwIDAgMC0uNDAyLjQwMkwxNCAxNy41bC4wMDguMDlhLjUuNSAwIDAgMCAuNDAyLjQwMmwuMDkuMDA4SDE3djIuNWwuMDA4LjA5YS41LjUgMCAwIDAgLjQwMi40MDJsLjA5LjAwOC4wOS0uMDA4YS41LjUgMCAwIDAgLjQwMi0uNDAyTDE4IDIwLjVWMThoMi41bC4wOS0uMDA4YS41LjUgMCAwIDAgLjQwMi0uNDAyTDIxIDE3LjVsLS4wMDgtLjA5YS41LjUgMCAwIDAtLjQwMi0uNDAyTDIwLjUgMTdIMTh2LTIuNDk4bC0uMDA4LS4wOWEuNS41IDAgMCAwLS40MDItLjQwMnpNOC41IDJhNC41IDQuNSAwIDEgMSAwIDkgNC41IDQuNSAwIDAgMSAwLTltOSAyYTMuNSAzLjUgMCAxIDEgMCA3IDMuNSAzLjUgMCAwIDEgMC03bS05LS41Yy0xLjY1NCAwLTMgMS4zNDYtMyAzczEuMzQ2IDMgMyAzIDMtMS4zNDYgMy0zLTEuMzQ2LTMtMy0zbTkgMmMtMS4xMDMgMC0yIC44OTctMiAycy44OTcgMiAyIDIgMi0uODk3IDItMi0uODk3LTItMi0yJy8+PC9zdmc+) !important; background-repeat: no-repeat !important; background-position: center center !important;} .icon_new_email {background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PHBhdGggZmlsbD0nIzAwMCcgZD0nTTIzIDYuNWE1LjUgNS41IDAgMSAwLTExIDAgNS41IDUuNSAwIDAgMCAxMSAwTTE4IDdsLjAwMSAyLjUwNGEuNS41IDAgMSAxLTEgMFY3aC0yLjUwNWEuNS41IDAgMCAxIDAtMUgxN1YzLjVhLjUuNSAwIDAgMSAxIDBWNmgyLjQ5N2EuNS41IDAgMCAxIDAgMXptMi41IDkuNzV2LTQuNDgyQTYuNSA2LjUgMCAwIDAgMjIgMTEuMTl2NS41NmEzLjI1IDMuMjUgMCAwIDEtMy4wNjYgMy4yNDVMMTguNzUgMjBINS4yNWEzLjI1IDMuMjUgMCAwIDEtMy4yNDUtMy4wNjZMMiAxNi43NXYtOS41YTMuMjUgMy4yNSAwIDAgMSAzLjA2Ni0zLjI0NUw1LjI1IDRoNi4yNDhhNi41IDYuNSAwIDAgMC0uNDIyIDEuNUg1LjI1YTEuNzUgMS43NSAwIDAgMC0xLjc0NCAxLjYwNkwzLjUgNy4yNXYuNDI5bDguNSA0LjQ3MyAxLjMwNS0uNjg3Yy40MzkuMzcxLjkyNy42ODUgMS40NTQuOTNsLTIuNDEgMS4yNjlhLjc1Ljc1IDAgMCAxLS42MDIuMDQybC0uMDk2LS4wNDJMMy41IDkuMzc0djcuMzc2YTEuNzUgMS43NSAwIDAgMCAxLjYwNiAxLjc0NGwuMTQ0LjAwNmgxMy41YTEuNzUgMS43NSAwIDAgMCAxLjc0NC0xLjYwN3onLz48L3N2Zz4=) !important; background-repeat: no-repeat !important; background-position: center center !important;} .icon_new_meeting_request {background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PHBhdGggZmlsbD0nIzAwMCcgZD0nTTE3LjUgMTJhNS41IDUuNSAwIDEgMSAwIDExIDUuNSA1LjUgMCAwIDEgMC0xMW0wIDItLjA5LjAwN2EuNS41IDAgMCAwLS40MDIuNDAyTDE3IDE0LjVWMTdoLTIuNTAybC0uMDkuMDA4YS41LjUgMCAwIDAtLjQwMi40MDJsLS4wMDguMDkuMDA4LjA5YS41LjUgMCAwIDAgLjQwMi40MDJsLjA5LjAwOEgxN3YyLjUwM2wuMDA4LjA5YS41LjUgMCAwIDAgLjQwMi40MDJsLjA5LjAwOC4wOS0uMDA4YS41LjUgMCAwIDAgLjQwMi0uNDAybC4wMDgtLjA5VjE4bDIuNTA0LjAwMS4wOS0uMDA4YS41LjUgMCAwIDAgLjQwMi0uNDAybC4wMDgtLjA5LS4wMDgtLjA5YS41LjUgMCAwIDAtLjQwMy0uNDAybC0uMDktLjAwOEgxOHYtMi41bC0uMDA4LS4wOWEuNS41IDAgMCAwLS40MDItLjQwM3ptLTMuMjQ2LTRjLjgzNSAwIDEuNTYzLjQ1NCAxLjk1MSAxLjEzYTYuNCA2LjQgMCAwIDAtMS41MTguNTA5Ljc0Ljc0IDAgMCAwLS40MzMtLjEzOUg5Ljc1MmEuNzUuNzUgMCAwIDAtLjc1Ljc1djQuMjQ5YzAgMS40MS45NzQgMi41OTQgMi4yODYgMi45MTVhNi40IDYuNCAwIDAgMCAuNzM1IDEuNTg3bC0uMDItLjAwMWE0LjUgNC41IDAgMCAxLTQuNTAxLTQuNTAxVjEyLjI1QTIuMjUgMi4yNSAwIDAgMSA5Ljc1MiAxMHptLTYuODQ4IDBhMy4yNCAzLjI0IDAgMCAwLS44MTcgMS41SDQuMjVhLjc1Ljc1IDAgMCAwLS43NS43NXYyLjc0OWEyLjUgMi41IDAgMCAwIDMuMDgyIDIuNDMzYy4wODUuNTA0LjI0Ljk4NS40NTMgMS40MzJBNCA0IDAgMCAxIDIgMTQuOTk5VjEyLjI1YTIuMjUgMi4yNSAwIDAgMSAyLjA5Ni0yLjI0NUw0LjI1IDEwem0xMi4zNDQgMEEyLjI1IDIuMjUgMCAwIDEgMjIgMTIuMjV2LjU2QTYuNDggNi40OCAwIDAgMCAxNy41IDExbC0uMjQ1LjAwNUEzLjIgMy4yIDAgMCAwIDE2LjYgMTB6TTE4LjUgNGEyLjUgMi41IDAgMSAxIDAgNSAyLjUgMi41IDAgMCAxIDAtNU0xMiAzYTMgMyAwIDEgMSAwIDYgMyAzIDAgMCAxIDAtNk01LjUgNGEyLjUgMi41IDAgMSAxIDAgNSAyLjUgMi41IDAgMCAxIDAtNW0xMyAxLjVhMSAxIDAgMSAwIDAgMiAxIDEgMCAwIDAgMC0ybS02LjUtMWExLjUgMS41IDAgMSAwIDAgMyAxLjUgMS41IDAgMCAwIDAtM20tNi41IDFhMSAxIDAgMSAwIDAgMiAxIDEgMCAwIDAgMC0yJy8+PC9zdmc+) !important; background-repeat: no-repeat !important; background-position: center center !important;} -.icon_new_note {background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PHBhdGggZmlsbD0nIzAwMCcgZD0nTTEyIDYuNWE1LjUgNS41IDAgMSAwLTExIDAgNS41IDUuNSAwIDAgMCAxMSAwTTcgN2wuMDAxIDIuNTA0YS41LjUgMCAwIDEtMSAwVjdIMy40OTZhLjUuNSAwIDAgMSAwLTFINlYzLjVhLjUuNSAwIDAgMSAxIDBWNmgyLjUwM2EuNS41IDAgMCAxIDAgMUg3LjAwMVonLz48cGF0aCBmaWxsPScjMDAwJyBkPSdNMTcuNzUgNC41aC01LjA2M2E2LjUgNi41IDAgMCAwLS43MDktMS41aDUuNzcyQTMuMjUgMy4yNSAwIDAgMSAyMSA2LjI1djYuODc5YTIuMjUgMi4yNSAwIDAgMS0uNjU5IDEuNTlsLTUuNjIxIDUuNjIyYTIuMjUgMi4yNSAwIDAgMS0xLjU5MS42NTlINi4yNUEzLjI1IDMuMjUgMCAwIDEgMyAxNy43NXYtNS43NzJjLjQ2My4yOTcuOTY3LjUzNiAxLjUuNzA5djUuMDYzYzAgLjk2Ni43ODQgMS43NSAxLjc1IDEuNzVIMTN2LTMuMjVhMy4yNSAzLjI1IDAgMCAxIDMuMDY2LTMuMjQ1TDE2LjI1IDEzaDMuMjVWNi4yNWExLjc1IDEuNzUgMCAwIDAtMS43NS0xLjc1bS42ODkgMTBIMTYuMjVhMS43NSAxLjc1IDAgMCAwLTEuNzQ0IDEuNjA3bC0uMDA2LjE0M3YyLjE4OXonLz48L3N2Zz4=) !important; background-repeat: no-repeat !important; background-position: center center !important;} +.icon_new_note {background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PG1hc2sgaWQ9J2EnPjxyZWN0IHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nI2ZmZicvPjxjaXJjbGUgY3g9JzE3LjUnIGN5PScxNy41JyByPSc2LjUnIGZpbGw9JyMwMDAnLz48L21hc2s+PGcgbWFzaz0ndXJsKCNhKSc+PGcgdHJhbnNmb3JtPSd0cmFuc2xhdGUoLjE1IDIwLjU1KSBzY2FsZSguODUgLS44NSknPjxwYXRoIGZpbGw9JyMwMDAnIGQ9J00xNy43NSAzSDYuMjVBMy4yNSAzLjI1IDAgMCAwIDMgNi4yNXYxMS41QTMuMjUgMy4yNSAwIDAgMCA2LjI1IDIxaDYuODc5YTIuMjUgMi4yNSAwIDAgMCAxLjU5MS0uNjU5bDUuNjIxLTUuNjIyQTIuMjUgMi4yNSAwIDAgMCAyMSAxMy4xMjlWNi4yNUEzLjI1IDMuMjUgMCAwIDAgMTcuNzUgM20wIDEuNWMuOTY2IDAgMS43NS43ODQgMS43NSAxLjc1VjEzaC0zLjI1YTMuMjUgMy4yNSAwIDAgMC0zLjI1IDMuMjV2My4yNUg2LjI1YTEuNzUgMS43NSAwIDAgMS0xLjc1LTEuNzVWNi4yNWMwLS45NjYuNzg0LTEuNzUgMS43NS0xLjc1em0xLjc1IDguNWgtMy4yNWExLjc1IDEuNzUgMCAwIDAtMS43NSAxLjc1djMuMjV6Jy8+PC9nPjwvZz48cGF0aCBmaWxsPScjMDAwJyBkPSdNMjMgMTcuNWE1LjUgNS41IDAgMSAwLTExIDAgNS41IDUuNSAwIDAgMCAxMSAwbS01IC41LjAwMSAyLjUwM2EuNS41IDAgMSAxLTEgMFYxOGgtMi41MDVhLjUuNSAwIDAgMSAwLTFIMTd2LTIuNWEuNS41IDAgMSAxIDEgMFYxN2gyLjQ5N2EuNS41IDAgMCAxIDAgMXonLz48L3N2Zz4=) !important; background-repeat: no-repeat !important; background-position: center center !important;} .icon_new_task {background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PHBhdGggZmlsbD0nIzAwMCcgZD0nTTEzLjk4NiA0YTIuMjUgMi4yNSAwIDAgMC0yLjIzNi0yaC0zLjVhMi4yNSAyLjI1IDAgMCAwLTIuMjM2IDJINC4yNUEyLjI1IDIuMjUgMCAwIDAgMiA2LjI1djEzLjVBMi4yNSAyLjI1IDAgMCAwIDQuMjUgMjJoOC41NmE2LjUgNi41IDAgMCAxLTEuMDc4LTEuNUg0LjI1YS43NS43NSAwIDAgMS0uNzUtLjc1VjYuMjVhLjc1Ljc1IDAgMCAxIC43NS0uNzVoMi4xMjljLjQwNC42MDMgMS4wOTEgMSAxLjg3MSAxaDMuNWMuNzggMCAxLjQ2Ny0uMzk3IDEuODcxLTFoMi4xMjlhLjc1Ljc1IDAgMCAxIC43NS43NXY0LjgyNmE2LjYgNi42IDAgMCAxIDEuNS0uMDU3VjYuMjVBMi4yNSAyLjI1IDAgMCAwIDE1Ljc1IDR6bS4wMDkuMDk2TDE0IDQuMjVxMC0uMDc4LS4wMDUtLjE1NE04LjI1IDMuNWgzLjVhLjc1Ljc1IDAgMCAxIDAgMS41aC0zLjVhLjc1Ljc1IDAgMCAxIDAtMS41bTMuNzA3IDEwLjYwNGE2LjUgNi41IDAgMCAxIDIuMTQ3LTIuMTQ3bC45MjYtLjkyN2EuNzUuNzUgMCAxIDAtMS4wNi0xLjA2TDkgMTQuOTRsLTEuOTctMS45N2EuNzUuNzUgMCAwIDAtMS4wNiAxLjA2bDIuNSAyLjVhLjc1Ljc1IDAgMCAwIDEuMDYgMHpNMjMgMTcuNWE1LjUgNS41IDAgMSAwLTExIDAgNS41IDUuNSAwIDAgMCAxMSAwbS01IC41LjAwMSAyLjUwM2EuNS41IDAgMSAxLTEgMFYxOGgtMi41MDVhLjUuNSAwIDAgMSAwLTFIMTd2LTIuNWEuNS41IDAgMSAxIDEgMFYxN2gyLjQ5N2EuNS41IDAgMCAxIDAgMXonLz48L3N2Zz4=) !important; background-repeat: no-repeat !important; background-position: center center !important;} .icon_not_current, .icon_no_response_required {background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PHBhdGggZmlsbD0nIzI5ODBCOScgZD0nTTEyIDEuOTk5YzUuNTI0IDAgMTAuMDAyIDQuNDc4IDEwLjAwMiAxMC4wMDIgMCA1LjUyMy00LjQ3OCAxMC4wMDEtMTAuMDAyIDEwLjAwMVMxLjk5OCAxNy41MjQgMS45OTggMTIuMDAxQzEuOTk4IDYuNDc3IDYuNDc2IDEuOTk5IDEyIDEuOTk5bS0uMDA0IDguMjVhMSAxIDAgMCAwLS45OTIuODg1bC0uMDA3LjExNi4wMDMgNS41MDIuMDA3LjExN2ExIDEgMCAwIDAgMS45ODctLjAwMkwxMyAxNi43NWwtLjAwMy01LjUwMS0uMDA3LS4xMTdhMSAxIDAgMCAwLS45OTQtLjg4MlpNMTIgNi41YTEuMjUxIDEuMjUxIDAgMSAwIDAgMi41MDNBMS4yNTEgMS4yNTEgMCAwIDAgMTIgNi41Jy8+PC9zdmc+) !important; background-repeat: no-repeat !important; background-position: center center !important;} .icon_note_blue {background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PHBhdGggZmlsbD0nIzI5ODBCOScgZD0nTTE3Ljc1IDNBMy4yNSAzLjI1IDAgMCAxIDIxIDYuMjVWMTNoLTQuNzVBMy4yNSAzLjI1IDAgMCAwIDEzIDE2LjI1VjIxSDYuMjVBMy4yNSAzLjI1IDAgMCAxIDMgMTcuNzVWNi4yNUEzLjI1IDMuMjUgMCAwIDEgNi4yNSAzem0yLjgxIDExLjUtNi4wNiA2LjA2di00LjMxYzAtLjk2Ni43ODQtMS43NSAxLjc1LTEuNzV6Jy8+PC9zdmc+) !important; background-repeat: no-repeat !important; background-position: center center !important;} @@ -776,8 +776,8 @@ width: 24px; height: 24px; background-color: currentColor; - -webkit-mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PHBhdGggZmlsbD0nIzAwMCcgZD0nTTEyIDYuNWE1LjUgNS41IDAgMSAwLTExIDAgNS41IDUuNSAwIDAgMCAxMSAwTTcgN2wuMDAxIDIuNTA0YS41LjUgMCAwIDEtMSAwVjdIMy40OTZhLjUuNSAwIDAgMSAwLTFINlYzLjVhLjUuNSAwIDAgMSAxIDBWNmgyLjUwM2EuNS41IDAgMCAxIDAgMUg3LjAwMVonLz48cGF0aCBmaWxsPScjMDAwJyBkPSdNMTcuNzUgNC41aC01LjA2M2E2LjUgNi41IDAgMCAwLS43MDktMS41aDUuNzcyQTMuMjUgMy4yNSAwIDAgMSAyMSA2LjI1djYuODc5YTIuMjUgMi4yNSAwIDAgMS0uNjU5IDEuNTlsLTUuNjIxIDUuNjIyYTIuMjUgMi4yNSAwIDAgMS0xLjU5MS42NTlINi4yNUEzLjI1IDMuMjUgMCAwIDEgMyAxNy43NXYtNS43NzJjLjQ2My4yOTcuOTY3LjUzNiAxLjUuNzA5djUuMDYzYzAgLjk2Ni43ODQgMS43NSAxLjc1IDEuNzVIMTN2LTMuMjVhMy4yNSAzLjI1IDAgMCAxIDMuMDY2LTMuMjQ1TDE2LjI1IDEzaDMuMjVWNi4yNWExLjc1IDEuNzUgMCAwIDAtMS43NS0xLjc1bS42ODkgMTBIMTYuMjVhMS43NSAxLjc1IDAgMCAwLTEuNzQ0IDEuNjA3bC0uMDA2LjE0M3YyLjE4OXonLz48L3N2Zz4=); - mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PHBhdGggZmlsbD0nIzAwMCcgZD0nTTEyIDYuNWE1LjUgNS41IDAgMSAwLTExIDAgNS41IDUuNSAwIDAgMCAxMSAwTTcgN2wuMDAxIDIuNTA0YS41LjUgMCAwIDEtMSAwVjdIMy40OTZhLjUuNSAwIDAgMSAwLTFINlYzLjVhLjUuNSAwIDAgMSAxIDBWNmgyLjUwM2EuNS41IDAgMCAxIDAgMUg3LjAwMVonLz48cGF0aCBmaWxsPScjMDAwJyBkPSdNMTcuNzUgNC41aC01LjA2M2E2LjUgNi41IDAgMCAwLS43MDktMS41aDUuNzcyQTMuMjUgMy4yNSAwIDAgMSAyMSA2LjI1djYuODc5YTIuMjUgMi4yNSAwIDAgMS0uNjU5IDEuNTlsLTUuNjIxIDUuNjIyYTIuMjUgMi4yNSAwIDAgMS0xLjU5MS42NTlINi4yNUEzLjI1IDMuMjUgMCAwIDEgMyAxNy43NXYtNS43NzJjLjQ2My4yOTcuOTY3LjUzNiAxLjUuNzA5djUuMDYzYzAgLjk2Ni43ODQgMS43NSAxLjc1IDEuNzVIMTN2LTMuMjVhMy4yNSAzLjI1IDAgMCAxIDMuMDY2LTMuMjQ1TDE2LjI1IDEzaDMuMjVWNi4yNWExLjc1IDEuNzUgMCAwIDAtMS43NS0xLjc1bS42ODkgMTBIMTYuMjVhMS43NSAxLjc1IDAgMCAwLTEuNzQ0IDEuNjA3bC0uMDA2LjE0M3YyLjE4OXonLz48L3N2Zz4=); + -webkit-mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PG1hc2sgaWQ9J2EnPjxyZWN0IHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nI2ZmZicvPjxjaXJjbGUgY3g9JzE3LjUnIGN5PScxNy41JyByPSc2LjUnIGZpbGw9JyMwMDAnLz48L21hc2s+PGcgbWFzaz0ndXJsKCNhKSc+PGcgdHJhbnNmb3JtPSd0cmFuc2xhdGUoLjE1IDIwLjU1KSBzY2FsZSguODUgLS44NSknPjxwYXRoIGZpbGw9JyMwMDAnIGQ9J00xNy43NSAzSDYuMjVBMy4yNSAzLjI1IDAgMCAwIDMgNi4yNXYxMS41QTMuMjUgMy4yNSAwIDAgMCA2LjI1IDIxaDYuODc5YTIuMjUgMi4yNSAwIDAgMCAxLjU5MS0uNjU5bDUuNjIxLTUuNjIyQTIuMjUgMi4yNSAwIDAgMCAyMSAxMy4xMjlWNi4yNUEzLjI1IDMuMjUgMCAwIDAgMTcuNzUgM20wIDEuNWMuOTY2IDAgMS43NS43ODQgMS43NSAxLjc1VjEzaC0zLjI1YTMuMjUgMy4yNSAwIDAgMC0zLjI1IDMuMjV2My4yNUg2LjI1YTEuNzUgMS43NSAwIDAgMS0xLjc1LTEuNzVWNi4yNWMwLS45NjYuNzg0LTEuNzUgMS43NS0xLjc1em0xLjc1IDguNWgtMy4yNWExLjc1IDEuNzUgMCAwIDAtMS43NSAxLjc1djMuMjV6Jy8+PC9nPjwvZz48cGF0aCBmaWxsPScjMDAwJyBkPSdNMjMgMTcuNWE1LjUgNS41IDAgMSAwLTExIDAgNS41IDUuNSAwIDAgMCAxMSAwbS01IC41LjAwMSAyLjUwM2EuNS41IDAgMSAxLTEgMFYxOGgtMi41MDVhLjUuNSAwIDAgMSAwLTFIMTd2LTIuNWEuNS41IDAgMSAxIDEgMFYxN2gyLjQ5N2EuNS41IDAgMCAxIDAgMXonLz48L3N2Zz4=); + mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PG1hc2sgaWQ9J2EnPjxyZWN0IHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nI2ZmZicvPjxjaXJjbGUgY3g9JzE3LjUnIGN5PScxNy41JyByPSc2LjUnIGZpbGw9JyMwMDAnLz48L21hc2s+PGcgbWFzaz0ndXJsKCNhKSc+PGcgdHJhbnNmb3JtPSd0cmFuc2xhdGUoLjE1IDIwLjU1KSBzY2FsZSguODUgLS44NSknPjxwYXRoIGZpbGw9JyMwMDAnIGQ9J00xNy43NSAzSDYuMjVBMy4yNSAzLjI1IDAgMCAwIDMgNi4yNXYxMS41QTMuMjUgMy4yNSAwIDAgMCA2LjI1IDIxaDYuODc5YTIuMjUgMi4yNSAwIDAgMCAxLjU5MS0uNjU5bDUuNjIxLTUuNjIyQTIuMjUgMi4yNSAwIDAgMCAyMSAxMy4xMjlWNi4yNUEzLjI1IDMuMjUgMCAwIDAgMTcuNzUgM20wIDEuNWMuOTY2IDAgMS43NS43ODQgMS43NSAxLjc1VjEzaC0zLjI1YTMuMjUgMy4yNSAwIDAgMC0zLjI1IDMuMjV2My4yNUg2LjI1YTEuNzUgMS43NSAwIDAgMS0xLjc1LTEuNzVWNi4yNWMwLS45NjYuNzg0LTEuNzUgMS43NS0xLjc1em0xLjc1IDguNWgtMy4yNWExLjc1IDEuNzUgMCAwIDAtMS43NSAxLjc1djMuMjV6Jy8+PC9nPjwvZz48cGF0aCBmaWxsPScjMDAwJyBkPSdNMjMgMTcuNWE1LjUgNS41IDAgMSAwLTExIDAgNS41IDUuNSAwIDAgMCAxMSAwbS01IC41LjAwMSAyLjUwM2EuNS41IDAgMSAxLTEgMFYxOGgtMi41MDVhLjUuNSAwIDAgMSAwLTFIMTd2LTIuNWEuNS41IDAgMSAxIDEgMFYxN2gyLjQ5N2EuNS41IDAgMCAxIDAgMXonLz48L3N2Zz4=); -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-size: 24px 24px; @@ -3575,8 +3575,8 @@ ul.x-tab-strip li.x-tab-strip-active .x-tab-strip-text.icon_new_note::before { margin-right: 6px; vertical-align: middle; background-color: currentColor; - -webkit-mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PHBhdGggZmlsbD0nIzAwMCcgZD0nTTEyIDYuNWE1LjUgNS41IDAgMSAwLTExIDAgNS41IDUuNSAwIDAgMCAxMSAwTTcgN2wuMDAxIDIuNTA0YS41LjUgMCAwIDEtMSAwVjdIMy40OTZhLjUuNSAwIDAgMSAwLTFINlYzLjVhLjUuNSAwIDAgMSAxIDBWNmgyLjUwM2EuNS41IDAgMCAxIDAgMUg3LjAwMVonLz48cGF0aCBmaWxsPScjMDAwJyBkPSdNMTcuNzUgNC41aC01LjA2M2E2LjUgNi41IDAgMCAwLS43MDktMS41aDUuNzcyQTMuMjUgMy4yNSAwIDAgMSAyMSA2LjI1djYuODc5YTIuMjUgMi4yNSAwIDAgMS0uNjU5IDEuNTlsLTUuNjIxIDUuNjIyYTIuMjUgMi4yNSAwIDAgMS0xLjU5MS42NTlINi4yNUEzLjI1IDMuMjUgMCAwIDEgMyAxNy43NXYtNS43NzJjLjQ2My4yOTcuOTY3LjUzNiAxLjUuNzA5djUuMDYzYzAgLjk2Ni43ODQgMS43NSAxLjc1IDEuNzVIMTN2LTMuMjVhMy4yNSAzLjI1IDAgMCAxIDMuMDY2LTMuMjQ1TDE2LjI1IDEzaDMuMjVWNi4yNWExLjc1IDEuNzUgMCAwIDAtMS43NS0xLjc1bS42ODkgMTBIMTYuMjVhMS43NSAxLjc1IDAgMCAwLTEuNzQ0IDEuNjA3bC0uMDA2LjE0M3YyLjE4OXonLz48L3N2Zz4=); - mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PHBhdGggZmlsbD0nIzAwMCcgZD0nTTEyIDYuNWE1LjUgNS41IDAgMSAwLTExIDAgNS41IDUuNSAwIDAgMCAxMSAwTTcgN2wuMDAxIDIuNTA0YS41LjUgMCAwIDEtMSAwVjdIMy40OTZhLjUuNSAwIDAgMSAwLTFINlYzLjVhLjUuNSAwIDAgMSAxIDBWNmgyLjUwM2EuNS41IDAgMCAxIDAgMUg3LjAwMVonLz48cGF0aCBmaWxsPScjMDAwJyBkPSdNMTcuNzUgNC41aC01LjA2M2E2LjUgNi41IDAgMCAwLS43MDktMS41aDUuNzcyQTMuMjUgMy4yNSAwIDAgMSAyMSA2LjI1djYuODc5YTIuMjUgMi4yNSAwIDAgMS0uNjU5IDEuNTlsLTUuNjIxIDUuNjIyYTIuMjUgMi4yNSAwIDAgMS0xLjU5MS42NTlINi4yNUEzLjI1IDMuMjUgMCAwIDEgMyAxNy43NXYtNS43NzJjLjQ2My4yOTcuOTY3LjUzNiAxLjUuNzA5djUuMDYzYzAgLjk2Ni43ODQgMS43NSAxLjc1IDEuNzVIMTN2LTMuMjVhMy4yNSAzLjI1IDAgMCAxIDMuMDY2LTMuMjQ1TDE2LjI1IDEzaDMuMjVWNi4yNWExLjc1IDEuNzUgMCAwIDAtMS43NS0xLjc1bS42ODkgMTBIMTYuMjVhMS43NSAxLjc1IDAgMCAwLTEuNzQ0IDEuNjA3bC0uMDA2LjE0M3YyLjE4OXonLz48L3N2Zz4=); + -webkit-mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PG1hc2sgaWQ9J2EnPjxyZWN0IHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nI2ZmZicvPjxjaXJjbGUgY3g9JzE3LjUnIGN5PScxNy41JyByPSc2LjUnIGZpbGw9JyMwMDAnLz48L21hc2s+PGcgbWFzaz0ndXJsKCNhKSc+PGcgdHJhbnNmb3JtPSd0cmFuc2xhdGUoLjE1IDIwLjU1KSBzY2FsZSguODUgLS44NSknPjxwYXRoIGZpbGw9JyMwMDAnIGQ9J00xNy43NSAzSDYuMjVBMy4yNSAzLjI1IDAgMCAwIDMgNi4yNXYxMS41QTMuMjUgMy4yNSAwIDAgMCA2LjI1IDIxaDYuODc5YTIuMjUgMi4yNSAwIDAgMCAxLjU5MS0uNjU5bDUuNjIxLTUuNjIyQTIuMjUgMi4yNSAwIDAgMCAyMSAxMy4xMjlWNi4yNUEzLjI1IDMuMjUgMCAwIDAgMTcuNzUgM20wIDEuNWMuOTY2IDAgMS43NS43ODQgMS43NSAxLjc1VjEzaC0zLjI1YTMuMjUgMy4yNSAwIDAgMC0zLjI1IDMuMjV2My4yNUg2LjI1YTEuNzUgMS43NSAwIDAgMS0xLjc1LTEuNzVWNi4yNWMwLS45NjYuNzg0LTEuNzUgMS43NS0xLjc1em0xLjc1IDguNWgtMy4yNWExLjc1IDEuNzUgMCAwIDAtMS43NSAxLjc1djMuMjV6Jy8+PC9nPjwvZz48cGF0aCBmaWxsPScjMDAwJyBkPSdNMjMgMTcuNWE1LjUgNS41IDAgMSAwLTExIDAgNS41IDUuNSAwIDAgMCAxMSAwbS01IC41LjAwMSAyLjUwM2EuNS41IDAgMSAxLTEgMFYxOGgtMi41MDVhLjUuNSAwIDAgMSAwLTFIMTd2LTIuNWEuNS41IDAgMSAxIDEgMFYxN2gyLjQ5N2EuNS41IDAgMCAxIDAgMXonLz48L3N2Zz4=); + mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nbm9uZSc+PG1hc2sgaWQ9J2EnPjxyZWN0IHdpZHRoPScyNCcgaGVpZ2h0PScyNCcgZmlsbD0nI2ZmZicvPjxjaXJjbGUgY3g9JzE3LjUnIGN5PScxNy41JyByPSc2LjUnIGZpbGw9JyMwMDAnLz48L21hc2s+PGcgbWFzaz0ndXJsKCNhKSc+PGcgdHJhbnNmb3JtPSd0cmFuc2xhdGUoLjE1IDIwLjU1KSBzY2FsZSguODUgLS44NSknPjxwYXRoIGZpbGw9JyMwMDAnIGQ9J00xNy43NSAzSDYuMjVBMy4yNSAzLjI1IDAgMCAwIDMgNi4yNXYxMS41QTMuMjUgMy4yNSAwIDAgMCA2LjI1IDIxaDYuODc5YTIuMjUgMi4yNSAwIDAgMCAxLjU5MS0uNjU5bDUuNjIxLTUuNjIyQTIuMjUgMi4yNSAwIDAgMCAyMSAxMy4xMjlWNi4yNUEzLjI1IDMuMjUgMCAwIDAgMTcuNzUgM20wIDEuNWMuOTY2IDAgMS43NS43ODQgMS43NSAxLjc1VjEzaC0zLjI1YTMuMjUgMy4yNSAwIDAgMC0zLjI1IDMuMjV2My4yNUg2LjI1YTEuNzUgMS43NSAwIDAgMS0xLjc1LTEuNzVWNi4yNWMwLS45NjYuNzg0LTEuNzUgMS43NS0xLjc1em0xLjc1IDguNWgtMy4yNWExLjc1IDEuNzUgMCAwIDAtMS43NSAxLjc1djMuMjV6Jy8+PC9nPjwvZz48cGF0aCBmaWxsPScjMDAwJyBkPSdNMjMgMTcuNWE1LjUgNS41IDAgMSAwLTExIDAgNS41IDUuNSAwIDAgMCAxMSAwbS01IC41LjAwMSAyLjUwM2EuNS41IDAgMSAxLTEgMFYxOGgtMi41MDVhLjUuNSAwIDAgMSAwLTFIMTd2LTIuNWEuNS41IDAgMSAxIDEgMFYxN2gyLjQ5N2EuNS41IDAgMCAxIDAgMXonLz48L3N2Zz4=); -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-size: 24px 24px; diff --git a/client/zarafa/mail/MailRecord.js b/client/zarafa/mail/MailRecord.js index 8eda9f249..e0f5d45ce 100644 --- a/client/zarafa/mail/MailRecord.js +++ b/client/zarafa/mail/MailRecord.js @@ -33,7 +33,8 @@ Zarafa.mail.MailRecordFields = [ {name: 'depth', type: 'int', defaultValue: 0}, {name: 'conversation_count', type: 'int', defaultValue: 0}, {name: 'conversation_id'}, - {name: 'folder_name'} + {name: 'folder_name'}, + {name: 'internet_message_id'} ]; Zarafa.mail.MailRecordPhantomHandler = function(record) { diff --git a/client/zarafa/mail/ui/MailGridContextMenu.js b/client/zarafa/mail/ui/MailGridContextMenu.js index 6be37e8a9..9c607e225 100644 --- a/client/zarafa/mail/ui/MailGridContextMenu.js +++ b/client/zarafa/mail/ui/MailGridContextMenu.js @@ -265,7 +265,11 @@ Zarafa.mail.ui.MailGridContextMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalM records: records } }, - container.populateInsertionPoint('context.mail.contextmenu.topoptions', this), { + container.populateInsertionPoint('context.mail.contextmenu.topoptions', this), + // Placed here rather than through the insertion point above, because items + // there appear in plugin load order - which puts notes ahead of tasks - while + // "Create note" belongs after "Create task", the order the navigation uses. + Zarafa.note.Actions.createLinkedNoteContextItem(), { xtype: 'zarafa.conditionalitem', text: _('Print'), iconCls: 'icon_print', diff --git a/client/zarafa/note/Actions.js b/client/zarafa/note/Actions.js index c6566520c..7ea4aa3c1 100644 --- a/client/zarafa/note/Actions.js +++ b/client/zarafa/note/Actions.js @@ -37,6 +37,207 @@ Zarafa.note.Actions = { Zarafa.core.data.UIFactory.openCreateRecord(record, config); }, + /** + * Open a Panel in which a new {@link Zarafa.core.data.IPMRecord note} can be + * edited, linked to the given mail. + * + * The note is created in the Notes folder of the store the mail lives in, not + * in the user's own, so that everyone with access to a shared mailbox sees it. + * The mail is not written to, the link is held by the note alone. + * + * @param {Zarafa.core.data.IPMRecord} mailRecord The mail to link the note to + * @param {Object} config (optional) Configuration object used to create + * the Content Panel. + */ + openCreateLinkedNote: function(mailRecord, config) + { + var folder = Zarafa.note.Actions.getLinkedNotesFolder(mailRecord); + if (!folder) { + return; + } + + var record = Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.StickyNote', { + store_entryid: folder.get('store_entryid'), + parent_entryid: folder.get('entryid'), + note_link_id: mailRecord.get('internet_message_id'), + note_link_entryid: mailRecord.get('entryid'), + note_link_parent_entryid: mailRecord.get('parent_entryid'), + note_link_subject: mailRecord.get('subject') + }); + + Zarafa.core.data.UIFactory.openCreateRecord(record, config); + }, + + /** + * Opens the mail a note annotates. + * + * The note stores the mail's entryid when the link is made. Should the mail have + * been moved since, that entryid no longer resolves and the mail cannot be opened + * this way; the link from the mail to its notes still holds, since that runs off + * the message id, which moving does not change. + * + * @param {Zarafa.core.data.IPMRecord} noteRecord The note whose mail to open + */ + openLinkedMail: function(noteRecord) + { + var mail = Zarafa.note.Actions.getLinkedMailRecord(noteRecord); + if (!mail) { + return; + } + + Zarafa.core.data.UIFactory.openViewRecord(mail); + }, + + /** + * Returns a record for the mail a note annotates, or undefined when the note is + * not linked to one or too little was recorded to identify it. + * + * An already existing record for that mail is preferred over building a second + * one: two record objects for the same mail are compared against each other while + * it opens, and a mail is identified by all of entryid, parent_entryid and + * store_entryid together, so a copy carrying fewer of them breaks that comparison + * and the mail then never finishes loading. + * + * @param {Zarafa.core.data.IPMRecord} noteRecord The note + * @return {Zarafa.core.data.IPMRecord} The mail record, or undefined + */ + getLinkedMailRecord: function(noteRecord) + { + var entryid = noteRecord.get('note_link_entryid'); + if (Ext.isEmpty(entryid)) { + return undefined; + } + + var existing = Zarafa.note.Actions.findLoadedMailRecord(entryid); + if (existing) { + return existing; + } + + // Nothing loaded for it, so build one. Notes made before the parent entryid + // was recorded cannot be opened this way; the note still names its mail. + var parentEntryid = noteRecord.get('note_link_parent_entryid'); + if (Ext.isEmpty(parentEntryid)) { + return undefined; + } + + // The note always lives in the same store as the mail it annotates, so the + // note's own store locates the mail. + var mail = Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.Note', { + entryid: entryid, + parent_entryid: parentEntryid, + store_entryid: noteRecord.get('store_entryid') + }, entryid); + + mail.opened = false; + + return mail; + }, + + /** + * Finds a record for the given mail among those already loaded, so that a second + * record object for the same mail is not created. + * + * @param {String} entryid The mail's entryid + * @return {Zarafa.core.data.IPMRecord} The record, or undefined when none is loaded + */ + findLoadedMailRecord: function(entryid) + { + var stores = [ container.getShadowStore() ]; + + var mailContext = container.getContextByName('mail'); + if (mailContext && mailContext.getModel() && mailContext.getModel().getStore()) { + stores.push(mailContext.getModel().getStore()); + } + + var found; + Ext.each(stores, function(store) { + store.each(function(record) { + if (record.isMessageClass('IPM.Note', true) && + Zarafa.core.EntryId.compareEntryIds(record.get('entryid'), entryid)) { + found = record; + return false; + } + }); + return !found; + }); + + return found; + }, + + /** + * Returns the Notes folder of the store the given mail lives in, which is where + * notes linked to that mail are kept. + * + * @param {Zarafa.core.data.IPMRecord} mailRecord The mail record + * @return {Zarafa.hierarchy.data.MAPIFolderRecord} The notes folder, or undefined + * when the store or its Notes folder is not available to this user. + */ + getLinkedNotesFolder: function(mailRecord) + { + var store = container.getHierarchyStore().getById(mailRecord.get('store_entryid')); + + return store ? store.getDefaultFolder('note') : undefined; + }, + + /** + * Returns the contextmenu item which annotates a mail with a note, for use in the + * mail contextmenus. + * + * @return {Object} The contextmenu item for creating a note on a mail + */ + createLinkedNoteContextItem: function() + { + return { + xtype: 'zarafa.conditionalitem', + text: _('Create note'), + iconCls: 'icon_new_note', + hidden: true, + handler: this.onContextItemCreateNote, + beforeShow: this.onBeforeShowCreateNote, + scope: this + }; + }, + + /** + * Event handler triggered when the {@link #createLinkedNoteContextItem create note} + * contextmenu item is clicked. + * + * @param {Zarafa.core.ui.menu.ConditionalItem} menuItem The clicked item + * @private + */ + onContextItemCreateNote: function(menuItem) + { + Zarafa.note.Actions.openCreateLinkedNote(menuItem.getRecords()[0]); + }, + + /** + * Shows the {@link #createLinkedNoteContextItem create note} contextmenu item only + * for a single mail which can actually be annotated: it needs an + * 'internet_message_id' to link the note to, and a Notes folder in its own store + * which this user may create in. + * + * @param {Zarafa.core.ui.menu.ConditionalItem} menuItem The item about to be shown + * @private + */ + onBeforeShowCreateNote: function(menuItem) + { + var records = menuItem.getRecords(); + + if (!Array.isArray(records) || records.length !== 1) { + menuItem.setVisible(false); + return; + } + + var record = records[0]; + if (!record.isMessageClass('IPM.Note', true) || Ext.isEmpty(record.get('internet_message_id'))) { + menuItem.setVisible(false); + return; + } + + var folder = Zarafa.note.Actions.getLinkedNotesFolder(record); + menuItem.setVisible(!!folder && folder.hasCreateRights()); + }, + /** * Opens the options dialog for a note record. * diff --git a/client/zarafa/note/NoteContext.js b/client/zarafa/note/NoteContext.js index c624939be..0e948c57e 100644 --- a/client/zarafa/note/NoteContext.js +++ b/client/zarafa/note/NoteContext.js @@ -65,6 +65,9 @@ Zarafa.note.NoteContext = Ext.extend(Zarafa.core.Context, { // The control will be shown when the user selects the note context from the button panel. this.registerInsertionPoint('navigation.center', this.createNoteNavigationPanel, this); + // Show the notes which annotate a mail inside the mail preview. + this.registerInsertionPoint('previewpanel.toolbar.detaillinks', this.getLinkedNoteDetailLinks, this); + Zarafa.core.data.SharedComponentType.addProperty('note.dialog.options'); }, @@ -291,6 +294,19 @@ Zarafa.note.NoteContext = Ext.extend(Zarafa.core.Context, { }; }, + /** + * Returns the panel which shows the notes annotating the previewed mail, for the + * 'previewpanel.toolbar.detaillinks' insertion point. + * + * @return {Object} configuration object for {@link Zarafa.note.ui.LinkedNoteLinks} + */ + getLinkedNoteDetailLinks: function() + { + return { + xtype: 'zarafa.linkednotelinks' + }; + }, + /** * Returns the buttons for the dropdown list of the Print button in the main toolbar. It will use the * main.maintoolbar.print.note insertion point to allow other plugins to add their items at the end. diff --git a/client/zarafa/note/NoteRecord.js b/client/zarafa/note/NoteRecord.js index 9ac13cea1..feb2748d6 100644 --- a/client/zarafa/note/NoteRecord.js +++ b/client/zarafa/note/NoteRecord.js @@ -12,7 +12,15 @@ Ext.namespace('Zarafa.note'); */ Zarafa.note.NoteRecordFields = [ {name: 'icon_index', type: 'int', defaultValue:Zarafa.core.mapi.IconIndex['note_yellow']}, - {name: 'color', type: 'int', defaultValue:Zarafa.core.mapi.NoteColor['note_yellow']} + {name: 'color', type: 'int', defaultValue:Zarafa.core.mapi.NoteColor['note_yellow']}, + // The mail this note annotates, empty when it is not linked to one. + // See Zarafa.note.ui.LinkedNoteLinks. + {name: 'note_link_id', type: 'string', defaultValue: ''}, + // entryid and parent_entryid together, because a mail record is identified by + // all of its id properties; one of them missing breaks comparing it to others. + {name: 'note_link_entryid'}, + {name: 'note_link_parent_entryid'}, + {name: 'note_link_subject', type: 'string', defaultValue: ''} ]; Zarafa.core.data.RecordFactory.addFieldToMessageClass('IPM.StickyNote', Zarafa.note.NoteRecordFields); diff --git a/client/zarafa/note/dialogs/NoteEditContentPanel.js b/client/zarafa/note/dialogs/NoteEditContentPanel.js index 9d00e91ea..4fb0be82e 100644 --- a/client/zarafa/note/dialogs/NoteEditContentPanel.js +++ b/client/zarafa/note/dialogs/NoteEditContentPanel.js @@ -38,15 +38,17 @@ Zarafa.note.dialogs.NoteEditContentPanel = Ext.extend(Zarafa.core.ui.RecordConte */ createPanel: function() { - //Add toolbar and text area in window panel + // Add toolbar and text area in window panel. The layout is deliberately left to + // the panel itself: it stacks the linked-mail bar above the editor, and a 'fit' + // layout imposed from here would size only the first of the two, leaving the + // editor without a height. return { iconCls : 'icon_folder_note', border : false, tbar : { xtype: 'zarafa.noteedittoolbar' }, - xtype : 'zarafa.noteeditpanel', - layout : 'fit' + xtype : 'zarafa.noteeditpanel' }; }, diff --git a/client/zarafa/note/dialogs/NoteEditPanel.js b/client/zarafa/note/dialogs/NoteEditPanel.js index 7631aaaea..ab6bc9394 100644 --- a/client/zarafa/note/dialogs/NoteEditPanel.js +++ b/client/zarafa/note/dialogs/NoteEditPanel.js @@ -15,6 +15,19 @@ Zarafa.note.dialogs.NoteEditPanel = Ext.extend(Ext.FormPanel, { * @type String */ currentColorCss: undefined, + + /** + * @cfg {Ext.Template/String} linkedMailTemplate The template for the bar naming the + * mail this note annotates, shown only when it is linked to one. + */ + linkedMailTemplate: + '' + + /* # TRANSLATORS: Names the mail a note is attached to, e.g. "Note on: Re: invoice" */ + pgettext('note.dialog', 'Note on') + ': ' + + '{note_link_subject:htmlEncode}' + + '' + _('(no subject)') + '' + + '', + /** * @constructor * @param {Object} config Configuration structure @@ -28,13 +41,26 @@ Zarafa.note.dialogs.NoteEditPanel = Ext.extend(Ext.FormPanel, { Ext.applyIf(config,{ xtype : 'zarafa.noteeditpanel', - layout : 'fit', + layout : { type: 'vbox', align: 'stretch' }, border : false, items : [{ + xtype: 'container', + ref: 'linkedMailBar', + cls: 'stickynote_linkedmail', + hidden: true, + autoHeight: true, + listeners: { + // The record usually arrives before this bar is rendered, so it + // has to be filled in once the element exists. + afterrender: this.onLinkedMailBarRender, + scope: this + } + }, { xtype: 'zarafa.editorfield', useHtml: false, ref: 'noteText', plaintextName: 'body', + flex: 1, listeners: { change: this.onTextareaChange, scope: this @@ -43,6 +69,78 @@ Zarafa.note.dialogs.NoteEditPanel = Ext.extend(Ext.FormPanel, { }); Zarafa.note.dialogs.NoteEditPanel.superclass.constructor.call(this,config); + + if (Ext.isString(this.linkedMailTemplate)) { + this.linkedMailTemplate = new Ext.XTemplate(this.linkedMailTemplate, { + compiled: true + }); + } + }, + + /** + * Binds the click handler which opens the mail this note annotates. + * @private + */ + afterRender: function() + { + Zarafa.note.dialogs.NoteEditPanel.superclass.afterRender.apply(this, arguments); + + // Delegated from the panel rather than the bar, whose element does not exist + // yet at this point. + this.mon(this.el, 'click', this.onLinkedMailClick, this, { + delegate: '.stickynote_linkedmail_link' + }); + }, + + /** + * Fills in the linked-mail bar once it has an element to render into. + * @private + */ + onLinkedMailBarRender: function() + { + this.updateLinkedMailBar(this.record); + }, + + /** + * Opens the mail this note annotates. + * @private + */ + onLinkedMailClick: function() + { + if (this.record) { + Zarafa.note.Actions.openLinkedMail(this.record); + } + }, + + /** + * Shows which mail this note annotates, or hides the bar when it annotates none. + * The mail can only be opened from here if its entryid was recorded, which notes + * made before this feature existed do not have. + * + * @param {Zarafa.core.data.IPMRecord} record The note record + * @private + */ + updateLinkedMailBar: function(record) + { + var bar = this.linkedMailBar; + + if (!bar || !bar.rendered) { + return; + } + + if (!record || Ext.isEmpty(record.get('note_link_id'))) { + bar.setVisible(false); + return; + } + + this.linkedMailTemplate.overwrite(bar.getEl(), record.data); + // Only toggle our own class here; assigning className would drop the classes + // Ext puts on the element, including the one that hides it. + bar.getEl()[Zarafa.note.Actions.getLinkedMailRecord(record) ? 'removeClass' : 'addClass']( + 'stickynote_linkedmail_unopenable' + ); + bar.setVisible(true); + this.doLayout(); }, /** @@ -105,6 +203,8 @@ Zarafa.note.dialogs.NoteEditPanel = Ext.extend(Ext.FormPanel, { { var textArea = this.noteText.getEditor(); + this.updateLinkedMailBar(record); + if (record) { this.record = record; if (contentReset || record.isModifiedSinceLastUpdate('body')) { diff --git a/client/zarafa/note/ui/LinkedNoteLinks.js b/client/zarafa/note/ui/LinkedNoteLinks.js new file mode 100644 index 000000000..1c1b6154e --- /dev/null +++ b/client/zarafa/note/ui/LinkedNoteLinks.js @@ -0,0 +1,276 @@ +/* + * #dependsFile client/zarafa/note/NoteStore.js + * #dependsFile client/zarafa/note/Actions.js + * #dependsFile client/zarafa/core/data/RestrictionFactory.js + * #dependsFile client/zarafa/core/mapi/Restrictions.js + * #dependsFile client/zarafa/core/mapi/NoteColor.js + */ +Ext.namespace('Zarafa.note.ui'); + +/** + * @class Zarafa.note.ui.LinkedNoteLinks + * @extends Ext.Container + * @xtype zarafa.linkednotelinks + * + * Shows the notes which annotate the mail being previewed, inside the message + * header. A note is linked by holding the mail's 'internet_message_id' in its + * 'note_link_id'; the mail itself is never written to. + * + * The notes live in the Notes folder of the store the mail is in rather than in + * the user's own, so everyone with access to a shared mailbox sees the same + * notes. That is what makes this usable for coordinating on a shared mailbox. + */ +Zarafa.note.ui.LinkedNoteLinks = Ext.extend(Ext.Container, { + /** + * The store the linked notes are loaded into. Private to this panel: the note + * {@link Zarafa.note.NoteContextModel context model} must not be used, because + * loading into it would replace what the Notes context is showing. + * + * @property + * @type Zarafa.note.NoteStore + * @private + */ + noteStore: undefined, + + /** + * The 'internet_message_id' {@link #noteStore} was last loaded for, used to skip + * reloading when {@link #update} fires again for the same mail. + * + * @property + * @type String + * @private + */ + loadedMessageId: undefined, + + /** + * @cfg {Ext.Template/String} linkedNotesTemplate The template applied to build the + * list of linked notes. + */ + linkedNotesTemplate: + '
' + + '
' + + '' + + '
' + + // A sticky note's subject is generated from the first line of + // its body, so showing both would print the same text twice. + '' + + '{[this.noteBody(values.body)]}' + + '' + + '' + + '{subject:htmlEncode}' + + '' + + '
' + + '
' + + '
', + + /** + * @constructor + * @param {Object} config configuration object. + */ + constructor: function(config) + { + config = config || {}; + + config.plugins = Ext.value(config.plugins, []); + config.plugins.push('zarafa.recordcomponentupdaterplugin'); + + config = Ext.applyIf(config, { + xtype: 'zarafa.linkednotelinks', + cls: 'preview-header-linkednotes', + hidden: true, + forceLayout: true + }); + + Zarafa.note.ui.LinkedNoteLinks.superclass.constructor.call(this, config); + + if (Ext.isString(this.linkedNotesTemplate)) { + this.linkedNotesTemplate = new Ext.XTemplate(this.linkedNotesTemplate, { + compiled: true, + + /** + * Maps the note color onto its CSS class. + * @param {Number} color The {@link Zarafa.core.mapi.NoteColor} value + * @return {String} the CSS class for that color + */ + colorName: function(color) + { + return Zarafa.core.mapi.NoteColor.getName(color) || 'note_yellow'; + }, + + /** + * Prepares a note body for display. Note bodies are plain text, the + * sticky note module forces it, so they must be encoded before being + * placed into the preview. + * + * A body read from a folder listing is cut off at 255 characters, the + * same limit the Notes list itself shows, so say so with an ellipsis + * rather than letting the text stop mid-sentence. The whole note is one + * click away. + * + * @param {String} body The note body + * @return {String} the body, safe to insert + */ + noteBody: function(body) + { + var text = Ext.util.Format.htmlEncode(body); + + if (body.length >= Zarafa.note.ui.LinkedNoteLinks.bodyPreviewLimit) { + text += '…'; + } + + return Ext.util.Format.nl2br(text); + } + }); + } + + this.noteStore = new Zarafa.note.NoteStore(); + this.mon(this.noteStore, 'load', this.onNoteStoreLoad, this); + // A mailbox whose Notes folder cannot be read must not raise an error + // dialog over the mail being previewed; it simply has no notes to show. + this.mon(this.noteStore, 'exception', this.onNoteStoreException, this); + }, + + /** + * Binds the click handler for opening a note. + * @private + */ + afterRender: function() + { + Zarafa.note.ui.LinkedNoteLinks.superclass.afterRender.apply(this, arguments); + + this.mon(this.el, 'click', this.onNoteClick, this, { + delegate: '.preview-header-linkednote' + }); + }, + + /** + * Update the panel for the given {@link Zarafa.core.data.IPMRecord record}. Anything + * which is not a mail is ignored, as is a mail with no 'internet_message_id', which + * cannot be annotated because there is nothing to link a note to. + * + * @param {Zarafa.core.data.IPMRecord} record The record to show the notes of + * @param {Boolean} contentReset true if the record was reloaded rather than updated + */ + update: function(record, contentReset) + { + if (!this.el.dom) { + return; + } + + var messageId = record ? record.get('internet_message_id') : undefined; + + if (!record || !record.isMessageClass('IPM.Note', true) || Ext.isEmpty(messageId)) { + this.clear(); + return; + } + + // The updater plugin fires for every change to the record, reloading on each + // would put a request behind things like marking the mail read. + if (!contentReset && messageId === this.loadedMessageId) { + return; + } + + this.record = record; + this.loadedMessageId = messageId; + + var folder = Zarafa.note.Actions.getLinkedNotesFolder(record); + if (!folder) { + this.clear(); + return; + } + + this.noteStore.load({ + folder: folder, + params: { + restriction: { + note: Zarafa.core.data.RestrictionFactory.dataResProperty( + 'note_link_id', + Zarafa.core.mapi.Restrictions.RELOP_EQ, + messageId + ) + } + } + }); + }, + + /** + * Event handler for the {@link Zarafa.note.NoteStore#load load} event. Renders the + * notes which were found. A mail with no notes shows nothing at all, so the header + * of an ordinary mail is left exactly as it was. + * @private + */ + onNoteStoreLoad: function(store, records) + { + if (!this.el || !this.el.dom) { + return; + } + + if (Ext.isEmpty(records)) { + this.clear(); + return; + } + + this.linkedNotesTemplate.overwrite(Ext.get(this.el.dom), Ext.pluck(records, 'data')); + this.setVisible(true); + this.doLayout(); + }, + + /** + * Event handler for the {@link Zarafa.note.NoteStore#exception exception} event. + * Failing to read the notes folder is not worth interrupting the user over: the + * mail itself is unaffected, so the panel just stays hidden. + * @private + */ + onNoteStoreException: function() + { + this.clear(); + }, + + /** + * Opens the note which was clicked. + * @private + */ + onNoteClick: function(event, target) + { + var index = parseInt(Ext.get(target).getAttribute('data-noteindex'), 10); + var record = this.noteStore.getAt(index); + + if (record) { + Zarafa.note.Actions.openNoteContent([record]); + } + }, + + /** + * Empties and hides the panel. + * @private + */ + clear: function() + { + this.el.dom.innerHTML = ''; + this.setVisible(false); + }, + + /** + * @private + */ + onDestroy: function() + { + if (this.noteStore) { + this.noteStore.destroy(); + this.noteStore = undefined; + } + + Zarafa.note.ui.LinkedNoteLinks.superclass.onDestroy.apply(this, arguments); + } +}); + +/** + * @property + * The number of characters a note body read from a folder listing is cut off at, by + * the table query rather than by this panel. + * @type Number + * @static + */ +Zarafa.note.ui.LinkedNoteLinks.bodyPreviewLimit = 255; + +Ext.reg('zarafa.linkednotelinks', Zarafa.note.ui.LinkedNoteLinks); diff --git a/server/includes/core/class.properties.php b/server/includes/core/class.properties.php index 18300f654..7db2d0c79 100644 --- a/server/includes/core/class.properties.php +++ b/server/includes/core/class.properties.php @@ -112,6 +112,11 @@ public function setStore($store = false) { else { $stores = [$store]; $this->store = $store; + // The active store changed, so the mapping key has to be resolved + // again. Init() only does that while $init is false, and it is reset + // once per request rather than once per module, so without this the + // next getter would answer for the previous module's store. + $this->init = false; } foreach ($stores as $key => $store) { @@ -140,13 +145,26 @@ public function getStore() { * @return string PR_MAPPING_SIGNATURE of the given MAPI Message Store if exists else 0 */ private function getStoreMappingSignature($store) { + $storeProps = []; + try { - $storeMapping = mapi_getprops($store, [PR_MAPPING_SIGNATURE]); + $storeProps = mapi_getprops($store, [PR_MAPPING_SIGNATURE, PR_ENTRYID]); } catch (Exception) { } - return isset($storeMapping[PR_MAPPING_SIGNATURE]) ? bin2hex((string) $storeMapping[PR_MAPPING_SIGNATURE]) : '0'; + $signature = isset($storeProps[PR_MAPPING_SIGNATURE]) ? bin2hex((string) $storeProps[PR_MAPPING_SIGNATURE]) : ''; + + // gromox reports an all-zero PR_MAPPING_SIGNATURE for every store, while + // still allocating named property ids per store. Keying the cache on that + // makes every store share one entry, so the first store to resolve a name + // hands its ids to all the others. Fall back to the store entryid, which + // does identify the store. + if ($signature === '' || strspn($signature, '0') === strlen($signature)) { + $signature = isset($storeProps[PR_ENTRYID]) ? bin2hex((string) $storeProps[PR_ENTRYID]) : '0'; + } + + return $signature; } /** @@ -1031,6 +1049,11 @@ public function getMailListProperties() { // Used by the client to group the mail list into conversations. $properties['conversation_id'] = PR_CONVERSATION_ID; + // Identifies the mail for notes linked to it. Deliberately list-only: + // the open/save map is used by createmailitemmodule, where saving a new + // mail would write back an empty PR_INTERNET_MESSAGE_ID. + $properties['internet_message_id'] = PR_INTERNET_MESSAGE_ID; + unset( $properties['access'], $properties['appointment_duedate'], @@ -1084,6 +1107,19 @@ public function getStickyNoteProperties() { $properties["color"] = "PT_LONG:PSETID_Note:0x8B00"; $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords"; $properties["deleted_on"] = PR_DELETED_ON; + // The mail this note annotates, empty when it is not linked to one. + // Named by string rather than dispid: the id space is shared, so a + // number could collide. + // + // note_link_id is PR_INTERNET_MESSAGE_ID and is what identifies the + // mail: it survives the mail being moved between folders, which an + // entryid does not. The other two only serve the note -> mail + // direction: the entryid opens the mail directly and the subject + // names it without having to fetch it first. Both are best-effort. + $properties["note_link_id"] = "PT_STRING8:PS_PUBLIC_STRINGS:GrommunioWebNoteLinkId"; + $properties["note_link_entryid"] = "PT_BINARY:PS_PUBLIC_STRINGS:GrommunioWebNoteLinkEntryId"; + $properties["note_link_parent_entryid"] = "PT_BINARY:PS_PUBLIC_STRINGS:GrommunioWebNoteLinkParentEntryId"; + $properties["note_link_subject"] = "PT_STRING8:PS_PUBLIC_STRINGS:GrommunioWebNoteLinkSubject"; $this->mapping[$this->storeMapping]['note'] = getPropIdsFromStrings($this->store, $properties); } @@ -1097,7 +1133,15 @@ public function getStickyNoteProperties() { * @return array properties for a sticky note */ public function getStickyNoteListProperties() { - return $this->getStickyNoteProperties(); + $properties = $this->getStickyNoteProperties(); + + // The note text itself, so notes linked to a mail can be read where they + // are shown next to it rather than only after opening each one. Sticky + // notes are small and live in a single folder, so unlike the mail list + // this does not meaningfully grow the request. + $properties['body'] = PR_BODY; + + return $properties; } /**