{ browser.i18n.getMessage('maximumPadSizeExceeded') }
+${browser.i18n.getMessage('welcomeSubtitle')}
+${browser.i18n.getMessage('welcomeOpenNotes')}
+Alt+Shift+W
')}Opt+Shift+W
')}${browser.i18n.getMessage('welcomeAccessNotes')}
+${browser.i18n.getMessage('welcomeFormatText')}
+').replace(/`/g, '
')}').replace(/`/g, '
')}').replace(/`/g, '
')}').replace(/`/g, '
')}').replace(/`/g, '
')}`').replace(/``/g, '`
')}${browser.i18n.getMessage('welcomeSuggestion')}
+${browser.i18n.getMessage('welcomeThatsIt')}
+`.replace(/(?:\n(?:\s*))+/g, '').trim(); + +export default INITIAL_CONTENT; diff --git a/src/sidebar/app/utils/constants.js b/src/sidebar/app/utils/constants.js new file mode 100644 index 000000000..02ceeb938 --- /dev/null +++ b/src/sidebar/app/utils/constants.js @@ -0,0 +1 @@ +export const SURVEY_PATH = 'https://qsurvey.mozilla.com/s3/notes?ref=sidebar'; diff --git a/src/sidebar/editor.js b/src/sidebar/app/utils/editor.js similarity index 52% rename from src/sidebar/editor.js rename to src/sidebar/app/utils/editor.js index 7872f1688..96bb3ca7b 100644 --- a/src/sidebar/editor.js +++ b/src/sidebar/app/utils/editor.js @@ -1,10 +1,15 @@ -/* exported customizeEditor, getPadStats, localizeEditorButtons, setAnimation, formatFooterTime */ + +// const UI_LANG = browser.i18n.getUILanguage(); +// const RTL_LANGS = ['ar', 'fa', 'he']; +// const LANG_DIR = RTL_LANGS.includes(UI_LANG) ? 'rtl' : 'ltr'; +// const TEXT_ALIGN_DIR = LANG_DIR === 'rtl' ? 'right' : 'left'; + function customizeEditor(editor) { const mainEditor = document.querySelector('.ck-editor__main'); // Disable right clicks // Refs: https://stackoverflow.com/a/737043/186202 - document.querySelectorAll('.ck-toolbar, #footer-buttons').forEach((sel) => { + document.querySelectorAll('.ck-toolbar, #footer-buttons').forEach(sel => { sel.addEventListener('contextmenu', e => { e.preventDefault(); }); @@ -12,11 +17,13 @@ function customizeEditor(editor) { // Fixes an issue with CKEditor and keeping multiple Firefox windows in sync // Ref: https://github.com/mozilla/notes/issues/424 - document.querySelectorAll('.ck-heading-dropdown .ck-list__item').forEach((btn) => { - btn.addEventListener('click', () => { - editor.fire('changesDone'); + document + .querySelectorAll('.ck-heading-dropdown .ck-list__item') + .forEach(btn => { + btn.addEventListener('click', () => { + editor.fire('changesDone'); + }); }); - }); document.addEventListener('dragover', () => { mainEditor.classList.add('drag-n-drop-focus'); @@ -38,18 +45,26 @@ function customizeEditor(editor) { localizeEditorButtons(); } +function insertSelectedText(editor, selectedText) { + const currentNotesContent = editor.getData(); + const updatedNotesContent = currentNotesContent + `${selectedText.replace(/\n\n/g, '
')}
`; + editor.setData(updatedNotesContent); + browser.runtime.sendMessage({ + action: 'metrics-context-menu' + }); +} + + function localizeEditorButtons() { // Clear CKEditor tooltips. Fixes: https://github.com/mozilla/notes/issues/410 - document.querySelectorAll('.ck-toolbar .ck-tooltip__text').forEach((sel) => { + document.querySelectorAll('.ck-toolbar .ck-tooltip__text').forEach(sel => { sel.remove(); }); let userOSKey; - if (navigator.appVersion.indexOf('Mac') !== -1) - userOSKey = '⌘'; - else - userOSKey = 'Ctrl'; + if (navigator.appVersion.indexOf('Mac') !== -1) userOSKey = '⌘'; + else userOSKey = 'Ctrl'; const size = document.querySelector('button.ck-button:nth-child(1)'), // Need to target buttons by index. Ref: https://github.com/ckeditor/ckeditor5-basic-styles/issues/59 @@ -59,10 +74,11 @@ function localizeEditorButtons() { bullet = document.querySelector('button.ck-button:nth-child(5)'), ordered = document.querySelector('button.ck-button:nth-child(6)'); -// Setting button titles in place of tooltips + // Setting button titles in place of tooltips size.title = browser.i18n.getMessage('fontSizeTitle'); bold.title = browser.i18n.getMessage('boldTitle') + ' (' + userOSKey + '+B)'; - italic.title = browser.i18n.getMessage('italicTitle') + ' (' + userOSKey + '+I)'; + italic.title = + browser.i18n.getMessage('italicTitle') + ' (' + userOSKey + '+I)'; strike.title = browser.i18n.getMessage('strikethroughTitle'); ordered.title = browser.i18n.getMessage('numberedListTitle'); bullet.title = browser.i18n.getMessage('bulletedListTitle'); @@ -81,9 +97,9 @@ function getPadStats(editor) { list_numbered: false }; - const range = ClassicEditor.imports.range.createIn( editor.document.getRoot() ); + const range = ClassicEditor.imports.range.createIn(editor.document.getRoot()); - for ( const value of range ) { + for (const value of range) { if (value.type === 'text') { // Bold if (value.item.textNode._attrs.get('bold')) { @@ -129,61 +145,4 @@ function getPadStats(editor) { }; } -/** - * Set animation on footerButtons toolbar - * @param {Boolean} animateSyncIcon Start looping animation on sync icon - * @param {Boolean} syncingLayout if true, animate to syncingLayout (sync icon on right) - * if false, animate to savingLayout (sync icon on left) - * @param {Boolean} warning Apply yellow warning styling on toolbar - */ -function setAnimation( animateSyncIcon = true, syncingLayout, warning, syncSuccess ) { // animateSyncIcon, syncingLayout, warning, syncSuccess - const footerButtons = document.getElementById('footer-buttons'); - const enableSync = document.getElementById('enable-sync'); - const savingIndicator = document.getElementById('saving-indicator'); - - if (animateSyncIcon === true && !footerButtons.classList.contains('animateSyncIcon')) { - footerButtons.classList.add('animateSyncIcon'); - } else if (animateSyncIcon === false && footerButtons.classList.contains('animateSyncIcon')) { - footerButtons.classList.remove('animateSyncIcon'); - } - - if (syncingLayout === true && footerButtons.classList.contains('savingLayout')) { - footerButtons.classList.replace('savingLayout', 'syncingLayout'); - enableSync.style.backgroundColor = 'transparent'; - // Start blink animation on saving-indicator - savingIndicator.classList.add('blink'); - // Reset CSS animation by removeing class - setTimeout(() => savingIndicator.classList.remove('blink'), 400); - } else if (syncingLayout === false && footerButtons.classList.contains('syncingLayout')) { - // Animate savingIndicator text - savingIndicator.classList.add('blink'); - setTimeout(() => savingIndicator.classList.remove('blink'), 400); - setTimeout(() => { - enableSync.style.backgroundColor = null; - }, 400); - // - footerButtons.classList.replace('syncingLayout', 'savingLayout'); - } - - if (warning === true && !footerButtons.classList.contains('warning')) { - footerButtons.classList.add('warning'); - } else if (warning === false && footerButtons.classList.contains('warning')) { - footerButtons.classList.remove('warning'); - } - - if (syncSuccess === true) { - footerButtons.classList.add('actionable'); - } else { - footerButtons.classList.remove('actionable'); - } -} - -/** - * Formats time for the Notes footer - * @param time - * @returns {string} - */ -function formatFooterTime(date) { - date = date || Date.now(); - return new Date(date).toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'}); -} +export { customizeEditor, getPadStats, insertSelectedText }; diff --git a/src/sidebar/theme.js b/src/sidebar/app/utils/theme.js similarity index 95% rename from src/sidebar/theme.js rename to src/sidebar/app/utils/theme.js index 72847ae7a..93392b4ba 100644 --- a/src/sidebar/theme.js +++ b/src/sidebar/app/utils/theme.js @@ -9,7 +9,7 @@ function getThemeFromStorage() { darkSS.id = 'dark-styles'; darkSS.type = 'text/css'; darkSS.rel = 'stylesheet'; - darkSS.href = 'styles-dark.css'; + darkSS.href = 'static/css/styles-dark.css'; document.getElementsByTagName('head')[0].appendChild(darkSS); } } else if (data.theme === 'default' || data.theme === undefined) { diff --git a/src/sidebar/app/utils/utils.js b/src/sidebar/app/utils/utils.js new file mode 100644 index 000000000..169d5b640 --- /dev/null +++ b/src/sidebar/app/utils/utils.js @@ -0,0 +1,14 @@ +/** + * Formats time for the Notes footer + * @param time + * @returns {string} + */ +function formatFooterTime(date) { + date = date || Date.now(); + return new Date(date).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit' + }); +} + +export { formatFooterTime }; diff --git a/src/sidebar/index.html b/src/sidebar/index.html new file mode 100644 index 000000000..6c2f4e6a1 --- /dev/null +++ b/src/sidebar/index.html @@ -0,0 +1,13 @@ + + + + +${browser.i18n.getMessage('welcomeSubtitle')}
${browser.i18n.getMessage('welcomeOpenNotes')}
Alt+Shift+W
')}Opt+Shift+W
')}${browser.i18n.getMessage('welcomeAccessNotes')}
${browser.i18n.getMessage('welcomeFormatText')}
').replace(/`/g, '
')}').replace(/`/g, '
')}').replace(/`/g, '
')}').replace(/`/g, '
')}').replace(/`/g, '
')}`').replace(/``/g, '`
')}${browser.i18n.getMessage('welcomeSuggestion')}
${browser.i18n.getMessage('welcomeThatsIt')}
`; - -let isAuthenticated = false; -let waitingToReconnect = false; -let loginTimeout; -let editingInProcess = false; -let syncingInProcess = false; - -enableSync.setAttribute('title', browser.i18n.getMessage('syncNotes')); -giveFeedbackButton.setAttribute('title', browser.i18n.getMessage('feedback')); -giveFeedbackMenuItem.text = browser.i18n.getMessage('feedback'); - -function giveFeedbackCallback(e) { - e.preventDefault(); - browser.tabs.create({ - url: SURVEY_PATH - }); -} - -giveFeedbackButton.addEventListener('dragstart', (e) => { - e.preventDefault(); -}); - -giveFeedbackMenuItem.addEventListener('dragstart', (e) => { - e.preventDefault(); -}); - -giveFeedbackButton.addEventListener('click', giveFeedbackCallback); -giveFeedbackMenuItem.addEventListener('click', giveFeedbackCallback); - -// ignoreNextLoadEvent is used to make sure the update does not trigger on other sidebars -let ignoreNextLoadEvent = false; -let ignoreTextSynced = false; -let lastModified; - -ClassicEditor.create(document.querySelector('#editor'), { - heading: { - options: [ - { modelElement: 'heading1', viewElement: 'h1', title: browser.i18n.getMessage('title1'), class: 'ck-heading_heading1' }, - { modelElement: 'heading2', viewElement: 'h2', title: browser.i18n.getMessage('title2'), class: 'ck-heading_heading2' }, - { modelElement: 'heading3', viewElement: 'h3', title: browser.i18n.getMessage('title3'), class: 'ck-heading_heading3' }, - { modelElement: 'paragraph', title: browser.i18n.getMessage('bodyText'), class: 'ck-heading_paragraph' } - ] - }, - toolbar: ['headings', 'bold', 'italic', 'strike', 'bulletedList', 'numberedList'], -}).then(editor => { - return migrationCheck(editor) - .then(() => { - editor.document.on('change', (eventInfo, name) => { - const isFocused = document.querySelector('.ck-editor__editable').classList.contains('ck-focused'); - // Only use the focused editor or handle 'rename' events to set the data into storage. - if (isFocused || name === 'rename' || name === 'insert') { - const content = editor.getData(); - if (!ignoreNextLoadEvent && content !== undefined && - content.replace(/ /g, ' ') !== INITIAL_CONTENT.replace(/\s\s+/g, ' ')) { - ignoreTextSynced = true; - if (content.length > 15000) { - console.error('Maximum notepad size reached:', content.length); // eslint-disable-line no-console - migrationNote.classList.add('visible'); - migrationBody.textContent = browser.i18n.getMessage('maximumPadSizeExceeded'); - browser.runtime.sendMessage({ - action: 'metrics-limit-reached', - context: getPadStats(editor) - }); - } else { - migrationNote.classList.remove('visible'); - } - - chrome.runtime.sendMessage({ - action: 'kinto-save', - content - }); - - chrome.runtime.sendMessage({ - action: 'metrics-changed', - context: getPadStats(editor) - }); - } - ignoreNextLoadEvent = false; - } - }); - - savingIndicator.onclick = () => { - enableSyncAction(editor); - }; - // to make reconnectSync text Field act like savingIndicator - enableSync.onclick = () => { - enableSyncAction(editor); - }; - - customizeEditor(editor); - loadContent(); - - chrome.runtime.onMessage.addListener(eventData => { - let content; - switch (eventData.action) { - case 'sync-authenticated': - setAnimation(true, true, false); // animateSyncIcon, syncingLayout, warning - isAuthenticated = true; - waitingToReconnect = false; - clearTimeout(loginTimeout); - // set title attr of footer to the currently logged in account - if (eventData.profile) { - footerButtons.title = `${browser.i18n.getMessage('syncToMail', eventData.profile.email)}`; - } - localStorage.setItem('userEmail', footerButtons.title); - savingIndicator.textContent = browser.i18n.getMessage('syncProgress'); - browser.runtime.sendMessage({ - action: 'kinto-sync' - }); - break; - case 'kinto-loaded': - clearTimeout(loginTimeout); - content = eventData.data; - // Switch to Date.now() to show when we pulled notes instead of 'eventData.last_modified' - lastModified = Date.now(); - getLastSyncedTime(); - handleLocalContent(editor, content); - document.getElementById('loading').style.display = 'none'; - break; - case 'text-change': - ignoreNextLoadEvent = true; - browser.runtime.sendMessage({ - action: 'kinto-load' - }); - break; - case 'text-syncing': - setAnimation(true); // animateSyncIcon, syncingLayout, warning - savingIndicator.textContent = browser.i18n.getMessage('syncProgress'); - // Disable sync-action - syncingInProcess = true; - break; - case 'text-editing': - if (isAuthenticated) { - setAnimation(true); // animateSyncIcon, syncingLayout, warning - syncingInProcess = true; - } - if (!waitingToReconnect) { - if (isAuthenticated) { - savingIndicator.textContent = browser.i18n.getMessage('syncProgress'); - } else { - savingIndicator.textContent = browser.i18n.getMessage('savingChanges'); - } - } - // Disable sync-action - editingInProcess = true; - break; - case 'text-synced': - lastModified = eventData.last_modified; - if (!ignoreTextSynced || eventData.conflict) { - handleLocalContent(editor, eventData.content); - } - ignoreTextSynced = false; - getLastSyncedTime(); - // Enable sync-action - syncingInProcess = false; - break; - case 'text-saved': - if (!waitingToReconnect && !isAuthenticated) { - // persist reconnect warning, do not override with the 'saved at' - savingIndicator.textContent = browser.i18n.getMessage('savedComplete2', formatFooterTime()); - } - // Enable sync-action - editingInProcess = false; - break; - case 'reconnect': - clearTimeout(loginTimeout); - reconnectSync(); - // Enable sync-action - syncingInProcess = false; - break; - case 'disconnected': - giveFeedbackButton.style.display = 'inherit'; - localStorage.removeItem('userEmail'); - disconnectSync.style.display = 'none'; - footerButtons.removeAttribute('title');// remove profile email from title attribute - isAuthenticated = false; - setAnimation(false, false, false); // animateSyncIcon, syncingLayout, warning - getLastSyncedTime(); - break; - } - }); - }); - -}).catch(error => { - console.error(error); // eslint-disable-line no-console -}); - -function loadContent() { - browser.storage.local.get('credentials').then((data) => { - if (data.hasOwnProperty('credentials')) { - isAuthenticated = true; - const userEmail = localStorage.getItem('userEmail'); - if (userEmail) { - footerButtons.title = userEmail; - } - } - }); - chrome.runtime.sendMessage({ - action: 'kinto-load' // Load locally. - }); - -} - -function handleLocalContent(editor, content) { - if (!content) { - browser.storage.local.get('notes2').then((data) => { - if (!data.hasOwnProperty('notes2')) { - editor.setData(INITIAL_CONTENT); - ignoreNextLoadEvent = true; - } else { - editor.setData(data.notes2); - chrome.runtime.sendMessage({ - action: 'kinto-save', - content: data.notes2 - }).then(() => { - // Clean-up - browser.storage.local.remove('notes2'); - }); - } - }); - } else if (editor.getData() !== content) { - // Prevent from loading too big content but allow for conflict handling. - editor.setData(content); - } -} - -function reconnectSync() { - waitingToReconnect = true; - isAuthenticated = false; - setAnimation(false, true, true); // animateSyncIcon, syncingLayout, warning - savingIndicator.textContent = browser.i18n.getMessage('reconnectSync'); - chrome.runtime.sendMessage({ - action: 'metrics-reconnect-sync' - }); -} - -function disconnectFromSync() { - waitingToReconnect = false; - disconnectSync.style.display = 'none'; - isAuthenticated = false; - setAnimation(false, false, false); // animateSyncIcon, syncingLayout, warning - setTimeout(() => { - savingIndicator.textContent = browser.i18n.getMessage('disconnected'); - }, 200); - setTimeout(() => { - getLastSyncedTime(); - }, 2000); - browser.runtime.sendMessage('notes@mozilla.com', { - action: 'disconnected' - }); -} - -disconnectSync.addEventListener('click', disconnectFromSync); - -function enableSyncAction(editor) { - if (editingInProcess || syncingInProcess) { - return; - } - - if (isAuthenticated && footerButtons.classList.contains('syncingLayout')) { - // Trigger manual sync - giveFeedbackButton.style.display = 'none'; - setAnimation(true); - browser.runtime.sendMessage({ - action: 'kinto-sync' - }); - } else if (!isAuthenticated && (footerButtons.classList.contains('savingLayout') || waitingToReconnect)) { - // Login - giveFeedbackButton.style.display = 'none'; - setAnimation(true, true, false); // animateSyncIcon, syncingLayout, warning - - // enable disable sync button - disconnectSync.style.display = 'block'; - - setTimeout(() => { - savingIndicator.textContent = browser.i18n.getMessage('openingLogin'); - }, 200); // Delay text for smooth animation - - loginTimeout = setTimeout(() => { - setAnimation(false, true, true); // animateSyncIcon, syncingLayout, warning - savingIndicator.textContent = browser.i18n.getMessage('pleaseLogin'); - waitingToReconnect = true; - }, 5000); - - browser.runtime.sendMessage({ - action: 'authenticate', - context: getPadStats(editor) - }); - } - - waitingToReconnect = false; -} - -function getLastSyncedTime() { - if (waitingToReconnect) { - // persist reconnect warning, do not override with the 'saved at' - return; - } - - if (isAuthenticated) { - giveFeedbackButton.style.display = 'none'; - // eslint-disable-next-line no-unsanitized/property - savingIndicator.innerHTML = browser.i18n.getMessage('syncComplete3', formatFooterTime(lastModified)); - disconnectSync.style.display = 'block'; - isAuthenticated = true; - setAnimation(false, true, false, true); // animateSyncIcon, syncingLayout, warning, syncSuccess - } else { - savingIndicator.textContent = browser.i18n.getMessage('changesSaved', formatFooterTime()); - } -} - -document.addEventListener('DOMContentLoaded', getLastSyncedTime); - -// Create a connection with the background script to handle open and -// close events. -browser.runtime.connect(); diff --git a/src/sidebar/styles-dark.css b/src/sidebar/static/css/styles-dark.css similarity index 78% rename from src/sidebar/styles-dark.css rename to src/sidebar/static/css/styles-dark.css index ed783b881..fe6b7365c 100644 --- a/src/sidebar/styles-dark.css +++ b/src/sidebar/static/css/styles-dark.css @@ -1,9 +1,9 @@ body { - background-color: #393F4C; + background-color: #393f4c; } -#notes .ck-toolbar { - background-color: #272B35; +#notes .ck-toolbar { + background-color: #272b35; } #enable-sync svg path { @@ -24,23 +24,38 @@ body { /* material-design-lite (MDL) menu */ #context-menu-button { - background-image: url('dark-svg/three-dots-dark.svg'); + background-image: url('../svg/three-dots-dark.svg'); background-color: #323744; } #footer-buttons.warning #context-menu-button { background-color: rgb(255, 236, 173); - background-image: url('three-dots.svg'); + background-image: url('../svg/three-dots.svg'); } +#syncing-indicator { + background: none !important; + color: inherit !important; +} + +#footer-buttons div:first-child.isClickable button { + background: none !important; + color: #e6e6e6 !important; +} + +.warning #syncing-indicator { + color: black !important; +} + + .mdl-menu__outline { - background: #272B35; + background: #272b35; transition: none; } .context-menu-item { color: #e6e6e6; - background-color: #272B35; + background-color: #272b35; } .context-menu-item:hover { @@ -52,9 +67,10 @@ body { } /* end MDL menu */ -#footer-buttons button { - background-color: #393F4C; - color: #e6e6e6; + +#footer-buttons button, +#footer-buttons a { + background-color: #393f4c; transition: background 50ms; } @@ -68,13 +84,12 @@ body { background-color: #3d4352; } -#footer-buttons.actionable div:first-child -{ +#footer-buttons.actionable div:first-child { color: white; } ::-moz-selection { - background-color: #5675B9; + background-color: #5675b9; color: #fff; } @@ -94,8 +109,8 @@ body { fill: none; } -.ck-editor__editable { - background-color: #393F4C; +.ck-editor__editable { + background-color: #393f4c; color: #e6e6e6; } @@ -119,7 +134,7 @@ body { #notes .ck-toolbar * { color: #e6e6e6; - background: #272B35; + background: #272b35; } #notes .ck-toolbar button.ck-on *, @@ -143,7 +158,7 @@ body { } #notes .ck-toolbar .ck-button__label { -background: transparent; + background: transparent; } #notes .ck-toolbar .ck-button .ck-icon, @@ -191,18 +206,18 @@ background: transparent; color: #e6e6e6; } .ql-snow .ql-picker-options { - background-color: #393F4C; + background-color: #393f4c; } .mdl-menu__item:focus { - background-color: #272B35; + background-color: #272b35; } .mdl-menu__item:hover { - background-color: #323744; + background-color: #323744; } .mdl-menu__item:active { background-color: #3d4352; - } +} .actionable { color: white; diff --git a/src/sidebar/static/scss/ckeditor.scss b/src/sidebar/static/scss/ckeditor.scss new file mode 100644 index 000000000..a10357829 --- /dev/null +++ b/src/sidebar/static/scss/ckeditor.scss @@ -0,0 +1,156 @@ +/** + CKEditor overrides +*/ + +.editorWrapper { + display: flex; + flex: calc(100% - 40px); + flex-direction: column; + flex-grow: 1; + margin: 0; + padding: 0; + overflow: hidden; + position: relative; + +} + +.ck-editor { + display: flex; + flex-direction: column; + height: 100%; + margin: 0; + overflow: hidden; + padding: 0; +} + +#notes { + .ck-toolbar { + border: 0; + border-radius: 0; + } + + .ck-editor__top { + flex: 40px; + flex-direction: column; + height: 40px; + border-bottom: 1px solid #ccc; + } + + .ck-editor__main { + flex: auto; + font-size: 14px; + flex-direction: column; + height: 100%; + overflow: hidden; + } + + .ck-editor__editable { + flex: auto; + flex-direction: column; + height: 100%; + border: 0; + box-shadow: none; + cursor: text; + + p { + margin: 5px 0; + font-size: 14px; + } + + ul, ol { + padding-left: 20px; + } + + ul > li, ol > li { + line-height: 1.5rem; + } + + h1 { + margin: 2px 0; + font-size: 24px; + padding: 0; + } + + h2 { + margin: 7px 0; + font-size: 20px; + } + + h3 { + margin: 5px 0; + font-size: 16px; + } + } + + .drag-n-drop-focus, + .drag-n-drop-focus .ck-editor__editable { + border: 1px solid #008000; + background-color: #dcd6da; + } + + .ck-heading-dropdown button { + width: 45px; + height: 25px; + padding: 2px 2px 2px 5px !important; + } + + .ck-heading-dropdown .ck-dropdown__panel { + width: auto; + min-width: 70px; + } + + .ck-toolbar > * { + margin-right: 0.2em; + } + + .ck-button { + padding: 0.2em; + margin: 0.2em; + } + + .ck-button__label { + max-width: 30px; + } + + .ck-balloon-panel { + left: 0 !important; + width: 100% !important; + padding: 0.1em; + box-shadow: none; + } +} + +// CSS related to Notifications +#sync-note { + background: #00FFFE; // green background is: #B6FEB4; + width: 100%; + position: relative; + margin: 0 auto; + border: 0; + border-top: 1px solid #ccc; + color: #424242; + font-size: 12px; + padding: 0px 15% 0px 10px; + + button { + width: 16px; + position: absolute; + top: 6px; + right: 6px; + padding: 0 0; + opacity: 0.4; + + &:hover { + cursor: pointer; + opacity: 0.6 + } + &:active { + cursor: pointer; + opacity: 0.8 + } + } +} + + + + diff --git a/src/sidebar/static/scss/footer.scss b/src/sidebar/static/scss/footer.scss new file mode 100644 index 000000000..aa13d049f --- /dev/null +++ b/src/sidebar/static/scss/footer.scss @@ -0,0 +1,258 @@ + +/** +Footer Styles + */ + +footer * { + font-size: 14px; + min-height: 30px; +} + +button { + cursor: pointer; + display: block; + background: none; + border: none; +} + +footer { + position: relative; + flex: 40px; +} + +#footer-buttons { + align-items: stretch; + position: relative; + border-top: 1px solid #d7d7db; + display: flex; + justify-content: space-between; + width: 100%; + height: 40px; + background-color: transparent; + transition: background var(--sync-animation-duration) + cubic-bezier(0.07, 0.95, 0, 1); + + div:first-child { + position: relative; + flex: 1 0; + padding: 10px 40px 10px 10px; + color: #969696; + text-align: left; + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + transition: padding 0s 0.2s; + + p { + width: calc(100% - 35px); + text-overflow: ellipsis; + overflow: hidden; + padding-right: 6px; + margin-top: 1px; + } + + button { + cursor: default; + } + + &.isClickable { + button { + color: black; + cursor: pointer !important; + } + } + } +} + +/* warning style */ +#footer-buttons.warning { + background-color: rgb(255, 236, 173); + transition: background var(--sync-animation-duration) + cubic-bezier(0.07, 0.95, 0, 1); + + button { + color: black; + } +} + +#footer-buttons.syncingLayout div:first-child { + padding: 10px 10px 10px 40px; + transition: padding 0s 0.2s; +} + +#footer-buttons.actionable div:first-child { + color: black; +} + +/* Sync icon */ +#enable-sync { + position: absolute; + right: 0px; + top: 0px; + bottom: 0px; + width: 40px; + border-left: 1px solid #d7d7db; + border-right: 1px solid #d7d7db; + display: flex; + justify-content: center; + align-items: center; +} + +.savingLayout { + #saving-indicator{ + opacity: 1; + visibility: visible; + transition: opacity .2s .2s; + } + #syncing-indicator{ + opacity: 0; + visibility: hidden; + transition: opacity .1s, visibility 0s .2s; + } + #enable-sync { + margin-right: 32px; + transition: margin var(--sync-animation-duration) + cubic-bezier(0.07, 0.95, 0, 1) 0.05s, + border 0s cubic-bezier(0.07, 0.95, 0, 1) var(--sync-animation-duration); + + &:hover { + background-color: #ebebee; + } + } +} + +.syncingLayout { + #saving-indicator{ + transition: opacity .1s, visibility 0s .1s; + opacity: 0; + visibility: hidden; + } + #syncing-indicator{ + opacity: 1; + visibility: visible; + transition: opacity .2s .2s; + cursor: default; + } + #enable-sync { + margin-right: calc(100% - 40px); + background: none; + border: none; + transition: margin var(--sync-animation-duration) + cubic-bezier(0.07, 0.95, 0, 1) 0.05s; + + &:hover { + background: transparent; + } + } +} + +#saving-indicator { + background: none; + padding-bottom: 12px; + color: inherit; +} + +#syncing-indicator { + width: calc(100% - 25px); + text-overflow: ellipsis; + overflow: hidden; + margin-top: 1px; + position: absolute; + top: 8px; + padding-right: 40px; + background: none; + padding-bottom: 12px; + color: inherit; + + &:hover { + background: none !important; + } +} + +/* Animate sync icon */ +@keyframes rotate { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +#enable-sync svg { + animation-name: rotate; + animation-duration: 1s; + animation-fill-mode: forwards; + animation-timing-function: linear; + animation-iteration-count: infinite; + animation-play-state: paused; +} + +.animateSyncIcon #enable-sync svg { + animation-play-state: running; +} + +.animateSyncIcon #enable-sync svg path, +.syncingLayout>div:hover #enable-sync svg path { + fill: #4e92dd; +} + +// Always black on warning/yellow background +.warning>div:hover #enable-sync svg path { + fill: black !important; +} + +#footer-buttons button, +#footer-buttons a { + transition: background 50ms; +} + +#footer-buttons button:hover, +#footer-buttons a:hover { + background-color: #ebebee; +} + +#footer-buttons button:active, +#footer-buttons a:active { + background-color: #d7d7db; +} + +#menu { + position: absolute; + bottom: 10px; + right: 10px; +} + +/* Select menu from sync */ +#context-menu-button { + background-size: 3px; + background-image: url('../svg/three-dots.svg'); + background-position: 50%; + background-repeat: no-repeat; + width: 22px; + text-align: left; + position: absolute; + right: 6px; + top: 5px; + bottom: 6px; + +} + +#context-menu-button:hover, +#context-menu-button:focus, +#context-menu-button:active { + background-color: rgba(128, 128, 128, 0.2) !important; +} + +.context-menu { + font-family: inherit; + padding: 0; +} + +.context-menu-item { + font-size: 12px; + padding: 0 0 0 15px; + height: 18px; + line-height: 30px; +} diff --git a/src/sidebar/menu.css b/src/sidebar/static/scss/menu.scss similarity index 64% rename from src/sidebar/menu.css rename to src/sidebar/static/scss/menu.scss index dacaeec36..14e6eb9d1 100644 --- a/src/sidebar/menu.css +++ b/src/sidebar/static/scss/menu.scss @@ -27,14 +27,17 @@ height: 0; width: 0; visibility: hidden; - z-index: -1; } -.mdl-menu__container.is-visible, .mdl-menu__container.is-animating { + z-index: -1; +} +.mdl-menu__container.is-visible, +.mdl-menu__container.is-animating { z-index: 999; - visibility: visible; } + visibility: visible; +} .mdl-menu__outline { display: block; - background: rgb(255,255,255); + background: rgb(255, 255, 255); margin: 0; padding: 0; border: none; @@ -48,26 +51,36 @@ transform: scale(0); -webkit-transform-origin: 0 0; transform-origin: 0 0; - box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12); + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), + 0 1px 5px 0 rgba(0, 0, 0, 0.12); will-change: transform; - transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1), -webkit-transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1); - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1), -webkit-transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); - z-index: -1; } + transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1), + -webkit-transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1), + -webkit-transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + z-index: -1; +} .mdl-menu__container.is-visible .mdl-menu__outline { opacity: 1; -webkit-transform: scale(1); transform: scale(1); - z-index: 999; } + z-index: 999; +} .mdl-menu__outline.mdl-menu--bottom-right { -webkit-transform-origin: 100% 0; - transform-origin: 100% 0; } + transform-origin: 100% 0; +} .mdl-menu__outline.mdl-menu--top-left { -webkit-transform-origin: 0 100%; - transform-origin: 0 100%; } + transform-origin: 0 100%; +} .mdl-menu__outline.mdl-menu--top-right { -webkit-transform-origin: 100% 100%; - transform-origin: 100% 100%; } + transform-origin: 100% 100%; +} .mdl-menu { position: absolute; @@ -81,41 +94,48 @@ margin: 0; opacity: 0; clip: rect(0 0 0 0); - z-index: -1; } + z-index: -1; +} .mdl-menu__container.is-visible .mdl-menu { opacity: 1; - z-index: 999; } + z-index: 999; +} .mdl-menu.is-animating { - transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1), clip 0.3s cubic-bezier(0.4, 0, 0.2, 1); } + transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1), + clip 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} .mdl-menu.mdl-menu--bottom-right { left: auto; - right: 0; } + right: 0; +} .mdl-menu.mdl-menu--top-left { top: auto; - bottom: 0; } + bottom: 0; +} .mdl-menu.mdl-menu--top-right { top: auto; left: auto; bottom: 0; - right: 0; } + right: 0; +} .mdl-menu.mdl-menu--unaligned { top: auto; - left: auto; } + left: auto; +} .mdl-menu__item { display: block; border: none; - color: rgba(0,0,0, 0.87); + color: rgba(0, 0, 0, 0.87); background-color: transparent; text-align: left; margin: 0; padding: 0 16px; - outline-color: rgb(189,189,189); + outline-color: rgb(189, 189, 189); position: relative; overflow: hidden; font-size: 14px; font-weight: 400; - line-height: 24px; letter-spacing: 0; text-decoration: none; cursor: pointer; @@ -127,30 +147,45 @@ -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; - user-select: none; } + user-select: none; +} .mdl-menu__container.is-visible .mdl-menu__item { - opacity: 1; } + opacity: 1; +} .mdl-menu__item::-moz-focus-inner { - border: 0; } + border: 0; +} .mdl-menu__item--full-bleed-divider { - border-bottom: 1px solid rgba(0,0,0, 0.12); } -.mdl-menu__item[disabled], .mdl-menu__item[data-mdl-disabled] { - color: rgb(189,189,189); + border-bottom: 1px solid rgba(0, 0, 0, 0.12); +} +.mdl-menu__item[disabled], +.mdl-menu__item[data-mdl-disabled] { + color: rgb(189, 189, 189); + background-color: transparent; + cursor: auto; +} +.mdl-menu__item[disabled]:hover, +.mdl-menu__item[data-mdl-disabled]:hover { background-color: transparent; - cursor: auto; } -.mdl-menu__item[disabled]:hover, .mdl-menu__item[data-mdl-disabled]:hover { - background-color: transparent; } -.mdl-menu__item[disabled]:focus, .mdl-menu__item[data-mdl-disabled]:focus { - background-color: transparent; } -.mdl-menu__item[disabled] .mdl-ripple, .mdl-menu__item[data-mdl-disabled] .mdl-ripple { - background: transparent; } +} +.mdl-menu__item[disabled]:focus, +.mdl-menu__item[data-mdl-disabled]:focus { + background-color: transparent; +} +.mdl-menu__item[disabled] .mdl-ripple, +.mdl-menu__item[data-mdl-disabled] .mdl-ripple { + background: transparent; +} .mdl-menu__item:hover { - background-color: rgb(238,238,238); } + background-color: rgb(238, 238, 238); +} .mdl-menu__item:focus { outline: none; - background-color: rgb(238,238,238); } + background-color: rgb(238, 238, 238); +} .mdl-menu__item:active { - background-color: rgb(224,224,224); } + background-color: rgb(224, 224, 224); +} .mdl-menu__item--ripple-container { display: block; @@ -160,4 +195,5 @@ top: 0px; width: 100%; z-index: 0; - overflow: hidden; } + overflow: hidden; +} diff --git a/src/sidebar/static/scss/styles.scss b/src/sidebar/static/scss/styles.scss new file mode 100644 index 000000000..0c13014ec --- /dev/null +++ b/src/sidebar/static/scss/styles.scss @@ -0,0 +1,55 @@ +@import 'ckeditor'; +@import 'menu'; +@import 'footer'; + +html { + height: 100%; + font-family: sans-serif; + line-height: 1.2em; + overflow: hidden; + margin: 0; + --sync-animation-duration: 0.4s; +} + +body, +body > div { + display: flex; + flex-direction: column; + height: 100%; + margin: 0; + overflow: hidden; + padding: 0; +} + +/** +Custom list types for indents +https://github.com/mozilla/notes/issues/413 + */ +ol ol, +ol ol ol ol ol { + list-style-type: lower-alpha; +} + +ol ol ol, +ol ol ol ol ol ol { + list-style-type: lower-roman; +} + +ol ol ol ol { + list-style-type: decimal; +} + +/** +List indents end + */ + +div#loading { + height: 100%; + width: 100%; + position: absolute; + top: 0; + left: 0; + background: white; + z-index: 10; +} + diff --git a/src/sidebar/close.svg b/src/sidebar/static/svg/close.svg similarity index 100% rename from src/sidebar/close.svg rename to src/sidebar/static/svg/close.svg diff --git a/src/sidebar/dark-svg/three-dots-dark.svg b/src/sidebar/static/svg/three-dots-dark.svg similarity index 100% rename from src/sidebar/dark-svg/three-dots-dark.svg rename to src/sidebar/static/svg/three-dots-dark.svg diff --git a/src/sidebar/three-dots.svg b/src/sidebar/static/svg/three-dots.svg similarity index 100% rename from src/sidebar/three-dots.svg rename to src/sidebar/static/svg/three-dots.svg diff --git a/src/sidebar/styles.css b/src/sidebar/styles.css deleted file mode 100644 index 2d6680d74..000000000 --- a/src/sidebar/styles.css +++ /dev/null @@ -1,432 +0,0 @@ -html { - height: 100%; - font-family: sans-serif; - line-height: 1.2em; - overflow: hidden; - margin: 0; - --sync-animation-duration: .4s; -} - -body, body .ck-editor { - display: flex; - flex-direction: column; - height: 100%; - margin: 0; - overflow: hidden; - padding: 0; -} - -body .ck-toolbar { - border: 0; - border-radius: 0; -} - -body .ck-editor__top { - flex: 40px; - flex-direction: column; - height: 40px; - border-bottom: 1px solid #ccc; -} - -body .ck-editor__main { - flex: auto; - font-size: 14px; - flex-direction: column; - height: 100%; - overflow: hidden; -} - - -body .ck-editor .ck-editor__editable { - flex: auto; - flex-direction: column; - height: 100%; - border: 0; - box-shadow: none; - cursor: text; -} - -.ck-editor__editable ol, .ck-editor__editable ul { - padding-left: 20px; -} - -.ck-editor__editable ol > li, .ck-editor__editable ul > li { - line-height: 1.5rem; -} - -/** -Custom list types for indents -https://github.com/mozilla/notes/issues/413 - */ -ol ol, ol ol ol ol ol { - list-style-type: lower-alpha; -} - -ol ol ol, ol ol ol ol ol ol { - list-style-type: lower-roman; -} - -ol ol ol ol { - list-style-type: decimal; -} -/** -List indents end - */ - -div#loading { - height: 100%; - width: 100%; - position: absolute; - top: 0; - left: 0; - background: white; - z-index: 10; -} - -/** -Footer Styles - */ - -footer * { - font-size: 14px; - min-height: 30px; -} - -button { - cursor: pointer; - display: block; - background: none; - border: none; -} - -footer { - position: relative; - flex: 40px; -} - -#footer-buttons { - align-items: stretch; - position: relative; - border-top: 1px solid #d7d7db; - display: flex; - justify-content: space-between; - width: 100%; - height: 40px; - background-color: transparent; - transition: background var(--sync-animation-duration) cubic-bezier(.07,.95,0,1); -} - -/* warning style */ -#footer-buttons.warning { - background-color: rgb(255, 236, 173); - transition: background var(--sync-animation-duration) cubic-bezier(.07,.95,0,1); -} - -#footer-buttons div:first-child { - position: relative; - flex: 1 0; - padding: 10px 40px 10px 10px; - color: #969696; - text-align: left; - display: block; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - transition: padding 0s .2s; -} - -#footer-buttons.syncingLayout div:first-child { - padding: 10px 10px 10px 40px; - transition: padding 0s .2s; -} - -#footer-buttons.warning div:first-child, -#footer-buttons.actionable div:first-child -{ - color: black; -} - -/* Hide saving-indication then move enable-sync then show saving-indicator */ -@keyframes blink { - 0% { opacity: 1; } - 25% { opacity: 0; } - 75% { opacity: 0; } - 100% { opacity: 1; } -} -#saving-indicator.blink { - animation-name: blink; - animation-duration: var(--sync-animation-duration); - animation-timing-function: linear; - animation-iteration-count: 1; -} - -#saving-indicator { - cursor: pointer; -} - -/* Sync icon */ -#enable-sync { - position: absolute; - right: 0px; - top: 0px; - bottom: 0px; - width: 40px; - cursor: pointer; - border-left: 1px solid #d7d7db; - display: flex; - justify-content: center; - align-items: center; -} - -.savingLayout #enable-sync { - margin-right: 0px; - transition: margin var(--sync-animation-duration) cubic-bezier(.07,.95,0,1) .05s, border 0s cubic-bezier(.07,.95,0,1) var(--sync-animation-duration); -} - -.syncingLayout #enable-sync { - margin-right: calc(100% - 40px); - background: none; - border-left: none; - transition: margin var(--sync-animation-duration) cubic-bezier(.07,.95,0,1) .05s; -} - -.savingLayout #enable-sync:hover { - background-color: #ebebee; -} - -.syncingLayout #enable-sync:hover { - background: transparent; -} - -/* Animate sync icon */ -@keyframes rotate { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} -#enable-sync svg { - animation-name: rotate; - animation-duration: 1s; - animation-fill-mode: forwards; - animation-timing-function: linear; - animation-iteration-count: infinite; - animation-play-state: paused; -} -.animateSyncIcon #enable-sync svg { - animation-play-state: running; -} - -.animateSyncIcon #enable-sync svg path { - fill: #4E92DD; -} - -/* Feedback button on right side */ -#give-feedback-button { - cursor: pointer; - border-left: 1px solid #d7d7db; - display: flex; - flex: 0 0 40px; - display: flex; - justify-content: center; - align-items: center; -} - -.savingLayout #context-menu-button { - display: none; -} - -/* Display feedback button with opacity */ -.savingLayout #give-feedback-button { - opacity: 1; - z-index: 1; - visiblity: visible; - transition: opacity var(--sync-animation-duration) cubic-bezier(.07,.95,0,1) var(--sync-animation-duration); -} - -/* Hide Feedback button with opacity */ -.syncingLayout #give-feedback-button { - opacity: 0; - z-index: 0; - cursor: default; - visiblity: hidden; - transition: opacity var(--sync-animation-duration) cubic-bezier(.07,.95,0,1), visibity 0s var(--sync-animation-duration); -} - -#footer-buttons button, -#footer-buttons a { - transition: background 50ms; -} - -#footer-buttons button:hover, -#footer-buttons a:hover { - background-color: #ebebee; -} - -#footer-buttons button:active, -#footer-buttons a:active { - background-color: #d7d7db; -} - - -#menu { - position: absolute; - bottom: 10px; - right: 10px; -} - -#sync-note, -#migration-note { - display: none; - padding: 3px 0 5px 0; - background: #ffe900; - width: 100%; - position: relative; - margin: 0 auto; - border: 0; - border-top: 1px solid #ccc; -} - -#close-button, -#migration-close-button { - width: 16px; - position: absolute; - top: -6px; - right: 4px; - padding: 0 0; - opacity: 0.4; -} - -#close-button:hover, -#migration-close-button:hover { - cursor: pointer; - opacity: 0.6 -} - -#close-button:active, -#migration-close-button:active { - cursor: pointer; - opacity: 0.8 -} - -#sync-note-dialog, -#migration-note-dialog { - width: 85%; - font-size: 12px; - margin: 5px 0 5px 10px; - color: #424242; -} - -#sync-note.visible, -#migration-note.visible { - display: block; -} - - -/* Select menu from sync */ -#context-menu-button { - background-size: 3px; - background-image: url('three-dots.svg'); - background-position: 50%; - background-repeat: no-repeat; - width: 22px; - text-align: left; - position: absolute; - right: 6px; - top: 5px; - bottom: 6px; -} - -#context-menu-button:hover, -#context-menu-button:focus, -#context-menu-button:active { - background-color: rgba(128, 128, 128, 0.2) !important; -} - -/* Hide select menu on saving mode */ -.savingLayout #context-menu-button { - opacity: 0; - visibility: none; - z-index: 0; - transition: visibility 0s var(--sync-animation-duration), opacity var(--sync-animation-duration) cubic-bezier(.07,.95,0,1); -} - -/* Show select menu on syncing mode */ -.syncingLayout #context-menu-button { - opacity: 1; - visibility: visible; - z-index: 1; - transition: visibility 0s var(--sync-animation-duration), opacity var(--sync-animation-duration) cubic-bezier(.07,.95,0,1) var(--sync-animation-duration); -} - -.context-menu { - font-family: inherit; - padding: 0; -} - -.context-menu-item { - font-size: 12px; - padding: 0 0 0 15px; - height: 18px; - line-height: 30px; -} - -/** -CKEditor overrides - */ - -#notes .ck-editor__editable p { - margin: 5px 0; - font-size: 14px; -} - -#notes .ck-editor__editable h1 { - margin: 2px 0; - font-size: 24px; - padding: 0; -} - -#notes .ck-editor__editable h2 { - margin: 7px 0; - font-size: 20px; -} - -#notes .ck-editor__editable h3 { - margin: 5px 0; - font-size: 16px; -} - -#notes .drag-n-drop-focus, -#notes .drag-n-drop-focus .ck-editor__editable { - border: 1px solid #008000; - background-color: #dcd6da; -} - -.ck-heading-dropdown button { - width: 45px; - height: 25px; - padding: 2px 2px 2px 5px!important; -} - -.ck-heading-dropdown .ck-dropdown__panel { - width: auto; - min-width: 70px; -} - -#notes .ck-toolbar > * { - margin-right: 0.2em; -} - -#notes .ck-button { - padding: 0.2em; - margin: 0.2em; -} - -#notes .ck-button__label { - max-width: 30px; -} - -.ck-balloon-panel { - left: 0!important; - width:100%!important; - padding: 0.1em; - box-shadow: none; -} diff --git a/src/sync.js b/src/sync.js index 846e37110..5d42d5625 100644 --- a/src/sync.js +++ b/src/sync.js @@ -1,11 +1,5 @@ -/* exported BrowserStorageCredentials */ -/* exported disconnectFromKinto */ -/* exported loadFromKinto */ -/* exported saveToKinto */ - let syncDebounce = null; - const cryptographer = new Jose.WebCryptographer(); cryptographer.setKeyEncryptionAlgorithm('A256KW'); cryptographer.setContentEncryptionAlgorithm('A256GCM'); @@ -139,7 +133,7 @@ class Credentials { } } -class BrowserStorageCredentials extends Credentials { +class BrowserStorageCredentials extends Credentials { // eslint-disable-line no-unused-vars constructor(storage) { super(); this.storage = storage; @@ -209,7 +203,7 @@ function syncKinto(client, credentials) { } else { const mergeWarning = browser.i18n.getMessage('mergeWarning'); let totalOps = conflict.remote.content; - totalOps += `\n${mergeWarning}\n\n`; + totalOps += `${mergeWarning}
`; totalOps += conflict.local.content; client.conflict = true; @@ -290,7 +284,7 @@ function retrieveNote(client) { * last_modified: the timestamp of the sync, or null * } */ -function loadFromKinto(client, credentials) { +function loadFromKinto(client, credentials) { // eslint-disable-line no-unused-vars return syncKinto(client, credentials) // Ignore failure of syncKinto by retrieving note even when promise rejected .then(() => retrieveNote(client), () => retrieveNote(client)) @@ -303,7 +297,7 @@ function loadFromKinto(client, credentials) { }); } -function saveToKinto(client, credentials, content) { +function saveToKinto(client, credentials, content) { // eslint-disable-line no-unused-vars let resolve; const promise = new Promise(thisResolve => { resolve = thisResolve; @@ -347,9 +341,10 @@ function saveToKinto(client, credentials, content) { return promise; } -function disconnectFromKinto(client) { +function disconnectFromKinto(client) { // eslint-disable-line no-unused-vars const notes = client.collection('notes', { idSchema: notesIdSchema, }); return notes.resetSyncStatus(); } + diff --git a/test/unit/index.js b/test/unit/index.js index ddce560c4..702363b8a 100644 --- a/test/unit/index.js +++ b/test/unit/index.js @@ -1,4 +1,5 @@ browser = require('sinon-chrome/webextensions/index'); +var sinon = require('sinon'); // Provide something static that the background script can load // without choking. browser.storage.local.get.resolves({}); @@ -14,7 +15,12 @@ browser.runtime = { onConnect: { addListener: function () {} }, - sendMessage: function () { + sendMessage: sinon.spy(function () { + }) +}; +browser.i18n = { + getMessage: function () { + return 'localized string'; } }; diff --git a/test/unit/main.test.js b/test/unit/main.test.js index 9e12268b2..4a79c291b 100644 --- a/test/unit/main.test.js +++ b/test/unit/main.test.js @@ -32,6 +32,7 @@ describe('Authorization', function() { afterEach(function() { sandbox.verifyAndRestore(); browser.flush(); + browser.runtime.sendMessage.reset(); }); it('should define syncKinto', function() { @@ -139,7 +140,7 @@ describe('Authorization', function() { decryptMock = sandbox.stub(global, 'decrypt'); decryptMock.withArgs(staticCredential.key, "encrypted content").resolves({ id: "singleNote", - content: {ops: [{insert: "Hi there"}]}, + content: "Hi there
", }); // sync() tries to gather local changes, even when a conflict @@ -196,23 +197,15 @@ describe('Authorization', function() { }); decryptMock.withArgs(staticCredential.key, "encrypted resolution").resolves({ id: "singleNote", - content: {ops: [{insert: "Resolution"}]}, + content: "Resolution
", }); - return collection.upsert({id: "singleNote", content: {ops: [{insert: "Local"}]}}) + return collection.upsert({id: "singleNote", content: "Local
"}) .then(() => syncKinto(client, credentials)) .then(() => collection.getAny('singleNote')) .then(result => { - chai.expect(result.data.content).eql( - {ops: [ - {insert: "Resolution"} - ]}); - const expectedContent = { - ops: [ - {insert: "Hi there"}, - {insert: "\n====== On this computer: ======\n\n"}, - {insert: "Local"}, - ]}; + chai.expect(result.data.content).eql("Resolution
"); + const expectedContent = "Hi there
localized string
Local
"; const expectedResolution = { id: "singleNote", content: expectedContent, @@ -279,10 +272,7 @@ describe('Authorization', function() { .then(() => collection.getAny('singleNote')) .then(result => { chai.expect(result.data._status).eql("synced"); - chai.expect(result.data.content).eql({ - ops: [ - {insert: "Hi there"} - ]}); + chai.expect(result.data.content).eql("Hi there
"); // This sync will try to retrieve the record after 1234, // which has an older kid. @@ -369,7 +359,9 @@ describe('Authorization', function() { chai.expect(browser.runtime.sendMessage.getCall(2).args[0]).eql('notes@mozilla.com'); chai.expect(browser.runtime.sendMessage.getCall(2).args[1]).eql({ action: 'text-synced', - last_modified: 'abc', + content: "def", + last_modified: "abc", + conflict: false }); }); }); diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 000000000..875ef5ef2 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,66 @@ +'use strict'; + +/* eslint-env node */ + +const path = require('path'); +const CleanWebpackPlugin = require('clean-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); + +module.exports = { + devtool: 'source-map', + + entry: [ + path.resolve(__dirname, 'src', 'sidebar', 'app', 'app.js'), + ], + + output: { + // build to the extension src vendor directory + path: path.resolve(__dirname, 'build'), + filename: path.join('sidebar', 'app.js'), + }, + + plugins: [ + // cleanup build folder + new CleanWebpackPlugin([ + 'build' + ]), + // Moves files + new CopyWebpackPlugin([ + { from: path.join('src') } + ], { + ignore: [ + path.join('sidebar', 'app', '**', '*'), + path.join('sidebar', 'static', 'scss', '**', '*') + ], + }), + ], + + module: { + rules: [ + { + test: /\.js$/, // Babel-loader compile jsx syntax to javascript + exclude: /node_modules/, + use: ['babel-loader?presets[]=env&presets[]=react&presets[]=stage-2&sourceMaps=true'], + }, + { + test: /\.scss$/, + use: [{ + loader: 'style-loader' // creates style nodes from JS strings + }, { + loader: 'css-loader' // translates CSS into CommonJS + }, { + loader: 'sass-loader' // compiles Sass to CSS + }] + }, + { + test: /\.(jpe?g|png|gif|svg|eot|woff|ttf|svg|woff2)$/, + options: { + name: '[path][name].[ext]', + context: 'src', + publicPath: '../' + }, + loader: 'file-loader' + } + ] + } +};